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 eb5ac5a52b
commit fac228d6c6
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. * 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. * 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. * 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 ### 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 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 Html customization
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^

View File

@ -1,5 +1,5 @@
[loggers] [loggers]
keys = root,build,http,stderr,boto3,botocore,nose,s3transfer keys = root,http,stderr,boto3,botocore,nose,s3transfer
[handlers] [handlers]
keys = console_handler,syslog_handler keys = console_handler,syslog_handler
@ -20,11 +20,11 @@ formatter = syslog_format
args = ("/dev/log",) args = ("/dev/log",)
[formatter_generic_format] [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 = datefmt =
[formatter_syslog_format] [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 = datefmt =
[logger_root] [logger_root]
@ -32,12 +32,6 @@ level = DEBUG
handlers = syslog_handler handlers = syslog_handler
qualname = root qualname = root
[logger_build]
level = DEBUG
handlers = syslog_handler
qualname = build
propagate = 0
[logger_http] [logger_http]
level = DEBUG level = DEBUG
handlers = syslog_handler handlers = syslog_handler

View File

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

View File

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

View File

@ -19,21 +19,17 @@
# #
from __future__ import annotations from __future__ import annotations
import logging
from typing import Dict, List, Type from typing import Dict, List, Type
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
class Remote: class Remote(LazyLogging):
""" """
base class for remote package search base class for remote package search
Attributes:
logger(logging.Logger): class logger
Examples: Examples:
These classes are designed to be used without instancing. In order to achieve it several class methods are 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:: 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. 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 @classmethod
def info(cls: Type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage: 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import logging
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from ahriman.core.exceptions import BuildFailed from ahriman.core.exceptions import BuildFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output from ahriman.core.util import check_output
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
class Repo: class Repo(LazyLogging):
""" """
repo-add and repo-remove wrapper repo-add and repo-remove wrapper
Attributes: Attributes:
logger(logging.Logger): class logger
name(str): repository name name(str): repository name
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive 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 paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive sign_args(List[str]): additional args which have to be used to sign repository archive
""" """
self.logger = logging.getLogger("build")
self.name = name self.name = name
self.paths = paths self.paths = paths
self.uid, _ = paths.root_owner self.uid, _ = paths.root_owner

View File

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

View File

@ -17,82 +17,31 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import logging
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output, walk from ahriman.core.util import check_output, walk
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.remote_source import RemoteSource from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
class Sources: class Sources(LazyLogging):
""" """
helper to download package sources (PKGBUILD etc) helper to download package sources (PKGBUILD etc)
Attributes: Attributes:
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories. 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 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 DEFAULT_BRANCH = "master" # default fallback branch
logger = logging.getLogger("build")
_check_output = check_output _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 @staticmethod
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None: def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
""" """
@ -102,37 +51,38 @@ class Sources:
sources_dir(Path): local path to fetch sources_dir(Path): local path to fetch
remote(Optional[RemoteSource]): remote target (from where to fetch) remote(Optional[RemoteSource]): remote target (from where to fetch)
""" """
instance = Sources()
# 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()
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 # 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 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: 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, 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: 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", Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
remote.git_url, str(sources_dir), remote.git_url, str(sources_dir),
exception=None, cwd=sources_dir, logger=Sources.logger) exception=None, cwd=sources_dir, logger=instance.logger)
else: else:
# it will cause an exception later # 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 # and now force reset to our branch
Sources._check_output("git", "checkout", "--force", 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}", 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 # move content if required
# we are using full path to source directory in order to make append possible # 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() 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 @staticmethod
def has_remotes(sources_dir: Path) -> bool: def has_remotes(sources_dir: Path) -> bool:
@ -145,7 +95,8 @@ class Sources:
Returns: Returns:
bool: True in case if there is any remote and false otherwise 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) return bool(remotes)
@staticmethod @staticmethod
@ -156,8 +107,9 @@ class Sources:
Args: Args:
sources_dir(Path): local path to sources sources_dir(Path): local path to sources
""" """
instance = Sources()
Sources._check_output("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH, 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 @staticmethod
def load(sources_dir: Path, package: Package, patch: Optional[str], paths: RepositoryPaths) -> None: 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 patch(Optional[str]): optional patch to be applied
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
""" """
instance = Sources()
if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir: 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 # no need to clone whole repository, just copy from cache first
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True) shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
Sources.fetch(sources_dir, package.remote) Sources.fetch(sources_dir, package.remote)
if patch is None: if patch is None:
Sources.logger.info("no patches found") instance.logger.info("no patches found")
return return
Sources.patch_apply(sources_dir, patch) instance.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)
@staticmethod @staticmethod
def patch_create(sources_dir: Path, *pattern: str) -> str: def patch_create(sources_dir: Path, *pattern: str) -> str:
@ -206,6 +145,67 @@ class Sources:
Returns: Returns:
str: patch as plain text str: patch as plain text
""" """
Sources._add(sources_dir, *pattern) instance = Sources()
diff = Sources._diff(sources_dir) instance.add(sources_dir, *pattern)
diff = instance.diff(sources_dir)
return f"{diff}\n" # otherwise, patch will be broken 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import logging
from pathlib import Path from pathlib import Path
from typing import List 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.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
from ahriman.core.exceptions import BuildFailed from ahriman.core.exceptions import BuildFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output from ahriman.core.util import check_output
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
class Task: class Task(LazyLogging):
""" """
base package build task base package build task
Attributes: Attributes:
logger(logging.Logger): class logger
package(Package): package definitions package(Package): package definitions
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
uid(int): uid of the repository owner user uid(int): uid of the repository owner user
@ -53,7 +51,6 @@ class Task:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
""" """
self.logger = logging.getLogger("build")
self.package = package self.package = package
self.paths = paths self.paths = paths
self.uid, _ = paths.root_owner self.uid, _ = paths.root_owner

