mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2026-05-18 21:43:36 -04:00
Add Environment abstraction to internal Downloader API (#6710)
This commit is contained in:
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -35,7 +36,11 @@ from typing import (
|
||||
)
|
||||
|
||||
import discord
|
||||
from redbot.core import commands, Config, version_info as red_version_info
|
||||
from packaging.version import Version
|
||||
from typing_extensions import Self
|
||||
|
||||
from redbot import __version__
|
||||
from redbot.core import commands, Config
|
||||
from redbot.core._cog_manager import CogManager
|
||||
from redbot.core.data_manager import cog_data_path
|
||||
from redbot.core.utils._internal_utils import detailed_progress
|
||||
@@ -554,8 +559,34 @@ async def reinstall_requirements() -> Tuple[Tuple[str, ...], Tuple[Installable,
|
||||
return failed_reqs, tuple(all_failed_libs)
|
||||
|
||||
|
||||
# TODO: make kw_only
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Environment:
|
||||
"""
|
||||
Environment to check version bounds against.
|
||||
|
||||
Usually the metadata from current environment is used but this allows for alternative uses
|
||||
such as checking, if the cog will work on a Red version you want to update to.
|
||||
"""
|
||||
|
||||
red_version: Version
|
||||
python_version: Version
|
||||
|
||||
@classmethod
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def current(cls) -> Self:
|
||||
return cls(
|
||||
red_version=Version(__version__),
|
||||
python_version=Version(".".join(map(str, sys.version_info[:3]))),
|
||||
)
|
||||
|
||||
|
||||
async def install_cogs(
|
||||
repo: Repo, rev: Optional[str], cog_names: Iterable[str]
|
||||
repo: Repo,
|
||||
rev: Optional[str],
|
||||
cog_names: Iterable[str],
|
||||
*,
|
||||
env: Environment = Environment.current(),
|
||||
) -> CogInstallResult:
|
||||
commit = None
|
||||
|
||||
@@ -588,12 +619,12 @@ async def install_cogs(
|
||||
already_installed.append(cog)
|
||||
elif discord.utils.get(_installed_cogs, name=cog.name):
|
||||
name_already_used.append(cog)
|
||||
elif cog.min_python_version > sys.version_info:
|
||||
elif cog.min_python_version > env.python_version:
|
||||
incompatible_python_version.append(cog)
|
||||
elif cog.min_bot_version > red_version_info or (
|
||||
elif cog.min_bot_version > env.red_version or (
|
||||
# max version should be ignored when it's lower than min version
|
||||
cog.min_bot_version <= cog.max_bot_version
|
||||
and cog.max_bot_version < red_version_info
|
||||
and cog.max_bot_version < env.red_version
|
||||
):
|
||||
incompatible_bot_version.append(cog)
|
||||
else:
|
||||
@@ -653,6 +684,7 @@ async def check_cog_updates(
|
||||
repos: Optional[Iterable[Repo]] = None,
|
||||
cogs: Optional[Iterable[InstalledModule]] = None,
|
||||
update_repos: bool = True,
|
||||
env: Environment = Environment.current(),
|
||||
) -> CogUpdateCheckResult:
|
||||
cogs_to_check, failed_repos = await _get_cogs_to_check(
|
||||
repos=repos, cogs=cogs, update_repos=update_repos
|
||||
@@ -663,12 +695,12 @@ async def check_cog_updates(
|
||||
incompatible_python_version: List[Installable] = []
|
||||
incompatible_bot_version: List[Installable] = []
|
||||
for cog in outdated_cogs:
|
||||
if cog.min_python_version > sys.version_info:
|
||||
if cog.min_python_version > env.python_version:
|
||||
incompatible_python_version.append(cog)
|
||||
elif cog.min_bot_version > red_version_info or (
|
||||
elif cog.min_bot_version > env.red_version or (
|
||||
# max version should be ignored when it's lower than min version
|
||||
cog.min_bot_version <= cog.max_bot_version
|
||||
and cog.max_bot_version < red_version_info
|
||||
and cog.max_bot_version < env.red_version
|
||||
):
|
||||
incompatible_bot_version.append(cog)
|
||||
else:
|
||||
@@ -686,19 +718,26 @@ async def check_cog_updates(
|
||||
|
||||
# update given cogs or all cogs
|
||||
async def update_cogs(
|
||||
*, cogs: Optional[List[InstalledModule]] = None, repos: Optional[List[Repo]] = None
|
||||
*,
|
||||
cogs: Optional[List[InstalledModule]] = None,
|
||||
repos: Optional[List[Repo]] = None,
|
||||
env: Environment = Environment.current(),
|
||||
) -> CogUpdateResult:
|
||||
if cogs is not None and repos is not None:
|
||||
raise ValueError("You can specify cogs or repos argument, not both")
|
||||
|
||||
cogs_to_check, failed_repos = await _get_cogs_to_check(repos=repos, cogs=cogs)
|
||||
return await _update_cogs(cogs_to_check, failed_repos=failed_repos)
|
||||
return await _update_cogs(cogs_to_check, failed_repos=failed_repos, env=env)
|
||||
|
||||
|
||||
# update given cogs or all cogs from the specified repo
|
||||
# using the specified revision (or latest if not specified)
|
||||
async def update_repo_cogs(
|
||||
repo: Repo, cogs: Optional[List[InstalledModule]] = None, *, rev: Optional[str] = None
|
||||
repo: Repo,
|
||||
cogs: Optional[List[InstalledModule]] = None,
|
||||
*,
|
||||
rev: Optional[str] = None,
|
||||
env: Environment = Environment.current(),
|
||||
) -> CogUpdateResult:
|
||||
try:
|
||||
await repo.update()
|
||||
@@ -712,11 +751,14 @@ async def update_repo_cogs(
|
||||
commit = await repo.get_full_sha1(rev)
|
||||
async with repo.checkout(commit, exit_to_rev=repo.branch):
|
||||
cogs_to_check, __ = await _get_cogs_to_check(repos=[repo], cogs=cogs, update_repos=False)
|
||||
return await _update_cogs(cogs_to_check, failed_repos=())
|
||||
return await _update_cogs(cogs_to_check, failed_repos=(), env=env)
|
||||
|
||||
|
||||
async def _update_cogs(
|
||||
cogs_to_check: Set[InstalledModule], *, failed_repos: Sequence[str]
|
||||
cogs_to_check: Set[InstalledModule],
|
||||
*,
|
||||
failed_repos: Sequence[str],
|
||||
env: Environment = Environment.current(),
|
||||
) -> CogUpdateResult:
|
||||
pinned_cogs = {cog for cog in cogs_to_check if cog.pinned}
|
||||
cogs_to_check -= pinned_cogs
|
||||
@@ -737,12 +779,12 @@ async def _update_cogs(
|
||||
outdated_cogs, outdated_libs = await _available_updates(cogs_to_check)
|
||||
|
||||
for cog in outdated_cogs:
|
||||
if cog.min_python_version > sys.version_info:
|
||||
if cog.min_python_version > env.python_version:
|
||||
incompatible_python_version.append(cog)
|
||||
elif cog.min_bot_version > red_version_info or (
|
||||
elif cog.min_bot_version > env.red_version or (
|
||||
# max version should be ignored when it's lower than min version
|
||||
cog.min_bot_version <= cog.max_bot_version
|
||||
and cog.max_bot_version < red_version_info
|
||||
and cog.max_bot_version < env.red_version
|
||||
):
|
||||
incompatible_bot_version.append(cog)
|
||||
else:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Union
|
||||
|
||||
from redbot import VersionInfo, version_info as red_version_info
|
||||
from packaging.version import Version
|
||||
|
||||
from . import installable
|
||||
from .log import log
|
||||
@@ -67,38 +67,40 @@ def ensure_str(info_file: Path, key_name: str, value: Union[Any, UseDefault]) ->
|
||||
return value
|
||||
|
||||
|
||||
def ensure_red_version_info(
|
||||
info_file: Path, key_name: str, value: Union[Any, UseDefault]
|
||||
) -> VersionInfo:
|
||||
default = red_version_info
|
||||
if value is USE_DEFAULT:
|
||||
return default
|
||||
if not isinstance(value, str):
|
||||
log.warning(
|
||||
"Invalid value of '%s' key (expected str, got %s)"
|
||||
" in JSON information file at path: %s",
|
||||
key_name,
|
||||
type(value).__name__,
|
||||
info_file,
|
||||
)
|
||||
return default
|
||||
try:
|
||||
version_info = VersionInfo.from_str(value)
|
||||
except ValueError:
|
||||
log.warning(
|
||||
"Invalid value of '%s' key (given value isn't a valid version string)"
|
||||
" in JSON information file at path: %s",
|
||||
key_name,
|
||||
info_file,
|
||||
)
|
||||
return default
|
||||
return version_info
|
||||
def create_ensure_red_version(default: Version) -> EnsureCallable:
|
||||
def ensure_red_version(
|
||||
info_file: Path, key_name: str, value: Union[Any, UseDefault]
|
||||
) -> Version:
|
||||
if value is USE_DEFAULT:
|
||||
return default
|
||||
if not isinstance(value, str):
|
||||
log.warning(
|
||||
"Invalid value of '%s' key (expected str, got %s)"
|
||||
" in JSON information file at path: %s",
|
||||
key_name,
|
||||
type(value).__name__,
|
||||
info_file,
|
||||
)
|
||||
return default
|
||||
try:
|
||||
version_info = Version(value)
|
||||
except ValueError:
|
||||
log.warning(
|
||||
"Invalid value of '%s' key (given value isn't a valid version string)"
|
||||
" in JSON information file at path: %s",
|
||||
key_name,
|
||||
info_file,
|
||||
)
|
||||
return default
|
||||
return version_info
|
||||
|
||||
return ensure_red_version
|
||||
|
||||
|
||||
def ensure_python_version_info(
|
||||
def ensure_python_version(
|
||||
info_file: Path, key_name: str, value: Union[Any, UseDefault]
|
||||
) -> Tuple[int, int, int]:
|
||||
default = (3, 5, 1)
|
||||
) -> Version:
|
||||
default = Version("3.5.1")
|
||||
if value is USE_DEFAULT:
|
||||
return default
|
||||
if not isinstance(value, list):
|
||||
@@ -130,7 +132,7 @@ def ensure_python_version_info(
|
||||
info_file,
|
||||
)
|
||||
return default
|
||||
return cast(Tuple[int, int, int], tuple(value))
|
||||
return Version(".".join(map(str, value)))
|
||||
|
||||
|
||||
def ensure_bool(
|
||||
@@ -211,9 +213,13 @@ REPO_SCHEMA: SchemaType = {
|
||||
"short": ensure_str,
|
||||
}
|
||||
INSTALLABLE_SCHEMA: SchemaType = {
|
||||
"min_bot_version": ensure_red_version_info,
|
||||
"max_bot_version": ensure_red_version_info,
|
||||
"min_python_version": ensure_python_version_info,
|
||||
"min_bot_version": create_ensure_red_version(Version("0.0.dev0")),
|
||||
# Using little-known version epoch feature to represent something that,
|
||||
# for all practical purposes, will be considered higher than any version number
|
||||
# that we may ever have.
|
||||
# https://packaging.python.org/en/latest/specifications/version-specifiers/#version-epochs
|
||||
"max_bot_version": create_ensure_red_version(Version("99999!99999.99999.post99999+hi.mom")),
|
||||
"min_python_version": ensure_python_version,
|
||||
"hidden": ensure_bool,
|
||||
"disabled": ensure_bool,
|
||||
"required_cogs": ensure_required_cogs_mapping,
|
||||
|
||||
@@ -6,12 +6,12 @@ from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union, cast
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
from .log import log
|
||||
from .info_schemas import INSTALLABLE_SCHEMA, update_mixin
|
||||
from .json_mixins import RepoJSONMixin
|
||||
|
||||
from redbot.core import VersionInfo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .repo_manager import RepoManager, Repo
|
||||
|
||||
@@ -44,12 +44,12 @@ class Installable(RepoJSONMixin):
|
||||
Name(s) of the author(s).
|
||||
end_user_data_statement : `str`
|
||||
End user data statement of the module.
|
||||
min_bot_version : `VersionInfo`
|
||||
min_bot_version : `packaging.version.Version`
|
||||
The minimum bot version required for this Installable.
|
||||
max_bot_version : `VersionInfo`
|
||||
max_bot_version : `packaging.version.Version`
|
||||
The maximum bot version required for this Installable.
|
||||
Ignored if `min_bot_version` is newer than `max_bot_version`.
|
||||
min_python_version : `tuple` of `int`
|
||||
min_python_version : `packaging.version.Version`
|
||||
The minimum python version required for this cog.
|
||||
hidden : `bool`
|
||||
Whether or not this cog will be hidden from the user when they use
|
||||
@@ -87,9 +87,9 @@ class Installable(RepoJSONMixin):
|
||||
self.commit = commit
|
||||
|
||||
self.end_user_data_statement: str
|
||||
self.min_bot_version: VersionInfo
|
||||
self.max_bot_version: VersionInfo
|
||||
self.min_python_version: Tuple[int, int, int]
|
||||
self.min_bot_version: Version
|
||||
self.max_bot_version: Version
|
||||
self.min_python_version: Version
|
||||
self.hidden: bool
|
||||
self.disabled: bool
|
||||
self.required_cogs: Dict[str, str] # Cog name -> repo URL
|
||||
|
||||
Reference in New Issue
Block a user