review loggers once more

This commit makes loggers like java.util.logging with fully qualified
logger name which is created by LazyLogging trait
This commit is contained in:
Evgenii Alekseev 2022-06-27 01:34:25 +03:00
parent cee4fd4cce
commit cd361a483d
28 changed files with 319 additions and 274 deletions

View File

@ -60,6 +60,7 @@ Again, the most checks can be performed by `make check` command, though some add
* The file size mentioned above must be applicable in general. In case of big classes consider splitting them into traits.
* No global variable allowed outside of `ahriman.version` module.
* Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent.
* If your class writes anything to log, the `ahriman.core.lazy_logging.LazyLogging` trait must be used.
### Other checks

View File

@ -692,7 +692,7 @@ You can also edit configuration and forward logs to ``stderr``, just change ``ha
sed -i 's/handlers = syslog_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_.
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_. The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger).
Html customization
^^^^^^^^^^^^^^^^^^

View File

@ -1,5 +1,5 @@
[loggers]
keys = root,build,http,stderr,boto3,botocore,nose,s3transfer
keys = root,http,stderr,boto3,botocore,nose,s3transfer
[handlers]
keys = console_handler,syslog_handler
@ -20,11 +20,11 @@ formatter = syslog_format
args = ("/dev/log",)
[formatter_generic_format]
format = [%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s
format = [%(levelname)s %(asctime)s] [%(threadName)s] [%(name)s]: %(message)s
datefmt =
[formatter_syslog_format]
format = [%(levelname)s] [%(name)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s
format = [%(levelname)s] [%(threadName)s] [%(name)s]: %(message)s
datefmt =
[logger_root]
@ -32,12 +32,6 @@ level = DEBUG
handlers = syslog_handler
qualname = root
[logger_build]
level = DEBUG
handlers = syslog_handler
qualname = build
propagate = 0
[logger_http]
level = DEBUG
handlers = syslog_handler

View File

@ -17,14 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.repository import Repository
class ApplicationProperties:
class ApplicationProperties(LazyLogging):
"""
application base properties class
@ -32,7 +31,6 @@ class ApplicationProperties:
architecture(str): repository architecture
configuration(Configuration): configuration instance
database(SQLite): database instance
logger(logging.Logger): application logger
repository(Repository): repository instance
"""
@ -44,9 +42,8 @@ class ApplicationProperties:
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
unsafe(bool): if set no user check will be performed before path creation
"""
self.logger = logging.getLogger("root")
self.configuration = configuration
self.architecture = architecture
self.database = SQLite.load(configuration)

View File

@ -20,7 +20,6 @@
from __future__ import annotations
import argparse
import logging
from pathlib import Path
from types import TracebackType
@ -29,12 +28,13 @@ from typing import Literal, Optional, Type
from ahriman import version
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import DuplicateRun
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.status.client import Client
from ahriman.core.util import check_user
from ahriman.models.build_status import BuildStatusEnum
class Lock:
class Lock(LazyLogging):
"""
wrapper for application lock file
@ -115,10 +115,8 @@ class Lock:
"""
status = self.reporter.get_internal()
if status.version is not None and status.version != version.__version__:
logging.getLogger("root").warning(
"status watcher version mismatch, our %s, their %s",
version.__version__,
status.version)
self.logger.warning("status watcher version mismatch, our %s, their %s",
version.__version__, status.version)
def check_user(self) -> None:
"""

View File

@ -19,21 +19,17 @@
#
from __future__ import annotations
import logging
from typing import Dict, List, Type
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.aur_package import AURPackage
class Remote:
class Remote(LazyLogging):
"""
base class for remote package search
Attributes:
logger(logging.Logger): class logger
Examples:
These classes are designed to be used without instancing. In order to achieve it several class methods are
provided: ``info``, ``multisearch`` and ``search``. Thus, the basic flow is the following::
@ -47,12 +43,6 @@ class Remote:
directly, whereas ``multisearch`` splits search one by one and finds intersection between search results.
"""
def __init__(self) -> None:
"""
default constructor
"""
self.logger = logging.getLogger("build")
@classmethod
def info(cls: Type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage:
"""

View File

@ -17,22 +17,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from pathlib import Path
from typing import List
from ahriman.core.exceptions import BuildFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output
from ahriman.models.repository_paths import RepositoryPaths
class Repo:
class Repo(LazyLogging):
"""
repo-add and repo-remove wrapper
Attributes:
logger(logging.Logger): class logger
name(str): repository name
paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive
@ -50,7 +48,6 @@ class Repo:
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")
self.name = name
self.paths = paths
self.uid, _ = paths.root_owner

View File

@ -19,23 +19,21 @@
#
from __future__ import annotations
import logging
from typing import Optional, Type
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user_access import UserAccess
class Auth:
class Auth(LazyLogging):
"""
helper to deal with user authorization
Attributes:
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
allow_read_only(bool): allow read only access to APIs
"""
@ -48,8 +46,6 @@ class Auth:
configuration(Configuration): configuration instance
provider(AuthSettings, optional): authorization type definition (Default value = AuthSettings.Disabled)
"""
self.logger = logging.getLogger("http")
self.allow_read_only = configuration.getboolean("auth", "allow_read_only")
self.enabled = provider.is_enabled

View File

@ -17,82 +17,31 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import shutil
from pathlib import Path
from typing import List, Optional
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output, walk
from ahriman.models.package import Package
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths
class Sources:
class Sources(LazyLogging):
"""
helper to download package sources (PKGBUILD etc)
Attributes:
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages
logger(logging.Logger): (class attribute) class logger
"""
DEFAULT_BRANCH = "master" # default fallback branch
logger = logging.getLogger("build")
_check_output = check_output
@staticmethod
def _add(sources_dir: Path, *pattern: str) -> None:
"""
track found files via git
Args:
sources_dir(Path): local path to git repository
*pattern(str): glob patterns
"""
# glob directory to find files which match the specified patterns
found_files: List[Path] = []
for glob in pattern:
found_files.extend(sources_dir.glob(glob))
if not found_files:
return # no additional files found
Sources.logger.info("found matching files %s", found_files)
# add them to index
Sources._check_output("git", "add", "--intent-to-add",
*[str(fn.relative_to(sources_dir)) for fn in found_files],
exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod
def _diff(sources_dir: Path) -> str:
"""
generate diff from the current version and write it to the output file
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)
@staticmethod
def _move(pkgbuild_dir: Path, sources_dir: Path) -> None:
"""
move content from pkgbuild_dir to sources_dir
Args:
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
sources_dir(Path): path to target directory
"""
if pkgbuild_dir == sources_dir:
return # directories are the same, no need to move
for src in walk(pkgbuild_dir):
dst = sources_dir / src.relative_to(pkgbuild_dir)
shutil.move(src, dst)
@staticmethod
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
"""
@ -102,37 +51,38 @@ class Sources:
sources_dir(Path): local path to fetch
remote(Optional[RemoteSource]): remote target (from where to fetch)
"""
instance = Sources()
# local directory exists and there is .git directory
is_initialized_git = (sources_dir / ".git").is_dir()
if is_initialized_git and not Sources.has_remotes(sources_dir):
if is_initialized_git and not instance.has_remotes(sources_dir):
# there is git repository, but no remote configured so far
Sources.logger.info("skip update at %s because there are no branches configured", sources_dir)
instance.logger.info("skip update at %s because there are no branches configured", sources_dir)
return
branch = remote.branch if remote is not None else Sources.DEFAULT_BRANCH
branch = remote.branch if remote is not None else instance.DEFAULT_BRANCH
if is_initialized_git:
Sources.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
instance.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
Sources._check_output("git", "fetch", "origin", branch,
exception=None, cwd=sources_dir, logger=Sources.logger)
exception=None, cwd=sources_dir, logger=instance.logger)
elif remote is not None:
Sources.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
remote.git_url, str(sources_dir),
exception=None, cwd=sources_dir, logger=Sources.logger)
exception=None, cwd=sources_dir, logger=instance.logger)
else:
# it will cause an exception later
Sources.logger.error("%s is not initialized, but no remote provided", sources_dir)
instance.logger.error("%s is not initialized, but no remote provided", sources_dir)
# and now force reset to our branch
Sources._check_output("git", "checkout", "--force", branch,
exception=None, cwd=sources_dir, logger=Sources.logger)
exception=None, cwd=sources_dir, logger=instance.logger)
Sources._check_output("git", "reset", "--hard", f"origin/{branch}",
exception=None, cwd=sources_dir, logger=Sources.logger)
exception=None, cwd=sources_dir, logger=instance.logger)
# move content if required
# we are using full path to source directory in order to make append possible
pkgbuild_dir = remote.pkgbuild_dir if remote is not None else sources_dir.resolve()
Sources._move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
instance.move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
@staticmethod
def has_remotes(sources_dir: Path) -> bool:
@ -145,7 +95,8 @@ class Sources:
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)
instance = Sources()
remotes = Sources._check_output("git", "remote", exception=None, cwd=sources_dir, logger=instance.logger)
return bool(remotes)
@staticmethod
@ -156,8 +107,9 @@ class Sources:
Args:
sources_dir(Path): local path to sources
"""
instance = Sources()
Sources._check_output("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH,
exception=None, cwd=sources_dir, logger=Sources.logger)
exception=None, cwd=sources_dir, logger=instance.logger)
@staticmethod
def load(sources_dir: Path, package: Package, patch: Optional[str], paths: RepositoryPaths) -> None:
@ -170,29 +122,16 @@ class Sources:
patch(Optional[str]): optional patch to be applied
paths(RepositoryPaths): repository paths instance
"""
instance = Sources()
if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir:
# no need to clone whole repository, just copy from cache first
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
Sources.fetch(sources_dir, package.remote)
if patch is None:
Sources.logger.info("no patches found")
instance.logger.info("no patches found")
return
Sources.patch_apply(sources_dir, patch)
@staticmethod
def patch_apply(sources_dir: Path, patch: str) -> None:
"""
apply patches if any
Args:
sources_dir(Path): local path to directory with git sources
patch(str): patch to be applied
"""
# create patch
Sources.logger.info("apply patch from database")
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
exception=None, cwd=sources_dir, input_data=patch, logger=Sources.logger)
instance.patch_apply(sources_dir, patch)
@staticmethod
def patch_create(sources_dir: Path, *pattern: str) -> str:
@ -206,6 +145,67 @@ class Sources:
Returns:
str: patch as plain text
"""
Sources._add(sources_dir, *pattern)
diff = Sources._diff(sources_dir)
instance = Sources()
instance.add(sources_dir, *pattern)
diff = instance.diff(sources_dir)
return f"{diff}\n" # otherwise, patch will be broken
def add(self, sources_dir: Path, *pattern: str) -> None:
"""
track found files via git
Args:
sources_dir(Path): local path to git repository
*pattern(str): glob patterns
"""
# glob directory to find files which match the specified patterns
found_files: List[Path] = []
for glob in pattern:
found_files.extend(sources_dir.glob(glob))
if not found_files:
return # no additional files found
self.logger.info("found matching files %s", found_files)
# add them to index
Sources._check_output("git", "add", "--intent-to-add",
*[str(fn.relative_to(sources_dir)) for fn in found_files],
exception=None, cwd=sources_dir, logger=self.logger)
def diff(self, sources_dir: Path) -> str:
"""
generate diff from the current version and write it to the output file
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=self.logger)
def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None:
"""
move content from pkgbuild_dir to sources_dir
Args:
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
sources_dir(Path): path to target directory
"""
del self
if pkgbuild_dir == sources_dir:
return # directories are the same, no need to move
for src in walk(pkgbuild_dir):
dst = sources_dir / src.relative_to(pkgbuild_dir)
shutil.move(src, dst)
def patch_apply(self, sources_dir: Path, patch: str) -> None:
"""
apply patches if any
Args:
sources_dir(Path): local path to directory with git sources
patch(str): patch to be applied
"""
# create patch
self.logger.info("apply patch from database")
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
exception=None, cwd=sources_dir, input_data=patch, logger=self.logger)

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from pathlib import Path
from typing import List
@ -26,17 +24,17 @@ from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import BuildFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
class Task:
class Task(LazyLogging):
"""
base package build task
Attributes:
logger(logging.Logger): class logger
package(Package): package definitions
paths(RepositoryPaths): repository paths instance
uid(int): uid of the repository owner user
@ -53,7 +51,6 @@ class Task:
configuration(Configuration): configuration instance
paths(RepositoryPaths): repository paths instance
"""
self.logger = logging.getLogger("build")
self.package = package
self.paths = paths
self.uid, _ = paths.root_owner

