mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-03 23:33:41 +00:00 
			
		
		
		
	add console printer
also add python-requests as explicit dependency and escape symbols in repository name for badges in default tempate
This commit is contained in:
		@ -24,7 +24,7 @@ RUN YAY_DIR="$(runuser -u build -- mktemp -d)" && \
 | 
			
		||||
    runuser -u build -- makepkg --noconfirm --install && \
 | 
			
		||||
    cd - && rm -r "$YAY_DIR"
 | 
			
		||||
## install package dependencies
 | 
			
		||||
RUN runuser -u build -- yay --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-srcinfo && \
 | 
			
		||||
RUN runuser -u build -- yay --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-requests python-srcinfo && \
 | 
			
		||||
    runuser -u build -- yay --noconfirm -Sy python-pip && \
 | 
			
		||||
    runuser -u build -- yay --noconfirm -Sy breezy darcs mercurial python-aioauth-client python-aiohttp \
 | 
			
		||||
                                            python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security \
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ pkgdesc="ArcH Linux ReposItory MANager"
 | 
			
		||||
arch=('any')
 | 
			
		||||
url="https://github.com/arcan1s/ahriman"
 | 
			
		||||
license=('GPL3')
 | 
			
		||||
depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-srcinfo')
 | 
			
		||||
depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo')
 | 
			
		||||
makedepends=('python-pip')
 | 
			
		||||
