type: use ClassVar decorator for class attributes

This commit is contained in:
Evgenii Alekseev 2025-03-09 15:43:27 +02:00
parent 6f57ed550b
commit f00b575641
66 changed files with 193 additions and 151 deletions

View File

@ -80,7 +80,7 @@ Again, the most checks can be performed by `tox` command, though some additional
>>> clazz = Clazz()
"""
CLAZZ_ATTRIBUTE = 42
CLAZZ_ATTRIBUTE: ClassVar[int] = 42
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""
@ -96,6 +96,7 @@ Again, the most checks can be performed by `tox` command, though some additional
* 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 `typing.Optional` (e.g. `str | None` instead of `Optional[str]`).
* `classmethod` should (almost) 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.
* Class attributes must be decorated as `ClassVar[...]`.
* Recommended order of function definitions in class:
```python

View File

@ -22,7 +22,7 @@ import logging
from collections.abc import Callable, Iterable
from multiprocessing import Pool
from typing import TypeVar
from typing import ClassVar, TypeVar
from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration
@ -58,8 +58,8 @@ class Handler:
>>> Add.execute(args)
"""
ALLOW_MULTI_ARCHITECTURE_RUN = True
arguments: list[Callable[[SubParserAction], argparse.ArgumentParser]]
ALLOW_MULTI_ARCHITECTURE_RUN: ClassVar[bool] = True
arguments: ClassVar[list[Callable[[SubParserAction], argparse.ArgumentParser]]]
@classmethod
def call(cls, args: argparse.Namespace, repository_id: RepositoryId) -> bool:

View File