View File

@ -19,8 +19,6 @@
#
from __future__ import annotations
import logging
from importlib import import_module
from pathlib import Path
from pkgutil import iter_modules
@ -29,11 +27,12 @@ from typing import List, Type
from ahriman.core.configuration import Configuration
from ahriman.core.database.data import migrate_data
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.migration import Migration
from ahriman.models.migration_result import MigrationResult
class Migrations:
class Migrations(LazyLogging):
"""
simple migration wrapper for the sqlite
idea comes from https://www.ash.dev/blog/simple-migration-system-in-sqlite/
@ -41,7 +40,6 @@ class Migrations:
Attributes:
configuration(Configuration): configuration instance
connection(Connection): database connection
logger(logging.Logger): class logger
"""
def __init__(self, connection: Connection, configuration: Configuration) -> None:
@ -54,7 +52,6 @@ class Migrations:
"""
self.connection = connection
self.configuration = configuration
self.logger = logging.getLogger("root")
@classmethod
def migrate(cls: Type[Migrations], connection: Connection, configuration: Configuration) -> MigrationResult:

View File

@ -17,23 +17,22 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import sqlite3
from pathlib import Path
from sqlite3 import Connection, Cursor
from typing import Any, Dict, Tuple, TypeVar, Callable
from ahriman.core.lazy_logging import LazyLogging
T = TypeVar("T")
class Operations:
class Operations(LazyLogging):
"""
base operation class
Attributes:
logger(logging.Logger): class logger
path(Path): path to the database file
"""
@ -45,7 +44,6 @@ class Operations:
path(Path): path to the database file
"""
self.path = path
self.logger = logging.getLogger("root")
@staticmethod
def factory(cursor: Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]:

