Skip to content

feat: add exclude-newer to filter packages by publish date#10813

Open
satyamsoni2211 wants to merge 7 commits intopython-poetry:mainfrom
satyamsoni2211:feature/exclude_newer_flag
Open

feat: add exclude-newer to filter packages by publish date#10813
satyamsoni2211 wants to merge 7 commits intopython-poetry:mainfrom
satyamsoni2211:feature/exclude_newer_flag

Conversation

@satyamsoni2211
Copy link
Copy Markdown

@satyamsoni2211 satyamsoni2211 commented Mar 31, 2026

PR Summary: Add exclude-newer Package Filter Feature

Branch: feature/exclude_newer_flag
Author: Satyam Soni


Summary

This PR introduces the exclude-newer configuration option to Poetry, allowing users to filter out package versions published within a specified duration. This is particularly useful for avoiding recently published packages with unknown vulnerabilities or for maintaining reproducible builds.


Feature: exclude-newer Package Filter

Overview

Add exclude-newer to [tool.poetry] in pyproject.toml to prevent Poetry from selecting package versions published within a specified time window.

Usage

[tool.poetry]
exclude-newer = "1 week"

Supported duration formats: seconds, minutes, hours, days, weeks, months, years (e.g., "3 days", "2 weeks", "1 month")

Behavior

  • Poetry will not consider any package version where any file was uploaded within the specified duration
  • If a package version has multiple files (e.g., wheel and source tarball), it will be excluded if any file was uploaded within the duration
  • Packages without upload time information are included by default (assumed to be older/safe)

Files Changed

File Change
src/poetry/utils/duration.py Duration parsing utility using pytimeparse
src/poetry/poetry.py Add exclude_newer property
src/poetry/factory.py Parse exclude-newer config
src/poetry/puzzle/provider.py Filter packages by upload_time
src/poetry/puzzle/solver.py Pass exclude_newer to Provider
src/poetry/installation/installer.py Pass exclude_newer to Solver
src/poetry/console/application.py Pass exclude_newer to Installer
src/poetry/console/commands/show.py Pass exclude_newer to Provider
src/poetry/console/commands/installer_command.py Reset exclude_newer
src/poetry/json/schemas/poetry.json JSON schema validation
docs/pyproject.md Documentation

Tests Added

  • tests/test_utils/test_duration.py - Unit tests for duration parsing
  • tests/puzzle/test_provider.py - Integration tests for filtering
  • tests/pyproject/test_pyproject_toml.py - Config parsing tests
  • tests/pyproject/conftest.py - Test fixtures

Add exclude-newer configuration option to [tool.poetry] that prevents
Poetry from selecting package versions published within a specified
duration. This helps avoid recently published packages with unknown
vulnerabilities.

Features:
- Add exclude-newer = "1 week" (or 3 days, 2 months, etc.) to pyproject.toml
- Parses human-readable duration strings using pytimeparse library
- Filters packages in Provider.search_for() based on upload_time
- Propagates exclude_newer through Solver and Installer layers
- Excludes versions where ANY file was uploaded within the duration
- Packages without upload_time are included (assumed old/safe)

Files:
- src/poetry/utils/duration.py: Duration parsing utility
- src/poetry/poetry.py: Add exclude_newer property
- src/poetry/factory.py: Parse exclude-newer config
- src/poetry/puzzle/provider.py: Filter packages by upload_time
- src/poetry/puzzle/solver.py: Pass exclude_newer to Provider
- src/poetry/installation/installer.py: Pass exclude_newer to Solver
- src/poetry/console/application.py: Pass exclude_newer to Installer
- src/poetry/console/commands/show.py: Pass exclude_newer to Provider
- src/poetry/console/commands/installer_command.py: Reset exclude_newer
- tests/test_utils/test_duration.py: Unit tests for duration parsing
- tests/puzzle/test_provider.py: Integration test for filtering
- docs/pyproject.md: Documentation
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 31, 2026

Reviewer's Guide

