Skip to content

Commit 9efdba1

Browse files
committed
Allow defining Python requirements through special packages
1 parent 7e2a74b commit 9efdba1

3 files changed

Lines changed: 47 additions & 5 deletions

File tree

redbot/_update/cmd/cog_compatibility.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ async def _check_cog_compatibility_command_impl(
9090
if red_version is None or python_version is None:
9191
with console.status("Checking latest version..."):
9292
latest = await fetch_latest_red_version()
93+
await latest.fetch_extra_info()
9394
red_version = latest.version
9495

9596
python_version = Version(".".join(map(str, sys.version_info[:3])))

redbot/_update/updater.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ async def _prepare_metadata(self) -> None:
301301
with self.console.status("Checking latest version..."):
302302
available_versions = await fetch_available_red_versions()
303303
latest_major = available_versions[0]
304+
await latest_major.fetch_extra_info()
304305

305306
self.metadata = UpdaterMetadata(
306307
self.options,
@@ -347,6 +348,8 @@ async def _prepare_metadata(self) -> None:
347348
)
348349
raise SystemExit(1)
349350

351+
await self.metadata.latest.fetch_extra_info()
352+
350353
async def _show_changelog(self) -> None:
351354
with self.console.status("Fetching changelogs..."):
352355
changelogs = await changelog.fetch_changelogs()

redbot/core/utils/_internal_utils.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from packaging.metadata import Metadata
4444
from packaging.requirements import Requirement
4545
from packaging.specifiers import SpecifierSet
46-
from packaging.utils import parse_sdist_filename
46+
from packaging.utils import canonicalize_name, parse_sdist_filename
4747
from packaging.version import Version
4848
import rapidfuzz
4949
import 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

418428
class 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

Comments
 (0)