View File

@ -0,0 +1,64 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from typing import Any
class LazyLogging:
"""
wrapper for the logger library inspired by scala lazy logging module
Attributes:
logger(logging.Logger): class logger instance
"""
logger: logging.Logger
def __getattr__(self, item: str) -> Any:
"""
logger extractor
Args:
item(str) property name:
Returns:
Any: attribute by its name
Raises:
AttributeError: in case if no such attribute found
"""
if item == "logger":
logger = logging.getLogger(self.logger_name)
setattr(self, item, logger)
return logger
raise AttributeError(f"'{self.__class__.__qualname__}' object has no attribute '{item}'")
@property
def logger_name(self) -> str:
"""
extract logger name for the class
Returns:
str: logger name as combination of module name and class name
"""
clazz = self.__class__
prefix = "" if clazz.__module__ is None else f"{clazz.__module__}."
return f"{prefix}{self.__class__.__qualname__}"

View File

@ -19,25 +19,23 @@
#
from __future__ import annotations
import logging
from typing import Iterable, Type
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ReportFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.package import Package
from ahriman.models.report_settings import ReportSettings
from ahriman.models.result import Result
class Report:
class Report(LazyLogging):
"""
base report generator
Attributes:
architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): class logger
Examples:
``Report`` classes provide several method in order to operate with the report generation and additional class
@ -67,7 +65,6 @@ class Report:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("root")
self.architecture = architecture
self.configuration = configuration

View File

@ -17,20 +17,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.repo import Repo
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnsafeRun
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.sign.gpg import GPG
from ahriman.core.status.client import Client
from ahriman.core.triggers import TriggerLoader
from ahriman.core.util import check_user
class RepositoryProperties:
class RepositoryProperties(LazyLogging):
"""
repository internal objects holder
@ -39,7 +38,6 @@ class RepositoryProperties:
configuration(Configuration): configuration instance
database(SQLite): database instance
ignore_list(List[str]): package bases which will be ignored during auto updates
logger(logging.Logger): class logger
name(str): repository name
pacman(Pacman): alpm wrapper instance
paths(RepositoryPaths): repository paths instance
@ -61,7 +59,6 @@ class RepositoryProperties:
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.architecture = architecture
self.configuration = configuration
self.database = database

