diff --git a/.github/workflows/setup.sh b/.github/workflows/setup.sh index 37c93dc1..5f5aebaa 100755 --- a/.github/workflows/setup.sh +++ b/.github/workflows/setup.sh @@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = http://repo.arcanis.me/$arch\nSigLevel = Never' # refresh the image pacman --noconfirm -Syu # main dependencies -pacman --noconfirm -Sy base-devel devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-setuptools python-srcinfo sudo +pacman --noconfirm -Sy base-devel devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo sudo # make dependencies pacman --noconfirm -Sy python-build python-installer python-wheel # optional dependencies diff --git a/.github/workflows/tests.sh b/.github/workflows/tests.sh index 736a189b..50c6f7cd 100755 --- a/.github/workflows/tests.sh +++ b/.github/workflows/tests.sh @@ -4,7 +4,7 @@ set -ex # install dependencies -pacman --noconfirm -Syu base-devel python-pip python-setuptools python-tox +pacman --noconfirm -Syu base-devel python-setuptools python-tox # run test and check targets make check tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cc9ce94..ca945987 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,8 +90,9 @@ Again, the most checks can be performed by `make check` command, though some add self.instance_attribute = "" ``` -* Type annotations are the must, even for local functions. +* Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated. * For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typinng.Optional` (e.g. `str | None` instead of `Optional[str]`). +* `classmethod` should always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead. * Recommended order of function definitions in class: ```python @@ -103,7 +104,7 @@ Again, the most checks can be performed by `make check` command, though some add def property(self) -> Any: ... @classmethod - def class_method(cls: type[Clazz]) -> Clazz: ... + def class_method(cls) -> Self: ... @staticmethod def static_method() -> Any: ... diff --git a/Dockerfile b/Dockerfile index 7737bb8e..96b5ae8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN useradd -m -d "/home/build" -s "/usr/bin/nologin" build && \ COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package" ## install package dependencies ## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size -RUN pacman --noconfirm -Sy devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-setuptools python-srcinfo && \ +RUN pacman --noconfirm -Sy devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \ pacman --noconfirm -Sy python-build python-installer python-wheel && \ pacman --noconfirm -Sy breezy mercurial python-aiohttp python-aiohttp-cors python-boto3 python-cryptography python-jinja python-requests-unixsocket rsync subversion && \ runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-jinja2 \ diff --git a/package/archlinux/PKGBUILD b/package/archlinux/PKGBUILD index f14122d4..44c33b11 100644 --- a/package/archlinux/PKGBUILD +++ b/package/archlinux/PKGBUILD @@ -7,7 +7,7 @@ pkgdesc="ArcH linux ReposItory MANager" arch=('any') url="https://github.com/arcan1s/ahriman" license=('GPL3') -depends=('devtools' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-setuptools' 'python-srcinfo') +depends=('devtools' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo') makedepends=('python-build' 'python-installer' 'python-wheel') optdepends=('breezy: -bzr packages support' 'darcs: -darcs packages support' diff --git a/setup.py b/setup.py index 9eee78fc..56226e62 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,6 @@ setup( "inflection", "passlib", "requests", - "setuptools", "srcinfo", ], setup_requires=[ diff --git a/src/ahriman/__init__.py b/src/ahriman/__init__.py index 8fc622e9..1c75c71f 100644 --- a/src/ahriman/__init__.py +++ b/src/ahriman/__init__.py @@ -17,3 +17,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# workaround for python3.10 while it wasn't yet released in arch +# also remove mypy --python-version flag later +import typing +import typing_extensions + + +for extension in dir(typing_extensions): + if extension not in dir(typing): + setattr(typing, extension, getattr(typing_extensions, extension)) diff --git a/src/ahriman/application/application/application_properties.py b/src/ahriman/application/application/application_properties.py index eb7dead4..66aac048 100644 --- a/src/ahriman/application/application/application_properties.py +++ b/src/ahriman/application/application/application_properties.py @@ -21,6 +21,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.log import LazyLogging from ahriman.core.repository import Repository +from ahriman.models.pacman_synchronization import PacmanSynchronization class ApplicationProperties(LazyLogging): @@ -34,8 +35,8 @@ class ApplicationProperties(LazyLogging): repository(Repository): repository instance """ - def __init__(self, architecture: str, configuration: Configuration, *, - report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> None: + def __init__(self, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool, + refresh_pacman_database: PacmanSynchronization = PacmanSynchronization.Disabled) -> None: """ default constructor @@ -44,8 +45,8 @@ class ApplicationProperties(LazyLogging): configuration(Configuration): configuration instance report(bool): force enable or disable reporting unsafe(bool): if set no user check will be performed before path creation - refresh_pacman_database(int, optional): pacman database syncronization level, ``0`` is disabled - (Default value = 0) + refresh_pacman_database(PacmanSynchronization, optional): pacman database synchronization level + (Default value = PacmanSynchronization.Disabled) """ self.configuration = configuration self.architecture = architecture diff --git a/src/ahriman/application/handlers/add.py b/src/ahriman/application/handlers/add.py index 00536b5b..414c4299 100644 --- a/src/ahriman/application/handlers/add.py +++ b/src/ahriman/application/handlers/add.py @@ -30,7 +30,7 @@ class Add(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/backup.py b/src/ahriman/application/handlers/backup.py index 5561fdbc..c2bc5da7 100644 --- a/src/ahriman/application/handlers/backup.py +++ b/src/ahriman/application/handlers/backup.py @@ -36,7 +36,7 @@ class Backup(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/clean.py b/src/ahriman/application/handlers/clean.py index 47919251..5b3e54c2 100644 --- a/src/ahriman/application/handlers/clean.py +++ b/src/ahriman/application/handlers/clean.py @@ -30,7 +30,7 @@ class Clean(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/daemon.py b/src/ahriman/application/handlers/daemon.py index 33ae3a74..3ef65fd0 100644 --- a/src/ahriman/application/handlers/daemon.py +++ b/src/ahriman/application/handlers/daemon.py @@ -30,7 +30,7 @@ class Daemon(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/dump.py b/src/ahriman/application/handlers/dump.py index 121363ea..2a1d23a3 100644 --- a/src/ahriman/application/handlers/dump.py +++ b/src/ahriman/application/handlers/dump.py @@ -32,7 +32,7 @@ class Dump(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/handler.py b/src/ahriman/application/handlers/handler.py index db6d5600..186ca259 100644 --- a/src/ahriman/application/handlers/handler.py +++ b/src/ahriman/application/handlers/handler.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import argparse import logging @@ -52,7 +50,7 @@ class Handler: ALLOW_MULTI_ARCHITECTURE_RUN = True @classmethod - def architectures_extract(cls: type[Handler], args: argparse.Namespace) -> list[str]: + def architectures_extract(cls, args: argparse.Namespace) -> list[str]: """ get known architectures @@ -83,7 +81,7 @@ class Handler: return sorted(architectures) @classmethod - def call(cls: type[Handler], args: argparse.Namespace, architecture: str) -> bool: + def call(cls, args: argparse.Namespace, architecture: str) -> bool: """ additional function to wrap all calls for multiprocessing library @@ -108,7 +106,7 @@ class Handler: return False @classmethod - def execute(cls: type[Handler], args: argparse.Namespace) -> int: + def execute(cls, args: argparse.Namespace) -> int: """ execute function for all aru @@ -137,7 +135,7 @@ class Handler: return 0 if all(result) else 1 @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/help.py b/src/ahriman/application/handlers/help.py index dafd351f..f8417bde 100644 --- a/src/ahriman/application/handlers/help.py +++ b/src/ahriman/application/handlers/help.py @@ -31,7 +31,7 @@ class Help(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/key_import.py b/src/ahriman/application/handlers/key_import.py index d6447f1b..4293a890 100644 --- a/src/ahriman/application/handlers/key_import.py +++ b/src/ahriman/application/handlers/key_import.py @@ -32,7 +32,7 @@ class KeyImport(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py index d2b2cc34..385018a6 100644 --- a/src/ahriman/application/handlers/patch.py +++ b/src/ahriman/application/handlers/patch.py @@ -38,7 +38,7 @@ class Patch(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/rebuild.py b/src/ahriman/application/handlers/rebuild.py index 15cb2745..2aea52a5 100644 --- a/src/ahriman/application/handlers/rebuild.py +++ b/src/ahriman/application/handlers/rebuild.py @@ -32,7 +32,7 @@ class Rebuild(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/remove.py b/src/ahriman/application/handlers/remove.py index 7a84272a..2c3eba06 100644 --- a/src/ahriman/application/handlers/remove.py +++ b/src/ahriman/application/handlers/remove.py @@ -30,7 +30,7 @@ class Remove(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/remove_unknown.py b/src/ahriman/application/handlers/remove_unknown.py index 895ae1fc..33d367c3 100644 --- a/src/ahriman/application/handlers/remove_unknown.py +++ b/src/ahriman/application/handlers/remove_unknown.py @@ -31,7 +31,7 @@ class RemoveUnknown(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/restore.py b/src/ahriman/application/handlers/restore.py index 0e425205..abd68165 100644 --- a/src/ahriman/application/handlers/restore.py +++ b/src/ahriman/application/handlers/restore.py @@ -33,7 +33,7 @@ class Restore(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/search.py b/src/ahriman/application/handlers/search.py index 625c64d8..2e2f25d1 100644 --- a/src/ahriman/application/handlers/search.py +++ b/src/ahriman/application/handlers/search.py @@ -40,10 +40,14 @@ class Search(Handler): """ ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" - SORT_FIELDS = {field.name for field in fields(AURPackage) if field.default_factory is not list} # type: ignore + SORT_FIELDS = { + field.name + for field in fields(AURPackage) + if field.default_factory is not list # type: ignore[comparison-overlap] + } @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/service_updates.py b/src/ahriman/application/handlers/service_updates.py index 0e25f13c..feb2a400 100644 --- a/src/ahriman/application/handlers/service_updates.py +++ b/src/ahriman/application/handlers/service_updates.py @@ -35,7 +35,7 @@ class ServiceUpdates(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py index 7a29d9d8..d3ffe97d 100644 --- a/src/ahriman/application/handlers/setup.py +++ b/src/ahriman/application/handlers/setup.py @@ -45,7 +45,7 @@ class Setup(Handler): SUDOERS_DIR_PATH = Path("/etc/sudoers.d") @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line @@ -153,7 +153,7 @@ class Setup(Handler): configuration = Configuration(allow_no_value=True) # preserve case # stupid mypy thinks that it is impossible - configuration.optionxform = lambda key: key # type: ignore + configuration.optionxform = lambda key: key # type: ignore[method-assign] # load default configuration first # we cannot use Include here because it will be copied to new chroot, thus no includes there diff --git a/src/ahriman/application/handlers/shell.py b/src/ahriman/application/handlers/shell.py index 99afd391..797b3e97 100644 --- a/src/ahriman/application/handlers/shell.py +++ b/src/ahriman/application/handlers/shell.py @@ -37,7 +37,7 @@ class Shell(Handler): ALLOW_MULTI_ARCHITECTURE_RUN = False @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/sign.py b/src/ahriman/application/handlers/sign.py index c2fd0e3f..d6076542 100644 --- a/src/ahriman/application/handlers/sign.py +++ b/src/ahriman/application/handlers/sign.py @@ -30,7 +30,7 @@ class Sign(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/status.py b/src/ahriman/application/handlers/status.py index 1ae4c0d8..11e5132f 100644 --- a/src/ahriman/application/handlers/status.py +++ b/src/ahriman/application/handlers/status.py @@ -37,7 +37,7 @@ class Status(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/status_update.py b/src/ahriman/application/handlers/status_update.py index 60832513..ac496ac1 100644 --- a/src/ahriman/application/handlers/status_update.py +++ b/src/ahriman/application/handlers/status_update.py @@ -33,7 +33,7 @@ class StatusUpdate(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/structure.py b/src/ahriman/application/handlers/structure.py index 56493fba..9c378485 100644 --- a/src/ahriman/application/handlers/structure.py +++ b/src/ahriman/application/handlers/structure.py @@ -34,7 +34,7 @@ class Structure(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/triggers.py b/src/ahriman/application/handlers/triggers.py index 2c990499..3f5d406d 100644 --- a/src/ahriman/application/handlers/triggers.py +++ b/src/ahriman/application/handlers/triggers.py @@ -31,7 +31,7 @@ class Triggers(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/unsafe_commands.py b/src/ahriman/application/handlers/unsafe_commands.py index d40f9107..ed0002af 100644 --- a/src/ahriman/application/handlers/unsafe_commands.py +++ b/src/ahriman/application/handlers/unsafe_commands.py @@ -33,7 +33,7 @@ class UnsafeCommands(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/update.py b/src/ahriman/application/handlers/update.py index 8f62ee32..a90d87d3 100644 --- a/src/ahriman/application/handlers/update.py +++ b/src/ahriman/application/handlers/update.py @@ -32,7 +32,7 @@ class Update(Handler): """ @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/users.py b/src/ahriman/application/handlers/users.py index 964cb9d4..2d570b6e 100644 --- a/src/ahriman/application/handlers/users.py +++ b/src/ahriman/application/handlers/users.py @@ -39,7 +39,7 @@ class Users(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/validate.py b/src/ahriman/application/handlers/validate.py index f27e9041..f67812ee 100644 --- a/src/ahriman/application/handlers/validate.py +++ b/src/ahriman/application/handlers/validate.py @@ -39,7 +39,7 @@ class Validate(Handler): ALLOW_AUTO_ARCHITECTURE_RUN = False @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/handlers/versions.py b/src/ahriman/application/handlers/versions.py index 86a31f55..8b98e013 100644 --- a/src/ahriman/application/handlers/versions.py +++ b/src/ahriman/application/handlers/versions.py @@ -18,9 +18,12 @@ # along with this program. If not, see . # import argparse -import pkg_resources +import re import sys +from collections.abc import Generator +from importlib import metadata + from ahriman import version from ahriman.application.handlers import Handler from ahriman.core.configuration import Configuration @@ -30,12 +33,16 @@ from ahriman.core.formatters import VersionPrinter class Versions(Handler): """ version handler + + Attributes: + PEP423_PACKAGE_NAME(str): (class attribute) special regex for valid PEP423 package name """ ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" + PEP423_PACKAGE_NAME = re.compile(r"^[A-Za-z0-9._-]+") @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line @@ -49,37 +56,43 @@ class Versions(Handler): """ VersionPrinter(f"Module version {version.__version__}", {"Python": sys.version}).print(verbose=False, separator=" ") - packages = Versions.package_dependencies("ahriman", ("pacman", "s3", "web")) - VersionPrinter("Installed packages", packages).print(verbose=False, separator=" ") + packages = Versions.package_dependencies("ahriman") + VersionPrinter("Installed packages", dict(packages)).print(verbose=False, separator=" ") @staticmethod - def package_dependencies(root: str, root_extras: tuple[str, ...] = ()) -> dict[str, str]: + def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]: """ extract list of ahriman package dependencies installed into system with their versions Args: root(str): root package name - root_extras(tuple[str, ...], optional): extras for the root package (Default value = ()) Returns: - dict[str, str]: map of installed dependency to its version + Generator[tuple[str, str], None, None]: map of installed dependency to its version """ - resources: dict[str, pkg_resources.Distribution] = pkg_resources.working_set.by_key # type: ignore - - def dependencies_by_key(key: str, extras: tuple[str, ...] = ()) -> list[str]: - return [entry.key for entry in resources[key].requires(extras)] + def dependencies_by_key(key: str) -> Generator[str, None, None]: + # in importlib it returns requires in the following format + # ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] + try: + requires = metadata.requires(key) + except metadata.PackageNotFoundError: + return + for entry in requires or []: + yield from Versions.PEP423_PACKAGE_NAME.findall(entry) keys: list[str] = [] - portion = {key for key in dependencies_by_key(root, root_extras) if key in resources} + portion = set(dependencies_by_key(root)) while portion: keys.extend(portion) portion = { key - for key in sum((dependencies_by_key(key) for key in portion), start=[]) - if key not in keys and key in resources + for key in sum((list(dependencies_by_key(key)) for key in portion), start=[]) + if key not in keys } - return { - resource.project_name: resource.version - for resource in map(lambda key: resources[key], keys) - } + for key in keys: + try: + distribution = metadata.distribution(key) + yield distribution.name, distribution.version + except metadata.PackageNotFoundError: + continue diff --git a/src/ahriman/application/handlers/web.py b/src/ahriman/application/handlers/web.py index 6c2b08ba..b6ae9793 100644 --- a/src/ahriman/application/handlers/web.py +++ b/src/ahriman/application/handlers/web.py @@ -33,7 +33,7 @@ class Web(Handler): ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes @classmethod - def run(cls: type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, + def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool) -> None: """ callback for command line diff --git a/src/ahriman/application/lock.py b/src/ahriman/application/lock.py index da552f75..b10cc495 100644 --- a/src/ahriman/application/lock.py +++ b/src/ahriman/application/lock.py @@ -17,12 +17,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import argparse from types import TracebackType -from typing import Literal +from typing import Literal, Self from ahriman import version from ahriman.core.configuration import Configuration @@ -111,7 +109,7 @@ class Lock(LazyLogging): except FileExistsError: raise DuplicateRunError() - def __enter__(self) -> Lock: + def __enter__(self) -> Self: """ default workflow is the following: @@ -120,6 +118,9 @@ class Lock(LazyLogging): 3. Check web status watcher status 4. Create lock file 5. Report to status page if enabled + + Returns: + Self: always instance of self """ self.check_user() self.check_version() diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py index ee586fba..03657b54 100644 --- a/src/ahriman/core/alpm/pacman.py +++ b/src/ahriman/core/alpm/pacman.py @@ -21,12 +21,13 @@ import shutil from collections.abc import Callable, Generator from pathlib import Path -from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore +from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore[import] from typing import Any from ahriman.core.configuration import Configuration from ahriman.core.log import LazyLogging from ahriman.core.util import trim_package +from ahriman.models.pacman_synchronization import PacmanSynchronization from ahriman.models.repository_paths import RepositoryPaths @@ -40,28 +41,28 @@ class Pacman(LazyLogging): handle: Handle - def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None: + def __init__(self, architecture: str, configuration: Configuration, *, + refresh_database: PacmanSynchronization) -> None: """ default constructor Args: architecture(str): repository architecture configuration(Configuration): configuration instance - refresh_database(int): synchronize local cache to remote. If set to ``0``, no synchronization will be - enabled, if set to ``1`` - normal synchronization, if set to ``2`` - force synchronization + refresh_database(PacmanSynchronization): synchronize local cache to remote """ self.__create_handle_fn: Callable[[], Handle] = lambda: self.__create_handle( architecture, configuration, refresh_database=refresh_database) - def __create_handle(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> Handle: + def __create_handle(self, architecture: str, configuration: Configuration, *, + refresh_database: PacmanSynchronization) -> Handle: """ create lazy handle function Args: architecture(str): repository architecture configuration(Configuration): configuration instance - refresh_database(int): synchronize local cache to remote. If set to ``0``, no synchronization will be - enabled, if set to ``1`` - normal synchronization, if set to ``2`` - force synchronization + refresh_database(PacmanSynchronization): synchronize local cache to remote Returns: Handle: fully initialized pacman handle @@ -79,7 +80,7 @@ class Pacman(LazyLogging): self.database_copy(handle, database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache) if use_ahriman_cache and refresh_database: - self.database_sync(handle, force=refresh_database > 1) + self.database_sync(handle, force=refresh_database == PacmanSynchronization.Force) return handle diff --git a/src/ahriman/core/alpm/remote/aur.py b/src/ahriman/core/alpm/remote/aur.py index 4007f278..14cb1150 100644 --- a/src/ahriman/core/alpm/remote/aur.py +++ b/src/ahriman/core/alpm/remote/aur.py @@ -44,6 +44,33 @@ class AUR(Remote): DEFAULT_RPC_VERSION = "5" DEFAULT_TIMEOUT = 30 + @classmethod + def remote_git_url(cls, package_base: str, repository: str) -> str: + """ + generate remote git url from the package base + + Args + package_base(str): package base + repository(str): repository name + + Returns: + str: git url for the specific base + """ + return f"{AUR.DEFAULT_AUR_URL}/{package_base}.git" + + @classmethod + def remote_web_url(cls, package_base: str) -> str: + """ + generate remote web url from the package base + + Args + package_base(str): package base + + Returns: + str: web url for the specific base + """ + return f"{AUR.DEFAULT_AUR_URL}/packages/{package_base}" + @staticmethod def parse_response(response: dict[str, Any]) -> list[AURPackage]: """ @@ -64,33 +91,6 @@ class AUR(Remote): raise PackageInfoError(error_details) return [AURPackage.from_json(package) for package in response["results"]] - @classmethod - def remote_git_url(cls: type[Remote], package_base: str, repository: str) -> str: - """ - generate remote git url from the package base - - Args - package_base(str): package base - repository(str): repository name - - Returns: - str: git url for the specific base - """ - return f"{AUR.DEFAULT_AUR_URL}/{package_base}.git" - - @classmethod - def remote_web_url(cls: type[Remote], package_base: str) -> str: - """ - generate remote web url from the package base - - Args - package_base(str): package base - - Returns: - str: web url for the specific base - """ - return f"{AUR.DEFAULT_AUR_URL}/packages/{package_base}" - def make_request(self, request_type: str, *args: str, **kwargs: str) -> list[AURPackage]: """ perform request to AUR RPC diff --git a/src/ahriman/core/alpm/remote/official.py b/src/ahriman/core/alpm/remote/official.py index e551f65f..5b7e48e4 100644 --- a/src/ahriman/core/alpm/remote/official.py +++ b/src/ahriman/core/alpm/remote/official.py @@ -44,6 +44,35 @@ class Official(Remote): DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json" DEFAULT_TIMEOUT = 30 + @classmethod + def remote_git_url(cls, package_base: str, repository: str) -> str: + """ + generate remote git url from the package base + + Args + package_base(str): package base + repository(str): repository name + + Returns: + str: git url for the specific base + """ + if repository.lower() in ("core", "extra", "testing", "kde-unstable"): + return "https://github.com/archlinux/svntogit-packages.git" # hardcoded, ok + return "https://github.com/archlinux/svntogit-community.git" + + @classmethod + def remote_web_url(cls, package_base: str) -> str: + """ + generate remote web url from the package base + + Args + package_base(str): package base + + Returns: + str: web url for the specific base + """ + return f"{Official.DEFAULT_ARCHLINUX_URL}/packages/{package_base}" + @staticmethod def parse_response(response: dict[str, Any]) -> list[AURPackage]: """ @@ -62,35 +91,6 @@ class Official(Remote): raise PackageInfoError("API validation error") return [AURPackage.from_repo(package) for package in response["results"]] - @classmethod - def remote_git_url(cls: type[Remote], package_base: str, repository: str) -> str: - """ - generate remote git url from the package base - - Args - package_base(str): package base - repository(str): repository name - - Returns: - str: git url for the specific base - """ - if repository.lower() in ("core", "extra", "testing", "kde-unstable"): - return "https://github.com/archlinux/svntogit-packages.git" # hardcoded, ok - return "https://github.com/archlinux/svntogit-community.git" - - @classmethod - def remote_web_url(cls: type[Remote], package_base: str) -> str: - """ - generate remote web url from the package base - - Args - package_base(str): package base - - Returns: - str: web url for the specific base - """ - return f"{Official.DEFAULT_ARCHLINUX_URL}/packages/{package_base}" - def make_request(self, *args: str, by: str) -> list[AURPackage]: """ perform request to official repositories RPC diff --git a/src/ahriman/core/alpm/remote/remote.py b/src/ahriman/core/alpm/remote/remote.py index 32733ac1..81660877 100644 --- a/src/ahriman/core/alpm/remote/remote.py +++ b/src/ahriman/core/alpm/remote/remote.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from ahriman.core.alpm.pacman import Pacman from ahriman.core.log import LazyLogging from ahriman.models.aur_package import AURPackage @@ -42,7 +40,7 @@ class Remote(LazyLogging): """ @classmethod - def info(cls: type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage: + def info(cls, package_name: str, *, pacman: Pacman) -> AURPackage: """ get package info by its name @@ -56,7 +54,7 @@ class Remote(LazyLogging): return cls().package_info(package_name, pacman=pacman) @classmethod - def multisearch(cls: type[Remote], *keywords: str, pacman: Pacman) -> list[AURPackage]: + def multisearch(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]: """ search in remote repository by using API with multiple words. This method is required in order to handle https://bugs.archlinux.org/task/49133. In addition, short words will be dropped @@ -80,7 +78,7 @@ class Remote(LazyLogging): return list(packages.values()) @classmethod - def remote_git_url(cls: type[Remote], package_base: str, repository: str) -> str: + def remote_git_url(cls, package_base: str, repository: str) -> str: """ generate remote git url from the package base @@ -97,7 +95,7 @@ class Remote(LazyLogging): raise NotImplementedError @classmethod - def remote_web_url(cls: type[Remote], package_base: str) -> str: + def remote_web_url(cls, package_base: str) -> str: """ generate remote web url from the package base @@ -113,7 +111,7 @@ class Remote(LazyLogging): raise NotImplementedError @classmethod - def search(cls: type[Remote], *keywords: str, pacman: Pacman) -> list[AURPackage]: + def search(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]: """ search package in AUR web diff --git a/src/ahriman/core/auth/auth.py b/src/ahriman/core/auth/auth.py index 9f9ac6af..e9316ce0 100644 --- a/src/ahriman/core/auth/auth.py +++ b/src/ahriman/core/auth/auth.py @@ -62,8 +62,8 @@ class Auth(LazyLogging): """ return """""" - @classmethod - def load(cls: type[Auth], configuration: Configuration, database: SQLite) -> Auth: + @staticmethod + def load(configuration: Configuration, database: SQLite) -> Auth: """ load authorization module from settings @@ -81,7 +81,7 @@ class Auth(LazyLogging): if provider == AuthSettings.OAuth: from ahriman.core.auth.oauth import OAuth return OAuth(configuration, database) - return cls(configuration) + return Auth(configuration) async def check_credentials(self, username: str | None, password: str | None) -> bool: """ diff --git a/src/ahriman/core/auth/helpers.py b/src/ahriman/core/auth/helpers.py index 8232ef2e..14c38b2b 100644 --- a/src/ahriman/core/auth/helpers.py +++ b/src/ahriman/core/auth/helpers.py @@ -20,7 +20,7 @@ from typing import Any try: - import aiohttp_security # type: ignore + import aiohttp_security # type: ignore[import] _has_aiohttp_security = True except ImportError: _has_aiohttp_security = False diff --git a/src/ahriman/core/auth/oauth.py b/src/ahriman/core/auth/oauth.py index e9456f16..f1c1fbeb 100644 --- a/src/ahriman/core/auth/oauth.py +++ b/src/ahriman/core/auth/oauth.py @@ -128,7 +128,7 @@ class OAuth(Mapping): client.access_token = access_token user, _ = await client.user_info() - username: str = user.email # type: ignore + username: str = user.email # type: ignore[attr-defined] return username except Exception: self.logger.exception("got exception while performing request") diff --git a/src/ahriman/core/configuration/configuration.py b/src/ahriman/core/configuration/configuration.py index 2ee49642..4547a205 100644 --- a/src/ahriman/core/configuration/configuration.py +++ b/src/ahriman/core/configuration/configuration.py @@ -17,15 +17,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import configparser import shlex import sys from collections.abc import Callable from pathlib import Path -from typing import Any +from typing import Any, Self from ahriman.core.exceptions import InitializeError from ahriman.models.repository_paths import RepositoryPaths @@ -113,7 +111,7 @@ class Configuration(configparser.RawConfigParser): return RepositoryPaths(self.getpath("repository", "root"), architecture) @classmethod - def from_path(cls: type[Configuration], path: Path, architecture: str) -> Configuration: + def from_path(cls, path: Path, architecture: str) -> Self: """ constructor with full object initialization @@ -122,7 +120,7 @@ class Configuration(configparser.RawConfigParser): architecture(str): repository architecture Returns: - Configuration: configuration instance + Self: configuration instance """ configuration = cls() configuration.load(path) @@ -186,9 +184,9 @@ class Configuration(configparser.RawConfigParser): # pylint and mypy are too stupid to find these methods # pylint: disable=missing-function-docstring,multiple-statements,unused-argument - def getlist(self, *args: Any, **kwargs: Any) -> list[str]: ... # type: ignore + def getlist(self, *args: Any, **kwargs: Any) -> list[str]: ... # type: ignore[empty-body] - def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore + def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore[empty-body] def gettype(self, section: str, architecture: str, *, fallback: str | None = None) -> tuple[str, str]: """ diff --git a/src/ahriman/core/configuration/validator.py b/src/ahriman/core/configuration/validator.py index 26011391..c7e98915 100644 --- a/src/ahriman/core/configuration/validator.py +++ b/src/ahriman/core/configuration/validator.py @@ -19,7 +19,7 @@ # import ipaddress -from cerberus import TypeDefinition, Validator as RootValidator # type: ignore +from cerberus import TypeDefinition, Validator as RootValidator # type: ignore[import] from pathlib import Path from typing import Any from urllib.parse import urlparse @@ -74,7 +74,7 @@ class Validator(RootValidator): bool: value converted to boolean according to configuration rules """ # pylint: disable=protected-access - converted: bool = self.configuration._convert_to_boolean(value) # type: ignore + converted: bool = self.configuration._convert_to_boolean(value) # type: ignore[attr-defined] return converted def _normalize_coerce_integer(self, value: str) -> int: diff --git a/src/ahriman/core/database/migrations/m005_make_opt_depends.py b/src/ahriman/core/database/migrations/m005_make_opt_depends.py index add68775..e337d4b5 100644 --- a/src/ahriman/core/database/migrations/m005_make_opt_depends.py +++ b/src/ahriman/core/database/migrations/m005_make_opt_depends.py @@ -23,6 +23,7 @@ from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration from ahriman.core.util import package_like from ahriman.models.package import Package +from ahriman.models.pacman_synchronization import PacmanSynchronization __all__ = ["migrate_data", "steps"] @@ -61,7 +62,7 @@ def migrate_package_depends(connection: Connection, configuration: Configuration return _, architecture = configuration.check_loaded() - pacman = Pacman(architecture, configuration, refresh_database=False) + pacman = Pacman(architecture, configuration, refresh_database=PacmanSynchronization.Disabled) package_list = [] for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()): diff --git a/src/ahriman/core/database/sqlite.py b/src/ahriman/core/database/sqlite.py index eb68b761..1bbc8bae 100644 --- a/src/ahriman/core/database/sqlite.py +++ b/src/ahriman/core/database/sqlite.py @@ -17,12 +17,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import json import sqlite3 from pathlib import Path +from typing import Self from ahriman.core.configuration import Configuration from ahriman.core.database.migrations import Migrations @@ -46,7 +45,7 @@ class SQLite(AuthOperations, BuildOperations, LogsOperations, PackageOperations, """ @classmethod - def load(cls: type[SQLite], configuration: Configuration) -> SQLite: + def load(cls, configuration: Configuration) -> Self: """ construct instance from configuration @@ -54,7 +53,7 @@ class SQLite(AuthOperations, BuildOperations, LogsOperations, PackageOperations, configuration(Configuration): configuration instance Returns: - SQLite: fully initialized instance of the database + Self: fully initialized instance of the database """ path = cls.database_path(configuration) database = cls(path) diff --git a/src/ahriman/core/gitremote/remote_pull_trigger.py b/src/ahriman/core/gitremote/remote_pull_trigger.py index 20092c60..e9c400a5 100644 --- a/src/ahriman/core/gitremote/remote_pull_trigger.py +++ b/src/ahriman/core/gitremote/remote_pull_trigger.py @@ -68,7 +68,7 @@ class RemotePullTrigger(Trigger): self.targets = self.configuration_sections(configuration) @classmethod - def configuration_sections(cls: type[Trigger], configuration: Configuration) -> list[str]: + def configuration_sections(cls, configuration: Configuration) -> list[str]: """ extract configuration sections from configuration diff --git a/src/ahriman/core/gitremote/remote_push_trigger.py b/src/ahriman/core/gitremote/remote_push_trigger.py index 8a1a10a9..d5f9a2a1 100644 --- a/src/ahriman/core/gitremote/remote_push_trigger.py +++ b/src/ahriman/core/gitremote/remote_push_trigger.py @@ -76,7 +76,7 @@ class RemotePushTrigger(Trigger): self.targets = self.configuration_sections(configuration) @classmethod - def configuration_sections(cls: type[Trigger], configuration: Configuration) -> list[str]: + def configuration_sections(cls, configuration: Configuration) -> list[str]: """ extract configuration sections from configuration diff --git a/src/ahriman/core/log/http_log_handler.py b/src/ahriman/core/log/http_log_handler.py index 3347f990..bff807a5 100644 --- a/src/ahriman/core/log/http_log_handler.py +++ b/src/ahriman/core/log/http_log_handler.py @@ -17,10 +17,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import logging +from typing import Self + from ahriman.core.configuration import Configuration @@ -52,7 +52,7 @@ class HttpLogHandler(logging.Handler): self.suppress_errors = suppress_errors @classmethod - def load(cls, configuration: Configuration, *, report: bool) -> HttpLogHandler: + def load(cls, configuration: Configuration, *, report: bool) -> Self: """ install logger. This function creates handler instance and adds it to the handler list in case if no other http handler found @@ -60,6 +60,9 @@ class HttpLogHandler(logging.Handler): Args: configuration(Configuration): configuration instance report(bool): force enable or disable reporting + + Returns: + Self: logger instance with loaded settings """ root = logging.getLogger() if (handler := next((handler for handler in root.handlers if isinstance(handler, cls)), None)) is not None: diff --git a/src/ahriman/core/report/report.py b/src/ahriman/core/report/report.py index c2937a3b..f512a5d7 100644 --- a/src/ahriman/core/report/report.py +++ b/src/ahriman/core/report/report.py @@ -66,8 +66,8 @@ class Report(LazyLogging): self.architecture = architecture self.configuration = configuration - @classmethod - def load(cls: type[Report], architecture: str, configuration: Configuration, target: str) -> Report: + @staticmethod + def load(architecture: str, configuration: Configuration, target: str) -> Report: """ load client from settings @@ -93,7 +93,7 @@ class Report(LazyLogging): if provider == ReportSettings.Telegram: from ahriman.core.report.telegram import Telegram return Telegram(architecture, configuration, section) - return cls(architecture, configuration) # should never happen + return Report(architecture, configuration) # should never happen def generate(self, packages: list[Package], result: Result) -> None: """ diff --git a/src/ahriman/core/report/report_trigger.py b/src/ahriman/core/report/report_trigger.py index 1994f66d..9a4691af 100644 --- a/src/ahriman/core/report/report_trigger.py +++ b/src/ahriman/core/report/report_trigger.py @@ -205,7 +205,7 @@ class ReportTrigger(Trigger): self.targets = self.configuration_sections(configuration) @classmethod - def configuration_sections(cls: type[Trigger], configuration: Configuration) -> list[str]: + def configuration_sections(cls, configuration: Configuration) -> list[str]: """ extract configuration sections from configuration diff --git a/src/ahriman/core/repository/repository.py b/src/ahriman/core/repository/repository.py index 8fe21736..a6fa210c 100644 --- a/src/ahriman/core/repository/repository.py +++ b/src/ahriman/core/repository/repository.py @@ -17,10 +17,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from collections.abc import Iterable from pathlib import Path +from typing import Self from ahriman.core import _Context, context from ahriman.core.alpm.pacman import Pacman @@ -32,6 +31,7 @@ from ahriman.core.sign.gpg import GPG from ahriman.core.util import package_like from ahriman.models.context_key import ContextKey from ahriman.models.package import Package +from ahriman.models.pacman_synchronization import PacmanSynchronization class Repository(Executor, UpdateHandler): @@ -58,8 +58,8 @@ class Repository(Executor, UpdateHandler): """ @classmethod - def load(cls: type[Repository], architecture: str, configuration: Configuration, database: SQLite, *, - report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> Repository: + def load(cls, architecture: str, configuration: Configuration, database: SQLite, *, report: bool, unsafe: bool, + refresh_pacman_database: PacmanSynchronization = PacmanSynchronization.Disabled) -> Self: """ load instance from argument list @@ -69,8 +69,11 @@ class Repository(Executor, UpdateHandler): database(SQLite): database instance report(bool): force enable or disable reporting unsafe(bool): if set no user check will be performed before path creation - refresh_pacman_database(int, optional): pacman database syncronization level, ``0`` is disabled - (Default value = 0) + refresh_pacman_database(PacmanSynchronization, optional): pacman database synchronization level + (Default value = PacmanSynchronization.Disabled) + + Returns: + Self: fully loaded repository class instance """ instance = cls(architecture, configuration, database, report=report, unsafe=unsafe, refresh_pacman_database=refresh_pacman_database) diff --git a/src/ahriman/core/repository/repository_properties.py b/src/ahriman/core/repository/repository_properties.py index cc2da460..0c961d65 100644 --- a/src/ahriman/core/repository/repository_properties.py +++ b/src/ahriman/core/repository/repository_properties.py @@ -27,6 +27,7 @@ from ahriman.core.sign.gpg import GPG from ahriman.core.status.client import Client from ahriman.core.triggers import TriggerLoader from ahriman.core.util import check_user +from ahriman.models.pacman_synchronization import PacmanSynchronization from ahriman.models.repository_paths import RepositoryPaths @@ -49,8 +50,8 @@ class RepositoryProperties(LazyLogging): vcs_allowed_age(int): maximal age of the VCS packages before they will be checked """ - def __init__(self, architecture: str, configuration: Configuration, database: SQLite, *, - report: bool, unsafe: bool, refresh_pacman_database: int) -> None: + def __init__(self, architecture: str, configuration: Configuration, database: SQLite, *, report: bool, unsafe: bool, + refresh_pacman_database: PacmanSynchronization) -> None: """ default constructor @@ -60,7 +61,7 @@ class RepositoryProperties(LazyLogging): database(SQLite): database instance report(bool): force enable or disable reporting unsafe(bool): if set no user check will be performed before path creation - refresh_pacman_database(int, optional): pacman database syncronization level, ``0`` is disabled + refresh_pacman_database(PacmanSynchronization): pacman database synchronization level """ self.architecture = architecture self.configuration = configuration diff --git a/src/ahriman/core/status/client.py b/src/ahriman/core/status/client.py index 4000c870..d3efafca 100644 --- a/src/ahriman/core/status/client.py +++ b/src/ahriman/core/status/client.py @@ -32,8 +32,8 @@ class Client: base build status reporter client """ - @classmethod - def load(cls: type[Client], configuration: Configuration, *, report: bool) -> Client: + @staticmethod + def load(configuration: Configuration, *, report: bool) -> Client: """ load client from settings @@ -45,7 +45,7 @@ class Client: Client: client according to current settings """ if not report: - return cls() + return Client() address = configuration.get("web", "address", fallback=None) host = configuration.get("web", "host", fallback=None) @@ -58,7 +58,7 @@ class Client: if address or (host and port) or socket: from ahriman.core.status.web_client import WebClient return WebClient(configuration) - return cls() + return Client() def add(self, package: Package, status: BuildStatusEnum) -> None: """ diff --git a/src/ahriman/core/status/web_client.py b/src/ahriman/core/status/web_client.py index a835fae1..3dc2f00f 100644 --- a/src/ahriman/core/status/web_client.py +++ b/src/ahriman/core/status/web_client.py @@ -131,7 +131,7 @@ class WebClient(Client, LazyLogging): requests.Session: generated session object """ if use_unix_socket: - import requests_unixsocket # type: ignore + import requests_unixsocket # type: ignore[import] session: requests.Session = requests_unixsocket.Session() return session diff --git a/src/ahriman/core/triggers/trigger.py b/src/ahriman/core/triggers/trigger.py index d931c068..6442097c 100644 --- a/src/ahriman/core/triggers/trigger.py +++ b/src/ahriman/core/triggers/trigger.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from ahriman.core.configuration import Configuration from ahriman.core.configuration.schema import ConfigurationSchema from ahriman.core.log import LazyLogging @@ -70,8 +68,7 @@ class Trigger(LazyLogging): self.configuration = configuration @classmethod - def configuration_schema(cls: type[Trigger], architecture: str, - configuration: Configuration | None) -> ConfigurationSchema: + def configuration_schema(cls, architecture: str, configuration: Configuration | None) -> ConfigurationSchema: """ configuration schema based on supplied service configuration @@ -102,7 +99,7 @@ class Trigger(LazyLogging): return result @classmethod - def configuration_sections(cls: type[Trigger], configuration: Configuration) -> list[str]: + def configuration_sections(cls, configuration: Configuration) -> list[str]: """ extract configuration sections from configuration @@ -116,8 +113,8 @@ class Trigger(LazyLogging): This method can be used in order to extract specific configuration sections which are set by user, e.g. from sources:: - >>> @staticmethod - >>> def configuration_sections(cls: type[Trigger], configuration: Configuration) -> list[str]: + >>> @classmethod + >>> def configuration_sections(cls, configuration: Configuration) -> list[str]: >>> return configuration.getlist("report", "target", fallback=[]) """ del configuration diff --git a/src/ahriman/core/triggers/trigger_loader.py b/src/ahriman/core/triggers/trigger_loader.py index 4d23344c..ace455cf 100644 --- a/src/ahriman/core/triggers/trigger_loader.py +++ b/src/ahriman/core/triggers/trigger_loader.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import contextlib import os @@ -26,6 +24,7 @@ from collections.abc import Generator from importlib import import_module, machinery from pathlib import Path from types import ModuleType +from typing import Self from ahriman.core.configuration import Configuration from ahriman.core.exceptions import ExtensionError @@ -66,7 +65,7 @@ class TriggerLoader(LazyLogging): self.triggers: list[Trigger] = [] @classmethod - def load(cls: type[TriggerLoader], architecture: str, configuration: Configuration) -> TriggerLoader: + def load(cls, architecture: str, configuration: Configuration) -> Self: """ create instance from configuration @@ -75,7 +74,7 @@ class TriggerLoader(LazyLogging): configuration(Configuration): configuration instance Returns: - TriggerLoader: fully loaded trigger instance + Self: fully loaded trigger instance """ instance = cls() instance.triggers = [ diff --git a/src/ahriman/core/upload/s3.py b/src/ahriman/core/upload/s3.py index b086e734..dbf3bc4b 100644 --- a/src/ahriman/core/upload/s3.py +++ b/src/ahriman/core/upload/s3.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import boto3 # type: ignore +import boto3 # type: ignore[import] import hashlib import mimetypes diff --git a/src/ahriman/core/upload/upload.py b/src/ahriman/core/upload/upload.py index 052fc335..3b8f9fb1 100644 --- a/src/ahriman/core/upload/upload.py +++ b/src/ahriman/core/upload/upload.py @@ -66,8 +66,8 @@ class Upload(LazyLogging): self.architecture = architecture self.configuration = configuration - @classmethod - def load(cls: type[Upload], architecture: str, configuration: Configuration, target: str) -> Upload: + @staticmethod + def load(architecture: str, configuration: Configuration, target: str) -> Upload: """ load client from settings @@ -90,7 +90,7 @@ class Upload(LazyLogging): if provider == UploadSettings.Github: from ahriman.core.upload.github import Github return Github(architecture, configuration, section) - return cls(architecture, configuration) # should never happen + return Upload(architecture, configuration) # should never happen def run(self, path: Path, built_packages: list[Package]) -> None: """ diff --git a/src/ahriman/core/upload/upload_trigger.py b/src/ahriman/core/upload/upload_trigger.py index ad96f30e..a3af9b6e 100644 --- a/src/ahriman/core/upload/upload_trigger.py +++ b/src/ahriman/core/upload/upload_trigger.py @@ -136,7 +136,7 @@ class UploadTrigger(Trigger): self.targets = self.configuration_sections(configuration) @classmethod - def configuration_sections(cls: type[Trigger], configuration: Configuration) -> list[str]: + def configuration_sections(cls, configuration: Configuration) -> list[str]: """ extract configuration sections from configuration diff --git a/src/ahriman/models/aur_package.py b/src/ahriman/models/aur_package.py index 709ee096..851ae17d 100644 --- a/src/ahriman/models/aur_package.py +++ b/src/ahriman/models/aur_package.py @@ -17,15 +17,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import datetime import inflection from collections.abc import Callable from dataclasses import dataclass, field, fields -from pyalpm import Package # type: ignore -from typing import Any +from pyalpm import Package # type: ignore[import] +from typing import Any, Self from ahriman.core.util import filter_json, full_version @@ -102,7 +100,7 @@ class AURPackage: keywords: list[str] = field(default_factory=list) @classmethod - def from_json(cls: type[AURPackage], dump: dict[str, Any]) -> AURPackage: + def from_json(cls, dump: dict[str, Any]) -> Self: """ construct package descriptor from RPC properties @@ -110,7 +108,7 @@ class AURPackage: dump(dict[str, Any]): json dump body Returns: - AURPackage: AUR package descriptor + Self: AUR package descriptor """ # filter to only known fields known_fields = [pair.name for pair in fields(cls)] @@ -118,7 +116,7 @@ class AURPackage: return cls(**filter_json(properties, known_fields)) @classmethod - def from_pacman(cls: type[AURPackage], package: Package) -> AURPackage: + def from_pacman(cls, package: Package) -> Self: """ construct package descriptor from official repository wrapper @@ -126,7 +124,7 @@ class AURPackage: package(Package): pyalpm package descriptor Returns: - AURPackage: AUR package descriptor + Self: AUR package descriptor """ return cls( id=0, @@ -155,7 +153,7 @@ class AURPackage: ) @classmethod - def from_repo(cls: type[AURPackage], dump: dict[str, Any]) -> AURPackage: + def from_repo(cls, dump: dict[str, Any]) -> Self: """ construct package descriptor from official repository RPC properties @@ -163,7 +161,7 @@ class AURPackage: dump(dict[str, Any]): json dump body Returns: - AURPackage: AUR package descriptor + Self: AUR package descriptor """ return cls( id=0, diff --git a/src/ahriman/models/auth_settings.py b/src/ahriman/models/auth_settings.py index b61917a6..c036be54 100644 --- a/src/ahriman/models/auth_settings.py +++ b/src/ahriman/models/auth_settings.py @@ -36,23 +36,6 @@ class AuthSettings(str, Enum): Configuration = "configuration" OAuth = "oauth2" - @classmethod - def from_option(cls: type[AuthSettings], value: str) -> AuthSettings: - """ - construct value from configuration - - Args: - value(str): configuration value - - Returns: - AuthSettings: parsed value - """ - if value.lower() in ("configuration", "mapping"): - return cls.Configuration - if value.lower() in ("oauth", "oauth2"): - return cls.OAuth - return cls.Disabled - @property def is_enabled(self) -> bool: """ @@ -64,3 +47,20 @@ class AuthSettings(str, Enum): if self == AuthSettings.Disabled: return False return True + + @staticmethod + def from_option(value: str) -> AuthSettings: + """ + construct value from configuration + + Args: + value(str): configuration value + + Returns: + AuthSettings: parsed value + """ + if value.lower() in ("configuration", "mapping"): + return AuthSettings.Configuration + if value.lower() in ("oauth", "oauth2"): + return AuthSettings.OAuth + return AuthSettings.Disabled diff --git a/src/ahriman/models/build_status.py b/src/ahriman/models/build_status.py index fe68dd49..03307c5e 100644 --- a/src/ahriman/models/build_status.py +++ b/src/ahriman/models/build_status.py @@ -17,11 +17,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from dataclasses import dataclass, field, fields from enum import Enum -from typing import Any +from typing import Any, Self from ahriman.core.util import filter_json, pretty_datetime, utcnow @@ -65,7 +63,7 @@ class BuildStatus: object.__setattr__(self, "status", BuildStatusEnum(self.status)) @classmethod - def from_json(cls: type[BuildStatus], dump: dict[str, Any]) -> BuildStatus: + def from_json(cls, dump: dict[str, Any]) -> Self: """ construct status properties from json dump @@ -73,7 +71,7 @@ class BuildStatus: dump(dict[str, Any]): json dump body Returns: - BuildStatus: status properties + Self: status properties """ known_fields = [pair.name for pair in fields(cls)] return cls(**filter_json(dump, known_fields)) diff --git a/src/ahriman/models/counters.py b/src/ahriman/models/counters.py index ccb2f8ea..8b99505c 100644 --- a/src/ahriman/models/counters.py +++ b/src/ahriman/models/counters.py @@ -17,10 +17,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from dataclasses import dataclass, fields -from typing import Any +from typing import Any, Self from ahriman.core.util import filter_json from ahriman.models.build_status import BuildStatus @@ -49,7 +47,7 @@ class Counters: success: int = 0 @classmethod - def from_json(cls: type[Counters], dump: dict[str, Any]) -> Counters: + def from_json(cls, dump: dict[str, Any]) -> Self: """ construct counters from json dump @@ -57,14 +55,14 @@ class Counters: dump(dict[str, Any]): json dump body Returns: - Counters: status counters + Self: status counters """ # filter to only known fields known_fields = [pair.name for pair in fields(cls)] return cls(**filter_json(dump, known_fields)) @classmethod - def from_packages(cls: type[Counters], packages: list[tuple[Package, BuildStatus]]) -> Counters: + def from_packages(cls, packages: list[tuple[Package, BuildStatus]]) -> Self: """ construct counters from packages statuses @@ -72,7 +70,7 @@ class Counters: packages(list[tuple[Package, BuildStatus]]): list of package and their status as per watcher property Returns: - Counters: status counters + Self: status counters """ per_status = {"total": len(packages)} for _, status in packages: diff --git a/src/ahriman/models/internal_status.py b/src/ahriman/models/internal_status.py index 8b76c1d8..1dbb6444 100644 --- a/src/ahriman/models/internal_status.py +++ b/src/ahriman/models/internal_status.py @@ -17,10 +17,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from dataclasses import asdict, dataclass, field -from typing import Any +from typing import Any, Self from ahriman.models.build_status import BuildStatus from ahriman.models.counters import Counters @@ -46,7 +44,7 @@ class InternalStatus: version: str | None = None @classmethod - def from_json(cls: type[InternalStatus], dump: dict[str, Any]) -> InternalStatus: + def from_json(cls, dump: dict[str, Any]) -> Self: """ construct internal status from json dump @@ -54,7 +52,7 @@ class InternalStatus: dump(dict[str, Any]): json dump body Returns: - InternalStatus: internal status + Self: internal status """ counters = Counters.from_json(dump["packages"]) if "packages" in dump else Counters(total=0) build_status = dump.get("status") or {} diff --git a/src/ahriman/models/package.py b/src/ahriman/models/package.py index 1523e9a1..64315992 100644 --- a/src/ahriman/models/package.py +++ b/src/ahriman/models/package.py @@ -25,9 +25,9 @@ import copy from collections.abc import Iterable from dataclasses import asdict, dataclass from pathlib import Path -from pyalpm import vercmp # type: ignore -from srcinfo.parse import parse_srcinfo # type: ignore -from typing import Any +from pyalpm import vercmp # type: ignore[import] +from srcinfo.parse import parse_srcinfo # type: ignore[import] +from typing import Any, Self from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb @@ -179,7 +179,7 @@ class Package(LazyLogging): return sorted(packages) @classmethod - def from_archive(cls: type[Package], path: Path, pacman: Pacman, remote: RemoteSource | None) -> Package: + def from_archive(cls, path: Path, pacman: Pacman, remote: RemoteSource | None) -> Self: """ construct package properties from package archive @@ -189,14 +189,14 @@ class Package(LazyLogging): remote(RemoteSource): package remote source if applicable Returns: - Package: package properties + Self: package properties """ package = pacman.handle.load_pkg(str(path)) description = PackageDescription.from_package(package, path) return cls(base=package.base, version=package.version, remote=remote, packages={package.name: description}) @classmethod - def from_aur(cls: type[Package], name: str, pacman: Pacman) -> Package: + def from_aur(cls, name: str, pacman: Pacman) -> Self: """ construct package properties from AUR page @@ -205,7 +205,7 @@ class Package(LazyLogging): pacman(Pacman): alpm wrapper instance Returns: - Package: package properties + Self: package properties """ package = AUR.info(name, pacman=pacman) remote = RemoteSource.from_source(PackageSource.AUR, package.package_base, package.repository) @@ -216,7 +216,7 @@ class Package(LazyLogging): packages={package.name: PackageDescription.from_aur(package)}) @classmethod - def from_build(cls: type[Package], path: Path, architecture: str) -> Package: + def from_build(cls, path: Path, architecture: str) -> Self: """ construct package properties from sources directory @@ -225,7 +225,7 @@ class Package(LazyLogging): architecture(str): load package for specific architecture Returns: - Package: package properties + Self: package properties Raises: InvalidPackageInfo: if there are parsing errors @@ -254,7 +254,7 @@ class Package(LazyLogging): return cls(base=srcinfo["pkgbase"], version=version, remote=None, packages=packages) @classmethod - def from_json(cls: type[Package], dump: dict[str, Any]) -> Package: + def from_json(cls, dump: dict[str, Any]) -> Self: """ construct package properties from json dump @@ -262,7 +262,7 @@ class Package(LazyLogging): dump(dict[str, Any]): json dump body Returns: - Package: package properties + Self: package properties """ packages_json = dump.get("packages") or {} packages = { @@ -273,7 +273,7 @@ class Package(LazyLogging): return cls(base=dump["base"], version=dump["version"], remote=RemoteSource.from_json(remote), packages=packages) @classmethod - def from_official(cls: type[Package], name: str, pacman: Pacman, *, use_syncdb: bool = True) -> Package: + def from_official(cls, name: str, pacman: Pacman, *, use_syncdb: bool = True) -> Self: """ construct package properties from official repository page @@ -283,7 +283,7 @@ class Package(LazyLogging): use_syncdb(bool, optional): use pacman databases instead of official repositories RPC (Default value = True) Returns: - Package: package properties + Self: package properties """ package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman) remote = RemoteSource.from_source(PackageSource.Repository, package.package_base, package.repository) diff --git a/src/ahriman/models/package_description.py b/src/ahriman/models/package_description.py index 4868f93e..6f627922 100644 --- a/src/ahriman/models/package_description.py +++ b/src/ahriman/models/package_description.py @@ -17,12 +17,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from dataclasses import asdict, dataclass, field, fields from pathlib import Path -from pyalpm import Package # type: ignore -from typing import Any +from pyalpm import Package # type: ignore[import] +from typing import Any, Self from ahriman.core.util import filter_json, trim_package from ahriman.models.aur_package import AURPackage @@ -99,7 +97,7 @@ class PackageDescription: return Path(self.filename) if self.filename is not None else None @classmethod - def from_aur(cls: type[PackageDescription], package: AURPackage) -> PackageDescription: + def from_aur(cls, package: AURPackage) -> Self: """ construct properties from AUR package model @@ -107,7 +105,7 @@ class PackageDescription: package(AURPackage): AUR package model Returns: - PackageDescription: package properties based on source AUR package + Self: package properties based on source AUR package """ return cls( depends=package.depends, @@ -120,7 +118,7 @@ class PackageDescription: ) @classmethod - def from_json(cls: type[PackageDescription], dump: dict[str, Any]) -> PackageDescription: + def from_json(cls, dump: dict[str, Any]) -> Self: """ construct package properties from json dump @@ -128,14 +126,14 @@ class PackageDescription: dump(dict[str, Any]): json dump body Returns: - PackageDescription: package properties + Self: package properties """ # filter to only known fields known_fields = [pair.name for pair in fields(cls)] return cls(**filter_json(dump, known_fields)) @classmethod - def from_package(cls: type[PackageDescription], package: Package, path: Path) -> PackageDescription: + def from_package(cls, package: Package, path: Path) -> Self: """ construct class from alpm package class @@ -144,7 +142,7 @@ class PackageDescription: path(Path): path to package archive Returns: - PackageDescription: package properties based on tarball + Self: package properties based on tarball """ return cls( architecture=package.arch, diff --git a/src/ahriman/models/pacman_synchronization.py b/src/ahriman/models/pacman_synchronization.py new file mode 100644 index 00000000..ac9e2f10 --- /dev/null +++ b/src/ahriman/models/pacman_synchronization.py @@ -0,0 +1,35 @@ +# +# Copyright (c) 2021-2023 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from enum import Enum + + +class PacmanSynchronization(int, Enum): + """ + pacman database synchronization flag + + Attributes: + Disabled(PacmanSynchronization): (class attribute) do not synchronize local database + Enabled(PacmanSynchronization): (class attribute) synchronize local database (same as pacman -Sy) + Force(PacmanSynchronization): (class attribute) force synchronize local database (same as pacman -Syy) + """ + + Disabled = 0 + Enabled = 1 + Force = 2 diff --git a/src/ahriman/models/remote_source.py b/src/ahriman/models/remote_source.py index 47aca111..4960b7c6 100644 --- a/src/ahriman/models/remote_source.py +++ b/src/ahriman/models/remote_source.py @@ -17,11 +17,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from dataclasses import asdict, dataclass, fields from pathlib import Path -from typing import Any +from typing import Any, Self from ahriman.core.util import filter_json from ahriman.models.package_source import PackageSource @@ -63,7 +61,7 @@ class RemoteSource: return Path(self.path) @classmethod - def from_json(cls: type[RemoteSource], dump: dict[str, Any]) -> RemoteSource | None: + def from_json(cls, dump: dict[str, Any]) -> Self | None: """ construct remote source from the json dump (or database row) @@ -71,7 +69,7 @@ class RemoteSource: dump(dict[str, Any]): json dump body Returns: - RemoteSource | None: remote source + Self | None: remote source """ # filter to only known fields known_fields = [pair.name for pair in fields(cls)] @@ -81,8 +79,7 @@ class RemoteSource: return None @classmethod - def from_source(cls: type[RemoteSource], source: PackageSource, package_base: str, - repository: str) -> RemoteSource | None: + def from_source(cls, source: PackageSource, package_base: str, repository: str) -> Self | None: """ generate remote source from the package base @@ -92,11 +89,11 @@ class RemoteSource: repository(str): repository name Returns: - RemoteSource | None: generated remote source if any, None otherwise + Self | None: generated remote source if any, None otherwise """ if source == PackageSource.AUR: from ahriman.core.alpm.remote import AUR - return RemoteSource( + return cls( git_url=AUR.remote_git_url(package_base, repository), web_url=AUR.remote_web_url(package_base), path=".", @@ -105,7 +102,7 @@ class RemoteSource: ) if source == PackageSource.Repository: from ahriman.core.alpm.remote import Official - return RemoteSource( + return cls( git_url=Official.remote_git_url(package_base, repository), web_url=Official.remote_web_url(package_base), path="trunk", diff --git a/src/ahriman/models/report_settings.py b/src/ahriman/models/report_settings.py index d6eef851..258e0386 100644 --- a/src/ahriman/models/report_settings.py +++ b/src/ahriman/models/report_settings.py @@ -40,8 +40,8 @@ class ReportSettings(str, Enum): Console = "console" Telegram = "telegram" - @classmethod - def from_option(cls: type[ReportSettings], value: str) -> ReportSettings: + @staticmethod + def from_option(value: str) -> ReportSettings: """ construct value from configuration @@ -52,11 +52,11 @@ class ReportSettings(str, Enum): ReportSettings: parsed value """ if value.lower() in ("html",): - return cls.HTML + return ReportSettings.HTML if value.lower() in ("email",): - return cls.Email + return ReportSettings.Email if value.lower() in ("console",): - return cls.Console + return ReportSettings.Console if value.lower() in ("telegram",): - return cls.Telegram - return cls.Disabled + return ReportSettings.Telegram + return ReportSettings.Disabled diff --git a/src/ahriman/models/repository_paths.py b/src/ahriman/models/repository_paths.py index 607d5915..42ac1f06 100644 --- a/src/ahriman/models/repository_paths.py +++ b/src/ahriman/models/repository_paths.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - import os import shutil @@ -117,7 +115,7 @@ class RepositoryPaths: return self.owner(self.root) @classmethod - def known_architectures(cls: type[RepositoryPaths], root: Path) -> set[str]: + def known_architectures(cls, root: Path) -> set[str]: """ get known architectures diff --git a/src/ahriman/models/sign_settings.py b/src/ahriman/models/sign_settings.py index bfeadb76..73dd51d9 100644 --- a/src/ahriman/models/sign_settings.py +++ b/src/ahriman/models/sign_settings.py @@ -36,8 +36,8 @@ class SignSettings(str, Enum): Packages = "packages" Repository = "repository" - @classmethod - def from_option(cls: type[SignSettings], value: str) -> SignSettings: + @staticmethod + def from_option(value: str) -> SignSettings: """ construct value from configuration @@ -48,7 +48,7 @@ class SignSettings(str, Enum): SignSettings: parsed value """ if value.lower() in ("package", "packages", "sign-package"): - return cls.Packages + return SignSettings.Packages if value.lower() in ("repository", "sign-repository"): - return cls.Repository - return cls.Disabled + return SignSettings.Repository + return SignSettings.Disabled diff --git a/src/ahriman/models/smtp_ssl_settings.py b/src/ahriman/models/smtp_ssl_settings.py index 752fa84d..e7eac78b 100644 --- a/src/ahriman/models/smtp_ssl_settings.py +++ b/src/ahriman/models/smtp_ssl_settings.py @@ -36,8 +36,8 @@ class SmtpSSLSettings(str, Enum): SSL = "ssl" STARTTLS = "starttls" - @classmethod - def from_option(cls: type[SmtpSSLSettings], value: str) -> SmtpSSLSettings: + @staticmethod + def from_option(value: str) -> SmtpSSLSettings: """ construct value from configuration @@ -48,7 +48,7 @@ class SmtpSSLSettings(str, Enum): SmtpSSLSettings: parsed value """ if value.lower() in ("ssl", "ssl/tls"): - return cls.SSL + return SmtpSSLSettings.SSL if value.lower() in ("starttls",): - return cls.STARTTLS - return cls.Disabled + return SmtpSSLSettings.STARTTLS + return SmtpSSLSettings.Disabled diff --git a/src/ahriman/models/upload_settings.py b/src/ahriman/models/upload_settings.py index 26456642..d6f959e6 100644 --- a/src/ahriman/models/upload_settings.py +++ b/src/ahriman/models/upload_settings.py @@ -38,8 +38,8 @@ class UploadSettings(str, Enum): S3 = "s3" Github = "github" - @classmethod - def from_option(cls: type[UploadSettings], value: str) -> UploadSettings: + @staticmethod + def from_option(value: str) -> UploadSettings: """ construct value from configuration @@ -50,9 +50,9 @@ class UploadSettings(str, Enum): UploadSettings: parsed value """ if value.lower() in ("rsync",): - return cls.Rsync + return UploadSettings.Rsync if value.lower() in ("s3",): - return cls.S3 + return UploadSettings.S3 if value.lower() in ("github",): - return cls.Github - return cls.Disabled + return UploadSettings.Github + return UploadSettings.Disabled diff --git a/src/ahriman/models/user.py b/src/ahriman/models/user.py index 20efa6de..8c9b1864 100644 --- a/src/ahriman/models/user.py +++ b/src/ahriman/models/user.py @@ -17,11 +17,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - from dataclasses import dataclass, replace from passlib.hash import sha512_crypt from passlib.pwd import genword as generate_password +from typing import Self from ahriman.models.user_access import UserAccess @@ -66,8 +65,8 @@ class User: _HASHER = sha512_crypt @classmethod - def from_option(cls: type[User], username: str | None, password: str | None, - access: UserAccess = UserAccess.Read) -> User | None: + def from_option(cls, username: str | None, password: str | None, + access: UserAccess = UserAccess.Read) -> Self | None: """ build user descriptor from configuration options @@ -77,7 +76,7 @@ class User: access(UserAccess, optional): optional user access (Default value = UserAccess.Read) Returns: - User | None: generated user descriptor if all options are supplied and None otherwise + Self | None: generated user descriptor if all options are supplied and None otherwise """ if username is None or password is None: return None @@ -114,7 +113,7 @@ class User: verified = False # the absence of evidence is not the evidence of absence (c) Gin Rummy return verified - def hash_password(self, salt: str) -> User: + def hash_password(self, salt: str) -> Self: """ generate hashed password from plain text @@ -122,7 +121,7 @@ class User: salt(str): salt for hashed password Returns: - User: user with hashed password to store in configuration + Self: user with hashed password to store in configuration """ if not self.password: # in case of empty password we leave it empty. This feature is used by any external (like OAuth) provider diff --git a/src/ahriman/web/apispec.py b/src/ahriman/web/apispec.py index 21bf7a80..e4d55b25 100644 --- a/src/ahriman/web/apispec.py +++ b/src/ahriman/web/apispec.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import Application from typing import Any diff --git a/src/ahriman/web/cors.py b/src/ahriman/web/cors.py index 83f75399..2d9f19cc 100644 --- a/src/ahriman/web/cors.py +++ b/src/ahriman/web/cors.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_cors # type: ignore +import aiohttp_cors # type: ignore[import] from aiohttp.web import Application diff --git a/src/ahriman/web/middlewares/auth_handler.py b/src/ahriman/web/middlewares/auth_handler.py index 21770383..82360f83 100644 --- a/src/ahriman/web/middlewares/auth_handler.py +++ b/src/ahriman/web/middlewares/auth_handler.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_security # type: ignore +import aiohttp_security # type: ignore[import] import socket import types diff --git a/src/ahriman/web/views/base.py b/src/ahriman/web/views/base.py index 15ceffe1..5a57c084 100644 --- a/src/ahriman/web/views/base.py +++ b/src/ahriman/web/views/base.py @@ -17,9 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from __future__ import annotations - -from aiohttp_cors import CorsViewMixin # type: ignore +from aiohttp_cors import CorsViewMixin # type: ignore[import] from aiohttp.web import Request, StreamResponse, View from collections.abc import Awaitable, Callable from typing import Any, TypeVar @@ -89,7 +87,7 @@ class BaseView(View, CorsViewMixin): return validator @classmethod - async def get_permission(cls: type[BaseView], request: Request) -> UserAccess: + async def get_permission(cls, request: Request) -> UserAccess: """ retrieve user permission from the request @@ -168,7 +166,7 @@ class BaseView(View, CorsViewMixin): return await self.data_as_json(list_keys or []) # pylint: disable=not-callable,protected-access - async def head(self) -> StreamResponse: # type: ignore + async def head(self) -> StreamResponse: # type: ignore[return] """ HEAD method implementation based on the result of GET method @@ -181,7 +179,7 @@ class BaseView(View, CorsViewMixin): if get_method is not None: # there is a bug in pylint, see https://github.com/pylint-dev/pylint/issues/6005 response = await get_method() - response._body = b"" # type: ignore + response._body = b"" # type: ignore[assignment] return response self._raise_allowed_methods() diff --git a/src/ahriman/web/views/service/add.py b/src/ahriman/web/views/service/add.py index 581d8479..e74524fc 100644 --- a/src/ahriman/web/views/service/add.py +++ b/src/ahriman/web/views/service/add.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent diff --git a/src/ahriman/web/views/service/pgp.py b/src/ahriman/web/views/service/pgp.py index df4513e9..80f48fb0 100644 --- a/src/ahriman/web/views/service/pgp.py +++ b/src/ahriman/web/views/service/pgp.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response diff --git a/src/ahriman/web/views/service/rebuild.py b/src/ahriman/web/views/service/rebuild.py index 9b062cbd..7ee12b78 100644 --- a/src/ahriman/web/views/service/rebuild.py +++ b/src/ahriman/web/views/service/rebuild.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent diff --git a/src/ahriman/web/views/service/remove.py b/src/ahriman/web/views/service/remove.py index 664ef565..a3e7ac62 100644 --- a/src/ahriman/web/views/service/remove.py +++ b/src/ahriman/web/views/service/remove.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent diff --git a/src/ahriman/web/views/service/request.py b/src/ahriman/web/views/service/request.py index a5342dfa..7e5dd695 100644 --- a/src/ahriman/web/views/service/request.py +++ b/src/ahriman/web/views/service/request.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent diff --git a/src/ahriman/web/views/service/search.py b/src/ahriman/web/views/service/search.py index 2cd33010..85906767 100644 --- a/src/ahriman/web/views/service/search.py +++ b/src/ahriman/web/views/service/search.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response from collections.abc import Callable diff --git a/src/ahriman/web/views/service/update.py b/src/ahriman/web/views/service/update.py index da8da5e5..a164f563 100644 --- a/src/ahriman/web/views/service/update.py +++ b/src/ahriman/web/views/service/update.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPNoContent diff --git a/src/ahriman/web/views/status/logs.py b/src/ahriman/web/views/status/logs.py index 7cc5c07b..6d2a6694 100644 --- a/src/ahriman/web/views/status/logs.py +++ b/src/ahriman/web/views/status/logs.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response diff --git a/src/ahriman/web/views/status/package.py b/src/ahriman/web/views/status/package.py index 0813a428..23e3e3d5 100644 --- a/src/ahriman/web/views/status/package.py +++ b/src/ahriman/web/views/status/package.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response diff --git a/src/ahriman/web/views/status/packages.py b/src/ahriman/web/views/status/packages.py index 4a46f1e4..fb0b7ae6 100644 --- a/src/ahriman/web/views/status/packages.py +++ b/src/ahriman/web/views/status/packages.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPNoContent, Response, json_response diff --git a/src/ahriman/web/views/status/status.py b/src/ahriman/web/views/status/status.py index 29e214b2..e6269d18 100644 --- a/src/ahriman/web/views/status/status.py +++ b/src/ahriman/web/views/status/status.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response diff --git a/src/ahriman/web/views/user/login.py b/src/ahriman/web/views/user/login.py index ecaf6b04..35e1c59e 100644 --- a/src/ahriman/web/views/user/login.py +++ b/src/ahriman/web/views/user/login.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized diff --git a/src/ahriman/web/views/user/logout.py b/src/ahriman/web/views/user/logout.py index 24ab01ff..4e7e4ba9 100644 --- a/src/ahriman/web/views/user/logout.py +++ b/src/ahriman/web/views/user/logout.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -import aiohttp_apispec # type: ignore +import aiohttp_apispec # type: ignore[import] from aiohttp.web import HTTPFound, HTTPUnauthorized diff --git a/src/ahriman/web/web.py b/src/ahriman/web/web.py index f25f72a8..f9b8884a 100644 --- a/src/ahriman/web/web.py +++ b/src/ahriman/web/web.py @@ -163,7 +163,7 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw application.logger.info("setup debug panel") debug_enabled = configuration.getboolean("web", "debug", fallback=False) if debug_enabled: - import aiohttp_debugtoolbar # type: ignore + import aiohttp_debugtoolbar # type: ignore[import] aiohttp_debugtoolbar.setup(application, hosts=configuration.getlist("web", "debug_allowed_hosts", fallback=[]), check_host=configuration.getboolean("web", "debug_check_host", fallback=False)) diff --git a/tests/ahriman/application/handlers/test_handler_versions.py b/tests/ahriman/application/handlers/test_handler_versions.py index 44f55aed..735ffed3 100644 --- a/tests/ahriman/application/handlers/test_handler_versions.py +++ b/tests/ahriman/application/handlers/test_handler_versions.py @@ -15,7 +15,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc print_mock = mocker.patch("ahriman.core.formatters.Printer.print") Versions.run(args, "x86_64", configuration, report=False, unsafe=False) - application_mock.assert_called_once_with("ahriman", ("pacman", "s3", "web")) + application_mock.assert_called_once_with("ahriman") print_mock.assert_has_calls([MockCall(verbose=False, separator=" "), MockCall(verbose=False, separator=" ")]) @@ -23,7 +23,7 @@ def test_package_dependencies() -> None: """ must extract package dependencies """ - packages = Versions.package_dependencies("srcinfo") + packages = dict(Versions.package_dependencies("srcinfo")) assert packages assert packages.get("parse") is not None @@ -32,7 +32,7 @@ def test_package_dependencies_missing() -> None: """ must extract package dependencies even if some of them are missing """ - packages = Versions.package_dependencies("ahriman", ("docs", "pacman", "s3", "web")) + packages = dict(Versions.package_dependencies("ahriman")) assert packages assert packages.get("pyalpm") is not None assert packages.get("Sphinx") is None diff --git a/tests/ahriman/web/conftest.py b/tests/ahriman/web/conftest.py index eeb4885b..665bbdac 100644 --- a/tests/ahriman/web/conftest.py +++ b/tests/ahriman/web/conftest.py @@ -68,7 +68,7 @@ def schema_request(handler: Callable[..., Awaitable[Any]], *, location: str = "j Returns: Schema: request schema as set by the decorators """ - schemas: list[dict[str, Any]] = handler.__schemas__ # type: ignore + schemas: list[dict[str, Any]] = handler.__schemas__ # type: ignore[attr-defined] return next(schema["schema"] for schema in schemas if schema["put_into"] == location) @@ -84,7 +84,7 @@ def schema_response(handler: Callable[..., Awaitable[Any]], *, code: int = 200) Returns: Schema: response schema as set by the decorators """ - schemas: dict[int, Any] = handler.__apispec__["responses"] # type: ignore + schemas: dict[int, Any] = handler.__apispec__["responses"] # type: ignore[attr-defined] schema = schemas[code]["schema"] if callable(schema): schema = schema() diff --git a/tests/ahriman/web/views/api/test_views_api_swagger.py b/tests/ahriman/web/views/api/test_views_api_swagger.py index 80490b3c..5d39e977 100644 --- a/tests/ahriman/web/views/api/test_views_api_swagger.py +++ b/tests/ahriman/web/views/api/test_views_api_swagger.py @@ -1,22 +1,25 @@ import pytest from aiohttp.test_utils import TestClient +from pytest_mock import MockerFixture +from typing import Any from ahriman.models.user_access import UserAccess from ahriman.web.views.api.swagger import SwaggerView -def _client(client: TestClient) -> TestClient: +def _client(client: TestClient, mocker: MockerFixture) -> TestClient: """ - generate test client with docs + generate test client with docs. Thanks to deprecation, we can't change application state since it was run Args: client(TestClient): test client fixture + mocker(MockerFixture): mocker object Returns: TestClient: test client fixture with additional properties """ - client.app["swagger_dict"] = { + swagger_dict = { "paths": { "/api/v1/logout": { "get": { @@ -62,6 +65,14 @@ def _client(client: TestClient) -> TestClient: }, ], } + source = client.app.__getitem__ + + def getitem(name: str) -> Any: + if name == "swagger_dict": + return swagger_dict + return source(name) + + mocker.patch("aiohttp.web.Application.__getitem__", side_effect=getitem) return client @@ -75,11 +86,11 @@ async def test_get_permission() -> None: assert await SwaggerView.get_permission(request) == UserAccess.Unauthorized -async def test_get(client: TestClient) -> None: +async def test_get(client: TestClient, mocker: MockerFixture) -> None: """ must generate api-docs correctly """ - client = _client(client) + client = _client(client, mocker) response = await client.get("/api-docs/swagger.json") assert response.ok diff --git a/tox.ini b/tox.ini index ff76590f..d19b3ad3 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ dependencies = -e .[pacman,s3,web] project_name = ahriman [mypy] -flags = --implicit-reexport --strict --allow-untyped-decorators --allow-subclassing-any +flags = --implicit-reexport --strict --allow-untyped-decorators --allow-subclassing-any --python-version 3.11 [pytest] addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec @@ -57,5 +57,6 @@ commands = deps = {[tox]dependencies} -e .[tests] + typing_extensions commands = pytest {posargs}