@ -21,6 +21,7 @@ import argparse
from collections.abc import Callable, Iterable
from dataclasses import fields
from typing import ClassVar
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.alpm.remote import AUR, Official
@ -40,7 +41,7 @@ class Search(Handler):
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
SORT_FIELDS = {
SORT_FIELDS: ClassVar[set[str]] = {
field.name
for field in fields(AURPackage)
if field.default_factory is not list

View File

@ -21,6 +21,7 @@ import argparse
from pathlib import Path
from pwd import getpwuid
from typing import ClassVar
from urllib.parse import quote_plus as url_encode
from ahriman.application.application import Application
@ -46,9 +47,9 @@ class Setup(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
ARCHBUILD_COMMAND_PATH = Path("/") / "usr" / "bin" / "archbuild"
MIRRORLIST_PATH = Path("/") / "etc" / "pacman.d" / "mirrorlist"
SUDOERS_DIR_PATH = Path("/") / "etc" / "sudoers.d"
ARCHBUILD_COMMAND_PATH: ClassVar[Path] = Path("/") / "usr" / "bin" / "archbuild"
MIRRORLIST_PATH: ClassVar[Path] = Path("/") / "etc" / "pacman.d" / "mirrorlist"
SUDOERS_DIR_PATH: ClassVar[Path] = Path("/") / "etc" / "sudoers.d"
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,

View File

@ -23,6 +23,7 @@ import sys
from collections.abc import Generator
from importlib import metadata
from typing import ClassVar
from ahriman import __version__
from ahriman.application.handlers.handler import Handler, SubParserAction
@ -36,11 +37,11 @@ class Versions(Handler):
version handler
Attributes:
PEP423_PACKAGE_NAME(str): (class attribute) special regex for valid PEP423 package name
PEP423_PACKAGE_NAME(re.Pattern[str]): (class attribute) special regex for valid PEP423 package name
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
PEP423_PACKAGE_NAME = re.compile(r"^[A-Za-z0-9._-]+")
PEP423_PACKAGE_NAME: ClassVar[re.Pattern[str]] = re.compile(r"^[A-Za-z0-9._-]+")
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,

View File

@ -23,6 +23,7 @@ import shutil
from email.utils import parsedate_to_datetime
from pathlib import Path
from pyalpm import DB # type: ignore[import-not-found]
from typing import ClassVar
from urllib.parse import urlparse
from ahriman.core.configuration import Configuration
@ -41,7 +42,7 @@ class PacmanDatabase(SyncHttpClient):
sync_files_database(bool): sync files database
"""
LAST_MODIFIED_HEADER = "Last-Modified"
LAST_MODIFIED_HEADER: ClassVar[str] = "Last-Modified"
def __init__(self, database: DB, configuration: Configuration) -> None:
"""

View File

@ -34,14 +34,14 @@ class PkgbuildToken(StrEnum):
well-known tokens dictionary
Attributes:
ArrayEnds(PkgbuildToken): (class attribute) array ends token
ArrayStarts(PkgbuildToken): (class attribute) array starts token
Comma(PkgbuildToken): (class attribute) comma token
Comment(PkgbuildToken): (class attribute) comment token
FunctionDeclaration(PkgbuildToken): (class attribute) function declaration token
FunctionEnds(PkgbuildToken): (class attribute) function ends token
FunctionStarts(PkgbuildToken): (class attribute) function starts token
NewLine(PkgbuildToken): (class attribute) new line token
ArrayEnds(PkgbuildToken): array ends token
ArrayStarts(PkgbuildToken): array starts token
Comma(PkgbuildToken): comma token
Comment(PkgbuildToken): comment token
FunctionDeclaration(PkgbuildToken): function declaration token
FunctionEnds(PkgbuildToken): function ends token
FunctionStarts(PkgbuildToken): function starts token
NewLine(PkgbuildToken): new line token
"""
ArrayStarts = "("

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/>.
#
from typing import Any
from typing import Any, ClassVar
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote
@ -35,9 +35,9 @@ class AUR(Remote):
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
"""
DEFAULT_AUR_URL = "https://aur.archlinux.org"
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc"
DEFAULT_RPC_VERSION = "5"
DEFAULT_AUR_URL: ClassVar[str] = "https://aur.archlinux.org"
DEFAULT_RPC_URL: ClassVar[str] = f"{DEFAULT_AUR_URL}/rpc"
DEFAULT_RPC_VERSION: ClassVar[str] = "5"
@classmethod
def remote_git_url(cls, package_base: str, repository: str) -> str:

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/>.
#
from typing import Any
from typing import Any, ClassVar
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote
@ -36,10 +36,10 @@ class Official(Remote):
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
"""
DEFAULT_ARCHLINUX_GIT_URL = "https://gitlab.archlinux.org"
DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib"]
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
DEFAULT_ARCHLINUX_GIT_URL: ClassVar[str] = "https://gitlab.archlinux.org"
DEFAULT_ARCHLINUX_URL: ClassVar[str] = "https://archlinux.org"
DEFAULT_SEARCH_REPOSITORIES: ClassVar[list[str]] = ["Core", "Extra", "Multilib"]
DEFAULT_RPC_URL: ClassVar[str] = "https://archlinux.org/packages/search/json"
@classmethod
def remote_git_url(cls, package_base: str, repository: str) -> str:

View File

@ -21,6 +21,7 @@ import shutil
from collections.abc import Generator
from pathlib import Path
from typing import ClassVar
from ahriman.core.exceptions import CalledProcessError
from ahriman.core.log import LazyLogging
@ -42,9 +43,9 @@ class Sources(LazyLogging):
GITCONFIG(dict[str, str]): (class attribute) git config options to suppress annoying hints
"""
DEFAULT_BRANCH = "master" # default fallback branch
DEFAULT_COMMIT_AUTHOR = ("ahriman", "ahriman@localhost")
GITCONFIG = {
DEFAULT_BRANCH: ClassVar[str] = "master" # default fallback branch
DEFAULT_COMMIT_AUTHOR: ClassVar[tuple[str, str]] = ("ahriman", "ahriman@localhost")
GITCONFIG: ClassVar[dict[str, str]] = {
"init.defaultBranch": DEFAULT_BRANCH,
}

View File

@ -22,7 +22,7 @@ import shlex
import sys
from pathlib import Path
from typing import Any, Self
from typing import Any, ClassVar, Self
from ahriman.core.configuration.configuration_multi_dict import ConfigurationMultiDict
from ahriman.core.configuration.shell_interpolator import ShellInterpolator
@ -65,8 +65,8 @@ class Configuration(configparser.RawConfigParser):
"""
_LEGACY_ARCHITECTURE_SPECIFIC_SECTIONS = ["web"]
ARCHITECTURE_SPECIFIC_SECTIONS = ["alpm", "build", "sign"]
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
ARCHITECTURE_SPECIFIC_SECTIONS: ClassVar[list[str]] = ["alpm", "build", "sign"]
SYSTEM_CONFIGURATION_PATH: ClassVar[Path] = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
def __init__(self, allow_no_value: bool = False, allow_multi_key: bool = True) -> None:
"""

View File

@ -23,6 +23,7 @@ import sys
from collections.abc import Generator, Mapping, MutableMapping
from string import Template
from typing import ClassVar
from ahriman.core.configuration.shell_template import ShellTemplate
@ -32,7 +33,7 @@ class ShellInterpolator(configparser.Interpolation):
custom string interpolator, because we cannot use defaults argument due to config validation
"""
DATA_LINK_ESCAPE = "\x10"
DATA_LINK_ESCAPE: ClassVar[str] = "\x10"
@staticmethod
def _extract_variables(parser: MutableMapping[str, Mapping[str, str]], value: str,

View File

@ -28,9 +28,6 @@ class ShellTemplate(Template):
"""
extension to the default :class:`Template` class, which also adds additional tokens to braced regex and enables
bash expansion
Attributes:
braceidpattern(str): regular expression to match every character except for closing bracket
"""
braceidpattern = r"(?a:[_a-z0-9][^}]*)"

View File

@ -22,7 +22,6 @@ import contextlib
from functools import cached_property
from ahriman.core.configuration import Configuration
from ahriman.core.configuration.schema import ConfigurationSchema
from ahriman.core.status.web_client import WebClient
from ahriman.core.triggers import Trigger
from ahriman.models.repository_id import RepositoryId
@ -34,7 +33,7 @@ class DistributedSystem(Trigger, WebClient):
simple class to (un)register itself as a distributed worker
"""
CONFIGURATION_SCHEMA: ConfigurationSchema = {
CONFIGURATION_SCHEMA = {
"worker": {
"type": "dict",
"schema": {

View File

@ -17,6 +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 typing import ClassVar
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property
@ -31,7 +33,7 @@ class ConfigurationPrinter(StringPrinter):
values(dict[str, str]): configuration values dictionary
"""
HIDE_KEYS = [
HIDE_KEYS: ClassVar[list[str]] = [
"api_key", # telegram key
"client_secret", # oauth secret
"cookie_secret_key", # cookie secret key

View File

@ -17,6 +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 typing import ClassVar
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property
@ -26,10 +28,11 @@ class PackageStatsPrinter(StringPrinter):
print packages statistics
Attributes:
MAX_COUNT(int): (class attribute) maximum number of packages to print
events(dict[str, int]): map of package to its event frequency
"""
MAX_COUNT = 10
MAX_COUNT: ClassVar[int] = 10
def __init__(self, events: dict[str, int]) -> None:
"""

View File

@ -21,6 +21,7 @@ import logging
from logging.config import fileConfig
from pathlib import Path
from typing import ClassVar
from ahriman.core.configuration import Configuration
from ahriman.core.log.http_log_handler import HttpLogHandler
@ -38,9 +39,9 @@ class LogLoader:
DEFAULT_SYSLOG_DEVICE(Path): (class attribute) default path to syslog device
"""
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
DEFAULT_LOG_LEVEL = logging.DEBUG
DEFAULT_SYSLOG_DEVICE = Path("/") / "dev" / "log"
DEFAULT_LOG_FORMAT: ClassVar[str] = "[%(levelname)s %(asctime)s] [%(name)s]: %(message)s"
DEFAULT_LOG_LEVEL: ClassVar[int] = logging.DEBUG
DEFAULT_SYSLOG_DEVICE: ClassVar[Path] = Path("/") / "dev" / "log"
@staticmethod
def handler(selected: LogHandler | None) -> LogHandler:

View File

@ -17,6 +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 typing import ClassVar
from ahriman.core.configuration import Configuration
from ahriman.core.http import SyncHttpClient
from ahriman.core.report.jinja_template import JinjaTemplate
@ -39,8 +41,8 @@ class Telegram(Report, JinjaTemplate, SyncHttpClient):
template_type(str): template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown
"""
TELEGRAM_API_URL = "https://api.telegram.org"
TELEGRAM_MAX_CONTENT_LENGTH = 4096
TELEGRAM_API_URL: ClassVar[str] = "https://api.telegram.org"
TELEGRAM_MAX_CONTENT_LENGTH: ClassVar[int] = 4096
def __init__(self, repository_id: RepositoryId, configuration: Configuration, section: str) -> None:
"""

View File

@ -22,6 +22,7 @@ import itertools
from collections.abc import Callable, Generator
from pathlib import Path
from typing import ClassVar
from ahriman.core.utils import utcnow
from ahriman.models.pkgbuild_patch import PkgbuildPatch
@ -35,7 +36,7 @@ class PkgbuildGenerator:
PKGBUILD_STATIC_PROPERTIES(list[PkgbuildPatch]): (class attribute) list of default pkgbuild static properties
"""
PKGBUILD_STATIC_PROPERTIES = [
PKGBUILD_STATIC_PROPERTIES: ClassVar[list[PkgbuildPatch]] = [
PkgbuildPatch("pkgrel", "1"),
PkgbuildPatch("arch", ["any"]),
]

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from collections.abc import Callable
from typing import ClassVar
from ahriman.core.configuration import Configuration
from ahriman.core.configuration.schema import ConfigurationSchema
@ -56,8 +57,8 @@ class Trigger(LazyLogging):
>>> loader.on_result(Result(), [])
"""
CONFIGURATION_SCHEMA: ConfigurationSchema = {}
CONFIGURATION_SCHEMA_FALLBACK: str | None = None
CONFIGURATION_SCHEMA: ClassVar[ConfigurationSchema] = {}
CONFIGURATION_SCHEMA_FALLBACK: ClassVar[str | None] = None
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
"""

View File

@ -25,9 +25,9 @@ class Action(StrEnum):
base action enumeration
Attributes:
List(Action): (class attribute) list available values
Remove(Action): (class attribute) remove everything from local storage
Update(Action): (class attribute) update local storage or add to
List(Action): list available values
Remove(Action): remove everything from local storage
Update(Action): update local storage or add to
"""
List = "list"

View File

@ -27,10 +27,10 @@ class AuthSettings(StrEnum):
web authorization type
Attributes:
Disabled(AuthSettings): (class attribute) authorization is disabled
Configuration(AuthSettings): (class attribute) configuration based authorization
OAuth(AuthSettings): (class attribute) OAuth based provider
PAM(AuthSettings): (class attribute) PAM based provider
Disabled(AuthSettings): authorization is disabled
Configuration(AuthSettings): configuration based authorization
OAuth(AuthSettings): OAuth based provider
PAM(AuthSettings): PAM based provider
"""
Disabled = "disabled"

View File

@ -29,11 +29,11 @@ class BuildStatusEnum(StrEnum):
build status enumeration
Attributes:
Unknown(BuildStatusEnum): (class attribute) build status is unknown
Pending(BuildStatusEnum): (class attribute) package is out-of-dated and will be built soon
Building(BuildStatusEnum): (class attribute) package is building right now
Failed(BuildStatusEnum): (class attribute) package build failed
Success(BuildStatusEnum): (class attribute) package has been built without errors
Unknown(BuildStatusEnum): build status is unknown
Pending(BuildStatusEnum): package is out-of-dated and will be built soon
Building(BuildStatusEnum): package is building right now
Failed(BuildStatusEnum): package build failed
Success(BuildStatusEnum): package has been built without errors
"""
Unknown = "unknown"

View File

@ -28,10 +28,10 @@ class EventType(StrEnum):
predefined event types
Attributes:
PackageOutdated(EventType): (class attribute) package has been marked as out-of-date
PackageRemoved(EventType): (class attribute) package has been removed
PackageUpdateFailed(EventType): (class attribute) package update has been failed
PackageUpdated(EventType): (class attribute) package has been updated
PackageOutdated(EventType): package has been marked as out-of-date
PackageRemoved(EventType): package has been removed
PackageUpdateFailed(EventType): package update has been failed
PackageUpdated(EventType): package has been updated
"""
PackageOutdated = "package-outdated"

View File

@ -25,9 +25,9 @@ class LogHandler(StrEnum):
log handler as described by default configuration
Attributes:
Console(LogHandler): (class attribute) write logs to console
Syslog(LogHandler): (class attribute) write logs to syslog device /dev/null
Journald(LogHandler): (class attribute) write logs to journald directly
Console(LogHandler): write logs to console
Syslog(LogHandler): write logs to syslog device /dev/null
Journald(LogHandler): write logs to journald directly
"""
Console = "console"

View File

@ -32,13 +32,13 @@ class PackageSource(StrEnum):
package source for addition enumeration
Attributes:
Auto(PackageSource): (class attribute) automatically determine type of the source
Archive(PackageSource): (class attribute) source is a package archive
AUR(PackageSource): (class attribute) source is an AUR package for which it should search
Directory(PackageSource): (class attribute) source is a directory which contains packages
Local(PackageSource): (class attribute) source is locally stored PKGBUILD
Remote(PackageSource): (class attribute) source is remote (http, ftp etc...) link
Repository(PackageSource): (class attribute) source is official repository
Auto(PackageSource): automatically determine type of the source
Archive(PackageSource): source is a package archive
AUR(PackageSource): source is an AUR package for which it should search
Directory(PackageSource): source is a directory which contains packages
Local(PackageSource): source is locally stored PKGBUILD
Remote(PackageSource): source is remote (http, ftp etc...) link
Repository(PackageSource): source is official repository
Examples:
In case if source is unknown the :func:`resolve()` and the source

View File

@ -25,9 +25,9 @@ class PacmanSynchronization(IntEnum):
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(PacmanSynchronization): do not synchronize local database
Enabled(PacmanSynchronization): synchronize local database (same as pacman -Sy)
Force(PacmanSynchronization): force synchronize local database (same as pacman -Syy)
"""
Disabled = 0

View File

@ -21,7 +21,7 @@ from collections.abc import Iterator, Mapping
from dataclasses import dataclass
from io import StringIO
from pathlib import Path
from typing import Any, IO, Self
from typing import Any, ClassVar, IO, Self
from ahriman.core.alpm.pkgbuild_parser import PkgbuildParser, PkgbuildToken
from ahriman.core.exceptions import EncodeError
@ -40,7 +40,7 @@ class Pkgbuild(Mapping[str, Any]):
fields: dict[str, PkgbuildPatch]
DEFAULT_ENCODINGS = ["utf8", "latin-1"]
DEFAULT_ENCODINGS: ClassVar[list[str]] = ["utf8", "latin-1"]
@property
def variables(self) -> dict[str, str]:

View File

@ -27,13 +27,13 @@ class ReportSettings(StrEnum):
report targets enumeration
Attributes:
Disabled(ReportSettings): (class attribute) option which generates no report for testing purpose
HTML(ReportSettings): (class attribute) html report generation
Email(ReportSettings): (class attribute) email report generation
Console(ReportSettings): (class attribute) print result to console
Telegram(ReportSettings): (class attribute) markdown report to telegram channel
RSS(ReportSettings): (class attribute) RSS report generation
RemoteCall(ReportSettings): (class attribute) remote ahriman server call
Disabled(ReportSettings): option which generates no report for testing purpose
HTML(ReportSettings): html report generation
Email(ReportSettings): email report generation
Console(ReportSettings): print result to console
Telegram(ReportSettings): markdown report to telegram channel
RSS(ReportSettings): RSS report generation
RemoteCall(ReportSettings): remote ahriman server call
"""
Disabled = "disabled" # for testing purpose

View File

@ -20,7 +20,7 @@
from __future__ import annotations
from collections.abc import Callable, Iterable
from typing import Any, Self
from typing import Any, ClassVar, Self
from ahriman.models.package import Package
@ -33,7 +33,7 @@ class Result:
STATUS_PRIORITIES(list[str]): (class attribute) list of statues according to their priorities
"""
STATUS_PRIORITIES = [
STATUS_PRIORITIES: ClassVar[list[str]] = [
"failed",
"removed",
"updated",

View File

@ -27,9 +27,9 @@ class SignSettings(StrEnum):
sign targets enumeration
Attributes:
Disabled(SignSettings): (class attribute) option which generates no report for testing purpose
Packages(SignSettings): (class attribute) sign each package
Repository(SignSettings): (class attribute) sign repository database file
Disabled(SignSettings): option which generates no report for testing purpose
Packages(SignSettings): sign each package
Repository(SignSettings): sign repository database file
"""
Disabled = "disabled"

View File

@ -27,9 +27,9 @@ class SmtpSSLSettings(StrEnum):
SMTP SSL mode enumeration
Attributes:
Disabled(SmtpSSLSettings): (class attribute) no SSL enabled
SSL(SmtpSSLSettings): (class attribute) use SMTP_SSL instead of normal SMTP client
STARTTLS(SmtpSSLSettings): (class attribute) use STARTTLS in normal SMTP client
Disabled(SmtpSSLSettings): no SSL enabled
SSL(SmtpSSLSettings): use SMTP_SSL instead of normal SMTP client
STARTTLS(SmtpSSLSettings): use STARTTLS in normal SMTP client
"""
Disabled = "disabled"

View File

@ -27,11 +27,11 @@ class UploadSettings(StrEnum):
remote synchronization targets enumeration
Attributes:
Disabled(UploadSettings): (class attribute) no sync will be performed, required for testing purpose
Rsync(UploadSettings): (class attribute) sync via rsync
S3(UploadSettings): (class attribute) sync to Amazon S3
GitHub(UploadSettings): (class attribute) sync to GitHub releases page
RemoteService(UploadSettings): (class attribute) sync to another ahriman instance
Disabled(UploadSettings): no sync will be performed, required for testing purpose
Rsync(UploadSettings): sync via rsync
S3(UploadSettings): sync to Amazon S3
GitHub(UploadSettings): sync to GitHub releases page
RemoteService(UploadSettings): sync to another ahriman instance
"""
Disabled = "disabled" # for testing purpose

View File

@ -21,7 +21,7 @@ import bcrypt
from dataclasses import dataclass, replace
from secrets import token_urlsafe as generate_password
from typing import Self
from typing import ClassVar, Self
from ahriman.models.user_access import UserAccess
@ -69,7 +69,7 @@ class User:
packager_id: str | None = None
key: str | None = None
SUPPORTED_ALGOS = {"$2$", "$2a$", "$2x$", "$2y$", "$2b$"}
SUPPORTED_ALGOS: ClassVar[set[str]] = {"$2$", "$2a$", "$2x$", "$2y$", "$2b$"}
def __post_init__(self) -> None:
"""

View File

@ -27,11 +27,11 @@ class UserAccess(StrEnum):
web user access enumeration
Attributes:
Unauthorized(UserAccess): (class attribute) user can access specific resources which are marked as available
Unauthorized(UserAccess): user can access specific resources which are marked as available
without authorization (e.g. login, logout, static)
Read(UserAccess): (class attribute) user can read the page
Reporter(UserAccess): (class attribute) user can read everything and is able to perform some modifications
Full(UserAccess): (class attribute) user has full access
Read(UserAccess): user can read the page
Reporter(UserAccess): user can read everything and is able to perform some modifications
Full(UserAccess): user has full access
"""
Unauthorized = "unauthorized"

View File

@ -19,7 +19,7 @@
#
import aiohttp_jinja2
from typing import Any
from typing import Any, ClassVar
from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess
@ -35,7 +35,7 @@ class DocsView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Unauthorized
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/api-docs"]
@classmethod

View File

@ -19,6 +19,7 @@
#
from aiohttp.web import Response, json_response
from collections.abc import Callable
from typing import ClassVar
from ahriman.core.configuration import Configuration
from ahriman.core.utils import partition
@ -35,7 +36,7 @@ class SwaggerView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Unauthorized
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/api-docs/swagger.json"]
@classmethod

View File

@ -20,7 +20,7 @@
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Request, StreamResponse, View
from aiohttp_cors import CorsViewMixin # type: ignore[import-untyped]
from collections.abc import Awaitable, Callable
from typing import TypeVar
from typing import ClassVar, TypeVar
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
@ -46,8 +46,8 @@ class BaseView(View, CorsViewMixin):
ROUTES(list[str]): (class attribute) list of supported routes
"""
OPTIONS_PERMISSION = UserAccess.Unauthorized
ROUTES: list[str] = []
OPTIONS_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES: ClassVar[list[str]] = []
@property
def configuration(self) -> Configuration:

View File

@ -19,7 +19,7 @@
#
import aiohttp_jinja2
from typing import Any
from typing import Any, ClassVar
from ahriman.core.auth.helpers import authorized_userid
from ahriman.models.user_access import UserAccess
@ -48,7 +48,7 @@ class IndexView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Unauthorized
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/", "/index.html"]
@aiohttp_jinja2.template("build-status.jinja2")

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPFound, HTTPNotFound
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.views.base import BaseView
@ -31,7 +32,7 @@ class StaticView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Unauthorized
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/favicon.ico"]
async def get(self) -> None:

View File

@ -17,6 +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 typing import ClassVar
from ahriman.core.configuration import Configuration
@ -25,7 +27,7 @@ class StatusViewGuard:
helper for check if status routes are enabled
"""
ROUTES: list[str]
ROUTES: ClassVar[list[str]]
@classmethod
def routes(cls, configuration: Configuration) -> list[str]:

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from typing import ClassVar
from ahriman.models.event import Event
from ahriman.models.user_access import UserAccess
@ -35,7 +36,7 @@ class EventsView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = POST_PERMISSION = UserAccess.Full
GET_PERMISSION = POST_PERMISSION = UserAccess.Full # type: ClassVar[UserAccess]
ROUTES = ["/api/v1/events"]
@apidocs(

View File

@ -19,6 +19,7 @@
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from collections.abc import Callable
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.models.worker import Worker
@ -37,7 +38,7 @@ class WorkersView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
DELETE_PERMISSION = GET_PERMISSION = POST_PERMISSION = UserAccess.Full
DELETE_PERMISSION = GET_PERMISSION = POST_PERMISSION = UserAccess.Full # type: ClassVar[UserAccess]
ROUTES = ["/api/v1/distributed"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from typing import ClassVar
from ahriman.models.changes import Changes
from ahriman.models.user_access import UserAccess
@ -36,8 +37,8 @@ class ChangesView(StatusViewGuard, BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
POST_PERMISSION = UserAccess.Full
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/changes"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from typing import ClassVar
from ahriman.models.dependencies import Dependencies
from ahriman.models.user_access import UserAccess
@ -36,8 +37,8 @@ class DependenciesView(StatusViewGuard, BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
POST_PERMISSION = UserAccess.Full
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/dependencies"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from typing import ClassVar
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.utils import pretty_datetime
@ -39,8 +40,8 @@ class LogsView(StatusViewGuard, BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full
GET_PERMISSION = UserAccess.Reporter
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full # type: ClassVar[UserAccess]
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
ROUTES = ["/api/v1/packages/{package}/logs"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from typing import ClassVar
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.build_status import BuildStatusEnum
@ -40,8 +41,8 @@ class PackageView(StatusViewGuard, BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full
GET_PERMISSION = UserAccess.Read
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full # type: ClassVar[UserAccess]
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Read
ROUTES = ["/api/v1/packages/{package}"]
@apidocs(

View File

@ -21,6 +21,7 @@ import itertools
from aiohttp.web import HTTPNoContent, Response, json_response
from collections.abc import Callable
from typing import ClassVar
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
@ -40,8 +41,8 @@ class PackagesView(StatusViewGuard, BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Read
POST_PERMISSION = UserAccess.Full
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Read
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/packages"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -35,8 +36,8 @@ class PatchView(StatusViewGuard, BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
DELETE_PERMISSION = UserAccess.Full
GET_PERMISSION = UserAccess.Reporter
DELETE_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
ROUTES = ["/api/v1/packages/{package}/patches/{patch}"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from typing import ClassVar
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess
@ -36,8 +37,8 @@ class PatchesView(StatusViewGuard, BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
POST_PERMISSION = UserAccess.Full
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/patches"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from typing import ClassVar
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess
@ -34,7 +35,7 @@ class AddView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
POST_PERMISSION = UserAccess.Full
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/service/add"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -33,7 +34,7 @@ class LogsView(BaseView):
DELETE_PERMISSION(UserAccess): (class attribute) delete permissions of self
"""
DELETE_PERMISSION = UserAccess.Full
DELETE_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/service/logs"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -34,8 +35,8 @@ class PGPView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
POST_PERMISSION = UserAccess.Full
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/service/pgp"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPNotFound, Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -33,7 +34,7 @@ class ProcessView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
ROUTES = ["/api/v1/service/process/{process_id}"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -33,7 +34,7 @@ class RebuildView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
POST_PERMISSION = UserAccess.Full
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/service/rebuild"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -33,7 +34,7 @@ class RemoveView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
POST_PERMISSION = UserAccess.Full
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/service/remove"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from typing import ClassVar
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess
@ -34,7 +35,7 @@ class RequestView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
POST_PERMISSION = UserAccess.Reporter
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
ROUTES = ["/api/v1/service/request"]
@apidocs(

View File

@ -19,6 +19,7 @@
#
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from collections.abc import Callable
from typing import ClassVar
from ahriman.core.alpm.remote import AUR
from ahriman.models.aur_package import AURPackage
@ -36,7 +37,7 @@ class SearchView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
ROUTES = ["/api/v1/service/search"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -33,7 +34,7 @@ class UpdateView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
POST_PERMISSION = UserAccess.Full
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/service/update"]
@apidocs(

View File

@ -23,6 +23,7 @@ from aiohttp import BodyPartReader
from aiohttp.web import HTTPBadRequest, HTTPCreated
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import ClassVar
from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths
@ -40,7 +41,7 @@ class UploadView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
POST_PERMISSION = UserAccess.Full
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/service/upload"]
@classmethod

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import Response, json_response
from typing import ClassVar
from ahriman import __version__
from ahriman.models.user_access import UserAccess
@ -34,7 +35,7 @@ class InfoView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Unauthorized
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/api/v1/info"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -33,7 +34,7 @@ class RepositoriesView(BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Read
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Read
ROUTES = ["/api/v1/repositories"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from typing import ClassVar
from ahriman import __version__
from ahriman.models.build_status import BuildStatusEnum
@ -40,8 +41,8 @@ class StatusView(StatusViewGuard, BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Read
POST_PERMISSION = UserAccess.Full
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Read
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
ROUTES = ["/api/v1/status"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized
from typing import ClassVar
from ahriman.core.auth.helpers import remember
from ahriman.models.user_access import UserAccess
@ -35,7 +36,7 @@ class LoginView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = POST_PERMISSION = UserAccess.Unauthorized
GET_PERMISSION = POST_PERMISSION = UserAccess.Unauthorized # type: ClassVar[UserAccess]
ROUTES = ["/api/v1/login"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPFound, HTTPUnauthorized
from typing import ClassVar
from ahriman.core.auth.helpers import check_authorized, forget
from ahriman.models.user_access import UserAccess
@ -33,7 +34,7 @@ class LogoutView(BaseView):
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
POST_PERMISSION = UserAccess.Unauthorized
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/api/v1/logout"]
@apidocs(

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import Response, json_response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
@ -34,7 +35,7 @@ class LogsView(StatusViewGuard, BaseView):
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
ROUTES = ["/api/v2/packages/{package}/logs"]
@apidocs(