View File

@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import requests
from pathlib import Path
@ -25,11 +24,12 @@ from typing import List, Optional, Set, Tuple
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import BuildFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output, exception_response_text
from ahriman.models.sign_settings import SignSettings
class GPG:
class GPG(LazyLogging):
"""
gnupg wrapper
@ -37,7 +37,6 @@ class GPG:
architecture(str): repository architecture
configuration(Configuration): configuration instance
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)
"""
@ -51,7 +50,6 @@ class GPG:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("build")
self.architecture = architecture
self.configuration = configuration
self.targets, self.default_key = self.sign_options(configuration)

View File

@ -20,7 +20,6 @@
from __future__ import annotations
import argparse
import logging
import uuid
from multiprocessing import Process, Queue
@ -28,10 +27,11 @@ from threading import Lock, Thread
from typing import Callable, Dict, Iterable, Tuple
from ahriman.core.configuration import Configuration
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.package_source import PackageSource
class Spawn(Thread):
class Spawn(Thread, LazyLogging):
"""
helper to spawn external ahriman process
MUST NOT be used directly, the only one usage allowed is to spawn process from web services
@ -40,7 +40,6 @@ class Spawn(Thread):
active(Dict[str, Process]): map of active child processes required to avoid zombies
architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): spawner logger
queue(Queue[Tuple[str, bool]]): multiprocessing queue to read updates from processes
"""
@ -57,7 +56,6 @@ class Spawn(Thread):
self.architecture = architecture
self.args_parser = args_parser
self.configuration = configuration
self.logger = logging.getLogger("http")
self.lock = Lock()
self.active: Dict[str, Process] = {}

