def test_publish_read_from_environment_variable( fixture_dir: FixtureDirGetter, environ: None, mocker: MockerFixture, config: Config, ): os.environ["POETRY_REPOSITORIES_FOO_URL"] = "https://foo.bar" os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "******" os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "******" uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) publisher = Publisher(poetry, NullIO()) publisher.publish("foo", None, None) assert [("bar", "baz")] == uploader_auth.call_args assert [ ("https://foo.bar",), {"cert": None, "client_cert": None, "dry_run": False, "skip_existing": False}, ] == uploader_upload.call_args
def test_dist_info_file_permissions(): module_path = fixtures_dir / "complete" WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl" with zipfile.ZipFile(str(whl)) as z: assert ( z.getinfo("my_package-1.2.3.dist-info/WHEEL").external_attr == 0o644 << 16 ) assert ( z.getinfo("my_package-1.2.3.dist-info/METADATA").external_attr == 0o644 << 16 ) assert ( z.getinfo("my_package-1.2.3.dist-info/RECORD").external_attr == 0o644 << 16 ) assert ( z.getinfo("my_package-1.2.3.dist-info/entry_points.txt").external_attr == 0o644 << 16 )
def test_debug_resolve_tree_option_gives_the_dependency_tree(tester, repo): cachy2 = get_package("cachy", "0.2.0") cachy2.add_dependency( Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(cachy2) repo.add_package(get_package("msgpack-python", "0.5.3")) tester.execute("cachy --tree") expected = """\ Resolving dependencies... Resolution results: cachy 0.2.0 `-- msgpack-python >=0.5 <0.6 """ assert expected == tester.io.fetch_output()
def test_builder_find_case_sensitive_excluded_files(mocker): p = mocker.patch("poetry.vcs.git.Git.get_ignored_files") p.return_value = [] builder = Builder( Factory().create_poetry( Path(__file__).parent / "fixtures" / "case_sensitive_exclusions" ), NullEnv(), NullIO(), ) assert builder.find_excluded_files() == { "my_package/FooBar/Bar.py", "my_package/FooBar/lowercasebar.py", "my_package/Foo/SecondBar.py", "my_package/Foo/Bar.py", "my_package/Foo/lowercasebar.py", "my_package/bar/foo.py", "my_package/bar/CapitalFoo.py", }
def test_show_basic_with_installed_packages_single( tester: "CommandTester", poetry: "Poetry", installed: "Repository" ): poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0")) cachy_010 = get_package("cachy", "0.1.0") cachy_010.description = "Cachy package" installed.add_package(cachy_010) poetry.locker.mock_lock_data( { "package": [ { "name": "cachy", "version": "0.1.0", "description": "Cachy package", "category": "main", "optional": False, "platform": "*", "python-versions": "*", "checksum": [], }, ], "metadata": { "python-versions": "*", "platform": "*", "content-hash": "123456789", "hashes": {"cachy": []}, }, } ) tester.execute("cachy") assert [ "name : cachy", "version : 0.1.0", "description : Cachy package", ] == [line.strip() for line in tester.io.fetch_output().splitlines()]
def handle(self) -> int: from pathlib import Path from cleo.io.inputs.string_input import StringInput from cleo.io.io import IO from poetry.factory import Factory from poetry.utils.env import EnvManager plugins = self.argument("plugins") system_env = EnvManager.get_system_env() env_dir = Path( os.getenv("POETRY_HOME") if os.getenv("POETRY_HOME") else system_env.path ) # From this point forward, all the logic will be deferred to # the remove command, by using the global `pyproject.toml` file. application = cast("Application", self.application) remove_command: "RemoveCommand" = cast( "RemoveCommand", application.find("remove") ) # We won't go through the event dispatching done by the application # so we need to configure the command manually remove_command.set_poetry(Factory().create_poetry(env_dir)) remove_command.set_env(system_env) application._configure_installer(remove_command, self._io) argv = ["remove"] + plugins if self.option("dry-run"): argv.append("--dry-run") return remove_command.run( IO( StringInput(" ".join(argv)), self._io.output, self._io.error_output, ) )
def test_simple_transitive(root, provider, repo): # Only one version of baz, so foo and bar will have to downgrade # until they reach it root.add_dependency(Factory.create_dependency("foo", "*")) add_to_repo(repo, "foo", "1.0.0", deps={"bar": "1.0.0"}) add_to_repo(repo, "foo", "2.0.0", deps={"bar": "2.0.0"}) add_to_repo(repo, "foo", "3.0.0", deps={"bar": "3.0.0"}) add_to_repo(repo, "bar", "1.0.0", deps={"baz": "*"}) add_to_repo(repo, "bar", "2.0.0", deps={"baz": "2.0.0"}) add_to_repo(repo, "bar", "3.0.0", deps={"baz": "3.0.0"}) add_to_repo(repo, "baz", "1.0.0") check_solver_result(root, provider, { "foo": "1.0.0", "bar": "1.0.0", "baz": "1.0.0" }, tries=3)
def find_best_candidate( self, package_name: str, target_package_version: str | None = None, allow_prereleases: bool = False, source: str | None = None, ) -> Package | None: """ Given a package name and optional version, returns the latest Package that matches """ from poetry.factory import Factory dependency = Factory.create_dependency( package_name, { "version": target_package_version or "*", "allow-prereleases": allow_prereleases, "source": source, }, ) candidates = self._pool.find_packages(dependency) only_prereleases = all(c.version.is_unstable() for c in candidates) if not candidates: return None package = None for candidate in candidates: if (candidate.is_prerelease() and not dependency.allows_prereleases() and not only_prereleases): continue # Select highest version of the two if package is None or package.version < candidate.version: package = candidate return package
def test_with_compatible_locked_dependencies(root, provider, repo): root.add_dependency(Factory.create_dependency("foo", "*")) add_to_repo(repo, "foo", "1.0.0", deps={"bar": "1.0.0"}) add_to_repo(repo, "foo", "1.0.1", deps={"bar": "1.0.1"}) add_to_repo(repo, "foo", "1.0.2", deps={"bar": "1.0.2"}) add_to_repo(repo, "bar", "1.0.0") add_to_repo(repo, "bar", "1.0.1") add_to_repo(repo, "bar", "1.0.2") check_solver_result( root, provider, result={ "foo": "1.0.1", "bar": "1.0.1" }, locked={ "foo": get_package("foo", "1.0.1"), "bar": get_package("bar", "1.0.1") }, )
def test_with_unrelated_locked_dependencies(root: "ProjectPackage", provider: "Provider", repo: "Repository"): root.add_dependency(Factory.create_dependency("foo", "*")) add_to_repo(repo, "foo", "1.0.0", deps={"bar": "1.0.0"}) add_to_repo(repo, "foo", "1.0.1", deps={"bar": "1.0.1"}) add_to_repo(repo, "foo", "1.0.2", deps={"bar": "1.0.2"}) add_to_repo(repo, "bar", "1.0.0") add_to_repo(repo, "bar", "1.0.1") add_to_repo(repo, "bar", "1.0.2") add_to_repo(repo, "baz", "1.0.0") check_solver_result( root, provider, result={ "foo": "1.0.2", "bar": "1.0.2" }, locked={"baz": get_package("baz", "1.0.1")}, )
def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config, fixture_name): uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch( "poetry.publishing.uploader.Uploader.upload") config.merge({ "repositories": { "foo": { "url": "http://foo.bar" } }, "http-basic": { "foo": { "username": "******", "password": "******" } }, }) mocker.patch("poetry.factory.Factory.create_config", return_value=config) poetry = Factory().create_poetry(fixture_dir(fixture_name)) io = BufferedIO() publisher = Publisher(poetry, io) publisher.publish("foo", None, None) assert [("foo", "bar")] == uploader_auth.call_args assert [ ("http://foo.bar", ), { "cert": None, "client_cert": None, "dry_run": False }, ] == uploader_upload.call_args assert "Publishing my-package (1.2.3) to foo" in io.fetch_output()
def test_with_src_module_file(): poetry = Factory().create_poetry(project("source_file")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) # Check setup.py setup = builder.build_setup() setup_ast = ast.parse(setup) setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)] ns = {} exec(compile(setup_ast, filename="setup.py", mode="exec"), ns) assert ns["package_dir"] == {"": "src"} assert ns["modules"] == ["module_src"] builder.build() sdist = fixtures_dir / "source_file" / "dist" / "module-src-0.1.tar.gz" assert sdist.exists() with tarfile.open(str(sdist), "r") as tar: assert "module-src-0.1/src/module_src.py" in tar.getnames()
def test_module_src(): module_path = fixtures_dir / "source_file" builder = CompleteBuilder(Factory().create_poetry(module_path), NullEnv(execute=True), NullIO()) builder.build() sdist = module_path / "dist" / "module-src-0.1.tar.gz" assert sdist.exists() with tarfile.open(str(sdist), "r") as tar: assert "module-src-0.1/src/module_src.py" in tar.getnames() whl = module_path / "dist" / "module_src-0.1-py2.py3-none-any.whl" assert whl.exists() zip = zipfile.ZipFile(str(whl)) try: assert "module_src.py" in zip.namelist() finally: zip.close()
def generate_system_pyproject(self) -> None: preserved = {} if self.system_pyproject.exists(): content = PyProjectTOML(self.system_pyproject).poetry_config for key in {"group", "source"}: if key in content: preserved[key] = content[key] package = ProjectPackage(name="poetry-instance", version=__version__) package.add_dependency( Dependency(name="poetry", constraint=f"{__version__}")) package.python_versions = ".".join( str(v) for v in self.env.version_info[:3]) content = Factory.create_pyproject_from_package(package=package) for key in preserved: content[key] = preserved[key] self.system_pyproject.write_text(content.as_string(), encoding="utf-8")
def verify_project_directory(path: Path, package_name: str, package_path: str, include_from: Optional[str] = None) -> Poetry: package_path = Path(package_path) assert path.is_dir() pyproject = path / "pyproject.toml" assert pyproject.is_file() init_file = path / package_path / "__init__.py" assert init_file.is_file() tests_init_file = path / "tests" / "__init__.py" assert tests_init_file.is_file() poetry = Factory().create_poetry(cwd=path) assert poetry.package.name == package_name if include_from: package_include = { "include": package_path.relative_to(include_from).parts[0], "from": include_from, } else: package_include = {"include": package_path.parts[0]} packages = poetry.local_config.get("packages") if not packages: assert poetry.local_config.get("name") == package_include.get( "include") else: assert len(packages) == 1 assert packages[0] == package_include return poetry
def test_build_should_temporarily_remove_the_pyproject_file(tmp_dir, mocker): move = mocker.patch("shutil.move") tmp_dir = Path(tmp_dir) env = MockEnv(path=tmp_dir, pip_version="19.1", execute=False) env.site_packages.mkdir(parents=True) module_path = fixtures_dir / "extended" builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO()) builder.build() expected = [["python", "-m", "pip", "install", "-e", str(module_path)]] assert expected == env.executed assert 2 == move.call_count expected_calls = [ mocker.call(str(module_path / "pyproject.toml"), str(module_path / "pyproject.tmp")), mocker.call(str(module_path / "pyproject.tmp"), str(module_path / "pyproject.toml")), ] assert expected_calls == move.call_args_list
def test_extras_dependencies_are_ordered(locker, root): package_a = get_package("A", "1.0.0") package_a.add_dependency( Factory.create_dependency("B", { "version": "^1.0.0", "optional": True, "extras": ["c", "a", "b"] })) package_a.requires[-1].activate() locker.set_lock_data(root, [package_a]) expected = """[[package]] name = "A" version = "1.0.0" description = "" category = "main" optional = false python-versions = "*" [package.dependencies] B = {version = "^1.0.0", extras = ["a", "b", "c"], optional = true} [metadata] lock-version = "1.1" python-versions = "*" content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831" [metadata.files] A = [] """ with locker.lock.open(encoding="utf-8") as f: content = f.read() assert expected == content
def get_self_command_dependencies(locked: bool = True) -> TOMLTable: from poetry.console.commands.self.self_command import SelfCommand from poetry.locations import CONFIG_DIR system_pyproject_file = SelfCommand.get_default_system_pyproject_file() assert system_pyproject_file.exists() assert system_pyproject_file.parent == Path(CONFIG_DIR) if locked: assert system_pyproject_file.parent.joinpath("poetry.lock").exists() poetry = Factory().create_poetry(system_pyproject_file.parent, disable_plugins=True) content = poetry.file.read()["tool"]["poetry"] assert "group" in content assert SelfCommand.ADDITIONAL_PACKAGE_GROUP in content["group"] assert "dependencies" in content["group"][ SelfCommand.ADDITIONAL_PACKAGE_GROUP] return content["group"][ SelfCommand.ADDITIONAL_PACKAGE_GROUP]["dependencies"]
def test_debug_resolve_gives_resolution_results(app, repo): command = app.find("debug resolve") tester = CommandTester(command) cachy2 = get_package("cachy", "0.2.0") cachy2.add_dependency( Factory.create_dependency("msgpack-python", ">=0.5 <0.6")) repo.add_package(get_package("cachy", "0.1.0")) repo.add_package(cachy2) repo.add_package(get_package("msgpack-python", "0.5.3")) tester.execute("cachy") expected = """\ Resolving dependencies... Resolution results: msgpack-python 0.5.3 cachy 0.2.0 """ assert expected == tester.io.fetch_output()
def test_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage): package_a = get_package("A", "1.0.0") package_a.add_dependency( Factory.create_dependency("B", { "version": "^1.0.0", "optional": True, "extras": ["c", "a", "b"] })) package_a.requires[-1].activate() locker.set_lock_data(root, [package_a]) expected = """[[package]] name = "A" version = "1.0.0" description = "" category = "main" optional = false python-versions = "*" [package.dependencies] B = {version = "^1.0.0", extras = ["a", "b", "c"], optional = true} [metadata] lock-version = "1.1" python-versions = "*" content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" [metadata.files] A = [] """ with locker.lock.open(encoding="utf-8") as f: content = f.read() assert content == expected
def install_directory(self, package: Package) -> str | int: from cleo.io.null_io import NullIO from poetry.factory import Factory req: Path if package.root_dir: req = (package.root_dir / package.source_url).as_posix() else: req = Path(package.source_url).resolve(strict=False) pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) if pyproject.is_poetry_project(): # Even if there is a build system specified # some versions of pip (< 19.0.0) don't understand it # so we need to check the version of pip to know # if we can rely on the build system legacy_pip = self._env.pip_version < self._env.pip_version.__class__( 19, 0, 0) package_poetry = Factory().create_poetry( pyproject.file.path.parent) if package.develop and not package_poetry.package.build_script: from poetry.masonry.builders.editable import EditableBuilder # This is a Poetry package in editable mode # we can use the EditableBuilder without going through pip # to install it, unless it has a build script. builder = EditableBuilder(package_poetry, self._env, NullIO()) builder.build() return 0 elif legacy_pip or package_poetry.package.build_script: from poetry.core.masonry.builders.sdist import SdistBuilder # We need to rely on creating a temporary setup.py # file since the version of pip does not support # build-systems # We also need it for non-PEP-517 packages builder = SdistBuilder(package_poetry) with builder.setup_py(): if package.develop: return pip_install( directory=req, environment=self._env, upgrade=True, editable=True, ) return pip_install(path=req, environment=self._env, deps=False, upgrade=True) if package.develop: return pip_install(directory=req, environment=self._env, upgrade=True, editable=True) return pip_install(path=req, environment=self._env, deps=False, upgrade=True)
def poetry(fixture_dir, locker): p = Factory().create_poetry(fixture_dir("sample_project")) p._locker = locker return p
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 _install_directory(self, operation): from poetry.factory import Factory package = operation.package operation_message = self.get_operation_message(operation) message = " <fg=blue;options=bold>•</> {message}: <info>Building...</info>".format( message=operation_message, ) self._write(operation, message) if package.root_dir: req = os.path.join(str(package.root_dir), package.source_url) else: req = os.path.realpath(package.source_url) args = ["install", "--no-deps", "-U"] pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) if pyproject.is_poetry_project(): # Even if there is a build system specified # some versions of pip (< 19.0.0) don't understand it # so we need to check the version of pip to know # if we can rely on the build system legacy_pip = self._env.pip_version < self._env.pip_version.__class__( 19, 0, 0) package_poetry = Factory().create_poetry( pyproject.file.path.parent) if package.develop and not package_poetry.package.build_script: from poetry.masonry.builders.editable import EditableBuilder # This is a Poetry package in editable mode # we can use the EditableBuilder without going through pip # to install it, unless it has a build script. builder = EditableBuilder(package_poetry, self._env, NullIO()) builder.build() return 0 elif legacy_pip or package_poetry.package.build_script: from poetry.core.masonry.builders.sdist import SdistBuilder # We need to rely on creating a temporary setup.py # file since the version of pip does not support # build-systems # We also need it for non-PEP-517 packages builder = SdistBuilder(package_poetry) with builder.setup_py(): if package.develop: args.append("-e") args.append(req) return self.run_pip(*args) if package.develop: args.append("-e") args.append(req) return self.run_pip(*args)
# poetry when there is no setup.py and an extension needs to be compiled. # See https://github.com/python-poetry/poetry/issues/1516. Running this # script creates a setup.py filled out with information generated by # poetry when parsing the pyproject.toml. import os import sys # If there is a global installation of poetry, prefer that. poetry_python_lib = os.path.expanduser('~/.poetry/lib') sys.path.append(os.path.realpath(poetry_python_lib)) try: from poetry.masonry.builders.sdist import SdistBuilder from poetry.factory import Factory except (ImportError, ModuleNotFoundError) as ee: raise ImportError('install poetry by doing pip install poetry to use ' f'this script: {ee}') # Generate a Poetry object that knows about the metadata in pyproject.toml factory = Factory() poetry = factory.create_poetry(os.path.dirname(__file__)) # Use the SdistBuilder to genrate a blob for setup.py sdist_builder = SdistBuilder(poetry, None, None) setuppy_blob = sdist_builder.build_setup() with open('setup.py', 'wb') as unit: unit.write(setuppy_blob) unit.write(b'\n# This setup.py was autogenerated using poetry.\n')
def extended_without_setup_poetry(): poetry = Factory().create_poetry( Path(__file__).parent.parent.parent / "fixtures" / "extended_project_without_setup") return poetry
def project_with_include(): poetry = Factory().create_poetry( Path(__file__).parent.parent.parent / "fixtures" / "with-include") return poetry
def simple_poetry(): poetry = Factory().create_poetry( Path(__file__).parent.parent.parent / "fixtures" / "simple_project") return poetry
def get_pyproject_hash(directory): p = Factory().create_poetry(directory) return json.dumps({"result": p.locker._get_content_hash()})
def test_find_packages_only_prereleases_empty_when_not_any(): repo = MockRepository() packages = repo.find_packages(Factory.create_dependency("black", ">=1")) assert len(packages) == 0