optdepends=('breezy: -bzr packages support'
 | 
			
		||||
            'darcs: -darcs packages support'
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,10 @@ root = /var/lib/ahriman
 | 
			
		||||
target =
 | 
			
		||||
 | 
			
		||||
[report]
 | 
			
		||||
target =
 | 
			
		||||
target = console
 | 
			
		||||
 | 
			
		||||
[console]
 | 
			
		||||
use_utf = yes
 | 
			
		||||
 | 
			
		||||
[email]
 | 
			
		||||
full_template_path = /usr/share/ahriman/repo-index.jinja2
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@
 | 
			
		||||
            <h1>ahriman
 | 
			
		||||
                {% if auth.authenticated %}
 | 
			
		||||
                    <img src="https://img.shields.io/badge/version-{{ version }}-informational" alt="{{ version }}">
 | 
			
		||||
                    <img src="https://img.shields.io/badge/repository-{{ repository }}-informational" alt="{{ repository }}">
 | 
			
		||||
                    <img src="https://img.shields.io/badge/repository-{{ repository | replace("-", "--") }}-informational" alt="{{ repository }}">
 | 
			
		||||
                    <img src="https://img.shields.io/badge/architecture-{{ architecture }}-informational" alt="{{ architecture }}">
 | 
			
		||||
                    <img src="https://img.shields.io/badge/service%20status-{{ service.status }}-{{ service.status_color }}" alt="{{ service.status }}" title="{{ service.timestamp }}">
 | 
			
		||||
                {% endif %}
 | 
			
		||||
 | 
			
		||||
@ -17,11 +17,11 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
from typing import Iterable, Set
 | 
			
		||||
from typing import Set
 | 
			
		||||
 | 
			
		||||
from ahriman.application.application.packages import Packages
 | 
			
		||||
from ahriman.application.application.repository import Repository
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Application(Packages, Repository):
 | 
			
		||||
@ -29,12 +29,13 @@ class Application(Packages, Repository):
 | 
			
		||||
    base application class
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def _finalize(self, built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def _finalize(self, result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report and sync to remote server
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        self.report([], built_packages)
 | 
			
		||||
        self.sync([], built_packages)
 | 
			
		||||
        self.report([], result)
 | 
			
		||||
        self.sync([], result.success)
 | 
			
		||||
 | 
			
		||||
    def _known_packages(self) -> Set[str]:
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@ from ahriman.core.build_tools.sources import Sources
 | 
			
		||||
from ahriman.core.util import package_like
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.package_source import PackageSource
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Packages(Properties):
 | 
			
		||||
@ -35,9 +36,10 @@ class Packages(Properties):
 | 
			
		||||
    package control class
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def _finalize(self, built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def _finalize(self, result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report and sync to remote server
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
@ -141,4 +143,4 @@ class Packages(Properties):
 | 
			
		||||
        :param names: list of packages (either base or name) to remove
 | 
			
		||||
        """
 | 
			
		||||
        self.repository.process_remove(names)
 | 
			
		||||
        self._finalize([])
 | 
			
		||||
        self._finalize(Result())
 | 
			
		||||
 | 
			
		||||
@ -23,11 +23,11 @@ from pathlib import Path
 | 
			
		||||
from typing import Callable, Iterable, List
 | 
			
		||||
 | 
			
		||||
from ahriman.application.application.properties import Properties
 | 
			
		||||
from ahriman.application.formatters.update_printer import UpdatePrinter
 | 
			
		||||
from ahriman.core.build_tools.sources import Sources
 | 
			
		||||
from ahriman.core.formatters.update_printer import UpdatePrinter
 | 
			
		||||
from ahriman.core.tree import Tree
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.package_source import PackageSource
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Repository(Properties):
 | 
			
		||||
@ -35,9 +35,10 @@ class Repository(Properties):
 | 
			
		||||
    repository control class
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def _finalize(self, built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def _finalize(self, result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report and sync to remote server
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
@ -64,14 +65,14 @@ class Repository(Properties):
 | 
			
		||||
        if patches:
 | 
			
		||||
            self.repository.clear_patches()
 | 
			
		||||
 | 
			
		||||
    def report(self, target: Iterable[str], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def report(self, target: Iterable[str], result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report
 | 
			
		||||
        :param target: list of targets to run (e.g. html)
 | 
			
		||||
        :param built_packages: list of packages which has just been built
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        targets = target or None
 | 
			
		||||
        self.repository.process_report(targets, built_packages)
 | 
			
		||||
        self.repository.process_report(targets, result)
 | 
			
		||||
 | 
			
		||||
    def sign(self, packages: Iterable[str]) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@ -94,7 +95,7 @@ class Repository(Properties):
 | 
			
		||||
        self.update([])
 | 
			
		||||
        # sign repository database if set
 | 
			
		||||
        self.repository.sign.process_sign_repository(self.repository.repo.repo_path)
 | 
			
		||||
        self._finalize([])
 | 
			
		||||
        self._finalize(Result())
 | 
			
		||||
 | 
			
		||||
    def sync(self, target: Iterable[str], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@ -142,26 +143,23 @@ class Repository(Properties):
 | 
			
		||||
        run package updates
 | 
			
		||||
        :param updates: list of packages to update
 | 
			
		||||
        """
 | 
			
		||||
        def process_update(paths: Iterable[Path]) -> None:
 | 
			
		||||
        def process_update(paths: Iterable[Path], result: Result) -> None:
 | 
			
		||||
            if not paths:
 | 
			
		||||
                return  # don't need to process if no update supplied
 | 
			
		||||
            updated = [
 | 
			
		||||
                Package.load(str(path), PackageSource.Archive, self.repository.pacman, self.repository.aur_url)
 | 
			
		||||
                for path in paths
 | 
			
		||||
            ]
 | 
			
		||||
            self.repository.process_update(paths)
 | 
			
		||||
            self._finalize(updated)
 | 
			
		||||
            update_result = self.repository.process_update(paths)
 | 
			
		||||
            self._finalize(result.merge(update_result))
 | 
			
		||||
 | 
			
		||||
        # process built packages
 | 
			
		||||
        packages = self.repository.packages_built()
 | 
			
		||||
        process_update(packages)
 | 
			
		||||
        process_update(packages, Result())
 | 
			
		||||
 | 
			
		||||
        # process manual packages
 | 
			
		||||
        tree = Tree.load(updates, self.repository.paths)
 | 
			
		||||
        for num, level in enumerate(tree.levels()):
 | 
			
		||||
            self.logger.info("processing level #%i %s", num, [package.base for package in level])
 | 
			
		||||
            packages = self.repository.process_build(level)
 | 
			
		||||
            process_update(packages)
 | 
			
		||||
            build_result = self.repository.process_build(level)
 | 
			
		||||
            packages = self.repository.packages_built()
 | 
			
		||||
            process_update(packages, build_result)
 | 
			
		||||
 | 
			
		||||
    def updates(self, filter_packages: Iterable[str], no_aur: bool, no_local: bool, no_manual: bool, no_vcs: bool,
 | 
			
		||||
                log_fn: Callable[[str], None]) -> List[Package]:
 | 
			
		||||
 | 
			
		||||
@ -21,9 +21,9 @@ import argparse
 | 
			
		||||
 | 
			
		||||
from typing import Type
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.configuration_printer import ConfigurationPrinter
 | 
			
		||||
from ahriman.application.handlers.handler import Handler
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Dump(Handler):
 | 
			
		||||
 | 
			
		||||
@ -22,9 +22,9 @@ import argparse
 | 
			
		||||
from typing import Type
 | 
			
		||||
 | 
			
		||||
from ahriman.application.application import Application
 | 
			
		||||
from ahriman.application.formatters.update_printer import UpdatePrinter
 | 
			
		||||
from ahriman.application.handlers.handler import Handler
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.formatters.update_printer import UpdatePrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Rebuild(Handler):
 | 
			
		||||
 | 
			
		||||
@ -22,9 +22,9 @@ import argparse
 | 
			
		||||
from typing import Type
 | 
			
		||||
 | 
			
		||||
from ahriman.application.application import Application
 | 
			
		||||
from ahriman.application.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.application.handlers.handler import Handler
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveUnknown(Handler):
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ from typing import Type
 | 
			
		||||
from ahriman.application.application import Application
 | 
			
		||||
from ahriman.application.handlers.handler import Handler
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Report(Handler):
 | 
			
		||||
@ -42,4 +43,4 @@ class Report(Handler):
 | 
			
		||||
        :param no_report: force disable reporting
 | 
			
		||||
        :param unsafe: if set no user check will be performed before path creation
 | 
			
		||||
        """
 | 
			
		||||
        Application(architecture, configuration, no_report, unsafe).report(args.target, [])
 | 
			
		||||
        Application(architecture, configuration, no_report, unsafe).report(args.target, Result())
 | 
			
		||||
 | 
			
		||||
@ -22,11 +22,11 @@ import argparse
 | 
			
		||||
from dataclasses import fields
 | 
			
		||||
from typing import Callable, Iterable, List, Tuple, Type
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.aur_printer import AurPrinter
 | 
			
		||||
from ahriman.application.handlers.handler import Handler
 | 
			
		||||
from ahriman.core.alpm.aur import AUR
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.exceptions import InvalidOption
 | 
			
		||||
from ahriman.core.formatters.aur_printer import AurPrinter
 | 
			
		||||
from ahriman.models.aur_package import AURPackage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -22,10 +22,10 @@ import argparse
 | 
			
		||||
from typing import Callable, Iterable, Tuple, Type
 | 
			
		||||
 | 
			
		||||
from ahriman.application.application import Application
 | 
			
		||||
from ahriman.application.formatters.package_printer import PackagePrinter
 | 
			
		||||
from ahriman.application.formatters.status_printer import StatusPrinter
 | 
			
		||||
from ahriman.application.handlers.handler import Handler
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.formatters.package_printer import PackagePrinter
 | 
			
		||||
from ahriman.core.formatters.status_printer import StatusPrinter
 | 
			
		||||
from ahriman.models.build_status import BuildStatus
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,9 +21,9 @@ import argparse
 | 
			
		||||
 | 
			
		||||
from typing import List, Type
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.application.handlers.handler import Handler
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsafeCommands(Handler):
 | 
			
		||||
 | 
			
		||||
@ -136,6 +136,19 @@ class ReportFailed(RuntimeError):
 | 
			
		||||
        RuntimeError.__init__(self, "Report failed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SuccessFailed(ValueError):
 | 
			
		||||
    """
 | 
			
		||||
    exception for merging invalid statues
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, package_base: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        default constructor
 | 
			
		||||
        :param package_base: package base name
 | 
			
		||||
        """
 | 
			
		||||
        ValueError.__init__(self, f"Package base {package_base} had status failed, but new status is success")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SyncFailed(RuntimeError):
 | 
			
		||||
    """
 | 
			
		||||
    remote synchronization exception
 | 
			
		||||
 | 
			
		||||
@ -17,17 +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/>.
 | 
			
		||||
#
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.printer import Printer
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.core.util import pretty_datetime
 | 
			
		||||
from ahriman.models.aur_package import AURPackage
 | 
			
		||||
from ahriman.models.property import Property
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AurPrinter(Printer):
 | 
			
		||||
class AurPrinter(StringPrinter):
 | 
			
		||||
    """
 | 
			
		||||
    print content of the AUR package
 | 
			
		||||
    :ivar package: AUR package description
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, package: AURPackage) -> None:
 | 
			
		||||
@ -35,7 +36,8 @@ class AurPrinter(Printer):
 | 
			
		||||
        default constructor
 | 
			
		||||
        :param package: AUR package description
 | 
			
		||||
        """
 | 
			
		||||
        self.content = package
 | 
			
		||||
        StringPrinter.__init__(self, f"{package.name} {package.version} ({package.num_votes})")
 | 
			
		||||
        self.package = package
 | 
			
		||||
 | 
			
		||||
    def properties(self) -> List[Property]:
 | 
			
		||||
        """
 | 
			
		||||
@ -43,19 +45,12 @@ class AurPrinter(Printer):
 | 
			
		||||
        :return: list of content properties
 | 
			
		||||
        """
 | 
			
		||||
        return [
 | 
			
		||||
            Property("Package base", self.content.package_base),
 | 
			
		||||
            Property("Description", self.content.description, is_required=True),
 | 
			
		||||
            Property("Upstream URL", self.content.url or ""),
 | 
			
		||||
            Property("Licenses", ",".join(self.content.license)),
 | 
			
		||||
            Property("Maintainer", self.content.maintainer or ""),
 | 
			
		||||
            Property("First submitted", pretty_datetime(self.content.first_submitted)),
 | 
			
		||||
            Property("Last updated", pretty_datetime(self.content.last_modified)),
 | 
			
		||||
            Property("Keywords", ",".join(self.content.keywords)),
 | 
			
		||||
            Property("Package base", self.package.package_base),
 | 
			
		||||
            Property("Description", self.package.description, is_required=True),
 | 
			
		||||
            Property("Upstream URL", self.package.url or ""),
 | 
			
		||||
            Property("Licenses", ",".join(self.package.license)),
 | 
			
		||||
            Property("Maintainer", self.package.maintainer or ""),
 | 
			
		||||
            Property("First submitted", pretty_datetime(self.package.first_submitted)),
 | 
			
		||||
            Property("Last updated", pretty_datetime(self.package.last_modified)),
 | 
			
		||||
            Property("Keywords", ",".join(self.package.keywords)),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def title(self) -> Optional[str]:
 | 
			
		||||
        """
 | 
			
		||||
        generate entry title from content
 | 
			
		||||
        :return: content title if it can be generated and None otherwise
 | 
			
		||||
        """
 | 
			
		||||
        return f"{self.content.name} {self.content.version} ({self.content.num_votes})"
 | 
			
		||||
							
								
								
									
										48
									
								
								src/ahriman/core/formatters/build_printer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/ahriman/core/formatters/build_printer.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2021 ahriman team.
 | 
			
		||||
#
 | 
			
		||||
# This file is part of ahriman
 | 
			
		||||
# (see https://github.com/arcan1s/ahriman).
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BuildPrinter(StringPrinter):
 | 
			
		||||
    """
 | 
			
		||||
    print content of the build result
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, package: Package, is_success: bool, use_utf: bool) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        default constructor
 | 
			
		||||
        :param package: built package
 | 
			
		||||
        :param is_success: True in case if build has success status and False otherwise
 | 
			
		||||
        :param use_utf: use utf instead of normal symbols
 | 
			
		||||
        """
 | 
			
		||||
        StringPrinter.__init__(self, f"{self.sign(is_success, use_utf)} {package.base}")
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def sign(is_success: bool, use_utf: bool) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        generate sign according to settings
 | 
			
		||||
        :param use_utf: use utf instead of normal symbols
 | 
			
		||||
        :param is_success: True in case if build has success status and False otherwise
 | 
			
		||||
        :return: sign symbol according to current settings
 | 
			
		||||
        """
 | 
			
		||||
        if is_success:
 | 
			
		||||
            return "[✔]" if use_utf else "[x]"
 | 
			
		||||
        return "[❌]" if use_utf else "[ ]"
 | 
			
		||||
@ -17,15 +17,16 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
from typing import Dict, List, Optional
 | 
			
		||||
from typing import Dict, List
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.printer import Printer
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.models.property import Property
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConfigurationPrinter(Printer):
 | 
			
		||||
class ConfigurationPrinter(StringPrinter):
 | 
			
		||||
    """
 | 
			
		||||
    print content of the configuration section
 | 
			
		||||
    :ivar values: configuration values dictionary
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, section: str, values: Dict[str, str]) -> None:
 | 
			
		||||
@ -34,8 +35,8 @@ class ConfigurationPrinter(Printer):
 | 
			
		||||
        :param section: section name
 | 
			
		||||
        :param values: configuration values dictionary
 | 
			
		||||
        """
 | 
			
		||||
        self.section = section
 | 
			
		||||
        self.content = values
 | 
			
		||||
        StringPrinter.__init__(self, f"[{section}]")
 | 
			
		||||
        self.values = values
 | 
			
		||||
 | 
			
		||||
    def properties(self) -> List[Property]:
 | 
			
		||||
        """
 | 
			
		||||
@ -44,12 +45,5 @@ class ConfigurationPrinter(Printer):
 | 
			
		||||
        """
 | 
			
		||||
        return [
 | 
			
		||||
            Property(key, value, is_required=True)
 | 
			
		||||
            for key, value in sorted(self.content.items())
 | 
			
		||||
            for key, value in sorted(self.values.items())
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def title(self) -> Optional[str]:
 | 
			
		||||
        """
 | 
			
		||||
        generate entry title from content
 | 
			
		||||
        :return: content title if it can be generated and None otherwise
 | 
			
		||||
        """
 | 
			
		||||
        return f"[{self.section}]"
 | 
			
		||||
@ -17,17 +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/>.
 | 
			
		||||
#
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.printer import Printer
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.models.build_status import BuildStatus
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.property import Property
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PackagePrinter(Printer):
 | 
			
		||||
class PackagePrinter(StringPrinter):
 | 
			
		||||
    """
 | 
			
		||||
    print content of the internal package object
 | 
			
		||||
    :ivar package: package description
 | 
			
		||||
    :ivar status: build status
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, package: Package, status: BuildStatus) -> None:
 | 
			
		||||
@ -36,7 +38,8 @@ class PackagePrinter(Printer):
 | 
			
		||||
        :param package: package description
 | 
			
		||||
        :param status: build status
 | 
			
		||||
        """
 | 
			
		||||
        self.content = package
 | 
			
		||||
        StringPrinter.__init__(self, package.pretty_print())
 | 
			
		||||
        self.package = package
 | 
			
		||||
        self.status = status
 | 
			
		||||
 | 
			
		||||
    def properties(self) -> List[Property]:
 | 
			
		||||
@ -45,16 +48,9 @@ class PackagePrinter(Printer):
 | 
			
		||||
        :return: list of content properties
 | 
			
		||||
        """
 | 
			
		||||
        return [
 | 
			
		||||
            Property("Version", self.content.version, is_required=True),
 | 
			
		||||
            Property("Groups", " ".join(self.content.groups)),
 | 
			
		||||
            Property("Licenses", " ".join(self.content.licenses)),
 | 
			
		||||
            Property("Depends", " ".join(self.content.depends)),
 | 
			
		||||
            Property("Version", self.package.version, is_required=True),
 | 
			
		||||
            Property("Groups", " ".join(self.package.groups)),
 | 
			
		||||
            Property("Licenses", " ".join(self.package.licenses)),
 | 
			
		||||
            Property("Depends", " ".join(self.package.depends)),
 | 
			
		||||
            Property("Status", self.status.pretty_print(), is_required=True),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def title(self) -> Optional[str]:
 | 
			
		||||
        """
 | 
			
		||||
        generate entry title from content
 | 
			
		||||
        :return: content title if it can be generated and None otherwise
 | 
			
		||||
        """
 | 
			
		||||
        return self.content.pretty_print()
 | 
			
		||||
@ -17,13 +17,11 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.printer import Printer
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.models.build_status import BuildStatus
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StatusPrinter(Printer):
 | 
			
		||||
class StatusPrinter(StringPrinter):
 | 
			
		||||
    """
 | 
			
		||||
    print content of the status object
 | 
			
		||||
    """
 | 
			
		||||
@ -33,11 +31,4 @@ class StatusPrinter(Printer):
 | 
			
		||||
        default constructor
 | 
			
		||||
        :param status: build status
 | 
			
		||||
        """
 | 
			
		||||
        self.content = status
 | 
			
		||||
 | 
			
		||||
    def title(self) -> Optional[str]:
 | 
			
		||||
        """
 | 
			
		||||
        generate entry title from content
 | 
			
		||||
        :return: content title if it can be generated and None otherwise
 | 
			
		||||
        """
 | 
			
		||||
        return self.content.pretty_print()
 | 
			
		||||
        StringPrinter.__init__(self, status.pretty_print())
 | 
			
		||||
@ -19,7 +19,7 @@
 | 
			
		||||
#
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.printer import Printer
 | 
			
		||||
from ahriman.core.formatters.printer import Printer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StringPrinter(Printer):
 | 
			
		||||
@ -19,14 +19,16 @@
 | 
			
		||||
#
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.printer import Printer
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.property import Property
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UpdatePrinter(Printer):
 | 
			
		||||
class UpdatePrinter(StringPrinter):
 | 
			
		||||
    """
 | 
			
		||||
    print content of the package update
 | 
			
		||||
    :ivar package: remote (new) package object
 | 
			
		||||
    :ivar local_version: local version of the package if any
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, remote: Package, local_version: Optional[str]) -> None:
 | 
			
		||||
@ -35,7 +37,8 @@ class UpdatePrinter(Printer):
 | 
			
		||||
        :param remote: remote (new) package object
 | 
			
		||||
        :param local_version: local version of the package if any
 | 
			
		||||
        """
 | 
			
		||||
        self.content = remote
 | 
			
		||||
        StringPrinter.__init__(self, remote.base)
 | 
			
		||||
        self.package = remote
 | 
			
		||||
        self.local_version = local_version or "N/A"
 | 
			
		||||
 | 
			
		||||
    def properties(self) -> List[Property]:
 | 
			
		||||
@ -43,11 +46,4 @@ class UpdatePrinter(Printer):
 | 
			
		||||
        convert content into printable data
 | 
			
		||||
        :return: list of content properties
 | 
			
		||||
        """
 | 
			
		||||
        return [Property(self.local_version, self.content.version, is_required=True)]
 | 
			
		||||
 | 
			
		||||
    def title(self) -> Optional[str]:
 | 
			
		||||
        """
 | 
			
		||||
        generate entry title from content
 | 
			
		||||
        :return: content title if it can be generated and None otherwise
 | 
			
		||||
        """
 | 
			
		||||
        return self.content.base
 | 
			
		||||
        return [Property(self.local_version, self.package.version, is_required=True)]
 | 
			
		||||
							
								
								
									
										54
									
								
								src/ahriman/core/report/console.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/ahriman/core/report/console.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2021 ahriman team.
 | 
			
		||||
#
 | 
			
		||||
# This file is part of ahriman
 | 
			
		||||
# (see https://github.com/arcan1s/ahriman).
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
from typing import Iterable
 | 
			
		||||
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.formatters.build_printer import BuildPrinter
 | 
			
		||||
from ahriman.core.report.report import Report
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Console(Report):
 | 
			
		||||
    """
 | 
			
		||||
    html report generator
 | 
			
		||||
    :ivar use_utf: print utf8 symbols instead of ASCII
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        default constructor
 | 
			
		||||
        :param architecture: repository architecture
 | 
			
		||||
        :param configuration: configuration instance
 | 
			
		||||
        :param section: settings section name
 | 
			
		||||
        """
 | 
			
		||||
        Report.__init__(self, architecture, configuration)
 | 
			
		||||
        self.use_utf = configuration.getboolean(section, "use_utf")
 | 
			
		||||
 | 
			
		||||
    def generate(self, packages: Iterable[Package], result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report for the specified packages
 | 
			
		||||
        :param packages: list of packages to generate report
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        for package in result.success:
 | 
			
		||||
            BuildPrinter(package, is_success=True, use_utf=self.use_utf).print(verbose=True)
 | 
			
		||||
        for package in result.failed:
 | 
			
		||||
            BuildPrinter(package, is_success=True, use_utf=self.use_utf).print(verbose=True)
 | 
			
		||||
@ -29,12 +29,14 @@ from ahriman.core.report.jinja_template import JinjaTemplate
 | 
			
		||||
from ahriman.core.report.report import Report
 | 
			
		||||
from ahriman.core.util import pretty_datetime
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
from ahriman.models.smtp_ssl_settings import SmtpSSLSettings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Email(Report, JinjaTemplate):
 | 
			
		||||
    """
 | 
			
		||||
    email report generator
 | 
			
		||||
    :ivar full_template_path: path to template for full package list
 | 
			
		||||
    :ivar host: SMTP host to connect
 | 
			
		||||
    :ivar no_empty_report: skip empty report generation
 | 
			
		||||
    :ivar password: password to authenticate via SMTP
 | 
			
		||||
@ -42,6 +44,7 @@ class Email(Report, JinjaTemplate):
 | 
			
		||||
    :ivar receivers: list of receivers emails
 | 
			
		||||
    :ivar sender: sender email address
 | 
			
		||||
    :ivar ssl: SSL mode for SMTP connection
 | 
			
		||||
    :ivar template_path: path to template for built packages
 | 
			
		||||
    :ivar user: username to authenticate via SMTP
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
@ -96,17 +99,17 @@ class Email(Report, JinjaTemplate):
 | 
			
		||||
        session.sendmail(self.sender, self.receivers, message.as_string())
 | 
			
		||||
        session.quit()
 | 
			
		||||
 | 
			
		||||
    def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def generate(self, packages: Iterable[Package], result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report for the specified packages
 | 
			
		||||
        :param packages: list of packages to generate report
 | 
			
		||||
        :param built_packages: list of packages which has just been built
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        if self.no_empty_report and not built_packages:
 | 
			
		||||
        if self.no_empty_report and not result.success:
 | 
			
		||||
            return
 | 
			
		||||
        text = self.make_html(built_packages, self.template_path)
 | 
			
		||||
        text = self.make_html(result, self.template_path)
 | 
			
		||||
        if self.full_template_path is not None:
 | 
			
		||||
            attachments = {"index.html": self.make_html(packages, self.full_template_path)}
 | 
			
		||||
            attachments = {"index.html": self.make_html(Result(success=packages), self.full_template_path)}
 | 
			
		||||
        else:
 | 
			
		||||
            attachments = {}
 | 
			
		||||
        self._send(text, attachments)
 | 
			
		||||
 | 
			
		||||
@ -23,12 +23,14 @@ from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.report.jinja_template import JinjaTemplate
 | 
			
		||||
from ahriman.core.report.report import Report
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTML(Report, JinjaTemplate):
 | 
			
		||||
    """
 | 
			
		||||
    html report generator
 | 
			
		||||
    :ivar report_path: output path to html report
 | 
			
		||||
    :ivar template_path: path to template for full package list
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
 | 
			
		||||
@ -44,11 +46,11 @@ class HTML(Report, JinjaTemplate):
 | 
			
		||||
        self.report_path = configuration.getpath(section, "path")
 | 
			
		||||
        self.template_path = configuration.getpath(section, "template_path")
 | 
			
		||||
 | 
			
		||||
    def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def generate(self, packages: Iterable[Package], result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report for the specified packages
 | 
			
		||||
        :param packages: list of packages to generate report
 | 
			
		||||
        :param built_packages: list of packages which has just been built
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        html = self.make_html(packages, self.template_path)
 | 
			
		||||
        html = self.make_html(Result(success=packages), self.template_path)
 | 
			
		||||
        self.report_path.write_text(html)
 | 
			
		||||
 | 
			
		||||
@ -20,12 +20,12 @@
 | 
			
		||||
import jinja2
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Callable, Dict, Iterable
 | 
			
		||||
from typing import Callable, Dict
 | 
			
		||||
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.sign.gpg import GPG
 | 
			
		||||
from ahriman.core.util import pretty_datetime, pretty_size
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
from ahriman.models.sign_settings import SignSettings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,10 +76,10 @@ class JinjaTemplate:
 | 
			
		||||
 | 
			
		||||
        self.sign_targets, self.default_pgp_key = GPG.sign_options(configuration)
 | 
			
		||||
 | 
			
		||||
    def make_html(self, packages: Iterable[Package], template_path: Path) -> str:
 | 
			
		||||
    def make_html(self, result: Result, template_path: Path) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        generate report for the specified packages
 | 
			
		||||
        :param packages: list of packages to generate report
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        :param template_path: path to jinja template
 | 
			
		||||
        """
 | 
			
		||||
        # idea comes from https://stackoverflow.com/a/38642558
 | 
			
		||||
@ -101,7 +101,7 @@ class JinjaTemplate:
 | 
			
		||||
                "name": package,
 | 
			
		||||
                "url": properties.url or "",
 | 
			
		||||
                "version": base.version
 | 
			
		||||
            } for base in packages for package, properties in base.packages.items()
 | 
			
		||||
            } for base in result.updated for package, properties in base.packages.items()
 | 
			
		||||
        ]
 | 
			
		||||
        comparator: Callable[[Dict[str, str]], str] = lambda item: item["filename"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.exceptions import ReportFailed
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.report_settings import ReportSettings
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Report:
 | 
			
		||||
@ -64,23 +65,26 @@ class Report:
 | 
			
		||||
        if provider == ReportSettings.Email:
 | 
			
		||||
            from ahriman.core.report.email import Email
 | 
			
		||||
            return Email(architecture, configuration, section)
 | 
			
		||||
        if provider == ReportSettings.Console:
 | 
			
		||||
            from ahriman.core.report.console import Console
 | 
			
		||||
            return Console(architecture, configuration, section)
 | 
			
		||||
        return cls(architecture, configuration)  # should never happen
 | 
			
		||||
 | 
			
		||||
    def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def generate(self, packages: Iterable[Package], result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate report for the specified packages
 | 
			
		||||
        :param packages: list of packages to generate report
 | 
			
		||||
        :param built_packages: list of packages which has just been built
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
    def run(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def run(self, packages: Iterable[Package], result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        run report generation
 | 
			
		||||
        :param packages: list of packages to generate report
 | 
			
		||||
        :param built_packages: list of packages which has just been built
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            self.generate(packages, built_packages)
 | 
			
		||||
            self.generate(packages, result)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            self.logger.exception("report generation failed")
 | 
			
		||||
            raise ReportFailed()
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ from ahriman.core.report.report import Report
 | 
			
		||||
from ahriman.core.repository.cleaner import Cleaner
 | 
			
		||||
from ahriman.core.upload.upload import Upload
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Executor(Cleaner):
 | 
			
		||||
@ -49,7 +50,7 @@ class Executor(Cleaner):
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def process_build(self, updates: Iterable[Package]) -> List[Path]:
 | 
			
		||||
    def process_build(self, updates: Iterable[Package]) -> Result:
 | 
			
		||||
        """
 | 
			
		||||
        build packages
 | 
			
		||||
        :param updates: list of packages properties to build
 | 
			
		||||
@ -64,15 +65,18 @@ class Executor(Cleaner):
 | 
			
		||||
                dst = self.paths.packages / src.name
 | 
			
		||||
                shutil.move(src, dst)
 | 
			
		||||
 | 
			
		||||
        result = Result()
 | 
			
		||||
        for single in updates:
 | 
			
		||||
            try:
 | 
			
		||||
                build_single(single)
 | 
			
		||||
                result.add_success(single)
 | 
			
		||||
            except Exception:
 | 
			
		||||
                self.reporter.set_failed(single.base)
 | 
			
		||||
                result.add_failed(single)
 | 
			
		||||
                self.logger.exception("%s (%s) build exception", single.base, self.architecture)
 | 
			
		||||
        self.clear_build()
 | 
			
		||||
 | 
			
		||||
        return self.packages_built()
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def process_remove(self, packages: Iterable[str]) -> Path:
 | 
			
		||||
        """
 | 
			
		||||
@ -116,17 +120,17 @@ class Executor(Cleaner):
 | 
			
		||||
 | 
			
		||||
        return self.repo.repo_path
 | 
			
		||||
 | 
			
		||||
    def process_report(self, targets: Optional[Iterable[str]], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
    def process_report(self, targets: Optional[Iterable[str]], result: Result) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        generate reports
 | 
			
		||||
        :param targets: list of targets to generate reports. Configuration option will be used if it is not set
 | 
			
		||||
        :param built_packages: list of packages which has just been built
 | 
			
		||||
        :param result: build result
 | 
			
		||||
        """
 | 
			
		||||
        if targets is None:
 | 
			
		||||
            targets = self.configuration.getlist("report", "target")
 | 
			
		||||
        for target in targets:
 | 
			
		||||
            runner = Report.load(self.architecture, self.configuration, target)
 | 
			
		||||
            runner.run(self.packages(), built_packages)
 | 
			
		||||
            runner.run(self.packages(), result)
 | 
			
		||||
 | 
			
		||||
    def process_sync(self, targets: Optional[Iterable[str]], built_packages: Iterable[Package]) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@ -140,7 +144,7 @@ class Executor(Cleaner):
 | 
			
		||||
            runner = Upload.load(self.architecture, self.configuration, target)
 | 
			
		||||
            runner.run(self.paths.repository, built_packages)
 | 
			
		||||
 | 
			
		||||
    def process_update(self, packages: Iterable[Path]) -> Path:
 | 
			
		||||
    def process_update(self, packages: Iterable[Path]) -> Result:
 | 
			
		||||
        """
 | 
			
		||||
        sign packages, add them to repository and update repository database
 | 
			
		||||
        :param packages: list of filenames to run
 | 
			
		||||
@ -163,20 +167,23 @@ class Executor(Cleaner):
 | 
			
		||||
        removed_packages: List[str] = []  # list of packages which have been removed from the base
 | 
			
		||||
        updates = self.load_archives(packages)
 | 
			
		||||
 | 
			
		||||
        result = Result()
 | 
			
		||||
        for local in updates:
 | 
			
		||||
            try:
 | 
			
		||||
                for description in local.packages.values():
 | 
			
		||||
                    update_single(description.filename, local.base)
 | 
			
		||||
                self.reporter.set_success(local)
 | 
			
		||||
                result.add_success(local)
 | 
			
		||||
 | 
			
		||||
                current_package_archives: Set[str] = next(
 | 
			
		||||
                    (set(current.packages) for current in current_packages if current.base == local.base), set())
 | 
			
		||||
                removed_packages.extend(current_package_archives.difference(local.packages))
 | 
			
		||||
            except Exception:
 | 
			
		||||
                self.reporter.set_failed(local.base)
 | 
			
		||||
                result.add_failed(local)
 | 
			
		||||
                self.logger.exception("could not process %s", local.base)
 | 
			
		||||
        self.clear_packages()
 | 
			
		||||
 | 
			
		||||
        self.process_remove(removed_packages)
 | 
			
		||||
 | 
			
		||||
        return self.repo.repo_path
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
@ -31,11 +31,13 @@ class ReportSettings(Enum):
 | 
			
		||||
    :cvar Disabled: option which generates no report for testing purpose
 | 
			
		||||
    :cvar HTML: html report generation
 | 
			
		||||
    :cvar Email: email report generation
 | 
			
		||||
    :cvar Console: print result to console
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    Disabled = "disabled"  # for testing purpose
 | 
			
		||||
    HTML = "html"
 | 
			
		||||
    Email = "email"
 | 
			
		||||
    Console = "console"
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_option(cls: Type[ReportSettings], value: str) -> ReportSettings:
 | 
			
		||||
@ -48,4 +50,6 @@ class ReportSettings(Enum):
 | 
			
		||||
            return cls.HTML
 | 
			
		||||
        if value.lower() in ("email",):
 | 
			
		||||
            return cls.Email
 | 
			
		||||
        if value.lower() in ("console",):
 | 
			
		||||
            return cls.Console
 | 
			
		||||
        raise InvalidOption(value)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								src/ahriman/models/result.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/ahriman/models/result.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2021 ahriman team.
 | 
			
		||||
#
 | 
			
		||||
# This file is part of ahriman
 | 
			
		||||
# (see https://github.com/arcan1s/ahriman).
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# This program is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
from typing import Any, List, Optional, Iterable
 | 
			
		||||
 | 
			
		||||
from ahriman.core.exceptions import SuccessFailed
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Result:
 | 
			
		||||
    """
 | 
			
		||||
    build result class holder
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, success: Optional[Iterable[Package]] = None, failed: Optional[Iterable[Package]] = None) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        default constructor
 | 
			
		||||
        :param success: initial list of successes packages
 | 
			
		||||
        :param failed: initial list of failed packages
 | 
			
		||||
        """
 | 
			
		||||
        success = success or []
 | 
			
		||||
        self._success = {package.base: package for package in success}
 | 
			
		||||
        failed = failed or []
 | 
			
		||||
        self._failed = {package.base: package for package in failed}
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def failed(self) -> List[Package]:
 | 
			
		||||
        """
 | 
			
		||||
        :return: list of packages which were failed
 | 
			
		||||
        """
 | 
			
		||||
        return list(self._failed.values())
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def success(self) -> List[Package]:
 | 
			
		||||
        """
 | 
			
		||||
        :return: list of packages with success result
 | 
			
		||||
        """
 | 
			
		||||
        return list(self._success.values())
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def updated(self) -> List[Package]:
 | 
			
		||||
        """
 | 
			
		||||
        :return: list of updated packages inclding both success and failed
 | 
			
		||||
        """
 | 
			
		||||
        return self.success + self.failed
 | 
			
		||||
 | 
			
		||||
    def add_failed(self, package: Package) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        add new package to failed built
 | 
			
		||||
        :param package: package with errors during build
 | 
			
		||||
        """
 | 
			
		||||
        self._failed[package.base] = package
 | 
			
		||||
 | 
			
		||||
    def add_success(self, package: Package) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        add new package to success built
 | 
			
		||||
        :param package: package built
 | 
			
		||||
        """
 | 
			
		||||
        self._success[package.base] = package
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=protected-access
 | 
			
		||||
    def merge(self, other: Result) -> Result:
 | 
			
		||||
        """
 | 
			
		||||
        merge other result into this one. This method assumes that other has fresh info about status and override it
 | 
			
		||||
        :param other: instance of the newest result
 | 
			
		||||
        :return: updated instance
 | 
			
		||||
        """
 | 
			
		||||
        for base, package in other._failed.items():
 | 
			
		||||
            if base in self._success:
 | 
			
		||||
                del self._success[base]
 | 
			
		||||
            self.add_failed(package)
 | 
			
		||||
        for base, package in other._success.items():
 | 
			
		||||
            if base in self._failed:
 | 
			
		||||
                raise SuccessFailed(base)
 | 
			
		||||
            self.add_success(package)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    # required for tests at least
 | 
			
		||||
    def __eq__(self, other: Any) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        check if other is the same object
 | 
			
		||||
        :param other: other object instance
 | 
			
		||||
        :return: True if the other object is the same and False otherwise
 | 
			
		||||
        """
 | 
			
		||||
        if not isinstance(other, Result):
 | 
			
		||||
            return False
 | 
			
		||||
        return self.success == other.success and self.failed == other.failed
 | 
			
		||||
@ -2,6 +2,7 @@ from pytest_mock import MockerFixture
 | 
			
		||||
 | 
			
		||||
from ahriman.application.application import Application
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_finalize(application: Application, mocker: MockerFixture) -> None:
 | 
			
		||||
@ -11,8 +12,8 @@ def test_finalize(application: Application, mocker: MockerFixture) -> None:
 | 
			
		||||
    report_mock = mocker.patch("ahriman.application.application.Application.report")
 | 
			
		||||
    sync_mock = mocker.patch("ahriman.application.application.Application.sync")
 | 
			
		||||
 | 
			
		||||
    application._finalize([])
 | 
			
		||||
    report_mock.assert_called_once_with([], [])
 | 
			
		||||
    application._finalize(Result())
 | 
			
		||||
    report_mock.assert_called_once_with([], Result())
 | 
			
		||||
    sync_mock.assert_called_once_with([], [])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ from ahriman.application.application.packages import Packages
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.package_description import PackageDescription
 | 
			
		||||
from ahriman.models.package_source import PackageSource
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_finalize(application_packages: Packages) -> None:
 | 
			
		||||
@ -211,4 +212,4 @@ def test_remove(application_packages: Packages, mocker: MockerFixture) -> None:
 | 
			
		||||
 | 
			
		||||
    application_packages.remove([])
 | 
			
		||||
    executor_mock.assert_called_once_with([])
 | 
			
		||||
    finalize_mock.assert_called_once_with([])
 | 
			
		||||
    finalize_mock.assert_called_once_with(Result())
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ from unittest import mock
 | 
			
		||||
from ahriman.application.application.repository import Repository
 | 
			
		||||
from ahriman.core.tree import Leaf, Tree
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_finalize(application_repository: Repository) -> None:
 | 
			
		||||
@ -98,7 +99,7 @@ def test_sign(application_repository: Repository, package_ahriman: Package, pack
 | 
			
		||||
    ])
 | 
			
		||||
    update_mock.assert_called_once_with([])
 | 
			
		||||
    sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path)
 | 
			
		||||
    finalize_mock.assert_called_once_with([])
 | 
			
		||||
    finalize_mock.assert_called_once_with(Result())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sign_skip(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
@ -132,7 +133,7 @@ def test_sign_specific(application_repository: Repository, package_ahriman: Pack
 | 
			
		||||
        application_repository.repository.paths.packages / filename.name)
 | 
			
		||||
    update_mock.assert_called_once_with([])
 | 
			
		||||
    sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path)
 | 
			
		||||
    finalize_mock.assert_called_once_with([])
 | 
			
		||||
    finalize_mock.assert_called_once_with(Result())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sync(application_repository: Repository, mocker: MockerFixture) -> None:
 | 
			
		||||
@ -181,7 +182,8 @@ def test_unknown_no_local(application_repository: Repository, package_ahriman: P
 | 
			
		||||
    assert not application_repository.unknown()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_update(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
def test_update(application_repository: Repository, package_ahriman: Package, result: Result,
 | 
			
		||||
                mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must process package updates
 | 
			
		||||
    """
 | 
			
		||||
@ -189,16 +191,33 @@ def test_update(application_repository: Repository, package_ahriman: Package, mo
 | 
			
		||||
    tree = Tree([Leaf(package_ahriman, set())])
 | 
			
		||||
 | 
			
		||||
    mocker.patch("ahriman.core.tree.Tree.load", return_value=tree)
 | 
			
		||||
    mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=[])
 | 
			
		||||
    mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=paths)
 | 
			
		||||
    mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
 | 
			
		||||
    build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=paths)
 | 
			
		||||
    update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update")
 | 
			
		||||
    build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=result)
 | 
			
		||||
    update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update", return_value=result)
 | 
			
		||||
    finalize_mock = mocker.patch("ahriman.application.application.repository.Repository._finalize")
 | 
			
		||||
 | 
			
		||||
    application_repository.update([package_ahriman])
 | 
			
		||||
    build_mock.assert_called_once_with([package_ahriman])
 | 
			
		||||
    update_mock.assert_called_once_with(paths)
 | 
			
		||||
    finalize_mock.assert_called_once_with([package_ahriman])
 | 
			
		||||
    update_mock.assert_has_calls([mock.call(paths), mock.call(paths)])
 | 
			
		||||
    finalize_mock.assert_has_calls([mock.call(result), mock.call(result)])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_update_empty(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must skip updating repository if no packages supplied
 | 
			
		||||
    """
 | 
			
		||||
    paths = [package.filepath for package in package_ahriman.packages.values()]
 | 
			
		||||
    tree = Tree([Leaf(package_ahriman, set())])
 | 
			
		||||
 | 
			
		||||
    mocker.patch("ahriman.core.tree.Tree.load", return_value=tree)
 | 
			
		||||
    mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=[])
 | 
			
		||||
    mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
 | 
			
		||||
    mocker.patch("ahriman.core.repository.executor.Executor.process_build")
 | 
			
		||||
    update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update")
 | 
			
		||||
 | 
			
		||||
    application_repository.update([package_ahriman])
 | 
			
		||||
    update_mock.assert_not_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_updates_all(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
 | 
			
		||||
    must run command
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
    application_mock = mocker.patch("ahriman.core.configuration.Configuration.dump",
 | 
			
		||||
                                    return_value=configuration.dump())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, pac
 | 
			
		||||
    application_mock = mocker.patch("ahriman.application.application.Application.unknown",
 | 
			
		||||
                                    return_value=[package_ahriman])
 | 
			
		||||
    remove_mock = mocker.patch("ahriman.application.application.Application.remove")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
 | 
			
		||||
    RemoveUnknown.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    application_mock.assert_called_once_with()
 | 
			
		||||
@ -65,7 +65,7 @@ def test_run_dry_run_verbose(args: argparse.Namespace, configuration: Configurat
 | 
			
		||||
    application_mock = mocker.patch("ahriman.application.application.Application.unknown",
 | 
			
		||||
                                    return_value=[package_ahriman])
 | 
			
		||||
    remove_mock = mocker.patch("ahriman.application.application.Application.remove")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
 | 
			
		||||
    RemoveUnknown.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    application_mock.assert_called_once_with()
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ from pytest_mock import MockerFixture
 | 
			
		||||
 | 
			
		||||
from ahriman.application.handlers import Report
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
 | 
			
		||||
@ -25,4 +26,4 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
 | 
			
		||||
    application_mock = mocker.patch("ahriman.application.application.Application.report")
 | 
			
		||||
 | 
			
		||||
    Report.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    application_mock.assert_called_once_with(args.target, [])
 | 
			
		||||
    application_mock.assert_called_once_with(args.target, Result())
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
 | 
			
		||||
    """
 | 
			
		||||
    args = _default_args(args)
 | 
			
		||||
    search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman])
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
 | 
			
		||||
    Search.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    search_mock.assert_called_once_with("ahriman")
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, package_ahr
 | 
			
		||||
    packages_mock = mocker.patch("ahriman.core.status.client.Client.get",
 | 
			
		||||
                                 return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)),
 | 
			
		||||
                                               (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))])
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
 | 
			
		||||
    Status.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    application_mock.assert_called_once_with()
 | 
			
		||||
@ -51,7 +51,7 @@ def test_run_verbose(args: argparse.Namespace, configuration: Configuration, pac
 | 
			
		||||
    mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
 | 
			
		||||
    mocker.patch("ahriman.core.status.client.Client.get",
 | 
			
		||||
                 return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))])
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
 | 
			
		||||
    Status.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    print_mock.assert_has_calls([mock.call(True) for _ in range(2)])
 | 
			
		||||
@ -83,7 +83,7 @@ def test_run_by_status(args: argparse.Namespace, configuration: Configuration, p
 | 
			
		||||
                 return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)),
 | 
			
		||||
                               (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))])
 | 
			
		||||
    mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
 | 
			
		||||
    Status.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    print_mock.assert_has_calls([mock.call(False) for _ in range(2)])
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
 | 
			
		||||
    args.parser = _parser
 | 
			
		||||
    commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands",
 | 
			
		||||
                                 return_value=["command"])
 | 
			
		||||
    print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
 | 
			
		||||
    UnsafeCommands.run(args, "x86_64", configuration, True, False)
 | 
			
		||||
    commands_mock.assert_called_once_with(pytest.helpers.anyvar(int))
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ from ahriman.models.aur_package import AURPackage
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.package_description import PackageDescription
 | 
			
		||||
from ahriman.models.repository_paths import RepositoryPaths
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
from ahriman.models.user import User
 | 
			
		||||
from ahriman.models.user_access import UserAccess
 | 
			
		||||
 | 
			
		||||
@ -234,6 +235,18 @@ def repository_paths(configuration: Configuration) -> RepositoryPaths:
 | 
			
		||||
        root=configuration.getpath("repository", "root"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def result(package_ahriman: Package) -> Result:
 | 
			
		||||
    """
 | 
			
		||||
    result fixture
 | 
			
		||||
    :param package_ahriman: package fixture
 | 
			
		||||
    :return: result test instance
 | 
			
		||||
    """
 | 
			
		||||
    result = Result()
 | 
			
		||||
    result.add_success(package_ahriman)
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def spawner(configuration: Configuration) -> Spawn:
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.aur_printer import AurPrinter
 | 
			
		||||
from ahriman.application.formatters.configuration_printer import ConfigurationPrinter
 | 
			
		||||
from ahriman.application.formatters.package_printer import PackagePrinter
 | 
			
		||||
from ahriman.application.formatters.status_printer import StatusPrinter
 | 
			
		||||
from ahriman.application.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.application.formatters.update_printer import UpdatePrinter
 | 
			
		||||
from ahriman.core.formatters.aur_printer import AurPrinter
 | 
			
		||||
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
 | 
			
		||||
from ahriman.core.formatters.package_printer import PackagePrinter
 | 
			
		||||
from ahriman.core.formatters.status_printer import StatusPrinter
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.core.formatters.update_printer import UpdatePrinter
 | 
			
		||||
from ahriman.models.aur_package import AURPackage
 | 
			
		||||
from ahriman.models.build_status import BuildStatus
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
from ahriman.application.formatters.aur_printer import AurPrinter
 | 
			
		||||
from ahriman.core.formatters.aur_printer import AurPrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties(aur_package_ahriman_printer: AurPrinter) -> None:
 | 
			
		||||
							
								
								
									
										36
									
								
								tests/ahriman/core/formatters/test_build_printer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								tests/ahriman/core/formatters/test_build_printer.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from ahriman.core.formatters.build_printer import BuildPrinter
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return empty properties list
 | 
			
		||||
    """
 | 
			
		||||
    assert not BuildPrinter(package_ahriman, is_success=True, use_utf=False).properties()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sign_ascii(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must correctly generate sign in ascii
 | 
			
		||||
    """
 | 
			
		||||
    BuildPrinter(package_ahriman, is_success=True, use_utf=False).title().encode("ascii")
 | 
			
		||||
    BuildPrinter(package_ahriman, is_success=False, use_utf=False).title().encode("ascii")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sign_utf8(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must correctly generate sign in ascii
 | 
			
		||||
    """
 | 
			
		||||
    with pytest.raises(UnicodeEncodeError):
 | 
			
		||||
        BuildPrinter(package_ahriman, is_success=True, use_utf=True).title().encode("ascii")
 | 
			
		||||
    with pytest.raises(UnicodeEncodeError):
 | 
			
		||||
        BuildPrinter(package_ahriman, is_success=False, use_utf=True).title().encode("ascii")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_title(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return non empty title
 | 
			
		||||
    """
 | 
			
		||||
    assert BuildPrinter(package_ahriman, is_success=True, use_utf=False).title() is not None
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
from ahriman.application.formatters.configuration_printer import ConfigurationPrinter
 | 
			
		||||
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties(configuration_printer: ConfigurationPrinter) -> None:
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
from ahriman.application.formatters.package_printer import PackagePrinter
 | 
			
		||||
from ahriman.core.formatters.package_printer import PackagePrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties(package_ahriman_printer: PackagePrinter) -> None:
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
from unittest.mock import MagicMock
 | 
			
		||||
 | 
			
		||||
from ahriman.application.formatters.package_printer import PackagePrinter
 | 
			
		||||
from ahriman.application.formatters.printer import Printer
 | 
			
		||||
from ahriman.core.formatters.package_printer import PackagePrinter
 | 
			
		||||
from ahriman.core.formatters.printer import Printer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_print(package_ahriman_printer: PackagePrinter) -> None:
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
from ahriman.application.formatters.status_printer import StatusPrinter
 | 
			
		||||
from ahriman.core.formatters.status_printer import StatusPrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties(status_printer: StatusPrinter) -> None:
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
from ahriman.application.formatters.string_printer import StringPrinter
 | 
			
		||||
from ahriman.core.formatters.string_printer import StringPrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties(string_printer: StringPrinter) -> None:
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
from ahriman.application.formatters.update_printer import UpdatePrinter
 | 
			
		||||
from ahriman.core.formatters.update_printer import UpdatePrinter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_properties(update_printer: UpdatePrinter) -> None:
 | 
			
		||||
							
								
								
									
										20
									
								
								tests/ahriman/core/report/test_console.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/ahriman/core/report/test_console.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
from pytest_mock import MockerFixture
 | 
			
		||||
from unittest import mock
 | 
			
		||||
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.report.console import Console
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_generate(configuration: Configuration, result: Result, package_python_schedule: Package,
 | 
			
		||||
                  mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must print result to stdout
 | 
			
		||||
    """
 | 
			
		||||
    print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
 | 
			
		||||
    result.add_failed(package_python_schedule)
 | 
			
		||||
    report = Console("x86_64", configuration, "console")
 | 
			
		||||
 | 
			
		||||
    report.generate([], result)
 | 
			
		||||
    print_mock.assert_has_calls([mock.call(verbose=True), mock.call(verbose=True)])
 | 
			
		||||
@ -5,6 +5,7 @@ from pytest_mock import MockerFixture
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.report.email import Email
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_send(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
@ -92,24 +93,26 @@ def test_generate(configuration: Configuration, package_ahriman: Package, mocker
 | 
			
		||||
    send_mock = mocker.patch("ahriman.core.report.email.Email._send")
 | 
			
		||||
 | 
			
		||||
    report = Email("x86_64", configuration, "email")
 | 
			
		||||
    report.generate([package_ahriman], [])
 | 
			
		||||
    report.generate([package_ahriman], Result())
 | 
			
		||||
    send_mock.assert_called_once_with(pytest.helpers.anyvar(int), {})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_generate_with_built(configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
def test_generate_with_built(configuration: Configuration, package_ahriman: Package, result: Result,
 | 
			
		||||
                             mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate report with built packages
 | 
			
		||||
    """
 | 
			
		||||
    send_mock = mocker.patch("ahriman.core.report.email.Email._send")
 | 
			
		||||
 | 
			
		||||
    report = Email("x86_64", configuration, "email")
 | 
			
		||||
    report.generate([package_ahriman], [package_ahriman])
 | 
			
		||||
    report.generate([package_ahriman], result)
 | 
			
		||||
    send_mock.assert_called_once_with(pytest.helpers.anyvar(int), {})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_generate_with_built_and_full_path(
 | 
			
		||||
        configuration: Configuration,
 | 
			
		||||
        package_ahriman: Package,
 | 
			
		||||
        result: Result,
 | 
			
		||||
        mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate report with built packages
 | 
			
		||||
@ -118,7 +121,7 @@ def test_generate_with_built_and_full_path(
 | 
			
		||||
 | 
			
		||||
    report = Email("x86_64", configuration, "email")
 | 
			
		||||
    report.full_template_path = report.template_path
 | 
			
		||||
    report.generate([package_ahriman], [package_ahriman])
 | 
			
		||||
    report.generate([package_ahriman], result)
 | 
			
		||||
    send_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -130,11 +133,11 @@ def test_generate_no_empty(configuration: Configuration, package_ahriman: Packag
 | 
			
		||||
    send_mock = mocker.patch("ahriman.core.report.email.Email._send")
 | 
			
		||||
 | 
			
		||||
    report = Email("x86_64", configuration, "email")
 | 
			
		||||
    report.generate([package_ahriman], [])
 | 
			
		||||
    report.generate([package_ahriman], Result())
 | 
			
		||||
    send_mock.assert_not_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_generate_no_empty_with_built(configuration: Configuration, package_ahriman: Package,
 | 
			
		||||
def test_generate_no_empty_with_built(configuration: Configuration, package_ahriman: Package, result: Result,
 | 
			
		||||
                                      mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate report with built packages if no_empty_report is set
 | 
			
		||||
@ -143,5 +146,5 @@ def test_generate_no_empty_with_built(configuration: Configuration, package_ahri
 | 
			
		||||
    send_mock = mocker.patch("ahriman.core.report.email.Email._send")
 | 
			
		||||
 | 
			
		||||
    report = Email("x86_64", configuration, "email")
 | 
			
		||||
    report.generate([package_ahriman], [package_ahriman])
 | 
			
		||||
    report.generate([package_ahriman], result)
 | 
			
		||||
    send_mock.assert_called_once_with(pytest.helpers.anyvar(int), {})
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.report.jinja_template import JinjaTemplate
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_generate(configuration: Configuration, package_ahriman: Package) -> None:
 | 
			
		||||
@ -9,4 +10,4 @@ def test_generate(configuration: Configuration, package_ahriman: Package) -> Non
 | 
			
		||||
    """
 | 
			
		||||
    path = configuration.getpath("html", "template_path")
 | 
			
		||||
    report = JinjaTemplate("html", configuration)
 | 
			
		||||
    assert report.make_html([package_ahriman], path)
 | 
			
		||||
    assert report.make_html(Result(success=[package_ahriman]), path)
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.exceptions import ReportFailed
 | 
			
		||||
from ahriman.core.report.report import Report
 | 
			
		||||
from ahriman.models.report_settings import ReportSettings
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_report_failure(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
@ -14,32 +15,41 @@ def test_report_failure(configuration: Configuration, mocker: MockerFixture) ->
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception())
 | 
			
		||||
    with pytest.raises(ReportFailed):
 | 
			
		||||
        Report.load("x86_64", configuration, "html").run([], [])
 | 
			
		||||
        Report.load("x86_64", configuration, "html").run([], Result())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
def test_report_dummy(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must construct dummy report class
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.models.report_settings.ReportSettings.from_option", return_value=ReportSettings.Disabled)
 | 
			
		||||
    report_mock = mocker.patch("ahriman.core.report.report.Report.generate")
 | 
			
		||||
    Report.load("x86_64", configuration, "disabled").run([], [])
 | 
			
		||||
    report_mock.assert_called_once_with([], [])
 | 
			
		||||
    Report.load("x86_64", configuration, "disabled").run([], result)
 | 
			
		||||
    report_mock.assert_called_once_with([], result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_report_email(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
def test_report_console(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate console report
 | 
			
		||||
    """
 | 
			
		||||
    report_mock = mocker.patch("ahriman.core.report.console.Console.generate")
 | 
			
		||||
    Report.load("x86_64", configuration, "console").run([], result)
 | 
			
		||||
    report_mock.assert_called_once_with([], result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_report_email(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate email report
 | 
			
		||||
    """
 | 
			
		||||
    report_mock = mocker.patch("ahriman.core.report.email.Email.generate")
 | 
			
		||||
    Report.load("x86_64", configuration, "email").run([], [])
 | 
			
		||||
    report_mock.assert_called_once_with([], [])
 | 
			
		||||
    Report.load("x86_64", configuration, "email").run([], result)
 | 
			
		||||
    report_mock.assert_called_once_with([], result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_report_html(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
def test_report_html(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate html report
 | 
			
		||||
    """
 | 
			
		||||
    report_mock = mocker.patch("ahriman.core.report.html.HTML.generate")
 | 
			
		||||
    Report.load("x86_64", configuration, "html").run([], [])
 | 
			
		||||
    report_mock.assert_called_once_with([], [])
 | 
			
		||||
    Report.load("x86_64", configuration, "html").run([], result)
 | 
			
		||||
    report_mock.assert_called_once_with([], result)
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,6 @@ def test_process_build(executor: Executor, package_ahriman: Package, mocker: Moc
 | 
			
		||||
    mocker.patch("ahriman.core.build_tools.task.Task.init")
 | 
			
		||||
    move_mock = mocker.patch("shutil.move")
 | 
			
		||||
    status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building")
 | 
			
		||||
    built_packages_mock = mocker.patch("ahriman.core.repository.executor.Executor.packages_built")
 | 
			
		||||
 | 
			
		||||
    executor.process_build([package_ahriman])
 | 
			
		||||
    # must move files (once)
 | 
			
		||||
@ -45,8 +44,6 @@ def test_process_build(executor: Executor, package_ahriman: Package, mocker: Moc
 | 
			
		||||
    # must clear directory
 | 
			
		||||
    from ahriman.core.repository.cleaner import Cleaner
 | 
			
		||||
    Cleaner.clear_build.assert_called_once_with()
 | 
			
		||||
    # must return build packages after all
 | 
			
		||||
    built_packages_mock.assert_called_once_with()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_process_build_failure(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
 | 
			
		||||
@ -21,3 +21,6 @@ def test_from_option_valid() -> None:
 | 
			
		||||
 | 
			
		||||
    assert ReportSettings.from_option("email") == ReportSettings.Email
 | 
			
		||||
    assert ReportSettings.from_option("EmAil") == ReportSettings.Email
 | 
			
		||||
 | 
			
		||||
    assert ReportSettings.from_option("console") == ReportSettings.Console
 | 
			
		||||
    assert ReportSettings.from_option("conSOle") == ReportSettings.Console
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										119
									
								
								tests/ahriman/models/test_result.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								tests/ahriman/models/test_result.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from ahriman.core.exceptions import SuccessFailed
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.result import Result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_add_failed(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must add package to failed list
 | 
			
		||||
    """
 | 
			
		||||
    result = Result()
 | 
			
		||||
    result.add_failed(package_ahriman)
 | 
			
		||||
    assert result.failed == [package_ahriman]
 | 
			
		||||
    assert not result.success
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_add_success(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must add package to success list
 | 
			
		||||
    """
 | 
			
		||||
    result = Result()
 | 
			
		||||
    result.add_success(package_ahriman)
 | 
			
		||||
    assert result.success == [package_ahriman]
 | 
			
		||||
    assert not result.failed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_merge(package_ahriman: Package, package_python_schedule: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must merge success packages
 | 
			
		||||
    """
 | 
			
		||||
    left = Result()
 | 
			
		||||
    left.add_success(package_ahriman)
 | 
			
		||||
    right = Result()
 | 
			
		||||
    right.add_success(package_python_schedule)
 | 
			
		||||
 | 
			
		||||
    result = left.merge(right)
 | 
			
		||||
    assert result.success == [package_ahriman, package_python_schedule]
 | 
			
		||||
    assert not left.failed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_merge_failed(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must merge and remove failed packages from success list
 | 
			
		||||
    """
 | 
			
		||||
    left = Result()
 | 
			
		||||
    left.add_success(package_ahriman)
 | 
			
		||||
    right = Result()
 | 
			
		||||
    right.add_failed(package_ahriman)
 | 
			
		||||
 | 
			
		||||
    result = left.merge(right)
 | 
			
		||||
    assert result.failed == [package_ahriman]
 | 
			
		||||
    assert not left.success
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_merge_exception(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must raise exception in case if package was failed
 | 
			
		||||
    """
 | 
			
		||||
    left = Result()
 | 
			
		||||
    left.add_failed(package_ahriman)
 | 
			
		||||
    right = Result()
 | 
			
		||||
    right.add_success(package_ahriman)
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(SuccessFailed):
 | 
			
		||||
        left.merge(right)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_eq(package_ahriman: Package, package_python_schedule: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return True for same objects
 | 
			
		||||
    """
 | 
			
		||||
    left = Result()
 | 
			
		||||
    left.add_success(package_ahriman)
 | 
			
		||||
    left.add_failed(package_python_schedule)
 | 
			
		||||
    right = Result()
 | 
			
		||||
    right.add_success(package_ahriman)
 | 
			
		||||
    right.add_failed(package_python_schedule)
 | 
			
		||||
 | 
			
		||||
    assert left == right
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_eq_false(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return False in case if lists do not match
 | 
			
		||||
    """
 | 
			
		||||
    left = Result()
 | 
			
		||||
    left.add_success(package_ahriman)
 | 
			
		||||
    right = Result()
 | 
			
		||||
    right.add_failed(package_ahriman)
 | 
			
		||||
 | 
			
		||||
    assert left != right
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_eq_false_failed(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return False in case if failed does not match
 | 
			
		||||
    """
 | 
			
		||||
    left = Result()
 | 
			
		||||
    left.add_failed(package_ahriman)
 | 
			
		||||
 | 
			
		||||
    assert left != Result()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_eq_false_success(package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return False in case if success does not match
 | 
			
		||||
    """
 | 
			
		||||
    left = Result()
 | 
			
		||||
    left.add_success(package_ahriman)
 | 
			
		||||
 | 
			
		||||
    assert left != Result()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_eq_other() -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return False in case if object is not an instance of result
 | 
			
		||||
    """
 | 
			
		||||
    assert Result() != 42
 | 
			
		||||
@ -42,6 +42,9 @@ receivers = mail@example.com
 | 
			
		||||
sender = mail@example.com
 | 
			
		||||
template_path = ../web/templates/repo-index.jinja2
 | 
			
		||||
 | 
			
		||||
[console]
 | 
			
		||||
use_utf = yes
 | 
			
		||||
 | 
			
		||||
[html]
 | 
			
		||||
path =
 | 
			
		||||
homepage =
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user