migrate docstrings from reST to google format

This commit is contained in:
Evgenii Alekseev 2022-04-16 21:13:17 +03:00
parent 0db619136d
commit 5b6ba721fe
168 changed files with 3271 additions and 1520 deletions

View File

@ -43,8 +43,12 @@ SubParserAction = TypeVar("SubParserAction", bound="argparse._SubParsersAction[a
def _formatter(prog: str) -> argparse.HelpFormatter: def _formatter(prog: str) -> argparse.HelpFormatter:
""" """
formatter for the help message formatter for the help message
:param prog: application name
:return: formatter used by default Args:
prog(str): application name
Returns:
argparse.HelpFormatter: formatter used by default
""" """
return argparse.ArgumentDefaultsHelpFormatter(prog, width=120) return argparse.ArgumentDefaultsHelpFormatter(prog, width=120)
@ -52,7 +56,9 @@ def _formatter(prog: str) -> argparse.HelpFormatter:
def _parser() -> argparse.ArgumentParser: def _parser() -> argparse.ArgumentParser:
""" """
command line parser generator command line parser generator
:return: command line parser for the application
Returns:
argparse.ArgumentParser: command line parser for the application
""" """
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH Linux ReposItory MANager", parser = argparse.ArgumentParser(prog="ahriman", description="ArcH Linux ReposItory MANager",
epilog="Argument list can also be read from file by using @ prefix.", epilog="Argument list can also be read from file by using @ prefix.",
@ -107,8 +113,12 @@ def _parser() -> argparse.ArgumentParser:
def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for AUR search subcommand add parser for AUR search subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("aur-search", aliases=["search"], help="search for package", parser = root.add_parser("aur-search", aliases=["search"], help="search for package",
description="search for package in AUR using API", formatter_class=_formatter) description="search for package in AUR using API", formatter_class=_formatter)
@ -126,8 +136,12 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for listing help subcommand add parser for listing help subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("help", help="show help message", parser = root.add_parser("help", help="show help message",
description="show help message for application or command and exit", description="show help message for application or command and exit",
@ -141,8 +155,12 @@ def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for listing unsafe commands add parser for listing unsafe commands
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands", parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
description="list unsafe commands as defined in default args", formatter_class=_formatter) description="list unsafe commands as defined in default args", formatter_class=_formatter)
@ -156,8 +174,12 @@ def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.Argument
def _set_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for key import subcommand add parser for key import subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("key-import", help="import PGP key", parser = root.add_parser("key-import", help="import PGP key",
description="import PGP key from public sources to the repository user", description="import PGP key from public sources to the repository user",
@ -175,8 +197,12 @@ def _set_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for package addition subcommand add parser for package addition subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("package-add", aliases=["add", "package-update"], help="add package", parser = root.add_parser("package-add", aliases=["add", "package-update"], help="add package",
description="add existing or new package to the build queue", description="add existing or new package to the build queue",
@ -204,8 +230,12 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for package removal subcommand add parser for package removal subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("package-remove", aliases=["remove"], help="remove package", parser = root.add_parser("package-remove", aliases=["remove"], help="remove package",
description="remove package from the repository", formatter_class=_formatter) description="remove package from the repository", formatter_class=_formatter)
@ -217,8 +247,12 @@ def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser
def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for package status subcommand add parser for package status subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("package-status", aliases=["status"], help="get package status", parser = root.add_parser("package-status", aliases=["status"], help="get package status",
description="request status of the package", description="request status of the package",
@ -237,8 +271,12 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
def _set_package_status_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_package_status_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for package status remove subcommand add parser for package status remove subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("package-status-remove", help="remove package status", parser = root.add_parser("package-status-remove", help="remove package status",
description="remove the package from the status page", description="remove the package from the status page",
@ -254,8 +292,12 @@ def _set_package_status_remove_parser(root: SubParserAction) -> argparse.Argumen
def _set_package_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_package_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for package status update subcommand add parser for package status update subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("package-status-update", aliases=["status-update"], help="update package status", parser = root.add_parser("package-status-update", aliases=["status-update"], help="update package status",
description="update package status on the status page", formatter_class=_formatter) description="update package status on the status page", formatter_class=_formatter)
@ -272,8 +314,12 @@ def _set_package_status_update_parser(root: SubParserAction) -> argparse.Argumen
def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for new patch subcommand add parser for new patch subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("patch-add", help="add patch set", description="create or update source patches", parser = root.add_parser("patch-add", help="add patch set", description="create or update source patches",
epilog="In order to add a patch set for the package you will need to clone " epilog="In order to add a patch set for the package you will need to clone "
@ -292,8 +338,12 @@ def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for list patches subcommand add parser for list patches subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("patch-list", help="list patch sets", parser = root.add_parser("patch-list", help="list patch sets",
description="list available patches for the package", formatter_class=_formatter) description="list available patches for the package", formatter_class=_formatter)
@ -306,8 +356,12 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for remove patches subcommand add parser for remove patches subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("patch-remove", help="remove patch set", description="remove patches for the package", parser = root.add_parser("patch-remove", help="remove patch set", description="remove patches for the package",
formatter_class=_formatter) formatter_class=_formatter)
@ -319,8 +373,12 @@ def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository backup subcommand add parser for repository backup subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-backup", help="backup repository data", parser = root.add_parser("repo-backup", help="backup repository data",
description="backup settings and database", formatter_class=_formatter) description="backup settings and database", formatter_class=_formatter)
@ -332,8 +390,12 @@ def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository check subcommand add parser for repository check subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-check", aliases=["check"], help="check for updates", parser = root.add_parser("repo-check", aliases=["check"], help="check for updates",
description="check for packages updates. Same as update --dry-run --no-manual", description="check for packages updates. Same as update --dry-run --no-manual",
@ -348,8 +410,12 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_clean_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository clean subcommand add parser for repository clean subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-clean", aliases=["clean"], help="clean local caches", parser = root.add_parser("repo-clean", aliases=["clean"], help="clean local caches",
description="remove local caches", description="remove local caches",
@ -368,8 +434,12 @@ def _set_repo_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_config_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_config_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for config subcommand add parser for config subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-config", aliases=["config"], help="dump configuration", parser = root.add_parser("repo-config", aliases=["config"], help="dump configuration",
description="dump configuration for the specified architecture", description="dump configuration for the specified architecture",
@ -381,8 +451,12 @@ def _set_repo_config_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository rebuild subcommand add parser for repository rebuild subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository", parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository",
description="force rebuild whole repository", formatter_class=_formatter) description="force rebuild whole repository", formatter_class=_formatter)
@ -403,8 +477,12 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for remove unknown packages subcommand add parser for remove unknown packages subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-remove-unknown", aliases=["remove-unknown"], help="remove unknown packages", parser = root.add_parser("repo-remove-unknown", aliases=["remove-unknown"], help="remove unknown packages",
description="remove packages which are missing in AUR and do not have local PKGBUILDs", description="remove packages which are missing in AUR and do not have local PKGBUILDs",
@ -418,8 +496,12 @@ def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentP
def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for report subcommand add parser for report subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-report", aliases=["report"], help="generate report", parser = root.add_parser("repo-report", aliases=["report"], help="generate report",
description="generate repository report according to current settings", description="generate repository report according to current settings",
@ -433,8 +515,12 @@ def _set_repo_report_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository restore subcommand add parser for repository restore subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-restore", help="restore repository data", parser = root.add_parser("repo-restore", help="restore repository data",
description="restore settings and database", formatter_class=_formatter) description="restore settings and database", formatter_class=_formatter)
@ -447,8 +533,12 @@ def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for setup subcommand add parser for setup subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-setup", aliases=["init", "repo-init", "setup"], help="initial service configuration", parser = root.add_parser("repo-setup", aliases=["init", "repo-init", "setup"], help="initial service configuration",
description="create initial service configuration, requires root", description="create initial service configuration, requires root",
@ -472,8 +562,12 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for sign subcommand add parser for sign subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-sign", aliases=["sign"], help="sign packages", parser = root.add_parser("repo-sign", aliases=["sign"], help="sign packages",
description="(re-)sign packages and repository database according to current settings", description="(re-)sign packages and repository database according to current settings",
@ -487,8 +581,12 @@ def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository status update subcommand add parser for repository status update subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-status-update", help="update repository status", parser = root.add_parser("repo-status-update", help="update repository status",
description="update repository status on the status page", formatter_class=_formatter) description="update repository status on the status page", formatter_class=_formatter)
@ -502,8 +600,12 @@ def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentPa
def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository sync subcommand add parser for repository sync subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-sync", aliases=["sync"], help="sync repository", parser = root.add_parser("repo-sync", aliases=["sync"], help="sync repository",
description="sync repository files to remote server according to current settings", description="sync repository files to remote server according to current settings",
@ -517,8 +619,12 @@ def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for repository update subcommand add parser for repository update subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("repo-update", aliases=["update"], help="update packages", parser = root.add_parser("repo-update", aliases=["update"], help="update packages",
description="check for packages updates and run build process if requested", description="check for packages updates and run build process if requested",
@ -537,8 +643,12 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for create user subcommand add parser for create user subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("user-add", help="create or update user", parser = root.add_parser("user-add", help="create or update user",
description="update user for web services with the given password and role. " description="update user for web services with the given password and role. "
@ -559,8 +669,12 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for user list subcommand add parser for user list subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("user-list", help="user known users and their access", parser = root.add_parser("user-list", help="user known users and their access",
description="list users from the user mapping and their roles", description="list users from the user mapping and their roles",
@ -576,8 +690,12 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for user removal subcommand add parser for user removal subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("user-remove", help="remove user", parser = root.add_parser("user-remove", help="remove user",
description="remove user from the user mapping and update the configuration", description="remove user from the user mapping and update the configuration",
@ -592,8 +710,12 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for web subcommand add parser for web subcommand
:param root: subparsers for the commands
:return: created argument parser Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
""" """
parser = root.add_parser("web", help="web server", description="start web server", formatter_class=_formatter) parser = root.add_parser("web", help="web server", description="start web server", formatter_class=_formatter)
parser.set_defaults(handler=handlers.Web, lock=None, no_report=True, parser=_parser) parser.set_defaults(handler=handlers.Web, lock=None, no_report=True, parser=_parser)

View File

@ -32,7 +32,9 @@ class Application(Packages, Repository):
def _finalize(self, result: Result) -> None: def _finalize(self, result: Result) -> None:
""" """
generate report and sync to remote server generate report and sync to remote server
:param result: build result
Args:
result(Result): build result
""" """
self.report([], result) self.report([], result)
self.sync([], result.success) self.sync([], result.success)
@ -40,7 +42,9 @@ class Application(Packages, Repository):
def _known_packages(self) -> Set[str]: def _known_packages(self) -> Set[str]:
""" """
load packages from repository and pacman repositories load packages from repository and pacman repositories
:return: list of known packages
Returns:
Set[str]: list of known packages
""" """
known_packages: Set[str] = set() known_packages: Set[str] = set()
# local set # local set

View File

@ -39,21 +39,27 @@ class Packages(Properties):
def _finalize(self, result: Result) -> None: def _finalize(self, result: Result) -> None:
""" """
generate report and sync to remote server generate report and sync to remote server
:param result: build result
Args:
result(Result): build result
""" """
raise NotImplementedError raise NotImplementedError
def _known_packages(self) -> Set[str]: def _known_packages(self) -> Set[str]:
""" """
load packages from repository and pacman repositories load packages from repository and pacman repositories
:return: list of known packages
Returns:
Set[str]: list of known packages
""" """
raise NotImplementedError raise NotImplementedError
def _add_archive(self, source: str, *_: Any) -> None: def _add_archive(self, source: str, *_: Any) -> None:
""" """
add package from archive add package from archive
:param source: path to package archive
Args:
source(str): path to package archive
""" """
local_path = Path(source) local_path = Path(source)
dst = self.repository.paths.packages / local_path.name dst = self.repository.paths.packages / local_path.name
@ -62,9 +68,11 @@ class Packages(Properties):
def _add_aur(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None: def _add_aur(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
""" """
add package from AUR add package from AUR
:param source: package base name
:param known_packages: list of packages which are known by the service Args:
:param without_dependencies: if set, dependency check will be disabled source(str): package base name
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
""" """
package = Package.load(source, PackageSource.AUR, self.repository.pacman, self.repository.aur_url) package = Package.load(source, PackageSource.AUR, self.repository.pacman, self.repository.aur_url)
@ -77,7 +85,9 @@ class Packages(Properties):
def _add_directory(self, source: str, *_: Any) -> None: def _add_directory(self, source: str, *_: Any) -> None:
""" """
add packages from directory add packages from directory
:param source: path to local directory
Args:
source(str): path to local directory
""" """
local_path = Path(source) local_path = Path(source)
for full_path in filter(package_like, local_path.iterdir()): for full_path in filter(package_like, local_path.iterdir()):
@ -86,9 +96,11 @@ class Packages(Properties):
def _add_local(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None: def _add_local(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
""" """
add package from local PKGBUILDs add package from local PKGBUILDs
:param source: path to directory with local source files
:param known_packages: list of packages which are known by the service Args:
:param without_dependencies: if set, dependency check will be disabled source(str): path to directory with local source files
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
""" """
package = Package.load(source, PackageSource.Local, self.repository.pacman, self.repository.aur_url) package = Package.load(source, PackageSource.Local, self.repository.pacman, self.repository.aur_url)
cache_dir = self.repository.paths.cache_for(package.base) cache_dir = self.repository.paths.cache_for(package.base)
@ -102,7 +114,9 @@ class Packages(Properties):
def _add_remote(self, source: str, *_: Any) -> None: def _add_remote(self, source: str, *_: Any) -> None:
""" """
add package from remote sources (e.g. HTTP) add package from remote sources (e.g. HTTP)
:param remote_url: remote URL to the package archive
Args:
source(str):
""" """
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it? dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
response = requests.get(source, stream=True) response = requests.get(source, stream=True)
@ -115,9 +129,11 @@ class Packages(Properties):
def _process_dependencies(self, local_path: Path, known_packages: Set[str], without_dependencies: bool) -> None: def _process_dependencies(self, local_path: Path, known_packages: Set[str], without_dependencies: bool) -> None:
""" """
process package dependencies process package dependencies
:param local_path: path to local package sources (i.e. cloned AUR repository)
:param known_packages: list of packages which are known by the service Args:
:param without_dependencies: if set, dependency check will be disabled local_path(Path): path to local package sources (i.e. cloned AUR repository)
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
""" """
if without_dependencies: if without_dependencies:
return return
@ -128,9 +144,11 @@ class Packages(Properties):
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None: def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:
""" """
add packages for the next build add packages for the next build
:param names: list of package bases to add
:param source: package source to add Args:
:param without_dependencies: if set, dependency check will be disabled names(Iterable[str]): list of package bases to add
source(PackageSource): package source to add
without_dependencies(bool): if set, dependency check will be disabled
""" """
known_packages = self._known_packages() # speedup dependencies processing known_packages = self._known_packages() # speedup dependencies processing
@ -142,7 +160,9 @@ class Packages(Properties):
def remove(self, names: Iterable[str]) -> None: def remove(self, names: Iterable[str]) -> None:
""" """
remove packages from repository remove packages from repository
:param names: list of packages (either base or name) to remove
Args:
names(Iterable[str]): list of packages (either base or name) to remove
""" """
self.repository.process_remove(names) self.repository.process_remove(names)
self._finalize(Result()) self._finalize(Result())

View File

@ -27,20 +27,24 @@ from ahriman.core.repository import Repository
class Properties: class Properties:
""" """
application base properties class application base properties class
:ivar architecture: repository architecture
:ivar configuration: configuration instance Attributes:
:ivar database: database instance architecture(str): repository architecture
:ivar logger: application logger configuration(Configuration): configuration instance
:ivar repository: repository instance database(SQLite): database instance
logger(logging.Logger): application logger
repository(Repository): repository instance
""" """
def __init__(self, architecture: str, configuration: Configuration, no_report: bool, unsafe: bool) -> None: def __init__(self, architecture: str, configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
self.logger = logging.getLogger("root") self.logger = logging.getLogger("root")
self.configuration = configuration self.configuration = configuration

View File

@ -38,17 +38,21 @@ class Repository(Properties):
def _finalize(self, result: Result) -> None: def _finalize(self, result: Result) -> None:
""" """
generate report and sync to remote server generate report and sync to remote server
:param result: build result
Args:
result(Result): build result
""" """
raise NotImplementedError raise NotImplementedError
def clean(self, cache: bool, chroot: bool, manual: bool, packages: bool) -> None: def clean(self, cache: bool, chroot: bool, manual: bool, packages: bool) -> None:
""" """
run all clean methods. Warning: some functions might not be available under non-root run all clean methods. Warning: some functions might not be available under non-root
:param cache: clear directory with package caches
:param chroot: clear build chroot Args:
:param manual: clear directory with manually added packages cache(bool): clear directory with package caches
:param packages: clear directory with built packages chroot(bool): clear build chroot
manual(bool): clear directory with manually added packages
packages(bool): clear directory with built packages
""" """
if cache: if cache:
self.repository.clear_cache() self.repository.clear_cache()
@ -62,8 +66,10 @@ class Repository(Properties):
def report(self, target: Iterable[str], result: Result) -> None: def report(self, target: Iterable[str], result: Result) -> None:
""" """
generate report generate report
:param target: list of targets to run (e.g. html)
:param result: build result Args:
target(Iterable[str]): list of targets to run (e.g. html)
result(Result): build result
""" """
targets = target or None targets = target or None
self.repository.process_report(targets, result) self.repository.process_report(targets, result)
@ -71,7 +77,9 @@ class Repository(Properties):
def sign(self, packages: Iterable[str]) -> None: def sign(self, packages: Iterable[str]) -> None:
""" """
sign packages and repository sign packages and repository
:param packages: only sign specified packages
Args:
packages(Iterable[str]): only sign specified packages
""" """
# copy to prebuilt directory # copy to prebuilt directory
for package in self.repository.packages(): for package in self.repository.packages():
@ -94,8 +102,10 @@ class Repository(Properties):
def sync(self, target: Iterable[str], built_packages: Iterable[Package]) -> None: def sync(self, target: Iterable[str], built_packages: Iterable[Package]) -> None:
""" """
sync to remote server sync to remote server
:param target: list of targets to run (e.g. s3)
:param built_packages: list of packages which has just been built Args:
target(Iterable[str]): list of targets to run (e.g. s3)
built_packages(Iterable[Package]): list of packages which has just been built
""" """
targets = target or None targets = target or None
self.repository.process_sync(targets, built_packages) self.repository.process_sync(targets, built_packages)
@ -103,7 +113,9 @@ class Repository(Properties):
def unknown(self) -> List[str]: def unknown(self) -> List[str]:
""" """
get packages which were not found in AUR get packages which were not found in AUR
:return: unknown package archive list
Returns:
List[str]: unknown package archive list
""" """
def has_local(probe: Package) -> bool: def has_local(probe: Package) -> bool:
cache_dir = self.repository.paths.cache_for(probe.base) cache_dir = self.repository.paths.cache_for(probe.base)
@ -135,7 +147,12 @@ class Repository(Properties):
def update(self, updates: Iterable[Package]) -> Result: def update(self, updates: Iterable[Package]) -> Result:
""" """
run package updates run package updates
:param updates: list of packages to update
Args:
updates(Iterable[Package]): list of packages to update
Returns:
Result: update result
""" """
def process_update(paths: Iterable[Path], result: Result) -> None: def process_update(paths: Iterable[Path], result: Result) -> None:
if not paths: if not paths:
@ -162,13 +179,18 @@ class Repository(Properties):
log_fn: Callable[[str], None]) -> List[Package]: log_fn: Callable[[str], None]) -> List[Package]:
""" """
get list of packages to run update process get list of packages to run update process
:param filter_packages: do not check every package just specified in the list
:param no_aur: do not check for aur updates Args:
:param no_local: do not check local packages for updates filter_packages(Iterable[str]): do not check every package just specified in the list
:param no_manual: do not check for manual updates no_aur(bool): do not check for aur updates
:param no_vcs: do not check VCS packages no_local(bool): do not check local packages for updates
:param log_fn: logger function to log updates no_manual(bool): do not check for manual updates
:return: list of out-of-dated packages no_vcs(bool): do not check VCS packages
log_fn(Callable[[str]): logger function to log updates
None]:
Returns:
List[Package]: list of out-of-dated packages
""" """
updates = {} updates = {}

View File

@ -36,11 +36,13 @@ class Add(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
application.add(args.package, args.source, args.without_dependencies) application.add(args.package, args.source, args.without_dependencies)

View File

@ -41,11 +41,13 @@ class Backup(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
backup_paths = Backup.get_paths(configuration) backup_paths = Backup.get_paths(configuration)
with TarFile(args.path, mode="w") as archive: # well we don't actually use compression with TarFile(args.path, mode="w") as archive: # well we don't actually use compression
@ -56,8 +58,12 @@ class Backup(Handler):
def get_paths(configuration: Configuration) -> Set[Path]: def get_paths(configuration: Configuration) -> Set[Path]:
""" """
extract paths to backup extract paths to backup
:param configuration: configuration instance
:return: map of the filesystem paths Args:
configuration(Configuration): configuration instance
Returns:
Set[Path]: map of the filesystem paths
""" """
paths = set(configuration.include.glob("*.ini")) paths = set(configuration.include.glob("*.ini"))

View File

@ -36,11 +36,13 @@ class Clean(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
Application(architecture, configuration, no_report, unsafe).clean( Application(architecture, configuration, no_report, unsafe).clean(
args.cache, args.chroot, args.manual, args.packages) args.cache, args.chroot, args.manual, args.packages)

View File

@ -38,11 +38,13 @@ class Dump(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
dump = configuration.dump() dump = configuration.dump()
for section, values in sorted(dump.items()): for section, values in sorted(dump.items()):

View File

@ -34,8 +34,10 @@ from ahriman.models.repository_paths import RepositoryPaths
class Handler: class Handler:
""" """
base handler class for command callbacks base handler class for command callbacks
:cvar ALLOW_AUTO_ARCHITECTURE_RUN: allow to define architecture from existing repositories
:cvar ALLOW_MULTI_ARCHITECTURE_RUN: allow to run with multiple architectures Attributes:
ALLOW_AUTO_ARCHITECTURE_RUN(bool): (class attribute) allow defining architecture from existing repositories
ALLOW_MULTI_ARCHITECTURE_RUN(bool): (class attribute) allow running with multiple architectures
""" """
ALLOW_AUTO_ARCHITECTURE_RUN = True ALLOW_AUTO_ARCHITECTURE_RUN = True
@ -45,8 +47,12 @@ class Handler:
def architectures_extract(cls: Type[Handler], args: argparse.Namespace) -> List[str]: def architectures_extract(cls: Type[Handler], args: argparse.Namespace) -> List[str]:
""" """
get known architectures get known architectures
:param args: command line args
:return: list of architectures for which tree is created Args:
args(argparse.Namespace): command line args
Returns:
List[str]: list of architectures for which tree is created
""" """
if not cls.ALLOW_AUTO_ARCHITECTURE_RUN and args.architecture is None: if not cls.ALLOW_AUTO_ARCHITECTURE_RUN and args.architecture is None:
# for some parsers (e.g. config) we need to run with specific architecture # for some parsers (e.g. config) we need to run with specific architecture
@ -69,9 +75,13 @@ class Handler:
def call(cls: Type[Handler], args: argparse.Namespace, architecture: str) -> bool: def call(cls: Type[Handler], args: argparse.Namespace, architecture: str) -> bool:
""" """
additional function to wrap all calls for multiprocessing library additional function to wrap all calls for multiprocessing library
:param args: command line args
:param architecture: repository architecture Args:
:return: True on success, False otherwise args(argparse.Namespace): command line args
architecture(str): repository architecture
Returns:
bool: True on success, False otherwise
""" """
try: try:
configuration = Configuration.from_path(args.configuration, architecture, args.quiet) configuration = Configuration.from_path(args.configuration, architecture, args.quiet)
@ -89,8 +99,12 @@ class Handler:
def execute(cls: Type[Handler], args: argparse.Namespace) -> int: def execute(cls: Type[Handler], args: argparse.Namespace) -> int:
""" """
execute function for all aru execute function for all aru
:param args: command line args
:return: 0 on success, 1 otherwise Args:
args(argparse.Namespace): command line args
Returns:
int: 0 on success, 1 otherwise
""" """
architectures = cls.architectures_extract(args) architectures = cls.architectures_extract(args)
@ -112,11 +126,13 @@ class Handler:
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
raise NotImplementedError raise NotImplementedError
@ -124,8 +140,10 @@ class Handler:
def check_if_empty(enabled: bool, predicate: bool) -> None: def check_if_empty(enabled: bool, predicate: bool) -> None:
""" """
check condition and flag and raise ExitCode exception in case if it is enabled and condition match check condition and flag and raise ExitCode exception in case if it is enabled and condition match
:param enabled: if False no check will be performed
:param predicate: indicates condition on which exception should be thrown Args:
enabled(bool): if False no check will be performed
predicate(bool): indicates condition on which exception should be thrown
""" """
if enabled and predicate: if enabled and predicate:
raise ExitCode() raise ExitCode()

View File

@ -37,11 +37,13 @@ class Help(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
parser: argparse.ArgumentParser = args.parser() parser: argparse.ArgumentParser = args.parser()
if args.command is None: if args.command is None:

View File

@ -38,11 +38,13 @@ class KeyImport(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
Application(architecture, configuration, no_report, unsafe).repository.sign.key_import( Application(architecture, configuration, no_report, unsafe).repository.sign.key_import(
args.key_server, args.key) args.key_server, args.key)

View File

@ -42,11 +42,13 @@ class Patch(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
@ -61,9 +63,11 @@ class Patch(Handler):
def patch_set_create(application: Application, sources_dir: str, track: List[str]) -> None: def patch_set_create(application: Application, sources_dir: str, track: List[str]) -> None:
""" """
create patch set for the package base create patch set for the package base
:param application: application instance
:param sources_dir: path to directory with the package sources Args:
:param track: track files which match the glob before creating the patch application(Application): application instance
sources_dir(str): path to directory with the package sources
track(List[str]): track files which match the glob before creating the patch
""" """
package = Package.load(sources_dir, PackageSource.Local, application.repository.pacman, package = Package.load(sources_dir, PackageSource.Local, application.repository.pacman,
application.repository.aur_url) application.repository.aur_url)
@ -74,9 +78,11 @@ class Patch(Handler):
def patch_set_list(application: Application, package_base: Optional[str], exit_code: bool) -> None: def patch_set_list(application: Application, package_base: Optional[str], exit_code: bool) -> None:
""" """
list patches available for the package base list patches available for the package base
:param application: application instance
:param package_base: package base Args:
:param exit_code: raise ExitCode on empty search result application(Application): application instance
package_base(Optional[str]): package base
exit_code(bool): raise ExitCode on empty search result
""" """
patches = application.database.patches_list(package_base) patches = application.database.patches_list(package_base)
Patch.check_if_empty(exit_code, not patches) Patch.check_if_empty(exit_code, not patches)
@ -89,7 +95,9 @@ class Patch(Handler):
def patch_set_remove(application: Application, package_base: str) -> None: def patch_set_remove(application: Application, package_base: str) -> None:
""" """
remove patch set for the package base remove patch set for the package base
:param application: application instance
:param package_base: package base Args:
application(Application): application instance
package_base(str): package base
""" """
application.database.patches_remove(package_base) application.database.patches_remove(package_base)

View File

@ -38,11 +38,13 @@ class Rebuild(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
depends_on = set(args.depends_on) if args.depends_on else None depends_on = set(args.depends_on) if args.depends_on else None
@ -65,7 +67,11 @@ class Rebuild(Handler):
def extract_packages(application: Application) -> List[Package]: def extract_packages(application: Application) -> List[Package]:
""" """
extract packages from database file extract packages from database file
:param application: application instance
:return: list of packages which were stored in database Args:
application(Application): application instance
Returns:
List[Package]: list of packages which were stored in database
""" """
return [package for (package, _) in application.database.packages_get()] return [package for (package, _) in application.database.packages_get()]

View File

@ -36,10 +36,12 @@ class Remove(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
Application(architecture, configuration, no_report, unsafe).remove(args.package) Application(architecture, configuration, no_report, unsafe).remove(args.package)

View File

@ -37,11 +37,13 @@ class RemoveUnknown(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
unknown_packages = application.unknown() unknown_packages = application.unknown()

View File

@ -37,10 +37,12 @@ class Report(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
Application(architecture, configuration, no_report, unsafe).report(args.target, Result()) Application(architecture, configuration, no_report, unsafe).report(args.target, Result())

View File

@ -38,11 +38,13 @@ class Restore(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
with TarFile(args.path) as archive: with TarFile(args.path) as archive:
archive.extractall(path=args.output) archive.extractall(path=args.output)

View File

@ -34,7 +34,9 @@ from ahriman.models.aur_package import AURPackage
class Search(Handler): class Search(Handler):
""" """
packages search handler packages search handler
:cvar SORT_FIELDS: allowed fields to sort the package list
Attributes:
SORT_FIELDS(Set[str]): (class attribute) allowed fields to sort the package list
""" """
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@ -45,11 +47,13 @@ class Search(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
official_packages_list = Official.multisearch(*args.search) official_packages_list = Official.multisearch(*args.search)
aur_packages_list = AUR.multisearch(*args.search) aur_packages_list = AUR.multisearch(*args.search)
@ -64,9 +68,13 @@ class Search(Handler):
def sort(packages: Iterable[AURPackage], sort_by: str) -> List[AURPackage]: def sort(packages: Iterable[AURPackage], sort_by: str) -> List[AURPackage]:
""" """
sort package list by specified field sort package list by specified field
:param packages: packages list to sort
:param sort_by: AUR package field name to sort by Args:
:return: sorted list for packages packages(Iterable[AURPackage]): packages list to sort
sort_by(str): AUR package field name to sort by
Returns:
List[AURPackage]: sorted list for packages
""" """
if sort_by not in Search.SORT_FIELDS: if sort_by not in Search.SORT_FIELDS:
raise InvalidOption(sort_by) raise InvalidOption(sort_by)

View File

@ -31,10 +31,12 @@ from ahriman.models.repository_paths import RepositoryPaths
class Setup(Handler): class Setup(Handler):
""" """
setup handler setup handler
:cvar ARCHBUILD_COMMAND_PATH: default devtools command
:cvar BIN_DIR_PATH: directory for custom binaries Attributes:
:cvar MIRRORLIST_PATH: path to pacman default mirrorlist (used by multilib repository) ARCHBUILD_COMMAND_PATH(Path): (class attribute) default devtools command
:cvar SUDOERS_PATH: path to sudoers.d include configuration BIN_DIR_PATH(Path): (class attribute) directory for custom binaries
MIRRORLIST_PATH(Path): (class attribute) path to pacman default mirrorlist (used by multilib repository)
SUDOERS_PATH(Path): (class attribute) path to sudoers.d include configuration
""" """
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
@ -49,11 +51,13 @@ class Setup(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include) Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include)
configuration.reload() configuration.reload()
@ -72,9 +76,13 @@ class Setup(Handler):
def build_command(prefix: str, architecture: str) -> Path: def build_command(prefix: str, architecture: str) -> Path:
""" """
generate build command name generate build command name
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture Args:
:return: valid devtools command name prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
Returns:
Path: valid devtools command name
""" """
return Setup.BIN_DIR_PATH / f"{prefix}-{architecture}-build" return Setup.BIN_DIR_PATH / f"{prefix}-{architecture}-build"
@ -83,10 +91,12 @@ class Setup(Handler):
include_path: Path) -> None: include_path: Path) -> None:
""" """
create service specific configuration create service specific configuration
:param args: command line args
:param architecture: repository architecture Args:
:param repository: repository name args(argparse.Namespace): command line args
:param include_path: path to directory with configuration includes architecture(str): repository architecture
repository(str): repository name
include_path(Path): path to directory with configuration includes
""" """
configuration = Configuration() configuration = Configuration()
@ -114,12 +124,14 @@ class Setup(Handler):
no_multilib: bool, repository: str, paths: RepositoryPaths) -> None: no_multilib: bool, repository: str, paths: RepositoryPaths) -> None:
""" """
create configuration for devtools based on `source` configuration create configuration for devtools based on `source` configuration
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture Args:
:param source: path to source configuration file prefix(str): command prefix in {prefix}-{architecture}-build
:param no_multilib: do not add multilib repository architecture(str): repository architecture
:param repository: repository name source(Path): path to source configuration file
:param paths: repository paths instance no_multilib(bool): do not add multilib repository
repository(str): repository name
paths(RepositoryPaths): repository paths instance
""" """
configuration = Configuration() configuration = Configuration()
# preserve case # preserve case
@ -149,8 +161,10 @@ class Setup(Handler):
def configuration_create_makepkg(packager: str, paths: RepositoryPaths) -> None: def configuration_create_makepkg(packager: str, paths: RepositoryPaths) -> None:
""" """
create configuration for makepkg create configuration for makepkg
:param packager: packager identifier (e.g. name, email)
:param paths: repository paths instance Args:
packager(str): packager identifier (e.g. name, email)
paths(RepositoryPaths): repository paths instance
""" """
(paths.root / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8") (paths.root / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8")
@ -158,8 +172,10 @@ class Setup(Handler):
def configuration_create_sudo(prefix: str, architecture: str) -> None: def configuration_create_sudo(prefix: str, architecture: str) -> None:
""" """
create configuration to run build command with sudo without password create configuration to run build command with sudo without password
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture Args:
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
""" """
command = Setup.build_command(prefix, architecture) command = Setup.build_command(prefix, architecture)
Setup.SUDOERS_PATH.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n", encoding="utf8") Setup.SUDOERS_PATH.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n", encoding="utf8")
@ -169,8 +185,10 @@ class Setup(Handler):
def executable_create(prefix: str, architecture: str) -> None: def executable_create(prefix: str, architecture: str) -> None:
""" """
create executable for the service create executable for the service
:param prefix: command prefix in {prefix}-{architecture}-build
:param architecture: repository architecture Args:
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
""" """
command = Setup.build_command(prefix, architecture) command = Setup.build_command(prefix, architecture)
command.unlink(missing_ok=True) command.unlink(missing_ok=True)

View File

@ -36,10 +36,12 @@ class Sign(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
Application(architecture, configuration, no_report, unsafe).sign(args.package) Application(architecture, configuration, no_report, unsafe).sign(args.package)

View File

@ -42,11 +42,13 @@ class Status(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
# we are using reporter here # we are using reporter here
client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter

View File

@ -39,11 +39,13 @@ class StatusUpdate(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
# we are using reporter here # we are using reporter here
client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter

View File

@ -28,7 +28,7 @@ from ahriman.core.configuration import Configuration
class Sync(Handler): class Sync(Handler):
""" """
remove sync handler remote sync handler
""" """
@classmethod @classmethod
@ -36,10 +36,12 @@ class Sync(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
Application(architecture, configuration, no_report, unsafe).sync(args.target, []) Application(architecture, configuration, no_report, unsafe).sync(args.target, [])

View File

@ -40,11 +40,13 @@ class UnsafeCommands(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
parser = args.parser() parser = args.parser()
unsafe_commands = UnsafeCommands.get_unsafe_commands(parser) unsafe_commands = UnsafeCommands.get_unsafe_commands(parser)
@ -58,9 +60,11 @@ class UnsafeCommands(Handler):
def check_unsafe(command: str, unsafe_commands: List[str], parser: argparse.ArgumentParser) -> None: def check_unsafe(command: str, unsafe_commands: List[str], parser: argparse.ArgumentParser) -> None:
""" """
check if command is unsafe check if command is unsafe
:param command: command to check
:param unsafe_commands: list of unsafe commands Args:
:param parser: generated argument parser command(str): command to check
unsafe_commands(List[str]): list of unsafe commands
parser(argparse.ArgumentParser): generated argument parser
""" """
args = parser.parse_args(shlex.split(command)) args = parser.parse_args(shlex.split(command))
if args.command in unsafe_commands: if args.command in unsafe_commands:
@ -70,8 +74,12 @@ class UnsafeCommands(Handler):
def get_unsafe_commands(parser: argparse.ArgumentParser) -> List[str]: def get_unsafe_commands(parser: argparse.ArgumentParser) -> List[str]:
""" """
extract unsafe commands from argument parser extract unsafe commands from argument parser
:param parser: generated argument parser
:return: list of commands with default unsafe flag Args:
parser(argparse.ArgumentParser): generated argument parser
Returns:
List[str]: list of commands with default unsafe flag
""" """
# pylint: disable=protected-access # pylint: disable=protected-access
subparser = next(action for action in parser._actions if isinstance(action, argparse._SubParsersAction)) subparser = next(action for action in parser._actions if isinstance(action, argparse._SubParsersAction))

View File

@ -36,11 +36,13 @@ class Update(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
@ -56,9 +58,13 @@ class Update(Handler):
def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]: def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]:
""" """
package updates log function package updates log function
:param application: application instance
:param dry_run: do not perform update itself Args:
:return: in case if dry_run is set it will return print, logger otherwise application(Application): application instance
dry_run(bool): do not perform update itself
Returns:
Callable[[str],None]: in case if dry_run is set it will return print, logger otherwise
""" """
def inner(line: str) -> None: def inner(line: str) -> None:
return print(line) if dry_run else application.logger.info(line) return print(line) if dry_run else application.logger.info(line)

View File

@ -43,11 +43,13 @@ class User(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
database = SQLite.load(configuration) database = SQLite.load(configuration)
@ -72,11 +74,13 @@ class User(Handler):
as_service_user: bool, secure: bool) -> None: as_service_user: bool, secure: bool) -> None:
""" """
enable configuration if it has been disabled enable configuration if it has been disabled
:param configuration: configuration instance
:param user: user descriptor Args:
:param salt: password hash salt configuration(Configuration): configuration instance
:param as_service_user: add user as service user, also set password and user to configuration user(MUser): user descriptor
:param secure: if true then set file permissions to 0o600 salt(str): password hash salt
as_service_user(bool): add user as service user, also set password and user to configuration
secure(bool): if true then set file permissions to 0o600
""" """
configuration.set_option("auth", "salt", salt) configuration.set_option("auth", "salt", salt)
if as_service_user: if as_service_user:
@ -88,8 +92,12 @@ class User(Handler):
def configuration_get(include_path: Path) -> Configuration: def configuration_get(include_path: Path) -> Configuration:
""" """
create configuration instance create configuration instance
:param include_path: path to directory with configuration includes
:return: configuration instance. In case if there are local settings they will be loaded Args:
include_path(Path): path to directory with configuration includes
Returns:
Configuration: configuration instance. In case if there are local settings they will be loaded
""" """
target = include_path / "auth.ini" target = include_path / "auth.ini"
configuration = Configuration() configuration = Configuration()
@ -103,8 +111,10 @@ class User(Handler):
def configuration_write(configuration: Configuration, secure: bool) -> None: def configuration_write(configuration: Configuration, secure: bool) -> None:
""" """
write configuration file write configuration file
:param configuration: configuration instance
:param secure: if true then set file permissions to 0o600 Args:
configuration(Configuration): configuration instance
secure(bool): if true then set file permissions to 0o600
""" """
path, _ = configuration.check_loaded() path, _ = configuration.check_loaded()
with path.open("w") as ahriman_configuration: with path.open("w") as ahriman_configuration:
@ -116,9 +126,13 @@ class User(Handler):
def get_salt(configuration: Configuration, salt_length: int = 20) -> str: def get_salt(configuration: Configuration, salt_length: int = 20) -> str:
""" """
get salt from configuration or create new string get salt from configuration or create new string
:param configuration: configuration instance
:param salt_length: salt length Args:
:return: current salt configuration(Configuration): configuration instance
salt_length(int, optional): salt length (Default value = 20)
Returns:
str: current salt
""" """
if salt := configuration.get("auth", "salt", fallback=None): if salt := configuration.get("auth", "salt", fallback=None):
return salt return salt
@ -128,8 +142,12 @@ class User(Handler):
def user_create(args: argparse.Namespace) -> MUser: def user_create(args: argparse.Namespace) -> MUser:
""" """
create user descriptor from arguments create user descriptor from arguments
:param args: command line args
:return: built user descriptor Args:
args(argparse.Namespace): command line args
Returns:
MUser: built user descriptor
""" """
user = MUser(args.username, args.password, args.role) user = MUser(args.username, args.password, args.role)
if user.password is None: if user.password is None:

View File

@ -39,11 +39,13 @@ class Web(Handler):
configuration: Configuration, no_report: bool, unsafe: bool) -> None: configuration: Configuration, no_report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
:param no_report: force disable reporting architecture(str): repository architecture
:param unsafe: if set no user check will be performed before path creation configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
# we are using local import for optional dependencies # we are using local import for optional dependencies
from ahriman.web.web import run_server, setup_service from ahriman.web.web import run_server, setup_service

View File

@ -37,19 +37,23 @@ from ahriman.models.build_status import BuildStatusEnum
class Lock: class Lock:
""" """
wrapper for application lock file wrapper for application lock file
:ivar force: remove lock file on start if any
:ivar path: path to lock file if any Attributes:
:ivar reporter: build status reporter instance force(bool): remove lock file on start if any
:ivar paths: repository paths instance path(Path): path to lock file if any
:ivar unsafe: skip user check reporter(Client): build status reporter instance
paths(RepositoryPaths): repository paths instance
unsafe(bool): skip user check
""" """
def __init__(self, args: argparse.Namespace, architecture: str, configuration: Configuration) -> None: def __init__(self, args: argparse.Namespace, architecture: str, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param args: command line args
:param architecture: repository architecture Args:
:param configuration: configuration instance args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
""" """
self.path = Path(f"{args.lock}_{architecture}") if args.lock is not None else None self.path = Path(f"{args.lock}_{architecture}") if args.lock is not None else None
self.force = args.force self.force = args.force
@ -61,12 +65,12 @@ class Lock:
def __enter__(self) -> Lock: def __enter__(self) -> Lock:
""" """
default workflow is the following: default workflow is the following:
check user UID check user UID
check if there is lock file check if there is lock file
check web status watcher status check web status watcher status
create lock file create lock file
report to web if enabled report to status page if enabled
""" """
self.check_user() self.check_user()
self.check_version() self.check_version()
@ -78,10 +82,14 @@ class Lock:
exc_tb: TracebackType) -> Literal[False]: exc_tb: TracebackType) -> Literal[False]:
""" """
remove lock file when done remove lock file when done
:param exc_type: exception type name if any
:param exc_val: exception raised if any Args:
:param exc_tb: exception traceback if any exc_type(Optional[Type[Exception]]): exception type name if any
:return: always False (do not suppress any exception) exc_val(Optional[Exception]): exception raised if any
exc_tb(TracebackType): exception traceback if any
Returns:
Literal[False]: always False (do not suppress any exception)
""" """
self.clear() self.clear()
status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed

View File

@ -26,13 +26,17 @@ from ahriman.core.configuration import Configuration
class Pacman: class Pacman:
""" """
alpm wrapper alpm wrapper
:ivar handle: pyalpm root `Handle`
Attributes:
handle(Handle): pyalpm root `Handle`
""" """
def __init__(self, configuration: Configuration) -> None: def __init__(self, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param configuration: configuration instance
Args:
configuration(Configuration): configuration instance
""" """
root = configuration.get("alpm", "root") root = configuration.get("alpm", "root")
pacman_root = configuration.getpath("alpm", "database") pacman_root = configuration.getpath("alpm", "database")
@ -43,7 +47,9 @@ class Pacman:
def all_packages(self) -> Set[str]: def all_packages(self) -> Set[str]:
""" """
get list of packages known for alpm get list of packages known for alpm
:return: list of package names
Returns:
Set[str]: list of package names
""" """
result: Set[str] = set() result: Set[str] = set()
for database in self.handle.get_syncdbs(): for database in self.handle.get_syncdbs():

View File

@ -30,10 +30,12 @@ from ahriman.models.aur_package import AURPackage
class AUR(Remote): class AUR(Remote):
""" """
AUR RPC wrapper AUR RPC wrapper
:cvar DEFAULT_RPC_URL: default AUR RPC url
:cvar DEFAULT_RPC_VERSION: default AUR RPC version Attributes:
:ivar rpc_url: AUR RPC url DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
:ivar rpc_version: AUR RPC version DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
rpc_url(str): AUR RPC url
rpc_version(str): AUR RPC version
""" """
DEFAULT_RPC_URL = "https://aur.archlinux.org/rpc" DEFAULT_RPC_URL = "https://aur.archlinux.org/rpc"
@ -42,8 +44,10 @@ class AUR(Remote):
def __init__(self, rpc_url: Optional[str] = None, rpc_version: Optional[str] = None) -> None: def __init__(self, rpc_url: Optional[str] = None, rpc_version: Optional[str] = None) -> None:
""" """
default constructor default constructor
:param rpc_url: AUR RPC url
:param rpc_version: AUR RPC version Args:
rpc_url(Optional[str], optional): AUR RPC url (Default value = None)
rpc_version(Optional[str], optional): AUR RPC version (Default value = None)
""" """
Remote.__init__(self) Remote.__init__(self)
self.rpc_url = rpc_url or self.DEFAULT_RPC_URL self.rpc_url = rpc_url or self.DEFAULT_RPC_URL
@ -53,8 +57,12 @@ class AUR(Remote):
def parse_response(response: Dict[str, Any]) -> List[AURPackage]: def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
""" """
parse RPC response to package list parse RPC response to package list
:param response: RPC response json
:return: list of parsed packages Args:
response(Dict[str, Any]): RPC response json
Returns:
List[AURPackage]: list of parsed packages
""" """
response_type = response["type"] response_type = response["type"]
if response_type == "error": if response_type == "error":
@ -65,10 +73,14 @@ class AUR(Remote):
def make_request(self, request_type: str, *args: str, **kwargs: str) -> List[AURPackage]: def make_request(self, request_type: str, *args: str, **kwargs: str) -> List[AURPackage]:
""" """
perform request to AUR RPC perform request to AUR RPC
:param request_type: AUR request type, e.g. search, info
:param args: list of arguments to be passed as args query parameter Args:
:param kwargs: list of additional named parameters like by request_type(str): AUR request type, e.g. search, info
:return: response parsed to package list *args(str): list of arguments to be passed as args query parameter
**kwargs(str): list of additional named parameters like by
Returns:
List[AURPackage]: response parsed to package list
""" """
query: Dict[str, Any] = { query: Dict[str, Any] = {
"type": request_type, "type": request_type,
@ -98,8 +110,12 @@ class AUR(Remote):
def package_info(self, package_name: str) -> AURPackage: def package_info(self, package_name: str) -> AURPackage:
""" """
get package info by its name get package info by its name
:param package_name: package name to search
:return: package which match the package name Args:
package_name(str): package name to search
Returns:
AURPackage: package which match the package name
""" """
packages = self.make_request("info", package_name) packages = self.make_request("info", package_name)
return next(package for package in packages if package.name == package_name) return next(package for package in packages if package.name == package_name)
@ -107,7 +123,11 @@ class AUR(Remote):
def package_search(self, *keywords: str) -> List[AURPackage]: def package_search(self, *keywords: str) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria Args:
*keywords(str): keywords to search
Returns:
List[AURPackage]: list of packages which match the criteria
""" """
return self.make_request("search", *keywords, by="name-desc") return self.make_request("search", *keywords, by="name-desc")

View File

@ -30,8 +30,10 @@ from ahriman.models.aur_package import AURPackage
class Official(Remote): class Official(Remote):
""" """
official repository RPC wrapper official repository RPC wrapper
:cvar DEFAULT_RPC_URL: default AUR RPC url
:ivar rpc_url: AUR RPC url Attributes:
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
rpc_url(str): AUR RPC url
""" """
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json" DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
@ -39,7 +41,9 @@ class Official(Remote):
def __init__(self, rpc_url: Optional[str] = None) -> None: def __init__(self, rpc_url: Optional[str] = None) -> None:
""" """
default constructor default constructor
:param rpc_url: AUR RPC url
Args:
rpc_url(Optional[str], optional): AUR RPC url (Default value = None)
""" """
Remote.__init__(self) Remote.__init__(self)
self.rpc_url = rpc_url or self.DEFAULT_RPC_URL self.rpc_url = rpc_url or self.DEFAULT_RPC_URL
@ -48,8 +52,12 @@ class Official(Remote):
def parse_response(response: Dict[str, Any]) -> List[AURPackage]: def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
""" """
parse RPC response to package list parse RPC response to package list
:param response: RPC response json
:return: list of parsed packages Args:
response(Dict[str, Any]): RPC response json
Returns:
List[AURPackage]: list of parsed packages
""" """
if not response["valid"]: if not response["valid"]:
raise InvalidPackageInfo("API validation error") raise InvalidPackageInfo("API validation error")
@ -58,9 +66,13 @@ class Official(Remote):
def make_request(self, *args: str, by: str) -> List[AURPackage]: def make_request(self, *args: str, by: str) -> List[AURPackage]:
""" """
perform request to official repositories RPC perform request to official repositories RPC
:param args: list of arguments to be passed as args query parameter
:param by: search by the field Args:
:return: response parsed to package list *args(str): list of arguments to be passed as args query parameter
by(str): search by the field
Returns:
List[AURPackage]: response parsed to package list
""" """
try: try:
response = requests.get(self.rpc_url, params={by: args}) response = requests.get(self.rpc_url, params={by: args})
@ -76,8 +88,12 @@ class Official(Remote):
def package_info(self, package_name: str) -> AURPackage: def package_info(self, package_name: str) -> AURPackage:
""" """
get package info by its name get package info by its name
:param package_name: package name to search
:return: package which match the package name Args:
package_name(str): package name to search
Returns:
AURPackage: package which match the package name
""" """
packages = self.make_request(package_name, by="name") packages = self.make_request(package_name, by="name")
return next(package for package in packages if package.name == package_name) return next(package for package in packages if package.name == package_name)
@ -85,7 +101,11 @@ class Official(Remote):
def package_search(self, *keywords: str) -> List[AURPackage]: def package_search(self, *keywords: str) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria Args:
*keywords(str): keywords to search
Returns:
List[AURPackage]: list of packages which match the criteria
""" """
return self.make_request(*keywords, by="q") return self.make_request(*keywords, by="q")

View File

@ -29,7 +29,9 @@ from ahriman.models.aur_package import AURPackage
class Remote: class Remote:
""" """
base class for remote package search base class for remote package search
:ivar logger: class logger
Attributes:
logger(logging.Logger): class logger
""" """
def __init__(self) -> None: def __init__(self) -> None:
@ -42,8 +44,12 @@ class Remote:
def info(cls: Type[Remote], package_name: str) -> AURPackage: def info(cls: Type[Remote], package_name: str) -> AURPackage:
""" """
get package info by its name get package info by its name
:param package_name: package name to search
:return: package which match the package name Args:
package_name(str): package name to search
Returns:
AURPackage: package which match the package name
""" """
return cls().package_info(package_name) return cls().package_info(package_name)
@ -52,8 +58,12 @@ class Remote:
""" """
search in remote repository by using API with multiple words. This method is required in order to handle 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 https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
:param keywords: search terms, e.g. "ahriman", "is", "cool"
:return: list of packages each of them matches all search terms Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
Returns:
List[AURPackage]: list of packages each of them matches all search terms
""" """
instance = cls() instance = cls()
packages: Dict[str, AURPackage] = {} packages: Dict[str, AURPackage] = {}
@ -70,23 +80,35 @@ class Remote:
def search(cls: Type[Remote], *keywords: str) -> List[AURPackage]: def search(cls: Type[Remote], *keywords: str) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
Returns:
List[AURPackage]: list of packages which match the criteria
""" """
return cls().package_search(*keywords) return cls().package_search(*keywords)
def package_info(self, package_name: str) -> AURPackage: def package_info(self, package_name: str) -> AURPackage:
""" """
get package info by its name get package info by its name
:param package_name: package name to search
:return: package which match the package name Args:
package_name(str): package name to search
Returns:
AURPackage: package which match the package name
""" """
raise NotImplementedError raise NotImplementedError
def package_search(self, *keywords: str) -> List[AURPackage]: def package_search(self, *keywords: str) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
:param keywords: keywords to search
:return: list of packages which match the criteria Args:
*keywords(str): keywords to search
Returns:
List[AURPackage]: list of packages which match the criteria
""" """
raise NotImplementedError raise NotImplementedError

View File

@ -30,11 +30,13 @@ from ahriman.models.repository_paths import RepositoryPaths
class Repo: class Repo:
""" """
repo-add and repo-remove wrapper repo-add and repo-remove wrapper
:ivar logger: class logger
:ivar name: repository name Attributes:
:ivar paths: repository paths instance logger(logging.Logger): class logger
:ivar sign_args: additional args which have to be used to sign repository archive name(str): repository name
:ivar uid: uid of the repository owner user paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive
uid(int): uid of the repository owner user
""" """
_check_output = check_output _check_output = check_output
@ -42,9 +44,11 @@ class Repo:
def __init__(self, name: str, paths: RepositoryPaths, sign_args: List[str]) -> None: def __init__(self, name: str, paths: RepositoryPaths, sign_args: List[str]) -> None:
""" """
default constructor default constructor
:param name: repository name
:param paths: repository paths instance Args:
:param sign_args: additional args which have to be used to sign repository archive name(str): repository name
paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive
""" """
self.logger = logging.getLogger("build_details") self.logger = logging.getLogger("build_details")
self.name = name self.name = name
@ -55,14 +59,17 @@ class Repo:
@property @property
def repo_path(self) -> Path: def repo_path(self) -> Path:
""" """
:return: path to repository database Returns:
Path: path to repository database
""" """
return self.paths.repository / f"{self.name}.db.tar.gz" return self.paths.repository / f"{self.name}.db.tar.gz"
def add(self, path: Path) -> None: def add(self, path: Path) -> None:
""" """
add new package to repository add new package to repository
:param path: path to archive to add
Args:
path(Path): path to archive to add
""" """
Repo._check_output( Repo._check_output(
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path), "repo-add", *self.sign_args, "-R", str(self.repo_path), str(path),
@ -85,8 +92,10 @@ class Repo:
def remove(self, package: str, filename: Path) -> None: def remove(self, package: str, filename: Path) -> None:
""" """
remove package from repository remove package from repository
:param package: package name to remove
:param filename: package filename to remove Args:
package(str): package name to remove
filename(Path): package filename to remove
""" """
# remove package and signature (if any) from filesystem # remove package and signature (if any) from filesystem
for full_path in self.paths.repository.glob(f"{filename}*"): for full_path in self.paths.repository.glob(f"{filename}*"):

View File

@ -32,16 +32,21 @@ from ahriman.models.user_access import UserAccess
class Auth: class Auth:
""" """
helper to deal with user authorization helper to deal with user authorization
:ivar enabled: indicates if authorization is enabled
:ivar max_age: session age in seconds. It will be used for both client side and server side checks Attributes:
:ivar safe_build_status: allow read only access to the index page enabled(bool): indicates if authorization is enabled
logger(logging.Logger): class logger
max_age(int): session age in seconds. It will be used for both client side and server side checks
safe_build_status(bool): allow read only access to the index page
""" """
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None: def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
""" """
default constructor default constructor
:param configuration: configuration instance
:param provider: authorization type definition Args:
configuration(Configuration): configuration instance
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.Disabled)
""" """
self.logger = logging.getLogger("http") self.logger = logging.getLogger("http")
@ -57,7 +62,9 @@ class Auth:
In case of internal authentication it must provide an interface (modal form) to login with button sends POST In case of internal authentication it must provide an interface (modal form) to login with button sends POST
request. But for an external providers behaviour can be different: e.g. OAuth provider requires sending GET request. But for an external providers behaviour can be different: e.g. OAuth provider requires sending GET
request to external resource request to external resource
:return: login control as html code to insert
Returns:
str: login control as html code to insert
""" """
return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#loginForm" style="text-decoration: none">login</button>""" return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#loginForm" style="text-decoration: none">login</button>"""
@ -65,9 +72,13 @@ class Auth:
def load(cls: Type[Auth], configuration: Configuration, database: SQLite) -> Auth: def load(cls: Type[Auth], configuration: Configuration, database: SQLite) -> Auth:
""" """
load authorization module from settings load authorization module from settings
:param configuration: configuration instance
:param database: database instance Args:
:return: authorization module according to current settings configuration(Configuration): configuration instance
database(SQLite): database instance
Returns:
Auth: authorization module according to current settings
""" """
provider = AuthSettings.from_option(configuration.get("auth", "target", fallback="disabled")) provider = AuthSettings.from_option(configuration.get("auth", "target", fallback="disabled"))
if provider == AuthSettings.Configuration: if provider == AuthSettings.Configuration:
@ -81,9 +92,13 @@ class Auth:
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool: # pylint: disable=no-self-use async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool: # pylint: disable=no-self-use
""" """
validate user password validate user password
:param username: username
:param password: entered password Args:
:return: True in case if password matches, False otherwise username(Optional[str]): username
password(Optional[str]): entered password
Returns:
bool: True in case if password matches, False otherwise
""" """
del username, password del username, password
return True return True
@ -91,8 +106,12 @@ class Auth:
async def known_username(self, username: Optional[str]) -> bool: # pylint: disable=no-self-use async def known_username(self, username: Optional[str]) -> bool: # pylint: disable=no-self-use
""" """
check if user is known check if user is known
:param username: username
:return: True in case if user is known and can be authorized and False otherwise Args:
username(Optional[str]): username
Returns:
bool: True in case if user is known and can be authorized and False otherwise
""" """
del username del username
return True return True
@ -100,10 +119,14 @@ class Auth:
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool: # pylint: disable=no-self-use async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool: # pylint: disable=no-self-use
""" """
validate if user has access to requested resource validate if user has access to requested resource
:param username: username
:param required: required access level Args:
:param context: URI request path username(str): username
:return: True in case if user is allowed to do this request and False otherwise required(UserAccess): required access level
context(Optional[str]): URI request path
Returns:
bool: True in case if user is allowed to do this request and False otherwise
""" """
del username, required, context del username, required, context
return True return True

View File

@ -29,8 +29,12 @@ except ImportError:
async def authorized_userid(*args: Any) -> Any: async def authorized_userid(*args: Any) -> Any:
""" """
handle aiohttp security methods handle aiohttp security methods
:param args: argument list as provided by authorized_userid function
:return: None in case if no aiohttp_security module found and function call otherwise Args:
*args(Any): argument list as provided by authorized_userid function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
""" """
if _has_aiohttp_security: if _has_aiohttp_security:
return await aiohttp_security.authorized_userid(*args) # pylint: disable=no-value-for-parameter return await aiohttp_security.authorized_userid(*args) # pylint: disable=no-value-for-parameter
@ -40,8 +44,12 @@ async def authorized_userid(*args: Any) -> Any:
async def check_authorized(*args: Any) -> Any: async def check_authorized(*args: Any) -> Any:
""" """
handle aiohttp security methods handle aiohttp security methods
:param args: argument list as provided by check_authorized function
:return: None in case if no aiohttp_security module found and function call otherwise Args:
*args(Any): argument list as provided by check_authorized function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
""" """
if _has_aiohttp_security: if _has_aiohttp_security:
return await aiohttp_security.check_authorized(*args) # pylint: disable=no-value-for-parameter return await aiohttp_security.check_authorized(*args) # pylint: disable=no-value-for-parameter
@ -51,8 +59,12 @@ async def check_authorized(*args: Any) -> Any:
async def forget(*args: Any) -> Any: async def forget(*args: Any) -> Any:
""" """
handle aiohttp security methods handle aiohttp security methods
:param args: argument list as provided by forget function
:return: None in case if no aiohttp_security module found and function call otherwise Args:
*args(Any): argument list as provided by forget function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
""" """
if _has_aiohttp_security: if _has_aiohttp_security:
return await aiohttp_security.forget(*args) # pylint: disable=no-value-for-parameter return await aiohttp_security.forget(*args) # pylint: disable=no-value-for-parameter
@ -62,8 +74,12 @@ async def forget(*args: Any) -> Any:
async def remember(*args: Any) -> Any: async def remember(*args: Any) -> Any:
""" """
handle disabled auth handle disabled auth
:param args: argument list as provided by remember function
:return: None in case if no aiohttp_security module found and function call otherwise Args:
*args(Any): argument list as provided by remember function
Returns:
Any: None in case if no aiohttp_security module found and function call otherwise
""" """
if _has_aiohttp_security: if _has_aiohttp_security:
return await aiohttp_security.remember(*args) # pylint: disable=no-value-for-parameter return await aiohttp_security.remember(*args) # pylint: disable=no-value-for-parameter

View File

@ -31,17 +31,21 @@ from ahriman.models.user_access import UserAccess
class Mapping(Auth): class Mapping(Auth):
""" """
user authorization based on mapping from configuration file user authorization based on mapping from configuration file
:ivar salt: random generated string to salt passwords
:ivar database: database instance Attributes:
salt(str): random generated string to salt passwords
database(SQLite): database instance
""" """
def __init__(self, configuration: Configuration, database: SQLite, def __init__(self, configuration: Configuration, database: SQLite,
provider: AuthSettings = AuthSettings.Configuration) -> None: provider: AuthSettings = AuthSettings.Configuration) -> None:
""" """
default constructor default constructor
:param configuration: configuration instance
:param database: database instance Args:
:param provider: authorization type definition configuration(Configuration): configuration instance
database(SQLite): database instance
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.Configuration)
""" """
Auth.__init__(self, configuration, provider) Auth.__init__(self, configuration, provider)
self.database = database self.database = database
@ -50,9 +54,13 @@ class Mapping(Auth):
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool: async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool:
""" """
validate user password validate user password
:param username: username
:param password: entered password Args:
:return: True in case if password matches, False otherwise username(Optional[str]): username
password(Optional[str]): entered password
Returns:
bool: True in case if password matches, False otherwise
""" """
if username is None or password is None: if username is None or password is None:
return False # invalid data supplied return False # invalid data supplied
@ -62,26 +70,38 @@ class Mapping(Auth):
def get_user(self, username: str) -> Optional[User]: def get_user(self, username: str) -> Optional[User]:
""" """
retrieve user from in-memory mapping retrieve user from in-memory mapping
:param username: username
:return: user descriptor if username is known and None otherwise Args:
username(str): username
Returns:
Optional[User]: user descriptor if username is known and None otherwise
""" """
return self.database.user_get(username) return self.database.user_get(username)
async def known_username(self, username: Optional[str]) -> bool: async def known_username(self, username: Optional[str]) -> bool:
""" """
check if user is known check if user is known
:param username: username
:return: True in case if user is known and can be authorized and False otherwise Args:
username(Optional[str]): username
Returns:
bool: True in case if user is known and can be authorized and False otherwise
""" """
return username is not None and self.get_user(username) is not None return username is not None and self.get_user(username) is not None
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool: async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool:
""" """
validate if user has access to requested resource validate if user has access to requested resource
:param username: username
:param required: required access level Args:
:param context: URI request path username(str): username
:return: True in case if user is allowed to do this request and False otherwise required(UserAccess): required access level
context(Optional[str]): URI request path
Returns:
bool: True in case if user is allowed to do this request and False otherwise
""" """
user = self.get_user(username) user = self.get_user(username)
return user is not None and user.verify_access(required) return user is not None and user.verify_access(required)

View File

@ -32,20 +32,24 @@ class OAuth(Mapping):
""" """
OAuth user authorization. OAuth user authorization.
It is required to create application first and put application credentials. It is required to create application first and put application credentials.
:ivar client_id: application client id
:ivar client_secret: application client secret key Attributes:
:ivar provider: provider class, should be one of aiohttp-client provided classes client_id(str): application client id
:ivar redirect_uri: redirect URI registered in provider client_secret(str): application client secret key
:ivar scopes: list of scopes required by the application provider(aioauth_client.OAuth2Client): provider class, should be one of aiohttp-client provided classes
redirect_uri(str): redirect URI registered in provider
scopes(str): list of scopes required by the application
""" """
def __init__(self, configuration: Configuration, database: SQLite, def __init__(self, configuration: Configuration, database: SQLite,
provider: AuthSettings = AuthSettings.OAuth) -> None: provider: AuthSettings = AuthSettings.OAuth) -> None:
""" """
default constructor default constructor
:param configuration: configuration instance
:param database: database instance Args:
:param provider: authorization type definition configuration(Configuration): configuration instance
database(SQLite): database instance
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.OAuth)
""" """
Mapping.__init__(self, configuration, database, provider) Mapping.__init__(self, configuration, database, provider)
self.client_id = configuration.get("auth", "client_id") self.client_id = configuration.get("auth", "client_id")
@ -60,7 +64,8 @@ class OAuth(Mapping):
@property @property
def auth_control(self) -> str: def auth_control(self) -> str:
""" """
:return: login control as html code to insert Returns:
str: login control as html code to insert
""" """
return """<a class="nav-link" href="/user-api/v1/login" title="login via OAuth2">login</a>""" return """<a class="nav-link" href="/user-api/v1/login" title="login via OAuth2">login</a>"""
@ -68,8 +73,12 @@ class OAuth(Mapping):
def get_provider(name: str) -> Type[aioauth_client.OAuth2Client]: def get_provider(name: str) -> Type[aioauth_client.OAuth2Client]:
""" """
load OAuth2 provider by name load OAuth2 provider by name
:param name: name of the provider. Must be valid class defined in aioauth-client library
:return: loaded provider type Args:
name(str): name of the provider. Must be valid class defined in aioauth-client library
Returns:
Type[aioauth_client.OAuth2Client]: loaded provider type
""" """
provider: Type[aioauth_client.OAuth2Client] = getattr(aioauth_client, name) provider: Type[aioauth_client.OAuth2Client] = getattr(aioauth_client, name)
try: try:
@ -83,14 +92,18 @@ class OAuth(Mapping):
def get_client(self) -> aioauth_client.OAuth2Client: def get_client(self) -> aioauth_client.OAuth2Client:
""" """
load client from parameters load client from parameters
:return: generated client according to current settings
Returns:
aioauth_client.OAuth2Client: generated client according to current settings
""" """
return self.provider(client_id=self.client_id, client_secret=self.client_secret) return self.provider(client_id=self.client_id, client_secret=self.client_secret)
def get_oauth_url(self) -> str: def get_oauth_url(self) -> str:
""" """
get authorization URI for the specified settings get authorization URI for the specified settings
:return: authorization URI as a string
Returns:
str: authorization URI as a string
""" """
client = self.get_client() client = self.get_client()
uri: str = client.get_authorize_url(scope=self.scopes, redirect_uri=self.redirect_uri) uri: str = client.get_authorize_url(scope=self.scopes, redirect_uri=self.redirect_uri)
@ -99,8 +112,12 @@ class OAuth(Mapping):
async def get_oauth_username(self, code: str) -> Optional[str]: async def get_oauth_username(self, code: str) -> Optional[str]:
""" """
extract OAuth username from remote extract OAuth username from remote
:param code: authorization code provided by external service
:return: username as is in OAuth provider Args:
code(str): authorization code provided by external service
Returns:
Optional[str]: username as is in OAuth provider
""" """
try: try:
client = self.get_client() client = self.get_client()

View File

@ -28,7 +28,9 @@ from ahriman.core.util import check_output
class Sources: class Sources:
""" """
helper to download package sources (PKGBUILD etc) helper to download package sources (PKGBUILD etc)
:cvar logger: class logger
Attributes:
logger(logging.Logger): (class attribute) class logger
""" """
logger = logging.getLogger("build_details") logger = logging.getLogger("build_details")
@ -40,8 +42,10 @@ class Sources:
def add(sources_dir: Path, *pattern: str) -> None: def add(sources_dir: Path, *pattern: str) -> None:
""" """
track found files via git track found files via git
:param sources_dir: local path to git repository
:param pattern: glob patterns Args:
sources_dir(Path): local path to git repository
*pattern(str): glob patterns
""" """
# glob directory to find files which match the specified patterns # glob directory to find files which match the specified patterns
found_files: List[Path] = [] found_files: List[Path] = []
@ -59,8 +63,12 @@ class Sources:
def diff(sources_dir: Path) -> str: def diff(sources_dir: Path) -> str:
""" """
generate diff from the current version and write it to the output file generate diff from the current version and write it to the output file
:param sources_dir: local path to git repository
:return: patch as plain string Args:
sources_dir(Path): local path to git repository
Returns:
str: patch as plain string
""" """
return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger) return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger)
@ -68,8 +76,10 @@ class Sources:
def fetch(sources_dir: Path, remote: Optional[str]) -> None: def fetch(sources_dir: Path, remote: Optional[str]) -> None:
""" """
either clone repository or update it to origin/`branch` either clone repository or update it to origin/`branch`
:param sources_dir: local path to fetch
:param remote: remote target (from where to fetch) Args:
sources_dir(Path): local path to fetch
remote(Optional[str]): remote target (from where to fetch)
""" """
# local directory exists and there is .git directory # local directory exists and there is .git directory
is_initialized_git = (sources_dir / ".git").is_dir() is_initialized_git = (sources_dir / ".git").is_dir()
@ -98,8 +108,12 @@ class Sources:
def has_remotes(sources_dir: Path) -> bool: def has_remotes(sources_dir: Path) -> bool:
""" """
check if there are remotes for the repository check if there are remotes for the repository
:param sources_dir: local path to git repository
:return: True in case if there is any remote and false otherwise Args:
sources_dir(Path): local path to git repository
Returns:
bool: True in case if there is any remote and false otherwise
""" """
remotes = Sources._check_output("git", "remote", exception=None, cwd=sources_dir, logger=Sources.logger) remotes = Sources._check_output("git", "remote", exception=None, cwd=sources_dir, logger=Sources.logger)
return bool(remotes) return bool(remotes)
@ -108,7 +122,9 @@ class Sources:
def init(sources_dir: Path) -> None: def init(sources_dir: Path) -> None:
""" """
create empty git repository at the specified path create empty git repository at the specified path
:param sources_dir: local path to sources
Args:
sources_dir(Path): local path to sources
""" """
Sources._check_output("git", "init", "--initial-branch", Sources._branch, Sources._check_output("git", "init", "--initial-branch", Sources._branch,
exception=None, cwd=sources_dir, logger=Sources.logger) exception=None, cwd=sources_dir, logger=Sources.logger)
@ -117,9 +133,11 @@ class Sources:
def load(sources_dir: Path, remote: str, patch: Optional[str]) -> None: def load(sources_dir: Path, remote: str, patch: Optional[str]) -> None:
""" """
fetch sources from remote and apply patches fetch sources from remote and apply patches
:param sources_dir: local path to fetch
:param remote: remote target (from where to fetch) Args:
:param patch: optional patch to be applied sources_dir(Path): local path to fetch
remote(str): remote target (from where to fetch)
patch(Optional[str]): optional patch to be applied
""" """
Sources.fetch(sources_dir, remote) Sources.fetch(sources_dir, remote)
if patch is None: if patch is None:
@ -131,8 +149,10 @@ class Sources:
def patch_apply(sources_dir: Path, patch: str) -> None: def patch_apply(sources_dir: Path, patch: str) -> None:
""" """
apply patches if any apply patches if any
:param sources_dir: local path to directory with git sources
:param patch: patch to be applied Args:
sources_dir(Path): local path to directory with git sources
patch(str): patch to be applied
""" """
# create patch # create patch
Sources.logger.info("apply patch from database") Sources.logger.info("apply patch from database")
@ -143,9 +163,13 @@ class Sources:
def patch_create(sources_dir: Path, *pattern: str) -> str: def patch_create(sources_dir: Path, *pattern: str) -> str:
""" """
create patch set for the specified local path create patch set for the specified local path
:param sources_dir: local path to git repository
:param pattern: glob patterns Args:
:return: patch as plain text sources_dir(Path): local path to git repository
*pattern(str):
Returns:
str: patch as plain text
""" """
Sources.add(sources_dir, *pattern) Sources.add(sources_dir, *pattern)
diff = Sources.diff(sources_dir) diff = Sources.diff(sources_dir)

View File

@ -35,11 +35,13 @@ from ahriman.models.repository_paths import RepositoryPaths
class Task: class Task:
""" """
base package build task base package build task
:ivar build_logger: logger for build process
:ivar logger: class logger Attributes:
:ivar package: package definitions build_logger(logging.Logger): logger for build process
:ivar paths: repository paths instance logger(logging.Logger): class logger
:ivar uid: uid of the repository owner user package(Package): package definitions
paths(RepositoryPaths): repository paths instance
uid(int): uid of the repository owner user
""" """
_check_output = check_output _check_output = check_output
@ -47,9 +49,11 @@ class Task:
def __init__(self, package: Package, configuration: Configuration, paths: RepositoryPaths) -> None: def __init__(self, package: Package, configuration: Configuration, paths: RepositoryPaths) -> None:
""" """
default constructor default constructor
:param package: package definitions
:param configuration: configuration instance Args:
:param paths: repository paths instance package(Package): package definitions
configuration(Configuration): configuration instance
paths(RepositoryPaths): repository paths instance
""" """
self.logger = logging.getLogger("root") self.logger = logging.getLogger("root")
self.build_logger = logging.getLogger("build_details") self.build_logger = logging.getLogger("build_details")
@ -65,8 +69,12 @@ class Task:
def build(self, sources_path: Path) -> List[Path]: def build(self, sources_path: Path) -> List[Path]:
""" """
run package build run package build
:param sources_path: path to where sources are
:return: paths of produced packages Args:
sources_path(Path): path to where sources are
Returns:
List[Path]: paths of produced packages
""" """
command = [self.build_command, "-r", str(self.paths.chroot)] command = [self.build_command, "-r", str(self.paths.chroot)]
command.extend(self.archbuild_flags) command.extend(self.archbuild_flags)
@ -91,8 +99,10 @@ class Task:
def init(self, path: Path, database: SQLite) -> None: def init(self, path: Path, database: SQLite) -> None:
""" """
fetch package from git fetch package from git
:param path: local path to fetch
:param database: database instance Args:
path(Path): local path to fetch
database(SQLite): database instance
""" """
if self.paths.cache_for(self.package.base).is_dir(): if self.paths.cache_for(self.package.base).is_dir():
# no need to clone whole repository, just copy from cache first # no need to clone whole repository, just copy from cache first

View File

@ -34,12 +34,14 @@ from ahriman.models.repository_paths import RepositoryPaths
class Configuration(configparser.RawConfigParser): class Configuration(configparser.RawConfigParser):
""" """
extension for built-in configuration parser extension for built-in configuration parser
:ivar architecture: repository architecture
:ivar path: path to root configuration file Attributes:
:cvar ARCHITECTURE_SPECIFIC_SECTIONS: known sections which can be architecture specific (required by dump) ARCHITECTURE_SPECIFIC_SECTIONS(List[str]): (class attribute) known sections which can be architecture specific (required by dump)
:cvar DEFAULT_LOG_FORMAT: default log format (in case of fallback) DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback)
:cvar DEFAULT_LOG_LEVEL: default log level (in case of fallback) DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
:cvar SYSTEM_CONFIGURATION_PATH: default system configuration path distributed by package SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
architecture(Optional[str]): repository architecture
path(Optional[Path]): path to root configuration file
""" """
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s" DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
@ -62,21 +64,24 @@ class Configuration(configparser.RawConfigParser):
@property @property
def include(self) -> Path: def include(self) -> Path:
""" """
:return: path to directory with configuration includes Returns:
Path: path to directory with configuration includes
""" """
return self.getpath("settings", "include") return self.getpath("settings", "include")
@property @property
def logging_path(self) -> Path: def logging_path(self) -> Path:
""" """
:return: path to logging configuration Returns:
Path: path to logging configuration
""" """
return self.getpath("settings", "logging") return self.getpath("settings", "logging")
@property @property
def repository_paths(self) -> RepositoryPaths: def repository_paths(self) -> RepositoryPaths:
""" """
:return: repository paths instance Returns:
RepositoryPaths: repository paths instance
""" """
_, architecture = self.check_loaded() _, architecture = self.check_loaded()
return RepositoryPaths(self.getpath("repository", "root"), architecture) return RepositoryPaths(self.getpath("repository", "root"), architecture)
@ -85,10 +90,14 @@ class Configuration(configparser.RawConfigParser):
def from_path(cls: Type[Configuration], path: Path, architecture: str, quiet: bool) -> Configuration: def from_path(cls: Type[Configuration], path: Path, architecture: str, quiet: bool) -> Configuration:
""" """
constructor with full object initialization constructor with full object initialization
:param path: path to root configuration file
:param architecture: repository architecture Args:
:param quiet: force disable any log messages path(Path): path to root configuration file
:return: configuration instance architecture(str): repository architecture
quiet(bool): force disable any log messages
Returns:
Configuration: configuration instance
""" """
config = cls() config = cls()
config.load(path) config.load(path)
@ -100,8 +109,12 @@ class Configuration(configparser.RawConfigParser):
def __convert_list(value: str) -> List[str]: def __convert_list(value: str) -> List[str]:
""" """
convert string value to list of strings convert string value to list of strings
:param value: string configuration value
:return: list of string from the parsed string Args:
value(str): string configuration value
Returns:
List[str]: list of string from the parsed string
""" """
def generator() -> Generator[str, None, None]: def generator() -> Generator[str, None, None]:
quote_mark = None quote_mark = None
@ -126,17 +139,25 @@ class Configuration(configparser.RawConfigParser):
def section_name(section: str, suffix: str) -> str: def section_name(section: str, suffix: str) -> str:
""" """
generate section name for sections which depends on context generate section name for sections which depends on context
:param section: section name
:param suffix: session suffix, e.g. repository architecture Args:
:return: correct section name for repository specific section section(str): section name
suffix(str): session suffix, e.g. repository architecture
Returns:
str: correct section name for repository specific section
""" """
return f"{section}:{suffix}" return f"{section}:{suffix}"
def __convert_path(self, value: str) -> Path: def __convert_path(self, value: str) -> Path:
""" """
convert string value to path object convert string value to path object
:param value: string configuration value
:return: path object which represents the configuration value Args:
value(str): string configuration value
Returns:
Path: path object which represents the configuration value
""" """
path = Path(value) path = Path(value)
if self.path is None or path.is_absolute(): if self.path is None or path.is_absolute():
@ -146,7 +167,9 @@ class Configuration(configparser.RawConfigParser):
def check_loaded(self) -> Tuple[Path, str]: def check_loaded(self) -> Tuple[Path, str]:
""" """
check if service was actually loaded check if service was actually loaded
:return: configuration root path and architecture if loaded
Returns:
Tuple[Path, str]: configuration root path and architecture if loaded
""" """
if self.path is None or self.architecture is None: if self.path is None or self.architecture is None:
raise InitializeException("Configuration path and/or architecture are not set") raise InitializeException("Configuration path and/or architecture are not set")
@ -155,7 +178,9 @@ class Configuration(configparser.RawConfigParser):
def dump(self) -> Dict[str, Dict[str, str]]: def dump(self) -> Dict[str, Dict[str, str]]:
""" """
dump configuration to dictionary dump configuration to dictionary
:return: configuration dump for specific architecture
Returns:
Dict[str, Dict[str, str]]: configuration dump for specific architecture
""" """
return { return {
section: dict(self[section]) section: dict(self[section])
@ -172,9 +197,13 @@ class Configuration(configparser.RawConfigParser):
""" """
get type variable with fallback to old logic get type variable with fallback to old logic
Despite the fact that it has same semantics as other get* methods, but it has different argument list Despite the fact that it has same semantics as other get* methods, but it has different argument list
:param section: section name
:param architecture: repository architecture Args:
:return: section name and found type name section(str): section name
architecture(str): repository architecture
Returns:
Tuple[str, str]: section name and found type name
""" """
group_type = self.get(section, "type", fallback=None) # new-style logic group_type = self.get(section, "type", fallback=None) # new-style logic
if group_type is not None: if group_type is not None:
@ -191,7 +220,9 @@ class Configuration(configparser.RawConfigParser):
def load(self, path: Path) -> None: def load(self, path: Path) -> None:
""" """
fully load configuration fully load configuration
:param path: path to root configuration file
Args:
path(Path): path to root configuration file
""" """
if not path.is_file(): # fallback to the system file if not path.is_file(): # fallback to the system file
path = self.SYSTEM_CONFIGURATION_PATH path = self.SYSTEM_CONFIGURATION_PATH
@ -214,7 +245,9 @@ class Configuration(configparser.RawConfigParser):
def load_logging(self, quiet: bool) -> None: def load_logging(self, quiet: bool) -> None:
""" """
setup logging settings from configuration setup logging settings from configuration
:param quiet: force disable any log messages
Args:
quiet(bool): force disable any log messages
""" """
try: try:
path = self.logging_path path = self.logging_path
@ -229,7 +262,9 @@ class Configuration(configparser.RawConfigParser):
def merge_sections(self, architecture: str) -> None: def merge_sections(self, architecture: str) -> None:
""" """
merge architecture specific sections into main configuration merge architecture specific sections into main configuration
:param architecture: repository architecture
Args:
architecture(str): repository architecture
""" """
self.architecture = architecture self.architecture = architecture
for section in self.ARCHITECTURE_SPECIFIC_SECTIONS: for section in self.ARCHITECTURE_SPECIFIC_SECTIONS:
@ -260,9 +295,11 @@ class Configuration(configparser.RawConfigParser):
def set_option(self, section: str, option: str, value: Optional[str]) -> None: def set_option(self, section: str, option: str, value: Optional[str]) -> None:
""" """
set option. Unlike default `configparser.RawConfigParser.set` it also creates section if it does not exist set option. Unlike default `configparser.RawConfigParser.set` it also creates section if it does not exist
:param section: section name
:param option: option name Args:
:param value: option value as string in parsable format section(str): section name
option(str): option name
value(Optional[str]): option value as string in parsable format
""" """
if not self.has_section(section): if not self.has_section(section):
self.add_section(section) self.add_section(section)

View File

@ -31,10 +31,12 @@ def migrate_data(result: MigrationResult, connection: Connection,
configuration: Configuration, paths: RepositoryPaths) -> None: configuration: Configuration, paths: RepositoryPaths) -> None:
""" """
perform data migration perform data migration
:param result: result of the schema migration
:param connection: database connection Args:
:param configuration: configuration instance result(MigrationResult): result of the schema migration
:param paths: repository paths instance connection(Connection): database connection
configuration(Configuration): configuration instance
paths(RepositoryPaths): repository paths instance
""" """
# initial data migration # initial data migration
if result.old_version <= 0: if result.old_version <= 0:

View File

@ -29,8 +29,10 @@ from ahriman.models.repository_paths import RepositoryPaths
def migrate_package_statuses(connection: Connection, paths: RepositoryPaths) -> None: def migrate_package_statuses(connection: Connection, paths: RepositoryPaths) -> None:
""" """
perform migration for package statuses perform migration for package statuses
:param connection: database connection
:param paths: repository paths instance Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
""" """
def insert_base(metadata: Package, last_status: BuildStatus) -> None: def insert_base(metadata: Package, last_status: BuildStatus) -> None:
connection.execute( connection.execute(

View File

@ -25,8 +25,10 @@ from ahriman.models.repository_paths import RepositoryPaths
def migrate_patches(connection: Connection, paths: RepositoryPaths) -> None: def migrate_patches(connection: Connection, paths: RepositoryPaths) -> None:
""" """
perform migration for patches perform migration for patches
:param connection: database connection
:param paths: repository paths instance Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
""" """
root = paths.root / "patches" root = paths.root / "patches"
if not root.is_dir(): if not root.is_dir():

View File

@ -25,8 +25,10 @@ from ahriman.core.configuration import Configuration
def migrate_users_data(connection: Connection, configuration: Configuration) -> None: def migrate_users_data(connection: Connection, configuration: Configuration) -> None:
""" """
perform migration for users perform migration for users
:param connection: database connection
:param configuration: configuration instance Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
""" """
for section in configuration.sections(): for section in configuration.sections():
for option, value in configuration[section].items(): for option, value in configuration[section].items():

View File

@ -35,14 +35,18 @@ class Migrations:
""" """
simple migration wrapper for the sqlite simple migration wrapper for the sqlite
idea comes from https://www.ash.dev/blog/simple-migration-system-in-sqlite/ idea comes from https://www.ash.dev/blog/simple-migration-system-in-sqlite/
:ivar connection: database connection
:ivar logger: class logger Attributes:
connection(Connection): database connection
logger(logging.Logger): class logger
""" """
def __init__(self, connection: Connection) -> None: def __init__(self, connection: Connection) -> None:
""" """
default constructor default constructor
:param connection: database connection
Args:
connection(Connection): database connection
""" """
self.connection = connection self.connection = connection
self.logger = logging.getLogger("database") self.logger = logging.getLogger("database")
@ -51,8 +55,12 @@ class Migrations:
def migrate(cls: Type[Migrations], connection: Connection) -> MigrationResult: def migrate(cls: Type[Migrations], connection: Connection) -> MigrationResult:
""" """
perform migrations implicitly perform migrations implicitly
:param connection: database connection
:return: current schema version Args:
connection(Connection): database connection
Returns:
MigrationResult: current schema version
""" """
return cls(connection).run() return cls(connection).run()
@ -61,6 +69,8 @@ class Migrations:
extract all migrations from the current package extract all migrations from the current package
idea comes from https://julienharbulot.com/python-dynamical-import.html idea comes from https://julienharbulot.com/python-dynamical-import.html
Returns:
List[Migration]: list of found migrations
""" """
migrations: List[Migration] = [] migrations: List[Migration] = []
package_dir = Path(__file__).resolve().parent package_dir = Path(__file__).resolve().parent
@ -77,7 +87,9 @@ class Migrations:
def run(self) -> MigrationResult: def run(self) -> MigrationResult:
""" """
perform migrations perform migrations
:return: current schema version
Return:
MigrationResult: current schema version
""" """
migrations = self.migrations() migrations = self.migrations()
current_version = self.user_version() current_version = self.user_version()
@ -118,7 +130,9 @@ class Migrations:
def user_version(self) -> int: def user_version(self) -> int:
""" """
get schema version from sqlite database get schema version from sqlite database
;return: current schema version
Returns:
int: current schema version
""" """
cursor = self.connection.execute("pragma user_version") cursor = self.connection.execute("pragma user_version")
current_version: int = cursor.fetchone()["user_version"] current_version: int = cursor.fetchone()["user_version"]

View File

@ -33,17 +33,25 @@ class AuthOperations(Operations):
def user_get(self, username: str) -> Optional[User]: def user_get(self, username: str) -> Optional[User]:
""" """
get user by username get user by username
:param username: username
:return: user if it was found Args:
username(str): username
Returns:
Optional[User]: user if it was found
""" """
return next(iter(self.user_list(username, None)), None) return next(iter(self.user_list(username, None)), None)
def user_list(self, username: Optional[str], access: Optional[UserAccess]) -> List[User]: def user_list(self, username: Optional[str], access: Optional[UserAccess]) -> List[User]:
""" """
get users by filter get users by filter
:param username: optional filter by username
:param access: optional filter by role Args:
:return: list of users who match criteria username(Optional[str]): optional filter by username
access(Optional[UserAccess]): optional filter by role
Returns:
List[User]: list of users who match criteria
""" """
username_filter = username.lower() if username is not None else username username_filter = username.lower() if username is not None else username
access_filter = access.value if access is not None else access access_filter = access.value if access is not None else access
@ -64,7 +72,9 @@ class AuthOperations(Operations):
def user_remove(self, username: str) -> None: def user_remove(self, username: str) -> None:
""" """
remove user from storage remove user from storage
:param username: username
Args:
username(str): username
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute("""delete from users where username = :username""", {"username": username.lower()}) connection.execute("""delete from users where username = :username""", {"username": username.lower()})
@ -74,7 +84,9 @@ class AuthOperations(Operations):
def user_update(self, user: User) -> None: def user_update(self, user: User) -> None:
""" """
update user by username update user by username
:param user: user descriptor
Args:
user(User): user descriptor
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute( connection.execute(

View File

@ -32,7 +32,9 @@ class BuildOperations(Operations):
def build_queue_clear(self, package_base: Optional[str]) -> None: def build_queue_clear(self, package_base: Optional[str]) -> None:
""" """
remove packages from build queue remove packages from build queue
:param package_base: optional filter by package base
Args:
package_base(Optional[str]): optional filter by package base
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute( connection.execute(
@ -47,7 +49,9 @@ class BuildOperations(Operations):
def build_queue_get(self) -> List[Package]: def build_queue_get(self) -> List[Package]:
""" """
retrieve packages from build queue retrieve packages from build queue
:return: list of packages to be built
Return:
List[Package]: list of packages to be built
""" """
def run(connection: Connection) -> List[Package]: def run(connection: Connection) -> List[Package]:
return [ return [
@ -60,7 +64,9 @@ class BuildOperations(Operations):
def build_queue_insert(self, package: Package) -> None: def build_queue_insert(self, package: Package) -> None:
""" """
insert packages to build queue insert packages to build queue
:param package: package to be inserted
Args:
package(Package): package to be inserted
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute( connection.execute(

View File

@ -31,14 +31,18 @@ T = TypeVar("T")
class Operations: class Operations:
""" """
base operation class base operation class
:ivar logger: class logger
:ivar path: path to the database file Attributes:
logger(logging.Logger): class logger
path(Path): path to the database file
""" """
def __init__(self, path: Path) -> None: def __init__(self, path: Path) -> None:
""" """
default constructor default constructor
:param path: path to the database file
Args:
path(Path): path to the database file
""" """
self.path = path self.path = path
self.logger = logging.getLogger("database") self.logger = logging.getLogger("database")
@ -47,9 +51,13 @@ class Operations:
def factory(cursor: Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]: def factory(cursor: Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]:
""" """
dictionary factory based on official documentation dictionary factory based on official documentation
:param cursor: cursor descriptor
:param row: fetched row Args:
:return: row converted to dictionary cursor(Cursor): cursor descriptor
row(Tuple[Any, ...]): fetched row
Returns:
Dict[str, Any]: row converted to dictionary
""" """
result = {} result = {}
for index, column in enumerate(cursor.description): for index, column in enumerate(cursor.description):
@ -59,9 +67,13 @@ class Operations:
def with_connection(self, query: Callable[[Connection], T], commit: bool = False) -> T: def with_connection(self, query: Callable[[Connection], T], commit: bool = False) -> T:
""" """
perform operation in connection perform operation in connection
:param query: function to be called with connection
:param commit: if True commit() will be called on success Args:
:return: result of the `query` call query(Callable[[Connection], T]): function to be called with connection
commit(bool, optional): if True commit() will be called on success (Default value = False)
Returns:
T: result of the `query` call
""" """
with sqlite3.connect(self.path, detect_types=sqlite3.PARSE_DECLTYPES) as connection: with sqlite3.connect(self.path, detect_types=sqlite3.PARSE_DECLTYPES) as connection:
connection.row_factory = self.factory connection.row_factory = self.factory

View File

@ -35,8 +35,10 @@ class PackageOperations(Operations):
def _package_remove_package_base(connection: Connection, package_base: str) -> None: def _package_remove_package_base(connection: Connection, package_base: str) -> None:
""" """
remove package base information remove package base information
:param connection: database connection
:param package_base: package base name Args:
connection(Connection): database connection
package_base(str): package base name
""" """
connection.execute("""delete from package_statuses where package_base = :package_base""", connection.execute("""delete from package_statuses where package_base = :package_base""",
{"package_base": package_base}) {"package_base": package_base})
@ -47,9 +49,11 @@ class PackageOperations(Operations):
def _package_remove_packages(connection: Connection, package_base: str, current_packages: Iterable[str]) -> None: def _package_remove_packages(connection: Connection, package_base: str, current_packages: Iterable[str]) -> None:
""" """
remove packages belong to the package base remove packages belong to the package base
:param connection: database connection
:param package_base: package base name Args:
:param current_packages: current packages list which has to be left in database connection(Connection): database connection
package_base(str): package base name
current_packages(Iterable[str]): current packages list which has to be left in database
""" """
packages = [ packages = [
package package
@ -63,8 +67,10 @@ class PackageOperations(Operations):
def _package_update_insert_base(connection: Connection, package: Package) -> None: def _package_update_insert_base(connection: Connection, package: Package) -> None:
""" """
insert base package into table insert base package into table
:param connection: database connection
:param package: package properties Args:
connection(Connection): database connection
package(Package): package properties
""" """
connection.execute( connection.execute(
""" """
@ -81,8 +87,10 @@ class PackageOperations(Operations):
def _package_update_insert_packages(connection: Connection, package: Package) -> None: def _package_update_insert_packages(connection: Connection, package: Package) -> None:
""" """
insert packages into table insert packages into table
:param connection: database connection
:param package: package properties Args:
connection(Connection): database connection
package(Package): package properties
""" """
package_list = [] package_list = []
for name, description in package.packages.items(): for name, description in package.packages.items():
@ -108,9 +116,11 @@ class PackageOperations(Operations):
def _package_update_insert_status(connection: Connection, package_base: str, status: BuildStatus) -> None: def _package_update_insert_status(connection: Connection, package_base: str, status: BuildStatus) -> None:
""" """
insert base package status into table insert base package status into table
:param connection: database connection
:param package_base: package base name Args:
:param status: new build status connection(Connection): database connection
package_base(str): package base name
status(BuildStatus): new build status
""" """
connection.execute( connection.execute(
""" """
@ -126,8 +136,12 @@ class PackageOperations(Operations):
def _packages_get_select_package_bases(connection: Connection) -> Dict[str, Package]: def _packages_get_select_package_bases(connection: Connection) -> Dict[str, Package]:
""" """
select package bases from the table select package bases from the table
:param connection: database connection
:return: map of the package base to its descriptor (without packages themselves) Args:
connection(Connection): database connection
Returns:
Dict[str, Package]: map of the package base to its descriptor (without packages themselves)
""" """
return { return {
row["package_base"]: Package(row["package_base"], row["version"], row["aur_url"], {}) row["package_base"]: Package(row["package_base"], row["version"], row["aur_url"], {})
@ -138,9 +152,13 @@ class PackageOperations(Operations):
def _packages_get_select_packages(connection: Connection, packages: Dict[str, Package]) -> Dict[str, Package]: def _packages_get_select_packages(connection: Connection, packages: Dict[str, Package]) -> Dict[str, Package]:
""" """
select packages from the table select packages from the table
:param connection: database connection
:param packages: packages descriptor map Args:
:return: map of the package base to its descriptor including individual packages connection(Connection): database connection
packages(Dict[str, Package]): packages descriptor map
Returns:
Dict[str, Package]: map of the package base to its descriptor including individual packages
""" """
for row in connection.execute("""select * from packages"""): for row in connection.execute("""select * from packages"""):
if row["package_base"] not in packages: if row["package_base"] not in packages:
@ -152,8 +170,12 @@ class PackageOperations(Operations):
def _packages_get_select_statuses(connection: Connection) -> Dict[str, BuildStatus]: def _packages_get_select_statuses(connection: Connection) -> Dict[str, BuildStatus]:
""" """
select package build statuses from the table select package build statuses from the table
:param connection: database connection
:return: map of the package base to its status Args:
connection(Connection): database connection
Returns:
Dict[str, BuildStatus]: map of the package base to its status
""" """
return { return {
row["package_base"]: BuildStatus.from_json({"status": row["status"], "timestamp": row["last_updated"]}) row["package_base"]: BuildStatus.from_json({"status": row["status"], "timestamp": row["last_updated"]})
@ -163,7 +185,9 @@ class PackageOperations(Operations):
def package_remove(self, package_base: str) -> None: def package_remove(self, package_base: str) -> None:
""" """
remove package from database remove package from database
:param package_base: package base name
Args:
package_base(str): package base name
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
self._package_remove_packages(connection, package_base, []) self._package_remove_packages(connection, package_base, [])
@ -174,8 +198,10 @@ class PackageOperations(Operations):
def package_update(self, package: Package, status: BuildStatus) -> None: def package_update(self, package: Package, status: BuildStatus) -> None:
""" """
update package status update package status
:param package: package properties
:param status: new build status Args:
package(Package): package properties
status(BuildStatus): new build status
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
self._package_update_insert_base(connection, package) self._package_update_insert_base(connection, package)
@ -188,7 +214,9 @@ class PackageOperations(Operations):
def packages_get(self) -> List[Tuple[Package, BuildStatus]]: def packages_get(self) -> List[Tuple[Package, BuildStatus]]:
""" """
get package list and their build statuses from database get package list and their build statuses from database
:return: list of package properties and their statuses
Return:
List[Tuple[Package, BuildStatus]]: list of package properties and their statuses
""" """
def run(connection: Connection) -> Generator[Tuple[Package, BuildStatus], None, None]: def run(connection: Connection) -> Generator[Tuple[Package, BuildStatus], None, None]:
packages = self._packages_get_select_package_bases(connection) packages = self._packages_get_select_package_bases(connection)

View File

@ -31,16 +31,22 @@ class PatchOperations(Operations):
def patches_get(self, package_base: str) -> Optional[str]: def patches_get(self, package_base: str) -> Optional[str]:
""" """
retrieve patches for the package retrieve patches for the package
:param package_base: package base to search for patches
:return: plain text patch for the package Args:
package_base(str): package base to search for patches
Returns:
Optional[str]: plain text patch for the package
""" """
return self.patches_list(package_base).get(package_base) return self.patches_list(package_base).get(package_base)
def patches_insert(self, package_base: str, patch: str) -> None: def patches_insert(self, package_base: str, patch: str) -> None:
""" """
insert or update patch in database insert or update patch in database
:param package_base: package base to insert
:param patch: patch content Args:
package_base(str): package base to insert
patch(str): patch content
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute( connection.execute(
@ -59,8 +65,12 @@ class PatchOperations(Operations):
def patches_list(self, package_base: Optional[str]) -> Dict[str, str]: def patches_list(self, package_base: Optional[str]) -> Dict[str, str]:
""" """
extract all patches extract all patches
:param package_base: optional filter by package base
:return: map of package base to patch content Args:
package_base(Optional[str]): optional filter by package base
Returns:
Dict[str, str]: map of package base to patch content
""" """
def run(connection: Connection) -> Dict[str, str]: def run(connection: Connection) -> Dict[str, str]:
return { return {
@ -75,7 +85,9 @@ class PatchOperations(Operations):
def patches_remove(self, package_base: str) -> None: def patches_remove(self, package_base: str) -> None:
""" """
remove patch set remove patch set
:param package_base: package base to clear patches
Args:
package_base(str): package base to clear patches
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute( connection.execute(

View File

@ -44,8 +44,12 @@ class SQLite(AuthOperations, BuildOperations, PackageOperations, PatchOperations
def load(cls: Type[SQLite], configuration: Configuration) -> SQLite: def load(cls: Type[SQLite], configuration: Configuration) -> SQLite:
""" """
construct instance from configuration construct instance from configuration
:param configuration: configuration instance
:return: fully initialized instance of the database Args:
configuration(Configuration): configuration instance
Returns:
SQLite: fully initialized instance of the database
""" """
path = cls.database_path(configuration) path = cls.database_path(configuration)
database = cls(path) database = cls(path)
@ -56,15 +60,21 @@ class SQLite(AuthOperations, BuildOperations, PackageOperations, PatchOperations
def database_path(configuration: Configuration) -> Path: def database_path(configuration: Configuration) -> Path:
""" """
read database from configuration read database from configuration
:param configuration: configuration instance
:return: database path according to the configuration Args:
configuration(Configuration): configuration instance
Returns:
Path: database path according to the configuration
""" """
return configuration.getpath("settings", "database") return configuration.getpath("settings", "database")
def init(self, configuration: Configuration) -> None: def init(self, configuration: Configuration) -> None:
""" """
perform database migrations perform database migrations
:param configuration: configuration instance
Args:
configuration(Configuration): configuration instance
""" """
# custom types support # custom types support
sqlite3.register_adapter(dict, json.dumps) sqlite3.register_adapter(dict, json.dumps)

View File

@ -29,7 +29,9 @@ class BuildFailed(RuntimeError):
def __init__(self, package_base: str) -> None: def __init__(self, package_base: str) -> None:
""" """
default constructor default constructor
:param package_base: package base raised exception
Args:
package_base(str): package base raised exception
""" """
RuntimeError.__init__(self, f"Package {package_base} build failed, check logs for details") RuntimeError.__init__(self, f"Package {package_base} build failed, check logs for details")
@ -61,7 +63,9 @@ class InitializeException(RuntimeError):
def __init__(self, details: str) -> None: def __init__(self, details: str) -> None:
""" """
default constructor default constructor
:param details: details of the exception
Args:
details(str): details of the exception
""" """
RuntimeError.__init__(self, f"Could not load service: {details}") RuntimeError.__init__(self, f"Could not load service: {details}")
@ -74,7 +78,9 @@ class InvalidOption(ValueError):
def __init__(self, value: Any) -> None: def __init__(self, value: Any) -> None:
""" """
default constructor default constructor
:param value: option value
Args:
value(Any): option value
""" """
ValueError.__init__(self, f"Invalid or unknown option value `{value}`") ValueError.__init__(self, f"Invalid or unknown option value `{value}`")
@ -87,8 +93,10 @@ class InvalidPath(ValueError):
def __init__(self, path: Path, root: Path) -> None: def __init__(self, path: Path, root: Path) -> None:
""" """
default constructor default constructor
:param path: path which raised an exception
:param root: repository root (i.e. ahriman home) Args:
path(Path): path which raised an exception
root(Path): repository root (i.e. ahriman home)
""" """
ValueError.__init__(self, f"Path `{path}` does not belong to repository root `{root}`") ValueError.__init__(self, f"Path `{path}` does not belong to repository root `{root}`")
@ -101,7 +109,9 @@ class InvalidPackageInfo(RuntimeError):
def __init__(self, details: Any) -> None: def __init__(self, details: Any) -> None:
""" """
default constructor default constructor
:param details: error details
Args:
details(Any): error details
""" """
RuntimeError.__init__(self, f"There are errors during reading package information: `{details}`") RuntimeError.__init__(self, f"There are errors during reading package information: `{details}`")
@ -114,7 +124,9 @@ class MigrationError(RuntimeError):
def __init__(self, details: str) -> None: def __init__(self, details: str) -> None:
""" """
default constructor default constructor
:param details: error details
Args:
details(str): error details
""" """
RuntimeError.__init__(self, details) RuntimeError.__init__(self, details)
@ -127,7 +139,9 @@ class MissingArchitecture(ValueError):
def __init__(self, command: str) -> None: def __init__(self, command: str) -> None:
""" """
default constructor default constructor
:param command: command name which throws exception
Args:
command(str): command name which throws exception
""" """
ValueError.__init__(self, f"Architecture required for subcommand {command}, but missing") ValueError.__init__(self, f"Architecture required for subcommand {command}, but missing")
@ -140,7 +154,9 @@ class MultipleArchitectures(ValueError):
def __init__(self, command: str) -> None: def __init__(self, command: str) -> None:
""" """
default constructor default constructor
:param command: command name which throws exception
Args:
command(str): command name which throws exception
""" """
ValueError.__init__(self, f"Multiple architectures are not supported by subcommand {command}") ValueError.__init__(self, f"Multiple architectures are not supported by subcommand {command}")
@ -165,7 +181,9 @@ class SuccessFailed(ValueError):
def __init__(self, package_base: str) -> None: def __init__(self, package_base: str) -> None:
""" """
default constructor default constructor
:param package_base: package base name
Args:
package_base(str): package base name
""" """
ValueError.__init__(self, f"Package base {package_base} had status failed, but new status is success") ValueError.__init__(self, f"Package base {package_base} had status failed, but new status is success")
@ -190,7 +208,9 @@ class UnknownPackage(ValueError):
def __init__(self, package_base: str) -> None: def __init__(self, package_base: str) -> None:
""" """
default constructor default constructor
:param package_base: package base name
Args:
package_base(str): package base name
""" """
ValueError.__init__(self, f"Package base {package_base} is unknown") ValueError.__init__(self, f"Package base {package_base} is unknown")
@ -203,8 +223,10 @@ class UnsafeRun(RuntimeError):
def __init__(self, current_uid: int, root_uid: int) -> None: def __init__(self, current_uid: int, root_uid: int) -> None:
""" """
default constructor default constructor
:param current_uid: current user ID
:param root_uid: ID of the owner of root directory Args:
current_uid(int): current user ID
root_uid(int): ID of the owner of root directory
""" """
RuntimeError.__init__(self, f"Current UID {current_uid} differs from root owner {root_uid}. " RuntimeError.__init__(self, f"Current UID {current_uid} differs from root owner {root_uid}. "
f"Note that for the most actions it is unsafe to run application as different user." f"Note that for the most actions it is unsafe to run application as different user."

View File

@ -28,13 +28,17 @@ from ahriman.models.property import Property
class AurPrinter(StringPrinter): class AurPrinter(StringPrinter):
""" """
print content of the AUR package print content of the AUR package
:ivar package: AUR package description
Attributes:
package(AURPackage): AUR package description
""" """
def __init__(self, package: AURPackage) -> None: def __init__(self, package: AURPackage) -> None:
""" """
default constructor default constructor
:param package: AUR package description
Args:
package(AURPackage): AUR package description
""" """
StringPrinter.__init__(self, f"{package.name} {package.version} ({package.num_votes})") StringPrinter.__init__(self, f"{package.name} {package.version} ({package.num_votes})")
self.package = package self.package = package
@ -42,7 +46,9 @@ class AurPrinter(StringPrinter):
def properties(self) -> List[Property]: def properties(self) -> List[Property]:
""" """
convert content into printable data convert content into printable data
:return: list of content properties
Returns:
List[Property]: list of content properties
""" """
return [ return [
Property("Package base", self.package.package_base), Property("Package base", self.package.package_base),

View File

@ -29,9 +29,11 @@ class BuildPrinter(StringPrinter):
def __init__(self, package: Package, is_success: bool, use_utf: bool) -> None: def __init__(self, package: Package, is_success: bool, use_utf: bool) -> None:
""" """
default constructor default constructor
:param package: built package
:param is_success: True in case if build has success status and False otherwise Args:
:param use_utf: use utf instead of normal symbols package(Package): built package
is_success(bool): True in case if build has success status and False otherwise
use_utf(bool): use utf instead of normal symbols
""" """
StringPrinter.__init__(self, f"{self.sign(is_success, use_utf)} {package.base}") StringPrinter.__init__(self, f"{self.sign(is_success, use_utf)} {package.base}")
@ -39,9 +41,13 @@ class BuildPrinter(StringPrinter):
def sign(is_success: bool, use_utf: bool) -> str: def sign(is_success: bool, use_utf: bool) -> str:
""" """
generate sign according to settings generate sign according to settings
:param use_utf: use utf instead of normal symbols
:param is_success: True in case if build has success status and False otherwise Args:
:return: sign symbol according to current settings is_success(bool): True in case if build has success status and False otherwise
use_utf(bool): use utf instead of normal symbols
Returns:
str: sign symbol according to current settings
""" """
if is_success: if is_success:
return "[✔]" if use_utf else "[x]" return "[✔]" if use_utf else "[x]"

View File

@ -26,14 +26,18 @@ from ahriman.models.property import Property
class ConfigurationPrinter(StringPrinter): class ConfigurationPrinter(StringPrinter):
""" """
print content of the configuration section print content of the configuration section
:ivar values: configuration values dictionary
Attributes:
values(Dict[str, str]): configuration values dictionary
""" """
def __init__(self, section: str, values: Dict[str, str]) -> None: def __init__(self, section: str, values: Dict[str, str]) -> None:
""" """
default constructor default constructor
:param section: section name
:param values: configuration values dictionary Args:
section(str): section name
values(Dict[str, str]): configuration values dictionary
""" """
StringPrinter.__init__(self, f"[{section}]") StringPrinter.__init__(self, f"[{section}]")
self.values = values self.values = values
@ -41,7 +45,9 @@ class ConfigurationPrinter(StringPrinter):
def properties(self) -> List[Property]: def properties(self) -> List[Property]:
""" """
convert content into printable data convert content into printable data
:return: list of content properties
Returns:
List[Property]: list of content properties
""" """
return [ return [
Property(key, value, is_required=True) Property(key, value, is_required=True)

View File

@ -28,15 +28,19 @@ from ahriman.models.property import Property
class PackagePrinter(StringPrinter): class PackagePrinter(StringPrinter):
""" """
print content of the internal package object print content of the internal package object
:ivar package: package description
:ivar status: build status Attributes:
package(Package): package description
status(BuildStatus): build status
""" """
def __init__(self, package: Package, status: BuildStatus) -> None: def __init__(self, package: Package, status: BuildStatus) -> None:
""" """
default constructor default constructor
:param package: package description
:param status: build status Args:
package(Package): package description
status(BuildStatus): build status
""" """
StringPrinter.__init__(self, package.pretty_print()) StringPrinter.__init__(self, package.pretty_print())
self.package = package self.package = package
@ -45,7 +49,9 @@ class PackagePrinter(StringPrinter):
def properties(self) -> List[Property]: def properties(self) -> List[Property]:
""" """
convert content into printable data convert content into printable data
:return: list of content properties
Returns:
List[Property]: list of content properties
""" """
return [ return [
Property("Version", self.package.version, is_required=True), Property("Version", self.package.version, is_required=True),

View File

@ -30,9 +30,12 @@ class Printer:
def print(self, verbose: bool, log_fn: Callable[[str], None] = print, separator: str = ": ") -> None: def print(self, verbose: bool, log_fn: Callable[[str], None] = print, separator: str = ": ") -> None:
""" """
print content print content
:param verbose: print all fields
:param log_fn: logger function to log data Args:
:param separator: separator for property name and property value verbose(bool): print all fields
log_fn(Callable[[str]): logger function to log data
None]: (Default value = print)
separator(str, optional): separator for property name and property value (Default value = ": ")
""" """
if (title := self.title()) is not None: if (title := self.title()) is not None:
log_fn(title) log_fn(title)
@ -44,12 +47,16 @@ class Printer:
def properties(self) -> List[Property]: # pylint: disable=no-self-use def properties(self) -> List[Property]: # pylint: disable=no-self-use
""" """
convert content into printable data convert content into printable data
:return: list of content properties
Returns:
List[Property]: list of content properties
""" """
return [] return []
def title(self) -> Optional[str]: def title(self) -> Optional[str]:
""" """
generate entry title from content generate entry title from content
:return: content title if it can be generated and None otherwise
Returns:
Optional[str]: content title if it can be generated and None otherwise
""" """

View File

@ -29,6 +29,8 @@ class StatusPrinter(StringPrinter):
def __init__(self, status: BuildStatus) -> None: def __init__(self, status: BuildStatus) -> None:
""" """
default constructor default constructor
:param status: build status
Args:
status(BuildStatus): build status
""" """
StringPrinter.__init__(self, status.pretty_print()) StringPrinter.__init__(self, status.pretty_print())

View File

@ -30,13 +30,17 @@ class StringPrinter(Printer):
def __init__(self, content: str) -> None: def __init__(self, content: str) -> None:
""" """
default constructor default constructor
:param content: any content string
Args:
content(str): any content string
""" """
self.content = content self.content = content
def title(self) -> Optional[str]: def title(self) -> Optional[str]:
""" """
generate entry title from content generate entry title from content
:return: content title if it can be generated and None otherwise
Returns:
Optional[str]: content title if it can be generated and None otherwise
""" """
return self.content return self.content

View File

@ -27,15 +27,19 @@ from ahriman.models.property import Property
class UpdatePrinter(StringPrinter): class UpdatePrinter(StringPrinter):
""" """
print content of the package update print content of the package update
:ivar package: remote (new) package object
:ivar local_version: local version of the package if any Attributes:
package(Package): remote (new) package object
local_version(Optional[str]): local version of the package if any
""" """
def __init__(self, remote: Package, local_version: Optional[str]) -> None: def __init__(self, remote: Package, local_version: Optional[str]) -> None:
""" """
default constructor default constructor
:param remote: remote (new) package object
:param local_version: local version of the package if any Args:
remote(Package): remote (new) package object
local_version(Optional[str]): local version of the package if any
""" """
StringPrinter.__init__(self, remote.base) StringPrinter.__init__(self, remote.base)
self.package = remote self.package = remote
@ -44,6 +48,8 @@ class UpdatePrinter(StringPrinter):
def properties(self) -> List[Property]: def properties(self) -> List[Property]:
""" """
convert content into printable data convert content into printable data
:return: list of content properties
Returns:
List[Property]: list of content properties
""" """
return [Property(self.local_version, self.package.version, is_required=True)] return [Property(self.local_version, self.package.version, is_required=True)]

View File

@ -27,13 +27,17 @@ from ahriman.models.user import User
class UserPrinter(StringPrinter): class UserPrinter(StringPrinter):
""" """
print properties of user print properties of user
:ivar user: stored user
Attributes:
user(User): stored user
""" """
def __init__(self, user: User) -> None: def __init__(self, user: User) -> None:
""" """
default constructor default constructor
:param user: user to print
Args:
user(User): user to print
""" """
StringPrinter.__init__(self, user.username) StringPrinter.__init__(self, user.username)
self.user = user self.user = user
@ -41,6 +45,8 @@ class UserPrinter(StringPrinter):
def properties(self) -> List[Property]: def properties(self) -> List[Property]:
""" """
convert content into printable data convert content into printable data
:return: list of content properties
Returns:
List[Property]: list of content properties
""" """
return [Property("role", self.user.access.value, is_required=True)] return [Property("role", self.user.access.value, is_required=True)]

View File

@ -29,15 +29,19 @@ from ahriman.models.result import Result
class Console(Report): class Console(Report):
""" """
html report generator html report generator
:ivar use_utf: print utf8 symbols instead of ASCII
Attributes:
use_utf(bool): print utf8 symbols instead of ASCII
""" """
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param section: settings section name architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str): settings section name
""" """
Report.__init__(self, architecture, configuration) Report.__init__(self, architecture, configuration)
self.use_utf = configuration.getboolean(section, "use_utf", fallback=True) self.use_utf = configuration.getboolean(section, "use_utf", fallback=True)
@ -45,8 +49,10 @@ class Console(Report):
def generate(self, packages: Iterable[Package], result: Result) -> None: def generate(self, packages: Iterable[Package], result: Result) -> None:
""" """
generate report for the specified packages generate report for the specified packages
:param packages: list of packages to generate report
:param result: build result Args:
packages(Iterable[Package]): list of packages to generate report
result(Result): build result
""" """
for package in result.success: for package in result.success:
BuildPrinter(package, is_success=True, use_utf=self.use_utf).print(verbose=True) BuildPrinter(package, is_success=True, use_utf=self.use_utf).print(verbose=True)

View File

@ -36,24 +36,28 @@ from ahriman.models.smtp_ssl_settings import SmtpSSLSettings
class Email(Report, JinjaTemplate): class Email(Report, JinjaTemplate):
""" """
email report generator email report generator
:ivar full_template_path: path to template for full package list
:ivar host: SMTP host to connect Attributes:
:ivar no_empty_report: skip empty report generation full_template_path(Path): path to template for full package list
:ivar password: password to authenticate via SMTP host(str): SMTP host to connect
:ivar port: SMTP port to connect no_empty_report(bool): skip empty report generation
:ivar receivers: list of receivers emails password(Optional[str]): password to authenticate via SMTP
:ivar sender: sender email address port(int): SMTP port to connect
:ivar ssl: SSL mode for SMTP connection receivers(List[str]): list of receivers emails
:ivar template_path: path to template for built packages sender(str): sender email address
:ivar user: username to authenticate via SMTP ssl(SmtpSSLSettings): SSL mode for SMTP connection
template_path(Path): path to template for built packages
user(Optional[str]): username to authenticate via SMTP
""" """
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param section: settings section name architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str): settings section name
""" """
Report.__init__(self, architecture, configuration) Report.__init__(self, architecture, configuration)
JinjaTemplate.__init__(self, section, configuration) JinjaTemplate.__init__(self, section, configuration)
@ -74,8 +78,10 @@ class Email(Report, JinjaTemplate):
def _send(self, text: str, attachment: Dict[str, str]) -> None: def _send(self, text: str, attachment: Dict[str, str]) -> None:
""" """
send email callback send email callback
:param text: email body text
:param attachment: map of attachment filename to attachment content Args:
text(str): email body text
attachment(Dict[str, str]): map of attachment filename to attachment content
""" """
message = MIMEMultipart() message = MIMEMultipart()
message["From"] = self.sender message["From"] = self.sender
@ -102,8 +108,10 @@ class Email(Report, JinjaTemplate):
def generate(self, packages: Iterable[Package], result: Result) -> None: def generate(self, packages: Iterable[Package], result: Result) -> None:
""" """
generate report for the specified packages generate report for the specified packages
:param packages: list of packages to generate report
:param result: build result Args:
packages(Iterable[Package]): list of packages to generate report
result(Result): build result
""" """
if self.no_empty_report and not result.success: if self.no_empty_report and not result.success:
return return

View File

@ -29,16 +29,20 @@ from ahriman.models.result import Result
class HTML(Report, JinjaTemplate): class HTML(Report, JinjaTemplate):
""" """
html report generator html report generator
:ivar report_path: output path to html report
:ivar template_path: path to template for full package list Attributes:
report_path(Path): output path to html report
template_path(Path): path to template for full package list
""" """
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param section: settings section name architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str): settings section name
""" """
Report.__init__(self, architecture, configuration) Report.__init__(self, architecture, configuration)
JinjaTemplate.__init__(self, section, configuration) JinjaTemplate.__init__(self, section, configuration)
@ -49,8 +53,10 @@ class HTML(Report, JinjaTemplate):
def generate(self, packages: Iterable[Package], result: Result) -> None: def generate(self, packages: Iterable[Package], result: Result) -> None:
""" """
generate report for the specified packages generate report for the specified packages
:param packages: list of packages to generate report
:param result: build result Args:
packages(Iterable[Package]): list of packages to generate report
result(Result): build result
""" """
html = self.make_html(Result(success=packages), self.template_path) html = self.make_html(Result(success=packages), self.template_path)
self.report_path.write_text(html) self.report_path.write_text(html)

View File

@ -32,9 +32,9 @@ from ahriman.models.sign_settings import SignSettings
class JinjaTemplate: class JinjaTemplate:
""" """
jinja based report generator jinja based report generator
It uses jinja2 templates for report generation, the following variables are allowed: It uses jinja2 templates for report generation, the following variables are allowed:
homepage - link to homepage, string, optional homepage - link to homepage, string, optional
link_path - prefix fo packages to download, string, required link_path - prefix fo packages to download, string, required
has_package_signed - True in case if package sign enabled, False otherwise, required has_package_signed - True in case if package sign enabled, False otherwise, required
@ -55,18 +55,21 @@ class JinjaTemplate:
pgp_key - default PGP key ID, string, optional pgp_key - default PGP key ID, string, optional
repository - repository name, string, required repository - repository name, string, required
:ivar homepage: homepage link if any (for footer) Attributes:
:ivar link_path: prefix fo packages to download homepage(Optional[str]): homepage link if any (for footer)
:ivar name: repository name link_path(str): prefix fo packages to download
:ivar default_pgp_key: default PGP key name(str): repository name
:ivar sign_targets: targets to sign enabled in configuration default_pgp_key(Optional[str]): default PGP key
sign_targets(Set[SignSettings]): targets to sign enabled in configuration
""" """
def __init__(self, section: str, configuration: Configuration) -> None: def __init__(self, section: str, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param section: settings section name
:param configuration: configuration instance Args:
section(str): settings section name
configuration(Configuration): configuration instance
""" """
self.link_path = configuration.get(section, "link_path") self.link_path = configuration.get(section, "link_path")
@ -79,8 +82,10 @@ class JinjaTemplate:
def make_html(self, result: Result, template_path: Path) -> str: def make_html(self, result: Result, template_path: Path) -> str:
""" """
generate report for the specified packages generate report for the specified packages
:param result: build result
:param template_path: path to jinja template Args:
result(Result): build result
template_path(Path): path to jinja template
""" """
# idea comes from https://stackoverflow.com/a/38642558 # idea comes from https://stackoverflow.com/a/38642558
loader = jinja2.FileSystemLoader(searchpath=template_path.parent) loader = jinja2.FileSystemLoader(searchpath=template_path.parent)

View File

@ -33,16 +33,20 @@ from ahriman.models.result import Result
class Report: class Report:
""" """
base report generator base report generator
:ivar architecture: repository architecture
:ivar configuration: configuration instance Attributes:
:ivar logger: class logger architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): class logger
""" """
def __init__(self, architecture: str, configuration: Configuration) -> None: def __init__(self, architecture: str, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
""" """
self.logger = logging.getLogger("root") self.logger = logging.getLogger("root")
self.architecture = architecture self.architecture = architecture
@ -52,10 +56,14 @@ class Report:
def load(cls: Type[Report], architecture: str, configuration: Configuration, target: str) -> Report: def load(cls: Type[Report], architecture: str, configuration: Configuration, target: str) -> Report:
""" """
load client from settings load client from settings
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param target: target to generate report aka section name (e.g. html) architecture(str): repository architecture
:return: client according to current settings configuration(Configuration): configuration instance
target(str): target to generate report aka section name (e.g. html)
Returns:
Report: client according to current settings
""" """
section, provider_name = configuration.gettype(target, architecture) section, provider_name = configuration.gettype(target, architecture)
provider = ReportSettings.from_option(provider_name) provider = ReportSettings.from_option(provider_name)
@ -76,15 +84,19 @@ class Report:
def generate(self, packages: Iterable[Package], result: Result) -> None: def generate(self, packages: Iterable[Package], result: Result) -> None:
""" """
generate report for the specified packages generate report for the specified packages
:param packages: list of packages to generate report
:param result: build result Args:
packages(Iterable[Package]): list of packages to generate report
result(Result): build result
""" """
def run(self, packages: Iterable[Package], result: Result) -> None: def run(self, packages: Iterable[Package], result: Result) -> None:
""" """
run report generation run report generation
:param packages: list of packages to generate report
:param result: build result Args:
packages(Iterable[Package]): list of packages to generate report
result(Result): build result
""" """
try: try:
self.generate(packages, result) self.generate(packages, result)

View File

@ -33,12 +33,14 @@ from ahriman.models.result import Result
class Telegram(Report, JinjaTemplate): class Telegram(Report, JinjaTemplate):
""" """
telegram report generator telegram report generator
:cvar TELEGRAM_API_URL: telegram api base url
:cvar TELEGRAM_MAX_CONTENT_LENGTH: max content length of the message Attributes:
:ivar api_key: bot api key TELEGRAM_API_URL(str): (class attribute) telegram api base url
:ivar chat_id: chat id to post message, either string with @ or integer TELEGRAM_MAX_CONTENT_LENGTH(int): (class attribute) max content length of the message
:ivar template_path: path to template for built packages api_key(str): bot api key
:ivar template_type: template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown chat_id(str): chat id to post message, either string with @ or integer
template_path(Path): path to template for built packages
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_API_URL = "https://api.telegram.org"
@ -47,9 +49,11 @@ class Telegram(Report, JinjaTemplate):
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param section: settings section name architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str): settings section name
""" """
Report.__init__(self, architecture, configuration) Report.__init__(self, architecture, configuration)
JinjaTemplate.__init__(self, section, configuration) JinjaTemplate.__init__(self, section, configuration)
@ -62,7 +66,9 @@ class Telegram(Report, JinjaTemplate):
def _send(self, text: str) -> None: def _send(self, text: str) -> None:
""" """
send message to telegram channel send message to telegram channel
:param text: message body text
Args:
text(str): message body text
""" """
try: try:
response = requests.post( response = requests.post(
@ -79,8 +85,10 @@ class Telegram(Report, JinjaTemplate):
def generate(self, packages: Iterable[Package], result: Result) -> None: def generate(self, packages: Iterable[Package], result: Result) -> None:
""" """
generate report for the specified packages generate report for the specified packages
:param packages: list of packages to generate report
:param result: build result Args:
packages(Iterable[Package]): list of packages to generate report
result(Result): build result
""" """
if not result.success: if not result.success:
return return

View File

@ -33,7 +33,9 @@ class Cleaner(Properties):
def packages_built(self) -> List[Path]: def packages_built(self) -> List[Path]:
""" """
get list of files in built packages directory get list of files in built packages directory
:return: list of filenames from the directory
Returns:
List[Path]: list of filenames from the directory
""" """
raise NotImplementedError raise NotImplementedError

View File

@ -39,23 +39,33 @@ class Executor(Cleaner):
def load_archives(self, packages: Iterable[Path]) -> List[Package]: def load_archives(self, packages: Iterable[Path]) -> List[Package]:
""" """
load packages from list of archives load packages from list of archives
:param packages: paths to package archives
:return: list of read packages Args:
packages(Iterable[Path]): paths to package archives
Returns:
List[Package]: list of read packages
""" """
raise NotImplementedError raise NotImplementedError
def packages(self) -> List[Package]: def packages(self) -> List[Package]:
""" """
generate list of repository packages generate list of repository packages
:return: list of packages properties
Returns:
List[Package]: list of packages properties
""" """
raise NotImplementedError raise NotImplementedError
def process_build(self, updates: Iterable[Package]) -> Result: def process_build(self, updates: Iterable[Package]) -> Result:
""" """
build packages build packages
:param updates: list of packages properties to build
:return: `packages_built` Args:
updates(Iterable[Package]): list of packages properties to build
Returns:
Result: build result
""" """
def build_single(package: Package, local_path: Path) -> None: def build_single(package: Package, local_path: Path) -> None:
self.reporter.set_building(package.base) self.reporter.set_building(package.base)
@ -82,8 +92,12 @@ class Executor(Cleaner):
def process_remove(self, packages: Iterable[str]) -> Path: def process_remove(self, packages: Iterable[str]) -> Path:
""" """
remove packages from list remove packages from list
:param packages: list of package names or bases to remove
:return: path to repository database Args:
packages(Iterable[str]): list of package names or bases to remove
Returns:
Path: path to repository database
""" """
def remove_base(package_base: str) -> None: def remove_base(package_base: str) -> None:
try: try:
@ -126,8 +140,10 @@ class Executor(Cleaner):
def process_report(self, targets: Optional[Iterable[str]], result: Result) -> None: def process_report(self, targets: Optional[Iterable[str]], result: Result) -> None:
""" """
generate reports generate reports
:param targets: list of targets to generate reports. Configuration option will be used if it is not set
:param result: build result Args:
targets(Optional[Iterable[str]]): list of targets to generate reports. Configuration option will be used if it is not set
result(Result): build result
""" """
if targets is None: if targets is None:
targets = self.configuration.getlist("report", "target") targets = self.configuration.getlist("report", "target")
@ -138,8 +154,10 @@ class Executor(Cleaner):
def process_sync(self, targets: Optional[Iterable[str]], built_packages: Iterable[Package]) -> None: def process_sync(self, targets: Optional[Iterable[str]], built_packages: Iterable[Package]) -> None:
""" """
process synchronization to remote servers process synchronization to remote servers
:param targets: list of targets to sync. Configuration option will be used if it is not set
:param built_packages: list of packages which has just been built Args:
targets(Optional[Iterable[str]]): list of targets to sync. Configuration option will be used if it is not set
built_packages(Iterable[Package]): list of packages which has just been built
""" """
if targets is None: if targets is None:
targets = self.configuration.getlist("upload", "target") targets = self.configuration.getlist("upload", "target")
@ -150,8 +168,12 @@ class Executor(Cleaner):
def process_update(self, packages: Iterable[Path]) -> Result: def process_update(self, packages: Iterable[Path]) -> Result:
""" """
sign packages, add them to repository and update repository database sign packages, add them to repository and update repository database
:param packages: list of filenames to run
:return: path to repository database Args:
packages(Iterable[Path]): list of filenames to run
Returns:
Result: path to repository database
""" """
def update_single(name: Optional[str], base: str) -> None: def update_single(name: Optional[str], base: str) -> None:
if name is None: if name is None:

View File

@ -32,29 +32,33 @@ from ahriman.core.util import check_user
class Properties: class Properties:
""" """
repository internal objects holder repository internal objects holder
:ivar architecture: repository architecture
:ivar aur_url: base AUR url Attributes:
:ivar configuration: configuration instance architecture(str): repository architecture
:ivar database: database instance aur_url(str): base AUR url
:ivar ignore_list: package bases which will be ignored during auto updates configuration(Configuration): configuration instance
:ivar logger: class logger database(SQLite): database instance
:ivar name: repository name ignore_list(List[str]): package bases which will be ignored during auto updates
:ivar pacman: alpm wrapper instance logger(logging.Logger): class logger
:ivar paths: repository paths instance name(str): repository name
:ivar repo: repo commands wrapper instance pacman(Pacman): alpm wrapper instance
:ivar reporter: build status reporter instance paths(RepositoryPaths): repository paths instance
:ivar sign: GPG wrapper instance repo(Repo): repo commands wrapper instance
reporter(Client): build status reporter instance
sign(GPG): GPG wrapper instance
""" """
def __init__(self, architecture: str, configuration: Configuration, database: SQLite, def __init__(self, architecture: str, configuration: Configuration, database: SQLite,
no_report: bool, unsafe: bool) -> None: no_report: bool, unsafe: bool) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param database: database instance architecture(str): repository architecture
:param no_report: force disable reporting configuration(Configuration): configuration instance
:param unsafe: if set no user check will be performed before path creation database(SQLite): database instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
""" """
self.logger = logging.getLogger("root") self.logger = logging.getLogger("root")
self.architecture = architecture self.architecture = architecture

View File

@ -35,8 +35,12 @@ class Repository(Executor, UpdateHandler):
def load_archives(self, packages: Iterable[Path]) -> List[Package]: def load_archives(self, packages: Iterable[Path]) -> List[Package]:
""" """
load packages from list of archives load packages from list of archives
:param packages: paths to package archives
:return: list of read packages Args:
packages(Iterable[Path]): paths to package archives
Returns:
List[Package]: list of read packages
""" """
result: Dict[str, Package] = {} result: Dict[str, Package] = {}
# we are iterating over bases, not single packages # we are iterating over bases, not single packages
@ -58,22 +62,30 @@ class Repository(Executor, UpdateHandler):
def packages(self) -> List[Package]: def packages(self) -> List[Package]:
""" """
generate list of repository packages generate list of repository packages
:return: list of packages properties
Returns:
List[Package]: list of packages properties
""" """
return self.load_archives(filter(package_like, self.paths.repository.iterdir())) return self.load_archives(filter(package_like, self.paths.repository.iterdir()))
def packages_built(self) -> List[Path]: def packages_built(self) -> List[Path]:
""" """
get list of files in built packages directory get list of files in built packages directory
:return: list of filenames from the directory
Returns:
List[Path]: list of filenames from the directory
""" """
return list(filter(package_like, self.paths.packages.iterdir())) return list(filter(package_like, self.paths.packages.iterdir()))
def packages_depends_on(self, depends_on: Optional[Iterable[str]]) -> List[Package]: def packages_depends_on(self, depends_on: Optional[Iterable[str]]) -> List[Package]:
""" """
extract list of packages which depends on specified package extract list of packages which depends on specified package
:param: depends_on: dependencies of the packages
:return: list of repository packages which depend on specified packages Args:
depends_on(Optional[Iterable[str]]):
Returns:
List[Package]: list of repository packages which depend on specified packages
""" """
packages = self.packages() packages = self.packages()
if depends_on is None: if depends_on is None:

View File

@ -33,16 +33,22 @@ class UpdateHandler(Cleaner):
def packages(self) -> List[Package]: def packages(self) -> List[Package]:
""" """
generate list of repository packages generate list of repository packages
:return: list of packages properties
Returns:
List[Package]: list of packages properties
""" """
raise NotImplementedError raise NotImplementedError
def updates_aur(self, filter_packages: Iterable[str], no_vcs: bool) -> List[Package]: def updates_aur(self, filter_packages: Iterable[str], no_vcs: bool) -> List[Package]:
""" """
check AUR for updates check AUR for updates
:param filter_packages: do not check every package just specified in the list
:param no_vcs: do not check VCS packages Args:
:return: list of packages which are out-of-dated filter_packages(Iterable[str]): do not check every package just specified in the list
no_vcs(bool): do not check VCS packages
Returns:
List[Package]: list of packages which are out-of-dated
""" """
result: List[Package] = [] result: List[Package] = []
@ -70,7 +76,9 @@ class UpdateHandler(Cleaner):
def updates_local(self) -> List[Package]: def updates_local(self) -> List[Package]:
""" """
check local packages for updates check local packages for updates
:return: list of local packages which are out-of-dated
Returns:
List[Package]: list of local packages which are out-of-dated
""" """
result: List[Package] = [] result: List[Package] = []
packages = {local.base: local for local in self.packages()} packages = {local.base: local for local in self.packages()}
@ -97,7 +105,9 @@ class UpdateHandler(Cleaner):
def updates_manual(self) -> List[Package]: def updates_manual(self) -> List[Package]:
""" """
check for packages for which manual update has been requested check for packages for which manual update has been requested
:return: list of packages which are out-of-dated
Returns:
List[Package]: list of packages which are out-of-dated
""" """
result: List[Package] = [] result: List[Package] = []
known_bases = {package.base for package in self.packages()} known_bases = {package.base for package in self.packages()}

View File

@ -32,11 +32,13 @@ from ahriman.models.sign_settings import SignSettings
class GPG: class GPG:
""" """
gnupg wrapper gnupg wrapper
:ivar architecture: repository architecture
:ivar configuration: configuration instance Attributes:
:ivar default_key: default PGP key ID to use architecture(str): repository architecture
:ivar logger: class logger configuration(Configuration): configuration instance
:ivar targets: list of targets to sign (repository, package etc) default_key(Optional[str]): default PGP key ID to use
logger(logging.Logger): class logger
targets(Set[SignSettings]): list of targets to sign (repository, package etc)
""" """
_check_output = check_output _check_output = check_output
@ -44,8 +46,10 @@ class GPG:
def __init__(self, architecture: str, configuration: Configuration) -> None: def __init__(self, architecture: str, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
""" """
self.logger = logging.getLogger("build_details") self.logger = logging.getLogger("build_details")
self.architecture = architecture self.architecture = architecture
@ -55,7 +59,8 @@ class GPG:
@property @property
def repository_sign_args(self) -> List[str]: def repository_sign_args(self) -> List[str]:
""" """
:return: command line arguments for repo-add command to sign database Returns:
List[str]: command line arguments for repo-add command to sign database
""" """
if SignSettings.Repository not in self.targets: if SignSettings.Repository not in self.targets:
return [] return []
@ -68,9 +73,13 @@ class GPG:
def sign_command(path: Path, key: str) -> List[str]: def sign_command(path: Path, key: str) -> List[str]:
""" """
gpg command to run gpg command to run
:param path: path to file to sign
:param key: PGP key ID Args:
:return: gpg command with all required arguments path(Path): path to file to sign
key(str): PGP key ID
Returns:
List[str]: gpg command with all required arguments
""" """
return ["gpg", "-u", key, "-b", str(path)] return ["gpg", "-u", key, "-b", str(path)]
@ -78,8 +87,12 @@ class GPG:
def sign_options(configuration: Configuration) -> Tuple[Set[SignSettings], Optional[str]]: def sign_options(configuration: Configuration) -> Tuple[Set[SignSettings], Optional[str]]:
""" """
extract default sign options from configuration extract default sign options from configuration
:param configuration: configuration instance
:return: tuple of sign targets and default PGP key Args:
configuration(Configuration): configuration instance
Returns:
Tuple[Set[SignSettings], Optional[str]]: tuple of sign targets and default PGP key
""" """
targets = { targets = {
SignSettings.from_option(option) SignSettings.from_option(option)
@ -91,9 +104,13 @@ class GPG:
def key_download(self, server: str, key: str) -> str: def key_download(self, server: str, key: str) -> str:
""" """
download key from public PGP server download key from public PGP server
:param server: public PGP server which will be used to download the key
:param key: key ID to download Args:
:return: key as plain text server(str): public PGP server which will be used to download the key
key(str): key ID to download
Returns:
str: key as plain text
""" """
key = key if key.startswith("0x") else f"0x{key}" key = key if key.startswith("0x") else f"0x{key}"
try: try:
@ -111,8 +128,10 @@ class GPG:
def key_import(self, server: str, key: str) -> None: def key_import(self, server: str, key: str) -> None:
""" """
import key to current user and sign it locally import key to current user and sign it locally
:param server: public PGP server which will be used to download the key
:param key: key ID to import Args:
server(str): public PGP server which will be used to download the key
key(str): key ID to import
""" """
key_body = self.key_download(server, key) key_body = self.key_download(server, key)
GPG._check_output("gpg", "--import", input_data=key_body, exception=None, logger=self.logger) GPG._check_output("gpg", "--import", input_data=key_body, exception=None, logger=self.logger)
@ -120,9 +139,13 @@ class GPG:
def process(self, path: Path, key: str) -> List[Path]: def process(self, path: Path, key: str) -> List[Path]:
""" """
gpg command wrapper gpg command wrapper
:param path: path to file to sign
:param key: PGP key ID Args:
:return: list of generated files including original file path(Path): path to file to sign
key(str): PGP key ID
Returns:
List[Path]: list of generated files including original file
""" """
GPG._check_output( GPG._check_output(
*GPG.sign_command(path, key), *GPG.sign_command(path, key),
@ -133,9 +156,13 @@ class GPG:
def process_sign_package(self, path: Path, base: str) -> List[Path]: def process_sign_package(self, path: Path, base: str) -> List[Path]:
""" """
sign package if required by configuration sign package if required by configuration
:param path: path to file to sign
:param base: package base required to check for key overrides Args:
:return: list of generated files including original file path(Path): path to file to sign
base(str): package base required to check for key overrides
Returns:
List[Path]: list of generated files including original file
""" """
if SignSettings.Packages not in self.targets: if SignSettings.Packages not in self.targets:
return [path] return [path]
@ -149,8 +176,12 @@ class GPG:
""" """
sign repository if required by configuration sign repository if required by configuration
:note: more likely you just want to pass `repository_sign_args` to repo wrapper :note: more likely you just want to pass `repository_sign_args` to repo wrapper
:param path: path to repository database
:return: list of generated files including original file Args:
path(Path): path to repository database
Returns:
List[Path]: list of generated files including original file
""" """
if SignSettings.Repository not in self.targets: if SignSettings.Repository not in self.targets:
return [path] return [path]

View File

@ -35,19 +35,23 @@ class Spawn(Thread):
""" """
helper to spawn external ahriman process helper to spawn external ahriman process
MUST NOT be used directly, the only one usage allowed is to spawn process from web services MUST NOT be used directly, the only one usage allowed is to spawn process from web services
:ivar active: map of active child processes required to avoid zombies
:ivar architecture: repository architecture Attributes:
:ivar configuration: configuration instance active(Dict[str, Process]): map of active child processes required to avoid zombies
:ivar logger: spawner logger architecture(str): repository architecture
:ivar queue: multiprocessing queue to read updates from processes configuration(Configuration): configuration instance
logger(logging.Logger): spawner logger
queue(Queue[Tuple[str, bool]]): multiprocessing queue to read updates from processes
""" """
def __init__(self, args_parser: argparse.ArgumentParser, architecture: str, configuration: Configuration) -> None: def __init__(self, args_parser: argparse.ArgumentParser, architecture: str, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param args_parser: command line parser for the application
:param architecture: repository architecture Args:
:param configuration: configuration instance args_parser(argparse.ArgumentParser): command line parser for the application
architecture(str): repository architecture
configuration(Configuration): configuration instance
""" """
Thread.__init__(self, name="spawn") Thread.__init__(self, name="spawn")
self.architecture = architecture self.architecture = architecture
@ -65,11 +69,13 @@ class Spawn(Thread):
process_id: str, queue: Queue[Tuple[str, bool]]) -> None: # pylint: disable=unsubscriptable-object process_id: str, queue: Queue[Tuple[str, bool]]) -> None: # pylint: disable=unsubscriptable-object
""" """
helper to run external process helper to run external process
:param callback: application run function (i.e. Handler.run method)
:param args: command line arguments Args:
:param architecture: repository architecture callback(Callable[[argparse.Namespace, str], bool]): application run function (i.e. Handler.run method)
:param process_id: process unique identifier args(argparse.Namespace): command line arguments
:param queue: output queue architecture(str): repository architecture
process_id(str): process unique identifier
queue(Queue[Tuple[str, bool]]): output queue
""" """
result = callback(args, architecture) result = callback(args, architecture)
queue.put((process_id, result)) queue.put((process_id, result))
@ -77,8 +83,10 @@ class Spawn(Thread):
def packages_add(self, packages: Iterable[str], now: bool) -> None: def packages_add(self, packages: Iterable[str], now: bool) -> None:
""" """
add packages add packages
:param packages: packages list to add
:param now: build packages now Args:
packages(Iterable[str]): packages list to add
now(bool): build packages now
""" """
kwargs = {"source": PackageSource.AUR.value} # avoid abusing by building non-aur packages kwargs = {"source": PackageSource.AUR.value} # avoid abusing by building non-aur packages
if now: if now:
@ -88,16 +96,20 @@ class Spawn(Thread):
def packages_remove(self, packages: Iterable[str]) -> None: def packages_remove(self, packages: Iterable[str]) -> None:
""" """
remove packages remove packages
:param packages: packages list to remove
Args:
packages(Iterable[str]): packages list to remove
""" """
self.spawn_process("remove", *packages) self.spawn_process("remove", *packages)
def spawn_process(self, command: str, *args: str, **kwargs: str) -> None: def spawn_process(self, command: str, *args: str, **kwargs: str) -> None:
""" """
spawn external ahriman process with supplied arguments spawn external ahriman process with supplied arguments
:param command: subcommand to run
:param args: positional command arguments Args:
:param kwargs: named command arguments command(str): subcommand to run
*args(str): positional command arguments
**kwargs(str): named command arguments
""" """
# default arguments # default arguments
arguments = ["--architecture", self.architecture] arguments = ["--architecture", self.architecture]

View File

@ -36,8 +36,12 @@ class Client:
def load(cls: Type[Client], configuration: Configuration) -> Client: def load(cls: Type[Client], configuration: Configuration) -> Client:
""" """
load client from settings load client from settings
:param configuration: configuration instance
:return: client according to current settings Args:
configuration(Configuration): configuration instance
Returns:
Client: client according to current settings
""" """
address = configuration.get("web", "address", fallback=None) address = configuration.get("web", "address", fallback=None)
host = configuration.get("web", "host", fallback=None) host = configuration.get("web", "host", fallback=None)
@ -50,15 +54,21 @@ class Client:
def add(self, package: Package, status: BuildStatusEnum) -> None: def add(self, package: Package, status: BuildStatusEnum) -> None:
""" """
add new package with status add new package with status
:param package: package properties
:param status: current package build status Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
""" """
def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]: # pylint: disable=no-self-use def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]: # pylint: disable=no-self-use
""" """
get package status get package status
:param base: package base to get
:return: list of current package description and status if it has been found Args:
base(Optional[str]): package base to get
Returns:
List[Tuple[Package, BuildStatus]]: list of current package description and status if it has been found
""" """
del base del base
return [] return []
@ -66,67 +76,87 @@ class Client:
def get_internal(self) -> InternalStatus: # pylint: disable=no-self-use def get_internal(self) -> InternalStatus: # pylint: disable=no-self-use
""" """
get internal service status get internal service status
:return: current internal (web) service status
Returns:
InternalStatus: current internal (web) service status
""" """
return InternalStatus() return InternalStatus()
def get_self(self) -> BuildStatus: # pylint: disable=no-self-use def get_self(self) -> BuildStatus: # pylint: disable=no-self-use
""" """
get ahriman status itself get ahriman status itself
:return: current ahriman status
Returns:
BuildStatus: current ahriman status
""" """
return BuildStatus() return BuildStatus()
def remove(self, base: str) -> None: def remove(self, base: str) -> None:
""" """
remove packages from watcher remove packages from watcher
:param base: package base to remove
Args:
base(str): package base to remove
""" """
def update(self, base: str, status: BuildStatusEnum) -> None: def update(self, base: str, status: BuildStatusEnum) -> None:
""" """
update package build status. Unlike `add` it does not update package properties update package build status. Unlike `add` it does not update package properties
:param base: package base to update
:param status: current package build status Args:
base(str): package base to update
status(BuildStatusEnum): current package build status
""" """
def update_self(self, status: BuildStatusEnum) -> None: def update_self(self, status: BuildStatusEnum) -> None:
""" """
update ahriman status itself update ahriman status itself
:param status: current ahriman status
Args:
status(BuildStatusEnum): current ahriman status
""" """
def set_building(self, base: str) -> None: def set_building(self, base: str) -> None:
""" """
set package status to building set package status to building
:param base: package base to update
Args:
base(str): package base to update
""" """
return self.update(base, BuildStatusEnum.Building) return self.update(base, BuildStatusEnum.Building)
def set_failed(self, base: str) -> None: def set_failed(self, base: str) -> None:
""" """
set package status to failed set package status to failed
:param base: package base to update
Args:
base(str): package base to update
""" """
return self.update(base, BuildStatusEnum.Failed) return self.update(base, BuildStatusEnum.Failed)
def set_pending(self, base: str) -> None: def set_pending(self, base: str) -> None:
""" """
set package status to pending set package status to pending
:param base: package base to update
Args:
base(str): package base to update
""" """
return self.update(base, BuildStatusEnum.Pending) return self.update(base, BuildStatusEnum.Pending)
def set_success(self, package: Package) -> None: def set_success(self, package: Package) -> None:
""" """
set package status to success set package status to success
:param package: current package properties
Args:
package(Package): current package properties
""" """
return self.add(package, BuildStatusEnum.Success) return self.add(package, BuildStatusEnum.Success)
def set_unknown(self, package: Package) -> None: def set_unknown(self, package: Package) -> None:
""" """
set package status to unknown set package status to unknown
:param package: current package properties
Args:
package(Package): current package properties
""" """
return self.add(package, BuildStatusEnum.Unknown) return self.add(package, BuildStatusEnum.Unknown)

View File

@ -32,20 +32,24 @@ from ahriman.models.package import Package
class Watcher: class Watcher:
""" """
package status watcher package status watcher
:ivar architecture: repository architecture
:ivar database: database instance Attributes:
:ivar known: list of known packages. For the most cases `packages` should be used instead architecture(str): repository architecture
:ivar logger: class logger database(SQLite): database instance
:ivar repository: repository object known(Dict[str, Tuple[Package, BuildStatus]]): list of known packages. For the most cases `packages` should be used instead
:ivar status: daemon status logger(logging.Logger): class logger
repository(Repository): repository object
status(BuildStatus): daemon status
""" """
def __init__(self, architecture: str, configuration: Configuration, database: SQLite) -> None: def __init__(self, architecture: str, configuration: Configuration, database: SQLite) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param database: database instance architecture(str): repository architecture
configuration(Configuration): configuration instance
database(SQLite): database instance
""" """
self.logger = logging.getLogger("http") self.logger = logging.getLogger("http")
@ -59,14 +63,20 @@ class Watcher:
@property @property
def packages(self) -> List[Tuple[Package, BuildStatus]]: def packages(self) -> List[Tuple[Package, BuildStatus]]:
""" """
:return: list of packages together with their statuses Returns:
List[Tuple[Package, BuildStatus]]: list of packages together with their statuses
""" """
return list(self.known.values()) return list(self.known.values())
def get(self, base: str) -> Tuple[Package, BuildStatus]: def get(self, base: str) -> Tuple[Package, BuildStatus]:
""" """
get current package base build status get current package base build status
:return: package and its status
Args:
base(str):
Returns:
Tuple[Package, BuildStatus]: package and its status
""" """
try: try:
return self.known[base] return self.known[base]
@ -92,7 +102,9 @@ class Watcher:
def remove(self, package_base: str) -> None: def remove(self, package_base: str) -> None:
""" """
remove package base from known list if any remove package base from known list if any
:param package_base: package base
Args:
package_base(str): package base
""" """
self.known.pop(package_base, None) self.known.pop(package_base, None)
self.database.package_remove(package_base) self.database.package_remove(package_base)
@ -100,9 +112,11 @@ class Watcher:
def update(self, package_base: str, status: BuildStatusEnum, package: Optional[Package]) -> None: def update(self, package_base: str, status: BuildStatusEnum, package: Optional[Package]) -> None:
""" """
update package status and description update package status and description
:param package_base: package base to update
:param status: new build status Args:
:param package: optional new package description. In case if not set current properties will be used package_base(str): package base to update
status(BuildStatusEnum): new build status
package(Optional[Package]): optional new package description. In case if not set current properties will be used
""" """
if package is None: if package is None:
try: try:
@ -116,6 +130,8 @@ class Watcher:
def update_self(self, status: BuildStatusEnum) -> None: def update_self(self, status: BuildStatusEnum) -> None:
""" """
update service status update service status
:param status: new service status
Args:
status(BuildStatusEnum): new service status
""" """
self.status = BuildStatus(status) self.status = BuildStatus(status)

View File

@ -34,15 +34,19 @@ from ahriman.models.user import User
class WebClient(Client): class WebClient(Client):
""" """
build status reporter web client build status reporter web client
:ivar address: address of the web service
:ivar logger: class logger Attributes:
:ivar user: web service user descriptor address(str): address of the web service
logger(logging.Logger): class logger
user(Optional[User]): web service user descriptor
""" """
def __init__(self, configuration: Configuration) -> None: def __init__(self, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param configuration: configuration instance
Args:
configuration(Configuration): configuration instance
""" """
self.logger = logging.getLogger("http") self.logger = logging.getLogger("http")
self.address = self.parse_address(configuration) self.address = self.parse_address(configuration)
@ -56,21 +60,24 @@ class WebClient(Client):
@property @property
def _ahriman_url(self) -> str: def _ahriman_url(self) -> str:
""" """
:return: full url for web service for ahriman service itself Returns:
str: full url for web service for ahriman service itself
""" """
return f"{self.address}/status-api/v1/ahriman" return f"{self.address}/status-api/v1/ahriman"
@property @property
def _login_url(self) -> str: def _login_url(self) -> str:
""" """
:return: full url for web service to login Returns:
str: full url for web service to login
""" """
return f"{self.address}/user-api/v1/login" return f"{self.address}/user-api/v1/login"
@property @property
def _status_url(self) -> str: def _status_url(self) -> str:
""" """
:return: full url for web service for status Returns:
str: full url for web service for status
""" """
return f"{self.address}/status-api/v1/status" return f"{self.address}/status-api/v1/status"
@ -78,8 +85,12 @@ class WebClient(Client):
def parse_address(configuration: Configuration) -> str: def parse_address(configuration: Configuration) -> str:
""" """
parse address from configuration parse address from configuration
:param configuration: configuration instance
:return: valid http address Args:
configuration(Configuration): configuration instance
Returns:
str: valid http address
""" """
address = configuration.get("web", "address", fallback=None) address = configuration.get("web", "address", fallback=None)
if not address: if not address:
@ -112,16 +123,22 @@ class WebClient(Client):
def _package_url(self, base: str = "") -> str: def _package_url(self, base: str = "") -> str:
""" """
url generator url generator
:param base: package base to generate url
:return: full url of web service for specific package base Args:
base(str, optional): package base to generate url (Default value = "")
Returns:
str: full url of web service for specific package base
""" """
return f"{self.address}/status-api/v1/packages/{base}" return f"{self.address}/status-api/v1/packages/{base}"
def add(self, package: Package, status: BuildStatusEnum) -> None: def add(self, package: Package, status: BuildStatusEnum) -> None:
""" """
add new package with status add new package with status
:param package: package properties
:param status: current package build status Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
""" """
payload = { payload = {
"status": status.value, "status": status.value,
@ -139,8 +156,12 @@ class WebClient(Client):
def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]: def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]:
""" """
get package status get package status
:param base: package base to get
:return: list of current package description and status if it has been found Args:
base(Optional[str]): package base to get
Returns:
List[Tuple[Package, BuildStatus]]: list of current package description and status if it has been found
""" """
try: try:
response = self.__session.get(self._package_url(base or "")) response = self.__session.get(self._package_url(base or ""))
@ -160,7 +181,9 @@ class WebClient(Client):
def get_internal(self) -> InternalStatus: def get_internal(self) -> InternalStatus:
""" """
get internal service status get internal service status
:return: current internal (web) service status
Returns:
InternalStatus: current internal (web) service status
""" """
try: try:
response = self.__session.get(self._status_url) response = self.__session.get(self._status_url)
@ -177,7 +200,9 @@ class WebClient(Client):
def get_self(self) -> BuildStatus: def get_self(self) -> BuildStatus:
""" """
get ahriman status itself get ahriman status itself
:return: current ahriman status
Returns:
BuildStatus: current ahriman status
""" """
try: try:
response = self.__session.get(self._ahriman_url) response = self.__session.get(self._ahriman_url)
@ -194,7 +219,9 @@ class WebClient(Client):
def remove(self, base: str) -> None: def remove(self, base: str) -> None:
""" """
remove packages from watcher remove packages from watcher
:param base: basename to remove
Args:
base(str): basename to remove
""" """
try: try:
response = self.__session.delete(self._package_url(base)) response = self.__session.delete(self._package_url(base))
@ -207,8 +234,10 @@ class WebClient(Client):
def update(self, base: str, status: BuildStatusEnum) -> None: def update(self, base: str, status: BuildStatusEnum) -> None:
""" """
update package build status. Unlike `add` it does not update package properties update package build status. Unlike `add` it does not update package properties
:param base: package base to update
:param status: current package build status Args:
base(str): package base to update
status(BuildStatusEnum): current package build status
""" """
payload = {"status": status.value} payload = {"status": status.value}
@ -223,7 +252,9 @@ class WebClient(Client):
def update_self(self, status: BuildStatusEnum) -> None: def update_self(self, status: BuildStatusEnum) -> None:
""" """
update ahriman status itself update ahriman status itself
:param status: current ahriman status
Args:
status(BuildStatusEnum): current ahriman status
""" """
payload = {"status": status.value} payload = {"status": status.value}

View File

@ -30,15 +30,19 @@ from ahriman.models.package import Package
class Leaf: class Leaf:
""" """
tree leaf implementation tree leaf implementation
:ivar dependencies: list of package dependencies
:ivar package: leaf package properties Attributes:
dependencies(Set[str]): list of package dependencies
package(Package): leaf package properties
""" """
def __init__(self, package: Package, dependencies: Set[str]) -> None: def __init__(self, package: Package, dependencies: Set[str]) -> None:
""" """
default constructor default constructor
:param package: package properties
:param dependencies: package dependencies Args:
package(Package): package properties
dependencies(Set[str]): package dependencies
""" """
self.package = package self.package = package
self.dependencies = dependencies self.dependencies = dependencies
@ -46,7 +50,8 @@ class Leaf:
@property @property
def items(self) -> Iterable[str]: def items(self) -> Iterable[str]:
""" """
:return: packages containing in this leaf Returns:
Iterable[str]: packages containing in this leaf
""" """
return self.package.packages.keys() return self.package.packages.keys()
@ -54,9 +59,13 @@ class Leaf:
def load(cls: Type[Leaf], package: Package, database: SQLite) -> Leaf: def load(cls: Type[Leaf], package: Package, database: SQLite) -> Leaf:
""" """
load leaf from package with dependencies load leaf from package with dependencies
:param package: package properties
:param database: database instance Args:
:return: loaded class package(Package): package properties
database(SQLite): database instance
Returns:
Leaf: loaded class
""" """
with tmpdir() as clone_dir: with tmpdir() as clone_dir:
Sources.load(clone_dir, package.git_url, database.patches_get(package.base)) Sources.load(clone_dir, package.git_url, database.patches_get(package.base))
@ -66,8 +75,12 @@ class Leaf:
def is_root(self, packages: Iterable[Leaf]) -> bool: def is_root(self, packages: Iterable[Leaf]) -> bool:
""" """
check if package depends on any other package from list of not check if package depends on any other package from list of not
:param packages: list of known leaves
:return: True if any of packages is dependency of the leaf, False otherwise Args:
packages(Iterable[Leaf]): list of known leaves
Returns:
bool: True if any of packages is dependency of the leaf, False otherwise
""" """
for leaf in packages: for leaf in packages:
if self.dependencies.intersection(leaf.items): if self.dependencies.intersection(leaf.items):
@ -78,13 +91,17 @@ class Leaf:
class Tree: class Tree:
""" """
dependency tree implementation dependency tree implementation
:ivar leaves: list of tree leaves
Attributes:
leaves[List[Leaf]): list of tree leaves
""" """
def __init__(self, leaves: List[Leaf]) -> None: def __init__(self, leaves: List[Leaf]) -> None:
""" """
default constructor default constructor
:param leaves: leaves to build the tree
Args:
leaves(List[Leaf]): leaves to build the tree
""" """
self.leaves = leaves self.leaves = leaves
@ -92,16 +109,22 @@ class Tree:
def load(cls: Type[Tree], packages: Iterable[Package], database: SQLite) -> Tree: def load(cls: Type[Tree], packages: Iterable[Package], database: SQLite) -> Tree:
""" """
load tree from packages load tree from packages
:param packages: packages list
:param database: database instance Args:
:return: loaded class packages(Iterable[Package]): packages list
database(SQLite): database instance
Returns:
Tree: loaded class
""" """
return cls([Leaf.load(package, database) for package in packages]) return cls([Leaf.load(package, database) for package in packages])
def levels(self) -> List[List[Package]]: def levels(self) -> List[List[Package]]:
""" """
get build levels starting from the packages which do not require any other package to build get build levels starting from the packages which do not require any other package to build
:return: list of packages lists
Returns:
List[List[Package]]: list of packages lists
""" """
result: List[List[Package]] = [] result: List[List[Package]] = []

View File

@ -32,16 +32,20 @@ from ahriman.models.package import Package
class Github(HttpUpload): class Github(HttpUpload):
""" """
upload files to github releases upload files to github releases
:ivar gh_owner: github repository owner
:ivar gh_repository: github repository name Attributes:
gh_owner(str): github repository owner
gh_repository(str): github repository name
""" """
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param section: settings section name architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str): settings section name
""" """
HttpUpload.__init__(self, architecture, configuration, section) HttpUpload.__init__(self, architecture, configuration, section)
self.gh_owner = configuration.get(section, "owner") self.gh_owner = configuration.get(section, "owner")
@ -50,8 +54,10 @@ class Github(HttpUpload):
def asset_remove(self, release: Dict[str, Any], name: str) -> None: def asset_remove(self, release: Dict[str, Any], name: str) -> None:
""" """
remove asset from the release by name remove asset from the release by name
:param release: release object
:param name: asset name Args:
release(Dict[str, Any]): release object
name(str): asset name
""" """
try: try:
asset = next(asset for asset in release["assets"] if asset["name"] == name) asset = next(asset for asset in release["assets"] if asset["name"] == name)
@ -62,8 +68,10 @@ class Github(HttpUpload):
def asset_upload(self, release: Dict[str, Any], path: Path) -> None: def asset_upload(self, release: Dict[str, Any], path: Path) -> None:
""" """
upload asset to the release upload asset to the release
:param release: release object
:param path: path to local file Args:
release(Dict[str, Any]): release object
path(Path): path to local file
""" """
exists = any(path.name == asset["name"] for asset in release["assets"]) exists = any(path.name == asset["name"] for asset in release["assets"])
if exists: if exists:
@ -76,8 +84,12 @@ class Github(HttpUpload):
def get_local_files(self, path: Path) -> Dict[Path, str]: def get_local_files(self, path: Path) -> Dict[Path, str]:
""" """
get all local files and their calculated checksums get all local files and their calculated checksums
:param path: local path to sync
:return: map of path objects to its checksum Args:
path(Path): local path to sync
Returns:
Dict[Path, str]: map of path objects to its checksum
""" """
return { return {
local_file: self.calculate_hash(local_file) local_file: self.calculate_hash(local_file)
@ -87,9 +99,11 @@ class Github(HttpUpload):
def files_remove(self, release: Dict[str, Any], local_files: Dict[Path, str], remote_files: Dict[str, str]) -> None: def files_remove(self, release: Dict[str, Any], local_files: Dict[Path, str], remote_files: Dict[str, str]) -> None:
""" """
remove files from github remove files from github
:param release: release object
:param local_files: map of local file paths to its checksum Args:
:param remote_files: map of the remote files and its checksum release(Dict[str, Any]): release object
local_files(Dict[Path, str]): map of local file paths to its checksum
remote_files(Dict[str, str]): map of the remote files and its checksum
""" """
local_filenames = {local_file.name for local_file in local_files} local_filenames = {local_file.name for local_file in local_files}
for remote_file in remote_files: for remote_file in remote_files:
@ -100,9 +114,11 @@ class Github(HttpUpload):
def files_upload(self, release: Dict[str, Any], local_files: Dict[Path, str], remote_files: Dict[str, str]) -> None: def files_upload(self, release: Dict[str, Any], local_files: Dict[Path, str], remote_files: Dict[str, str]) -> None:
""" """
upload files to github upload files to github
:param release: release object
:param local_files: map of local file paths to its checksum Args:
:param remote_files: map of the remote files and its checksum release(Dict[str, Any]): release object
local_files(Dict[Path, str]): map of local file paths to its checksum
remote_files(Dict[str, str]): map of the remote files and its checksum
""" """
for local_file, checksum in local_files.items(): for local_file, checksum in local_files.items():
remote_checksum = remote_files.get(local_file.name) remote_checksum = remote_files.get(local_file.name)
@ -113,7 +129,9 @@ class Github(HttpUpload):
def release_create(self) -> Dict[str, Any]: def release_create(self) -> Dict[str, Any]:
""" """
create empty release create empty release
:return: github API release object for the new release
Returns:
Dict[str, Any]: github API release object for the new release
""" """
response = self._request("POST", f"https://api.github.com/repos/{self.gh_owner}/{self.gh_repository}/releases", response = self._request("POST", f"https://api.github.com/repos/{self.gh_owner}/{self.gh_repository}/releases",
json={"tag_name": self.architecture, "name": self.architecture}) json={"tag_name": self.architecture, "name": self.architecture})
@ -123,7 +141,9 @@ class Github(HttpUpload):
def release_get(self) -> Optional[Dict[str, Any]]: def release_get(self) -> Optional[Dict[str, Any]]:
""" """
get release object if any get release object if any
:return: github API release object if release found and None otherwise
Returns:
Optional[Dict[str, Any]]: github API release object if release found and None otherwise
""" """
try: try:
response = self._request( response = self._request(
@ -140,16 +160,20 @@ class Github(HttpUpload):
def release_update(self, release: Dict[str, Any], body: str) -> None: def release_update(self, release: Dict[str, Any], body: str) -> None:
""" """
update release update release
:param release: release object
:param body: new release body Args:
release(Dict[str, Any]): release object
body(str): new release body
""" """
self._request("POST", release["url"], json={"body": body}) self._request("POST", release["url"], json={"body": body})
def sync(self, path: Path, built_packages: Iterable[Package]) -> None: def sync(self, path: Path, built_packages: Iterable[Package]) -> None:
""" """
sync data to remote server sync data to remote server
:param path: local path to sync
:param built_packages: list of packages which has just been built Args:
path(Path): local path to sync
built_packages(Iterable[Package]): list of packages which has just been built
""" """
release = self.release_get() release = self.release_get()
if release is None: if release is None:

View File

@ -31,15 +31,19 @@ from ahriman.core.util import exception_response_text
class HttpUpload(Upload): class HttpUpload(Upload):
""" """
helper for the http based uploads helper for the http based uploads
:ivar auth: HTTP auth object
Attributes:
auth(Tuple[str, str]): HTTP auth object
""" """
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param section: configuration section name architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str): configuration section name
""" """
Upload.__init__(self, architecture, configuration) Upload.__init__(self, architecture, configuration)
password = configuration.get(section, "password") password = configuration.get(section, "password")
@ -50,8 +54,12 @@ class HttpUpload(Upload):
def calculate_hash(path: Path) -> str: def calculate_hash(path: Path) -> str:
""" """
calculate file checksum calculate file checksum
:param path: path to local file
:return: calculated checksum of the file Args:
path(Path): path to local file
Returns:
str: calculated checksum of the file
""" """
with path.open("rb") as local_file: with path.open("rb") as local_file:
md5 = hashlib.md5(local_file.read()) # nosec md5 = hashlib.md5(local_file.read()) # nosec
@ -61,8 +69,12 @@ class HttpUpload(Upload):
def get_body(local_files: Dict[Path, str]) -> str: def get_body(local_files: Dict[Path, str]) -> str:
""" """
generate release body from the checksums as returned from HttpUpload.get_hashes method generate release body from the checksums as returned from HttpUpload.get_hashes method
:param local_files: map of the paths to its checksum
:return: body to be inserted into release Args:
local_files(Dict[Path, str]): map of the paths to its checksum
Returns:
str: body to be inserted into release
""" """
return "\n".join(f"{file.name} {md5}" for file, md5 in sorted(local_files.items())) return "\n".join(f"{file.name} {md5}" for file, md5 in sorted(local_files.items()))
@ -70,8 +82,12 @@ class HttpUpload(Upload):
def get_hashes(body: str) -> Dict[str, str]: def get_hashes(body: str) -> Dict[str, str]:
""" """
get checksums of the content from the repository get checksums of the content from the repository
:param body: release string body object
:return: map of the filename to its checksum as it is written in body Args:
body(str): release string body object
Returns:
Dict[str, str]: map of the filename to its checksum as it is written in body
""" """
files = {} files = {}
for line in body.splitlines(): for line in body.splitlines():
@ -82,10 +98,14 @@ class HttpUpload(Upload):
def _request(self, method: str, url: str, **kwargs: Any) -> requests.Response: def _request(self, method: str, url: str, **kwargs: Any) -> requests.Response:
""" """
request wrapper request wrapper
:param method: request method
:param url: request url Args:
:param kwargs: request parameters to be passed as is method(str): request method
:return: request response object url(str): request url
**kwargs(Any):
Returns:
requests.Response: request response object
""" """
try: try:
response = requests.request(method, url, auth=self.auth, **kwargs) response = requests.request(method, url, auth=self.auth, **kwargs)

View File

@ -29,8 +29,10 @@ from ahriman.models.package import Package
class Rsync(Upload): class Rsync(Upload):
""" """
rsync wrapper rsync wrapper
:ivar command: command arguments for sync
:ivar remote: remote address to sync Attributes:
command(List[str]): command arguments for sync
remote(str): remote address to sync
""" """
_check_output = check_output _check_output = check_output
@ -38,9 +40,11 @@ class Rsync(Upload):
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param section: settings section name architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str): settings section name
""" """
Upload.__init__(self, architecture, configuration) Upload.__init__(self, architecture, configuration)
self.command = configuration.getlist(section, "command") self.command = configuration.getlist(section, "command")
@ -49,7 +53,9 @@ class Rsync(Upload):
def sync(self, path: Path, built_packages: Iterable[Package]) -> None: def sync(self, path: Path, built_packages: Iterable[Package]) -> None:
""" """
sync data to remote server sync data to remote server
:param path: local path to sync
:param built_packages: list of packages which has just been built Args:
path(Path): local path to sync
built_packages(Iterable[Package]): list of packages which has just been built
""" """
Rsync._check_output(*self.command, str(path), self.remote, exception=None, logger=self.logger) Rsync._check_output(*self.command, str(path), self.remote, exception=None, logger=self.logger)

View File

@ -32,16 +32,21 @@ from ahriman.models.package import Package
class S3(Upload): class S3(Upload):
""" """
aws-cli wrapper boto3 wrapper
:ivar bucket: boto3 S3 bucket object
:ivar chunk_size: chunk size for calculating checksums Attributes
bucket(Any): boto3 S3 bucket object
chunk_size(int): chunk size for calculating checksums
""" """
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
section(str):
""" """
Upload.__init__(self, architecture, configuration) Upload.__init__(self, architecture, configuration)
self.bucket = self.get_bucket(configuration, section) self.bucket = self.get_bucket(configuration, section)
@ -53,9 +58,13 @@ class S3(Upload):
calculate amazon s3 etag calculate amazon s3 etag
credits to https://teppen.io/2018/10/23/aws_s3_verify_etags/ credits to https://teppen.io/2018/10/23/aws_s3_verify_etags/
For this method we have to define nosec because it is out of any security context and provided by AWS For this method we have to define nosec because it is out of any security context and provided by AWS
:param path: path to local file
:param chunk_size: read chunk size, which depends on client settings Args:
:return: calculated entity tag for local file path(Path): path to local file
chunk_size(int): read chunk size, which depends on client settings
Returns:
str: calculated entity tag for local file
""" """
md5s = [] md5s = []
with path.open("rb") as local_file: with path.open("rb") as local_file:
@ -73,9 +82,13 @@ class S3(Upload):
def get_bucket(configuration: Configuration, section: str) -> Any: def get_bucket(configuration: Configuration, section: str) -> Any:
""" """
create resource client from configuration create resource client from configuration
:param configuration: configuration instance
:param section: settings section name Args:
:return: amazon client configuration(Configuration): configuration instance
section(str): settings section name
Returns:
Any: amazon client
""" """
client = boto3.resource(service_name="s3", client = boto3.resource(service_name="s3",
region_name=configuration.get(section, "region"), region_name=configuration.get(section, "region"),
@ -87,8 +100,10 @@ class S3(Upload):
def files_remove(local_files: Dict[Path, str], remote_objects: Dict[Path, Any]) -> None: def files_remove(local_files: Dict[Path, str], remote_objects: Dict[Path, Any]) -> None:
""" """
remove files which have been removed locally remove files which have been removed locally
:param local_files: map of local path object to its checksum
:param remote_objects: map of remote path object to the remote s3 object Args:
local_files(Dict[Path, str]): map of local path object to its checksum
remote_objects(Dict[Path, Any]): map of remote path object to the remote s3 object
""" """
for local_file, remote_object in remote_objects.items(): for local_file, remote_object in remote_objects.items():
if local_file in local_files: if local_file in local_files:
@ -98,9 +113,11 @@ class S3(Upload):
def files_upload(self, path: Path, local_files: Dict[Path, str], remote_objects: Dict[Path, Any]) -> None: def files_upload(self, path: Path, local_files: Dict[Path, str], remote_objects: Dict[Path, Any]) -> None:
""" """
upload changed files to s3 upload changed files to s3
:param path: local path to sync
:param local_files: map of local path object to its checksum Args:
:param remote_objects: map of remote path object to the remote s3 object path(Path): local path to sync
local_files(Dict[Path, str]): map of local path object to its checksum
remote_objects(Dict[Path, Any]): map of remote path object to the remote s3 object
""" """
for local_file, checksum in local_files.items(): for local_file, checksum in local_files.items():
remote_object = remote_objects.get(local_file) remote_object = remote_objects.get(local_file)
@ -119,8 +136,12 @@ class S3(Upload):
def get_local_files(self, path: Path) -> Dict[Path, str]: def get_local_files(self, path: Path) -> Dict[Path, str]:
""" """
get all local files and their calculated checksums get all local files and their calculated checksums
:param path: local path to sync
:return: map of path object to its checksum Args:
path(Path): local path to sync
Returns:
Dict[Path, str]: map of path object to its checksum
""" """
return { return {
local_file.relative_to(path): self.calculate_etag(local_file, self.chunk_size) local_file.relative_to(path): self.calculate_etag(local_file, self.chunk_size)
@ -130,7 +151,9 @@ class S3(Upload):
def get_remote_objects(self) -> Dict[Path, Any]: def get_remote_objects(self) -> Dict[Path, Any]:
""" """
get all remote objects and their checksums get all remote objects and their checksums
:return: map of path object to the remote s3 object
Returns:
Dict[Path, Any]: map of path object to the remote s3 object
""" """
objects = self.bucket.objects.filter(Prefix=self.architecture) objects = self.bucket.objects.filter(Prefix=self.architecture)
return {Path(item.key).relative_to(self.architecture): item for item in objects} return {Path(item.key).relative_to(self.architecture): item for item in objects}
@ -138,8 +161,10 @@ class S3(Upload):
def sync(self, path: Path, built_packages: Iterable[Package]) -> None: def sync(self, path: Path, built_packages: Iterable[Package]) -> None:
""" """
sync data to remote server sync data to remote server
:param path: local path to sync
:param built_packages: list of packages which has just been built Args:
path(Path): local path to sync
built_packages(Iterable[Package]): list of packages which has just been built
""" """
remote_objects = self.get_remote_objects() remote_objects = self.get_remote_objects()
local_files = self.get_local_files(path) local_files = self.get_local_files(path)

View File

@ -33,16 +33,20 @@ from ahriman.models.upload_settings import UploadSettings
class Upload: class Upload:
""" """
base remote sync class base remote sync class
:ivar architecture: repository architecture
:ivar configuration: configuration instance Attributes:
:ivar logger: application logger architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): application logger
""" """
def __init__(self, architecture: str, configuration: Configuration) -> None: def __init__(self, architecture: str, configuration: Configuration) -> None:
""" """
default constructor default constructor
:param architecture: repository architecture
:param configuration: configuration instance Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
""" """
self.logger = logging.getLogger("root") self.logger = logging.getLogger("root")
self.architecture = architecture self.architecture = architecture
@ -52,10 +56,14 @@ class Upload:
def load(cls: Type[Upload], architecture: str, configuration: Configuration, target: str) -> Upload: def load(cls: Type[Upload], architecture: str, configuration: Configuration, target: str) -> Upload:
""" """
load client from settings load client from settings
:param architecture: repository architecture
:param configuration: configuration instance Args:
:param target: target to run sync (e.g. s3) architecture(str): repository architecture
:return: client according to current settings configuration(Configuration): configuration instance
target(str): target to run sync (e.g. s3)
Returns:
Upload: client according to current settings
""" """
section, provider_name = configuration.gettype(target, architecture) section, provider_name = configuration.gettype(target, architecture)
provider = UploadSettings.from_option(provider_name) provider = UploadSettings.from_option(provider_name)
@ -73,8 +81,10 @@ class Upload:
def run(self, path: Path, built_packages: Iterable[Package]) -> None: def run(self, path: Path, built_packages: Iterable[Package]) -> None:
""" """
run remote sync run remote sync
:param path: local path to sync
:param built_packages: list of packages which has just been built Args:
path(Path): local path to sync
built_packages(Iterable[Package]): list of packages which has just been built
""" """
try: try:
self.sync(path, built_packages) self.sync(path, built_packages)
@ -85,6 +95,8 @@ class Upload:
def sync(self, path: Path, built_packages: Iterable[Package]) -> None: def sync(self, path: Path, built_packages: Iterable[Package]) -> None:
""" """
sync data to remote server sync data to remote server
:param path: local path to sync
:param built_packages: list of packages which has just been built Args:
path(Path): local path to sync
built_packages(Iterable[Package]): list of packages which has just been built
""" """

View File

@ -37,13 +37,17 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
input_data: Optional[str] = None, logger: Optional[Logger] = None, user: Optional[int] = None) -> str: input_data: Optional[str] = None, logger: Optional[Logger] = None, user: Optional[int] = None) -> str:
""" """
subprocess wrapper subprocess wrapper
:param args: command line arguments
:param exception: exception which has to be reraised instead of default subprocess exception Args:
:param cwd: current working directory *args(str): command line arguments
:param input_data: data which will be written to command stdin exception(Optional[Exception]): exception which has to be reraised instead of default subprocess exception
:param logger: logger to log command result if required cwd(Optional[Path], optional): current working directory (Default value = None)
:param user: run process as specified user input_data(Optional[str], optional): data which will be written to command stdin (Default value = None)
:return: command output logger(Optional[Logger], optional): logger to log command result if required (Default value = None)
user(Optional[int], optional): run process as specified user (Default value = None)
Returns:
str: command output
""" """
def log(single: str) -> None: def log(single: str) -> None:
if logger is not None: if logger is not None:
@ -83,8 +87,10 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
def check_user(paths: RepositoryPaths, unsafe: bool) -> None: def check_user(paths: RepositoryPaths, unsafe: bool) -> None:
""" """
check if current user is the owner of the root check if current user is the owner of the root
:param paths: repository paths object
:param unsafe: if set no user check will be performed before path creation Args:
paths(RepositoryPaths): repository paths object
unsafe(bool): if set no user check will be performed before path creation
""" """
if not paths.root.exists(): if not paths.root.exists():
return # no directory found, skip check return # no directory found, skip check
@ -99,8 +105,12 @@ def check_user(paths: RepositoryPaths, unsafe: bool) -> None:
def exception_response_text(exception: requests.exceptions.HTTPError) -> str: def exception_response_text(exception: requests.exceptions.HTTPError) -> str:
""" """
safe response exception text generation safe response exception text generation
:param exception: exception raised
:return: text of the response if it is not None and empty string otherwise Args:
exception(requests.exceptions.HTTPError): exception raised
Returns:
str: text of the response if it is not None and empty string otherwise
""" """
result: str = exception.response.text if exception.response is not None else "" result: str = exception.response.text if exception.response is not None else ""
return result return result
@ -109,9 +119,13 @@ def exception_response_text(exception: requests.exceptions.HTTPError) -> str:
def filter_json(source: Dict[str, Any], known_fields: Iterable[str]) -> Dict[str, Any]: def filter_json(source: Dict[str, Any], known_fields: Iterable[str]) -> Dict[str, Any]:
""" """
filter json object by fields used for json-to-object conversion filter json object by fields used for json-to-object conversion
:param source: raw json object
:param known_fields: list of fields which have to be known for the target object Args:
:return: json object without unknown and empty fields source(Dict[str, Any]): raw json object
known_fields(Iterable[str]): list of fields which have to be known for the target object
Returns:
Dict[str, Any]: json object without unknown and empty fields
""" """
return {key: value for key, value in source.items() if key in known_fields and value is not None} return {key: value for key, value in source.items() if key in known_fields and value is not None}
@ -119,10 +133,14 @@ def filter_json(source: Dict[str, Any], known_fields: Iterable[str]) -> Dict[str
def full_version(epoch: Union[str, int, None], pkgver: str, pkgrel: str) -> str: def full_version(epoch: Union[str, int, None], pkgver: str, pkgrel: str) -> str:
""" """
generate full version from components generate full version from components
:param epoch: package epoch if any
:param pkgver: package version Args:
:param pkgrel: package release version (arch linux specific) epoch(Union[str, int, None]): package epoch if any
:return: generated version pkgver(str): package version
pkgrel(str): package release version (arch linux specific)
Returns:
str: generated version
""" """
prefix = f"{epoch}:" if epoch else "" prefix = f"{epoch}:" if epoch else ""
return f"{prefix}{pkgver}-{pkgrel}" return f"{prefix}{pkgver}-{pkgrel}"
@ -131,8 +149,12 @@ def full_version(epoch: Union[str, int, None], pkgver: str, pkgrel: str) -> str:
def package_like(filename: Path) -> bool: def package_like(filename: Path) -> bool:
""" """
check if file looks like package check if file looks like package
:param filename: name of file to check
:return: True in case if name contains `.pkg.` and not signature, False otherwise Args:
filename(Path): name of file to check
Returns:
bool: True in case if name contains `.pkg.` and not signature, False otherwise
""" """
name = filename.name name = filename.name
return ".pkg." in name and not name.endswith(".sig") return ".pkg." in name and not name.endswith(".sig")
@ -141,8 +163,12 @@ def package_like(filename: Path) -> bool:
def pretty_datetime(timestamp: Optional[Union[datetime.datetime, float, int]]) -> str: def pretty_datetime(timestamp: Optional[Union[datetime.datetime, float, int]]) -> str:
""" """
convert datetime object to string convert datetime object to string
:param timestamp: datetime to convert
:return: pretty printable datetime as string Args:
timestamp(Optional[Union[datetime.datetime, float, int]]): datetime to convert
Returns:
str: pretty printable datetime as string
""" """
if timestamp is None: if timestamp is None:
return "" return ""
@ -154,9 +180,13 @@ def pretty_datetime(timestamp: Optional[Union[datetime.datetime, float, int]]) -
def pretty_size(size: Optional[float], level: int = 0) -> str: def pretty_size(size: Optional[float], level: int = 0) -> str:
""" """
convert size to string convert size to string
:param size: size to convert
:param level: represents current units, 0 is B, 1 is KiB etc Args:
:return: pretty printable size as string size(Optional[float]): size to convert
level(int, optional): represents current units, 0 is B, 1 is KiB etc (Default value = 0)
Returns:
str: pretty printable size as string
""" """
def str_level() -> str: def str_level() -> str:
if level == 0: if level == 0:
@ -180,7 +210,9 @@ def pretty_size(size: Optional[float], level: int = 0) -> str:
def tmpdir() -> Generator[Path, None, None]: def tmpdir() -> Generator[Path, None, None]:
""" """
wrapper for tempfile to remove directory after all wrapper for tempfile to remove directory after all
:return: path to the created directory
Yields:
Path: path to the created directory
""" """
path = Path(tempfile.mkdtemp()) path = Path(tempfile.mkdtemp())
try: try:
@ -193,8 +225,12 @@ def walk(directory_path: Path) -> Generator[Path, None, None]:
""" """
list all file paths in given directory list all file paths in given directory
Credits to https://stackoverflow.com/a/64915960 Credits to https://stackoverflow.com/a/64915960
:param directory_path: root directory path
:return: all found files in given directory with full path Args:
directory_path(Path): root directory path
Yields:
Path: all found files in given directory with full path
""" """
for element in directory_path.iterdir(): for element in directory_path.iterdir():
if element.is_dir(): if element.is_dir():

View File

@ -23,9 +23,11 @@ from enum import Enum
class Action(Enum): class Action(Enum):
""" """
base action enumeration base action enumeration
:cvar List: list available values
:cvar Remove: remove everything from local storage Attributes:
:cvar Update: update local storage or add to 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 = "list" List = "list"

View File

@ -32,26 +32,28 @@ from ahriman.core.util import filter_json, full_version
class AURPackage: class AURPackage:
""" """
AUR package descriptor AUR package descriptor
:ivar id: package ID
:ivar name: package name Attributes:
:ivar package_base_id: package base ID id(int): package ID
:ivar version: package base version name(str): package name
:ivar description: package base description package_base_id(int): package base ID
:ivar url: package upstream URL version(str): package base version
:ivar num_votes: number of votes for the package description(str): package base description
:ivar polularity: package popularity url(Optional[str]): package upstream URL
:ivar out_of_date: package out of date timestamp if any num_votes(int): number of votes for the package
:ivar maintainer: package maintainer polularity(float): package popularity
:ivar first_submitted: timestamp of the first package submission out_of_date(Optional[datetime.datetime]): package out of date timestamp if any
:ivar last_modified: timestamp of the last package submission maintainer(Optional[str]): package maintainer
:ivar url_path: AUR package path first_submitted(datetime.datetime): timestamp of the first package submission
:ivar depends: list of package dependencies last_modified(datetime.datetime): timestamp of the last package submission
:ivar make_depends: list of package make dependencies url_path(str): AUR package path
:ivar opt_depends: list of package optional dependencies depends(List[str]): list of package dependencies
:ivar conflicts: conflicts list for the package make_depends(List[str]): list of package make dependencies
:ivar provides: list of packages which this package provides opt_depends(List[str]): list of package optional dependencies
:ivar license: list of package licenses conflicts(List[str]): conflicts list for the package
:ivar keywords: list of package keywords provides(List[str]): list of packages which this package provides
license(List[str]): list of package licenses
keywords(List[str]): list of package keywords
""" """
id: int id: int
@ -80,8 +82,12 @@ class AURPackage:
def from_json(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage: def from_json(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage:
""" """
construct package descriptor from RPC properties construct package descriptor from RPC properties
:param dump: json dump body
:return: AUR package descriptor Args:
dump(Dict[str, Any]): json dump body
Returns:
AURPackage: AUR package descriptor
""" """
# filter to only known fields # filter to only known fields
known_fields = [pair.name for pair in fields(cls)] known_fields = [pair.name for pair in fields(cls)]
@ -92,8 +98,12 @@ class AURPackage:
def from_repo(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage: def from_repo(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage:
""" """
construct package descriptor from official repository RPC properties construct package descriptor from official repository RPC properties
:param dump: json dump body
:return: AUR package descriptor Args:
dump(Dict[str, Any]): json dump body
Returns:
AURPackage: AUR package descriptor
""" """
return cls( return cls(
id=0, id=0,
@ -125,8 +135,12 @@ class AURPackage:
def convert(descriptor: Dict[str, Any]) -> Dict[str, Any]: def convert(descriptor: Dict[str, Any]) -> Dict[str, Any]:
""" """
covert AUR RPC key names to package keys covert AUR RPC key names to package keys
:param descriptor: RPC package descriptor
:return: package descriptor with names converted to snake case Args:
descriptor(Dict[str, Any]): RPC package descriptor
Returns:
Dict[str, Any]: package descriptor with names converted to snake case
""" """
identity_mapper: Callable[[Any], Any] = lambda value: value identity_mapper: Callable[[Any], Any] = lambda value: value
value_mapper: Dict[str, Callable[[Any], Any]] = { value_mapper: Dict[str, Callable[[Any], Any]] = {

View File

@ -28,9 +28,11 @@ from ahriman.core.exceptions import InvalidOption
class AuthSettings(Enum): class AuthSettings(Enum):
""" """
web authorization type web authorization type
:cvar Disabled: authorization is disabled
:cvar Configuration: configuration based authorization Attributes:
:cvar OAuth: OAuth based provider Disabled(AuthSettings): (class attribute) authorization is disabled
Configuration(AuthSettings): (class attribute) configuration based authorization
OAuth(AuthSettings): (class attribute) OAuth based provider
""" """
Disabled = "disabled" Disabled = "disabled"
@ -41,8 +43,12 @@ class AuthSettings(Enum):
def from_option(cls: Type[AuthSettings], value: str) -> AuthSettings: def from_option(cls: Type[AuthSettings], value: str) -> AuthSettings:
""" """
construct value from configuration construct value from configuration
:param value: configuration value
:return: parsed value Args:
value(str): configuration value
Returns:
AuthSettings: parsed value
""" """
if value.lower() in ("disabled", "no"): if value.lower() in ("disabled", "no"):
return cls.Disabled return cls.Disabled
@ -55,7 +61,8 @@ class AuthSettings(Enum):
@property @property
def is_enabled(self) -> bool: def is_enabled(self) -> bool:
""" """
:return: False in case if authorization is disabled and True otherwise Returns:
bool: False in case if authorization is disabled and True otherwise
""" """
if self == AuthSettings.Disabled: if self == AuthSettings.Disabled:
return False return False

View File

@ -31,11 +31,13 @@ from ahriman.core.util import filter_json, pretty_datetime
class BuildStatusEnum(Enum): class BuildStatusEnum(Enum):
""" """
build status enumeration build status enumeration
:cvar Unknown: build status is unknown
:cvar Pending: package is out-of-dated and will be built soon Attributes:
:cvar Building: package is building right now Unknown(BuildStatusEnum): (class attribute) build status is unknown
:cvar Failed: package build failed Pending(BuildStatusEnum): (class attribute) package is out-of-dated and will be built soon
:cvar Success: package has been built without errors 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 = "unknown" Unknown = "unknown"
@ -47,7 +49,9 @@ class BuildStatusEnum(Enum):
def badges_color(self) -> str: def badges_color(self) -> str:
""" """
convert itself to shield.io badges color convert itself to shield.io badges color
:return: shields.io color
Returns:
str: shields.io color
""" """
if self == BuildStatusEnum.Pending: if self == BuildStatusEnum.Pending:
return "yellow" return "yellow"
@ -62,7 +66,9 @@ class BuildStatusEnum(Enum):
def bootstrap_color(self) -> str: def bootstrap_color(self) -> str:
""" """
converts itself to bootstrap color converts itself to bootstrap color
:return: bootstrap color
Returns:
str: bootstrap color
""" """
if self == BuildStatusEnum.Pending: if self == BuildStatusEnum.Pending:
return "warning" return "warning"
@ -79,8 +85,10 @@ class BuildStatusEnum(Enum):
class BuildStatus: class BuildStatus:
""" """
build status holder build status holder
:ivar status: build status
:ivar timestamp: build status update time Attributes:
status(BuildStatusEnum): build status
timestamp(int): build status update time
""" """
status: BuildStatusEnum = BuildStatusEnum.Unknown status: BuildStatusEnum = BuildStatusEnum.Unknown
@ -96,8 +104,12 @@ class BuildStatus:
def from_json(cls: Type[BuildStatus], dump: Dict[str, Any]) -> BuildStatus: def from_json(cls: Type[BuildStatus], dump: Dict[str, Any]) -> BuildStatus:
""" """
construct status properties from json dump construct status properties from json dump
:param dump: json dump body
:return: status properties Args:
dump(Dict[str, Any]): json dump body
Returns:
BuildStatus: status properties
""" """
known_fields = [pair.name for pair in fields(cls)] known_fields = [pair.name for pair in fields(cls)]
return cls(**filter_json(dump, known_fields)) return cls(**filter_json(dump, known_fields))
@ -105,14 +117,18 @@ class BuildStatus:
def pretty_print(self) -> str: def pretty_print(self) -> str:
""" """
generate pretty string representation generate pretty string representation
:return: print-friendly string
Returns:
str: print-friendly string
""" """
return f"{self.status.value} ({pretty_datetime(self.timestamp)})" return f"{self.status.value} ({pretty_datetime(self.timestamp)})"
def view(self) -> Dict[str, Any]: def view(self) -> Dict[str, Any]:
""" """
generate json status view generate json status view
:return: json-friendly dictionary
Returns:
Dict[str, Any]: json-friendly dictionary
""" """
return { return {
"status": self.status.value, "status": self.status.value,

View File

@ -31,12 +31,14 @@ from ahriman.models.package import Package
class Counters: class Counters:
""" """
package counters package counters
:ivar total: total packages count
:ivar unknown: packages in unknown status count Attributes:
:ivar pending: packages in pending status count total(int): total packages count
:ivar building: packages in building status count unknown(int): packages in unknown status count
:ivar failed: packages in failed status count pending(int): packages in pending status count
:ivar success: packages in success status count building(int): packages in building status count
failed(int): packages in failed status count
success(int): packages in success status count
""" """
total: int total: int
@ -50,8 +52,12 @@ class Counters:
def from_json(cls: Type[Counters], dump: Dict[str, Any]) -> Counters: def from_json(cls: Type[Counters], dump: Dict[str, Any]) -> Counters:
""" """
construct counters from json dump construct counters from json dump
:param dump: json dump body
:return: status counters Args:
dump(Dict[str, Any]): json dump body
Returns:
Counters: status counters
""" """
# filter to only known fields # filter to only known fields
known_fields = [pair.name for pair in fields(cls)] known_fields = [pair.name for pair in fields(cls)]
@ -61,8 +67,12 @@ class Counters:
def from_packages(cls: Type[Counters], packages: List[Tuple[Package, BuildStatus]]) -> Counters: def from_packages(cls: Type[Counters], packages: List[Tuple[Package, BuildStatus]]) -> Counters:
""" """
construct counters from packages statuses construct counters from packages statuses
:param packages: list of package and their status as per watcher property
:return: status counters Args:
packages(List[Tuple[Package, BuildStatus]]): list of package and their status as per watcher property
Returns:
Counters: status counters
""" """
per_status = {"total": len(packages)} per_status = {"total": len(packages)}
for _, status in packages: for _, status in packages:

View File

@ -29,10 +29,12 @@ from ahriman.models.counters import Counters
class InternalStatus: class InternalStatus:
""" """
internal server status internal server status
:ivar architecture: repository architecture
:ivar packages: packages statuses counter object Attributes:
:ivar repository: repository name architecture(Optional[str]): repository architecture
:ivar version: service version packages(Counters): packages statuses counter object
repository(Optional[str]): repository name
version(Optional[str]): service version
""" """
architecture: Optional[str] = None architecture: Optional[str] = None
@ -44,8 +46,12 @@ class InternalStatus:
def from_json(cls: Type[InternalStatus], dump: Dict[str, Any]) -> InternalStatus: def from_json(cls: Type[InternalStatus], dump: Dict[str, Any]) -> InternalStatus:
""" """
construct internal status from json dump construct internal status from json dump
:param dump: json dump body
:return: internal status Args:
dump(Dict[str, Any]): json dump body
Returns:
InternalStatus: internal status
""" """
counters = Counters.from_json(dump["packages"]) if "packages" in dump else Counters(total=0) counters = Counters.from_json(dump["packages"]) if "packages" in dump else Counters(total=0)
return cls(architecture=dump.get("architecture"), return cls(architecture=dump.get("architecture"),
@ -56,6 +62,8 @@ class InternalStatus:
def view(self) -> Dict[str, Any]: def view(self) -> Dict[str, Any]:
""" """
generate json status view generate json status view
:return: json-friendly dictionary
Returns:
Dict[str, Any]: json-friendly dictionary
""" """
return asdict(self) return asdict(self)

View File

@ -25,9 +25,11 @@ from typing import List
class Migration: class Migration:
""" """
migration implementation migration implementation
:ivar index: migration position
:ivar name: migration name Attributes:
:ivar steps: migration steps index(int): migration position
name(str): migration name
steps(List[str]): migration steps
""" """
index: int index: int

View File

@ -26,8 +26,10 @@ from ahriman.core.exceptions import MigrationError
class MigrationResult: class MigrationResult:
""" """
migration result implementation model migration result implementation model
:ivar old_version: old schema version before migrations
:ivar new_version: new schema version after migrations Attributes:
old_version(int): old schema version before migrations
new_version(int): new schema version after migrations
""" """
old_version: int old_version: int
@ -36,7 +38,8 @@ class MigrationResult:
@property @property
def is_outdated(self) -> bool: def is_outdated(self) -> bool:
""" """
:return: True in case if it requires migrations and False otherwise Returns:
bool: True in case if it requires migrations and False otherwise
""" """
self.validate() self.validate()
return self.new_version > self.old_version return self.new_version > self.old_version

View File

@ -42,10 +42,12 @@ from ahriman.models.repository_paths import RepositoryPaths
class Package: class Package:
""" """
package properties representation package properties representation
:ivar aur_url: AUR root url
:ivar base: package base name Attributes:
:ivar packages: map of package names to their properties. Filled only on load from archive aur_url(str): AUR root url
:ivar version: package full version base(str): package base name
packages(Dict[str, PackageDescription): map of package names to their properties. Filled only on load from archive
version(str): package full version
""" """
base: str base: str
@ -58,35 +60,40 @@ class Package:
@property @property
def depends(self) -> List[str]: def depends(self) -> List[str]:
""" """
:return: sum of dependencies per arch package Returns:
List[str]: sum of dependencies per arch package
""" """
return sorted(set(sum([package.depends for package in self.packages.values()], start=[]))) return sorted(set(sum([package.depends for package in self.packages.values()], start=[])))
@property @property
def git_url(self) -> str: def git_url(self) -> str:
""" """
:return: package git url to clone Returns:
str: package git url to clone
""" """
return f"{self.aur_url}/{self.base}.git" return f"{self.aur_url}/{self.base}.git"
@property @property
def groups(self) -> List[str]: def groups(self) -> List[str]:
""" """
:return: sum of groups per each package Returns:
List[str]: sum of groups per each package
""" """
return sorted(set(sum([package.groups for package in self.packages.values()], start=[]))) return sorted(set(sum([package.groups for package in self.packages.values()], start=[])))
@property @property
def is_single_package(self) -> bool: def is_single_package(self) -> bool:
""" """
:return: true in case if this base has only one package with the same name Returns:
bool: true in case if this base has only one package with the same name
""" """
return self.base in self.packages and len(self.packages) == 1 return self.base in self.packages and len(self.packages) == 1
@property @property
def is_vcs(self) -> bool: def is_vcs(self) -> bool:
""" """
:return: True in case if package base looks like VCS package and false otherwise Returns:
bool: True in case if package base looks like VCS package and false otherwise
""" """
return self.base.endswith("-bzr") \ return self.base.endswith("-bzr") \
or self.base.endswith("-csv")\ or self.base.endswith("-csv")\
@ -98,14 +105,16 @@ class Package:
@property @property
def licenses(self) -> List[str]: def licenses(self) -> List[str]:
""" """
:return: sum of licenses per each package Returns:
List[str]: sum of licenses per each package
""" """
return sorted(set(sum([package.licenses for package in self.packages.values()], start=[]))) return sorted(set(sum([package.licenses for package in self.packages.values()], start=[])))
@property @property
def web_url(self) -> str: def web_url(self) -> str:
""" """
:return: package AUR url Returns:
str: package AUR url
""" """
return f"{self.aur_url}/packages/{self.base}" return f"{self.aur_url}/packages/{self.base}"
@ -113,10 +122,14 @@ class Package:
def from_archive(cls: Type[Package], path: Path, pacman: Pacman, aur_url: str) -> Package: def from_archive(cls: Type[Package], path: Path, pacman: Pacman, aur_url: str) -> Package:
""" """
construct package properties from package archive construct package properties from package archive
:param path: path to package archive
:param pacman: alpm wrapper instance Args:
:param aur_url: AUR root url path(Path): path to package archive
:return: package properties pacman(Pacman): alpm wrapper instance
aur_url(str): AUR root url
Returns:
Package: package properties
""" """
package = pacman.handle.load_pkg(str(path)) package = pacman.handle.load_pkg(str(path))
return cls(package.base, package.version, aur_url, return cls(package.base, package.version, aur_url,
@ -126,9 +139,13 @@ class Package:
def from_aur(cls: Type[Package], name: str, aur_url: str) -> Package: def from_aur(cls: Type[Package], name: str, aur_url: str) -> Package:
""" """
construct package properties from AUR page construct package properties from AUR page
:param name: package name (either base or normal name)
:param aur_url: AUR root url Args:
:return: package properties name(str): package name (either base or normal name)
aur_url(str): AUR root url
Returns:
Package: package properties
""" """
package = AUR.info(name) package = AUR.info(name)
return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()}) return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()})
@ -137,9 +154,13 @@ class Package:
def from_build(cls: Type[Package], path: Path, aur_url: str) -> Package: def from_build(cls: Type[Package], path: Path, aur_url: str) -> Package:
""" """
construct package properties from sources directory construct package properties from sources directory
:param path: path to package sources directory
:param aur_url: AUR root url Args:
:return: package properties path(Path): path to package sources directory
aur_url(str): AUR root url
Returns:
Package: package properties
""" """
srcinfo, errors = parse_srcinfo((path / ".SRCINFO").read_text()) srcinfo, errors = parse_srcinfo((path / ".SRCINFO").read_text())
if errors: if errors:
@ -153,8 +174,12 @@ class Package:
def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package: def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package:
""" """
construct package properties from json dump construct package properties from json dump
:param dump: json dump body
:return: package properties Args:
dump(Dict[str, Any]): json dump body
Returns:
Package: package properties
""" """
packages = { packages = {
key: PackageDescription.from_json(value) key: PackageDescription.from_json(value)
@ -170,9 +195,13 @@ class Package:
def from_official(cls: Type[Package], name: str, aur_url: str) -> Package: def from_official(cls: Type[Package], name: str, aur_url: str) -> Package:
""" """
construct package properties from official repository page construct package properties from official repository page
:param name: package name (either base or normal name)
:param aur_url: AUR root url Args:
:return: package properties name(str): package name (either base or normal name)
aur_url(str): AUR root url
Returns:
Package: package properties
""" """
package = Official.info(name) package = Official.info(name)
return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()}) return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()})
@ -181,11 +210,15 @@ class Package:
def load(cls: Type[Package], package: str, source: PackageSource, pacman: Pacman, aur_url: str) -> Package: def load(cls: Type[Package], package: str, source: PackageSource, pacman: Pacman, aur_url: str) -> Package:
""" """
package constructor from available sources package constructor from available sources
:param package: one of path to sources directory, path to archive or package name/base
:param source: source of the package required to define the load method Args:
:param pacman: alpm wrapper instance (required to load from archive) package(str): one of path to sources directory, path to archive or package name/base
:param aur_url: AUR root url source(PackageSource): source of the package required to define the load method
:return: package properties pacman(Pacman): alpm wrapper instance (required to load from archive)
aur_url(str): AUR root url
Returns:
Package: package properties
""" """
try: try:
resolved_source = source.resolve(package) resolved_source = source.resolve(package)
@ -207,8 +240,12 @@ class Package:
def dependencies(path: Path) -> Set[str]: def dependencies(path: Path) -> Set[str]:
""" """
load dependencies from package sources load dependencies from package sources
:param path: path to package sources directory
:return: list of package dependencies including makedepends array, but excluding packages from this base Args:
path(Path): path to package sources directory
Returns:
Set[str]: list of package dependencies including makedepends array, but excluding packages from this base
""" """
# additional function to remove versions from dependencies # additional function to remove versions from dependencies
def extract_packages(raw_packages_list: List[str]) -> Set[str]: def extract_packages(raw_packages_list: List[str]) -> Set[str]:
@ -234,8 +271,12 @@ class Package:
def actual_version(self, paths: RepositoryPaths) -> str: def actual_version(self, paths: RepositoryPaths) -> str:
""" """
additional method to handle VCS package versions additional method to handle VCS package versions
:param paths: repository paths instance
:return: package version if package is not VCS and current version according to VCS otherwise Args:
paths(RepositoryPaths): repository paths instance
Returns:
str: package version if package is not VCS and current version according to VCS otherwise
""" """
if not self.is_vcs: if not self.is_vcs:
return self.version return self.version
@ -265,9 +306,13 @@ class Package:
def full_depends(self, pacman: Pacman, packages: Iterable[Package]) -> List[str]: def full_depends(self, pacman: Pacman, packages: Iterable[Package]) -> List[str]:
""" """
generate full dependencies list including transitive dependencies generate full dependencies list including transitive dependencies
:param pacman: alpm wrapper instance
:param packages: repository package list Args:
:return: all dependencies of the package pacman(Pacman): alpm wrapper instance
packages(Iterable[Package]): repository package list
Returns:
List[str]: all dependencies of the package
""" """
dependencies = {} dependencies = {}
# load own package dependencies # load own package dependencies
@ -295,10 +340,14 @@ class Package:
def is_outdated(self, remote: Package, paths: RepositoryPaths, calculate_version: bool = True) -> bool: def is_outdated(self, remote: Package, paths: RepositoryPaths, calculate_version: bool = True) -> bool:
""" """
check if package is out-of-dated check if package is out-of-dated
:param remote: package properties from remote source
:param paths: repository paths instance. Required for VCS packages cache Args:
:param calculate_version: expand version to actual value (by calculating git versions) remote(Package): package properties from remote source
:return: True if the package is out-of-dated and False otherwise paths(RepositoryPaths): repository paths instance. Required for VCS packages cache
calculate_version(bool, optional): expand version to actual value (by calculating git versions) (Default value = True)
Returns:
bool: True if the package is out-of-dated and False otherwise
""" """
remote_version = remote.actual_version(paths) if calculate_version else remote.version remote_version = remote.actual_version(paths) if calculate_version else remote.version
result: int = vercmp(self.version, remote_version) result: int = vercmp(self.version, remote_version)
@ -307,7 +356,9 @@ class Package:
def pretty_print(self) -> str: def pretty_print(self) -> str:
""" """
generate pretty string representation generate pretty string representation
:return: print-friendly string
Returns:
str: print-friendly string
""" """
details = "" if self.is_single_package else f""" ({" ".join(sorted(self.packages.keys()))})""" details = "" if self.is_single_package else f""" ({" ".join(sorted(self.packages.keys()))})"""
return f"{self.base}{details}" return f"{self.base}{details}"
@ -315,6 +366,8 @@ class Package:
def view(self) -> Dict[str, Any]: def view(self) -> Dict[str, Any]:
""" """
generate json package view generate json package view
:return: json-friendly dictionary
Returns:
Dict[str, Any]: json-friendly dictionary
""" """
return asdict(self) return asdict(self)

View File

@ -31,17 +31,19 @@ from ahriman.core.util import filter_json
class PackageDescription: class PackageDescription:
""" """
package specific properties package specific properties
:ivar architecture: package architecture
:ivar archive_size: package archive size Attributes:
:ivar build_date: package build date architecture(Optional[str]): package architecture
:ivar depends: package dependencies list archive_size(Optional[int]): package archive size
:ivar description: package description build_date(Optional[int]): package build date
:ivar filename: package archive name depends(List[str]): package dependencies list
:ivar groups: package groups description(Optional[str]): package description
:ivar installed_size: package installed size filename(Optional[str]): package archive name
:ivar licenses: package licenses list groups(List[str]): package groups
:ivar provides: list of provided packages installed_size(Optional[int]): package installed size
:ivar url: package url licenses(List[str]): package licenses list
provides(List[str]): list of provided packages
url(Optional[str]): package url
""" """
architecture: Optional[str] = None architecture: Optional[str] = None
@ -59,7 +61,8 @@ class PackageDescription:
@property @property
def filepath(self) -> Optional[Path]: def filepath(self) -> Optional[Path]:
""" """
:return: path object for current filename Returns:
Optional[Path]: path object for current filename
""" """
return Path(self.filename) if self.filename is not None else None return Path(self.filename) if self.filename is not None else None
@ -67,8 +70,12 @@ class PackageDescription:
def from_json(cls: Type[PackageDescription], dump: Dict[str, Any]) -> PackageDescription: def from_json(cls: Type[PackageDescription], dump: Dict[str, Any]) -> PackageDescription:
""" """
construct package properties from json dump construct package properties from json dump
:param dump: json dump body
:return: package properties Args:
dump(Dict[str, Any]): json dump body
Returns:
PackageDescription: package properties
""" """
# filter to only known fields # filter to only known fields
known_fields = [pair.name for pair in fields(cls)] known_fields = [pair.name for pair in fields(cls)]
@ -78,9 +85,13 @@ class PackageDescription:
def from_package(cls: Type[PackageDescription], package: Package, path: Path) -> PackageDescription: def from_package(cls: Type[PackageDescription], package: Package, path: Path) -> PackageDescription:
""" """
construct class from alpm package class construct class from alpm package class
:param package: alpm generated object
:param path: path to package archive Args:
:return: package properties based on tarball package(Package): alpm generated object
path(Path): path to package archive
Returns:
PackageDescription: package properties based on tarball
""" """
return cls( return cls(
architecture=package.arch, architecture=package.arch,
@ -98,6 +109,8 @@ class PackageDescription:
def view(self) -> Dict[str, Any]: def view(self) -> Dict[str, Any]:
""" """
generate json package view generate json package view
:return: json-friendly dictionary
Returns:
Dict[str, Any]: json-friendly dictionary
""" """
return asdict(self) return asdict(self)

View File

@ -29,13 +29,15 @@ from ahriman.core.util import package_like
class PackageSource(Enum): class PackageSource(Enum):
""" """
package source for addition enumeration package source for addition enumeration
:cvar Auto: automatically determine type of the source
:cvar Archive: source is a package archive Attributes:
:cvar AUR: source is an AUR package for which it should search Auto(PackageSource): (class attribute) automatically determine type of the source
:cvar Directory: source is a directory which contains packages Archive(PackageSource): (class attribute) source is a package archive
:cvar Local: source is locally stored PKGBUILD AUR(PackageSource): (class attribute) source is an AUR package for which it should search
:cvar Remote: source is remote (http, ftp etc) link Directory(PackageSource): (class attribute) source is a directory which contains packages
:cvar Repository: source is official repository 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 = "auto" Auto = "auto"
@ -49,8 +51,12 @@ class PackageSource(Enum):
def resolve(self, source: str) -> PackageSource: def resolve(self, source: str) -> PackageSource:
""" """
resolve auto into the correct type resolve auto into the correct type
:param source: source of the package
:return: non-auto type of the package source Args:
source(str): source of the package
Returns:
PackageSource: non-auto type of the package source
""" """
if self != PackageSource.Auto: if self != PackageSource.Auto:
return self return self

View File

@ -25,9 +25,11 @@ from typing import Any
class Property: class Property:
""" """
holder of object properties descriptor holder of object properties descriptor
:ivar name: name of the property
:ivar value: property value Attributes:
:ivar is_required: if set to True then this property is required name(str): name of the property
value(Any): property value
is_required(bool): if set to True then this property is required
""" """
name: str name: str

View File

@ -28,11 +28,13 @@ from ahriman.core.exceptions import InvalidOption
class ReportSettings(Enum): class ReportSettings(Enum):
""" """
report targets enumeration report targets enumeration
:cvar Disabled: option which generates no report for testing purpose
:cvar HTML: html report generation Attributes:
:cvar Email: email report generation Disabled(ReportSettings): (class attribute) option which generates no report for testing purpose
:cvar Console: print result to console HTML(ReportSettings): (class attribute) html report generation
:cvar Telegram: markdown report to telegram channel Email(ReportSettings): (class attribute) email report generation
Console(ReportSettings): (class attribute) print result to console
Telegram(ReportSettings): (class attribute) markdown report to telegram channel
""" """
Disabled = "disabled" # for testing purpose Disabled = "disabled" # for testing purpose
@ -45,8 +47,12 @@ class ReportSettings(Enum):
def from_option(cls: Type[ReportSettings], value: str) -> ReportSettings: def from_option(cls: Type[ReportSettings], value: str) -> ReportSettings:
""" """
construct value from configuration construct value from configuration
:param value: configuration value
:return: parsed value Args:
value(str): configuration value
Returns:
ReportSettings: parsed value
""" """
if value.lower() in ("html",): if value.lower() in ("html",):
return cls.HTML return cls.HTML

View File

@ -33,8 +33,10 @@ from ahriman.core.exceptions import InvalidPath
class RepositoryPaths: class RepositoryPaths:
""" """
repository paths holder. For the most operations with paths you want to use this object repository paths holder. For the most operations with paths you want to use this object
:ivar root: repository root (i.e. ahriman home)
:ivar architecture: repository architecture Attributes:
root(Path): repository root (i.e. ahriman home)
architecture(str): repository architecture
""" """
root: Path root: Path
@ -43,14 +45,16 @@ class RepositoryPaths:
@property @property
def cache(self) -> Path: def cache(self) -> Path:
""" """
:return: directory for packages cache (mainly used for VCS packages) Returns:
Path: directory for packages cache (mainly used for VCS packages)
""" """
return self.root / "cache" return self.root / "cache"
@property @property
def chroot(self) -> Path: def chroot(self) -> Path:
""" """
:return: directory for devtools chroot Returns:
Path: directory for devtools chroot
""" """
# for the chroot directory devtools will create own tree, and we don"t have to specify architecture here # for the chroot directory devtools will create own tree, and we don"t have to specify architecture here
return self.root / "chroot" return self.root / "chroot"
@ -58,21 +62,24 @@ class RepositoryPaths:
@property @property
def packages(self) -> Path: def packages(self) -> Path:
""" """
:return: directory for built packages Returns:
Path: directory for built packages
""" """
return self.root / "packages" / self.architecture return self.root / "packages" / self.architecture
@property @property
def repository(self) -> Path: def repository(self) -> Path:
""" """
:return: repository directory Returns:
Path: repository directory
""" """
return self.root / "repository" / self.architecture return self.root / "repository" / self.architecture
@property @property
def root_owner(self) -> Tuple[int, int]: def root_owner(self) -> Tuple[int, int]:
""" """
:return: owner user and group of the root directory Returns:
Tuple[int, int]: owner user and group of the root directory
""" """
return self.owner(self.root) return self.owner(self.root)
@ -80,8 +87,12 @@ class RepositoryPaths:
def known_architectures(cls: Type[RepositoryPaths], root: Path) -> Set[str]: def known_architectures(cls: Type[RepositoryPaths], root: Path) -> Set[str]:
""" """
get known architectures get known architectures
:param root: repository root
:return: list of architectures for which tree is created Args:
root(Path): repository root
Returns:
Set[str]: list of architectures for which tree is created
""" """
paths = cls(root, "") paths = cls(root, "")
return { return {
@ -94,8 +105,12 @@ class RepositoryPaths:
def owner(path: Path) -> Tuple[int, int]: def owner(path: Path) -> Tuple[int, int]:
""" """
retrieve owner information by path retrieve owner information by path
:param path: path for which extract ids
:return: owner user and group ids of the directory Args:
path(Path): path for which extract ids
Returns:
Tuple[int, int]: owner user and group ids of the directory
""" """
stat = path.stat() stat = path.stat()
return stat.st_uid, stat.st_gid return stat.st_uid, stat.st_gid
@ -103,21 +118,23 @@ class RepositoryPaths:
def cache_for(self, package_base: str) -> Path: def cache_for(self, package_base: str) -> Path:
""" """
get path to cached PKGBUILD and package sources for the package base get path to cached PKGBUILD and package sources for the package base
:param package_base: package base name
:return: full path to directory for specified package base cache Args:
package_base(str): package base name
Returns:
Path: full path to directory for specified package base cache
""" """
return self.cache / package_base return self.cache / package_base
def chown(self, path: Path) -> None: def chown(self, path: Path) -> None:
""" """
set owner of path recursively (from root) to root owner set owner of path recursively (from root) to root owner
:param path: path to be chown
Args:
path(Path): path to be chown
""" """
def set_owner(current: Path) -> None: def set_owner(current: Path) -> None:
"""
set owner to the specified path
:param current: path to set
"""
uid, gid = self.owner(current) uid, gid = self.owner(current)
if uid == root_uid and gid == root_gid: if uid == root_uid and gid == root_gid:
return return
@ -133,7 +150,9 @@ class RepositoryPaths:
def tree_clear(self, package_base: str) -> None: def tree_clear(self, package_base: str) -> None:
""" """
clear package specific files clear package specific files
:param package_base: package base name
Args:
package_base(str): package base name
""" """
for directory in ( for directory in (
self.cache_for(package_base), self.cache_for(package_base),

View File

@ -33,8 +33,10 @@ class Result:
def __init__(self, success: Optional[Iterable[Package]] = None, failed: Optional[Iterable[Package]] = None) -> None: def __init__(self, success: Optional[Iterable[Package]] = None, failed: Optional[Iterable[Package]] = None) -> None:
""" """
default constructor default constructor
:param success: initial list of successes packages
:param failed: initial list of failed packages Args:
success(Optional[Iterable[Package]], optional): initial list of successes packages (Default value = None)
failed(Optional[Iterable[Package]], optional): initial list of failed packages (Default value = None)
""" """
success = success or [] success = success or []
self._success = {package.base: package for package in success} self._success = {package.base: package for package in success}
@ -44,35 +46,42 @@ class Result:
@property @property
def failed(self) -> List[Package]: def failed(self) -> List[Package]:
""" """
:return: list of packages which were failed Returns:
List[Package]: list of packages which were failed
""" """
return list(self._failed.values()) return list(self._failed.values())
@property @property
def is_empty(self) -> bool: def is_empty(self) -> bool:
""" """
:return: True in case if success list is empty and False otherwise Returns:
bool: True in case if success list is empty and False otherwise
""" """
return not bool(self._success) return not bool(self._success)
@property @property
def success(self) -> List[Package]: def success(self) -> List[Package]:
""" """
:return: list of packages with success result Returns:
List[Package]: list of packages with success result
""" """
return list(self._success.values()) return list(self._success.values())
def add_failed(self, package: Package) -> None: def add_failed(self, package: Package) -> None:
""" """
add new package to failed built add new package to failed built
:param package: package with errors during build
Args:
package(Package): package with errors during build
""" """
self._failed[package.base] = package self._failed[package.base] = package
def add_success(self, package: Package) -> None: def add_success(self, package: Package) -> None:
""" """
add new package to success built add new package to success built
:param package: package built
Args:
package(Package): package built
""" """
self._success[package.base] = package self._success[package.base] = package
@ -80,8 +89,12 @@ class Result:
def merge(self, other: Result) -> Result: def merge(self, other: Result) -> Result:
""" """
merge other result into this one. This method assumes that other has fresh info about status and override it merge other result into this one. This method assumes that other has fresh info about status and override it
:param other: instance of the newest result
:return: updated instance Args:
other(Result): instance of the newest result
Returns:
Result: updated instance
""" """
for base, package in other._failed.items(): for base, package in other._failed.items():
if base in self._success: if base in self._success:
@ -97,8 +110,12 @@ class Result:
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
""" """
check if other is the same object check if other is the same object
:param other: other object instance
:return: True if the other object is the same and False otherwise Args:
other(Any): other object instance
Returns:
bool: True if the other object is the same and False otherwise
""" """
if not isinstance(other, Result): if not isinstance(other, Result):
return False return False

Some files were not shown because too many files have changed in this diff Show More