def test_self_update_does_not_update_non_recommended_installation( tester: CommandTester, http: type[httpretty.httpretty], mocker: MockerFixture, environ: None, tmp_venv: VirtualEnv, ): mocker.patch.object(EnvManager, "get_system_env", return_value=tmp_venv) command = tester.command new_version = Version.parse(__version__).next_minor().text old_poetry = Package("poetry", __version__) old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2")) new_poetry = Package("poetry", new_version) new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0")) installed_repository = Repository() installed_repository.add_package(old_poetry) installed_repository.add_package(Package("cleo", "0.8.2")) repository = Repository() repository.add_package(new_poetry) repository.add_package(Package("cleo", "1.0.0")) pool = Pool() pool.add_repository(repository) command._pool = pool with pytest.raises(PoetrySimpleConsoleException): tester.execute()
def test_package_clone(f: Factory) -> None: # TODO(nic): this test is not future-proof, in that any attributes added # to the Package object and not filled out in this test setup might # cause comparisons to match that otherwise should not. A factory method # to create a Package object with all fields fully randomized would be the # most rigorous test for this, but that's likely overkill. p = Package( "lol_wut", "3.141.5926535", pretty_version="③.⑭.⑮", source_type="git", source_url="http://some.url", source_reference="fe4d2adabf3feb5d32b70ab5c105285fa713b10c", source_resolved_reference="fe4d2adabf3feb5d32b70ab5c105285fa713b10c", features=["abc", "def"], develop=random.choice((True, False)), ) p.add_dependency(Factory.create_dependency("foo", "^1.2.3")) p.add_dependency(Factory.create_dependency("foo", "^1.2.3", groups=["dev"])) p.files = (["file1", "file2", "file3"], ) # type: ignore[assignment] p.homepage = "https://some.other.url" p.repository_url = "http://bug.farm" p.documentation_url = "http://lorem.ipsum/dolor/sit.amet" p2 = p.clone() assert p == p2 assert p.__dict__ == p2.__dict__ assert len(p2.requires) == 1 assert len(p2.all_requires) == 2
def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( app, repo, tester, env, installed): poetry_package = Package("poetry", "1.2.0") poetry_package.add_dependency( Factory.create_dependency("tomlkit", "^0.7.0")) plugin_package = Package("poetry-plugin", "1.2.3") plugin_package.add_dependency( Factory.create_dependency("tomlkit", "^0.7.2")) installed.add_package(poetry_package) installed.add_package(Package("tomlkit", "0.7.1")) repo.add_package(plugin_package) repo.add_package(Package("tomlkit", "0.7.1")) repo.add_package(Package("tomlkit", "0.7.2")) tester.execute("poetry-plugin") expected = """\ Using version ^1.2.3 for poetry-plugin Updating dependencies Resolving dependencies... Writing lock file Package operations: 1 install, 1 update, 0 removals • Updating tomlkit (0.7.1 -> 0.7.2) • Installing poetry-plugin (1.2.3) """ assert_plugin_add_result(tester, app, env, expected, "^1.2.3")
def plugin_package(plugin_package_requires_dist: list[str]) -> Package: package = Package("poetry-plugin", "1.2.3") for requirement in plugin_package_requires_dist: package.add_dependency(Dependency.create_from_pep_508(requirement)) return package
def test_self_update_can_update_from_recommended_installation( tester: CommandTester, http: type[httpretty.httpretty], mocker: MockerFixture, environ: None, tmp_venv: VirtualEnv, ): mocker.patch.object(EnvManager, "get_system_env", return_value=tmp_venv) command = tester.command command._data_dir = tmp_venv.path.parent new_version = Version.parse(__version__).next_minor().text old_poetry = Package("poetry", __version__) old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2")) new_poetry = Package("poetry", new_version) new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0")) installed_repository = Repository() installed_repository.add_package(old_poetry) installed_repository.add_package(Package("cleo", "0.8.2")) repository = Repository() repository.add_package(new_poetry) repository.add_package(Package("cleo", "1.0.0")) pool = Pool() pool.add_repository(repository) command._pool = pool mocker.patch.object(InstalledRepository, "load", return_value=installed_repository) tester.execute() expected_output = f"""\ Updating Poetry to 1.2.0 Updating dependencies Resolving dependencies... Package operations: 0 installs, 2 updates, 0 removals - Updating cleo (0.8.2 -> 1.0.0) - Updating poetry ({__version__} -> {new_version}) Updating the poetry script Poetry ({new_version}) is installed now. Great! """ assert tester.io.fetch_output() == expected_output
def add_to_repo(repository, name, version, deps=None, python=None): package = Package(name, version) if python: package.python_versions = python if deps: for dep_name, dep_constraint in deps.items(): package.add_dependency( Factory.create_dependency(dep_name, dep_constraint)) repository.add_package(package)
def package_with_groups(): package = Package("foo", "1.2.3") optional_group = DependencyGroup("optional", optional=True) optional_group.add_dependency(Factory.create_dependency("bam", "^3.0.0")) package.add_dependency(Factory.create_dependency("bar", "^1.0.0")) package.add_dependency(Factory.create_dependency("baz", "^1.1.0")) package.add_dependency(Factory.create_dependency("bim", "^2.0.0", groups=["dev"])) package.add_dependency_group(optional_group) return package
def test_package_add_dependency_vcs_category_default_main(f): package = Package("foo", "0.1.0") dependency = package.add_dependency( f.create_dependency( "poetry", {"git": "https://github.com/python-poetry/poetry.git"})) assert dependency.category == "main"
def test_package_add_dependency_vcs_groups_default_main(f: Factory) -> None: package = Package("foo", "0.1.0") dependency = package.add_dependency( f.create_dependency( "poetry", {"git": "https://github.com/python-poetry/poetry.git"})) assert dependency.groups == frozenset(["main"])
def add_to_repo( repository: Repository, name: str, version: str, deps: dict[str, str] | None = None, python: str | None = None, ) -> None: package = Package(name, version) if python: package.python_versions = python if deps: for dep_name, dep_constraint in deps.items(): package.add_dependency(Factory.create_dependency(dep_name, dep_constraint)) repository.add_package(package)
def test_show_displays_installed_plugins_with_dependencies( app: PoetryTestApplication, tester: CommandTester, installed: Repository, mocker: MockerFixture, plugin_package: Package, plugin_distro: Distribution, ): mocker.patch( "entrypoints.get_group_all", side_effect=[ [ EntryPoint( "poetry-plugin", "poetry_plugin.plugins:ApplicationPlugin", "FirstApplicationPlugin", distro=plugin_distro, ) ], [ EntryPoint( "poetry-plugin", "poetry_plugin.plugins:Plugin", "FirstPlugin", distro=plugin_distro, ) ], ], ) plugin_package.add_dependency(Factory.create_dependency("foo", ">=1.2.3")) plugin_package.add_dependency(Factory.create_dependency("bar", "<4.5.6")) installed.add_package(plugin_package) tester.execute("") expected = """ • poetry-plugin (1.2.3) 1 plugin and 1 application plugin Dependencies - foo (>=1.2.3) - bar (<4.5.6) """ assert tester.io.fetch_output() == expected
def test_adding_a_plugin_can_update_poetry_dependencies_if_needed( app, repo, tester, env, installed): poetry_package = Package("poetry", "1.2.0") poetry_package.add_dependency( Factory.create_dependency("tomlkit", "^0.7.0")) plugin_package = Package("poetry-plugin", "1.2.3") plugin_package.add_dependency( Factory.create_dependency("tomlkit", "^0.7.2")) installed.add_package(poetry_package) installed.add_package(Package("tomlkit", "0.7.1")) repo.add_package(plugin_package) repo.add_package(Package("tomlkit", "0.7.1")) repo.add_package(Package("tomlkit", "0.7.2")) tester.execute("poetry-plugin") expected = """\ Using version ^1.2.3 for poetry-plugin Updating dependencies Resolving dependencies... Writing lock file Package operations: 1 install, 1 update, 0 removals • Updating tomlkit (0.7.1 -> 0.7.2) • Installing poetry-plugin (1.2.3) """ assert tester.io.fetch_output() == expected update_command = app.find("update") assert update_command.poetry.file.parent == env.path assert update_command.poetry.locker.lock.parent == env.path assert update_command.poetry.locker.lock.exists() content = update_command.poetry.file.read()["tool"]["poetry"] assert "poetry-plugin" in content["dependencies"] assert content["dependencies"]["poetry-plugin"] == "^1.2.3"
def test_package_add_dependency_vcs_groups(groups: list[str], f: Factory) -> None: package = Package("foo", "0.1.0") dependency = package.add_dependency( f.create_dependency( "poetry", {"git": "https://github.com/python-poetry/poetry.git"}, groups=groups, )) assert dependency.groups == frozenset(groups)
def test_show_displays_installed_plugins_with_dependencies( app, tester, installed, mocker): mocker.patch( "entrypoints.get_group_all", side_effect=[ [ EntryPoint( "poetry-plugin", "poetry_plugin.plugins:ApplicationPlugin", "FirstApplicationPlugin", ) ], [ EntryPoint( "poetry-plugin", "poetry_plugin.plugins:Plugin", "FirstPlugin", ) ], ], ) plugin = Package("poetry-plugin", "1.2.3") plugin.add_dependency(Factory.create_dependency("foo", ">=1.2.3")) plugin.add_dependency(Factory.create_dependency("bar", "<4.5.6")) installed.add_package(plugin) tester.execute("") expected = """ • poetry-plugin (1.2.3) 1 plugin and 1 application plugin Dependencies - foo (>=1.2.3) - bar (<4.5.6) """ assert tester.io.fetch_output() == expected
def test_self_update_can_update_from_recommended_installation( tester: CommandTester, repo: TestRepository, installed: TestRepository, ): new_version = Version.parse(__version__).next_minor().text old_poetry = Package("poetry", __version__) old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2")) new_poetry = Package("poetry", new_version) new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0")) installed.add_package(old_poetry) installed.add_package(Package("cleo", "0.8.2")) repo.add_package(new_poetry) repo.add_package(Package("cleo", "1.0.0")) tester.execute() expected_output = f"""\ Updating Poetry version ... Using version ^{new_version} for poetry Updating dependencies Resolving dependencies... Writing lock file Package operations: 0 installs, 2 updates, 0 removals • Updating cleo (0.8.2 -> 1.0.0) • Updating poetry ({__version__} -> {new_version}) """ assert tester.io.fetch_output() == expected_output
def test_package_url_groups_optional(groups, optional, f): package = Package("foo", "0.1.0") dependency = package.add_dependency( f.create_dependency( "poetry", { "url": "https://github.com/python-poetry/poetry/releases/download/1.0.5/poetry-1.0.5-linux.tar.gz", "optional": optional, }, groups=groups, ) ) assert dependency.groups == frozenset(groups) assert dependency.is_optional() == optional
def test_package_url_category_optional(category, optional, f): package = Package("foo", "0.1.0") dependency = package.add_dependency( f.create_dependency( "poetry", { "url": "https://github.com/python-poetry/poetry/releases/download/1.0.5/poetry-1.0.5-linux.tar.gz", "optional": optional, }, category=category, )) assert dependency.category == category assert dependency.is_optional() == optional
def locked_repository( self, with_dev_reqs: bool = False) -> poetry.repositories.Repository: """ Searches and returns a repository of locked packages. """ from poetry.factory import Factory if not self.is_locked(): return poetry.repositories.Repository() lock_data = self.lock_data packages = poetry.repositories.Repository() if with_dev_reqs: locked_packages = lock_data["package"] else: locked_packages = [ p for p in lock_data["package"] if p["category"] == "main" ] if not locked_packages: return packages for info in locked_packages: source = info.get("source", {}) source_type = source.get("type") url = source.get("url") if source_type in ["directory", "file"]: url = self._lock.path.parent.joinpath(url).resolve().as_posix() package = Package( info["name"], info["version"], info["version"], source_type=source_type, source_url=url, source_reference=source.get("reference"), source_resolved_reference=source.get("resolved_reference"), ) package.description = info.get("description", "") package.category = info.get("category", "main") package.groups = info.get("groups", ["default"]) package.optional = info["optional"] if "hashes" in lock_data["metadata"]: # Old lock so we create dummy files from the hashes package.files = [{ "name": h, "hash": h } for h in lock_data["metadata"]["hashes"][info["name"]]] else: package.files = lock_data["metadata"]["files"][info["name"]] package.python_versions = info["python-versions"] extras = info.get("extras", {}) if extras: for name, deps in extras.items(): package.extras[name] = [] for dep in deps: try: dependency = Dependency.create_from_pep_508(dep) except InvalidRequirement: # handle lock files with invalid PEP 508 m = re.match( r"^(.+?)(?:\[(.+?)])?(?:\s+\((.+)\))?$", dep) dep_name = m.group(1) extras = m.group(2) or "" constraint = m.group(3) or "*" dependency = Dependency(dep_name, constraint, extras=extras.split(",")) package.extras[name].append(dependency) if "marker" in info: package.marker = parse_marker(info["marker"]) else: # Compatibility for old locks if "requirements" in info: dep = Dependency("foo", "0.0.0") for name, value in info["requirements"].items(): if name == "python": dep.python_versions = value elif name == "platform": dep.platform = value split_dep = dep.to_pep_508(False).split(";") if len(split_dep) > 1: package.marker = parse_marker(split_dep[1].strip()) for dep_name, constraint in info.get("dependencies", {}).items(): root_dir = self._lock.path.parent if package.source_type == "directory": # root dir should be the source of the package relative to the lock path root_dir = Path(package.source_url) if isinstance(constraint, list): for c in constraint: package.add_dependency( Factory.create_dependency(dep_name, c, root_dir=root_dir)) continue package.add_dependency( Factory.create_dependency(dep_name, constraint, root_dir=root_dir)) if "develop" in info: package.develop = info["develop"] packages.add_package(package) return packages
def locked_repository(self, with_dev_reqs=False ): # type: (bool) -> poetry.repositories.Repository """ Searches and returns a repository of locked packages. """ if not self.is_locked(): return poetry.repositories.Repository() lock_data = self.lock_data packages = poetry.repositories.Repository() if with_dev_reqs: locked_packages = lock_data["package"] else: locked_packages = [ p for p in lock_data["package"] if p["category"] == "main" ] if not locked_packages: return packages for info in locked_packages: package = Package(info["name"], info["version"], info["version"]) package.description = info.get("description", "") package.category = info["category"] package.optional = info["optional"] if "hashes" in lock_data["metadata"]: # Old lock so we create dummy files from the hashes package.files = [{ "name": h, "hash": h } for h in lock_data["metadata"]["hashes"][info["name"]]] else: package.files = lock_data["metadata"]["files"][info["name"]] package.python_versions = info["python-versions"] extras = info.get("extras", {}) if extras: for name, deps in extras.items(): package.extras[name] = [] for dep in deps: m = re.match(r"^(.+?)(?:\s+\((.+)\))?$", dep) dep_name = m.group(1) constraint = m.group(2) or "*" package.extras[name].append( Dependency(dep_name, constraint)) if "marker" in info: package.marker = parse_marker(info["marker"]) else: # Compatibility for old locks if "requirements" in info: dep = Dependency("foo", "0.0.0") for name, value in info["requirements"].items(): if name == "python": dep.python_versions = value elif name == "platform": dep.platform = value split_dep = dep.to_pep_508(False).split(";") if len(split_dep) > 1: package.marker = parse_marker(split_dep[1].strip()) for dep_name, constraint in info.get("dependencies", {}).items(): if isinstance(constraint, list): for c in constraint: package.add_dependency(dep_name, c) continue package.add_dependency(dep_name, constraint) if "develop" in info: package.develop = info["develop"] if "source" in info: package.source_type = info["source"].get("type", "") package.source_url = info["source"]["url"] package.source_reference = info["source"]["reference"] packages.add_package(package) return packages
def load(cls, env: Env, with_dependencies: bool = False) -> "InstalledRepository": """ Load installed packages. """ from poetry.core.packages.dependency import Dependency repo = cls() seen = set() for entry in reversed(env.sys_path): for distribution in sorted( metadata.distributions(path=[entry]), key=lambda d: str(d._path), ): name = distribution.metadata["name"] path = Path(str(distribution._path)) version = distribution.metadata["version"] package = Package(name, version, version) package.description = distribution.metadata.get("summary", "") if with_dependencies: for require in distribution.metadata.get_all( "requires-dist", []): dep = Dependency.create_from_pep_508(require) package.add_dependency(dep) if package.name in seen: continue try: path.relative_to(_VENDORS) except ValueError: pass else: continue seen.add(package.name) repo.add_package(package) is_standard_package = env.is_path_relative_to_lib(path) if is_standard_package: if path.name.endswith(".dist-info"): paths = cls.get_package_paths(env=env, name=package.pretty_name) if paths: is_editable_package = False for src in paths: if cls.is_vcs_package(src, env): cls.set_package_vcs_properties( package, env) break if not (is_editable_package or env.is_path_relative_to_lib(src)): is_editable_package = True else: # TODO: handle multiple source directories? if is_editable_package: package._source_type = "directory" package._source_url = paths.pop().as_posix( ) continue if cls.is_vcs_package(path, env): cls.set_package_vcs_properties(package, env) else: # If not, it's a path dependency package._source_type = "directory" package._source_url = str(path.parent) return repo
import pytest from poetry.core.packages.package import Package from poetry.factory import Factory from poetry.utils.extras import get_extra_package_names _PACKAGE_FOO = Package("foo", "0.1.0") _PACKAGE_SPAM = Package("spam", "0.2.0") _PACKAGE_BAR = Package("bar", "0.3.0") _PACKAGE_BAR.add_dependency(Factory.create_dependency("foo", "*")) # recursive dependency _PACKAGE_BAZ = Package("baz", "0.4.0") _PACKAGE_BAZ.add_dependency(Factory.create_dependency("quix", "*")) _PACKAGE_QUIX = Package("quix", "0.5.0") _PACKAGE_QUIX.add_dependency(Factory.create_dependency("baz", "*")) @pytest.mark.parametrize( "packages,extras,extra_names,expected_extra_package_names", [ # Empty edge case ([], {}, [], []), # Selecting no extras is fine ([_PACKAGE_FOO], {}, [], []), # An empty extras group should return an empty list ([_PACKAGE_FOO], { "group0": [] }, ["group0"], []), # Selecting an extras group should return the contained packages (
def to_package( self, name: str | None = None, extras: list[str] | None = None, root_dir: Path | None = None, ) -> 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( f"Unable to retrieve the package version for {name}") package = Package( name=name, version=self.version, source_type=self._source_type, source_url=self._source_url, source_reference=self._source_reference, ) package.description = self.summary package.root_dir = root_dir package.python_versions = self.requires_python or "*" package.files = self.files if root_dir or (self._source_type in {"directory"} and self._source_url): # this is a local poetry project, this means we can extract "richer" # requirement information, eg: development requirements etc. poetry_package = self._get_poetry_package( path=root_dir or self._source_url) if poetry_package: package.extras = poetry_package.extras for dependency in poetry_package.requires: package.add_dependency(dependency) return package seen_requirements = set() for req in self.requires_dist or []: try: # Attempt to parse the PEP-508 requirement string dependency = Dependency.create_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.create_from_pep_508( req, relative_to=root_dir) except ValueError: # Likely unable to parse constraint so we skip it self._log( f"Invalid constraint ({req}) found in" f" {package.name}-{package.version} dependencies, skipping", level="warning", ) 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] = [] package.extras[extra].append(dependency) req = dependency.to_pep_508(with_extras=True) if req not in seen_requirements: package.add_dependency(dependency) seen_requirements.add(req) return package