Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace the export command by the export plugin #4824

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ generate-setup-file = false
[tool.poetry.dependencies]
python = "^3.6"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious -- does the export plugin require the current changes in core? If not I'd like to move this change to a new PR as it affects other PRs and changes (e.g. unblocks #4743).

poetry-core = "^1.1.0a6"
poetry-core = { git = "https://github.com/python-poetry/poetry-core.git", branch = "master" }
cachecontrol = { version = "^0.12.9", extras = ["filecache"] }
cachy = "^0.3.0"
cleo = "^1.0.0a4"
Expand All @@ -47,6 +47,7 @@ keyring = ">=21.2.0"
packaging = "^20.4"
pexpect = "^4.7.0"
pkginfo = "^1.5"
poetry-export-plugin = "^0.2.1"
requests = "^2.18"
requests-toolbelt = "^0.9.1"
shellingham = "^1.1"
Expand Down
1 change: 0 additions & 1 deletion src/poetry/console/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def _load() -> Type[Command]:
"build",
"check",
"config",
"export",
"init",
"install",
"lock",
Expand Down
79 changes: 0 additions & 79 deletions src/poetry/console/commands/export.py

This file was deleted.

7 changes: 1 addition & 6 deletions src/poetry/console/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,16 +377,11 @@ def _find_best_version_for_package(
return package.pretty_name, selector.find_recommended_require_version(package)

def _parse_requirements(self, requirements: List[str]) -> List[Dict[str, str]]:
from poetry.core.pyproject.exceptions import PyProjectException

from poetry.puzzle.provider import Provider

result = []

try:
cwd = self.poetry.file.parent
except (PyProjectException, RuntimeError):
cwd = Path.cwd()
cwd = Path.cwd()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks unrelated. I'm all for simplification, but could we explain this change/break it out into a separate commit?


for requirement in requirements:
requirement = requirement.strip()
Expand Down
167 changes: 57 additions & 110 deletions src/poetry/console/commands/plugin/add.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import os

from typing import Dict
from typing import List
from typing import cast

from cleo.helpers import argument
from cleo.helpers import option

from poetry.console.application import Application
from poetry.console.commands.init import InitCommand
from poetry.console.commands.update import UpdateCommand
from poetry.console.commands.plugin.plugin_command_mixin import PluginCommandMixin


class PluginAddCommand(InitCommand):
class PluginAddCommand(InitCommand, PluginCommandMixin):

name = "plugin add"

Expand Down Expand Up @@ -51,80 +44,69 @@ class PluginAddCommand(InitCommand):
"""

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.locations import home_dir
from poetry.utils.env import EnvManager
from poetry.utils.helpers import canonicalize_name

plugins = self.argument("plugins")
requested_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") if os.getenv("POETRY_HOME") else system_env.path
)
env_dir = home_dir()

# 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"
existing_plugins = {}
if env_dir.joinpath("plugins.toml").exists():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think a separate file looks easier. If I'm reading this right, this is a manifest of known plugins in $POETRY_HOME/the Poetry venv. Does this require a docs change at all? I don't think it does, but I'd like to be sure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire file (and most of these codepaths) are long, imperative, and repeat a fair bit. We're also sprinkling env_dir.joinpath("plugins.toml") everywhere. Have we given any thought to encapsulating all the logic around reading/manipulating this file in a class PluginManifest or similar?

existing_plugins = tomlkit.loads(
env_dir.joinpath("plugins.toml").read_text(encoding="utf-8")
)

if existing_packages:
self.notify_about_existing_packages(existing_packages)
root_package = self.create_env_package(system_env, existing_plugins)

plugins = [plugin for plugin in plugins if plugin not in existing_packages]
installed_plugins = {
canonicalize_name(ep.distro.name) for ep in self.get_plugin_entry_points()
}

if not plugins:
return 0
# We check for the plugins existence first.
plugins = []
skipped_plugins = []
parsed_plugins = self._parse_requirements(requested_plugins)
for i, plugin in enumerate(parsed_plugins):
plugin_name = canonicalize_name(plugin.pop("name"))
if plugin_name in installed_plugins:
if plugin_name not in existing_plugins:
existing_plugins[plugin_name] = plugin

if not plugin:
skipped_plugins.append(plugin_name)

plugins = self._determine_requirements(plugins)
continue

# 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
)
plugins.append(canonicalize_name(requested_plugins[i]))

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)
if skipped_plugins:
self.line(
"The following plugins are already present and will be skipped:\n"
)
for name in sorted(skipped_plugins):
self.line(f" • <c1>{name}</c1>")

break
self.line(
"\nIf you want to upgrade it to the latest compatible version, "
"you can use `poetry plugin add plugin@latest.\n"
sdispater marked this conversation as resolved.
Show resolved Hide resolved
)

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"]
if not plugins:
return 0

plugins = self._determine_requirements(plugins)

# We add the plugins to the plugins.toml file
plugin_names = []
for plugin in plugins:
if "version" in plugin:
Expand All @@ -141,57 +123,22 @@ def handle(self) -> int:
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,
root_package.add_dependency(
Factory.create_dependency(plugin["name"], constraint)
)
)

def get_existing_packages_from_input(
self, packages: List[str], poetry_content: Dict, target_section: str
) -> List[str]:
existing_packages = []
existing_plugins[plugin["name"]] = constraint
plugin_names.append(plugin["name"])

for name in packages:
for key in poetry_content[target_section]:
if key.lower() == name.lower():
existing_packages.append(name)
return_code = self.update(
system_env, root_package, self._io, whitelist=plugin_names
)

return existing_packages
if return_code != 0 or self.option("dry-run"):
return return_code

def notify_about_existing_packages(self, existing_packages: List[str]) -> None:
self.line(
"The following plugins are already present in the "
"<c2>pyproject.toml</c2> file and will be skipped:\n"
)
for name in existing_packages:
self.line(f" • <c1>{name}</c1>")

self.line(
"\nIf you want to update it to the latest compatible version, "
"you can use `<c2>poetry plugin update package</c2>`.\n"
"If you prefer to upgrade it to the latest available version, "
"you can use `<c2>poetry plugin add package@latest</c2>`.\n"
env_dir.joinpath("plugins.toml").write_text(
tomlkit.dumps(existing_plugins, sort_keys=True), encoding="utf-8"
)

return 0
Loading