def _install_directory(self, operation: Install | Update) -> int: from poetry.factory import Factory package = operation.package operation_message = self.get_operation_message(operation) message = ( f" <fg=blue;options=bold>•</> {operation_message}:" " <info>Building...</info>" ) self._write(operation, message) assert package.source_url is not None if package.root_dir: req = package.root_dir / package.source_url 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__.from_parts(19, 0, 0) ) package_poetry = Factory().create_poetry(pyproject.file.path.parent) builder: Builder 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 self.pip_install(req, upgrade=True, editable=True) return self.pip_install(req, upgrade=True) if package.develop: return self.pip_install(req, upgrade=True, editable=True) return self.pip_install(req, upgrade=True)
def install_directory(self, package: "Package") -> Union[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_editable_install(directory=req, environment=self._env) return pip_install(path=req, environment=self._env, deps=False, upgrade=True) if package.develop: return pip_editable_install(directory=req, environment=self._env) return pip_install(path=req, environment=self._env, deps=False, upgrade=True)
def test_pyproject_toml_poetry_config( pyproject_toml: Path, poetry_section: str ) -> None: pyproject = PyProjectTOML(pyproject_toml) doc: dict[str, Any] = TOMLFile(pyproject_toml.as_posix()).read() config = doc["tool"]["poetry"] assert pyproject.is_poetry_project() assert pyproject.poetry_config == config
def install_directory(self, package): from poetry.factory import Factory from poetry.io.null_io import NullIO if package.root_dir: req = (package.root_dir / package.source_url).as_posix() 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(*args)
def test_pyproject_toml_reload(pyproject_toml, poetry_section): pyproject = PyProjectTOML(pyproject_toml) name_original = pyproject.poetry_config["name"] name_new = str(uuid.uuid4()) pyproject.poetry_config["name"] = name_new assert pyproject.poetry_config["name"] == name_new pyproject.reload() assert pyproject.poetry_config["name"] == name_original
def test_pyproject_toml_no_poetry_config(pyproject_toml): pyproject = PyProjectTOML(pyproject_toml) assert not pyproject.is_poetry_project() with pytest.raises(PyProjectException) as excval: _ = pyproject.poetry_config assert "[tool.poetry] section not found in {}".format( pyproject_toml.as_posix()) in str(excval.value)
def test_pyproject_toml_no_poetry_config(pyproject_toml: Path) -> None: pyproject = PyProjectTOML(pyproject_toml) assert not pyproject.is_poetry_project() with pytest.raises(PyProjectException) as excval: _ = pyproject.poetry_config assert f"[tool.poetry] section not found in {pyproject_toml.as_posix()}" in str( excval.value )
def create_poetry(self, cwd: Path | None = None, with_groups: bool = True) -> Poetry: from poetry.core.poetry import Poetry from poetry.core.pyproject.toml import PyProjectTOML poetry_file = self.locate(cwd) local_config = PyProjectTOML(path=poetry_file).poetry_config # Checking validity check_result = self.validate(local_config) if check_result["errors"]: message = "" for error in check_result["errors"]: message += f" - {error}\n" raise RuntimeError("The Poetry configuration is invalid:\n" + message) # Load package name = cast(str, local_config["name"]) version = cast(str, local_config["version"]) package = self.get_package(name, version) package = self.configure_package(package, local_config, poetry_file.parent, with_groups=with_groups) return Poetry(poetry_file, local_config, package)
def test_pyproject_toml_non_existent(pyproject_toml): pyproject_toml.unlink() pyproject = PyProjectTOML(pyproject_toml) build_system = pyproject.build_system assert pyproject.data == TOMLDocument() assert build_system.requires == ["poetry-core"] assert build_system.build_backend == "poetry.core.masonry.api"
def assert_version(repo: Repo, expected_revision: str) -> None: version = PyProjectTOML(path=Path(repo.path).joinpath( "pyproject.toml")).poetry_config["version"] revision = Git.get_revision(repo=repo) assert revision == expected_revision assert revision in REVISION_TO_VERSION_MAP assert version == REVISION_TO_VERSION_MAP[revision]
def test_pyproject_toml_no_build_system_defaults(): pyproject_toml = (Path(__file__).parent.parent / "fixtures" / "project_with_build_system_requires" / "pyproject.toml") build_system = PyProjectTOML(pyproject_toml).build_system assert build_system.requires == ["poetry-core", "Cython~=0.29.6"] assert len(build_system.dependencies) == 2 assert build_system.dependencies[0].to_pep_508() == "poetry-core" assert build_system.dependencies[1].to_pep_508( ) == "Cython (>=0.29.6,<0.30.0)"
def __init__( self, file: "Path", local_config: dict, package: "ProjectPackage", ) -> None: from poetry.core.pyproject.toml import PyProjectTOML # noqa self._pyproject = PyProjectTOML(file) self._package = package self._local_config = local_config
def __init__( self, file: Path, local_config: dict[str, Any], package: ProjectPackage, ) -> None: from poetry.core.pyproject.toml import PyProjectTOML self._pyproject = PyProjectTOML(file) self._package = package self._local_config = local_config
def __init__( self, name: str, path: Path, groups: Optional[List[str]] = None, optional: bool = False, base: Optional[Path] = None, develop: bool = False, extras: Optional[Union[List[str], FrozenSet[str]]] = None, ) -> None: from poetry.core.pyproject.toml import PyProjectTOML self._path = path self._base = base or Path.cwd() self._full_path = path if not self._path.is_absolute(): try: self._full_path = self._base.joinpath(self._path).resolve() except FileNotFoundError: raise ValueError("Directory {} does not exist".format(self._path)) self._develop = develop self._supports_poetry = False if not self._full_path.exists(): raise ValueError("Directory {} does not exist".format(self._path)) if self._full_path.is_file(): raise ValueError("{} is a file, expected a directory".format(self._path)) # Checking content to determine actions setup = self._full_path / "setup.py" self._supports_poetry = PyProjectTOML( self._full_path / "pyproject.toml" ).is_poetry_project() if not setup.exists() and not self._supports_poetry: raise ValueError( "Directory {} does not seem to be a Python package".format( self._full_path ) ) super(DirectoryDependency, self).__init__( name, "*", groups=groups, optional=optional, allows_prereleases=True, source_type="directory", source_url=self._full_path.as_posix(), extras=extras, )
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 handle(self): # Load poetry config and display errors, if any poetry_file = Factory.locate(Path.cwd()) config = PyProjectTOML(poetry_file).poetry_config check_result = Factory.validate(config, strict=True) if not check_result["errors"] and not check_result["warnings"]: self.info("All set!") return 0 for error in check_result["errors"]: self.line("<error>Error: {}</error>".format(error)) for error in check_result["warnings"]: self.line("<warning>Warning: {}</warning>".format(error)) return 1
def handle(self) -> int: from poetry.core.pyproject.toml import PyProjectTOML from poetry.factory import Factory # Load poetry config and display errors, if any poetry_file = Factory.locate(Path.cwd()) config = PyProjectTOML(poetry_file).poetry_config check_result = Factory.validate(config, strict=True) if not check_result["errors"] and not check_result["warnings"]: self.info("All set!") return 0 for error in check_result["errors"]: self.line_error(f"<error>Error: {error}</error>") for error in check_result["warnings"]: self.line_error(f"<warning>Warning: {error}</warning>") return 1
def test_pyproject_toml_save(pyproject_toml, poetry_section, build_system_section): pyproject = PyProjectTOML(pyproject_toml) name = str(uuid.uuid4()) build_backend = str(uuid.uuid4()) build_requires = str(uuid.uuid4()) pyproject.poetry_config["name"] = name pyproject.build_system.build_backend = build_backend pyproject.build_system.requires.append(build_requires) pyproject.save() pyproject = PyProjectTOML(pyproject_toml) assert pyproject.poetry_config["name"] == name assert pyproject.build_system.build_backend == build_backend assert build_requires in pyproject.build_system.requires
def handle(self) -> int: from pathlib import Path from poetry.core.vcs.git import GitConfig from poetry.layouts import layout from poetry.utils.env import SystemEnv pyproject = PyProjectTOML(Path.cwd() / "pyproject.toml") if pyproject.file.exists(): if pyproject.is_poetry_project(): self.line( "<error>A pyproject.toml file with a poetry section already exists.</error>" ) return 1 if pyproject.data.get("build-system"): self.line( "<error>A pyproject.toml file with a defined build-system already exists.</error>" ) return 1 vcs_config = GitConfig() self.line("") self.line( "This command will guide you through creating your <info>pyproject.toml</> config." ) self.line("") name = self.option("name") if not name: name = Path.cwd().name.lower() question = self.create_question( "Package name [<comment>{}</comment>]: ".format(name), default=name ) name = self.ask(question) version = "0.1.0" question = self.create_question( "Version [<comment>{}</comment>]: ".format(version), default=version ) version = self.ask(question) description = self.option("description") or "" question = self.create_question( "Description [<comment>{}</comment>]: ".format(description), default=description, ) description = self.ask(question) author = self.option("author") if not author and vcs_config and vcs_config.get("user.name"): author = vcs_config["user.name"] author_email = vcs_config.get("user.email") if author_email: author += " <{}>".format(author_email) question = self.create_question( "Author [<comment>{}</comment>, n to skip]: ".format(author), default=author ) question.set_validator(lambda v: self._validate_author(v, author)) author = self.ask(question) if not author: authors = [] else: authors = [author] license = self.option("license") or "" question = self.create_question( "License [<comment>{}</comment>]: ".format(license), default=license ) question.set_validator(self._validate_license) license = self.ask(question) python = self.option("python") if not python: current_env = SystemEnv(Path(sys.executable)) default_python = "^{}".format( ".".join(str(v) for v in current_env.version_info[:2]) ) question = self.create_question( "Compatible Python versions [<comment>{}</comment>]: ".format( default_python ), default=default_python, ) python = self.ask(question) self.line("") requirements = {} if self.option("dependency"): requirements = self._format_requirements( self._determine_requirements(self.option("dependency")) ) question = "Would you like to define your main dependencies interactively?" help_message = ( "You can specify a package in the following forms:\n" " - A single name (<b>requests</b>)\n" " - A name and a constraint (<b>requests@^2.23.0</b>)\n" " - A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)\n" " - A git url with a revision (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)\n" " - A file path (<b>../my-package/my-package.whl</b>)\n" " - A directory (<b>../my-package/</b>)\n" " - A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n" ) help_displayed = False if self.confirm(question, True): self.line(help_message) help_displayed = True requirements.update( self._format_requirements(self._determine_requirements([])) ) self.line("") dev_requirements = {} if self.option("dev-dependency"): dev_requirements = self._format_requirements( self._determine_requirements(self.option("dev-dependency")) ) question = ( "Would you like to define your development dependencies interactively?" ) if self.confirm(question, True): if not help_displayed: self.line(help_message) dev_requirements.update( self._format_requirements(self._determine_requirements([])) ) self.line("") layout_ = layout("standard")( name, version, description=description, author=authors[0] if authors else None, license=license, python=python, dependencies=requirements, dev_dependencies=dev_requirements, ) content = layout_.generate_poetry_content(original=pyproject) if self.io.is_interactive(): self.line("<info>Generated file</info>") self.line("") self.line(content) self.line("") if not self.confirm("Do you confirm generation?", True): self.line("<error>Command aborted</error>") return 1 with (Path.cwd() / "pyproject.toml").open("w", encoding="utf-8") as f: f.write(content)
def test_pyproject_toml_simple( pyproject_toml: Path, build_system_section: str, poetry_section: str ) -> None: data = TOMLFile(pyproject_toml.as_posix()).read() assert PyProjectTOML(pyproject_toml).data == data
def _get_poetry_package(path: Path) -> Optional[ProjectPackage]: # Note: we ignore any setup.py file at this step # TODO: add support for handling non-poetry PEP-517 builds if PyProjectTOML(path.joinpath("pyproject.toml")).is_poetry_project(): return Factory().create_poetry(path).package
def _install_directory(self, operation: Union[Install, Update]) -> int: 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)
def _write_poetry(self, path: Path) -> None: pyproject = PyProjectTOML(path / "pyproject.toml") content = self.generate_poetry_content() for section in content: pyproject.data.append(section, content[section]) pyproject.save()
def handle(self) -> int: from pathlib import Path import tomlkit from cleo.io.inputs.string_input import StringInput from cleo.io.io import IO from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.semver.helpers import parse_constraint from poetry.factory import Factory from poetry.packages.project_package import ProjectPackage from poetry.repositories.installed_repository import InstalledRepository from poetry.utils.env import EnvManager plugins = self.argument("plugins") # Plugins should be installed in the system env to be globally available system_env = EnvManager.get_system_env(naive=True) env_dir = Path(os.getenv("POETRY_HOME") or system_env.path) # We check for the plugins existence first. if env_dir.joinpath("pyproject.toml").exists(): pyproject = tomlkit.loads( env_dir.joinpath("pyproject.toml").read_text(encoding="utf-8")) poetry_content = pyproject["tool"]["poetry"] existing_packages = self.get_existing_packages_from_input( plugins, poetry_content, "dependencies") if existing_packages: self.notify_about_existing_packages(existing_packages) plugins = [ plugin for plugin in plugins if plugin not in existing_packages ] if not plugins: return 0 plugins = self._determine_requirements(plugins) # We retrieve the packages installed in the system environment. # We assume that this environment will be a self contained virtual environment # built by the official installer or by pipx. # If not, it might lead to side effects since other installed packages # might not be required by Poetry but still taken into account when resolving dependencies. installed_repository = InstalledRepository.load(system_env, with_dependencies=True) root_package = None for package in installed_repository.packages: if package.name == "poetry": root_package = ProjectPackage(package.name, package.version) for dependency in package.requires: root_package.add_dependency(dependency) break root_package.python_versions = ".".join( str(v) for v in system_env.version_info[:3]) # We create a `pyproject.toml` file based on all the information # we have about the current environment. if not env_dir.joinpath("pyproject.toml").exists(): Factory.create_pyproject_from_package(root_package, env_dir) # We add the plugins to the dependencies section of the previously # created `pyproject.toml` file pyproject = PyProjectTOML(env_dir.joinpath("pyproject.toml")) poetry_content = pyproject.poetry_config poetry_dependency_section = poetry_content["dependencies"] plugin_names = [] for plugin in plugins: if "version" in plugin: # Validate version constraint parse_constraint(plugin["version"]) constraint = tomlkit.inline_table() for name, value in plugin.items(): if name == "name": continue constraint[name] = value if len(constraint) == 1 and "version" in constraint: constraint = constraint["version"] poetry_dependency_section[plugin["name"]] = constraint plugin_names.append(plugin["name"]) pyproject.save() # From this point forward, all the logic will be deferred to # the update command, by using the previously created `pyproject.toml` # file. application = cast(Application, self.application) update_command: UpdateCommand = cast(UpdateCommand, application.find("update")) # We won't go through the event dispatching done by the application # so we need to configure the command manually update_command.set_poetry(Factory().create_poetry(env_dir)) update_command.set_env(system_env) application._configure_installer(update_command, self._io) argv = ["update"] + plugin_names if self.option("dry-run"): argv.append("--dry-run") return update_command.run( IO( StringInput(" ".join(argv)), self._io.output, self._io.error_output, ))
def test_pyproject_toml_simple(pyproject_toml, build_system_section, poetry_section): data = TOMLFile(pyproject_toml.as_posix()).read() assert PyProjectTOML(pyproject_toml).data == data
def test_pyproject_toml_poetry_config(pyproject_toml, poetry_section): pyproject = PyProjectTOML(pyproject_toml) config = TOMLFile(pyproject_toml.as_posix()).read()["tool"]["poetry"] assert pyproject.is_poetry_project() assert pyproject.poetry_config == config
def test_pyproject_toml_build_requires_as_dependencies(pyproject_toml): build_system = PyProjectTOML(pyproject_toml).build_system assert build_system.requires == ["setuptools", "wheel"] assert build_system.build_backend == "setuptools.build_meta:__legacy__"