Implements an exclude-newer configuration that parses human-readable durations into a cutoff datetime and threads it through Poetry’s core objects so the dependency provider can filter out package versions whose files were uploaded after the cutoff, with accompanying tests, dependency additions, and documentation updates.

Sequence diagram for dependency resolution with exclude_newer filtering

sequenceDiagram
    actor User
    participant Pyproject as PyprojectTOML
    participant Factory
    participant Poetry
    participant Application
    participant InstallerCommand as Command
    participant Installer
    participant Solver
    participant Provider
    participant Pool
    participant DurationUtil

    User->>Pyproject: Set exclude-newer = "1 week"
    User->>Command: poetry install

    Command->>Factory: create_poetry(cwd, with_groups)
    Factory->>Pyproject: read [tool.poetry].exclude-newer
    Pyproject-->>Factory: "1 week"
    Factory->>DurationUtil: parse_duration("1 week")
    DurationUtil-->>Factory: cutoff_datetime
    Factory->>Poetry: set_exclude_newer(cutoff_datetime)
    Factory-->>Command: Poetry instance

    Command->>Application: configure_installer_for_command(Command, io)
    Application->>Installer: __init__(..., exclude_newer=Poetry.exclude_newer)
    Application-->>Command: Installer instance

    Command->>Installer: execute
    Installer->>Solver: __init__(..., exclude_newer=_exclude_newer)
    Solver->>Provider: __init__(..., exclude_newer=exclude_newer)

    loop For each dependency
        Solver->>Provider: search_for(dependency)
        Provider->>Pool: find_packages(dependency)
        Pool-->>Provider: list packages
        alt exclude_newer is set
            Provider->>Provider: _is_package_excluded_by_newer(package)
            Note over Provider: Exclude versions where any file<br/>upload_time > cutoff_datetime
        end
        Provider-->>Solver: filtered packages
    end

    Solver-->>Installer: resolution result
    Installer-->>User: Packages installed without recent versions
Loading

Class diagram for exclude_newer configuration propagation

classDiagram
    class Poetry {
        - datetime _exclude_newer
        + datetime get_exclude_newer()
        + Poetry set_exclude_newer(datetime exclude_newer)
    }

    class Factory {
        + Poetry create_poetry(str cwd, bool with_groups)
        - datetime parse_exclude_newer(str exclude_newer_str)
    }

    class Installer {
        - datetime _exclude_newer
        + Installer(__IO io, __Env env, Poetry poetry, __Pool pool, __Locker locker, __Config config, bool disable_cache, Mapping build_constraints, datetime exclude_newer)
        + void set_exclude_newer(datetime exclude_newer)
    }

    class Solver {
        + Solver(Package package, __Pool pool, __Repository installed, list locked, __IO io, Collection active_root_extras, datetime exclude_newer)
    }

    class Provider {
        - datetime _exclude_newer
        + Provider(Package package, __Pool pool, __IO io, list locked, Collection active_root_extras, datetime exclude_newer)
        + list search_for(Dependency dependency)
        - bool _is_package_excluded_by_newer(Package package)
    }

    class DurationUtil {
        + datetime parse_duration(str duration_str)
    }

    class Application {
        + void configure_installer_for_command(InstallerCommand command, __IO io)
    }

    class InstallerCommand {
        + void reset_poetry()
        + Installer get_installer()
    }

    Factory --> Poetry : create_poetry
    Factory --> DurationUtil : uses
    Poetry <.. Application : provides exclude_newer
    Application --> Installer : passes exclude_newer
    Installer --> Solver : passes exclude_newer
    Solver --> Provider : passes exclude_newer
    Provider --> DurationUtil : uses datetime timezone
Loading

File-Level Changes

Change Details Files
Introduce a duration parsing utility used to convert human-readable configuration values into a UTC cutoff datetime.
  • Add parse_duration function that converts strings like "1 week" and "2 months" into datetime.now(timezone.utc) minus the duration
  • Special-case month-based durations using dateutil.relativedelta because pytimeparse does not support months
  • Validate input string and raise ValueError on empty or unparseable durations
  • Add unit tests verifying several valid formats and that invalid strings raise ValueError