View File

@ -19,8 +19,6 @@
# #
from __future__ import annotations from __future__ import annotations
import logging
from importlib import import_module from importlib import import_module
from pathlib import Path from pathlib import Path
from pkgutil import iter_modules from pkgutil import iter_modules
@ -29,11 +27,12 @@ from typing import List, Type
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database.data import migrate_data 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 import Migration
from ahriman.models.migration_result import MigrationResult from ahriman.models.migration_result import MigrationResult
class Migrations: class Migrations(LazyLogging):
""" """
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/
@ -41,7 +40,6 @@ class Migrations:
Attributes: Attributes:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
connection(Connection): database connection connection(Connection): database connection
logger(logging.Logger): class logger
""" """
def __init__(self, connection: Connection, configuration: Configuration) -> None: def __init__(self, connection: Connection, configuration: Configuration) -> None:
@ -54,7 +52,6 @@ class Migrations:
""" """
self.connection = connection self.connection = connection
self.configuration = configuration self.configuration = configuration
self.logger = logging.getLogger("root")
@classmethod @classmethod
def migrate(cls: Type[Migrations], connection: Connection, configuration: Configuration) -> MigrationResult: 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import logging
import sqlite3 import sqlite3
from pathlib import Path from pathlib import Path
from sqlite3 import Connection, Cursor from sqlite3 import Connection, Cursor
from typing import Any, Dict, Tuple, TypeVar, Callable from typing import Any, Dict, Tuple, TypeVar, Callable
from ahriman.core.lazy_logging import LazyLogging
T = TypeVar("T") T = TypeVar("T")
class Operations: class Operations(LazyLogging):
""" """
base operation class base operation class
Attributes: Attributes:
logger(logging.Logger): class logger
path(Path): path to the database file path(Path): path to the database file
""" """
@ -45,7 +44,6 @@ class Operations:
path(Path): path to the database file path(Path): path to the database file
""" """
self.path = path self.path = path
self.logger = logging.getLogger("root")
@staticmethod @staticmethod
def factory(cursor: Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]: 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 from __future__ import annotations
import logging
from typing import Iterable, Type from typing import Iterable, Type
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ReportFailed from ahriman.core.exceptions import ReportFailed
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.report_settings import ReportSettings from ahriman.models.report_settings import ReportSettings
from ahriman.models.result import Result from ahriman.models.result import Result
class Report: class Report(LazyLogging):
""" """
base report generator base report generator
Attributes: Attributes:
architecture(str): repository architecture architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
logger(logging.Logger): class logger
Examples: Examples:
``Report`` classes provide several method in order to operate with the report generation and additional class ``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 architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
""" """
self.logger = logging.getLogger("root")
self.architecture = architecture self.architecture = architecture
self.configuration = configuration self.configuration = configuration

View File

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

View File

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

View File

@ -20,7 +20,6 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import logging
import uuid import uuid
from multiprocessing import Process, Queue from multiprocessing import Process, Queue
@ -28,10 +27,11 @@ from threading import Lock, Thread
from typing import Callable, Dict, Iterable, Tuple from typing import Callable, Dict, Iterable, Tuple
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
class Spawn(Thread): class Spawn(Thread, LazyLogging):
""" """
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
@ -40,7 +40,6 @@ class Spawn(Thread):
active(Dict[str, Process]): map of active child processes required to avoid zombies active(Dict[str, Process]): map of active child processes required to avoid zombies
architecture(str): repository architecture architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
logger(logging.Logger): spawner logger
queue(Queue[Tuple[str, bool]]): multiprocessing queue to read updates from processes queue(Queue[Tuple[str, bool]]): multiprocessing queue to read updates from processes
""" """
@ -57,7 +56,6 @@ class Spawn(Thread):
self.architecture = architecture self.architecture = architecture
self.args_parser = args_parser self.args_parser = args_parser
self.configuration = configuration self.configuration = configuration
self.logger = logging.getLogger("http")
self.lock = Lock() self.lock = Lock()
self.active: Dict[str, Process] = {} self.active: Dict[str, Process] = {}