View File

@ -17,19 +17,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from typing import Dict, List, Optional, Tuple
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnknownPackage
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.repository import Repository
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
class Watcher:
class Watcher(LazyLogging):
"""
package status watcher
@ -38,7 +37,6 @@ class Watcher:
database(SQLite): database instance
known(Dict[str, Tuple[Package, BuildStatus]]): list of known packages. For the most cases ``packages`` should
be used instead
logger(logging.Logger): class logger
repository(Repository): repository object
status(BuildStatus): daemon status
"""
@ -52,8 +50,6 @@ class Watcher:
configuration(Configuration): configuration instance
database(SQLite): database instance
"""
self.logger = logging.getLogger("http")
self.architecture = architecture
self.database = database
self.repository = Repository(architecture, configuration, database, no_report=True, unsafe=False)

View File

@ -17,12 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
import requests
from typing import List, Optional, Tuple
from ahriman.core.configuration import Configuration
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.status.client import Client
from ahriman.core.util import exception_response_text
from ahriman.models.build_status import BuildStatusEnum, BuildStatus
@ -31,13 +31,12 @@ from ahriman.models.package import Package
from ahriman.models.user import User
class WebClient(Client):
class WebClient(Client, LazyLogging):
"""
build status reporter web client
Attributes:
address(str): address of the web service
logger(logging.Logger): class logger
user(Optional[User]): web service user descriptor
"""
@ -48,7 +47,6 @@ class WebClient(Client):
Args:
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("http")
self.address = self.parse_address(configuration)
self.user = User.from_option(
configuration.get("web", "username", fallback=None),

View File

@ -17,23 +17,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.package import Package
from ahriman.models.result import Result
class Trigger:
class Trigger(LazyLogging):
"""
trigger base class
Attributes:
architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): application logger
Examples:
This class must be used in order to create own extension. Basically idea is the following::
@ -61,7 +59,6 @@ class Trigger:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("root")
self.architecture = architecture
self.configuration = configuration

View File

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import importlib
import logging
import os
from pathlib import Path
@ -27,19 +26,19 @@ from typing import Iterable
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InvalidExtension
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.triggers import Trigger
from ahriman.models.package import Package
from ahriman.models.result import Result
class TriggerLoader:
class TriggerLoader(LazyLogging):
"""
trigger loader class
Attributes:
architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): application logger
triggers(List[Trigger]): list of loaded triggers according to the configuration
Examples:
@ -66,7 +65,6 @@ class TriggerLoader:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("root")
self.architecture = architecture
self.configuration = configuration

View File

@ -19,25 +19,23 @@
#
from __future__ import annotations
import logging
from pathlib import Path
from typing import Iterable, Type
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import SyncFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.package import Package
from ahriman.models.upload_settings import UploadSettings
class Upload:
class Upload(LazyLogging):
"""
base remote sync class
Attributes:
architecture(str): repository architecture
configuration(Configuration): configuration instance
logger(logging.Logger): application logger
Examples:
These classes provide the way to upload packages to remote sources as it is described in their implementations.
@ -66,7 +64,6 @@ class Upload:
architecture(str): repository architecture
configuration(Configuration): configuration instance
"""
self.logger = logging.getLogger("root")
self.architecture = architecture
self.configuration = configuration

View File

@ -20,14 +20,13 @@
import datetime
import io
import os
from enum import Enum
import requests
import shutil
import subprocess
import tempfile
from contextlib import contextmanager
from enum import Enum
from logging import Logger
from pathlib import Path
from typing import Any, Dict, Generator, IO, Iterable, List, Optional, Type, Union

View File

@ -20,7 +20,6 @@
from __future__ import annotations
import copy
import logging
from dataclasses import asdict, dataclass
from pathlib import Path
@ -31,6 +30,7 @@ from typing import Any, Dict, Iterable, List, Optional, Set, Type
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output, full_version
from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource
@ -39,7 +39,7 @@ from ahriman.models.repository_paths import RepositoryPaths
@dataclass
class Package:
class Package(LazyLogging):
"""
package properties representation
@ -281,23 +281,22 @@ class Package:
from ahriman.core.build_tools.sources import Sources
logger = logging.getLogger("build")
Sources.load(paths.cache_for(self.base), self, None, paths)
try:
# update pkgver first
Package._check_output("makepkg", "--nodeps", "--nobuild",
exception=None, cwd=paths.cache_for(self.base), logger=logger)
exception=None, cwd=paths.cache_for(self.base), logger=self.logger)
# generate new .SRCINFO and put it to parser
srcinfo_source = Package._check_output("makepkg", "--printsrcinfo",
exception=None, cwd=paths.cache_for(self.base), logger=logger)
exception=None, cwd=paths.cache_for(self.base), logger=self.logger)
srcinfo, errors = parse_srcinfo(srcinfo_source)
if errors:
raise InvalidPackageInfo(errors)
return full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"])
except Exception:
logger.exception("cannot determine version of VCS package, make sure that you have VCS tools installed")
self.logger.exception("cannot determine version of VCS package, make sure that VCS tools are installed")
return self.version