src/poetry/utils/duration.py
tests/test_utils/test_duration.py
pyproject.toml
poetry.lock
Add an exclude-newer setting to Poetry’s configuration model and propagate it through Factory, Poetry, Installer, Solver, and Provider.
  • Extend Factory.create_poetry to read [tool.poetry].exclude-newer, parse it via parse_duration, and store the resulting datetime on the Poetry object
  • Add _exclude_newer field, property, and setter to Poetry for holding the cutoff datetime
  • Update Installer to accept/store an exclude_newer datetime, expose a setter, and pass it into Solver construction in refresh and install paths
  • Update Solver to accept an exclude_newer datetime and pass it through to the Provider it constructs
  • Wire exclude_newer from Poetry into Installer in the console application configuration, and ensure InstallerCommand.reset_poetry re-syncs the installer’s exclude_newer from the current Poetry instance
src/poetry/factory.py
src/poetry/poetry.py
src/poetry/installation/installer.py
src/poetry/puzzle/solver.py
src/poetry/console/application.py
src/poetry/console/commands/installer_command.py
Filter provider search results to exclude package versions whose files were uploaded after the configured cutoff datetime.
  • Extend Provider’s constructor to accept an optional exclude_newer datetime and store it on the instance
  • In search_for, when exclude_newer is set, pre-filter packages returned from the pool by calling a new helper
  • Implement _is_package_excluded_by_newer to iterate over package.files, parse each upload_time as ISO8601 with dateutil.parser.isoparse, normalize to UTC, and exclude packages if any file upload time is later than the cutoff
  • Ensure packages without upload_time info, or without files, are retained by default
  • Add an integration test that configures a Provider with an exclude_newer cutoff and verifies that only packages with sufficiently old upload times are returned
src/poetry/puzzle/provider.py
tests/puzzle/test_provider.py
Ensure user-facing commands respect the exclude-newer setting and document the new behavior.
  • In the show command’s find_latest_package, construct the Provider with exclude_newer=self.poetry.exclude_newer so latest-version resolution honors the cutoff
  • Document [tool.poetry].exclude-newer in docs/pyproject.md, including usage example, supported duration words, semantics around multiple files per version, and behavior when upload time is missing
src/poetry/console/commands/show.py
docs/pyproject.md

Possibly linked issues

  • #: PR implements the requested defer-updates feature via exclude-newer, letting users skip too-new package versions.
  • #Support min release age configuration: PR’s exclude-newer option implements the requested min-release-age behavior by filtering out too-new package versions.
  • #unknown: PR’s exclude-newer duration directly implements the issue’s deferred updates by filtering recent packages via upload_time.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • In Provider._is_package_excluded_by_newer, timezone is used but never imported in that module, so this will raise a NameError; add from datetime import timezone at the module level or import it in the helper.
  • The test_search_for_exclude_newer test hardcodes upload timestamps in 2026 while computing exclude_newer from datetime.now(), which makes the assertion time-dependent and potentially brittle; consider deriving upload times relative to now or freezing time in the test.
  • In parse_duration, and in _is_package_excluded_by_newer, dateutil imports are done inside functions; moving these to the module level would reduce repeated imports and make dependencies clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `Provider._is_package_excluded_by_newer`, `timezone` is used but never imported in that module, so this will raise a `NameError`; add `from datetime import timezone` at the module level or import it in the helper.
- The `test_search_for_exclude_newer` test hardcodes upload timestamps in 2026 while computing `exclude_newer` from `datetime.now()`, which makes the assertion time-dependent and potentially brittle; consider deriving upload times relative to `now` or freezing time in the test.
- In `parse_duration`, and in `_is_package_excluded_by_newer`, `dateutil` imports are done inside functions; moving these to the module level would reduce repeated imports and make dependencies clearer.

## Individual Comments

