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:
2022-03-19 23:48:43 +03:00
parent 060c7412b1
commit a7c6d95b34
59 changed files with 631 additions and 187 deletions

View File

@ -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]:
"""

View File

@ -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())

View File

@ -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]:

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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})"

View 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 "[ ]"

View File

@ -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}]"

View File

@ -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()

View File

@ -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())

View File

@ -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):

View File

@ -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)]

View 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)

View File

@ -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)

View File

@ -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)

View File

@ -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"]

View File

@ -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()

View File

@ -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

View File

@ -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)

View 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