4343from packaging .metadata import Metadata
4444from packaging .requirements import Requirement
4545from packaging .specifiers import SpecifierSet
46- from packaging .utils import parse_sdist_filename
46+ from packaging .utils import canonicalize_name , parse_sdist_filename
4747from packaging .version import Version
4848import rapidfuzz
4949import rich .progress
@@ -414,6 +414,16 @@ async def preprocessor(bot: Red, destination: discord.abc.Messageable, content:
414414 },
415415)
416416
417+ _REQUIRES_PYTHON_PKG_NAMES = tuple (
418+ map (
419+ canonicalize_name ,
420+ (
421+ "Red-does-not-support-this-version-of-Python.-Please-follow-one-of-the-install-guides-at-docs.discord.red" ,
422+ "package-does-not-support-this-version-of-python" ,
423+ ),
424+ )
425+ )
426+
417427
418428class AvailableVersion :
419429 def __init__ (self , version : Version , files : Dict [str , ReleaseFile ]) -> None :
@@ -422,23 +432,51 @@ def __init__(self, version: Version, files: Dict[str, ReleaseFile]) -> None:
422432 required_pythons = {f .get ("requires-python" ) or "" for f in files .values ()}
423433 if len (required_pythons ) > 1 :
424434 raise ValueError ("found multiple files with different Requires-Python values" )
425- self .requires_python = SpecifierSet (required_pythons .pop ())
435+ self .base_requires_python = SpecifierSet (required_pythons .pop ())
436+ self ._requires_python : Optional [SpecifierSet ] = None
437+ self ._metadata : Optional [Metadata ] = None
438+
439+ @property
440+ def requires_python (self ) -> SpecifierSet :
441+ if self ._requires_python is None :
442+ raise TypeError (
443+ "`requires_python` attribute is missing - call `fetch_extra_info()` first."
444+ )
445+ return self ._requires_python
446+
447+ @property
448+ def metadata (self ) -> Metadata :
449+ if self ._metadata is None :
450+ raise TypeError ("`metadata` attribute is missing - call `fetch_extra_info()` first." )
451+ return self ._metadata
452+
453+ async def fetch_extra_info (self ) -> None :
454+ if self ._metadata is not None :
455+ return
456+ metadata = await self ._fetch_core_metadata ()
457+ # requires https://github.com/pypa/packaging/pull/1182
458+ version_range = self .base_requires_python .to_range ()
459+ for req in metadata .requires_dist or ():
460+ if canonicalize_name (req .name ) in _REQUIRES_PYTHON_PKG_NAMES :
461+ version_range &= ~ req .specifier .to_range ()
462+ self ._requires_python = version_range .to_specifier_set ()
463+ self ._metadata = metadata
426464
427465 @classmethod
428466 def from_json_dict (cls , data : Dict [str , Any ]) -> Self :
429467 ret = cls (Version (data ["version" ]), data ["files" ])
430- if str (ret .requires_python ) != data ["requires_python " ]:
468+ if str (ret .base_requires_python ) != data ["base_requires_python " ]:
431469 raise ValueError ("requires_python key in given data is inconsistent with files" )
432470 return ret
433471
434472 def to_json_dict (self ) -> Dict [str , Any ]:
435473 return {
436474 "version" : str (self .version ),
437- "requires_python " : str (self .requires_python ),
475+ "base_requires_python " : str (self .base_requires_python ),
438476 "files" : self .files ,
439477 }
440478
441- async def fetch_core_metadata (self ) -> Metadata :
479+ async def _fetch_core_metadata (self ) -> Metadata :
442480 for release_file in self .files .values ():
443481 core_metadata_hashes = release_file .get ("core-metadata" , False )
444482 if core_metadata_hashes is False :
0 commit comments