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

[optimize] early-ignore dependencies not matching chosen markers #4956

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
28 changes: 28 additions & 0 deletions docs/dependency-specification.md
Expand Up @@ -228,3 +228,31 @@ markers = "platform_python_implementation == 'CPython'"
The same information is still present, and ends up providing the exact
same specification. It's simply split into multiple, slightly more readable,
lines.

## Optimizing by environment pre-filtering

Poetry may take long to build a lock when they are exponentially many combinations of downstream packages, due to different variants of OS, platform architecture, Python versions and so on.

With this [PR 4956 ](https://github.com/python-poetry/poetry/pull/4956) we can narrow down the search space in the beginning.

As a real use-case consider Azure packages
```
[tool.poetry]
name = "test"
version = "0.1.0"
description = "demonstrates that poetry triggers overrides to easily (can be early pruned?)"
authors = ["Maciej Skorski <maciej.skorski@gmail.com>"]

[tool.poetry.dependencies]
python = "~3.8"
azureml-dataset-runtime = {version="1.37.0"}
```

Installing with debugging `poetry install --dry-run -vv` reveals that poetry considers many package combinations for Windows.
However, after adding the section `tool.poetry.dependencies` and specifying [environment markers](https://www.python.org/dev/peps/pep-0508/#environment-markers) there is no branching anymore.

```
[tool.poetry.target_env]
sys_platform = "linux"
platform_system = "Linux"
```
11 changes: 11 additions & 0 deletions src/poetry/console/commands/install.py
Expand Up @@ -67,6 +67,13 @@ class InstallCommand(InstallerCommand):
flag=False,
multiple=True,
),
option(
Copy link
Member

Choose a reason for hiding this comment

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

This option is dead and should be removed (given the move to using the pyproject.toml).

"match-marker",
None,
"List of environment markers to be matched early, to speed up resolving dependencies (see PEP 508).",
flag=False,
multiple=True,
),
]

help = """The <info>install</info> command reads the <comment>poetry.lock</> file from
Expand Down Expand Up @@ -94,6 +101,10 @@ def handle(self) -> int:
self.poetry.config.get("experimental.new-installer", False)
)

markers_to_filter = self.option("match-marker")
if markers_to_filter:
self._installer.match_markers(markers_to_filter)

extras = []
for extra in self.option("extras"):
if " " in extra:
Expand Down
8 changes: 8 additions & 0 deletions src/poetry/factory.py
Expand Up @@ -37,6 +37,14 @@ def create_poetry(
if io is None:
io = NullIO()

# redirect to the extended schema (from poetry, not from poetry-core)
Copy link
Member

Choose a reason for hiding this comment

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

We should be updating the schema in poetry-core as well, even if the implementation is in poetry only. The intention is to drop all schemas from this repo.

import os

import poetry.core.json

poetry.core.json.SCHEMA_DIR = os.path.join(
os.path.dirname(__file__), "json/schemas/extended"
)
base_poetry = super().create_poetry(cwd)

locker = Locker(
Expand Down
23 changes: 22 additions & 1 deletion src/poetry/installation/installer.py
Expand Up @@ -66,6 +66,8 @@ def __init__(

self._extras = []

self._match_markers = []

if executor is None:
executor = Executor(self._env, self._pool, config, self._io)

Expand Down Expand Up @@ -190,6 +192,11 @@ def extras(self, extras: list) -> "Installer":

return self

def match_markers(self, markers: list) -> "Installer":
self._match_markers = markers

return self

def use_executor(self, use_executor: bool = True) -> "Installer":
self._use_executor = use_executor

Expand Down Expand Up @@ -249,7 +256,21 @@ def _do_install(self, local_repo: Repository) -> int:
self._io,
)

ops = solver.solve(use_latest=self._whitelist).calculate_operations()
if (
Copy link
Member

Choose a reason for hiding this comment

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

I'm not 100% on if this is the only way to get at the config, but this smells to me. I'm pretty sure you should be able to get at the Config object from here, or at least create one from a Factory if not.

hasattr(self._locker, "_local_config")
and "target_env" in self._locker._local_config
):
# load and format as VirtualEnv from poetry.utils.env
markers_to_filter = self._locker._local_config["target_env"]
if markers_to_filter:
version_info = markers_to_filter.get("version_info")
if version_info:
markers_to_filter["version_info"] = tuple(version_info)
else:
markers_to_filter = {}

with solver.use_markers_filter(markers_to_filter):
ops = solver.solve(use_latest=self._whitelist).calculate_operations()
else:
self._io.write_line("<info>Installing dependencies from lock file</>")

Expand Down