### Comment 1
<location path="tests/puzzle/test_provider.py" line_range="1074-1083" />
<code_context>
+def test_search_for_exclude_newer(
</code_context>
<issue_to_address>
**issue (testing):** Make the test time-independent by not hard-coding future upload_time values

This test will eventually become flaky because `exclude_newer` is computed from `datetime.now()`, while `upload_time` is hard-coded to fixed calendar dates. Once the real date passes those timestamps, both packages may be older than the cutoff and the assertion will fail. Instead, derive the `upload_time` strings relative to `exclude_newer` (e.g., `exclude_newer + timedelta(hours=1)` for the newer package and `exclude_newer - timedelta(days=10)` for the older one) so the test stays valid over time.
</issue_to_address>

### Comment 2
<location path="tests/puzzle/test_provider.py" line_range="1087-1096" />
<code_context>
+    provider = Provider(provider._package, provider._pool, NullIO(), exclude_newer=exclude_newer)
+
+    # Add packages with different upload times
+    recent_package = Package("foo", "1.0")
+    recent_package.files = [
+        {"file": "foo-1.0-py3-none-any.whl", "hash": "sha256:abc",
+         "upload_time": "2026-03-30T12:00:00Z"}  # recent
+    ]
+
+    old_package = Package("foo", "2.0")
+    old_package.files = [
+        {"file": "foo-2.0-py3-none-any.whl", "hash": "sha256:def",
+         "upload_time": "2026-01-01T12:00:00Z"}  # old
+    ]
+
+    repository.add_package(recent_package)
+    repository.add_package(old_package)
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for files without upload_time and mixed old/new files to fully exercise the filtering logic

These tests miss several key behaviors of `_is_package_excluded_by_newer`:

1) Packages whose files all lack `upload_time` should still be included. Please add a case where a package only has files without `upload_time` to ensure `search_for()` doesn’t exclude them.
2) Packages with any file newer than the cutoff should be excluded, even if other files are older. Add a test with one old and one recent `upload_time` in the same package to assert this.
3) Add a boundary test where `upload_time` equals the cutoff to document the intended `>` vs `>=` behavior.

These will ensure the filtering logic is fully exercised and aligned with the rules described in the PR.

Suggested implementation:

```python
def test_search_for_exclude_newer(
    provider: Provider,
    repository: Repository,
    dep: Dependency,
    repo_package: Package,
) -> None:
    """Test that packages published within exclude_newer are filtered."""
    from datetime import datetime, timezone
    from dateutil.relativedelta import relativedelta

    # Set exclude_newer to 3 days ago
    exclude_newer = datetime.now(timezone.utc) - relativedelta(days=3)
    provider = Provider(
        provider._package,
        provider._pool,
        NullIO(),
        exclude_newer=exclude_newer,
    )

    # Baseline: the existing repo_package should still be returned.
    results = provider.search_for(dep)
    assert repo_package in results

    # 1) Packages whose files all lack `upload_time` should still be included.
    no_upload_time_pkg = Package("foo-no-upload-time", "1.0")
    no_upload_time_pkg.files = [
        {
            "file": "foo_no_upload_time-1.0-py3-none-any.whl",
            "hash": "sha256:no-upload",
        }
    ]
    repository.add_package(no_upload_time_pkg)

    results = provider.search_for(dep)
    # Packages without upload_time must not be excluded by the cutoff.
    assert no_upload_time_pkg in results

    # 2) Packages with any file newer than the cutoff should be excluded,
    #    even if other files are older.
    mixed_pkg = Package("foo-mixed", "1.0")
    old_upload_time = (exclude_newer - relativedelta(days=10)).isoformat()
    recent_upload_time = (exclude_newer + relativedelta(days=1)).isoformat()
    mixed_pkg.files = [
        {
            "file": "foo_mixed-1.0-old-py3-none-any.whl",
            "hash": "sha256:old",
            "upload_time": old_upload_time,
        },
        {
            "file": "foo_mixed-1.0-new-py3-none-any.whl",
            "hash": "sha256:new",
            "upload_time": recent_upload_time,
        },
    ]
    repository.add_package(mixed_pkg)

    results = provider.search_for(dep)
    # Any file newer than the cutoff excludes the whole package.
    assert mixed_pkg not in results

    # 3) Boundary test where `upload_time` equals the cutoff; this documents
    #    the intended `>` vs `>=` behavior in `_is_package_excluded_by_newer`.
    boundary_pkg = Package("foo-boundary", "1.0")
    boundary_pkg.files = [
        {
            "file": "foo_boundary-1.0-py3-none-any.whl",
            "hash": "sha256:boundary",
            "upload_time": exclude_newer.isoformat(),
        }
    ]
    repository.add_package(boundary_pkg)

    results = provider.search_for(dep)
    # If `_is_package_excluded_by_newer` uses a strict `>` comparison, the
    # boundary package should be included; if it uses `>=`, this assertion
    # should be updated to `assert boundary_pkg not in results`.
    assert boundary_pkg in results

```

