PEP-673 use Self as return type for classmethods (#94)

* PEP-673 use Self as return type for classmethods

* add dummy test file

* remove python3.10 compat
This commit is contained in:
Evgenii Alekseev 2023-05-04 03:28:08 +03:00 committed by GitHub
parent 9dc6d56a8d
commit 2ff56965d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 384 additions and 339 deletions

View File

@ -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

View File

@ -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

View File

@ -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: ...

View File

@ -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 \

View File

@ -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'

View File

@ -34,7 +34,6 @@ setup(
"inflection",
"passlib",
"requests",
"setuptools",
"srcinfo",
],
setup_requires=[

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -18,9 +18,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -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

View File

@ -17,12 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -62,8 +62,8 @@ class Auth(LazyLogging):
"""
return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#login-modal" style="text-decoration: none"><i class="bi bi-box-arrow-in-right"></i> login</button>"""
@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:
"""

View File

@ -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

View File

@ -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")

View File

@ -17,15 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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]:
"""

View File

@ -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:

View File

@ -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()):

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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)

View File

@ -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

View File

@ -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

View File

@ -17,10 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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:

View File

@ -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:
"""

View File

@ -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

View File

@ -17,10 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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)

View File

@ -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

View File

@ -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:
"""

View File

@ -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

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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 = [

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import boto3 # type: ignore
import boto3 # type: ignore[import]
import hashlib
import mimetypes

View File

@ -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:
"""

View File

@ -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

View File

@ -17,15 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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,

View File

@ -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

View File

@ -17,11 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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))

View File

@ -17,10 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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:

View File

@ -17,10 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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 {}

View File

@ -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)

View File

@ -17,12 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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,

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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

View File

@ -17,11 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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",

View File

@ -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

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -17,11 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import Application
from typing import Any

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_cors # type: ignore
import aiohttp_cors # type: ignore[import]
from aiohttp.web import Application

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_security # type: ignore
import aiohttp_security # type: ignore[import]
import socket
import types

View File

@ -17,9 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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()

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPNoContent

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPNoContent, Response, json_response

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore
import aiohttp_apispec # type: ignore[import]
from aiohttp.web import HTTPFound, HTTPUnauthorized

View File

@ -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))

View File

@ -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

View File

@ -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()

View File

@ -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