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

installer: add config installer.only-binary #9150

Merged
merged 3 commits into from
Mar 14, 2024
Merged
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
21 changes: 19 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,7 @@ you encounter on the [issue tracker](https://github.com/python-poetry/poetry/iss

*Introduced in 1.2.0*

When set this configuration allows users to configure package distribution format policy for all or
specific packages.
When set, this configuration allows users to disallow the use of binary distribution format for all, none or specific packages.

| Configuration | Description |
|------------------------|------------------------------------------------------------|
Expand Down Expand Up @@ -259,6 +258,24 @@ Unless this is required system-wide, if configured globally, you could encounter
across all your projects if incorrectly set.
{{% /warning %}}

### `installer.only-binary`

**Type**: `string | boolean`

**Default**: `false`

**Environment Variable**: `POETRY_INSTALLER_ONLY_BINARY`

*Introduced in 1.9.0*

When set, this configuration allows users to enforce the use of binary distribution format for all, none or
specific packages.

{{% note %}}
Please refer to [`installer.no-binary`]({{< relref "configuration#installerno-binary" >}}) for information on allowed
values, usage instructions and warnings.
{{% /note %}}

### `installer.parallel`

**Type**: `boolean`
Expand Down
3 changes: 2 additions & 1 deletion src/poetry/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class Config:
"parallel": True,
"max-workers": None,
"no-binary": None,
"only-binary": None,
},
"solver": {
"lazy-wheel": True,
Expand Down Expand Up @@ -323,7 +324,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
if name == "installer.max-workers":
return int_normalizer

if name == "installer.no-binary":
if name in ["installer.no-binary", "installer.only-binary"]:
return PackageFilterPolicy.normalize

return lambda val: val
Expand Down
4 changes: 4 additions & 0 deletions src/poetry/console/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any]]:
PackageFilterPolicy.validator,
PackageFilterPolicy.normalize,
),
"installer.only-binary": (
PackageFilterPolicy.validator,
PackageFilterPolicy.normalize,
),
"solver.lazy-wheel": (boolean_validator, boolean_normalizer),
"warnings.export": (boolean_validator, boolean_normalizer),
"keyring.enabled": (boolean_validator, boolean_normalizer),
Expand Down
12 changes: 12 additions & 0 deletions src/poetry/installation/chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def __init__(
self._no_binary_policy: PackageFilterPolicy = PackageFilterPolicy(
self._config.get("installer.no-binary", [])
)
self._only_binary_policy: PackageFilterPolicy = PackageFilterPolicy(
self._config.get("installer.only-binary", [])
)

def choose_for(self, package: Package) -> Link:
"""
Expand Down Expand Up @@ -68,6 +71,15 @@ def choose_for(self, package: Package) -> Link:
logger.debug("Skipping unsupported distribution %s", link.filename)
continue

if link.is_sdist and not self._only_binary_policy.allows(package.name):
logger.debug(
"Skipping source distribution for %s as requested in only binary policy for"
" package (%s)",
link.filename,
package.name,
)
continue

links.append(link)

if not links:
Expand Down
19 changes: 15 additions & 4 deletions tests/console/commands/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def test_list_displays_default_value_if_not_set(
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
Expand Down Expand Up @@ -90,6 +91,7 @@ def test_list_displays_set_get_setting(
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
Expand Down Expand Up @@ -143,6 +145,7 @@ def test_unset_setting(
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
Expand Down Expand Up @@ -174,6 +177,7 @@ def test_unset_repo_setting(
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
Expand Down Expand Up @@ -303,6 +307,7 @@ def test_list_displays_set_get_local_setting(
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
Expand Down Expand Up @@ -342,6 +347,7 @@ def test_list_must_not_display_sources_from_pyproject_toml(
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.only-binary = null
installer.parallel = true
keyring.enabled = true
repositories.foo.url = "https://foo.bar/simple/"
Expand Down Expand Up @@ -515,6 +521,13 @@ def test_config_installer_parallel(
assert workers == 1


@pytest.mark.parametrize(
("setting",),
[
("installer.no-binary",),
("installer.only-binary",),
],
)
@pytest.mark.parametrize(
("value", "expected"),
[
Expand All @@ -528,11 +541,9 @@ def test_config_installer_parallel(
("", []),
],
)
def test_config_installer_no_binary(
tester: CommandTester, value: str, expected: list[str]
def test_config_installer_binary_filter_config(
tester: CommandTester, setting: str, value: str, expected: list[str]
) -> None:
setting = "installer.no-binary"

tester.execute(setting)
assert tester.io.fetch_output().strip() == "null"

Expand Down
170 changes: 105 additions & 65 deletions tests/installation/test_chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,18 @@ def pool() -> RepositoryPool:
return pool


@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_universal_wheel_link_if_available(
def check_chosen_link_filename(
env: MockEnv,
mock_pypi: None,
mock_legacy: None,
source_type: str,
pool: RepositoryPool,
filename: str | None,
config: Config | None = None,
package_name: str = "pytest",
package_version: str = "3.5.0",
) -> None:
chooser = Chooser(pool, env)
chooser = Chooser(pool, env, config)
package = Package(package_name, package_version)

package = Package("pytest", "3.5.0")
if source_type == "legacy":
package = Package(
package.name,
Expand All @@ -145,9 +146,31 @@ def test_chooser_chooses_universal_wheel_link_if_available(
source_url="https://foo.bar/simple/",
)

link = chooser.choose_for(package)
try:
link = chooser.choose_for(package)
except RuntimeError as e:
if filename is None:
assert (
str(e)
== f"Unable to find installation candidates for {package.name} ({package.version})"
)
else:
pytest.fail("Package was not found")
else:
assert link.filename == filename

assert link.filename == "pytest-3.5.0-py2.py3-none-any.whl"

@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_chooses_universal_wheel_link_if_available(
env: MockEnv,
mock_pypi: None,
mock_legacy: None,
source_type: str,
pool: RepositoryPool,
) -> None:
check_chosen_link_filename(
env, source_type, pool, "pytest-3.5.0-py2.py3-none-any.whl"
)


@pytest.mark.parametrize(
Expand All @@ -172,22 +195,69 @@ def test_chooser_no_binary_policy(
config: Config,
) -> None:
config.merge({"installer": {"no-binary": policy.split(",")}})
check_chosen_link_filename(env, source_type, pool, filename, config)

chooser = Chooser(pool, env, config)

package = Package("pytest", "3.5.0")
if source_type == "legacy":
package = Package(
package.name,
package.version.text,
source_type="legacy",
source_reference="foo",
source_url="https://foo.bar/simple/",
)
@pytest.mark.parametrize(
("policy", "filename"),
[
(":all:", "pytest-3.5.0-py2.py3-none-any.whl"),
(":none:", "pytest-3.5.0-py2.py3-none-any.whl"),
("black", "pytest-3.5.0-py2.py3-none-any.whl"),
("pytest", "pytest-3.5.0-py2.py3-none-any.whl"),
("pytest,black", "pytest-3.5.0-py2.py3-none-any.whl"),
],
)
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_only_binary_policy(
env: MockEnv,
mock_pypi: None,
mock_legacy: None,
source_type: str,
pool: RepositoryPool,
policy: str,
filename: str,
config: Config,
) -> None:
config.merge({"installer": {"only-binary": policy.split(",")}})
check_chosen_link_filename(env, source_type, pool, filename, config)

link = chooser.choose_for(package)

assert link.filename == filename
@pytest.mark.parametrize(
("no_binary", "only_binary", "filename"),
[
(":all:", ":all:", None),
(":none:", ":none:", "pytest-3.5.0-py2.py3-none-any.whl"),
(":none:", ":all:", "pytest-3.5.0-py2.py3-none-any.whl"),
(":all:", ":none:", "pytest-3.5.0.tar.gz"),
("black", "black", "pytest-3.5.0-py2.py3-none-any.whl"),
("black", "pytest", "pytest-3.5.0-py2.py3-none-any.whl"),
("pytest", "black", "pytest-3.5.0.tar.gz"),
("pytest", "pytest", None),
("pytest,black", "pytest,black", None),
],
)
@pytest.mark.parametrize("source_type", ["", "legacy"])
def test_chooser_multiple_binary_policy(
env: MockEnv,
mock_pypi: None,
mock_legacy: None,
source_type: str,
pool: RepositoryPool,
no_binary: str,
only_binary: str,
filename: str | None,
config: Config,
) -> None:
config.merge(
{
"installer": {
"no-binary": no_binary.split(","),
"only-binary": only_binary.split(","),
}
}
)
check_chosen_link_filename(env, source_type, pool, filename, config)


@pytest.mark.parametrize("source_type", ["", "legacy"])
Expand All @@ -198,21 +268,9 @@ def test_chooser_chooses_specific_python_universal_wheel_link_if_available(
source_type: str,
pool: RepositoryPool,
) -> None:
chooser = Chooser(pool, env)

package = Package("isort", "4.3.4")
if source_type == "legacy":
package = Package(
package.name,
package.version.text,
source_type="legacy",
source_reference="foo",
source_url="https://foo.bar/simple/",
)

link = chooser.choose_for(package)

assert link.filename == "isort-4.3.4-py3-none-any.whl"
check_chosen_link_filename(
env, source_type, pool, "isort-4.3.4-py3-none-any.whl", None, "isort", "4.3.4"
)


@pytest.mark.parametrize("source_type", ["", "legacy"])
Expand All @@ -222,21 +280,15 @@ def test_chooser_chooses_system_specific_wheel_link_if_available(
env = MockEnv(
supported_tags=[Tag("cp37", "cp37m", "win32"), Tag("py3", "none", "any")]
)
chooser = Chooser(pool, env)

package = Package("pyyaml", "3.13.0")
if source_type == "legacy":
package = Package(
package.name,
package.version.text,
source_type="legacy",
source_reference="foo",
source_url="https://foo.bar/simple/",
)

link = chooser.choose_for(package)

assert link.filename == "PyYAML-3.13-cp37-cp37m-win32.whl"
check_chosen_link_filename(
env,
source_type,
pool,
"PyYAML-3.13-cp37-cp37m-win32.whl",
None,
"pyyaml",
"3.13.0",
)


@pytest.mark.parametrize("source_type", ["", "legacy"])
Expand All @@ -247,21 +299,9 @@ def test_chooser_chooses_sdist_if_no_compatible_wheel_link_is_available(
source_type: str,
pool: RepositoryPool,
) -> None:
chooser = Chooser(pool, env)

package = Package("pyyaml", "3.13.0")
if source_type == "legacy":
package = Package(
package.name,
package.version.text,
source_type="legacy",
source_reference="foo",
source_url="https://foo.bar/simple/",
)

link = chooser.choose_for(package)

assert link.filename == "PyYAML-3.13.tar.gz"
check_chosen_link_filename(
env, source_type, pool, "PyYAML-3.13.tar.gz", None, "pyyaml", "3.13.0"
)


@pytest.mark.parametrize("source_type", ["", "legacy"])
Expand Down
Loading