Skip to content

Commit f8a008f

Browse files
committed
feat: add a solver.min-release-age-exclude-source config option
1 parent e4f70c4 commit f8a008f

File tree

6 files changed

+100
-6
lines changed

6 files changed

+100
-6
lines changed

docs/configuration.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,26 @@ regardless of their upload age.
442442
poetry config solver.min-release-age-exclude "my-package,other-package"
443443
```
444444

445+
### `solver.min-release-age-exclude-source`
446+
447+
**Type**: `string`
448+
449+
**Default**: *not set*
450+
451+
**Environment Variable**: `POETRY_SOLVER_MIN_RELEASE_AGE_EXCLUDE_SOURCE`
452+
453+
*Introduced in 2.4.0*
454+
455+
A comma-separated list of source names or URLs that should be excluded from the
456+
[`solver.min-release-age`](#solvermin-release-age) filter.
457+
All packages from these sources will always be considered by the solver,
458+
regardless of their upload age.
459+
Sources can be referenced by the name defined in `pyproject.toml` or by URL.
460+
461+
```bash
462+
poetry config solver.min-release-age-exclude-source "private-repo,https://example.com/simple/"
463+
```
464+
445465
### `system-git-client`
446466

447467
**Type**: `boolean`

src/poetry/config/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ def int_normalizer(val: str) -> int:
4444
return int(val)
4545

4646

47+
def str_list_normalizer(val: str) -> list[str]:
48+
return [vs for v in val.split(",") if (vs := v.strip())]
49+
50+
4751
def build_config_setting_validator(val: str) -> bool:
4852
try:
4953
value = build_config_setting_normalizer(val)
@@ -176,6 +180,7 @@ class Config:
176180
"lazy-wheel": True,
177181
"min-release-age": 0,
178182
"min-release-age-exclude": None,
183+
"min-release-age-exclude-source": None,
179184
},
180185
"system-git-client": False,
181186
"keyring": {
@@ -411,6 +416,9 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
411416
}:
412417
return PackageFilterPolicy.normalize
413418

419+
if name == "solver.min-release-age-exclude-source":
420+
return str_list_normalizer
421+
414422
if name.startswith("installer.build-config-settings."):
415423
return build_config_setting_normalizer
416424

src/poetry/console/commands/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from poetry.config.config import build_config_setting_normalizer
2020
from poetry.config.config import build_config_setting_validator
2121
from poetry.config.config import int_normalizer
22+
from poetry.config.config import str_list_normalizer
2223
from poetry.config.config_source import UNSET
2324
from poetry.config.config_source import ConfigSourceMigration
2425
from poetry.config.config_source import PropertyNotFoundError
@@ -107,6 +108,10 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any]]:
107108
PackageFilterPolicy.validator,
108109
PackageFilterPolicy.normalize,
109110
),
111+
"solver.min-release-age-exclude-source": (
112+
lambda val: bool(val.strip()),
113+
str_list_normalizer,
114+
),
110115
"keyring.enabled": (boolean_validator, boolean_normalizer),
111116
"python.installation-dir": (str, lambda val: str(Path(val))),
112117
}

src/poetry/repositories/http_repository.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,19 @@ def __init__(
8484
self._min_release_age_cutoff: datetime | None = None
8585
self._min_release_age_exclude: set[NormalizedName] = set()
8686
if self._min_release_age:
87-
self._min_release_age_cutoff = datetime.now(tz=timezone.utc) - timedelta(
88-
days=self._min_release_age
87+
exclude_sources: set[str] = set(
88+
config.get("solver.min-release-age-exclude-source") or []
8989
)
90-
self._min_release_age_exclude = {
91-
canonicalize_name(n)
92-
for n in (config.get("solver.min-release-age-exclude") or [])
93-
}
90+
if name in exclude_sources or url in exclude_sources:
91+
self._min_release_age = 0
92+
else:
93+
self._min_release_age_cutoff = datetime.now(
94+
tz=timezone.utc
95+
) - timedelta(days=self._min_release_age)
96+
self._min_release_age_exclude = {
97+
canonicalize_name(n)
98+
for n in (config.get("solver.min-release-age-exclude") or [])
99+
}
94100
self._age_filtered_versions: dict[NormalizedName, set[Version]] = {}
95101
# We are tracking if a domain supports range requests or not to avoid
96102
# unnecessary requests.

tests/console/commands/test_config.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def test_list_displays_default_value_if_not_set(
7777
solver.lazy-wheel = true
7878
solver.min-release-age = 0
7979
solver.min-release-age-exclude = null
80+
solver.min-release-age-exclude-source = null
8081
system-git-client = false
8182
virtualenvs.create = true
8283
virtualenvs.in-project = null
@@ -114,6 +115,7 @@ def test_list_displays_set_get_setting(
114115
solver.lazy-wheel = true
115116
solver.min-release-age = 0
116117
solver.min-release-age-exclude = null
118+
solver.min-release-age-exclude-source = null
117119
system-git-client = false
118120
virtualenvs.create = false
119121
virtualenvs.in-project = null
@@ -172,6 +174,7 @@ def test_unset_setting(
172174
solver.lazy-wheel = true
173175
solver.min-release-age = 0
174176
solver.min-release-age-exclude = null
177+
solver.min-release-age-exclude-source = null
175178
system-git-client = false
176179
virtualenvs.create = true
177180
virtualenvs.in-project = null
@@ -208,6 +211,7 @@ def test_unset_repo_setting(
208211
solver.lazy-wheel = true
209212
solver.min-release-age = 0
210213
solver.min-release-age-exclude = null
214+
solver.min-release-age-exclude-source = null
211215
system-git-client = false
212216
virtualenvs.create = true
213217
virtualenvs.in-project = null
@@ -345,6 +349,7 @@ def test_list_displays_set_get_local_setting(
345349
solver.lazy-wheel = true
346350
solver.min-release-age = 0
347351
solver.min-release-age-exclude = null
352+
solver.min-release-age-exclude-source = null
348353
system-git-client = false
349354
virtualenvs.create = false
350355
virtualenvs.in-project = null
@@ -391,6 +396,7 @@ def test_list_must_not_display_sources_from_pyproject_toml(
391396
solver.lazy-wheel = true
392397
solver.min-release-age = 0
393398
solver.min-release-age-exclude = null
399+
solver.min-release-age-exclude-source = null
394400
system-git-client = false
395401
virtualenvs.create = true
396402
virtualenvs.in-project = null
@@ -651,6 +657,23 @@ def test_config_solver_min_release_age_exclude(
651657
assert repo._min_release_age_exclude == {"my-pkg", "other-pkg"}
652658

653659

660+
def test_config_solver_min_release_age_exclude_source(
661+
tester: CommandTester, command_tester_factory: CommandTesterFactory
662+
) -> None:
663+
tester.execute("--local solver.min-release-age-exclude-source")
664+
assert tester.io.fetch_output().strip() == "null"
665+
666+
tester.io.clear_output()
667+
tester.execute(
668+
"--local solver.min-release-age-exclude-source"
669+
" 'private-repo,https://example.com/simple/'"
670+
)
671+
tester.execute("--local solver.min-release-age-exclude-source")
672+
output = tester.io.fetch_output().strip()
673+
assert "private-repo" in output
674+
assert "https://example.com/simple/" in output
675+
676+
654677
current_config = """\
655678
[experimental]
656679
system-git-client = true

tests/repositories/test_http_repository.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,38 @@ def test_min_release_age_exclude_default(config: Config) -> None:
9696
assert repo._min_release_age_exclude == set()
9797

9898

99+
@pytest.mark.parametrize(
100+
("exclude_sources", "expect_cutoff"),
101+
[
102+
([], True),
103+
(["bar"], True), # name does not match
104+
(["foo"], False), # matches repo name
105+
(["https://foo.com"], False), # matches repo URL
106+
(["other", "https://foo.com"], False), # URL in list
107+
],
108+
)
109+
def test_min_release_age_exclude_source(
110+
config: Config,
111+
exclude_sources: list[str],
112+
expect_cutoff: bool,
113+
) -> None:
114+
config.merge(
115+
{
116+
"solver": {
117+
"min-release-age": 7,
118+
"min-release-age-exclude-source": exclude_sources,
119+
}
120+
}
121+
)
122+
repo = MockRepository(config=config)
123+
if expect_cutoff:
124+
assert repo._min_release_age == 7
125+
assert repo._min_release_age_cutoff is not None
126+
else:
127+
assert repo._min_release_age == 0
128+
assert repo._min_release_age_cutoff is None
129+
130+
99131
@pytest.mark.parametrize(
100132
("links", "expected"),
101133
[

0 commit comments

Comments
 (0)