1414
1515from packaging import specifiers , version
1616
17- CACHED_RELEASE_CYCLE = Path (__file__ ).parent / "cached_release_cycle.json"
17+ HERE = Path (__file__ ).parent
18+ CACHED_RELEASE_CYCLE = HERE / "cached_release_cycle.json"
19+ CACHED_EOL_VERSIONS = HERE / "cached_eol_versions.json"
1820
1921
2022class EOLPythonError (Exception ): ... # noqa: D101
@@ -81,8 +83,15 @@ def from_json(cls, ver: str, metadata: dict[str, t.Any]) -> PythonRelease:
8183 end_of_life = _parse_eol_date (metadata ["end_of_life" ]),
8284 )
8385
86+ def to_json (self ) -> tuple [str , dict [str , str ]]:
87+ """Recreates the original JSON object from the `PythonRelease` instance."""
88+ return str (self .python_ver ), {
89+ "status" : self .status .value ,
90+ "end_of_life" : self .end_of_life .isoformat (),
91+ }
8492
85- def _get_cached_release_cycle (cache_json : Path ) -> list [PythonRelease ]:
93+
94+ def _get_cached_release_cycle (cache_json : Path = CACHED_RELEASE_CYCLE ) -> list [PythonRelease ]:
8695 """
8796 Parse the locally cached Python release cycle into `PythonRelease` instance(s).
8897
@@ -100,7 +109,27 @@ def _get_cached_release_cycle(cache_json: Path) -> list[PythonRelease]:
100109 )
101110
102111
103- def check_python_support (toml_file : Path , cache_json : Path = CACHED_RELEASE_CYCLE ) -> None :
112+ def get_cached_eol_versions () -> abc .Iterator [PythonRelease ]:
113+ """Parse the locally cached EOL Python versions into `PythonRelease` instance(s)."""
114+ with CACHED_EOL_VERSIONS .open ("r" , encoding = "utf-8" ) as f :
115+ contents = json .load (f )
116+
117+ return (PythonRelease .from_json (v , m ) for v , m in contents .items ())
118+
119+
120+ def get_eol_versions (cache_json : Path = CACHED_RELEASE_CYCLE ) -> abc .Iterator [PythonRelease ]:
121+ """Enumerate all EOL Python versions."""
122+ release_cycle = _get_cached_release_cycle (cache_json )
123+ utc_today = dt .datetime .now (dt .timezone .utc ).date ()
124+
125+ for r in release_cycle :
126+ if r .status == ReleasePhase .EOL or r .end_of_life <= utc_today :
127+ yield r
128+
129+
130+ def check_python_support (
131+ toml_file : Path , * , cached : bool = False , cache_json : Path = CACHED_RELEASE_CYCLE
132+ ) -> None :
104133 """
105134 Check the input TOML's `requires-python` for overlap with EOL Python version(s).
106135
@@ -115,19 +144,12 @@ def check_python_support(toml_file: Path, cache_json: Path = CACHED_RELEASE_CYCL
115144 raise RequiresPythonNotFoundError
116145
117146 package_spec = specifiers .SpecifierSet (requires_python )
118- release_cycle = _get_cached_release_cycle (cache_json )
119- utc_today = dt .datetime .now (dt .timezone .utc ).date ()
120-
121- eol_supported = []
122- for r in release_cycle :
123- if r .python_ver in package_spec :
124- if r .status == ReleasePhase .EOL :
125- eol_supported .append (r )
126- continue
127147
128- if r .end_of_life <= utc_today :
129- eol_supported .append (r )
130- continue
148+ if cached :
149+ eol_versions = get_cached_eol_versions ()
150+ else :
151+ eol_versions = get_eol_versions (cache_json )
152+ eol_supported = [version for version in eol_versions if version .python_ver in package_spec ]
131153
132154 if eol_supported :
133155 eol_supported .sort (key = attrgetter ("python_ver" )) # Sort ascending for error msg generation
@@ -138,12 +160,13 @@ def check_python_support(toml_file: Path, cache_json: Path = CACHED_RELEASE_CYCL
138160def main (argv : abc .Sequence [str ] | None = None ) -> int : # noqa: D103
139161 parser = argparse .ArgumentParser ()
140162 parser .add_argument ("filenames" , nargs = "*" , type = Path )
163+ parser .add_argument ("--cached" , action = "set_true" )
141164 args = parser .parse_args (argv )
142165
143166 ec = 0
144167 for file in args .filenames :
145168 try :
146- check_python_support (file )
169+ check_python_support (file , cached = args . cached )
147170 except EOLPythonError as e :
148171 print (f"{ file } : { e } " )
149172 ec = 1
0 commit comments