+
+def parse_req_python_specifier(requires_python: str) -> Optional[List[TargetVersion]]:
+ """Parse a specifier string (i.e. ``">=3.7,<3.10"``) to a list of TargetVersion.
+
+ If parsing fails, will raise a packaging.specifiers.InvalidSpecifier error.
+ If the parsed specifier cannot be mapped to a valid TargetVersion, returns None.
+ """
+ specifier_set = strip_specifier_set(SpecifierSet(requires_python))
+ if not specifier_set:
+ return None
+
+ target_version_map = {f"3.{v.value}": v for v in TargetVersion}
+ compatible_versions: List[str] = list(specifier_set.filter(target_version_map))
+ if compatible_versions:
+ return [target_version_map[v] for v in compatible_versions]
+ return None
+
+
+def strip_specifier_set(specifier_set: SpecifierSet) -> SpecifierSet:
+ """Strip minor versions for some specifiers in the specifier set.
+
+ For background on version specifiers, see PEP 440:
+ https://peps.python.org/pep-0440/#version-specifiers
+ """
+ specifiers = []
+ for s in specifier_set:
+ if "*" in str(s):
+ specifiers.append(s)
+ elif s.operator in ["~=", "==", ">=", "==="]:
+ version = Version(s.version)
+ stripped = Specifier(f"{s.operator}{version.major}.{version.minor}")
+ specifiers.append(stripped)
+ elif s.operator == ">":
+ version = Version(s.version)
+ if len(version.release) > 2:
+ s = Specifier(f">={version.major}.{version.minor}")
+ specifiers.append(s)
+ else:
+ specifiers.append(s)
+
+ return SpecifierSet(",".join(str(s) for s in specifiers))
+
+
+@lru_cache