@@ -132,9 +132,8 @@ class GHAInput:
132132 python_min : tuple [int , int ]
133133
134134 # OPTIONAL (python)
135- python_max : tuple [int , int ] = dataclasses .field (
136- default_factory = semver_parser_tools .get_latest_py3_release # called only if no val
137- )
135+ python_max : tuple [int , int ] | None = None
136+
138137 # OPTIONAL (packaging)
139138 package_dirs : list [str ] = dataclasses .field (default_factory = list )
140139 exclude_dirs : list [str ] = dataclasses .field (
@@ -149,8 +148,10 @@ class GHAInput:
149148 "examples" ,
150149 ]
151150 )
151+
152152 # OPTIONAL (releases)
153153 pypi_name : str = ""
154+
154155 # OPTIONAL (meta)
155156 keywords : list [str ] = dataclasses .field (default_factory = list )
156157 author : str = ""
@@ -177,28 +178,60 @@ def __post_init__(self) -> None:
177178 f"(current mode: { self .mode } )"
178179 )
179180
180- # validate python min/max
181- for py , attr_name in [
182- (self .python_min , "python_min" ),
183- (self .python_max , "python_max" ),
184- ]:
185- if py [0 ] < 3 :
181+
182+ class PythonVersion :
183+ """A class for handling python version logic."""
184+
185+ def __init__ (
186+ self ,
187+ python_min : tuple [int , int ],
188+ python_max : tuple [int , int ] | None ,
189+ ):
190+ if python_max is None :
191+ python_max = semver_parser_tools .get_latest_py3_release ()
192+ did_autoconfig_python_max = True
193+ else :
194+ did_autoconfig_python_max = False
195+
196+ def _maj_validate (maj : int , attr_name : str ):
197+ if maj < 3 :
186198 raise _log_error_then_get_exception (
187199 f"Python-release automation ('{ attr_name } ') does not work for python <3."
188200 )
189- elif py [ 0 ] >= 4 :
201+ elif maj >= 4 :
190202 raise _log_error_then_get_exception (
191203 f"Python-release automation ('{ attr_name } ') does not work for python 4+."
192204 )
193- pystr = f"{ py [0 ]} .{ py [1 ]} "
194- if semver_parser_tools .is_python_eol (pystr ):
205+
206+ def _eol_check (py : tuple [int , int ], attr_name : str ):
207+ pystr = f"{ py [0 ]} .{ py [1 ]} " # ex: "3.14"
208+ if semver_parser_tools .is_python_eol (pystr ): # -> ValueError if not found
195209 raise _log_error_then_get_exception (
196210 f"Python version ('{ attr_name } ={ pystr } ') is passed its end-of-life date "
197211 f"("
198212 f"{ datetime .date .fromtimestamp (semver_parser_tools .get_python_eol_ts (pystr ))} "
199213 f")."
200214 )
201215
216+ # validate python min
217+ _maj_validate (python_min [0 ], "python_min" )
218+ _eol_check (python_min , "python_min" )
219+ # validate python max
220+ _maj_validate (python_max [0 ], "python_max" )
221+ try :
222+ _eol_check (python_max , "python_max" )
223+ except ValueError :
224+ # backup-plan: the auto python max is too new, so use the prev version
225+ # note -- no loop; if this one backup doesn't work, then err
226+ if did_autoconfig_python_max :
227+ python_max = (python_max [0 ], python_max [1 ] - 1 ) # ex: (3,14) -> (3,13)
228+ _eol_check (python_max , "python_max" )
229+ else :
230+ raise
231+
232+ self .python_min = python_min
233+ self .python_max = python_max
234+
202235 def get_requires_python (self ) -> str :
203236 """Get a `[project]/python_requires` string from `self.python_range`.
204237
@@ -435,6 +468,7 @@ def __init__(
435468 gha_input ,
436469 )
437470 gh_api = GitHubAPI (github_full_repo , oauth_token = token )
471+ py_ver = PythonVersion (gha_input .python_min , gha_input .python_max )
438472 self ._validate_repo_initial_state (toml_dict )
439473
440474 # [build-system]
@@ -456,13 +490,15 @@ def __init__(
456490 toml_dict ["project" ],
457491 gha_input ,
458492 ffile ,
493+ py_ver ,
459494 )
460495 elif gha_input .mode == "PACKAGING_AND_PYPI" :
461496 self .insert_packaging_and_pypi_attributes (
462497 toml_dict ["project" ],
463498 gha_input ,
464499 ffile ,
465500 gh_api ,
501+ py_ver ,
466502 )
467503 else :
468504 raise RuntimeError (f"Unknown mode: { gha_input .mode } " )
@@ -521,6 +557,7 @@ def insert_packaging_attributes(
521557 toml_project : TOMLDocumentTypeHint ,
522558 gha_input : GHAInput ,
523559 ffile : FromFiles ,
560+ py_ver : PythonVersion ,
524561 ) -> None :
525562 """Add the attributes for the 'PACKAGING' mode."""
526563 if gha_input .mode != "PACKAGING" :
@@ -531,7 +568,7 @@ def insert_packaging_attributes(
531568 )
532569 PyProjectTomlBuilder ._inline_dont_change_this_comment (toml_project ["name" ])
533570
534- toml_project ["requires-python" ] = gha_input .get_requires_python ()
571+ toml_project ["requires-python" ] = py_ver .get_requires_python ()
535572 PyProjectTomlBuilder ._inline_dont_change_this_comment (
536573 toml_project ["requires-python" ]
537574 )
@@ -565,6 +602,7 @@ def insert_packaging_and_pypi_attributes(
565602 gha_input : GHAInput ,
566603 ffile : FromFiles ,
567604 gh_api : GitHubAPI ,
605+ py_ver : PythonVersion ,
568606 ) -> None :
569607 """Add the attributes for the 'PACKAGING_AND_PYPI' mode."""
570608 if gha_input .mode != "PACKAGING_AND_PYPI" :
@@ -587,8 +625,8 @@ def insert_packaging_and_pypi_attributes(
587625 [gha_input .license_file ] if gha_input .license_file else []
588626 ),
589627 "keywords" : gha_input .keywords ,
590- "classifiers" : gha_input .python_classifiers (),
591- "requires-python" : gha_input .get_requires_python (),
628+ "classifiers" : py_ver .python_classifiers (),
629+ "requires-python" : py_ver .get_requires_python (),
592630 }
593631 toml_project .update (updates )
594632 for u in updates :
0 commit comments