diff --git a/.cirrus.yml b/.cirrus.yml index 8dbbded95e0..98088aa6e02 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -16,11 +16,9 @@ test_task: - pkg install -y git-lite $PYPACKAGE $SQLPACKAGE pip_script: - $PYTHON -m ensurepip - - $PYTHON -m pip install pip -U - - $PYTHON -m pip install poetry -U --pre - - $PYTHON -m poetry config virtualenvs.in-project true - deps_script: $PYTHON -m poetry install - test_script: $PYTHON -m poetry run pytest -q --junitxml=junit.xml tests + - $PYTHON -m pip install -U pip tox poetry + - poetry config virtualenvs.in-project true + tox_script: $PYTHON -m tox -e py -- -q --junitxml=junit.xml tests on_failure: annotate_failure_artifacts: path: junit.xml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 63a8fb7918f..30d8f5fde21 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: shell: bash run: | curl -fsS -o get-poetry.py https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py - python get-poetry.py --preview -y + python get-poetry.py -y echo "::set-env name=PATH::$HOME/.poetry/bin:$PATH" - name: Configure poetry diff --git a/poetry/inspection/__init__.py b/poetry/inspection/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/poetry/inspection/info.py b/poetry/inspection/info.py new file mode 100644 index 00000000000..50ef2793e5b --- /dev/null +++ b/poetry/inspection/info.py @@ -0,0 +1,499 @@ +import glob +import logging +import os +import tarfile +import zipfile + +from typing import Dict +from typing import Iterator +from typing import List +from typing import Optional +from typing import Union + +import pkginfo + +from poetry.core.factory import Factory +from poetry.core.packages import Package +from poetry.core.packages import ProjectPackage +from poetry.core.packages import dependency_from_pep_508 +from poetry.core.utils._compat import PY35 +from poetry.core.utils._compat import Path +from poetry.core.utils.helpers import parse_requires +from poetry.core.utils.helpers import temporary_directory +from poetry.core.version.markers import InvalidMarker +from poetry.utils.env import EnvCommandError +from poetry.utils.env import EnvManager +from poetry.utils.env import VirtualEnv +from poetry.utils.setup_reader import SetupReader +from poetry.utils.toml_file import TomlFile + + +logger = logging.getLogger(__name__) + + +class PackageInfoError(ValueError): + def __init__(self, path): # type: (Union[Path, str]) -> None + super(PackageInfoError, self).__init__( + "Unable to determine package info for path: {}".format(str(path)) + ) + + +class PackageInfo: + def __init__( + self, + name=None, # type: Optional[str] + version=None, # type: Optional[str] + summary=None, # type: Optional[str] + platform=None, # type: Optional[str] + requires_dist=None, # type: Optional[List[str]] + requires_python=None, # type: Optional[str] + files=None, # type: Optional[List[str]] + cache_version=None, # type: Optional[str] + ): + self.name = name + self.version = version + self.summary = summary + self.platform = platform + self.requires_dist = requires_dist + self.requires_python = requires_python + self.files = files or [] + self._cache_version = cache_version + + @property + def cache_version(self): # type: () -> Optional[str] + return self._cache_version + + def update(self, other): # type: (PackageInfo) -> PackageInfo + self.name = other.name or self.name + self.version = other.version or self.version + self.summary = other.summary or self.summary + self.platform = other.platform or self.platform + self.requires_dist = other.requires_dist or self.requires_dist + self.requires_python = other.requires_python or self.requires_python + self.files = other.files or self.files + self._cache_version = other.cache_version or self._cache_version + return self + + def asdict(self): # type: () -> Dict[str, Optional[Union[str, List[str]]]] + """ + Helper method to convert package info into a dictionary used for caching. + """ + return { + "name": self.name, + "version": self.version, + "summary": self.summary, + "platform": self.platform, + "requires_dist": self.requires_dist, + "requires_python": self.requires_python, + "files": self.files, + "_cache_version": self._cache_version, + } + + @classmethod + def load( + cls, data + ): # type: (Dict[str, Optional[Union[str, List[str]]]]) -> PackageInfo + """ + Helper method to load data from a dictionary produced by `PackageInfo.asdict()`. + + :param data: Data to load. This is expected to be a `dict` object output by `asdict()`. + """ + cache_version = data.pop("_cache_version", None) + return cls(cache_version=cache_version, **data) + + @classmethod + def _log(cls, msg, level="info"): + """Internal helper method to log information.""" + getattr(logger, level)("{}: {}".format(cls.__name__, msg)) + + def to_package( + self, name=None, extras=None, root_dir=None + ): # type: (Optional[str], Optional[List[str]], Optional[Path]) -> Package + """ + Create a new `poetry.core.packages.package.Package` instance using metadata from this instance. + + :param name: Name to use for the package, if not specified name from this instance is used. + :param extras: Extras to activate for this package. + :param root_dir: Optional root directory to use for the package. If set, dependency strings + will be parsed relative to this directory. + """ + name = name or self.name + + if not self.version: + # The version could not be determined, so we raise an error since it is mandatory. + raise RuntimeError( + "Unable to retrieve the package version for {}".format(name) + ) + + package = Package(name=name, version=self.version) + package.description = self.summary + package.root_dir = root_dir + package.python_versions = self.requires_python or "*" + package.files = self.files + + for req in self.requires_dist or []: + try: + # Attempt to parse the PEP-508 requirement string + dependency = dependency_from_pep_508(req, relative_to=root_dir) + except InvalidMarker: + # Invalid marker, We strip the markers hoping for the best + req = req.split(";")[0] + dependency = dependency_from_pep_508(req, relative_to=root_dir) + 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.in_extras: + # this dependency is required by an extra package + for extra in dependency.in_extras: + if extra not in package.extras: + # this is the first time we encounter this extra for this package + package.extras[extra] = [] + + # Activate extra dependencies if specified + if extras and extra in extras: + dependency.activate() + + package.extras[extra].append(dependency) + + if not dependency.is_optional() or dependency.is_activated(): + # we skip add only if the dependency is option and was not activated as part of an extra + package.requires.append(dependency) + + return package + + @classmethod + def _from_distribution( + cls, dist + ): # type: (Union[pkginfo.BDist, pkginfo.SDist, pkginfo.Wheel]) -> PackageInfo + """ + Helper method to parse package information from a `pkginfo.Distribution` instance. + + :param dist: The distribution instance to parse information from. + """ + if dist.requires_dist: + requirements = list(dist.requires_dist) + else: + requirements = [] + requires = Path(dist.filename) / "requires.txt" + if requires.exists(): + with requires.open(encoding="utf-8") as f: + requirements = parse_requires(f.read()) + + return cls( + name=dist.name, + version=dist.version, + summary=dist.summary, + platform=dist.supported_platforms, + requires_dist=requirements or None, + requires_python=dist.requires_python, + ) + + @classmethod + def _from_sdist_file(cls, path): # type: (Path) -> PackageInfo + """ + Helper method to parse package information from an sdist file. We attempt to first inspect the + file using `pkginfo.SDist`. If this does not provide us with package requirements, we extract the + source and handle it as a directory. + + :param path: The sdist file to parse information from. + """ + info = None + + try: + info = cls._from_distribution(pkginfo.SDist(str(path))) + except ValueError: + # Unable to determine dependencies + # We pass and go deeper + pass + else: + if info.requires_dist is not None: + # we successfully retrieved dependencies from sdist metadata + return info + + # Still not dependencies found + # So, we unpack and introspect + suffix = path.suffix + + if suffix == ".zip": + context = zipfile.ZipFile + else: + if suffix == ".bz2": + suffixes = path.suffixes + if len(suffixes) > 1 and suffixes[-2] == ".tar": + suffix = ".tar.bz2" + else: + suffix = ".tar.gz" + + context = tarfile.open + + with temporary_directory() as tmp: + tmp = Path(tmp) + with context(str(path)) as archive: + archive.extractall(str(tmp)) + + # a little bit of guess work to determine the directory we care about + elements = list(tmp.glob("*")) + + if len(elements) == 1 and elements[0].is_dir(): + sdist_dir = elements[0] + else: + sdist_dir = tmp / path.name.rstrip(suffix) + + # now this is an unpacked directory we know how to deal with + new_info = cls.from_directory(path=sdist_dir) + + if not info: + return new_info + + return info.update(new_info) + + @classmethod + def from_setup_py(cls, path): # type: (Union[str, Path]) -> PackageInfo + """ + Mechanism to parse package information from a `setup.py` file. This uses the implentation + at `poetry.utils.setup_reader.SetupReader` in order to parse the file. This is not reliable for + complex setup files and should only attempted as a fallback. + + :param path: Path to `setup.py` file + :return: + """ + result = SetupReader.read_from_directory(Path(path)) + python_requires = result["python_requires"] + if python_requires is None: + python_requires = "*" + + requires = "" + for dep in result["install_requires"]: + requires += dep + "\n" + + if result["extras_require"]: + requires += "\n" + + for extra_name, deps in result["extras_require"].items(): + requires += "[{}]\n".format(extra_name) + + for dep in deps: + requires += dep + "\n" + + requires += "\n" + + requirements = parse_requires(requires) + + return cls( + name=result.get("name"), + version=result.get("version"), + summary=result.get("description", ""), + requires_dist=requirements or None, + requires_python=python_requires, + ) + + @staticmethod + def _find_dist_info(path): # type: (Path) -> Iterator[Path] + """ + Discover all `*.*-info` directories in a given path. + + :param path: Path to search. + """ + pattern = "**/*.*-info" + if PY35: + # Sometimes pathlib will fail on recursive symbolic links, so we need to workaround it + # and use the glob module instead. Note that this does not happen with pathlib2 + # so it's safe to use it for Python < 3.4. + directories = glob.iglob(Path(path, pattern).as_posix(), recursive=True) + else: + directories = path.glob(pattern) + + for d in directories: + yield Path(d) + + @classmethod + def from_metadata(cls, path): # type: (Union[str, Path]) -> PackageInfo + """ + Helper method to parse package information from an unpacked metadata directory. + + :param path: The metadata directory to parse information from. + """ + path = Path(path) + + if path.suffix in {".dist-info", ".egg-info"}: + directories = [path.suffix] + else: + directories = cls._find_dist_info(path=path) + + for directory in directories: + try: + if directory.suffix == ".egg-info": + dist = pkginfo.UnpackedSDist(str(directory)) + elif directory.suffix == ".dist-info": + dist = pkginfo.Wheel(str(directory)) + else: + continue + info = cls._from_distribution(dist=dist) + if info: + return info + except ValueError: + pass + + @classmethod + def from_package(cls, package): # type: (Package) -> PackageInfo + """ + Helper method to inspect a `Package` object, in order to generate package info. + + :param package: This must be a poetry package instance. + """ + requires = {dependency.to_pep_508() for dependency in package.requires} + + for extra_requires in package.extras.values(): + for dependency in extra_requires: + requires.add(dependency.to_pep_508()) + + return cls( + name=package.name, + version=package.version, + summary=package.description, + platform=package.platform, + requires_dist=list(requires), + requires_python=package.python_versions, + files=package.files, + ) + + @staticmethod + def _get_poetry_package(path): # type: (Path) -> Optional[ProjectPackage] + pyproject = path.joinpath("pyproject.toml") + try: + # Note: we ignore any setup.py file at this step + if pyproject.exists(): + # TODO: add support for handling non-poetry PEP-517 builds + pyproject = TomlFile(pyproject) + pyproject_content = pyproject.read() + supports_poetry = ( + "tool" in pyproject_content + and "poetry" in pyproject_content["tool"] + ) + if supports_poetry: + return Factory().create_poetry(path).package + except RuntimeError: + pass + + @classmethod + def from_directory( + cls, path, allow_build=False + ): # type: (Union[str, Path], bool) -> PackageInfo + """ + Generate package information from a package source directory. When `allow_build` is enabled and + introspection of all available metadata fails, the package is attempted to be build in an isolated + environment so as to generate required metadata. + + :param path: Path to generate package information from. + :param allow_build: If enabled, as a fallback, build the project to gather metadata. + """ + path = Path(path) + + setup_py = path.joinpath("setup.py") + + project_package = cls._get_poetry_package(path) + if project_package: + return cls.from_package(project_package) + + if not setup_py.exists(): + # this means we cannot do anything else here + raise PackageInfoError(path) + + current_dir = os.getcwd() + + info = cls.from_metadata(path) + + if info and info.requires_dist is not None: + # return only if requirements are discovered + return info + + if not allow_build: + return cls.from_setup_py(path=path) + + try: + # TODO: replace with PEP517 + # we need to switch to the correct path in order for egg_info command to work + os.chdir(str(path)) + + # Execute egg_info + with temporary_directory() as tmp_dir: + EnvManager.build_venv(tmp_dir) + venv = VirtualEnv(Path(tmp_dir), Path(tmp_dir)) + venv.run("python", "setup.py", "egg_info") + except EnvCommandError: + cls._log( + "Falling back to parsing setup.py file for {}".format(path), "debug" + ) + # egg_info could not be generated, we fallback to ast parser + return cls.from_setup_py(path=path) + else: + info = cls.from_metadata(path) + if info: + return info + finally: + os.chdir(current_dir) + + # if we reach here, everything has failed and all hope is lost + raise PackageInfoError(path) + + @classmethod + def from_sdist(cls, path): # type: (Union[Path, pkginfo.SDist]) -> PackageInfo + """ + Gather package information from an sdist file, packed or unpacked. + + :param path: Path to an sdist file or unpacked directory. + """ + if path.is_file(): + return cls._from_sdist_file(path=path) + + # if we get here then it is neither an sdist instance nor a file + # so, we assume this is an directory + return cls.from_directory(path=path) + + @classmethod + def from_wheel(cls, path): # type: (Path) -> PackageInfo + """ + Gather package information from a wheel. + + :param path: Path to wheel. + """ + try: + return cls._from_distribution(pkginfo.Wheel(str(path))) + except ValueError: + return PackageInfo() + + @classmethod + def from_bdist(cls, path): # type: (Path) -> PackageInfo + """ + Gather package information from a bdist (wheel etc.). + + :param path: Path to bdist. + """ + if isinstance(path, (pkginfo.BDist, pkginfo.Wheel)): + cls._from_distribution(dist=path) + + if path.suffix == ".whl": + return cls.from_wheel(path=path) + + try: + return cls._from_distribution(pkginfo.BDist(str(path))) + except ValueError: + raise PackageInfoError(path) + + @classmethod + def from_path(cls, path): # type: (Path) -> PackageInfo + """ + Gather package information from a given path (bdist, sdist, directory). + + :param path: Path to inspect. + """ + try: + return cls.from_bdist(path=path) + except PackageInfoError: + return cls.from_sdist(path=path) diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 70c26d42618..71266538330 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -1,4 +1,3 @@ -import glob import logging import os import re @@ -10,8 +9,6 @@ from typing import List from typing import Optional -import pkginfo - from clikit.ui.components import ProgressIndicator from poetry.core.packages import Dependency @@ -20,33 +17,25 @@ from poetry.core.packages import Package from poetry.core.packages import URLDependency from poetry.core.packages import VCSDependency -from poetry.core.packages import dependency_from_pep_508 from poetry.core.packages.utils.utils import get_python_constraint_from_marker from poetry.core.vcs.git import Git from poetry.core.version.markers import MarkerUnion -from poetry.factory import Factory +from poetry.inspection.info import PackageInfo +from poetry.inspection.info import PackageInfoError from poetry.mixology.incompatibility import Incompatibility from poetry.mixology.incompatibility_cause import DependencyCause from poetry.mixology.incompatibility_cause import PythonCause from poetry.mixology.term import Term from poetry.packages import DependencyPackage from poetry.packages.package_collection import PackageCollection +from poetry.puzzle.exceptions import OverrideNeeded from poetry.repositories import Pool -from poetry.utils._compat import PY35 from poetry.utils._compat import OrderedDict from poetry.utils._compat import Path from poetry.utils._compat import urlparse -from poetry.utils.env import EnvCommandError -from poetry.utils.env import EnvManager -from poetry.utils.env import VirtualEnv -from poetry.utils.helpers import parse_requires +from poetry.utils.helpers import download_file from poetry.utils.helpers import safe_rmtree from poetry.utils.helpers import temporary_directory -from poetry.utils.inspector import Inspector -from poetry.utils.setup_reader import SetupReader -from poetry.utils.toml_file import TomlFile - -from .exceptions import OverrideNeeded logger = logging.getLogger(__name__) @@ -67,7 +56,6 @@ def __init__(self, package, pool, io): # type: (Package, Pool, Any) -> None self._package = package self._pool = pool self._io = io - self._inspector = Inspector() self._python_constraint = package.python_constraint self._search_for = {} self._is_debugging = self._io.is_debug() or self._io.is_very_verbose() @@ -244,31 +232,18 @@ def search_for_file(self, dependency): # type: (FileDependency) -> List[Package @classmethod def get_package_from_file(cls, file_path): # type: (Path) -> Package - info = Inspector().inspect(file_path) - if not info["name"]: + try: + package = PackageInfo.from_path(path=file_path).to_package( + root_dir=file_path + ) + except PackageInfoError: raise RuntimeError( - "Unable to determine the package name of {}".format(file_path) + "Unable to determine package info from path: {}".format(file_path) ) - package = Package(info["name"], info["version"]) package.source_type = "file" package.source_url = file_path.as_posix() - package.description = info["summary"] - for req in info["requires_dist"]: - dep = dependency_from_pep_508(req) - for extra in dep.in_extras: - if extra not in package.extras: - package.extras[extra] = [] - - package.extras[extra].append(dep) - - if not dep.is_optional(): - package.requires.append(dep) - - if info["requires_python"]: - package.python_versions = info["requires_python"] - return package def search_for_directory( @@ -297,136 +272,9 @@ def search_for_directory( def get_package_from_directory( cls, directory, name=None ): # type: (Path, Optional[str]) -> Package - supports_poetry = False - pyproject = directory.joinpath("pyproject.toml") - if pyproject.exists(): - pyproject = TomlFile(pyproject) - pyproject_content = pyproject.read() - supports_poetry = ( - "tool" in pyproject_content and "poetry" in pyproject_content["tool"] - ) - - if supports_poetry: - poetry = Factory().create_poetry(directory) - - pkg = poetry.package - package = Package(pkg.name, pkg.version) - - for dep in pkg.requires: - if not dep.is_optional(): - package.requires.append(dep) - - for extra, deps in pkg.extras.items(): - if extra not in package.extras: - package.extras[extra] = [] - - for dep in deps: - package.extras[extra].append(dep) - - package.python_versions = pkg.python_versions - else: - # Execute egg_info - current_dir = os.getcwd() - os.chdir(str(directory)) - - try: - with temporary_directory() as tmp_dir: - EnvManager.build_venv(tmp_dir) - venv = VirtualEnv(Path(tmp_dir), Path(tmp_dir)) - venv.run("python", "setup.py", "egg_info") - except EnvCommandError: - result = SetupReader.read_from_directory(directory) - if not result["name"]: - # The name could not be determined - # We use the dependency name - result["name"] = name - - if not result["version"]: - # The version could not be determined - # so we raise an error since it is mandatory - raise RuntimeError( - "Unable to retrieve the package version for {}".format( - directory - ) - ) - - package_name = result["name"] - package_version = result["version"] - python_requires = result["python_requires"] - if python_requires is None: - python_requires = "*" - - package_summary = "" - - requires = "" - for dep in result["install_requires"]: - requires += dep + "\n" - - if result["extras_require"]: - requires += "\n" - - for extra_name, deps in result["extras_require"].items(): - requires += "[{}]\n".format(extra_name) - - for dep in deps: - requires += dep + "\n" - - requires += "\n" - - reqs = parse_requires(requires) - else: - os.chdir(current_dir) - # Sometimes pathlib will fail on recursive - # symbolic links, so we need to workaround it - # and use the glob module instead. - # Note that this does not happen with pathlib2 - # so it's safe to use it for Python < 3.4. - if PY35: - egg_info = next( - Path(p) - for p in glob.glob( - os.path.join(str(directory), "**", "*.egg-info"), - recursive=True, - ) - ) - else: - egg_info = next(directory.glob("**/*.egg-info")) - - meta = pkginfo.UnpackedSDist(str(egg_info)) - package_name = meta.name - package_version = meta.version - package_summary = meta.summary - python_requires = meta.requires_python - - if meta.requires_dist: - reqs = list(meta.requires_dist) - else: - reqs = [] - requires = egg_info / "requires.txt" - if requires.exists(): - with requires.open(encoding="utf-8") as f: - reqs = parse_requires(f.read()) - finally: - os.chdir(current_dir) - - package = Package(package_name, package_version) - package.description = package_summary - - for req in reqs: - dep = dependency_from_pep_508(req) - if dep.in_extras: - for extra in dep.in_extras: - if extra not in package.extras: - package.extras[extra] = [] - - package.extras[extra].append(dep) - - if not dep.is_optional(): - package.requires.append(dep) - - if python_requires: - package.python_versions = python_requires - + package = PackageInfo.from_directory( + path=directory, allow_build=True + ).to_package(root_dir=directory) if name and name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( @@ -465,7 +313,7 @@ def get_package_from_url(cls, url): # type: (str) -> Package with temporary_directory() as temp_dir: temp_dir = Path(temp_dir) file_name = os.path.basename(urlparse.urlparse(url).path) - Inspector().download(url, temp_dir / file_name) + download_file(url, temp_dir / file_name) package = cls.get_package_from_file(temp_dir / file_name) diff --git a/poetry/repositories/legacy_repository.py b/poetry/repositories/legacy_repository.py index c441d17817c..7baf97ccf86 100644 --- a/poetry/repositories/legacy_repository.py +++ b/poetry/repositories/legacy_repository.py @@ -14,19 +14,17 @@ from cachy import CacheManager from poetry.core.packages import Package -from poetry.core.packages import dependency_from_pep_508 from poetry.core.packages.utils.link import Link from poetry.core.semver import Version from poetry.core.semver import VersionConstraint from poetry.core.semver import VersionRange from poetry.core.semver import parse_constraint -from poetry.core.version.markers import InvalidMarker from poetry.locations import REPOSITORY_CACHE_DIR from poetry.utils._compat import Path from poetry.utils.helpers import canonicalize_name -from poetry.utils.inspector import Inspector from poetry.utils.patterns import wheel_file_re +from ..inspection.info import PackageInfo from .auth import Auth from .exceptions import PackageNotFound from .pypi_repository import PyPiRepository @@ -171,7 +169,6 @@ def __init__( self._auth = auth self._client_cert = client_cert self._cert = cert - self._inspector = Inspector() self._cache_dir = REPOSITORY_CACHE_DIR / name self._cache = CacheManager( { @@ -298,63 +295,8 @@ def package(self, name, version, extras=None): # type: (...) -> Package return self._packages[index] except ValueError: - if extras is None: - extras = [] - - release_info = self.get_release_info(name, version) - - package = Package(name, version, version) - if release_info["requires_python"]: - package.python_versions = release_info["requires_python"] - + package = super(LegacyRepository, self).package(name, version, extras) package.source_url = self._url - package.source_reference = self.name - - requires_dist = release_info["requires_dist"] or [] - for req in requires_dist: - try: - dependency = dependency_from_pep_508(req) - except InvalidMarker: - # Invalid marker - # We strip the markers hoping for the best - req = req.split(";")[0] - - 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.in_extras: - for extra in dependency.in_extras: - if extra not in package.extras: - package.extras[extra] = [] - - package.extras[extra].append(dependency) - - if not dependency.is_optional(): - package.requires.append(dependency) - - # Adding description - package.description = release_info.get("summary", "") - - # Adding hashes information - package.files = release_info["files"] - - # Activate extra dependencies - for extra in extras: - if extra in package.extras: - for dep in package.extras[extra]: - dep.activate() - - package.requires += package.extras[extra] - - self._packages.append(package) - return package def _get_release_info(self, name, version): # type: (str, str) -> dict @@ -362,15 +304,16 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict if page is None: raise PackageNotFound('No package named "{}"'.format(name)) - data = { - "name": name, - "version": version, - "summary": "", - "requires_dist": [], - "requires_python": None, - "files": [], - "_cache_version": str(self.CACHE_VERSION), - } + data = PackageInfo( + name=name, + version=version, + summary="", + platform=None, + requires_dist=[], + requires_python=None, + files=[], + cache_version=str(self.CACHE_VERSION), + ) links = list(page.links_for_version(Version.parse(version))) if not links: @@ -394,26 +337,19 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict h = link.hash_name + ":" + link.hash files.append({"file": link.filename, "hash": h}) - data["files"] = files + data.files = files info = self._get_info_from_urls(urls) - data["summary"] = info["summary"] - data["requires_dist"] = info["requires_dist"] - data["requires_python"] = info["requires_python"] - - return data + data.summary = info.summary + data.requires_dist = info.requires_dist + data.requires_python = info.requires_python - def _download(self, url, dest): # type: (str, str) -> None - r = self._session.get(url, stream=True) - with open(dest, "wb") as f: - for chunk in r.iter_content(chunk_size=1024): - if chunk: - f.write(chunk) + return data.asdict() def _get(self, endpoint): # type: (str) -> Union[Page, None] url = self._url + endpoint - response = self._session.get(url) + response = self.session.get(url) if response.status_code == 404: return diff --git a/poetry/repositories/pypi_repository.py b/poetry/repositories/pypi_repository.py index e110da77879..e53a2af555e 100644 --- a/poetry/repositories/pypi_repository.py +++ b/poetry/repositories/pypi_repository.py @@ -6,14 +6,13 @@ from typing import List from typing import Union +import requests + from cachecontrol import CacheControl from cachecontrol.caches.file_cache import FileCache from cachecontrol.controller import logger as cache_control_logger from cachy import CacheManager from html5lib.html5parser import parse -from requests import get -from requests import session -from requests.exceptions import TooManyRedirects from poetry.core.packages import Package from poetry.core.packages import dependency_from_pep_508 @@ -22,15 +21,15 @@ from poetry.core.semver import VersionRange from poetry.core.semver import parse_constraint from poetry.core.semver.exceptions import ParseVersionError -from poetry.core.version.markers import InvalidMarker from poetry.core.version.markers import parse_marker from poetry.locations import REPOSITORY_CACHE_DIR from poetry.utils._compat import Path from poetry.utils._compat import to_str +from poetry.utils.helpers import download_file from poetry.utils.helpers import temporary_directory -from poetry.utils.inspector import Inspector from poetry.utils.patterns import wheel_file_re +from ..inspection.info import PackageInfo from .exceptions import PackageNotFound from .remote_repository import RemoteRepository @@ -70,11 +69,16 @@ def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True): ) self._cache_control_cache = FileCache(str(release_cache_dir / "_http")) - self._session = CacheControl(session(), cache=self._cache_control_cache) - self._inspector = Inspector() + self._session = CacheControl( + requests.session(), cache=self._cache_control_cache + ) self._name = "PyPI" + @property + def session(self): + return self._session + def find_packages( self, name, # type: str @@ -154,69 +158,15 @@ def package( name, # type: str version, # type: str extras=None, # type: (Union[list, None]) - ): # type: (...) -> Union[Package, None] - if extras is None: - extras = [] - - release_info = self.get_release_info(name, version) - package = Package(name, version, version) - requires_dist = release_info["requires_dist"] or [] - for req in requires_dist: - try: - dependency = dependency_from_pep_508(req) - except InvalidMarker: - # Invalid marker - # We strip the markers hoping for the best - req = req.split(";")[0] - - 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.in_extras: - for extra in dependency.in_extras: - if extra not in package.extras: - package.extras[extra] = [] - - package.extras[extra].append(dependency) - - if not dependency.is_optional(): - package.requires.append(dependency) - - # Adding description - package.description = release_info.get("summary", "") - - if release_info["requires_python"]: - package.python_versions = release_info["requires_python"] - - if release_info["platform"]: - package.platform = release_info["platform"] - - # Adding hashes information - package.files = release_info["files"] - - # Activate extra dependencies - for extra in extras: - if extra in package.extras: - for dep in package.extras[extra]: - dep.activate() - - package.requires += package.extras[extra] - - return package + ): # type: (...) -> Package + return self.get_release_info(name, version).to_package(name=name, extras=extras) def search(self, query): results = [] search = {"q": query} - response = session().get(self._base_url + "search", params=search) + response = requests.session().get(self._base_url + "search", params=search) content = parse(response.content, namespaceHTMLElements=False) for result in content.findall(".//*[@class='package-snippet']"): name = result.find("h3/*[@class='package-snippet__name']").text @@ -264,7 +214,7 @@ def _get_package_info(self, name): # type: (str) -> dict return data - def get_release_info(self, name, version): # type: (str, str) -> dict + def get_release_info(self, name, version): # type: (str, str) -> PackageInfo """ Return the release information given a package name and a version. @@ -272,7 +222,7 @@ def get_release_info(self, name, version): # type: (str, str) -> dict or retrieved from the remote server. """ if self._disable_cache: - return self._get_release_info(name, version) + return PackageInfo.load(self._get_release_info(name, version)) cached = self._cache.remember_forever( "{}:{}".format(name, version), lambda: self._get_release_info(name, version) @@ -289,7 +239,7 @@ def get_release_info(self, name, version): # type: (str, str) -> dict self._cache.forever("{}:{}".format(name, version), cached) - return cached + return PackageInfo.load(cached) def _get_release_info(self, name, version): # type: (str, str) -> dict self._log("Getting info for {} ({}) from PyPI".format(name, version), "debug") @@ -299,16 +249,17 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict raise PackageNotFound("Package [{}] not found.".format(name)) info = json_data["info"] - data = { - "name": info["name"], - "version": info["version"], - "summary": info["summary"], - "platform": info["platform"], - "requires_dist": info["requires_dist"], - "requires_python": info["requires_python"], - "files": [], - "_cache_version": str(self.CACHE_VERSION), - } + + data = PackageInfo( + name=info["name"], + version=info["version"], + summary=info["summary"], + platform=info["platform"], + requires_dist=info["requires_dist"], + requires_python=info["requires_python"], + files=info.get("files", []), + cache_version=str(self.CACHE_VERSION), + ) try: version_info = json_data["releases"][version] @@ -316,14 +267,14 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict version_info = [] for file_info in version_info: - data["files"].append( + data.files.append( { "file": file_info["filename"], "hash": "sha256:" + file_info["digests"]["sha256"], } ) - if self._fallback and data["requires_dist"] is None: + if self._fallback and data.requires_dist is None: self._log("No dependencies found, downloading archives", level="debug") # No dependencies set (along with other information) # This might be due to actually no dependencies @@ -340,25 +291,25 @@ def _get_release_info(self, name, version): # type: (str, str) -> dict urls[dist_type].append(url["url"]) if not urls: - return data + return data.asdict() info = self._get_info_from_urls(urls) - data["requires_dist"] = info["requires_dist"] + data.requires_dist = info.requires_dist - if not data["requires_python"]: - data["requires_python"] = info["requires_python"] + if not data.requires_python: + data.requires_python = info.requires_python - return data + return data.asdict() def _get(self, endpoint): # type: (str) -> Union[dict, None] try: - json_response = self._session.get(self._base_url + endpoint) - except TooManyRedirects: + json_response = self.session.get(self._base_url + endpoint) + except requests.exceptions.TooManyRedirects: # Cache control redirect loop. # We try to remove the cache and try again self._cache_control_cache.delete(self._base_url + endpoint) - json_response = self._session.get(self._base_url + endpoint) + json_response = self.session.get(self._base_url + endpoint) if json_response.status_code == 404: return None @@ -367,9 +318,7 @@ def _get(self, endpoint): # type: (str) -> Union[dict, None] return json_data - def _get_info_from_urls( - self, urls - ): # type: (Dict[str, List[str]]) -> Dict[str, Union[str, List, None]] + def _get_info_from_urls(self, urls): # type: (Dict[str, List[str]]) -> PackageInfo # Checking wheels first as they are more likely to hold # the necessary information if "bdist_wheel" in urls: @@ -404,24 +353,24 @@ def _get_info_from_urls( if universal_wheel is not None: return self._get_info_from_wheel(universal_wheel) - info = {} + info = None if universal_python2_wheel and universal_python3_wheel: info = self._get_info_from_wheel(universal_python2_wheel) py3_info = self._get_info_from_wheel(universal_python3_wheel) - if py3_info["requires_dist"]: - if not info["requires_dist"]: - info["requires_dist"] = py3_info["requires_dist"] + if py3_info.requires_dist: + if not info.requires_dist: + info.requires_dist = py3_info.requires_dist return info py2_requires_dist = set( dependency_from_pep_508(r).to_pep_508() - for r in info["requires_dist"] + for r in info.requires_dist ) py3_requires_dist = set( dependency_from_pep_508(r).to_pep_508() - for r in py3_info["requires_dist"] + for r in py3_info.requires_dist ) base_requires_dist = py2_requires_dist & py3_requires_dist py2_only_requires_dist = py2_requires_dist - py3_requires_dist @@ -443,7 +392,7 @@ def _get_info_from_urls( ) requires_dist.append(dep.to_pep_508()) - info["requires_dist"] = sorted(list(set(requires_dist))) + info.requires_dist = sorted(list(set(requires_dist))) if info: return info @@ -461,9 +410,7 @@ def _get_info_from_urls( return self._get_info_from_sdist(urls["sdist"][0]) - def _get_info_from_wheel( - self, url - ): # type: (str) -> Dict[str, Union[str, List, None]] + def _get_info_from_wheel(self, url): # type: (str) -> PackageInfo self._log( "Downloading wheel: {}".format(urlparse.urlparse(url).path.rsplit("/")[-1]), level="debug", @@ -475,11 +422,9 @@ def _get_info_from_wheel( filepath = Path(temp_dir) / filename self._download(url, str(filepath)) - return self._inspector.inspect_wheel(filepath) + return PackageInfo.from_wheel(filepath) - def _get_info_from_sdist( - self, url - ): # type: (str) -> Dict[str, Union[str, List, None]] + def _get_info_from_sdist(self, url): # type: (str) -> PackageInfo self._log( "Downloading sdist: {}".format(urlparse.urlparse(url).path.rsplit("/")[-1]), level="debug", @@ -491,16 +436,10 @@ def _get_info_from_sdist( filepath = Path(temp_dir) / filename self._download(url, str(filepath)) - return self._inspector.inspect_sdist(filepath) + return PackageInfo.from_sdist(filepath) def _download(self, url, dest): # type: (str, str) -> None - r = get(url, stream=True) - r.raise_for_status() - - with open(dest, "wb") as f: - for chunk in r.iter_content(chunk_size=1024): - if chunk: - f.write(chunk) + return download_file(url, dest, session=self.session) def _log(self, msg, level="info"): getattr(logger, level)("{}: {}".format(self._name, msg)) diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index 7b9af3814ca..ca0f43f8c78 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -5,9 +5,10 @@ import tempfile from contextlib import contextmanager -from typing import List from typing import Optional +import requests + from poetry.config.config import Config from poetry.core.version import Version from poetry.utils._compat import Path @@ -49,47 +50,6 @@ def temporary_directory(*args, **kwargs): shutil.rmtree(name) -def parse_requires(requires): # type: (str) -> List[str] - lines = requires.split("\n") - - requires_dist = [] - in_section = False - current_marker = None - for line in lines: - line = line.strip() - if not line: - if in_section: - in_section = False - - continue - - if line.startswith("["): - # extras or conditional dependencies - marker = line.lstrip("[").rstrip("]") - if ":" not in marker: - extra, marker = marker, None - else: - extra, marker = marker.split(":") - - if extra: - if marker: - marker = '{} and extra == "{}"'.format(marker, extra) - else: - marker = 'extra == "{}"'.format(extra) - - if marker: - current_marker = marker - - continue - - if current_marker: - line = "{}; {}".format(line, current_marker) - - requires_dist.append(line) - - return requires_dist - - def get_cert(config, repository_name): # type: (Config, str) -> Optional[Path] cert = config.get("certificates.{}.cert".format(repository_name)) if cert: @@ -127,3 +87,17 @@ def merge_dicts(d1, d2): merge_dicts(d1[k], d2[k]) else: d1[k] = d2[k] + + +def download_file( + url, dest, session=None, chunk_size=1024 +): # type: (str, str, Optional[requests.Session], int) -> None + get = requests.get if not session else session.get + + with get(url, stream=True) as response: + response.raise_for_status() + + with open(dest, "wb") as f: + for chunk in response.iter_content(chunk_size=chunk_size): + if chunk: + f.write(chunk) diff --git a/poetry/utils/inspector.py b/poetry/utils/inspector.py deleted file mode 100644 index f1675273b47..00000000000 --- a/poetry/utils/inspector.py +++ /dev/null @@ -1,230 +0,0 @@ -import logging -import os -import tarfile -import zipfile - -from typing import Dict -from typing import List -from typing import Union - -import pkginfo - -from requests import get - -from ._compat import Path -from .helpers import parse_requires -from .setup_reader import SetupReader -from .toml_file import TomlFile - - -logger = logging.getLogger(__name__) - - -class Inspector: - """ - A class to download and inspect remote packages. - """ - - @classmethod - def download(cls, url, dest): # type: (str, Path) -> None - r = get(url, stream=True) - r.raise_for_status() - - with open(str(dest), "wb") as f: - for chunk in r.iter_content(chunk_size=1024): - if chunk: - f.write(chunk) - - def inspect(self, file_path): # type: (Path) -> Dict[str, Union[str, List[str]]] - if file_path.suffix == ".whl": - return self.inspect_wheel(file_path) - - return self.inspect_sdist(file_path) - - def inspect_wheel( - self, file_path - ): # type: (Path) -> Dict[str, Union[str, List[str]]] - info = { - "name": "", - "version": "", - "summary": "", - "requires_python": None, - "requires_dist": [], - } - - try: - meta = pkginfo.Wheel(str(file_path)) - except ValueError: - # Unable to determine dependencies - # Assume none - return info - - if meta.name: - info["name"] = meta.name - - if meta.version: - info["version"] = meta.version - - if meta.summary: - info["summary"] = meta.summary or "" - - info["requires_python"] = meta.requires_python - - if meta.requires_dist: - info["requires_dist"] = meta.requires_dist - - return info - - def inspect_sdist( - self, file_path - ): # type: (Path) -> Dict[str, Union[str, List[str]]] - info = { - "name": "", - "version": "", - "summary": "", - "requires_python": None, - "requires_dist": None, - } - - try: - meta = pkginfo.SDist(str(file_path)) - if meta.name: - info["name"] = meta.name - - if meta.version: - info["version"] = meta.version - - if meta.summary: - info["summary"] = meta.summary - - if meta.requires_python: - info["requires_python"] = meta.requires_python - - if meta.requires_dist: - info["requires_dist"] = list(meta.requires_dist) - - return info - except ValueError: - # Unable to determine dependencies - # We pass and go deeper - pass - - # Still not dependencies found - # So, we unpack and introspect - suffix = file_path.suffix - if suffix == ".zip": - tar = zipfile.ZipFile(str(file_path)) - else: - if suffix == ".bz2": - suffixes = file_path.suffixes - if len(suffixes) > 1 and suffixes[-2] == ".tar": - suffix = ".tar.bz2" - else: - suffix = ".tar.gz" - - tar = tarfile.open(str(file_path)) - - try: - tar.extractall(os.path.join(str(file_path.parent), "unpacked")) - finally: - tar.close() - - unpacked = file_path.parent / "unpacked" - elements = list(unpacked.glob("*")) - if len(elements) == 1 and elements[0].is_dir(): - sdist_dir = elements[0] - else: - sdist_dir = unpacked / file_path.name.rstrip(suffix) - - pyproject = TomlFile(sdist_dir / "pyproject.toml") - if pyproject.exists(): - from poetry.factory import Factory - - pyproject_content = pyproject.read() - if "tool" in pyproject_content and "poetry" in pyproject_content["tool"]: - package = Factory().create_poetry(sdist_dir).package - return { - "name": package.name, - "version": package.version.text, - "summary": package.description, - "requires_dist": [dep.to_pep_508() for dep in package.requires], - "requires_python": package.python_versions, - } - - # Checking for .egg-info at root - eggs = list(sdist_dir.glob("*.egg-info")) - if eggs: - egg_info = eggs[0] - - requires = egg_info / "requires.txt" - if requires.exists(): - with requires.open(encoding="utf-8") as f: - info["requires_dist"] = parse_requires(f.read()) - - return info - - # Searching for .egg-info in sub directories - eggs = list(sdist_dir.glob("**/*.egg-info")) - if eggs: - egg_info = eggs[0] - - requires = egg_info / "requires.txt" - if requires.exists(): - with requires.open(encoding="utf-8") as f: - info["requires_dist"] = parse_requires(f.read()) - - return info - - # Still nothing, try reading (without executing it) - # the setup.py file. - try: - setup_info = self._inspect_sdist_with_setup(sdist_dir) - - for key, value in info.items(): - if value: - continue - - info[key] = setup_info[key] - - return info - except Exception as e: - logger.warning( - "An error occurred when reading setup.py or setup.cfg: {}".format( - str(e) - ) - ) - return info - - def _inspect_sdist_with_setup( - self, sdist_dir - ): # type: (Path) -> Dict[str, Union[str, List[str]]] - info = { - "name": None, - "version": None, - "summary": "", - "requires_python": None, - "requires_dist": None, - } - - result = SetupReader.read_from_directory(sdist_dir) - requires = "" - for dep in result["install_requires"]: - requires += dep + "\n" - - if result["extras_require"]: - requires += "\n" - - for extra_name, deps in result["extras_require"].items(): - requires += "[{}]\n".format(extra_name) - - for dep in deps: - requires += dep + "\n" - - requires += "\n" - - info["name"] = result["name"] - info["version"] = result["version"] - info["requires_dist"] = parse_requires(requires) - info["requires_python"] = result["python_requires"] - - return info diff --git a/tests/conftest.py b/tests/conftest.py index 57373d55d87..6c1eca07613 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -71,7 +71,9 @@ def config(config_source, auth_config_source, mocker): @pytest.fixture(autouse=True) def download_mock(mocker): # Patch download to not download anything but to just copy from fixtures - mocker.patch("poetry.utils.inspector.Inspector.download", new=mock_download) + mocker.patch("poetry.utils.helpers.download_file", new=mock_download) + mocker.patch("poetry.puzzle.provider.download_file", new=mock_download) + mocker.patch("poetry.repositories.pypi_repository.download_file", new=mock_download) @pytest.fixture diff --git a/tests/console/conftest.py b/tests/console/conftest.py index 16555c54f2b..05b7c49ce6b 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -16,7 +16,6 @@ from poetry.utils.env import MockEnv from poetry.utils.toml_file import TomlFile from tests.helpers import mock_clone -from tests.helpers import mock_download @pytest.fixture() @@ -54,9 +53,6 @@ def setup(mocker, installer, installed, config, env): p = mocker.patch("poetry.core.vcs.git.Git.rev_parse") p.return_value = "9cf87a285a2d3fbb0b9fa621997b3acc3631ed24" - # Patch download to not download anything but to just copy from fixtures - mocker.patch("poetry.utils.inspector.Inspector.download", new=mock_download) - # Patch the virtual environment creation do actually do nothing mocker.patch("poetry.utils.env.EnvManager.create_venv", return_value=env) diff --git a/tests/fixtures/directory/project_with_transitive_file_dependencies/setup.py b/tests/fixtures/directory/project_with_transitive_file_dependencies/setup.py deleted file mode 100644 index 24a8f05be9f..00000000000 --- a/tests/fixtures/directory/project_with_transitive_file_dependencies/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from distutils.core import setup - -packages = ["project_with_extras"] - -package_data = {"": ["*"]} - -extras_require = {"extras_a": ["pendulum>=1.4.4"], "extras_b": ["cachy>=0.2.0"]} - -setup_kwargs = { - "name": "project-with-extras", - "version": "1.2.3", - "description": "This is a description", - "long_description": None, - "author": "Your Name", - "author_email": "you@example.com", - "url": None, - "packages": packages, - "package_data": package_data, - "extras_require": extras_require, -} - - -setup(**setup_kwargs) diff --git a/tests/helpers.py b/tests/helpers.py index d9575be8e32..0dd7ce312e5 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -81,10 +81,10 @@ def mock_clone(_, source, dest): copy_or_symlink(folder, dest) -def mock_download(self, url, dest): +def mock_download(url, dest, **__): parts = urlparse.urlparse(url) fixtures = Path(__file__).parent / "fixtures" fixture = fixtures / parts.path.lstrip("/") - copy_or_symlink(fixture, dest) + copy_or_symlink(fixture, Path(dest)) diff --git a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test index 06f262f74e6..ffc00b0dd33 100644 --- a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test +++ b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test @@ -1,6 +1,6 @@ [[package]] category = "main" -description = "" +description = "This is a description" develop = true name = "project-with-extras" optional = false @@ -18,7 +18,7 @@ url = "tests/fixtures/directory/project_with_transitive_directory_dependencies/. [[package]] category = "main" -description = "" +description = "This is a description" develop = true name = "project-with-transitive-directory-dependencies" optional = false diff --git a/tests/installation/fixtures/with-directory-dependency-poetry.test b/tests/installation/fixtures/with-directory-dependency-poetry.test index df8b282d684..c9bd6b32c4b 100644 --- a/tests/installation/fixtures/with-directory-dependency-poetry.test +++ b/tests/installation/fixtures/with-directory-dependency-poetry.test @@ -8,7 +8,7 @@ version = "1.4.4" [[package]] category = "main" -description = "" +description = "This is a description" develop = true name = "project-with-extras" optional = false diff --git a/tests/installation/fixtures/with-file-dependency-transitive.test b/tests/installation/fixtures/with-file-dependency-transitive.test index db3b632694a..a580109dc32 100644 --- a/tests/installation/fixtures/with-file-dependency-transitive.test +++ b/tests/installation/fixtures/with-file-dependency-transitive.test @@ -28,7 +28,7 @@ version = "1.4.4" [[package]] category = "main" -description = "" +description = "This is a description" develop = true name = "project-with-transitive-file-dependencies" optional = false diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index 6b29ae8aa01..9b99ac3def5 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -1683,7 +1683,7 @@ def test_solver_chooses_from_correct_repository_if_forced( ops, [{"job": "install", "package": get_package("tomlkit", "0.5.2")}] ) - assert "http://foo.bar" == ops[0].package.source_url + assert "http://legacy.foo.bar" == ops[0].package.source_url def test_solver_chooses_from_correct_repository_if_forced_and_transitive_dependency( @@ -1711,7 +1711,7 @@ def test_solver_chooses_from_correct_repository_if_forced_and_transitive_depende ], ) - assert "http://foo.bar" == ops[0].package.source_url + assert "http://legacy.foo.bar" == ops[0].package.source_url assert "" == ops[1].package.source_type assert "" == ops[1].package.source_url @@ -1740,10 +1740,10 @@ def test_solver_does_not_choose_from_secondary_repository_by_default( ], ) - assert "http://foo.bar" == ops[0].package.source_url + assert "http://legacy.foo.bar" == ops[0].package.source_url assert "" == ops[1].package.source_type assert "" == ops[1].package.source_url - assert "http://foo.bar" == ops[2].package.source_url + assert "http://legacy.foo.bar" == ops[2].package.source_url def test_solver_chooses_from_secondary_if_explicit(package, installed, locked, io): @@ -1767,7 +1767,7 @@ def test_solver_chooses_from_secondary_if_explicit(package, installed, locked, i ], ) - assert "http://foo.bar" == ops[0].package.source_url + assert "http://legacy.foo.bar" == ops[0].package.source_url assert "" == ops[1].package.source_type assert "" == ops[1].package.source_url assert "" == ops[2].package.source_type diff --git a/tests/repositories/test_legacy_repository.py b/tests/repositories/test_legacy_repository.py index 7489947fee6..5413dfc7090 100644 --- a/tests/repositories/test_legacy_repository.py +++ b/tests/repositories/test_legacy_repository.py @@ -23,7 +23,7 @@ class MockRepository(LegacyRepository): def __init__(self, auth=None): super(MockRepository, self).__init__( - "legacy", url="http://foo.bar", auth=auth, disable_cache=True + "legacy", url="http://legacy.foo.bar", auth=auth, disable_cache=True ) def _get(self, endpoint): @@ -50,7 +50,7 @@ def test_page_relative_links_path_are_correct(): page = repo._get("/relative") for link in page.links: - assert link.netloc == "foo.bar" + assert link.netloc == "legacy.foo.bar" assert link.path.startswith("/relative/poetry") @@ -267,7 +267,7 @@ def test_get_package_retrieves_packages_with_no_hashes(): def test_username_password_special_chars(): - auth = Auth("http://foo.bar", "user:", "/%2Fp@ssword") + auth = Auth("http://legacy.foo.bar", "user:", "/%2Fp@ssword") repo = MockRepository(auth=auth) - assert "http://user%3A:%2F%252Fp%40ssword@foo.bar" == repo.authenticated_url + assert "http://user%3A:%2F%252Fp%40ssword@legacy.foo.bar" == repo.authenticated_url diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py index d34061c88d6..c4143701643 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py @@ -1,7 +1,7 @@ +from poetry.core.utils.helpers import parse_requires from poetry.utils._compat import Path from poetry.utils.helpers import get_cert from poetry.utils.helpers import get_client_cert -from poetry.utils.helpers import parse_requires def test_parse_requires(): diff --git a/tox.ini b/tox.ini index 16d181f6cc6..1f0a4b9ede4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,6 @@ [tox] -skipsdist = True +minversion = 3.3.0 +isolated_build = True envlist = py27, py35, py36, py37, py38 [testenv] @@ -7,4 +8,4 @@ whitelist_externals = poetry skip_install = true commands = poetry install -vvv - poetry run pytest tests/ + poetry run pytest {posargs} tests/