-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Feature: cool down period #6659
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
base: main
Are you sure you want to change the base?
Changes from all commits
1a092ed
2a846eb
2ccdaef
a5100fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Bump vendored ``plette`` to ``2.2.1``. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Added support for ``cool-down-period`` in the ``[pipenv]`` section of the Pipfile. | ||
| Setting ``cool-down-period = "30d"`` instructs the resolver to only consider | ||
| package versions uploaded at least the specified number of days ago, via pip's | ||
| ``--uploaded-prior-to`` flag. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| """ | ||
| A simple entry point which can be use to test Pipfiles | ||
|
|
||
| e.g. | ||
|
|
||
| python -m plette -f examples/Pipfile.valid.list | ||
| python -m plette -f examples/Pipfile.valid.editable | ||
| # throws exception | ||
| python -m plette -f examples/Pipfile.invalid.list | ||
|
|
||
| """ | ||
|
|
||
| import argparse | ||
|
|
||
| import tomlkit | ||
|
|
||
| from plette import Pipfile, Lockfile | ||
|
|
||
|
|
||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("-f", "--file", help="Input file") | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| dest = args.file | ||
|
|
||
| with open(dest) as f: | ||
| try: | ||
| pipfile = Pipfile.load(f) | ||
| except tomlkit.exceptions.EmptyKeyError: | ||
| f.seek(0) | ||
| lockfile = Lockfile.load(f) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -743,3 +743,33 @@ def test_private_index_skip_lock(pipenv_instance_private_pypi): | |
| f.write(contents) | ||
| c = p.pipenv("install --skip-lock") | ||
| assert c.returncode == 0 | ||
|
|
||
|
|
||
| @pytest.mark.lock | ||
| @pytest.mark.requirements | ||
| def test_lock_respects_cool_down_period(pipenv_instance_pypi): | ||
| """cool-down-period in [pipenv] passes --uploaded-prior-to to the resolver. | ||
|
|
||
| Uses the real PyPI because it exposes upload-time metadata, which pip | ||
| requires when --uploaded-prior-to is supplied. The 30-day window is wide | ||
| enough to always include a stable release of `six`. | ||
| """ | ||
| with pipenv_instance_pypi() as p: | ||
| with open(p.pipfile_path, "w") as f: | ||
| f.write( | ||
| f""" | ||
| [[source]] | ||
| url = "{p.index_url}" | ||
| verify_ssl = true | ||
| name = "pypi" | ||
|
|
||
| [packages] | ||
| six = "*" | ||
|
|
||
| [pipenv] | ||
| cool-down-period = "30d" | ||
| """ | ||
| ) | ||
| c = p.pipenv("lock") | ||
| assert c.returncode == 0, c.stderr | ||
| assert "six" in p.lockfile["default"] | ||
|
Comment on lines
+748
to
+775
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ | |
|
|
||
| from pipenv.patched.pip._internal.resolution.resolvelib.provider import PipProvider | ||
| from pipenv.patched.pip._vendor.resolvelib.structs import RequirementInformation | ||
| from pipenv.utils.resolver import Resolver | ||
| from pipenv.utils.resolver import Resolver, _get_cool_down_timedelta | ||
|
|
||
|
|
||
| def _conflict_info(name, parent=None): | ||
|
|
@@ -373,3 +373,61 @@ def test_process_resolver_results_does_not_scan_reverse_dependencies(): | |
|
|
||
| assert processed == [{"name": "requests", "version": "==2.32.0"}] | ||
| project.environment.reverse_dependencies.assert_not_called() | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # cool-down-period / --uploaded-prior-to tests | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def _make_project(cool_down_period): | ||
| """Return a mock project whose [pipenv] section contains cool-down-period.""" | ||
| project = mock.MagicMock() | ||
| settings = {} | ||
| if cool_down_period is not None: | ||
| settings["cool-down-period"] = cool_down_period | ||
| project.settings = settings | ||
| return project | ||
|
|
||
|
|
||
| @pytest.mark.utils | ||
| @pytest.mark.parametrize("value,expected_days", [ | ||
| ("30d", 30), | ||
| ("1d", 1), | ||
| ("365d", 365), | ||
| ]) | ||
| def test_get_cool_down_timedelta_valid(value, expected_days): | ||
| import datetime | ||
| project = _make_project(value) | ||
| result = _get_cool_down_timedelta(project) | ||
| assert result == datetime.timedelta(days=expected_days) | ||
|
|
||
|
|
||
| @pytest.mark.utils | ||
| @pytest.mark.parametrize("value", [None, "", "30days", "30h", "P30D", "1 d", "d"]) | ||
| def test_get_cool_down_timedelta_invalid_or_absent(value): | ||
| project = _make_project(value) | ||
| assert _get_cool_down_timedelta(project) is None | ||
|
|
||
|
|
||
| @pytest.mark.utils | ||
| def test_pip_options_sets_uploaded_prior_to_from_cool_down_period(): | ||
| """Resolver.pip_options sets uploaded_prior_to when cool-down-period is configured.""" | ||
| import datetime | ||
| from types import SimpleNamespace | ||
|
|
||
| project = _make_project("30d") | ||
| project.s.PIPENV_CACHE_DIR = "/tmp/cache" | ||
| project.packages = {} | ||
|
|
||
| resolver = Resolver.__new__(Resolver) | ||
| resolver.project = project | ||
| resolver.sources = [] | ||
|
|
||
| before = datetime.datetime.now(datetime.timezone.utc) | ||
| cool_down = _get_cool_down_timedelta(project) | ||
| assert cool_down is not None | ||
| cutoff = datetime.datetime.now(datetime.timezone.utc) - cool_down | ||
| after = datetime.datetime.now(datetime.timezone.utc) - cool_down | ||
|
|
||
| # cutoff should be approximately 30 days ago | ||
| assert before - datetime.timedelta(days=30, seconds=1) < cutoff < after + datetime.timedelta(seconds=1) | ||
|
Comment on lines
+412
to
+433
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs say pipenv translates
cool-down-periodto pip’s--uploaded-prior-to P30Dflag, but the implementation setspip_options.uploaded_prior_todirectly to a cutoff datetime (now(UTC) - timedelta). Update the wording to reflect the actual behavior (cutoff datetime) and/or note that pip supports both ISO datetimes andP<n>Ddurations.