11"""Routines related to PyPI, indexes"""
22
3- from __future__ import annotations
4-
53import enum
64import functools
75import itertools
@@ -111,6 +109,7 @@ class LinkType(enum.Enum):
111109 format_invalid = enum .auto ()
112110 platform_mismatch = enum .auto ()
113111 requires_python_mismatch = enum .auto ()
112+ upload_too_late = enum .auto ()
114113
115114
116115class LinkEvaluator :
@@ -131,7 +130,8 @@ def __init__(
131130 formats : frozenset [str ],
132131 target_python : TargetPython ,
133132 allow_yanked : bool ,
134- ignore_requires_python : bool | None = None ,
133+ ignore_requires_python : Optional [bool ] = None ,
134+ upload_before : Optional [datetime .datetime ] = None ,
135135 ) -> None :
136136 """
137137 :param project_name: The user supplied package name.
@@ -149,6 +149,7 @@ def __init__(
149149 :param ignore_requires_python: Whether to ignore incompatible
150150 PEP 503 "data-requires-python" values in HTML links. Defaults
151151 to False.
152+ :param upload_before: If set, only allow links prior to the given date.
152153 """
153154 if ignore_requires_python is None :
154155 ignore_requires_python = False
@@ -158,6 +159,7 @@ def __init__(
158159 self ._ignore_requires_python = ignore_requires_python
159160 self ._formats = formats
160161 self ._target_python = target_python
162+ self ._upload_before = upload_before
161163
162164 self .project_name = project_name
163165
@@ -176,6 +178,11 @@ def evaluate_link(self, link: Link) -> tuple[LinkType, str]:
176178 reason = link .yanked_reason or "<none given>"
177179 return (LinkType .yanked , f"yanked for reason: { reason } " )
178180
181+ if link .upload_time is not None and self ._upload_before is not None :
182+ if link .upload_time > self ._upload_before :
183+ reason = f"Upload time { link .upload_time } after { self ._upload_before } "
184+ return (LinkType .upload_too_late , reason )
185+
179186 if link .egg_fragment :
180187 egg_info = link .egg_fragment
181188 ext = link .ext
@@ -590,9 +597,10 @@ def __init__(
590597 link_collector : LinkCollector ,
591598 target_python : TargetPython ,
592599 allow_yanked : bool ,
593- format_control : FormatControl | None = None ,
594- candidate_prefs : CandidatePreferences | None = None ,
595- ignore_requires_python : bool | None = None ,
600+ format_control : Optional [FormatControl ] = None ,
601+ candidate_prefs : Optional [CandidatePreferences ] = None ,
602+ ignore_requires_python : Optional [bool ] = None ,
603+ upload_before : Optional [datetime .datetime ] = None ,
596604 ) -> None :
597605 """
598606 This constructor is primarily meant to be used by the create() class
@@ -614,6 +622,7 @@ def __init__(
614622 self ._ignore_requires_python = ignore_requires_python
615623 self ._link_collector = link_collector
616624 self ._target_python = target_python
625+ self ._upload_before = upload_before
617626
618627 self .format_control = format_control
619628
@@ -636,15 +645,17 @@ def create(
636645 cls ,
637646 link_collector : LinkCollector ,
638647 selection_prefs : SelectionPreferences ,
639- target_python : TargetPython | None = None ,
640- ) -> PackageFinder :
648+ target_python : Optional [TargetPython ] = None ,
649+ upload_before : Optional [datetime .datetime ] = None ,
650+ ) -> "PackageFinder" :
641651 """Create a PackageFinder.
642652
643653 :param selection_prefs: The candidate selection preferences, as a
644654 SelectionPreferences object.
645655 :param target_python: The target Python interpreter to use when
646656 checking compatibility. If None (the default), a TargetPython
647657 object will be constructed from the running Python.
658+ :param upload_before: If set, only find links prior to the given date.
648659 """
649660 if target_python is None :
650661 target_python = TargetPython ()
@@ -661,6 +672,7 @@ def create(
661672 allow_yanked = selection_prefs .allow_yanked ,
662673 format_control = selection_prefs .format_control ,
663674 ignore_requires_python = selection_prefs .ignore_requires_python ,
675+ upload_before = upload_before ,
664676 )
665677
666678 @property
@@ -739,6 +751,7 @@ def make_link_evaluator(self, project_name: str) -> LinkEvaluator:
739751 target_python = self ._target_python ,
740752 allow_yanked = self ._allow_yanked ,
741753 ignore_requires_python = self ._ignore_requires_python ,
754+ upload_before = self ._upload_before ,
742755 )
743756
744757 def _sort_links (self , links : Iterable [Link ]) -> list [Link ]:
0 commit comments