View File

@ -17,19 +17,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import logging
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnknownPackage from ahriman.core.exceptions import UnknownPackage
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
class Watcher: class Watcher(LazyLogging):
""" """
package status watcher package status watcher
@ -38,7 +37,6 @@ class Watcher:
database(SQLite): database instance database(SQLite): database instance
known(Dict[str, Tuple[Package, BuildStatus]]): list of known packages. For the most cases ``packages`` should known(Dict[str, Tuple[Package, BuildStatus]]): list of known packages. For the most cases ``packages`` should
be used instead be used instead
logger(logging.Logger): class logger
repository(Repository): repository object repository(Repository): repository object
status(BuildStatus): daemon status status(BuildStatus): daemon status
""" """
@ -52,8 +50,6 @@ class Watcher:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
database(SQLite): database instance database(SQLite): database instance
""" """
self.logger = logging.getLogger("http")
self.architecture = architecture self.architecture = architecture
self.database = database self.database = database
self.repository = Repository(architecture, configuration, database, no_report=True, unsafe=False) 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import logging
import requests import requests
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.status.client import Client from ahriman.core.status.client import Client
from ahriman.core.util import exception_response_text from ahriman.core.util import exception_response_text
from ahriman.models.build_status import BuildStatusEnum, BuildStatus from ahriman.models.build_status import BuildStatusEnum, BuildStatus
@ -31,13 +31,12 @@ from ahriman.models.package import Package
from ahriman.models.user import User from ahriman.models.user import User
class WebClient(Client): class WebClient(Client, LazyLogging):
""" """
build status reporter web client build status reporter web client
Attributes: Attributes:
address(str): address of the web service address(str): address of the web service
logger(logging.Logger): class logger
user(Optional[User]): web service user descriptor user(Optional[User]): web service user descriptor
""" """
@ -48,7 +47,6 @@ class WebClient(Client):
Args: Args:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
""" """
self.logger = logging.getLogger("http")
self.address = self.parse_address(configuration) self.address = self.parse_address(configuration)
self.user = User.from_option( self.user = User.from_option(
configuration.get("web", "username", fallback=None), configuration.get("web", "username", fallback=None),

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,6 @@
from __future__ import annotations from __future__ import annotations
import copy import copy
import logging
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from pathlib import Path 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.pacman import Pacman
from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output, full_version from ahriman.core.util import check_output, full_version
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
@ -39,7 +39,7 @@ from ahriman.models.repository_paths import RepositoryPaths
@dataclass @dataclass
class Package: class Package(LazyLogging):
""" """
package properties representation package properties representation
@ -281,23 +281,22 @@ class Package:
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
logger = logging.getLogger("build")
Sources.load(paths.cache_for(self.base), self, None, paths) Sources.load(paths.cache_for(self.base), self, None, paths)
try: try:
# update pkgver first # update pkgver first
Package._check_output("makepkg", "--nodeps", "--nobuild", 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 # generate new .SRCINFO and put it to parser
srcinfo_source = Package._check_output("makepkg", "--printsrcinfo", 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) srcinfo, errors = parse_srcinfo(srcinfo_source)
if errors: if errors:
raise InvalidPackageInfo(errors) raise InvalidPackageInfo(errors)
return full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"]) return full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"])
except Exception: 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 return self.version

View File

@ -94,7 +94,7 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw
Returns: Returns:
web.Application: web application instance 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_shutdown.append(on_shutdown)
application.on_startup.append(on_startup) 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 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: def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None:
""" """
must do nothing in case if no branches available 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("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", 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") 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") local = Path("local")
Sources.fetch(local, remote_source) 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) mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") 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") local = Path("local")
Sources.fetch(local, remote_source) 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) mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") 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") local = Path("local")
Sources.fetch(local, None) 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 must process move correctly on relative directory
""" """
mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") 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) Sources.fetch(Path("path"), remote_source)
move_mock.assert_called_once_with(Path("path").resolve(), Path("path")) 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 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: def test_patch_create(mocker: MockerFixture) -> None:
""" """
must create patch set for the package must create patch set for the package
""" """
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._add") add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add")
diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._diff") diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.diff")
Sources.patch_create(Path("local"), "glob") Sources.patch_create(Path("local"), "glob")
add_mock.assert_called_once_with(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 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.add")
mocker.patch("ahriman.core.build_tools.sources.Sources._diff", return_value="diff") mocker.patch("ahriman.core.build_tools.sources.Sources.diff", return_value="diff")
assert Sources.patch_create(Path("local"), "glob").endswith("\n") 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"