These edits assume the following are already imported and available in this test module as they are used elsewhere:

- `Dependency`
- `Package`
- `Provider`
- `NullIO`
- Fixtures: `dep`, `repo_package`, `provider`, and `repository`

If any of these fixtures or imports are named differently in your codebase, you will need to:

1. Adjust the test function signature to match your existing fixture names.
2. Ensure `Dependency`, `Package`, and `NullIO` are imported at the top of `tests/puzzle/test_provider.py`.
3. Confirm the intended boundary behavior:
   - If packages with `upload_time == exclude_newer` should be excluded, change the last assertion to `assert boundary_pkg not in results`.
</issue_to_address>

### Comment 3
<location path="docs/pyproject.md" line_range="760" />
<code_context>
+
+Supported duration formats include: `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, `years` (e.g., "3 days", "2 weeks", "1 month").
+
+When set, Poetry will not consider any package version where any file was uploaded within the specified duration. If a package version has multiple files (e.g., wheel and source tarball), it will be excluded if **any** file was uploaded within the duration. Packages without upload time information are included by default (assumed to be older).
+
+
</code_context>
<issue_to_address>
**suggestion (typo):** Clarify the parenthetical phrase to make the sentence grammatically complete.

The parenthetical "(assumed to be older)" reads as a fragment. Consider making it a complete clause, e.g. "…are included by default; they are assumed to be older" or "…, and are assumed to be older."

Suggested implementation:

```
Supported duration formats include: `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, `years` (e.g., "3 days", "2 weeks", "1 month").

When set, Poetry will not consider any package version where any file was uploaded within the specified duration. If a package version has multiple files (e.g., wheel and source tarball), it will be excluded if **any** file was uploaded within the duration. Packages without upload time information are included by default and are assumed to be older.

```

```
Supported duration formats include: `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, `years` (e.g., "3 days", "2 weeks", "1 month").

When set, Poetry will not consider any package version where any file was uploaded within the specified duration. If a package version has multiple files (e.g., wheel and source tarball), it will be excluded if **any** file was uploaded within the duration. Packages without upload time information are included by default and are assumed to be older.

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1074 to +1083
def test_search_for_exclude_newer(
provider: Provider,
repository: Repository,
) -> None:
"""Test that packages published within exclude_newer are filtered."""
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta

# Set exclude_newer to 3 days ago
exclude_newer = datetime.now(timezone.utc) - relativedelta(days=3)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (testing): Make the test time-independent by not hard-coding future upload_time values

This test will eventually become flaky because exclude_newer is computed from datetime.now(), while upload_time is hard-coded to fixed calendar dates. Once the real date passes those timestamps, both packages may be older than the cutoff and the assertion will fail. Instead, derive the upload_time strings relative to exclude_newer (e.g., exclude_newer + timedelta(hours=1) for the newer package and exclude_newer - timedelta(days=10) for the older one) so the test stays valid over time.

Comment on lines +1087 to +1096
recent_package = Package("foo", "1.0")
recent_package.files = [
{"file": "foo-1.0-py3-none-any.whl", "hash": "sha256:abc",
"upload_time": "2026-03-30T12:00:00Z"} # recent
]