View File

@ -94,7 +94,7 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw
Returns:
web.Application: web application instance
"""
application = web.Application(logger=logging.getLogger("http"))
application = web.Application(logger=logging.getLogger(__name__))
application.on_shutdown.append(on_shutdown)
application.on_startup.append(on_startup)

View File

@ -0,0 +1,14 @@
import pytest
from ahriman.core.build_tools.sources import Sources
@pytest.fixture
def sources() -> Sources:
"""
sources fixture
Returns:
Sources: sources instance
"""
return Sources()

View File

@ -10,64 +10,6 @@ from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths
def test_add(mocker: MockerFixture) -> None:
"""
must add files to git
"""
glob_mock = mocker.patch("pathlib.Path.glob", return_value=[Path("local/1"), Path("local/2")])
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
Sources._add(local, "pattern1", "pattern2")
glob_mock.assert_has_calls([mock.call("pattern1"), mock.call("pattern2")])
check_output_mock.assert_called_once_with(
"git", "add", "--intent-to-add", "1", "2", "1", "2",
exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
def test_add_skip(mocker: MockerFixture) -> None:
"""
must skip addition of files to index if no fiels found
"""
mocker.patch("pathlib.Path.glob", return_value=[])
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
Sources._add(Path("local"), "pattern1")
check_output_mock.assert_not_called()
def test_diff(mocker: MockerFixture) -> None:
"""
must calculate diff
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
assert Sources._diff(local)
check_output_mock.assert_called_once_with(
"git", "diff", exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
def test_move(mocker: MockerFixture) -> None:
"""
must move content between directories
"""
mocker.patch("ahriman.core.build_tools.sources.walk", return_value=[Path("/source/path")])
move_mock = mocker.patch("shutil.move")
Sources._move(Path("/source"), Path("/destination"))
move_mock.assert_called_once_with(Path("/source/path"), Path("/destination/path"))
def test_move_same(mocker: MockerFixture) -> None:
"""
must not do anything in case if directories are the same
"""
walk_mock = mocker.patch("ahriman.core.build_tools.sources.walk")
Sources._move(Path("/same"), Path("/same"))
walk_mock.assert_not_called()
def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None:
"""
must do nothing in case if no branches available
@ -87,7 +29,7 @@ def test_fetch_existing(remote_source: RemoteSource, mocker: MockerFixture) -> N
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._move")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local")
Sources.fetch(local, remote_source)
@ -108,7 +50,7 @@ def test_fetch_new(remote_source: RemoteSource, mocker: MockerFixture) -> None:
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._move")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local")
Sources.fetch(local, remote_source)
@ -129,7 +71,7 @@ def test_fetch_new_without_remote(mocker: MockerFixture) -> None:
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._move")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local")
Sources.fetch(local, None)
@ -147,7 +89,7 @@ def test_fetch_relative(remote_source: RemoteSource, mocker: MockerFixture) -> N
must process move correctly on relative directory
"""
mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._move")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
Sources.fetch(Path("path"), remote_source)
move_mock.assert_called_once_with(Path("path").resolve(), Path("path"))
@ -222,26 +164,12 @@ def test_load_with_cache(package_ahriman: Package, repository_paths: RepositoryP
copytree_mock.assert_called_once() # we do not check full command here, sorry
def test_patch_apply(mocker: MockerFixture) -> None:
"""
must apply patches if any
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
Sources.patch_apply(local, "patches")
check_output_mock.assert_called_once_with(
"git", "apply", "--ignore-space-change", "--ignore-whitespace",
exception=None, cwd=local, input_data="patches", logger=pytest.helpers.anyvar(int)
)
def test_patch_create(mocker: MockerFixture) -> None:
"""
must create patch set for the package
"""
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._add")
diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._diff")
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add")
diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.diff")
Sources.patch_create(Path("local"), "glob")
add_mock.assert_called_once_with(Path("local"), "glob")
@ -252,6 +180,78 @@ def test_patch_create_with_newline(mocker: MockerFixture) -> None:
"""
created patch must have new line at the end
"""
mocker.patch("ahriman.core.build_tools.sources.Sources._add")
mocker.patch("ahriman.core.build_tools.sources.Sources._diff", return_value="diff")
mocker.patch("ahriman.core.build_tools.sources.Sources.add")
mocker.patch("ahriman.core.build_tools.sources.Sources.diff", return_value="diff")
assert Sources.patch_create(Path("local"), "glob").endswith("\n")
def test_add(sources: Sources, mocker: MockerFixture) -> None:
"""
must add files to git
"""
glob_mock = mocker.patch("pathlib.Path.glob", return_value=[Path("local/1"), Path("local/2")])
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
sources.add(local, "pattern1", "pattern2")
glob_mock.assert_has_calls([mock.call("pattern1"), mock.call("pattern2")])
check_output_mock.assert_called_once_with(
"git", "add", "--intent-to-add", "1", "2", "1", "2",
exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
def test_add_skip(sources: Sources, mocker: MockerFixture) -> None:
"""
must skip addition of files to index if no fiels found
"""
mocker.patch("pathlib.Path.glob", return_value=[])
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
sources.add(Path("local"), "pattern1")
check_output_mock.assert_not_called()
def test_diff(sources: Sources, mocker: MockerFixture) -> None:
"""
must calculate diff
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
assert sources.diff(local)
check_output_mock.assert_called_once_with(
"git", "diff", exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
def test_move(sources: Sources, mocker: MockerFixture) -> None:
"""
must move content between directories
"""
mocker.patch("ahriman.core.build_tools.sources.walk", return_value=[Path("/source/path")])
move_mock = mocker.patch("shutil.move")
sources.move(Path("/source"), Path("/destination"))
move_mock.assert_called_once_with(Path("/source/path"), Path("/destination/path"))
def test_move_same(sources: Sources, mocker: MockerFixture) -> None:
"""
must not do anything in case if directories are the same
"""
walk_mock = mocker.patch("ahriman.core.build_tools.sources.walk")
sources.move(Path("/same"), Path("/same"))
walk_mock.assert_not_called()
def test_patch_apply(sources: Sources, mocker: MockerFixture) -> None:
"""
must apply patches if any
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
local = Path("local")
sources.patch_apply(local, "patches")
check_output_mock.assert_called_once_with(
"git", "apply", "--ignore-space-change", "--ignore-whitespace",
exception=None, cwd=local, input_data="patches", logger=pytest.helpers.anyvar(int)
)

View File

@ -0,0 +1,28 @@
import pytest
from ahriman.core.alpm.repo import Repo
from ahriman.core.database import SQLite
def test_logger(database: SQLite) -> None:
"""
must set logger attribute
"""
assert database.logger
assert database.logger.name == "ahriman.core.database.sqlite.SQLite"
def test_logger_attribute_error(database: SQLite) -> None:
"""
must raise AttributeError in case if no attribute found
"""
with pytest.raises(AttributeError):
database.loggerrrr
def test_logger_name(database: SQLite, repo: Repo) -> None:
"""
must correctly generate logger name
"""
assert database.logger_name == "ahriman.core.database.sqlite.SQLite"
assert repo.logger_name == "ahriman.core.alpm.repo.Repo"