def dependencies(self) -> List["Dependency"]: if self._dependencies is None: # avoid circular dependency when loading DirectoryDependency from poetry.core.packages.dependency import Dependency from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency self._dependencies = [] for requirement in self.requires: dependency = None try: dependency = Dependency.create_from_pep_508(requirement) except ValueError: # PEP 517 requires can be path if not PEP 508 path = Path(requirement) try: if path.is_file(): dependency = FileDependency(name=canonicalize_name( path.name), path=path) elif path.is_dir(): dependency = DirectoryDependency( name=canonicalize_name(path.name), path=path) except OSError: # compatibility Python < 3.8 # https://docs.python.org/3/library/pathlib.html#methods pass if dependency is None: # skip since we could not determine requirement continue self._dependencies.append(dependency) return self._dependencies
def test_search_for_file_sdist_with_extras(provider: Provider): dependency = FileDependency( "demo", Path(__file__).parent.parent / "fixtures" / "distributions" / "demo-0.1.0.tar.gz", extras=["foo"], ) package = provider.search_for_file(dependency)[0] assert package.name == "demo" assert package.version.text == "0.1.0" required = [ r for r in sorted(package.requires, key=lambda r: r.name) if not r.is_optional() ] optional = [ r for r in sorted(package.requires, key=lambda r: r.name) if r.is_optional() ] assert required == [get_dependency("pendulum", ">=1.4.4")] assert optional == [ get_dependency("cleo"), get_dependency("tomlkit"), ] assert package.extras == { "foo": [get_dependency("cleo")], "bar": [get_dependency("tomlkit")], }
def test_search_for_file_wheel(provider: Provider): dependency = FileDependency( "demo", Path(__file__).parent.parent / "fixtures" / "distributions" / "demo-0.1.0-py2.py3-none-any.whl", ) package = provider.search_for_direct_origin_dependency(dependency) assert package.name == "demo" assert package.version.text == "0.1.0" required = [ r for r in sorted(package.requires, key=lambda r: r.name) if not r.is_optional() ] optional = [ r for r in sorted(package.requires, key=lambda r: r.name) if r.is_optional() ] assert required == [get_dependency("pendulum", ">=1.4.4")] assert optional == [ get_dependency("cleo"), get_dependency("tomlkit"), ] assert package.extras == { "foo": [get_dependency("cleo")], "bar": [get_dependency("tomlkit")], }
def _download_link(self, operation, link): package = operation.package archive = self._chef.get_cached_archive_for_link(link) if archive is link: # No cached distributions was found, so we download and prepare it try: archive = self._download_archive(operation, link) except BaseException: cache_directory = self._chef.get_cache_directory_for_link(link) cached_file = cache_directory.joinpath(link.filename) # We can't use unlink(missing_ok=True) because it's not available # in pathlib2 for Python 2.7 if cached_file.exists(): cached_file.unlink() raise # TODO: Check readability of the created archive if not link.is_wheel: archive = self._chef.prepare(archive) if package.files: archive_hash = "sha256:" + FileDependency(package.name, archive).hash() if archive_hash not in {f["hash"] for f in package.files}: raise RuntimeError( "Invalid hash for {} using archive {}".format(package, archive.name) ) return archive
def _validate_archive_hash(archive: Union[Path, Link], package: "Package") -> str: archive_path = (url_to_path(archive.url) if isinstance(archive, Link) else archive) file_dep = FileDependency( package.name, archive_path, ) archive_hash = "sha256:" + file_dep.hash() known_hashes = {f["hash"] for f in package.files} if archive_hash not in known_hashes: raise RuntimeError( f"Hash for {package} from archive {archive_path.name} not found in" f" known hashes (was: {archive_hash})") return archive_hash
def _search_for_file(self, dependency: FileDependency) -> Package: package = self.get_package_from_file(dependency.full_path) self.validate_package_for_dependency(dependency=dependency, package=package) if dependency.base is not None: package.root_dir = dependency.base package.files = [{ "file": dependency.path.name, "hash": "sha256:" + dependency.hash() }] return package
def _download_link(self, operation, link): package = operation.package archive = self._chef.get_cached_archive_for_link(link) if archive is link: # No cached distributions was found, so we download and prepare it try: archive = self._download_archive(operation, link) except BaseException: cache_directory = self._chef.get_cache_directory_for_link(link) cached_file = cache_directory.joinpath(link.filename) # We can't use unlink(missing_ok=True) because it's not available # in pathlib2 for Python 2.7 if cached_file.exists(): cached_file.unlink() raise # TODO: Check readability of the created archive if not link.is_wheel: archive = self._chef.prepare(archive) if package.files: hashes = {f["hash"] for f in package.files} hash_types = {h.split(":")[0] for h in hashes} archive_hashes = set() for hash_type in hash_types: archive_hashes.add("{}:{}".format( hash_type, FileDependency( package.name, Path(archive.path) if isinstance(archive, Link) else archive, ).hash(hash_type), )) if archive_hashes.isdisjoint(hashes): raise RuntimeError( "Invalid hashes ({}) for {} using archive {}. Expected one of {}." .format( ", ".join(sorted(archive_hashes)), package, archive.name, ", ".join(sorted(hashes)), )) return archive
def test_search_for_file_wheel(provider): dependency = FileDependency( "demo", Path(__file__).parent.parent / "fixtures" / "distributions" / "demo-0.1.0-py2.py3-none-any.whl", ) package = provider.search_for_file(dependency)[0] assert package.name == "demo" assert package.version.text == "0.1.0" assert package.requires == [get_dependency("pendulum", ">=1.4.4")] assert package.extras == { "foo": [get_dependency("cleo")], "bar": [get_dependency("tomlkit")], }
def test_search_for_file_sdist_with_extras(provider): dependency = FileDependency( "demo", Path(__file__).parent.parent / "fixtures" / "distributions" / "demo-0.1.0.tar.gz", ) dependency.extras.append("foo") package = provider.search_for_file(dependency)[0] assert package.name == "demo" assert package.version.text == "0.1.0" assert package.requires == [ get_dependency("pendulum", ">=1.4.4"), get_dependency("cleo", optional=True), ] assert package.extras == { "foo": [get_dependency("cleo")], "bar": [get_dependency("tomlkit")], }
def _make_file_or_dir_dep( name: str, path: Path, base: Path | None = None, extras: list[str] | None = None, ) -> FileDependency | DirectoryDependency | None: """ Helper function to create a file or directoru dependency with the given arguments. If path is not a file or directory that exists, `None` is returned. """ from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency _path = path if not path.is_absolute() and base: # a base path was specified, so we should respect that _path = Path(base) / path if _path.is_file(): return FileDependency(name, path, base=base, extras=extras) elif _path.is_dir(): return DirectoryDependency(name, path, base=base, extras=extras) return None
def _make_file_or_dir_dep( name: str, path: Path, base: Optional[Path] = None, extras: Optional[List[str]] = None, ) -> Optional[Union["FileDependency", "DirectoryDependency"]]: """ Helper function to create a file or directoru dependency with the given arguments. If path is not a file or directory that exists, `None` is returned. """ from .directory_dependency import DirectoryDependency from .file_dependency import FileDependency _path = path if not path.is_absolute() and base: # a base path was specified, so we should respect that _path = Path(base) / path if _path.is_file(): return FileDependency(name, path, base=base, extras=extras) elif _path.is_dir(): return DirectoryDependency(name, path, base=base, extras=extras) return None
def _export_requirements_txt( self, cwd, output, with_hashes=True, dev=False, extras=None, with_credentials=False, ): # type: (Path, Union[IO, str], bool, bool, bool) -> None indexes = set() content = "" packages = self._poetry.locker.locked_repository(dev).packages # Build a set of all packages required by our selected extras extra_package_names = set( get_extra_package_names( packages, self._poetry.locker.lock_data.get("extras", {}), extras or ())) for package in sorted(packages, key=lambda p: p.name): # If a package is optional and we haven't opted in to it, continue if package.optional and package.name not in extra_package_names: continue if package.source_type == "git": dependency = VCSDependency( package.name, package.source_type, package.source_url, package.source_reference, ) dependency.marker = package.marker line = "-e git+{}@{}#egg={}".format(package.source_url, package.source_reference, package.name) elif package.source_type in ["directory", "file", "url"]: url = package.source_url if package.source_type == "file": dependency = FileDependency( package.name, Path(package.source_url), base=self._poetry.locker.lock.path.parent, ) url = Path( os.path.relpath( url, self._poetry.locker.lock.path.parent.as_posix( ))).as_posix() elif package.source_type == "directory": dependency = DirectoryDependency( package.name, Path(package.source_url), base=self._poetry.locker.lock.path.parent, ) url = Path( os.path.relpath( url, self._poetry.locker.lock.path.parent.as_posix( ))).as_posix() else: dependency = URLDependency(package.name, package.source_url) dependency.marker = package.marker line = "{}".format(url) if package.develop and package.source_type == "directory": line = "-e " + line else: dependency = package.to_dependency() line = "{}=={}".format(package.name, package.version) requirement = dependency.to_pep_508() if ";" in requirement: line += "; {}".format(requirement.split(";")[1].strip()) if (package.source_type not in {"git", "directory", "file", "url"} and package.source_url): indexes.add(package.source_url) if package.files and with_hashes: hashes = [] for f in package.files: h = f["hash"] algorithm = "sha256" if ":" in h: algorithm, h = h.split(":") if algorithm not in self.ALLOWED_HASH_ALGORITHMS: continue hashes.append("{}:{}".format(algorithm, h)) if hashes: line += " \\\n" for i, h in enumerate(hashes): line += " --hash={}{}".format( h, " \\\n" if i < len(hashes) - 1 else "") line += "\n" content += line if indexes: # If we have extra indexes, we add them to the beginning of the output indexes_header = "" for index in sorted(indexes): repository = [ r for r in self._poetry.pool.repositories if r.url == index.rstrip("/") ][0] if (self._poetry.pool.has_default() and repository is self._poetry.pool.repositories[0]): url = (repository.authenticated_url if with_credentials else repository.url) indexes_header = "--index-url {}\n".format(url) continue url = (repository.authenticated_url if with_credentials else repository.url) indexes_header += "--extra-index-url {}\n".format(url) content = indexes_header + "\n" + content self._output(content, cwd, output)
def to_dependency(self) -> Dependency: from pathlib import Path from poetry.core.packages.dependency import Dependency from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.url_dependency import URLDependency from poetry.core.packages.vcs_dependency import VCSDependency dep: Dependency if self.source_type == "directory": dep = DirectoryDependency( self._name, Path(cast(str, self._source_url)), groups=list(self._dependency_groups.keys()), optional=self.optional, base=self.root_dir, develop=self.develop, extras=self.features, ) elif self.source_type == "file": dep = FileDependency( self._name, Path(cast(str, self._source_url)), groups=list(self._dependency_groups.keys()), optional=self.optional, base=self.root_dir, extras=self.features, ) elif self.source_type == "url": dep = URLDependency( self._name, cast(str, self._source_url), groups=list(self._dependency_groups.keys()), optional=self.optional, extras=self.features, ) elif self.source_type == "git": dep = VCSDependency( self._name, self.source_type, cast(str, self.source_url), rev=self.source_reference, resolved_rev=self.source_resolved_reference, directory=self.source_subdirectory, groups=list(self._dependency_groups.keys()), optional=self.optional, develop=self.develop, extras=self.features, ) else: dep = Dependency(self._name, self._version, extras=self.features) if not self.marker.is_any(): dep.marker = self.marker if not self.python_constraint.is_any(): dep.python_versions = self.python_versions if not self.is_direct_origin(): return dep return dep.with_constraint(self._version)
def create_dependency( cls, name: str, constraint: DependencyConstraint, groups: list[str] | None = None, root_dir: Path | None = None, ) -> Dependency: from poetry.core.packages.constraints import ( parse_constraint as parse_generic_constraint, ) from poetry.core.packages.dependency import Dependency from poetry.core.packages.dependency_group import MAIN_GROUP from poetry.core.packages.directory_dependency import DirectoryDependency from poetry.core.packages.file_dependency import FileDependency from poetry.core.packages.url_dependency import URLDependency from poetry.core.packages.utils.utils import create_nested_marker from poetry.core.packages.vcs_dependency import VCSDependency from poetry.core.semver.helpers import parse_constraint from poetry.core.version.markers import AnyMarker from poetry.core.version.markers import parse_marker if groups is None: groups = [MAIN_GROUP] if constraint is None: constraint = "*" if isinstance(constraint, dict): optional = constraint.get("optional", False) python_versions = constraint.get("python") platform = constraint.get("platform") markers = constraint.get("markers") if "allows-prereleases" in constraint: message = ( f'The "{name}" dependency specifies ' 'the "allows-prereleases" property, which is deprecated. ' 'Use "allow-prereleases" instead.') warn(message, DeprecationWarning) logger.warning(message) allows_prereleases = constraint.get( "allow-prereleases", constraint.get("allows-prereleases", False)) dependency: Dependency if "git" in constraint: # VCS dependency dependency = VCSDependency( name, "git", constraint["git"], branch=constraint.get("branch", None), tag=constraint.get("tag", None), rev=constraint.get("rev", None), directory=constraint.get("subdirectory", None), groups=groups, optional=optional, develop=constraint.get("develop", False), extras=constraint.get("extras", []), ) elif "file" in constraint: file_path = Path(constraint["file"]) dependency = FileDependency( name, file_path, groups=groups, base=root_dir, extras=constraint.get("extras", []), ) elif "path" in constraint: path = Path(constraint["path"]) if root_dir: is_file = root_dir.joinpath(path).is_file() else: is_file = path.is_file() if is_file: dependency = FileDependency( name, path, groups=groups, optional=optional, base=root_dir, extras=constraint.get("extras", []), ) else: dependency = DirectoryDependency( name, path, groups=groups, optional=optional, base=root_dir, develop=constraint.get("develop", False), extras=constraint.get("extras", []), ) elif "url" in constraint: dependency = URLDependency( name, constraint["url"], groups=groups, optional=optional, extras=constraint.get("extras", []), ) else: version = constraint["version"] dependency = Dependency( name, version, optional=optional, groups=groups, allows_prereleases=allows_prereleases, extras=constraint.get("extras", []), ) marker = parse_marker(markers) if markers else AnyMarker() if python_versions: marker = marker.intersect( parse_marker( create_nested_marker( "python_version", parse_constraint(python_versions)))) if platform: marker = marker.intersect( parse_marker( create_nested_marker( "sys_platform", parse_generic_constraint(platform)))) if not marker.is_any(): dependency.marker = marker dependency.source_name = constraint.get("source") else: dependency = Dependency(name, constraint, groups=groups) return dependency
def test_file_dependency_dir(): with pytest.raises(ValueError): FileDependency("demo", DIST_PATH)
def test_file_dependency_wrong_path(): with pytest.raises(ValueError): FileDependency("demo", DIST_PATH / "demo-0.2.0.tar.gz")
def test_default_hash(): path = DIST_PATH / TEST_FILE dep = FileDependency("demo", path) SHA_256 = "72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6" assert dep.hash() == SHA_256
def test_guaranteed_hash(hash_name, expected): path = DIST_PATH / TEST_FILE dep = FileDependency("demo", path) assert dep.hash(hash_name) == expected