old_package = Package("foo", "2.0")
old_package.files = [
{"file": "foo-2.0-py3-none-any.whl", "hash": "sha256:def",
"upload_time": "2026-01-01T12:00:00Z"} # old
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add tests for files without upload_time and mixed old/new files to fully exercise the filtering logic

These tests miss several key behaviors of _is_package_excluded_by_newer:

  1. Packages whose files all lack upload_time should still be included. Please add a case where a package only has files without upload_time to ensure search_for() doesn’t exclude them.
  2. Packages with any file newer than the cutoff should be excluded, even if other files are older. Add a test with one old and one recent upload_time in the same package to assert this.
  3. Add a boundary test where upload_time equals the cutoff to document the intended > vs >= behavior.

These will ensure the filtering logic is fully exercised and aligned with the rules described in the PR.

Suggested implementation:

def test_search_for_exclude_newer(
    provider: Provider,
    repository: Repository,
    dep: Dependency,
    repo_package: Package,
) -> None:
    """Test that packages published within exclude_newer are filtered."""
    from datetime import datetime, timezone
    from dateutil.relativedelta import relativedelta

    # Set exclude_newer to 3 days ago
    exclude_newer = datetime.now(timezone.utc) - relativedelta(days=3)
    provider = Provider(
        provider._package,
        provider._pool,
        NullIO(),
        exclude_newer=exclude_newer,
    )

    # Baseline: the existing repo_package should still be returned.
    results = provider.search_for(dep)
    assert repo_package in results

    # 1) Packages whose files all lack `upload_time` should still be included.
    no_upload_time_pkg = Package("foo-no-upload-time", "1.0")
    no_upload_time_pkg.files = [
        {
            "file": "foo_no_upload_time-1.0-py3-none-any.whl",
            "hash": "sha256:no-upload",
        }
    ]
    repository.add_package(no_upload_time_pkg)

    results = provider.search_for(dep)
    # Packages without upload_time must not be excluded by the cutoff.
    assert no_upload_time_pkg in results

    # 2) Packages with any file newer than the cutoff should be excluded,
    #    even if other files are older.
    mixed_pkg = Package("foo-mixed", "1.0")
    old_upload_time = (exclude_newer - relativedelta(days=10)).isoformat()
    recent_upload_time = (exclude_newer + relativedelta(days=1)).isoformat()
    mixed_pkg.files = [
        {
            "file": "foo_mixed-1.0-old-py3-none-any.whl",
            "hash": "sha256:old",
            "upload_time": old_upload_time,
        },
        {
            "file": "foo_mixed-1.0-new-py3-none-any.whl",
            "hash": "sha256:new",
            "upload_time": recent_upload_time,
        },
    ]
    repository.add_package(mixed_pkg)

    results = provider.search_for(dep)
    # Any file newer than the cutoff excludes the whole package.
    assert mixed_pkg not in results

    # 3) Boundary test where `upload_time` equals the cutoff; this documents
    #    the intended `>` vs `>=` behavior in `_is_package_excluded_by_newer`.
    boundary_pkg = Package("foo-boundary", "1.0")
    boundary_pkg.files = [
        {
            "file": "foo_boundary-1.0-py3-none-any.whl",
            "hash": "sha256:boundary",
            "upload_time": exclude_newer.isoformat(),
        }
    ]
    repository.add_package(boundary_pkg)

    results = provider.search_for(dep)
    # If `_is_package_excluded_by_newer` uses a strict `>` comparison, the
    # boundary package should be included; if it uses `>=`, this assertion
    # should be updated to `assert boundary_pkg not in results`.
    assert boundary_pkg in results

These edits assume the following are already imported and available in this test module as they are used elsewhere:

  • Dependency
  • Package
  • Provider
  • NullIO
  • Fixtures: dep, repo_package, provider, and repository

If any of these fixtures or imports are named differently in your codebase, you will need to:

  1. Adjust the test function signature to match your existing fixture names.
  2. Ensure Dependency, Package, and NullIO are imported at the top of tests/puzzle/test_provider.py.
  3. Confirm the intended boundary behavior:
    • If packages with upload_time == exclude_newer should be excluded, change the last assertion to assert boundary_pkg not in results.


Supported duration formats include: `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, `years` (e.g., "3 days", "2 weeks", "1 month").

When set, Poetry will not consider any package version where any file was uploaded within the specified duration. If a package version has multiple files (e.g., wheel and source tarball), it will be excluded if **any** file was uploaded within the duration. Packages without upload time information are included by default (assumed to be older).
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (typo): Clarify the parenthetical phrase to make the sentence grammatically complete.

The parenthetical "(assumed to be older)" reads as a fragment. Consider making it a complete clause, e.g. "…are included by default; they are assumed to be older" or "…, and are assumed to be older."

Suggested implementation:

Supported duration formats include: `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, `years` (e.g., "3 days", "2 weeks", "1 month").

When set, Poetry will not consider any package version where any file was uploaded within the specified duration. If a package version has multiple files (e.g., wheel and source tarball), it will be excluded if **any** file was uploaded within the duration. Packages without upload time information are included by default and are assumed to be older.

Supported duration formats include: `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, `years` (e.g., "3 days", "2 weeks", "1 month").

When set, Poetry will not consider any package version where any file was uploaded within the specified duration. If a package version has multiple files (e.g., wheel and source tarball), it will be excluded if **any** file was uploaded within the duration. Packages without upload time information are included by default and are assumed to be older.

@dosubot
Copy link
Copy Markdown

dosubot bot commented Mar 31, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

Python Poetry

CHANGELOG /poetry/blob/main/CHANGELOG.md — ⏳ Awaiting Merge

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

Satyam Soni and others added 6 commits March 31, 2026 18:02
Add exclude-newer configuration option to [tool.poetry] that prevents
Poetry from selecting package versions published within a specified
duration. This helps avoid recently published packages with unknown
vulnerabilities.

Features:
- Add exclude-newer = "1 week" (or 3 days, 2 months, etc.) to pyproject.toml
- Parses human-readable duration strings using pytimeparse library
- Filters packages in Provider.search_for() based on upload_time
- Propagates exclude_newer through Solver and Installer layers
- Excludes versions where ANY file was uploaded within the duration
- Packages without upload_time are included (assumed old/safe)
- Added JSON schema validation for exclude-newer property

Files:
- src/poetry/utils/duration.py: Duration parsing utility
- src/poetry/poetry.py: Add exclude_newer property
- src/poetry/factory.py: Parse exclude-newer config
- src/poetry/puzzle/provider.py: Filter packages by upload_time
- src/poetry/puzzle/solver.py: Pass exclude_newer to Provider
- src/poetry/installation/installer.py: Pass exclude_newer to Solver
- src/poetry/console/application.py: Pass exclude_newer to Installer
- src/poetry/console/commands/show.py: Pass exclude_newer to Provider
- src/poetry/console/commands/installer_command.py: Reset exclude_newer
- src/poetry/json/schemas/poetry.json: Schema validation for exclude-newer
- tests/test_utils/test_duration.py: Unit tests for duration parsing
- tests/puzzle/test_provider.py: Integration test for filtering
- tests/pyproject/test_pyproject_toml.py: Test for exclude-newer config
- tests/pyproject/conftest.py: Test fixtures for exclude-newer
- tests/fixtures/complete.toml: Updated fixture with exclude-newer
- docs/pyproject.md: Documentation
- Add type ignores for untyped imports (pytimeparse,
dateutil.relativedelta,
    dateutil.parser) where library stubs are not installed
  - Add no-any-return ignores for relativedelta operations where mypy
cannot
    infer the return type
  - Add assertion to handle None return from get_embed_wheel
  - Add return type annotations to test functions missing them
  - Add index type ignore for nested dict access in conftest
@satyamsoni2211 satyamsoni2211 force-pushed the feature/exclude_newer_flag branch from e253e9a to 56c0cc2 Compare April 1, 2026 06:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant