Commit 329b334b by Sébastien Eustace

Merge branch 'master' into develop

parents 55668d76 f597d1fc
...@@ -25,7 +25,7 @@ pip-log.txt ...@@ -25,7 +25,7 @@ pip-log.txt
/test.py /test.py
/test_*.* /test_*.*
setup.cfg /setup.cfg
MANIFEST.in MANIFEST.in
/setup.py /setup.py
/docs/site/* /docs/site/*
......
...@@ -11,6 +11,55 @@ ...@@ -11,6 +11,55 @@
- Fixed transitive extra dependencies being removed when updating a specific dependency. - Fixed transitive extra dependencies being removed when updating a specific dependency.
## [0.12.8] - 2018-11-13
### Fixed
- Fixed permission errors when adding/removing git dependencies on Windows.
- Fixed `Pool` not raising an exception when no package could be found.
- Fixed reading `bz2` source distribution.
- Fixed handling of arbitrary equals in `InstalledRepository`.
## [0.12.7] - 2018-11-08
### Fixed
- Fixed reading of some `setup.py` files.
- Fixed a `KeyError` when getting information for packages which require reading setup files.
- Fixed the building of wheels with C extensions and an `src` layout.
- Fixed extras being selected when resolving dependencies even when not required.
- Fixed performance issues when packaging projects if a lot of files were excluded.
- Fixed installation of files.
- Fixed extras not being retrieved for legacy repositories.
- Fixed invalid transitive constraints raising an error for legacy repositories.
## [0.12.6] - 2018-11-05
### Changed
- Poetry will now try to read, without executing, setup files (`setup.py` and/or `setup.cfg`) if the `egg_info` command fails when resolving dependencies.
### Fixed
- Fixed installation of directory dependencies.
- Fixed handling of dependencies with a `not in` marker operator.
- Fixed support for VCS dependencies.
- Fixed the `exclude` property not being respected if no VCS was available.
## [0.12.5] - 2018-10-26
### Fixed
- Fixed installation of Poetry git dependencies with a build system.
- Fixed possible errors when resolving dependencies for specific packages.
- Fixed handling of Python versions compatibility.
- Fixed the dependency resolver picking up unnecessary dependencies due to not using the `python_full_version` marker.
- Fixed the `Python-Requires` metadata being invalid for single Python versions.
## [0.12.4] - 2018-10-21 ## [0.12.4] - 2018-10-21
### Fixed ### Fixed
...@@ -548,7 +597,11 @@ Initial release ...@@ -548,7 +597,11 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.4...develop [Unreleased]: https://github.com/sdispater/poetry/compare/0.12.8...master
[0.12.8]: https://github.com/sdispater/poetry/releases/tag/0.12.8
[0.12.7]: https://github.com/sdispater/poetry/releases/tag/0.12.7
[0.12.6]: https://github.com/sdispater/poetry/releases/tag/0.12.6
[0.12.5]: https://github.com/sdispater/poetry/releases/tag/0.12.5
[0.12.4]: https://github.com/sdispater/poetry/releases/tag/0.12.4 [0.12.4]: https://github.com/sdispater/poetry/releases/tag/0.12.4
[0.12.3]: https://github.com/sdispater/poetry/releases/tag/0.12.3 [0.12.3]: https://github.com/sdispater/poetry/releases/tag/0.12.3
[0.12.2]: https://github.com/sdispater/poetry/releases/tag/0.12.2 [0.12.2]: https://github.com/sdispater/poetry/releases/tag/0.12.2
......
...@@ -153,7 +153,7 @@ There are some things we can notice here: ...@@ -153,7 +153,7 @@ There are some things we can notice here:
`poetry` will also detect if you are inside a virtualenv and install the packages accordingly. `poetry` will also detect if you are inside a virtualenv and install the packages accordingly.
So, `poetry` can be installed globally and used everywhere. So, `poetry` can be installed globally and used everywhere.
`poetry` also comes with a full fledged dependency resolution library, inspired by [Molinillo](https://github.com/CocoaPods/Molinillo). `poetry` also comes with a full fledged dependency resolution library.
## Why? ## Why?
......
...@@ -35,18 +35,26 @@ The `^` operator works very well with libraries following [semantic versioning]( ...@@ -35,18 +35,26 @@ The `^` operator works very well with libraries following [semantic versioning](
## Is tox supported? ## Is tox supported?
For now, you can use Poetry with [tox](https://tox.readthedocs.io/en/latest/) by using something similar to what is done in the [Pendulum](https://github.com/sdispater/pendulum/blob/master/tox.ini) package. Yes. By using the [isolated builds](https://tox.readthedocs.io/en/latest/config.html#conf-isolated_build) `tox` provides,
you can use it in combination with the PEP 517 compliant build system provided by Poetry.
Minimal viable `tox.ini` configuration file looks like this: So, in your `pyproject.toml` file add this section if does not already exists:
```toml
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
```
And use a `tox.ini` configuration file similar to this:
```INI ```INI
[tox] [tox]
skipsdist = True isolated_build = true
envlist = py27, py36 envlist = py27, py36
[testenv] [testenv]
whitelist_externals = poetry whitelist_externals = poetry
skip_install = true
commands = commands =
poetry install -v poetry install -v
poetry run pytest tests/ poetry run pytest tests/
......
...@@ -412,7 +412,7 @@ class Installer: ...@@ -412,7 +412,7 @@ class Installer:
def customize_install(self): def customize_install(self):
if not self._accept_all: if not self._accept_all:
print("Before we start, please answer the following questions.") print("Before we start, please answer the following questions.")
print("You may simple press the Enter key to keave unchanged.") print("You may simply press the Enter key to leave unchanged.")
modify_path = input("Modify PATH variable? ([y]/n) ") or "y" modify_path = input("Modify PATH variable? ([y]/n) ") or "y"
if modify_path.lower() in {"n", "no"}: if modify_path.lower() in {"n", "no"}:
...@@ -434,17 +434,6 @@ class Installer: ...@@ -434,17 +434,6 @@ class Installer:
return True return True
def customize_install(self):
if not self._accept_all:
print("Before we start, please answer the following questions.")
print("You may simple press the Enter key to keave unchanged.")
modify_path = input("Modify PATH variable? ([y]/n) ") or "y"
if modify_path.lower() in {"n", "no"}:
self._modify_path = False
print("")
def ensure_home(self): def ensure_home(self):
""" """
Ensures that $POETRY_HOME exists or create it. Ensures that $POETRY_HOME exists or create it.
...@@ -701,7 +690,34 @@ class Installer: ...@@ -701,7 +690,34 @@ class Installer:
self.set_windows_path_var(path) self.set_windows_path_var(path)
def remove_from_unix_path(self): def remove_from_unix_path(self):
pass # Updating any profile we can on UNIX systems
export_string = self.get_export_string()
addition = "{}\n".format(export_string)
profiles = self.get_unix_profiles()
for profile in profiles:
if not os.path.exists(profile):
continue
with open(profile, "r") as f:
content = f.readlines()
if addition not in content:
continue
new_content = []
for line in content:
if line == addition:
if new_content and not new_content[-1].strip():
new_content = new_content[:-1]
continue
new_content.append(line)
with open(profile, "w") as f:
f.writelines(new_content)
def get_export_string(self): def get_export_string(self):
path = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") path = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
......
__version__ = "0.12.4" __version__ = "0.12.8"
...@@ -31,19 +31,22 @@ class DebugInfoCommand(Command): ...@@ -31,19 +31,22 @@ class DebugInfoCommand(Command):
env_python_version = ".".join(str(s) for s in env.version_info[:3]) env_python_version = ".".join(str(s) for s in env.version_info[:3])
self.output.title("Virtualenv") self.output.title("Virtualenv")
self.output.listing( listing = [
[ "<info>Python</info>: <comment>{}</>".format(env_python_version),
"<info>Python</info>: <comment>{}</>".format( "<info>Implementation</info>: <comment>{}</>".format(
env_python_version env.python_implementation
), ),
"<info>Implementation</info>: <comment>{}</>".format( "<info>Path</info>: <comment>{}</>".format(
env.python_implementation env.path if env.is_venv() else "NA"
), ),
"<info>Path</info>: <comment>{}</>".format( ]
env.path if env.is_venv() else "NA" if env.is_venv():
), listing.append(
] "<info>Valid</info>: <{tag}>{is_valid}</{tag}>".format(
) tag="comment" if env.is_sane() else "error", is_valid=env.is_sane()
)
)
self.output.listing(listing)
self.line("") self.line("")
......
...@@ -8,10 +8,27 @@ class EnvCommand(Command): ...@@ -8,10 +8,27 @@ class EnvCommand(Command):
super(EnvCommand, self).__init__() super(EnvCommand, self).__init__()
def initialize(self, i, o): def initialize(self, i, o):
from poetry.semver import parse_constraint
from poetry.utils.env import Env from poetry.utils.env import Env
super(EnvCommand, self).initialize(i, o) super(EnvCommand, self).initialize(i, o)
# Checking compatibility of the current environment with
# the python dependency specified in pyproject.toml
current_env = Env.get()
supported_python = self.poetry.package.python_constraint
current_python = parse_constraint(
".".join(str(v) for v in current_env.version_info[:3])
)
if not supported_python.allows(current_python):
raise RuntimeError(
"The current Python version ({}) is not supported by the project ({})\n"
"Please activate a compatible Python version.".format(
current_python, self.poetry.package.python_versions
)
)
self._env = Env.create_venv( self._env = Env.create_venv(
o, self.poetry.package.name, cwd=self.poetry.file.parent o, self.poetry.package.name, cwd=self.poetry.file.parent
) )
......
...@@ -37,6 +37,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -37,6 +37,7 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
def handle(self): def handle(self):
from poetry.layouts import layout from poetry.layouts import layout
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import Env
from poetry.vcs.git import GitConfig from poetry.vcs.git import GitConfig
if (Path.cwd() / "pyproject.toml").exists(): if (Path.cwd() / "pyproject.toml").exists():
...@@ -101,7 +102,16 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in ...@@ -101,7 +102,16 @@ The <info>init</info> command creates a basic <comment>pyproject.toml</> file in
question.validator = self._validate_license question.validator = self._validate_license
license = self.ask(question) license = self.ask(question)
question = self.create_question("Compatible Python versions [*]: ", default="*") current_env = Env.get()
default_python = "^{}".format(
".".join(str(v) for v in current_env.version_info[:2])
)
question = self.create_question(
"Compatible Python versions [<comment>{}</comment>]: ".format(
default_python
),
default=default_python,
)
python = self.ask(question) python = self.ask(question)
self.line("") self.line("")
......
...@@ -14,6 +14,7 @@ class NewCommand(Command): ...@@ -14,6 +14,7 @@ class NewCommand(Command):
def handle(self): def handle(self):
from poetry.layouts import layout from poetry.layouts import layout
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import Env
from poetry.vcs.git import GitConfig from poetry.vcs.git import GitConfig
if self.option("src"): if self.option("src"):
...@@ -44,7 +45,17 @@ class NewCommand(Command): ...@@ -44,7 +45,17 @@ class NewCommand(Command):
if author_email: if author_email:
author += " <{}>".format(author_email) author += " <{}>".format(author_email)
layout_ = layout_(name, "0.1.0", author=author, readme_format=readme_format) current_env = Env.get()
default_python = "^{}".format(
".".join(str(v) for v in current_env.version_info[:2])
)
layout_ = layout_(
name,
"0.1.0",
author=author,
readme_format=readme_format,
python=default_python,
)
layout_.create(path) layout_.create(path)
self.line( self.line(
......
...@@ -24,12 +24,8 @@ lists all packages available.""" ...@@ -24,12 +24,8 @@ lists all packages available."""
colors = ["green", "yellow", "cyan", "magenta", "blue"] colors = ["green", "yellow", "cyan", "magenta", "blue"]
def handle(self): def handle(self):
from poetry.packages.constraints import (
parse_constraint as parse_generic_constraint,
)
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.semver import Version from poetry.semver import Version
from poetry.semver import parse_constraint
package = self.argument("package") package = self.argument("package")
...@@ -172,7 +168,7 @@ lists all packages available.""" ...@@ -172,7 +168,7 @@ lists all packages available."""
color = "yellow" color = "yellow"
line += " <fg={}>{:{}}</>".format( line += " <fg={}>{:{}}</>".format(
color, latest.pretty_version, latest_length color, latest.full_pretty_version, latest_length
) )
if self.option("outdated") and update_status == "up-to-date": if self.option("outdated") and update_status == "up-to-date":
continue continue
...@@ -291,11 +287,17 @@ lists all packages available.""" ...@@ -291,11 +287,17 @@ lists all packages available."""
self.set_style(color, color) self.set_style(color, color)
def find_latest_package(self, package): def find_latest_package(self, package):
from poetry.io import NullIO
from poetry.puzzle.provider import Provider
from poetry.version.version_selector import VersionSelector from poetry.version.version_selector import VersionSelector
# find the latest version allowed in this pool # find the latest version allowed in this pool
if package.source_type == "git": if package.source_type == "git":
return for dep in self.poetry.package.requires:
if dep.name == package.name and dep.is_vcs():
return Provider(
self.poetry.package, self.poetry.pool, NullIO()
).search_for_vcs(dep)[0]
name = package.name name = package.name
selector = VersionSelector(self.poetry.pool) selector = VersionSelector(self.poetry.pool)
......
import os import os
import shutil
import tempfile import tempfile
from subprocess import CalledProcessError from subprocess import CalledProcessError
from poetry.config import Config from poetry.config import Config
from poetry.utils.helpers import get_http_basic_auth from poetry.utils.helpers import get_http_basic_auth
from poetry.utils.helpers import safe_rmtree
try: try:
...@@ -25,7 +27,7 @@ class PipInstaller(BaseInstaller): ...@@ -25,7 +27,7 @@ class PipInstaller(BaseInstaller):
def install(self, package, update=False): def install(self, package, update=False):
if package.source_type == "directory": if package.source_type == "directory":
self.install_directory(package, update=update) self.install_directory(package)
return return
...@@ -92,6 +94,12 @@ class PipInstaller(BaseInstaller): ...@@ -92,6 +94,12 @@ class PipInstaller(BaseInstaller):
self.install(target, update=True) self.install(target, update=True)
def remove(self, package): def remove(self, package):
# If we have a VCS package, remove its source directory
if package.source_type == "git":
src_dir = self._env.path / "src" / package.name
if src_dir.exists():
safe_rmtree(str(src_dir))
try: try:
self.run("uninstall", package.name, "-y") self.run("uninstall", package.name, "-y")
except CalledProcessError as e: except CalledProcessError as e:
...@@ -119,7 +127,7 @@ class PipInstaller(BaseInstaller): ...@@ -119,7 +127,7 @@ class PipInstaller(BaseInstaller):
else: else:
req = os.path.realpath(package.source_url) req = os.path.realpath(package.source_url)
if package.develop: if package.develop and package.source_type == "directory":
req = ["-e", req] req = ["-e", req]
return req return req
...@@ -143,7 +151,7 @@ class PipInstaller(BaseInstaller): ...@@ -143,7 +151,7 @@ class PipInstaller(BaseInstaller):
return name return name
def install_directory(self, package, update=False): def install_directory(self, package):
from poetry.io import NullIO from poetry.io import NullIO
from poetry.masonry.builder import SdistBuilder from poetry.masonry.builder import SdistBuilder
from poetry.poetry import Poetry from poetry.poetry import Poetry
...@@ -161,12 +169,16 @@ class PipInstaller(BaseInstaller): ...@@ -161,12 +169,16 @@ class PipInstaller(BaseInstaller):
pyproject = TomlFile(os.path.join(req, "pyproject.toml")) pyproject = TomlFile(os.path.join(req, "pyproject.toml"))
has_poetry = False has_poetry = False
has_build_system = False
if pyproject.exists(): if pyproject.exists():
pyproject_content = pyproject.read() pyproject_content = pyproject.read()
has_poetry = ( has_poetry = (
"tool" in pyproject_content and "poetry" in pyproject_content["tool"] "tool" in pyproject_content and "poetry" in pyproject_content["tool"]
) )
has_build_system = "build-system" in pyproject_content # Even if there is a build system specified
# pip as of right now does not support it fully
# TODO: Check for pip version when proper PEP-517 support lands
# has_build_system = ("build-system" in pyproject_content)
setup = os.path.join(req, "setup.py") setup = os.path.join(req, "setup.py")
has_setup = os.path.exists(setup) has_setup = os.path.exists(setup)
...@@ -193,21 +205,22 @@ class PipInstaller(BaseInstaller): ...@@ -193,21 +205,22 @@ class PipInstaller(BaseInstaller):
def install_git(self, package): def install_git(self, package):
from poetry.packages import Package from poetry.packages import Package
from poetry.utils._compat import Path
from poetry.utils.helpers import temporary_directory
from poetry.vcs import Git from poetry.vcs import Git
with temporary_directory() as tmp_dir: src_dir = self._env.path / "src" / package.name
tmp_dir = Path(tmp_dir) if src_dir.exists():
safe_rmtree(str(src_dir))
src_dir.parent.mkdir(exist_ok=True)
git = Git() git = Git()
git.clone(package.source_url, tmp_dir) git.clone(package.source_url, src_dir)
git.checkout(package.source_reference, tmp_dir) git.checkout(package.source_reference, src_dir)
# Now we just need to install from the temporary directory # Now we just need to install from the source directory
pkg = Package(package.name, package.version) pkg = Package(package.name, package.version)
pkg.source_type = "directory" pkg.source_type = "directory"
pkg.source_url = str(tmp_dir) pkg.source_url = str(src_dir)
pkg.develop = False pkg.develop = True
self.install_directory(pkg) self.install_directory(pkg)
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
import re import re
import shutil import shutil
import tempfile import tempfile
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from typing import Set
from typing import Union
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils._compat import lru_cache
from poetry.vcs import get_vcs from poetry.vcs import get_vcs
from ..metadata import Metadata from ..metadata import Metadata
...@@ -39,35 +42,40 @@ class Builder(object): ...@@ -39,35 +42,40 @@ class Builder(object):
def build(self): def build(self):
raise NotImplementedError() raise NotImplementedError()
def find_excluded_files(self): # type: () -> list @lru_cache(maxsize=None)
def find_excluded_files(self): # type: () -> Set[str]
# Checking VCS # Checking VCS
vcs = get_vcs(self._path) vcs = get_vcs(self._path)
if not vcs: if not vcs:
return [] vcs_ignored_files = set()
else:
vcs_ignored_files = set(vcs.get_ignored_files())
explicitely_excluded = [] explicitely_excluded = set()
for excluded_glob in self._package.exclude: for excluded_glob in self._package.exclude:
for excluded in self._path.glob(excluded_glob): for excluded in self._path.glob(excluded_glob):
explicitely_excluded.append(excluded) explicitely_excluded.add(excluded.relative_to(self._path).as_posix())
ignored = vcs.get_ignored_files() + explicitely_excluded ignored = vcs_ignored_files | explicitely_excluded
result = [] result = set()
for file in ignored: for file in ignored:
try: result.add(file)
file = Path(file).absolute().relative_to(self._path)
except ValueError:
# Should only happen in tests
continue
result.append(file)
# The list of excluded files might be big and we will do a lot
# containment check (x in excluded).
# Returning a set make those tests much much faster.
return result return result
def find_files_to_add(self, exclude_build=True): # type: () -> list def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool
if not isinstance(filepath, basestring):
filepath = filepath.as_posix()
return filepath in self.find_excluded_files()
def find_files_to_add(self, exclude_build=True): # type: (bool) -> list
""" """
Finds all files to add to the tarball Finds all files to add to the tarball
""" """
excluded = self.find_excluded_files()
to_add = [] to_add = []
for include in self._module.includes: for include in self._module.includes:
...@@ -80,7 +88,7 @@ class Builder(object): ...@@ -80,7 +88,7 @@ class Builder(object):
file = file.relative_to(self._path) file = file.relative_to(self._path)
if file in excluded and isinstance(include, PackageInclude): if self.is_excluded(file) and isinstance(include, PackageInclude):
continue continue
if file.suffix == ".pyc": if file.suffix == ".pyc":
......
...@@ -278,7 +278,7 @@ class SdistBuilder(Builder): ...@@ -278,7 +278,7 @@ class SdistBuilder(Builder):
if not f.is_dir() if not f.is_dir()
] ]
data = [e for e in data_elements if e not in excluded_files] data = [e for e in data_elements if not self.is_excluded(e)]
if not data: if not data:
continue continue
......
...@@ -11,10 +11,10 @@ import zipfile ...@@ -11,10 +11,10 @@ import zipfile
from base64 import urlsafe_b64encode from base64 import urlsafe_b64encode
from io import StringIO from io import StringIO
from typing import Set
from poetry.__version__ import __version__ from poetry.__version__ import __version__
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from poetry.utils._compat import Path
from ..utils.helpers import normalize_file_permissions from ..utils.helpers import normalize_file_permissions
from ..utils.package_include import PackageInclude from ..utils.package_include import PackageInclude
...@@ -67,8 +67,8 @@ class WheelBuilder(Builder): ...@@ -67,8 +67,8 @@ class WheelBuilder(Builder):
with zipfile.ZipFile( with zipfile.ZipFile(
os.fdopen(fd, "w+b"), mode="w", compression=zipfile.ZIP_DEFLATED os.fdopen(fd, "w+b"), mode="w", compression=zipfile.ZIP_DEFLATED
) as zip_file: ) as zip_file:
self._build()
self._copy_module(zip_file) self._copy_module(zip_file)
self._build(zip_file)
self._write_metadata(zip_file) self._write_metadata(zip_file)
self._write_record(zip_file) self._write_record(zip_file)
...@@ -79,7 +79,7 @@ class WheelBuilder(Builder): ...@@ -79,7 +79,7 @@ class WheelBuilder(Builder):
self._io.writeln(" - Built <fg=cyan>{}</>".format(self.wheel_filename)) self._io.writeln(" - Built <fg=cyan>{}</>".format(self.wheel_filename))
def _build(self): def _build(self, wheel):
if self._package.build: if self._package.build:
setup = self._path / "setup.py" setup = self._path / "setup.py"
...@@ -103,9 +103,22 @@ class WheelBuilder(Builder): ...@@ -103,9 +103,22 @@ class WheelBuilder(Builder):
return return
lib = lib[0] lib = lib[0]
for pkg in lib.glob("*"): excluded = self.find_excluded_files()
shutil.rmtree(str(self._path / pkg.name)) for pkg in lib.glob("**/*"):
shutil.copytree(str(pkg), str(self._path / pkg.name)) if pkg.is_dir() or pkg in excluded:
continue
rel_path = str(pkg.relative_to(lib))
if rel_path in wheel.namelist():
continue
self._io.writeln(
" - Adding: <comment>{}</comment>".format(rel_path),
verbosity=self._io.VERBOSITY_VERY_VERBOSE,
)
self._add_file(wheel, pkg, rel_path)
def _copy_module(self, wheel): def _copy_module(self, wheel):
excluded = self.find_excluded_files() excluded = self.find_excluded_files()
...@@ -173,9 +186,9 @@ class WheelBuilder(Builder): ...@@ -173,9 +186,9 @@ class WheelBuilder(Builder):
# RECORD itself is recorded with no hash or size # RECORD itself is recorded with no hash or size
f.write(self.dist_info + "/RECORD,,\n") f.write(self.dist_info + "/RECORD,,\n")
def find_excluded_files(self): # type: () -> list def find_excluded_files(self): # type: () -> Set
# Checking VCS # Checking VCS
return [] return set()
@property @property
def dist_info(self): # type: () -> str def dist_info(self): # type: () -> str
......
...@@ -39,7 +39,7 @@ class _Writer: ...@@ -39,7 +39,7 @@ class _Writer:
if required_python_version is not None: if required_python_version is not None:
buffer.append( buffer.append(
"The current supported Python versions are {}".format( "The current project must support the following Python versions: {}".format(
required_python_version required_python_version
) )
) )
......
...@@ -105,20 +105,21 @@ def dependency_from_pep_508(name): ...@@ -105,20 +105,21 @@ def dependency_from_pep_508(name):
op = "" op = ""
elif op == "!=": elif op == "!=":
version += ".*" version += ".*"
elif op == "in": elif op in ("in", "not in"):
versions = [] versions = []
for v in re.split("[ ,]+", version): for v in re.split("[ ,]+", version):
split = v.split(".") split = v.split(".")
if len(split) in [1, 2]: if len(split) in [1, 2]:
split.append("*") split.append("*")
op = "" op_ = "" if op == "in" else "!="
else: else:
op = "==" op_ = "==" if op == "in" else "!="
versions.append(op + ".".join(split)) versions.append(op_ + ".".join(split))
glue = " || " if op == "in" else ", "
if versions: if versions:
ands.append(" || ".join(versions)) ands.append(glue.join(versions))
continue continue
......
import os
import pkginfo
from pkginfo.distribution import HEADER_ATTRS from pkginfo.distribution import HEADER_ATTRS
from pkginfo.distribution import HEADER_ATTRS_2_0 from pkginfo.distribution import HEADER_ATTRS_2_0
from poetry.io import NullIO
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import decode
from poetry.utils.helpers import parse_requires
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
from poetry.utils.env import Env
from .dependency import Dependency from .dependency import Dependency
...@@ -22,15 +15,13 @@ HEADER_ATTRS.update( ...@@ -22,15 +15,13 @@ HEADER_ATTRS.update(
class DirectoryDependency(Dependency): class DirectoryDependency(Dependency):
def __init__( def __init__(
self, self,
name,
path, # type: Path path, # type: Path
category="main", # type: str category="main", # type: str
optional=False, # type: bool optional=False, # type: bool
base=None, # type: Path base=None, # type: Path
develop=True, # type: bool develop=True, # type: bool
): ):
from . import dependency_from_pep_508
from .package import Package
self._path = path self._path = path
self._base = base self._base = base
self._full_path = path self._full_path = path
...@@ -46,7 +37,7 @@ class DirectoryDependency(Dependency): ...@@ -46,7 +37,7 @@ class DirectoryDependency(Dependency):
if self._full_path.is_file(): if self._full_path.is_file():
raise ValueError("{} is a file, expected a directory".format(self._path)) raise ValueError("{} is a file, expected a directory".format(self._path))
# Checking content to dertermine actions # Checking content to determine actions
setup = self._full_path / "setup.py" setup = self._full_path / "setup.py"
pyproject = TomlFile(self._full_path / "pyproject.toml") pyproject = TomlFile(self._full_path / "pyproject.toml")
if pyproject.exists(): if pyproject.exists():
...@@ -62,67 +53,8 @@ class DirectoryDependency(Dependency): ...@@ -62,67 +53,8 @@ class DirectoryDependency(Dependency):
) )
) )
if self._supports_poetry:
from poetry.poetry import Poetry
poetry = Poetry.create(self._full_path)
package = poetry.package
self._package = Package(package.pretty_name, package.version)
self._package.requires += package.requires
self._package.dev_requires += package.dev_requires
self._package.extras = package.extras
self._package.python_versions = package.python_versions
else:
# Execute egg_info
current_dir = os.getcwd()
os.chdir(str(self._full_path))
try:
cwd = base
venv = Env.create_venv(NullIO(), cwd=cwd)
venv.run("python", "setup.py", "egg_info")
finally:
os.chdir(current_dir)
egg_info = list(self._full_path.glob("*.egg-info"))[0]
meta = pkginfo.UnpackedSDist(str(egg_info))
if meta.requires_dist:
reqs = list(meta.requires_dist)
else:
reqs = []
requires = egg_info / "requires.txt"
if requires.exists():
with requires.open() as f:
reqs = parse_requires(f.read())
package = Package(meta.name, meta.version)
package.description = meta.summary
for req in reqs:
package.requires.append(dependency_from_pep_508(req))
if meta.requires_python:
package.python_versions = meta.requires_python
if meta.platforms:
platforms = [p for p in meta.platforms if p.lower() != "unknown"]
if platforms:
package.platform = " || ".join(platforms)
self._package = package
self._package.source_type = "directory"
self._package.source_url = self._path.as_posix()
super(DirectoryDependency, self).__init__( super(DirectoryDependency, self).__init__(
self._package.name, name, "*", category=category, optional=optional, allows_prereleases=True
self._package.version,
category=category,
optional=optional,
allows_prereleases=True,
) )
@property @property
...@@ -134,8 +66,8 @@ class DirectoryDependency(Dependency): ...@@ -134,8 +66,8 @@ class DirectoryDependency(Dependency):
return self._full_path.resolve() return self._full_path.resolve()
@property @property
def package(self): def base(self):
return self._package return self._base
@property @property
def develop(self): def develop(self):
......
import hashlib import hashlib
import io import io
import pkginfo
from pkginfo.distribution import HEADER_ATTRS from pkginfo.distribution import HEADER_ATTRS
from pkginfo.distribution import HEADER_ATTRS_2_0 from pkginfo.distribution import HEADER_ATTRS_2_0
...@@ -19,6 +17,7 @@ HEADER_ATTRS.update( ...@@ -19,6 +17,7 @@ HEADER_ATTRS.update(
class FileDependency(Dependency): class FileDependency(Dependency):
def __init__( def __init__(
self, self,
name,
path, # type: Path path, # type: Path
category="main", # type: str category="main", # type: str
optional=False, # type: bool optional=False, # type: bool
...@@ -37,18 +36,8 @@ class FileDependency(Dependency): ...@@ -37,18 +36,8 @@ class FileDependency(Dependency):
if self._full_path.is_dir(): if self._full_path.is_dir():
raise ValueError("{} is a directory, expected a file".format(self._path)) raise ValueError("{} is a directory, expected a file".format(self._path))
if self._path.suffix == ".whl":
self._meta = pkginfo.Wheel(str(self._full_path))
else:
# Assume sdist
self._meta = pkginfo.SDist(str(self._full_path))
super(FileDependency, self).__init__( super(FileDependency, self).__init__(
self._meta.name, name, "*", category=category, optional=optional, allows_prereleases=True
self._meta.version,
category=category,
optional=optional,
allows_prereleases=True,
) )
@property @property
...@@ -59,10 +48,6 @@ class FileDependency(Dependency): ...@@ -59,10 +48,6 @@ class FileDependency(Dependency):
def full_path(self): def full_path(self):
return self._full_path.resolve() return self._full_path.resolve()
@property
def metadata(self):
return self._meta
def is_file(self): def is_file(self):
return True return True
......
...@@ -73,6 +73,7 @@ class Package(object): ...@@ -73,6 +73,7 @@ class Package(object):
self._python_constraint = parse_constraint("*") self._python_constraint = parse_constraint("*")
self._python_marker = AnyMarker() self._python_marker = AnyMarker()
self.platform = None
self.marker = AnyMarker() self.marker = AnyMarker()
self.root_dir = None self.root_dir = None
...@@ -168,19 +169,6 @@ class Package(object): ...@@ -168,19 +169,6 @@ class Package(object):
return self._python_marker return self._python_marker
@property @property
def platform(self): # type: () -> str
return self._platform
@platform.setter
def platform(self, value): # type: (str) -> None
self._platform = value
self._platform_constraint = parse_generic_constraint(value)
@property
def platform_constraint(self):
return self._platform_constraint
@property
def license(self): def license(self):
return self._license return self._license
...@@ -273,7 +261,7 @@ class Package(object): ...@@ -273,7 +261,7 @@ class Package(object):
file_path = Path(constraint["file"]) file_path = Path(constraint["file"])
dependency = FileDependency( dependency = FileDependency(
file_path, category=category, base=self.root_dir name, file_path, category=category, base=self.root_dir
) )
elif "path" in constraint: elif "path" in constraint:
path = Path(constraint["path"]) path = Path(constraint["path"])
...@@ -285,10 +273,15 @@ class Package(object): ...@@ -285,10 +273,15 @@ class Package(object):
if is_file: if is_file:
dependency = FileDependency( dependency = FileDependency(
path, category=category, optional=optional, base=self.root_dir name,
path,
category=category,
optional=optional,
base=self.root_dir,
) )
else: else:
dependency = DirectoryDependency( dependency = DirectoryDependency(
name,
path, path,
category=category, category=category,
optional=optional, optional=optional,
......
...@@ -146,6 +146,12 @@ def convert_markers(marker): ...@@ -146,6 +146,12 @@ def convert_markers(marker):
else: else:
variable, op, value = group variable, op, value = group
group_name = str(variable) group_name = str(variable)
# python_full_version is equivalent to python_version
# for Poetry so we merge them
if group_name == "python_full_version":
group_name = "python_version"
if group_name not in requirements: if group_name not in requirements:
requirements[group_name] = [] requirements[group_name] = []
......
...@@ -7,6 +7,7 @@ from typing import List ...@@ -7,6 +7,7 @@ from typing import List
from poetry.mixology import resolve_version from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage from poetry.packages import DependencyPackage
from poetry.packages import Package
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
from poetry.version.markers import AnyMarker from poetry.version.markers import AnyMarker
...@@ -55,8 +56,33 @@ class Solver: ...@@ -55,8 +56,33 @@ class Solver:
for pkg in self._installed.packages: for pkg in self._installed.packages:
if package.name == pkg.name: if package.name == pkg.name:
installed = True installed = True
# Checking version
if package.version != pkg.version: if pkg.source_type == "git" and package.source_type == "git":
# Trying to find the currently installed version
for locked in self._locked.packages:
if (
locked.name == pkg.name
and locked.source_type == pkg.source_type
and locked.source_url == pkg.source_url
and locked.source_reference == pkg.source_reference
):
pkg = Package(pkg.name, locked.version)
pkg.source_type = "git"
pkg.source_url = locked.source_url
pkg.source_reference = locked.source_reference
break
if (
pkg.source_url != package.source_url
or pkg.source_reference != package.source_reference
):
operations.append(Update(pkg, package))
else:
operations.append(
Install(package).skip("Already installed")
)
elif package.version != pkg.version:
# Checking version
operations.append(Update(pkg, package)) operations.append(Update(pkg, package))
else: else:
operations.append(Install(package).skip("Already installed")) operations.append(Install(package).skip("Already installed"))
......
class RepositoryError(Exception):
pass
class PackageNotFound(Exception):
pass
import re
from poetry.packages import Package from poetry.packages import Package
from poetry.utils.env import Env from poetry.utils.env import Env
...@@ -17,7 +19,26 @@ class InstalledRepository(Repository): ...@@ -17,7 +19,26 @@ class InstalledRepository(Repository):
freeze_output = env.run("pip", "freeze") freeze_output = env.run("pip", "freeze")
for line in freeze_output.split("\n"): for line in freeze_output.split("\n"):
if "==" in line: if "==" in line:
name, version = line.split("==") name, version = re.split("={2,3}", line)
repo.add_package(Package(name, version, version)) repo.add_package(Package(name, version, version))
elif line.startswith("-e "):
line = line[3:].strip()
if line.startswith("git+"):
url = line.lstrip("git+")
if "@" in url:
url, rev = url.rsplit("@", 1)
else:
rev = "master"
name = url.split("/")[-1].rstrip(".git")
if "#egg=" in rev:
rev, name = rev.split("#egg=")
package = Package(name, "0.0.0")
package.source_type = "git"
package.source_url = url
package.source_reference = rev
repo.add_package(package)
return repo return repo
...@@ -41,6 +41,7 @@ from poetry.utils._compat import Path ...@@ -41,6 +41,7 @@ from poetry.utils._compat import Path
from poetry.utils.helpers import canonicalize_name, get_http_basic_auth from poetry.utils.helpers import canonicalize_name, get_http_basic_auth
from poetry.version.markers import InvalidMarker from poetry.version.markers import InvalidMarker
from .exceptions import PackageNotFound
from .pypi_repository import PyPiRepository from .pypi_repository import PyPiRepository
...@@ -265,9 +266,17 @@ class LegacyRepository(PyPiRepository): ...@@ -265,9 +266,17 @@ class LegacyRepository(PyPiRepository):
req = req.split(";")[0] req = req.split(";")[0]
dependency = dependency_from_pep_508(req) dependency = dependency_from_pep_508(req)
except ValueError:
# Likely unable to parse constraint so we skip it
self._log(
"Invalid constraint ({}) found in {}-{} dependencies, "
"skipping".format(req, package.name, package.version),
level="debug",
)
continue
if dependency.extras: if dependency.in_extras:
for extra in dependency.extras: for extra in dependency.in_extras:
if extra not in package.extras: if extra not in package.extras:
package.extras[extra] = [] package.extras[extra] = []
...@@ -297,7 +306,7 @@ class LegacyRepository(PyPiRepository): ...@@ -297,7 +306,7 @@ class LegacyRepository(PyPiRepository):
def _get_release_info(self, name, version): # type: (str, str) -> dict def _get_release_info(self, name, version): # type: (str, str) -> dict
page = self._get("/{}/".format(canonicalize_name(name).replace(".", "-"))) page = self._get("/{}/".format(canonicalize_name(name).replace(".", "-")))
if page is None: if page is None:
raise ValueError('No package named "{}"'.format(name)) raise PackageNotFound('No package named "{}"'.format(name))
data = { data = {
"name": name, "name": name,
...@@ -310,7 +319,7 @@ class LegacyRepository(PyPiRepository): ...@@ -310,7 +319,7 @@ class LegacyRepository(PyPiRepository):
links = list(page.links_for_version(Version.parse(version))) links = list(page.links_for_version(Version.parse(version)))
if not links: if not links:
raise ValueError( raise PackageNotFound(
'No valid distribution links found for package: "{}" version: "{}"'.format( 'No valid distribution links found for package: "{}" version: "{}"'.format(
name, version name, version
) )
......
from typing import List from typing import List
from typing import Union from typing import Union
import poetry.packages
from .base_repository import BaseRepository from .base_repository import BaseRepository
from .exceptions import PackageNotFound
from .repository import Repository from .repository import Repository
...@@ -65,7 +65,7 @@ class Pool(BaseRepository): ...@@ -65,7 +65,7 @@ class Pool(BaseRepository):
for repository in self._repositories: for repository in self._repositories:
try: try:
package = repository.package(name, version, extras=extras) package = repository.package(name, version, extras=extras)
except ValueError: except PackageNotFound:
continue continue
if package: if package:
...@@ -73,7 +73,7 @@ class Pool(BaseRepository): ...@@ -73,7 +73,7 @@ class Pool(BaseRepository):
return package return package
raise PackageNotFound("Package [{}] not found.".format(name)) raise PackageNotFound("Package {} ({}) not found.".format(name, version))
def find_packages( def find_packages(
self, name, constraint=None, extras=None, allow_prereleases=False self, name, constraint=None, extras=None, allow_prereleases=False
......
...@@ -39,8 +39,10 @@ from poetry.utils._compat import to_str ...@@ -39,8 +39,10 @@ from poetry.utils._compat import to_str
from poetry.utils.helpers import parse_requires from poetry.utils.helpers import parse_requires
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.utils.env import Env from poetry.utils.env import Env
from poetry.utils.setup_reader import SetupReader
from poetry.version.markers import InvalidMarker from poetry.version.markers import InvalidMarker
from .exceptions import PackageNotFound
from .repository import Repository from .repository import Repository
...@@ -225,7 +227,7 @@ class PyPiRepository(Repository): ...@@ -225,7 +227,7 @@ class PyPiRepository(Repository):
def _get_package_info(self, name): # type: (str) -> dict def _get_package_info(self, name): # type: (str) -> dict
data = self._get("pypi/{}/json".format(name)) data = self._get("pypi/{}/json".format(name))
if data is None: if data is None:
raise ValueError("Package [{}] not found.".format(name)) raise PackageNotFound("Package [{}] not found.".format(name))
return data return data
...@@ -261,7 +263,7 @@ class PyPiRepository(Repository): ...@@ -261,7 +263,7 @@ class PyPiRepository(Repository):
json_data = self._get("pypi/{}/{}/json".format(name, version)) json_data = self._get("pypi/{}/{}/json".format(name, version))
if json_data is None: if json_data is None:
raise ValueError("Package [{}] not found.".format(name)) raise PackageNotFound("Package [{}] not found.".format(name))
info = json_data["info"] info = json_data["info"]
data = { data = {
...@@ -460,6 +462,9 @@ class PyPiRepository(Repository): ...@@ -460,6 +462,9 @@ class PyPiRepository(Repository):
else: else:
if suffix == ".bz2": if suffix == ".bz2":
gz = BZ2File(str(filepath)) gz = BZ2File(str(filepath))
suffixes = filepath.suffixes
if len(suffixes) > 1 and suffixes[-2] == ".tar":
suffix = ".tar.bz2"
else: else:
gz = GzipFile(str(filepath)) gz = GzipFile(str(filepath))
suffix = ".tar.gz" suffix = ".tar.gz"
...@@ -501,45 +506,42 @@ class PyPiRepository(Repository): ...@@ -501,45 +506,42 @@ class PyPiRepository(Repository):
return info return info
# Still nothing, assume no dependencies # Still nothing, try reading (without executing it)
# We could probably get them by executing # the setup.py file.
# python setup.py egg-info but I don't feel try:
# confortable executing a file just for the sake info.update(self._inspect_sdist_with_setup(sdist_dir))
# of getting dependencies.
return info return info
except Exception as e:
self._log(
"An error occurred when reading setup.py or setup.cfg: {}".format(
str(e)
),
"warning",
)
return info
def _inspect_sdist_with_setup(self, sdist_dir): def _inspect_sdist_with_setup(self, sdist_dir):
info = {"requires_python": None, "requires_dist": None} info = {"requires_python": None, "requires_dist": None}
setup = sdist_dir / "setup.py" result = SetupReader.read_from_directory(sdist_dir)
if not setup.exists(): requires = ""
return info for dep in result["install_requires"]:
requires += dep + "\n"
venv = Env.create_venv(NullIO()) if result["extras_require"]:
requires += "\n"
current_dir = os.getcwd() for extra_name, deps in result["extras_require"].items():
os.chdir(sdist_dir.as_posix()) requires += "[{}]\n".format(extra_name)
try: for dep in deps:
venv.run("python", "setup.py", "egg_info") requires += dep + "\n"
egg_info = list(sdist_dir.glob("**/*.egg-info"))[0] requires += "\n"
meta = pkginfo.UnpackedSDist(str(egg_info))
if meta.requires_python:
info["requires_python"] = meta.requires_python
if meta.requires_dist:
info["requires_dist"] = list(meta.requires_dist)
else:
requires = egg_info / "requires.txt"
if requires.exists():
with requires.open() as f:
info["requires_dist"] = parse_requires(f.read())
except Exception:
pass
os.chdir(current_dir) info["requires_dist"] = parse_requires(requires)
info["requires_python"] = result["python_requires"]
return info return info
......
import sys import sys
try: try:
import pathlib2 from functools32 import lru_cache
from pathlib2 import Path
except ImportError: except ImportError:
from pathlib import Path from functools import lru_cache
try: # Python 2 try: # Python 2
long = long long = long
...@@ -21,6 +20,12 @@ PY35 = sys.version_info >= (3, 5) ...@@ -21,6 +20,12 @@ PY35 = sys.version_info >= (3, 5)
PY36 = sys.version_info >= (3, 6) PY36 = sys.version_info >= (3, 6)
if PY35:
from pathlib import Path
else:
from pathlib2 import Path
def decode(string, encodings=None): def decode(string, encodings=None):
if not PY2 and not isinstance(string, bytes): if not PY2 and not isinstance(string, bytes):
return string return string
......
...@@ -302,8 +302,6 @@ class Env(object): ...@@ -302,8 +302,6 @@ class Env(object):
Return path to the given executable. Return path to the given executable.
""" """
bin_path = (self._bin_dir / bin).with_suffix(".exe" if self._is_windows else "") bin_path = (self._bin_dir / bin).with_suffix(".exe" if self._is_windows else "")
if not bin_path.exists():
return bin
return str(bin_path) return str(bin_path)
...@@ -516,3 +514,19 @@ class NullEnv(SystemEnv): ...@@ -516,3 +514,19 @@ class NullEnv(SystemEnv):
def _bin(self, bin): def _bin(self, bin):
return bin return bin
class MockEnv(NullEnv):
def __init__(self, version_info=(3, 7, 0), python_implementation="cpython"):
super(MockEnv, self).__init__()
self._version_info = version_info
self._python_implementation = python_implementation
@property
def version_info(self): # type: () -> Tuple[int]
return self._version_info
@property
def python_implementation(self): # type: () -> str
return self._python_implementation
import os
import re import re
import shutil import shutil
import stat
import tempfile import tempfile
from contextlib import contextmanager from contextlib import contextmanager
...@@ -89,3 +91,12 @@ def get_http_basic_auth( ...@@ -89,3 +91,12 @@ def get_http_basic_auth(
return repo_auth["username"], repo_auth.get("password") return repo_auth["username"], repo_auth.get("password")
return None return None
def _on_rm_error(func, path, exc_info):
os.chmod(path, stat.S_IWRITE)
func(path)
def safe_rmtree(path):
shutil.rmtree(path, onerror=_on_rm_error)
...@@ -21,7 +21,10 @@ def format_python_constraint(constraint): ...@@ -21,7 +21,10 @@ def format_python_constraint(constraint):
This helper will help in transforming This helper will help in transforming
disjunctive constraint into proper constraint. disjunctive constraint into proper constraint.
""" """
if isinstance(constraint, Version) and constraint.precision < 3: if isinstance(constraint, Version):
if constraint.precision >= 3:
return "=={}".format(str(constraint))
# Transform 3.6 or 3 # Transform 3.6 or 3
if constraint.precision == 2: if constraint.precision == 2:
# 3.6 # 3.6
......
[tool.poetry] [tool.poetry]
name = "poetry" name = "poetry"
version = "0.12.4" version = "0.12.8"
description = "Python dependency management and packaging made easy." description = "Python dependency management and packaging made easy."
authors = [ authors = [
"Sébastien Eustace <sebastien@eustace.io>" "Sébastien Eustace <sebastien@eustace.io>"
...@@ -34,7 +34,7 @@ cachecontrol = { version = "^0.12.4", extras = ["filecache"] } ...@@ -34,7 +34,7 @@ cachecontrol = { version = "^0.12.4", extras = ["filecache"] }
pkginfo = "^1.4" pkginfo = "^1.4"
html5lib = "^1.0" html5lib = "^1.0"
shellingham = "^1.1" shellingham = "^1.1"
tomlkit = "^0.4.4" tomlkit = "^0.5.1"
# The typing module is not in the stdlib in Python 2.7 and 3.4 # The typing module is not in the stdlib in Python 2.7 and 3.4
typing = { version = "^3.6", python = "~2.7 || ~3.4" } typing = { version = "^3.6", python = "~2.7 || ~3.4" }
...@@ -43,6 +43,8 @@ typing = { version = "^3.6", python = "~2.7 || ~3.4" } ...@@ -43,6 +43,8 @@ typing = { version = "^3.6", python = "~2.7 || ~3.4" }
pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" } pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" }
# Use virtualenv for Python 2.7 since venv does not exist # Use virtualenv for Python 2.7 since venv does not exist
virtualenv = { version = "^16.0", python = "~2.7" } virtualenv = { version = "^16.0", python = "~2.7" }
# functools32 is needed for Python 2.7
functools32 = { version = "^3.2.3", python = "~2.7" }
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^3.4" pytest = "^3.4"
...@@ -55,7 +57,7 @@ pygments-github-lexers = "^0.0.5" ...@@ -55,7 +57,7 @@ pygments-github-lexers = "^0.0.5"
black = { version = "^18.3-alpha.0", python = "^3.6" } black = { version = "^18.3-alpha.0", python = "^3.6" }
pre-commit = "^1.10" pre-commit = "^1.10"
tox = "^3.0" tox = "^3.0"
pytest-sugar = "^0.9.1" pytest-sugar = "^0.9.2"
[tool.poetry.scripts] [tool.poetry.scripts]
......
...@@ -51,6 +51,7 @@ class MakeReleaseCommand(Command): ...@@ -51,6 +51,7 @@ class MakeReleaseCommand(Command):
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.vcs import get_vcs
project = Poetry.create(Path.cwd()) project = Poetry.create(Path.cwd())
package = project.package package = project.package
...@@ -60,11 +61,21 @@ class MakeReleaseCommand(Command): ...@@ -60,11 +61,21 @@ class MakeReleaseCommand(Command):
pool = Pool() pool = Pool()
pool.add_repository(project.locker.locked_repository(with_dev_reqs=True)) pool.add_repository(project.locker.locked_repository(with_dev_reqs=True))
vcs = get_vcs(Path(__file__).parent)
if vcs:
vcs_excluded = [str(f) for f in vcs.get_ignored_files()]
else:
vcs_excluded = []
with temporary_directory() as tmp_dir: with temporary_directory() as tmp_dir:
# Copy poetry to tmp dir # Copy poetry to tmp dir
poetry_dir = os.path.join(tmp_dir, "poetry") poetry_dir = os.path.join(tmp_dir, "poetry")
shutil.copytree( shutil.copytree(
os.path.join(os.path.dirname(__file__), "poetry"), poetry_dir os.path.join(os.path.dirname(__file__), "poetry"),
poetry_dir,
ignore=lambda dir_, names: set(vcs_excluded).intersection(
set([os.path.join(dir_, name) for name in names])
),
) )
for version, python in sorted(pythons.items()): for version, python in sorted(pythons.items()):
self.line( self.line(
...@@ -102,9 +113,14 @@ class MakeReleaseCommand(Command): ...@@ -102,9 +113,14 @@ class MakeReleaseCommand(Command):
continue continue
path = os.path.join(os.path.realpath(root), f) path = os.path.join(os.path.realpath(root), f)
relpath = os.path.relpath( relpath = os.path.relpath(
path, os.path.realpath(tmp_dir) path, os.path.realpath(tmp_dir)
) )
if relpath in vcs_excluded:
continue
tar_info = tar.gettarinfo(str(path), arcname=relpath) tar_info = tar.gettarinfo(str(path), arcname=relpath)
if tar_info.isreg(): if tar_info.isreg():
......
import pytest import pytest
import shutil
import tempfile import tempfile
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
from poetry.config import Config from poetry.config import Config
from poetry.utils._compat import Path
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
...@@ -11,3 +18,28 @@ def config(): # type: () -> Config ...@@ -11,3 +18,28 @@ def config(): # type: () -> Config
f.close() f.close()
return Config(TomlFile(f.name)) return Config(TomlFile(f.name))
def mock_clone(_, source, dest):
# Checking source to determine which folder we need to copy
parts = urlparse.urlparse(source)
folder = (
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ parts.netloc
/ parts.path.lstrip("/").rstrip(".git")
)
shutil.rmtree(str(dest))
shutil.copytree(str(folder), str(dest))
@pytest.fixture(autouse=True)
def git_mock(mocker):
# Patch git module to not actually clone projects
mocker.patch("poetry.vcs.git.Git.clone", new=mock_clone)
mocker.patch("poetry.vcs.git.Git.checkout", new=lambda *_: None)
p = mocker.patch("poetry.vcs.git.Git.rev_parse")
p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
import sys
from cleo.testers import CommandTester from cleo.testers import CommandTester
from tests.helpers import get_dependency
from tests.helpers import get_package from tests.helpers import get_package
...@@ -78,7 +75,6 @@ Resolving dependencies... ...@@ -78,7 +75,6 @@ Resolving dependencies...
Resolution results: Resolution results:
- pendulum (2.0.3) - pendulum (2.0.3)
- cleo (0.6.5)
- demo (0.1.2) - demo (0.1.2)
""" """
......
...@@ -148,7 +148,7 @@ description = "" ...@@ -148,7 +148,7 @@ description = ""
authors = ["Your Name <you@example.com>"] authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "*" python = "^3.7"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
""" """
......
...@@ -17,6 +17,8 @@ from poetry.packages import Locker as BaseLocker ...@@ -17,6 +17,8 @@ from poetry.packages import Locker as BaseLocker
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils.env import Env
from poetry.utils.env import MockEnv
from poetry.utils.toml_file import TomlFile from poetry.utils.toml_file import TomlFile
...@@ -48,6 +50,8 @@ def installed(): ...@@ -48,6 +50,8 @@ def installed():
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup(mocker, installer, installed): def setup(mocker, installer, installed):
Env._env = MockEnv()
# Set Installer's installer # Set Installer's installer
p = mocker.patch("poetry.installation.installer.Installer._get_installer") p = mocker.patch("poetry.installation.installer.Installer._get_installer")
p.return_value = installer p.return_value = installer
...@@ -74,6 +78,7 @@ def setup(mocker, installer, installed): ...@@ -74,6 +78,7 @@ def setup(mocker, installer, installed):
os.environ.clear() os.environ.clear()
os.environ.update(environ) os.environ.update(environ)
Env._env = None
class Application(BaseApplication): class Application(BaseApplication):
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from setuptools import setup from setuptools import setup
...@@ -13,7 +12,7 @@ kwargs = dict( ...@@ -13,7 +12,7 @@ kwargs = dict(
url="https://github.com/demo/demo", url="https://github.com/demo/demo",
packages=["demo"], packages=["demo"],
install_requires=["pendulum>=1.4.4"], install_requires=["pendulum>=1.4.4"],
extras_require={"foo": ["cleo"]}, extras_require={"foo": ["cleo"], "bar": ["tomlkit"]},
) )
......
# -*- coding: utf-8 -*-
import ast
import os
from setuptools import setup
def read_version():
with open(os.path.join(os.path.dirname(__file__), "demo", "__init__.py")) as f:
for line in f:
if line.startswith("__version__ = "):
return ast.literal_eval(line[len("__version__ = ") :].strip())
kwargs = dict(
name="demo",
license="MIT",
version=read_version(),
description="Demo project.",
author="Sébastien Eustace",
author_email="sebastien@eustace.io",
url="https://github.com/demo/demo",
packages=["demo"],
install_requires=["pendulum>=1.4.4"],
extras_require={"foo": ["cleo"]},
)
setup(**kwargs)
[[package]] [[package]]
description = ""
category = "dev"
name = "cachy"
optional = true
python-versions = "*"
version = "0.2.0"
[[package]]
description = ""
category = "dev"
name = "pendulum"
optional = true
python-versions = "*"
version = "1.4.4"
[[package]]
category = "main" category = "main"
description = "" description = ""
name = "project-with-extras" name = "project-with-extras"
...@@ -40,7 +24,7 @@ python-versions = "*" ...@@ -40,7 +24,7 @@ python-versions = "*"
version = "1.2.3" version = "1.2.3"
[package.dependencies] [package.dependencies]
project-with-extras = "1.2.3" project-with-extras = "*"
[package.source] [package.source]
reference = "" reference = ""
...@@ -52,7 +36,5 @@ content-hash = "123456789" ...@@ -52,7 +36,5 @@ content-hash = "123456789"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.hashes]
cachy = []
project-with-extras = [] project-with-extras = []
project-with-transitive-directory-dependencies = [] project-with-transitive-directory-dependencies = []
pendulum = []
[[package]] [[package]]
description = "" description = ""
category = "main" category = "main"
name = "cachy"
optional = true
python-versions = "*"
version = "0.2.0"
[[package]]
description = ""
category = "main"
name = "pendulum" name = "pendulum"
optional = false optional = false
python-versions = "*" python-versions = "*"
...@@ -39,6 +31,5 @@ content-hash = "123456789" ...@@ -39,6 +31,5 @@ content-hash = "123456789"
python-versions = "*" python-versions = "*"
[metadata.hashes] [metadata.hashes]
cachy = []
project-with-extras = [] project-with-extras = []
pendulum = [] pendulum = []
[[package]] [[package]]
name = "demo" name = "demo"
version = "0.1.0" version = "0.1.0"
description = "Description" description = ""
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
...@@ -12,7 +12,7 @@ reference = "" ...@@ -12,7 +12,7 @@ reference = ""
url = "tests/fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" url = "tests/fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl"
[package.dependencies] [package.dependencies]
pendulum = ">=1.4.0.0,<2.0.0.0" pendulum = ">=1.4.4"
[[package]] [[package]]
name = "pendulum" name = "pendulum"
...@@ -27,5 +27,5 @@ python-versions = "*" ...@@ -27,5 +27,5 @@ python-versions = "*"
content-hash = "123456789" content-hash = "123456789"
[metadata.hashes] [metadata.hashes]
demo = ["373a7e4bb99653541f35e52738f1b8a8e889c12e5f8b93a88c757d948b0cbe89"] demo = ["70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"]
pendulum = [] pendulum = []
...@@ -77,21 +77,12 @@ class Locker(BaseLocker): ...@@ -77,21 +77,12 @@ class Locker(BaseLocker):
self._written_data = data self._written_data = data
@pytest.fixture(autouse=True)
def setup():
# Mock python version and platform to get reliable tests
original_platform = sys.platform
sys.platform = "darwin"
yield
sys.platform = original_platform
@pytest.fixture() @pytest.fixture()
def package(): def package():
return ProjectPackage("root", "1.0") p = ProjectPackage("root", "1.0")
p.root_dir = Path.cwd()
return p
@pytest.fixture() @pytest.fixture()
...@@ -472,8 +463,10 @@ def test_run_with_optional_and_python_restricted_dependencies( ...@@ -472,8 +463,10 @@ def test_run_with_optional_and_python_restricted_dependencies(
def test_run_with_optional_and_platform_restricted_dependencies( def test_run_with_optional_and_platform_restricted_dependencies(
installer, locker, repo, package installer, locker, repo, package, mocker
): ):
mocker.patch("sys.platform", "darwin")
package_a = get_package("A", "1.0") package_a = get_package("A", "1.0")
package_b = get_package("B", "1.1") package_b = get_package("B", "1.1")
package_c12 = get_package("C", "1.2") package_c12 = get_package("C", "1.2")
...@@ -678,11 +671,12 @@ def test_run_installs_with_local_file(installer, locker, repo, package): ...@@ -678,11 +671,12 @@ def test_run_installs_with_local_file(installer, locker, repo, package):
def test_run_installs_with_local_poetry_directory_and_extras( def test_run_installs_with_local_poetry_directory_and_extras(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path("tests/fixtures/project_with_extras/") file_path = Path("tests/fixtures/project_with_extras")
package.add_dependency("demo", {"path": str(file_path), "extras": ["extras_a"]}) package.add_dependency(
"project-with-extras", {"path": str(file_path), "extras": ["extras_a"]}
)
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cachy", "0.2.0"))
installer.run() installer.run()
...@@ -699,7 +693,9 @@ def test_run_installs_with_local_poetry_directory_transitive( ...@@ -699,7 +693,9 @@ def test_run_installs_with_local_poetry_directory_transitive(
file_path = Path( file_path = Path(
"tests/fixtures/directory/project_with_transitive_directory_dependencies/" "tests/fixtures/directory/project_with_transitive_directory_dependencies/"
) )
package.add_dependency("demo", {"path": str(file_path)}) package.add_dependency(
"project-with-transitive-directory-dependencies", {"path": str(file_path)}
)
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cachy", "0.2.0")) repo.add_package(get_package("cachy", "0.2.0"))
...@@ -717,7 +713,7 @@ def test_run_installs_with_local_setuptools_directory( ...@@ -717,7 +713,7 @@ def test_run_installs_with_local_setuptools_directory(
installer, locker, repo, package, tmpdir installer, locker, repo, package, tmpdir
): ):
file_path = Path("tests/fixtures/project_with_setup/") file_path = Path("tests/fixtures/project_with_setup/")
package.add_dependency("demo", {"path": str(file_path)}) package.add_dependency("my-package", {"path": str(file_path)})
repo.add_package(get_package("pendulum", "1.4.4")) repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cachy", "0.2.0")) repo.add_package(get_package("cachy", "0.2.0"))
......
...@@ -20,6 +20,10 @@ classifiers = [ ...@@ -20,6 +20,10 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules" "Topic :: Software Development :: Libraries :: Python Modules"
] ]
exclude = [
"**/*.xml"
]
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.6" python = "^3.6"
......
[tool.poetry]
name = "single-python"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io/"
[tool.poetry.dependencies]
python = "2.7.15"
from distutils.core import Extension
extensions = [Extension("extended.extended", ["src/extended/extended.c"])]
def build(setup_kwargs):
setup_kwargs.update({"ext_modules": extensions})
[tool.poetry]
name = "extended"
version = "0.1"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io/"
build = "build.py"
#include <Python.h>
static PyObject *hello(PyObject *self) {
return PyUnicode_FromString("Hello");
}
static PyMethodDef module_methods[] = {
{
"hello",
(PyCFunction) hello,
NULL,
PyDoc_STR("Say hello.")
},
{NULL}
};
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"extended",
NULL,
-1,
module_methods,
NULL,
NULL,
NULL,
NULL,
};
#endif
PyMODINIT_FUNC
#if PY_MAJOR_VERSION >= 3
PyInit_extended(void)
#else
init_extended(void)
#endif
{
PyObject *module;
#if PY_MAJOR_VERSION >= 3
module = PyModule_Create(&moduledef);
#else
module = Py_InitModule3("extended", module_methods, NULL);
#endif
if (module == NULL)
#if PY_MAJOR_VERSION >= 3
return NULL;
#else
return;
#endif
#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
...@@ -7,6 +7,7 @@ import shutil ...@@ -7,6 +7,7 @@ import shutil
import sys import sys
import tarfile import tarfile
import zipfile import zipfile
import tempfile
from poetry import __version__ from poetry import __version__
from poetry.io import NullIO from poetry.io import NullIO
...@@ -83,6 +84,67 @@ $""".format( ...@@ -83,6 +84,67 @@ $""".format(
) )
is not None is not None
) )
records = decode(zip.read("extended-0.1.dist-info/RECORD"))
assert re.search(r"\s+extended/extended.*\.(so|pyd)", records) is not None
finally:
zip.close()
@pytest.mark.skipif(
sys.platform == "win32" and sys.version_info <= (3, 4),
reason="Disable test on Windows for Python <=3.4",
)
def test_wheel_c_extension_src_layout():
module_path = fixtures_dir / "src_extended"
builder = CompleteBuilder(
Poetry.create(module_path), NullEnv(execute=True), NullIO()
)
builder.build()
sdist = fixtures_dir / "src_extended" / "dist" / "extended-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "extended-0.1/build.py" in tar.getnames()
assert "extended-0.1/src/extended/extended.c" in tar.getnames()
whl = list((module_path / "dist").glob("extended-0.1-cp*-cp*-*.whl"))[0]
assert whl.exists()
zip = zipfile.ZipFile(str(whl))
has_compiled_extension = False
for name in zip.namelist():
if name.startswith("extended/extended") and name.endswith((".so", ".pyd")):
has_compiled_extension = True
assert has_compiled_extension
try:
wheel_data = decode(zip.read("extended-0.1.dist-info/WHEEL"))
assert (
re.match(
"""(?m)^\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: false
Tag: cp[23]\\d-cp[23]\\dmu?-.+
$""".format(
__version__
),
wheel_data,
)
is not None
)
records = decode(zip.read("extended-0.1.dist-info/RECORD"))
assert re.search(r"\s+extended/extended.*\.(so|pyd)", records) is not None
finally: finally:
zip.close() zip.close()
...@@ -164,6 +226,105 @@ My Package ...@@ -164,6 +226,105 @@ My Package
zip.close() zip.close()
def test_complete_no_vcs():
# Copy the complete fixtures dir to a temporary directory
module_path = fixtures_dir / "complete"
temporary_dir = Path(tempfile.mkdtemp()) / "complete"
shutil.copytree(module_path.as_posix(), temporary_dir.as_posix())
builder = CompleteBuilder(
Poetry.create(temporary_dir), NullEnv(execute=True), NullIO()
)
builder.build()
whl = temporary_dir / "dist" / "my_package-1.2.3-py3-none-any.whl"
assert whl.exists()
zip = zipfile.ZipFile(str(whl))
# Check the zipped file to be sure that included and excluded files are
# correctly taken account of without vcs
expected_name_list = [
"my_package/__init__.py",
"my_package/data1/test.json",
"my_package/sub_pkg1/__init__.py",
"my_package/sub_pkg2/__init__.py",
"my_package/sub_pkg2/data2/data.json",
"my_package-1.2.3.dist-info/entry_points.txt",
"my_package-1.2.3.dist-info/LICENSE",
"my_package-1.2.3.dist-info/WHEEL",
"my_package-1.2.3.dist-info/METADATA",
"my_package-1.2.3.dist-info/RECORD",
]
assert sorted(zip.namelist()) == sorted(expected_name_list)
try:
entry_points = zip.read("my_package-1.2.3.dist-info/entry_points.txt")
assert (
decode(entry_points.decode())
== """\
[console_scripts]
extra-script=my_package.extra:main[time]
my-2nd-script=my_package:main2
my-script=my_package:main
"""
)
wheel_data = decode(zip.read("my_package-1.2.3.dist-info/WHEEL"))
assert (
wheel_data
== """\
Wheel-Version: 1.0
Generator: poetry {}
Root-Is-Purelib: true
Tag: py3-none-any
""".format(
__version__
)
)
wheel_data = decode(zip.read("my_package-1.2.3.dist-info/METADATA"))
assert (
wheel_data
== """\
Metadata-Version: 2.1
Name: my-package
Version: 1.2.3
Summary: Some description.
Home-page: https://poetry.eustace.io/
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Requires-Python: >=3.6,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); extra == "time"
Project-URL: Documentation, https://poetry.eustace.io/docs
Project-URL: Repository, https://github.com/sdispater/poetry
Description-Content-Type: text/x-rst
My Package
==========
"""
)
finally:
zip.close()
def test_module_src(): def test_module_src():
module_path = fixtures_dir / "source_file" module_path = fixtures_dir / "source_file"
builder = CompleteBuilder( builder = CompleteBuilder(
......
...@@ -309,6 +309,21 @@ def test_with_c_extensions(): ...@@ -309,6 +309,21 @@ def test_with_c_extensions():
assert "extended-0.1/extended/extended.c" in tar.getnames() assert "extended-0.1/extended/extended.c" in tar.getnames()
def test_with_c_extensions_src_layout():
poetry = Poetry.create(project("src_extended"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
builder.build()
sdist = fixtures_dir / "src_extended" / "dist" / "extended-0.1.tar.gz"
assert sdist.exists()
with tarfile.open(str(sdist), "r") as tar:
assert "extended-0.1/build.py" in tar.getnames()
assert "extended-0.1/src/extended/extended.c" in tar.getnames()
def test_with_src_module_file(): def test_with_src_module_file():
poetry = Poetry.create(project("source_file")) poetry = Poetry.create(project("source_file"))
...@@ -423,14 +438,18 @@ def test_default_with_excluded_data(mocker): ...@@ -423,14 +438,18 @@ def test_default_with_excluded_data(mocker):
# Patch git module to return specific excluded files # Patch git module to return specific excluded files
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files") p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = [ p.return_value = [
str( (
Path(__file__).parent (
/ "fixtures" Path(__file__).parent
/ "default_with_excluded_data" / "fixtures"
/ "my_package" / "default_with_excluded_data"
/ "data" / "my_package"
/ "sub_data" / "data"
/ "data2.txt" / "sub_data"
/ "data2.txt"
)
.relative_to(project("default_with_excluded_data"))
.as_posix()
) )
] ]
poetry = Poetry.create(project("default_with_excluded_data")) poetry = Poetry.create(project("default_with_excluded_data"))
...@@ -471,7 +490,7 @@ def test_default_with_excluded_data(mocker): ...@@ -471,7 +490,7 @@ def test_default_with_excluded_data(mocker):
assert "my-package-1.2.3/PKG-INFO" in names assert "my-package-1.2.3/PKG-INFO" in names
def test_proper_python_requires_if_single_version_specified(): def test_proper_python_requires_if_two_digits_precision_version_specified():
poetry = Poetry.create(project("simple_version")) poetry = Poetry.create(project("simple_version"))
builder = SdistBuilder(poetry, NullEnv(), NullIO()) builder = SdistBuilder(poetry, NullEnv(), NullIO())
...@@ -480,3 +499,14 @@ def test_proper_python_requires_if_single_version_specified(): ...@@ -480,3 +499,14 @@ def test_proper_python_requires_if_single_version_specified():
parsed = p.parsestr(to_str(pkg_info)) parsed = p.parsestr(to_str(pkg_info))
assert parsed["Requires-Python"] == ">=3.6,<3.7" assert parsed["Requires-Python"] == ">=3.6,<3.7"
def test_proper_python_requires_if_three_digits_precision_version_specified():
poetry = Poetry.create(project("single_python"))
builder = SdistBuilder(poetry, NullEnv(), NullIO())
pkg_info = builder.build_pkg_info()
p = Parser()
parsed = p.parsestr(to_str(pkg_info))
assert parsed["Requires-Python"] == "==2.7.15"
...@@ -8,7 +8,7 @@ def test_dependency_does_not_match_root_python_constraint(root, provider, repo): ...@@ -8,7 +8,7 @@ def test_dependency_does_not_match_root_python_constraint(root, provider, repo):
add_to_repo(repo, "foo", "1.0.0", python="<3.5") add_to_repo(repo, "foo", "1.0.0", python="<3.5")
error = """The current supported Python versions are ^3.6 error = """The current project must support the following Python versions: ^3.6
Because no versions of foo match !=1.0.0 Because no versions of foo match !=1.0.0
and foo (1.0.0) requires Python <3.5, foo is forbidden. and foo (1.0.0) requires Python <3.5, foo is forbidden.
......
import pytest
from poetry.packages.directory_dependency import DirectoryDependency
from poetry.utils._compat import Path
from poetry.utils.env import EnvCommandError
from poetry.utils.env import MockEnv as BaseMockEnv
from subprocess import CalledProcessError
class MockEnv(BaseMockEnv):
def run(self, bin, *args):
raise EnvCommandError(CalledProcessError(1, "python", output=""))
DIST_PATH = Path(__file__).parent.parent / "fixtures" / "git" / "github.com" / "demo"
def test_directory_dependency_must_exist():
with pytest.raises(ValueError):
DirectoryDependency("demo", DIST_PATH / "invalid")
...@@ -6,35 +6,11 @@ from poetry.utils._compat import Path ...@@ -6,35 +6,11 @@ from poetry.utils._compat import Path
DIST_PATH = Path(__file__).parent.parent / "fixtures" / "distributions" DIST_PATH = Path(__file__).parent.parent / "fixtures" / "distributions"
def test_file_dependency_wheel():
dependency = FileDependency(DIST_PATH / "demo-0.1.0-py2.py3-none-any.whl")
assert dependency.is_file()
assert dependency.name == "demo"
assert dependency.pretty_constraint == "0.1.0"
assert dependency.python_versions == "*"
meta = dependency.metadata
assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"]
def test_file_dependency_sdist():
dependency = FileDependency(DIST_PATH / "demo-0.1.0.tar.gz")
assert dependency.is_file()
assert dependency.name == "demo"
assert dependency.pretty_constraint == "0.1.0"
assert dependency.python_versions == "*"
meta = dependency.metadata
assert meta.requires_dist == ["pendulum (>=1.4.0.0,<2.0.0.0)"]
def test_file_dependency_wrong_path(): def test_file_dependency_wrong_path():
with pytest.raises(ValueError): with pytest.raises(ValueError):
FileDependency(DIST_PATH / "demo-0.2.0.tar.gz") FileDependency("demo", DIST_PATH / "demo-0.2.0.tar.gz")
def test_file_dependency_dir(): def test_file_dependency_dir():
with pytest.raises(ValueError): with pytest.raises(ValueError):
FileDependency(DIST_PATH) FileDependency("demo", DIST_PATH)
...@@ -152,3 +152,20 @@ def test_dependency_from_pep_508_with_python_version_union_of_multi(): ...@@ -152,3 +152,20 @@ def test_dependency_from_pep_508_with_python_version_union_of_multi():
'python_version >= "2.7" and python_version < "2.8" ' 'python_version >= "2.7" and python_version < "2.8" '
'or python_version >= "3.4" and python_version < "3.5"' 'or python_version >= "3.4" and python_version < "3.5"'
) )
def test_dependency_from_pep_508_with_not_in_op_marker():
name = (
"jinja2 (>=2.7,<2.8)"
'; python_version not in "3.0,3.1,3.2" and extra == "export"'
)
dep = dependency_from_pep_508(name)
assert dep.name == "jinja2"
assert str(dep.constraint) == ">=2.7,<2.8"
assert dep.in_extras == ["export"]
assert dep.python_versions == "!=3.0.*, !=3.1.*, !=3.2.*"
assert (
str(dep.marker) == 'python_version not in "3.0,3.1,3.2" and extra == "export"'
)
...@@ -10,6 +10,7 @@ from poetry.repositories.pool import Pool ...@@ -10,6 +10,7 @@ from poetry.repositories.pool import Pool
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.puzzle import Solver from poetry.puzzle import Solver
from poetry.puzzle.exceptions import SolverProblemError from poetry.puzzle.exceptions import SolverProblemError
from poetry.utils._compat import Path
from poetry.version.markers import parse_marker from poetry.version.markers import parse_marker
from tests.helpers import get_dependency from tests.helpers import get_dependency
...@@ -913,7 +914,6 @@ def test_solver_can_resolve_git_dependencies(solver, repo, package): ...@@ -913,7 +914,6 @@ def test_solver_can_resolve_git_dependencies(solver, repo, package):
ops, ops,
[ [
{"job": "install", "package": pendulum}, {"job": "install", "package": pendulum},
{"job": "install", "package": cleo},
{"job": "install", "package": get_package("demo", "0.1.2")}, {"job": "install", "package": get_package("demo", "0.1.2")},
], ],
) )
...@@ -1192,3 +1192,299 @@ def test_solver_should_not_resolve_prerelease_version_if_not_requested( ...@@ -1192,3 +1192,299 @@ def test_solver_should_not_resolve_prerelease_version_if_not_requested(
with pytest.raises(SolverProblemError): with pytest.raises(SolverProblemError):
solver.solve() solver.solve()
def test_solver_ignores_dependencies_with_incompatible_python_full_version_marker(
solver, repo, package
):
package.python_versions = "^3.6"
package.add_dependency("A", "^1.0")
package.add_dependency("B", "^2.0")
package_a = get_package("A", "1.0.0")
package_a.requires.append(
dependency_from_pep_508(
'B (<2.0); platform_python_implementation == "PyPy" and python_full_version < "2.7.9"'
)
)
package_b200 = get_package("B", "2.0.0")
package_b100 = get_package("B", "1.0.0")
repo.add_package(package_a)
repo.add_package(package_b100)
repo.add_package(package_b200)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_a},
{"job": "install", "package": package_b200},
],
)
def test_solver_git_dependencies_update(solver, repo, package, installed):
pendulum = get_package("pendulum", "2.0.3")
cleo = get_package("cleo", "1.0.0")
repo.add_package(pendulum)
repo.add_package(cleo)
demo = get_package("demo", "0.1.2")
demo.source_type = "git"
demo.source_url = "https://github.com/demo/demo.git"
demo.source_reference = "123456"
installed.add_package(demo)
package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": pendulum},
{
"job": "update",
"from": get_package("demo", "0.1.2"),
"to": get_package("demo", "0.1.2"),
},
],
)
op = ops[1]
assert op.job_type == "update"
assert op.package.source_type == "git"
assert op.package.source_reference.startswith("9cf87a2")
assert op.initial_package.source_reference == "123456"
def test_solver_git_dependencies_update_skipped(solver, repo, package, installed):
pendulum = get_package("pendulum", "2.0.3")
cleo = get_package("cleo", "1.0.0")
repo.add_package(pendulum)
repo.add_package(cleo)
demo = get_package("demo", "0.1.2")
demo.source_type = "git"
demo.source_url = "https://github.com/demo/demo.git"
demo.source_reference = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24"
installed.add_package(demo)
package.add_dependency("demo", {"git": "https://github.com/demo/demo.git"})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": pendulum},
{
"job": "install",
"package": get_package("demo", "0.1.2"),
"skipped": True,
},
],
)
def test_solver_can_resolve_directory_dependencies(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3")
repo.add_package(pendulum)
path = (
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ "demo"
).as_posix()
package.add_dependency("demo", {"path": path})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.2")},
],
)
op = ops[1]
assert op.package.name == "demo"
assert op.package.version.text == "0.1.2"
assert op.package.source_type == "directory"
assert op.package.source_url == path
def test_solver_can_resolve_directory_dependencies_with_extras(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3")
cleo = get_package("cleo", "1.0.0")
repo.add_package(pendulum)
repo.add_package(cleo)
path = (
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ "demo"
).as_posix()
package.add_dependency("demo", {"path": path, "extras": ["foo"]})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": cleo},
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.2")},
],
)
op = ops[2]
assert op.package.name == "demo"
assert op.package.version.text == "0.1.2"
assert op.package.source_type == "directory"
assert op.package.source_url == path
def test_solver_can_resolve_sdist_dependencies(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3")
repo.add_package(pendulum)
path = (
Path(__file__).parent.parent
/ "fixtures"
/ "distributions"
/ "demo-0.1.0.tar.gz"
).as_posix()
package.add_dependency("demo", {"path": path})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.0")},
],
)
op = ops[1]
assert op.package.name == "demo"
assert op.package.version.text == "0.1.0"
assert op.package.source_type == "file"
assert op.package.source_url == path
def test_solver_can_resolve_sdist_dependencies_with_extras(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3")
cleo = get_package("cleo", "1.0.0")
repo.add_package(pendulum)
repo.add_package(cleo)
path = (
Path(__file__).parent.parent
/ "fixtures"
/ "distributions"
/ "demo-0.1.0.tar.gz"
).as_posix()
package.add_dependency("demo", {"path": path, "extras": ["foo"]})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": cleo},
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.0")},
],
)
op = ops[2]
assert op.package.name == "demo"
assert op.package.version.text == "0.1.0"
assert op.package.source_type == "file"
assert op.package.source_url == path
def test_solver_can_resolve_wheel_dependencies(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3")
repo.add_package(pendulum)
path = (
Path(__file__).parent.parent
/ "fixtures"
/ "distributions"
/ "demo-0.1.0-py2.py3-none-any.whl"
).as_posix()
package.add_dependency("demo", {"path": path})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.0")},
],
)
op = ops[1]
assert op.package.name == "demo"
assert op.package.version.text == "0.1.0"
assert op.package.source_type == "file"
assert op.package.source_url == path
def test_solver_can_resolve_wheel_dependencies_with_extras(solver, repo, package):
pendulum = get_package("pendulum", "2.0.3")
cleo = get_package("cleo", "1.0.0")
repo.add_package(pendulum)
repo.add_package(cleo)
path = (
Path(__file__).parent.parent
/ "fixtures"
/ "distributions"
/ "demo-0.1.0-py2.py3-none-any.whl"
).as_posix()
package.add_dependency("demo", {"path": path, "extras": ["foo"]})
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": cleo},
{"job": "install", "package": pendulum},
{"job": "install", "package": get_package("demo", "0.1.0")},
],
)
op = ops[2]
assert op.package.name == "demo"
assert op.package.version.text == "0.1.0"
assert op.package.source_type == "file"
assert op.package.source_url == path
<!DOCTYPE html>
<html>
<head>
<title>Links for jupyter</title>
</head>
<body>
<h1>Links for jupyter</h1>
<a href="https://files.pythonhosted.org/packages/c9/a9/371d0b8fe37dd231cf4b2cff0a9f0f25e98f3a73c3771742444be27f2944/jupyter-1.0.0.tar.gz#sha256=d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f">jupyter-1.0.0.tar.gz</a><br/>
</body>
</html>
<!--SERIAL 1673841-->
<!DOCTYPE html>
<html>
<head>
<title>Links for python-language-server</title>
</head>
<body>
<h1>Links for python-language-server</h1>
<a href="https://files.pythonhosted.org/packages/9f/1d/2817b5dc2dd77f897410a11c1c9e2a6d96b3273c53d4219dd9edab7882af/python-language-server-0.21.2.tar.gz#sha256=fa9162acb1402b807132d7288b7f521db2bd666d63505d8a4d9464d4b8488c52">python-language-server-0.21.2.tar.gz</a><br/>
</body>
</html>
<!--SERIAL 4245719-->
{
"info": {
"author": "Mike Bayer",
"author_email": "mike_mp@zzzcomputing.com",
"bugtrack_url": null,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Database :: Front-Ends"
],
"description": "",
"description_content_type": "",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "http://www.sqlalchemy.org",
"keywords": "",
"license": "MIT License",
"maintainer": "",
"maintainer_email": "",
"name": "SQLAlchemy",
"package_url": "https://pypi.org/project/SQLAlchemy/",
"platform": "",
"project_url": "https://pypi.org/project/SQLAlchemy/",
"project_urls": {
"Homepage": "http://www.sqlalchemy.org"
},
"release_url": "https://pypi.org/project/SQLAlchemy/1.2.12/",
"requires_dist": null,
"requires_python": "",
"summary": "Database Abstraction Library",
"version": "1.2.12"
},
"last_serial": 4289618,
"releases": {
"1.2.12": [
{
"comment_text": "",
"digests": {
"md5": "3baca105a1e49798d6bc99eb2738cb3b",
"sha256": "c5951d9ef1d5404ed04bae5a16b60a0779087378928f997a294d1229c6ca4d3e"
},
"downloads": -1,
"filename": "SQLAlchemy-1.2.12.tar.gz",
"has_sig": true,
"md5_digest": "3baca105a1e49798d6bc99eb2738cb3b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 5634807,
"upload_time": "2018-09-19T18:14:55",
"url": "https://files.pythonhosted.org/packages/25/c9/b0552098cee325425a61efdf380c51b5c721e459081c85bbb860f501c091/SQLAlchemy-1.2.12.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "3baca105a1e49798d6bc99eb2738cb3b",
"sha256": "c5951d9ef1d5404ed04bae5a16b60a0779087378928f997a294d1229c6ca4d3e"
},
"downloads": -1,
"filename": "SQLAlchemy-1.2.12.tar.gz",
"has_sig": true,
"md5_digest": "3baca105a1e49798d6bc99eb2738cb3b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 5634807,
"upload_time": "2018-09-19T18:14:55",
"url": "https://files.pythonhosted.org/packages/25/c9/b0552098cee325425a61efdf380c51b5c721e459081c85bbb860f501c091/SQLAlchemy-1.2.12.tar.gz"
}
]
}
{
"info": {
"author": "Twisted Matrix Laboratories",
"author_email": "twisted-python@twistedmatrix.com",
"bugtrack_url": null,
"classifiers": [
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7"
],
"description": "description",
"description_content_type": "",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "http://twistedmatrix.com/",
"keywords": "",
"license": "MIT",
"maintainer": "Glyph Lefkowitz",
"maintainer_email": "glyph@twistedmatrix.com",
"name": "Twisted",
"package_url": "https://pypi.org/project/Twisted/",
"platform": "",
"project_url": "https://pypi.org/project/Twisted/",
"project_urls": {
"Homepage": "http://twistedmatrix.com/"
},
"release_url": "https://pypi.org/project/Twisted/18.9.0/",
"requires_dist": null,
"requires_python": "",
"summary": "An asynchronous networking framework written in Python",
"version": "18.9.0"
},
"last_serial": 4376865,
"releases": {
"18.9.0": [
{
"comment_text": "",
"digests": {
"md5": "20fe2ec156e6e45b0b0d2ff06d9e828f",
"sha256": "294be2c6bf84ae776df2fc98e7af7d6537e1c5e60a46d33c3ce2a197677da395"
},
"downloads": -1,
"filename": "Twisted-18.9.0.tar.bz2",
"has_sig": false,
"md5_digest": "20fe2ec156e6e45b0b0d2ff06d9e828f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 3088398,
"upload_time": "2018-10-15T09:11:22",
"url": "https://files.pythonhosted.org/packages/5d/0e/a72d85a55761c2c3ff1cb968143a2fd5f360220779ed90e0fadf4106d4f2/Twisted-18.9.0.tar.bz2"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "20fe2ec156e6e45b0b0d2ff06d9e828f",
"sha256": "294be2c6bf84ae776df2fc98e7af7d6537e1c5e60a46d33c3ce2a197677da395"
},
"downloads": -1,
"filename": "Twisted-18.9.0.tar.bz2",
"has_sig": false,
"md5_digest": "20fe2ec156e6e45b0b0d2ff06d9e828f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 3088398,
"upload_time": "2018-10-15T09:11:22",
"url": "https://files.pythonhosted.org/packages/5d/0e/a72d85a55761c2c3ff1cb968143a2fd5f360220779ed90e0fadf4106d4f2/Twisted-18.9.0.tar.bz2"
}
]
}
from poetry.repositories.installed_repository import InstalledRepository
from poetry.utils.env import MockEnv as BaseMockEnv
FREEZE_RESULTS = """cleo==0.6.8
-e git+https://github.com/sdispater/pendulum.git@bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6#egg=pendulum
orator===0.9.8
"""
class MockEnv(BaseMockEnv):
def run(self, bin, *args):
if bin == "pip" and args[0] == "freeze":
return FREEZE_RESULTS
super(MockEnv, self).run(bin, *args)
def test_load():
repository = InstalledRepository.load(MockEnv())
assert len(repository.packages) == 3
cleo = repository.packages[0]
assert cleo.name == "cleo"
assert cleo.version.text == "0.6.8"
pendulum = repository.packages[1]
assert pendulum.name == "pendulum"
assert pendulum.version.text == "0.0.0"
assert pendulum.source_type == "git"
assert pendulum.source_url == "https://github.com/sdispater/pendulum.git"
assert pendulum.source_reference == "bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6"
orator = repository.packages[2]
assert orator.name == "orator"
assert orator.version.text == "0.9.8"
import pytest import pytest
import shutil
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
from poetry.packages import Dependency
from poetry.repositories.exceptions import PackageNotFound
from poetry.repositories.legacy_repository import LegacyRepository from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.legacy_repository import Page from poetry.repositories.legacy_repository import Page
from poetry.utils._compat import PY35
from poetry.utils._compat import Path from poetry.utils._compat import Path
...@@ -23,6 +32,12 @@ class MockRepository(LegacyRepository): ...@@ -23,6 +32,12 @@ class MockRepository(LegacyRepository):
with fixture.open() as f: with fixture.open() as f:
return Page(self._url + endpoint, f.read(), {}) return Page(self._url + endpoint, f.read(), {})
def _download(self, url, dest):
filename = urlparse.urlparse(url).path.rsplit("/")[-1]
filepath = self.FIXTURES.parent / "pypi.org" / "dists" / filename
shutil.copyfile(str(filepath), dest)
def test_page_relative_links_path_are_correct(): def test_page_relative_links_path_are_correct():
repo = MockRepository() repo = MockRepository()
...@@ -52,8 +67,64 @@ def test_sdist_format_support(): ...@@ -52,8 +67,64 @@ def test_sdist_format_support():
assert bz2_links[0].filename == "poetry-0.1.1.tar.bz2" assert bz2_links[0].filename == "poetry-0.1.1.tar.bz2"
def test_missing_version(mocker): def test_missing_version():
repo = MockRepository() repo = MockRepository()
with pytest.raises(ValueError): with pytest.raises(PackageNotFound):
repo._get_release_info("missing_version", "1.1.0") repo._get_release_info("missing_version", "1.1.0")
def test_get_package_information_fallback_read_setup():
repo = MockRepository()
package = repo.package("jupyter", "1.0.0")
assert package.name == "jupyter"
assert package.version.text == "1.0.0"
assert (
package.description
== "Jupyter metapackage. Install all the Jupyter components in one go."
)
if PY35:
assert package.requires == [
Dependency("notebook", "*"),
Dependency("qtconsole", "*"),
Dependency("jupyter-console", "*"),
Dependency("nbconvert", "*"),
Dependency("ipykernel", "*"),
Dependency("ipywidgets", "*"),
]
def test_get_package_information_skips_dependencies_with_invalid_constraints():
repo = MockRepository()
package = repo.package("python-language-server", "0.21.2")
assert package.name == "python-language-server"
assert package.version.text == "0.21.2"
assert (
package.description == "Python Language Server for the Language Server Protocol"
)
assert sorted(package.requires, key=lambda r: r.name) == [
Dependency("configparser", "*"),
Dependency("future", ">=0.14.0"),
Dependency("futures", "*"),
Dependency("jedi", ">=0.12"),
Dependency("pluggy", "*"),
Dependency("python-jsonrpc-server", "*"),
]
all_extra = package.extras["all"]
# rope>-0.10.5 should be discarded
assert sorted(all_extra, key=lambda r: r.name) == [
Dependency("autopep8", "*"),
Dependency("mccabe", "*"),
Dependency("pycodestyle", "*"),
Dependency("pydocstyle", ">=2.0.0"),
Dependency("pyflakes", ">=1.6.0"),
Dependency("yapf", "*"),
]
import pytest
from poetry.repositories import Pool
from poetry.repositories import Repository
from poetry.repositories.exceptions import PackageNotFound
def test_pool_raises_package_not_found_when_no_package_is_found():
pool = Pool()
pool.add_repository(Repository())
with pytest.raises(PackageNotFound):
pool.package("foo", "1.0.0")
import json import json
import pytest
import shutil import shutil
from poetry.packages import Dependency
from poetry.repositories.pypi_repository import PyPiRepository from poetry.repositories.pypi_repository import PyPiRepository
from poetry.utils._compat import PY35
from poetry.utils._compat import Path from poetry.utils._compat import Path
...@@ -104,3 +107,62 @@ def test_fallback_inspects_sdist_first_if_no_matching_wheels_can_be_found(): ...@@ -104,3 +107,62 @@ def test_fallback_inspects_sdist_first_if_no_matching_wheels_can_be_found():
dep = package.requires[0] dep = package.requires[0]
assert dep.name == "futures" assert dep.name == "futures"
assert dep.python_versions == "~2.7" assert dep.python_versions == "~2.7"
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_fallback_can_read_setup_to_get_dependencies():
repo = MockRepository(fallback=True)
package = repo.package("sqlalchemy", "1.2.12")
assert package.name == "sqlalchemy"
assert len(package.requires) == 0
assert package.extras == {
"mssql_pymssql": [Dependency("pymssql", "*")],
"mssql_pyodbc": [Dependency("pyodbc", "*")],
"mysql": [Dependency("mysqlclient", "*")],
"oracle": [Dependency("cx_oracle", "*")],
"postgresql": [Dependency("psycopg2", "*")],
"postgresql_pg8000": [Dependency("pg8000", "*")],
"postgresql_psycopg2binary": [Dependency("psycopg2-binary", "*")],
"postgresql_psycopg2cffi": [Dependency("psycopg2cffi", "*")],
"pymysql": [Dependency("pymysql", "*")],
}
def test_pypi_repository_supports_reading_bz2_files():
repo = MockRepository(fallback=True)
package = repo.package("twisted", "18.9.0")
assert package.name == "twisted"
assert sorted(package.requires, key=lambda r: r.name) == [
Dependency("attrs", ">=17.4.0"),
Dependency("Automat", ">=0.3.0"),
Dependency("constantly", ">=15.1"),
Dependency("hyperlink", ">=17.1.1"),
Dependency("incremental", ">=16.10.1"),
Dependency("PyHamcrest", ">=1.9.0"),
Dependency("zope.interface", ">=4.4.2"),
]
expected_extras = {
"all_non_platform": [
Dependency("appdirs", ">=1.4.0"),
Dependency("cryptography", ">=1.5"),
Dependency("h2", ">=3.0,<4.0"),
Dependency("idna", ">=0.6,!=2.3"),
Dependency("priority", ">=1.1.0,<2.0"),
Dependency("pyasn1", "*"),
Dependency("pyopenssl", ">=16.0.0"),
Dependency("pyserial", ">=3.0"),
Dependency("service_identity", "*"),
Dependency("soappy", "*"),
]
}
for name, deps in expected_extras.items():
assert expected_extras[name] == sorted(
package.extras[name], key=lambda r: r.name
)
...@@ -64,25 +64,21 @@ def test_poetry(): ...@@ -64,25 +64,21 @@ def test_poetry():
assert demo.is_file() assert demo.is_file()
assert not demo.is_vcs() assert not demo.is_vcs()
assert demo.name == "demo" assert demo.name == "demo"
assert demo.pretty_constraint == "0.1.0" assert demo.pretty_constraint == "*"
demo = dependencies["my-package"] demo = dependencies["my-package"]
assert not demo.is_file() assert not demo.is_file()
assert demo.is_directory() assert demo.is_directory()
assert not demo.is_vcs() assert not demo.is_vcs()
assert demo.name == "my-package" assert demo.name == "my-package"
assert demo.pretty_constraint == "0.1.2" assert demo.pretty_constraint == "*"
assert demo.package.requires[0].name == "pendulum"
assert demo.package.requires[1].name == "cachy"
assert demo.package.requires[1].extras == ["msgpack"]
simple_project = dependencies["simple-project"] simple_project = dependencies["simple-project"]
assert not simple_project.is_file() assert not simple_project.is_file()
assert simple_project.is_directory() assert simple_project.is_directory()
assert not simple_project.is_vcs() assert not simple_project.is_vcs()
assert simple_project.name == "simple-project" assert simple_project.name == "simple-project"
assert simple_project.pretty_constraint == "1.2.3" assert simple_project.pretty_constraint == "*"
assert simple_project.package.requires == []
assert "db" in package.extras assert "db" in package.extras
......
# Note: this requirements.txt file is used to specify what dependencies are
# needed to make the package run rather than for deployment of a tested set of
# packages. Thus, this should be the loosest set possible (only required
# packages, not optional ones, and with the widest range of versions that could
# be suitable)
jinja2
PyYAML
paramiko
cryptography
setuptools
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import re
from collections import OrderedDict
from setuptools import setup
with io.open("README.rst", "rt", encoding="utf8") as f:
readme = f.read()
with io.open("flask/__init__.py", "rt", encoding="utf8") as f:
version = re.search(r"__version__ = \'(.*?)\'", f.read()).group(1)
setup(
name="Flask",
version=version,
url="https://www.palletsprojects.com/p/flask/",
project_urls=OrderedDict(
(
("Documentation", "http://flask.pocoo.org/docs/"),
("Code", "https://github.com/pallets/flask"),
("Issue tracker", "https://github.com/pallets/flask/issues"),
)
),
license="BSD",
author="Armin Ronacher",
author_email="armin.ronacher@active-4.com",
maintainer="Pallets team",
maintainer_email="contact@palletsprojects.com",
description="A simple framework for building complex web applications.",
long_description=readme,
packages=["flask", "flask.json"],
include_package_data=True,
zip_safe=False,
platforms="any",
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
install_requires=[
"Werkzeug>=0.14",
"Jinja2>=2.10",
"itsdangerous>=0.24",
"click>=5.1",
],
extras_require={
"dotenv": ["python-dotenv"],
"dev": [
"pytest>=3",
"coverage",
"tox",
"sphinx",
"pallets-sphinx-themes",
"sphinxcontrib-log-cabinet",
],
"docs": ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet"],
},
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Framework :: Flask",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Topic :: Software Development :: Libraries :: Python Modules",
],
entry_points={"console_scripts": ["flask = flask.cli:main"]},
)
# -*- coding: utf-8 -*-
from distutils.core import setup
packages = [
"pendulum",
"pendulum._extensions",
"pendulum.formatting",
"pendulum.locales",
"pendulum.locales.da",
"pendulum.locales.de",
"pendulum.locales.en",
"pendulum.locales.es",
"pendulum.locales.fa",
"pendulum.locales.fo",
"pendulum.locales.fr",
"pendulum.locales.ko",
"pendulum.locales.lt",
"pendulum.locales.pt_br",
"pendulum.locales.zh",
"pendulum.mixins",
"pendulum.parsing",
"pendulum.parsing.exceptions",
"pendulum.tz",
"pendulum.tz.data",
"pendulum.tz.zoneinfo",
"pendulum.utils",
]
package_data = {"": ["*"]}
install_requires = ["python-dateutil>=2.6,<3.0", "pytzdata>=2018.3"]
extras_require = {':python_version < "3.5"': ["typing>=3.6,<4.0"]}
setup_kwargs = {
"name": "pendulum",
"version": "2.0.4",
"description": "Python datetimes made easy",
"author": "Sébastien Eustace",
"author_email": "sebastien@eustace.io",
"url": "https://pendulum.eustace.io",
"packages": packages,
"package_data": package_data,
"install_requires": install_requires,
"extras_require": extras_require,
"python_requires": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
}
from build import *
build(setup_kwargs)
setup(**setup_kwargs)
#!/usr/bin/env python
# Learn more: https://github.com/kennethreitz/setup.py
import os
import re
import sys
from codecs import open
from setuptools import setup
from setuptools.command.test import test as TestCommand
here = os.path.abspath(os.path.dirname(__file__))
class PyTest(TestCommand):
user_options = [("pytest-args=", "a", "Arguments to pass into py.test")]
def initialize_options(self):
TestCommand.initialize_options(self)
try:
from multiprocessing import cpu_count
self.pytest_args = ["-n", str(cpu_count()), "--boxed"]
except (ImportError, NotImplementedError):
self.pytest_args = ["-n", "1", "--boxed"]
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
import pytest
errno = pytest.main(self.pytest_args)
sys.exit(errno)
# 'setup.py publish' shortcut.
if sys.argv[-1] == "publish":
os.system("python setup.py sdist bdist_wheel")
os.system("twine upload dist/*")
sys.exit()
packages = ["requests"]
requires = [
"chardet>=3.0.2,<3.1.0",
"idna>=2.5,<2.8",
"urllib3>=1.21.1,<1.25",
"certifi>=2017.4.17",
]
test_requirements = [
"pytest-httpbin==0.0.7",
"pytest-cov",
"pytest-mock",
"pytest-xdist",
"PySocks>=1.5.6, !=1.5.7",
"pytest>=2.8.0",
]
about = {}
with open(os.path.join(here, "requests", "__version__.py"), "r", "utf-8") as f:
exec(f.read(), about)
with open("README.md", "r", "utf-8") as f:
readme = f.read()
with open("HISTORY.md", "r", "utf-8") as f:
history = f.read()
setup(
name=about["__title__"],
version=about["__version__"],
description=about["__description__"],
long_description=readme,
long_description_content_type="text/markdown",
author=about["__author__"],
author_email=about["__author_email__"],
url=about["__url__"],
packages=packages,
package_data={"": ["LICENSE", "NOTICE"], "requests": ["*.pem"]},
package_dir={"requests": "requests"},
include_package_data=True,
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
install_requires=requires,
license=about["__license__"],
zip_safe=False,
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Natural Language :: English",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
cmdclass={"test": PyTest},
tests_require=test_requirements,
extras_require={
"security": ["pyOpenSSL >= 0.14", "cryptography>=1.3.4", "idna>=2.0.0"],
"socks": ["PySocks>=1.5.6, !=1.5.7"],
'socks:sys_platform == "win32" and python_version == "2.7"': ["win_inet_pton"],
},
)
import os
import platform
import re
import sys
from distutils.command.build_ext import build_ext
from distutils.errors import CCompilerError
from distutils.errors import DistutilsExecError
from distutils.errors import DistutilsPlatformError
from setuptools import Distribution as _Distribution, Extension
from setuptools import setup
from setuptools import find_packages
from setuptools.command.test import test as TestCommand
cmdclass = {}
if sys.version_info < (2, 7):
raise Exception("SQLAlchemy requires Python 2.7 or higher.")
cpython = platform.python_implementation() == "CPython"
ext_modules = [
Extension(
"sqlalchemy.cprocessors", sources=["lib/sqlalchemy/cextension/processors.c"]
),
Extension(
"sqlalchemy.cresultproxy", sources=["lib/sqlalchemy/cextension/resultproxy.c"]
),
Extension("sqlalchemy.cutils", sources=["lib/sqlalchemy/cextension/utils.c"]),
]
ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError)
if sys.platform == "win32":
# 2.6's distutils.msvc9compiler can raise an IOError when failing to
# find the compiler
ext_errors += (IOError,)
class BuildFailed(Exception):
def __init__(self):
self.cause = sys.exc_info()[1] # work around py 2/3 different syntax
class ve_build_ext(build_ext):
# This class allows C extension building to fail.
def run(self):
try:
build_ext.run(self)
except DistutilsPlatformError:
raise BuildFailed()
def build_extension(self, ext):
try:
build_ext.build_extension(self, ext)
except ext_errors:
raise BuildFailed()
except ValueError:
# this can happen on Windows 64 bit, see Python issue 7511
if "'path'" in str(sys.exc_info()[1]): # works with both py 2/3
raise BuildFailed()
raise
cmdclass["build_ext"] = ve_build_ext
class Distribution(_Distribution):
def has_ext_modules(self):
# We want to always claim that we have ext_modules. This will be fine
# if we don't actually have them (such as on PyPy) because nothing
# will get built, however we don't want to provide an overally broad
# Wheel package when building a wheel without C support. This will
# ensure that Wheel knows to treat us as if the build output is
# platform specific.
return True
class PyTest(TestCommand):
# from http://pytest.org/latest/goodpractices.html\
# #integrating-with-setuptools-python-setup-py-test-pytest-runner
# TODO: prefer pytest-runner package at some point, however it was
# not working at the time of this comment.
user_options = [("pytest-args=", "a", "Arguments to pass to py.test")]
default_options = ["-n", "4", "-q", "--nomemory"]
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = ""
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
import shlex
# import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(self.default_options + shlex.split(self.pytest_args))
sys.exit(errno)
cmdclass["test"] = PyTest
def status_msgs(*msgs):
print("*" * 75)
for msg in msgs:
print(msg)
print("*" * 75)
with open(
os.path.join(os.path.dirname(__file__), "lib", "sqlalchemy", "__init__.py")
) as v_file:
VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(v_file.read()).group(1)
with open(os.path.join(os.path.dirname(__file__), "README.rst")) as r_file:
readme = r_file.read()
def run_setup(with_cext):
kwargs = {}
if with_cext:
kwargs["ext_modules"] = ext_modules
else:
kwargs["ext_modules"] = []
setup(
name="SQLAlchemy",
version=VERSION,
description="Database Abstraction Library",
author="Mike Bayer",
author_email="mike_mp@zzzcomputing.com",
url="http://www.sqlalchemy.org",
packages=find_packages("lib"),
package_dir={"": "lib"},
license="MIT License",
cmdclass=cmdclass,
tests_require=["pytest >= 2.5.2", "mock", "pytest-xdist"],
long_description=readme,
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Database :: Front-Ends",
"Operating System :: OS Independent",
],
distclass=Distribution,
extras_require={
"mysql": ["mysqlclient"],
"pymysql": ["pymysql"],
"postgresql": ["psycopg2"],
"postgresql_pg8000": ["pg8000"],
"postgresql_psycopg2cffi": ["psycopg2cffi"],
"oracle": ["cx_oracle"],
"mssql_pyodbc": ["pyodbc"],
"mssql_pymssql": ["pymssql"],
},
**kwargs
)
if not cpython:
run_setup(False)
status_msgs(
"WARNING: C extensions are not supported on "
+ "this Python platform, speedups are not enabled.",
"Plain-Python build succeeded.",
)
elif os.environ.get("DISABLE_SQLALCHEMY_CEXT"):
run_setup(False)
status_msgs(
"DISABLE_SQLALCHEMY_CEXT is set; " + "not attempting to build C extensions.",
"Plain-Python build succeeded.",
)
else:
try:
run_setup(True)
except BuildFailed as exc:
status_msgs(
exc.cause,
"WARNING: The C extension could not be compiled, "
+ "speedups are not enabled.",
"Failure information, if any, is above.",
"Retrying the build without the C extension now.",
)
run_setup(False)
status_msgs(
"WARNING: The C extension could not be compiled, "
+ "speedups are not enabled.",
"Plain-Python build succeeded.",
)
[metadata]
name = with-setup-cfg
version = 1.2.3
[options]
zip_safe = true
python_requires = >=2.6,!=3.0,!=3.1,!=3.2,!=3.3
setup_requires = setuptools>=36.2.2
install_requires =
six
tomlkit
[options.extras_require]
validation =
cerberus
tests =
pytest
pytest-xdist
pytest-cov
from setuptools import setup
setup()
import os
import pytest
from poetry.utils._compat import PY35
from poetry.utils.setup_reader import SetupReader
@pytest.fixture()
def setup():
def _setup(name):
return os.path.join(os.path.dirname(__file__), "fixtures", "setups", name)
return _setup
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_setup_reader_read_first_level_setup_call_with_direct_types(setup):
result = SetupReader.read_from_directory(setup("flask"))
expected_name = "Flask"
expected_version = None
expected_install_requires = [
"Werkzeug>=0.14",
"Jinja2>=2.10",
"itsdangerous>=0.24",
"click>=5.1",
]
expected_extras_require = {
"dotenv": ["python-dotenv"],
"dev": [
"pytest>=3",
"coverage",
"tox",
"sphinx",
"pallets-sphinx-themes",
"sphinxcontrib-log-cabinet",
],
"docs": ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet"],
}
expected_python_requires = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
assert expected_name == result["name"]
assert expected_version == result["version"]
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"]
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_setup_reader_read_first_level_setup_call_with_variables(setup):
result = SetupReader.read_from_directory(setup("requests"))
expected_name = None
expected_version = None
expected_install_requires = [
"chardet>=3.0.2,<3.1.0",
"idna>=2.5,<2.8",
"urllib3>=1.21.1,<1.25",
"certifi>=2017.4.17",
]
expected_extras_require = {
"security": ["pyOpenSSL >= 0.14", "cryptography>=1.3.4", "idna>=2.0.0"],
"socks": ["PySocks>=1.5.6, !=1.5.7"],
'socks:sys_platform == "win32" and python_version == "2.7"': ["win_inet_pton"],
}
expected_python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
assert expected_name == result["name"]
assert expected_version == result["version"]
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"]
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_setup_reader_read_sub_level_setup_call_with_direct_types(setup):
result = SetupReader.read_from_directory(setup("sqlalchemy"))
expected_name = "SQLAlchemy"
expected_version = None
expected_install_requires = []
expected_extras_require = {
"mysql": ["mysqlclient"],
"pymysql": ["pymysql"],
"postgresql": ["psycopg2"],
"postgresql_pg8000": ["pg8000"],
"postgresql_psycopg2cffi": ["psycopg2cffi"],
"oracle": ["cx_oracle"],
"mssql_pyodbc": ["pyodbc"],
"mssql_pymssql": ["pymssql"],
}
assert expected_name == result["name"]
assert expected_version == result["version"]
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert result["python_requires"] is None
def test_setup_reader_read_setup_cfg(setup):
result = SetupReader.read_from_directory(setup("with-setup-cfg"))
expected_name = "with-setup-cfg"
expected_version = "1.2.3"
expected_install_requires = ["six", "tomlkit"]
expected_extras_require = {
"validation": ["cerberus"],
"tests": ["pytest", "pytest-xdist", "pytest-cov"],
}
expected_python_requires = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3"
assert expected_name == result["name"]
assert expected_version == result["version"]
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"]
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_setup_reader_read_setup_kwargs(setup):
result = SetupReader.read_from_directory(setup("pendulum"))
expected_name = "pendulum"
expected_version = "2.0.4"
expected_install_requires = ["python-dateutil>=2.6,<3.0", "pytzdata>=2018.3"]
expected_extras_require = {':python_version < "3.5"': ["typing>=3.6,<4.0"]}
expected_python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
assert expected_name == result["name"]
assert expected_version == result["version"]
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"]
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_setup_reader_read_setup_call_in_main(setup):
result = SetupReader.read_from_directory(setup("pyyaml"))
expected_name = "PyYAML"
expected_version = "3.13"
expected_install_requires = []
expected_extras_require = {}
expected_python_requires = None
assert expected_name == result["name"]
assert expected_version == result["version"]
assert expected_install_requires == result["install_requires"]
assert expected_extras_require == result["extras_require"]
assert expected_python_requires == result["python_requires"]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment