mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-15 06:55:48 +00:00
Add ability to show more info in search and status subcommands
This feature also introduces the followiing changes * aur-search command now works as expected with multiterms * printer classes for managing of data print * --sort-by argument for aur-search subcommand instead of using package name * --quiet argument now has also --no-quite option * if --quite is supplied, the log level will be set to warn instead of critical to be able to see error messages * pretty_datetime function now also supports datetime objects * BuildStatus is now pure dataclass
This commit is contained in:
@ -60,7 +60,8 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("-l", "--lock", help="lock file", type=Path,
|
||||
default=Path(tempfile.gettempdir()) / "ahriman.lock")
|
||||
parser.add_argument("--no-report", help="force disable reporting to web service", action="store_true")
|
||||
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
|
||||
parser.add_argument("-q", "--quiet", help="force disable any logging", action=argparse.BooleanOptionalAction,
|
||||
default=False) # sometimes we would like to run not quiet even if it is disabled by default
|
||||
parser.add_argument("--unsafe", help="allow to run ahriman as non-ahriman user. Some actions might be unavailable",
|
||||
action="store_true")
|
||||
parser.add_argument("-v", "--version", action="version", version=version.__version__)
|
||||
@ -104,7 +105,12 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
parser = root.add_parser("aur-search", aliases=["search"], help="search for package",
|
||||
description="search for package in AUR using API", formatter_class=_formatter)
|
||||
parser.add_argument("search", help="search terms, can be specified multiple times", nargs="+")
|
||||
parser.add_argument("search", help="search terms, can be specified multiple times, result will match all terms",
|
||||
nargs="+")
|
||||
parser.add_argument("-i", "--info", help="show additional package information", action="store_true")
|
||||
parser.add_argument("--sort-by", help="sort field by this field. In case if two packages have the same value of "
|
||||
"the specified field, they will be always sorted by name",
|
||||
default="name", choices=sorted(handlers.Search.SORT_FIELDS))
|
||||
parser.set_defaults(handler=handlers.Search, architecture=[""], lock=None, no_report=True, quiet=True, unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -181,6 +187,7 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("package", help="filter status by package base", nargs="*")
|
||||
parser.add_argument("--ahriman", help="get service status itself", action="store_true")
|
||||
parser.add_argument("-i", "--info", help="show additional package information", action="store_true")
|
||||
parser.add_argument("-s", "--status", help="filter packages by status",
|
||||
type=BuildStatusEnum, choices=BuildStatusEnum)
|
||||
parser.set_defaults(handler=handlers.Status, lock=None, no_report=True, quiet=True, unsafe=True)
|
||||
@ -354,6 +361,7 @@ def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentP
|
||||
description="remove packages which are missing in AUR and do not have local PKGBUILDs",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true")
|
||||
parser.add_argument("-i", "--info", help="show additional package information", action="store_true")
|
||||
parser.set_defaults(handler=handlers.RemoveUnknown)
|
||||
return parser
|
||||
|
||||
|
19
src/ahriman/application/formatters/__init__.py
Normal file
19
src/ahriman/application/formatters/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
62
src/ahriman/application/formatters/aur_printer.py
Normal file
62
src/ahriman/application/formatters/aur_printer.py
Normal file
@ -0,0 +1,62 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
import aur # type: ignore
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class AurPrinter(Printer):
|
||||
"""
|
||||
print content of the AUR package
|
||||
"""
|
||||
|
||||
def __init__(self, package: aur.Package) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param package: AUR package description
|
||||
"""
|
||||
self.content = package
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
: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),
|
||||
Property("Licenses", self.content.license), # it should be actually a list
|
||||
Property("Maintainer", self.content.maintainer or ""), # I think it is optional
|
||||
Property("First submitted", pretty_datetime(self.content.first_submitted)),
|
||||
Property("Last updated", pretty_datetime(self.content.last_modified)),
|
||||
# more fields coming https://github.com/cdown/aur/pull/29
|
||||
]
|
||||
|
||||
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})"
|
55
src/ahriman/application/formatters/configuration_printer.py
Normal file
55
src/ahriman/application/formatters/configuration_printer.py
Normal file
@ -0,0 +1,55 @@
|
||||
#
|
||||
# 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 Dict, List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class ConfigurationPrinter(Printer):
|
||||
"""
|
||||
print content of the configuration section
|
||||
"""
|
||||
|
||||
def __init__(self, section: str, values: Dict[str, str]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param section: section name
|
||||
:param values: configuration values dictionary
|
||||
"""
|
||||
self.section = section
|
||||
self.content = values
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return [
|
||||
Property(key, value, is_required=True)
|
||||
for key, value in sorted(self.content.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}]"
|
60
src/ahriman/application/formatters/package_printer.py
Normal file
60
src/ahriman/application/formatters/package_printer.py
Normal file
@ -0,0 +1,60 @@
|
||||
#
|
||||
# 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 List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class PackagePrinter(Printer):
|
||||
"""
|
||||
print content of the internal package object
|
||||
"""
|
||||
|
||||
def __init__(self, package: Package, status: BuildStatus) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param package: package description
|
||||
:param status: build status
|
||||
"""
|
||||
self.content = package
|
||||
self.status = status
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
: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("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()
|
55
src/ahriman/application/formatters/printer.py
Normal file
55
src/ahriman/application/formatters/printer.py
Normal file
@ -0,0 +1,55 @@
|
||||
#
|
||||
# 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 Callable, List, Optional
|
||||
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class Printer:
|
||||
"""
|
||||
base class for formatters
|
||||
"""
|
||||
|
||||
def print(self, verbose: bool, log_fn: Callable[[str], None] = print, separator: str = ": ") -> None:
|
||||
"""
|
||||
print content
|
||||
:param verbose: print all fields
|
||||
:param log_fn: logger function to log data
|
||||
:param separator: separator for property name and property value
|
||||
"""
|
||||
if (title := self.title()) is not None:
|
||||
log_fn(title)
|
||||
for prop in self.properties():
|
||||
if not verbose and not prop.is_required:
|
||||
continue
|
||||
log_fn(f"\t{prop.name}{separator}{prop.value}")
|
||||
|
||||
def properties(self) -> List[Property]: # pylint: disable=no-self-use
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return []
|
||||
|
||||
def title(self) -> Optional[str]:
|
||||
"""
|
||||
generate entry title from content
|
||||
:return: content title if it can be generated and None otherwise
|
||||
"""
|
51
src/ahriman/application/formatters/status_printer.py
Normal file
51
src/ahriman/application/formatters/status_printer.py
Normal file
@ -0,0 +1,51 @@
|
||||
#
|
||||
# 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 List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class StatusPrinter(Printer):
|
||||
"""
|
||||
print content of the status object
|
||||
"""
|
||||
|
||||
def __init__(self, status: BuildStatus) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param status: build status
|
||||
"""
|
||||
self.content = status
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return []
|
||||
|
||||
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()
|
@ -21,6 +21,7 @@ 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
|
||||
|
||||
@ -32,8 +33,6 @@ class Dump(Handler):
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||
|
||||
_print = print
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
@ -46,7 +45,4 @@ class Dump(Handler):
|
||||
"""
|
||||
dump = configuration.dump()
|
||||
for section, values in sorted(dump.items()):
|
||||
Dump._print(f"[{section}]")
|
||||
for key, value in sorted(values.items()):
|
||||
Dump._print(f"{key} = {value}")
|
||||
Dump._print()
|
||||
ConfigurationPrinter(section, values).print(verbose=False, separator=" = ")
|
||||
|
@ -22,9 +22,10 @@ import argparse
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.formatters.package_printer import PackagePrinter
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
|
||||
|
||||
class RemoveUnknown(Handler):
|
||||
@ -46,16 +47,7 @@ class RemoveUnknown(Handler):
|
||||
unknown_packages = application.unknown()
|
||||
if args.dry_run:
|
||||
for package in unknown_packages:
|
||||
RemoveUnknown.log_fn(package)
|
||||
PackagePrinter(package, BuildStatus()).print(args.info)
|
||||
return
|
||||
|
||||
application.remove(package.base for package in unknown_packages)
|
||||
|
||||
@staticmethod
|
||||
def log_fn(package: Package) -> None:
|
||||
"""
|
||||
log package information
|
||||
:param package: package object to log
|
||||
"""
|
||||
print(f"=> {package.base} {package.version}")
|
||||
print(f" {package.web_url}")
|
||||
|
@ -20,10 +20,12 @@
|
||||
import argparse
|
||||
import aur # type: ignore
|
||||
|
||||
from typing import Callable, Type
|
||||
from typing import Callable, Dict, Iterable, List, Tuple, Type
|
||||
|
||||
from ahriman.application.formatters.aur_printer import AurPrinter
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InvalidOption
|
||||
|
||||
|
||||
class Search(Handler):
|
||||
@ -32,6 +34,7 @@ class Search(Handler):
|
||||
"""
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||
SORT_FIELDS = set(aur.Package._fields) # later we will have to remove some fields from here (lists)
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
@ -43,20 +46,32 @@ class Search(Handler):
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
search = " ".join(args.search)
|
||||
packages = aur.search(search)
|
||||
packages: Dict[str, aur.Package] = {}
|
||||
# see https://bugs.archlinux.org/task/49133
|
||||
for search in args.search:
|
||||
portion = aur.search(search)
|
||||
packages = {
|
||||
package.package_base: package
|
||||
for package in portion
|
||||
if package.package_base in packages or not packages
|
||||
}
|
||||
|
||||
# it actually always should return string
|
||||
# explicit cast to string just to avoid mypy warning for untyped library
|
||||
comparator: Callable[[aur.Package], str] = lambda item: str(item.package_base)
|
||||
for package in sorted(packages, key=comparator):
|
||||
Search.log_fn(package)
|
||||
packages_list = list(packages.values()) # explicit conversion for the tests
|
||||
for package in Search.sort(packages_list, args.sort_by):
|
||||
AurPrinter(package).print(args.info)
|
||||
|
||||
@staticmethod
|
||||
def log_fn(package: aur.Package) -> None:
|
||||
def sort(packages: Iterable[aur.Package], sort_by: str) -> List[aur.Package]:
|
||||
"""
|
||||
log package information
|
||||
:param package: package object as from AUR
|
||||
sort package list by specified field
|
||||
:param packages: packages list to sort
|
||||
:param sort_by: AUR package field name to sort by
|
||||
:return: sorted list for packages
|
||||
"""
|
||||
print(f"=> {package.package_base} {package.version}")
|
||||
print(f" {package.description}")
|
||||
if sort_by not in Search.SORT_FIELDS:
|
||||
raise InvalidOption(sort_by)
|
||||
# always sort by package name at the last
|
||||
# well technically it is not a string, but we can deal with it
|
||||
comparator: Callable[[aur.Package], Tuple[str, str]] =\
|
||||
lambda package: (getattr(package, sort_by), package.name)
|
||||
return sorted(packages, key=comparator)
|
||||
|
@ -22,6 +22,8 @@ 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.models.build_status import BuildStatus
|
||||
@ -49,8 +51,7 @@ class Status(Handler):
|
||||
client = Application(architecture, configuration, no_report=False).repository.reporter
|
||||
if args.ahriman:
|
||||
ahriman = client.get_self()
|
||||
print(ahriman.pretty_print())
|
||||
print()
|
||||
StatusPrinter(ahriman).print(args.info)
|
||||
if args.package:
|
||||
packages: Iterable[Tuple[Package, BuildStatus]] = sum(
|
||||
[client.get(base) for base in args.package],
|
||||
@ -62,6 +63,4 @@ class Status(Handler):
|
||||
filter_fn: Callable[[Tuple[Package, BuildStatus]], bool] =\
|
||||
lambda item: args.status is None or item[1].status == args.status
|
||||
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
|
||||
print(package.pretty_print())
|
||||
print(f"\t{package.version}")
|
||||
print(f"\t{package_status.pretty_print()}")
|
||||
PackagePrinter(package, package_status).print(args.info)
|
||||
|
@ -113,8 +113,7 @@ class User(Handler):
|
||||
:param salt_length: salt length
|
||||
:return: current salt
|
||||
"""
|
||||
salt = configuration.get("auth", "salt", fallback=None)
|
||||
if salt:
|
||||
if salt := configuration.get("auth", "salt", fallback=None):
|
||||
return salt
|
||||
return MUser.generate_password(salt_length)
|
||||
|
||||
|
@ -175,7 +175,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
level=self.DEFAULT_LOG_LEVEL)
|
||||
logging.exception("could not load logging from configuration, fallback to stderr")
|
||||
if quiet:
|
||||
logging.disable()
|
||||
logging.disable(logging.WARNING) # only print errors here
|
||||
|
||||
def merge_sections(self, architecture: str) -> None:
|
||||
"""
|
||||
|
@ -138,17 +138,17 @@ class Executor(Cleaner):
|
||||
:param packages: list of filenames to run
|
||||
:return: path to repository database
|
||||
"""
|
||||
def update_single(fn: Optional[str], base: str) -> None:
|
||||
if fn is None:
|
||||
def update_single(name: Optional[str], base: str) -> None:
|
||||
if name is None:
|
||||
self.logger.warning("received empty package name for base %s", base)
|
||||
return # suppress type checking, it never can be none actually
|
||||
# in theory it might be NOT packages directory, but we suppose it is
|
||||
full_path = self.paths.packages / fn
|
||||
full_path = self.paths.packages / name
|
||||
files = self.sign.process_sign_package(full_path, base)
|
||||
for src in files:
|
||||
dst = self.paths.repository / src.name
|
||||
shutil.move(src, dst)
|
||||
package_path = self.paths.repository / fn
|
||||
package_path = self.paths.repository / name
|
||||
self.repo.add(package_path)
|
||||
|
||||
# we are iterating over bases, not single packages
|
||||
|
@ -72,16 +72,16 @@ class UpdateHandler(Cleaner):
|
||||
result: List[Package] = []
|
||||
known_bases = {package.base for package in self.packages()}
|
||||
|
||||
for fn in self.paths.manual.iterdir():
|
||||
for filename in self.paths.manual.iterdir():
|
||||
try:
|
||||
local = Package.load(fn, self.pacman, self.aur_url)
|
||||
local = Package.load(filename, self.pacman, self.aur_url)
|
||||
result.append(local)
|
||||
if local.base not in known_bases:
|
||||
self.reporter.set_unknown(local)
|
||||
else:
|
||||
self.reporter.set_pending(local.base)
|
||||
except Exception:
|
||||
self.logger.exception("could not add package from %s", fn)
|
||||
self.logger.exception("could not add package from %s", filename)
|
||||
self.clear_manual()
|
||||
|
||||
return result
|
||||
|
@ -126,11 +126,10 @@ class Watcher:
|
||||
"""
|
||||
for package in self.repository.packages():
|
||||
# get status of build or assign unknown
|
||||
current = self.known.get(package.base)
|
||||
if current is None:
|
||||
status = BuildStatus()
|
||||
else:
|
||||
if (current := self.known.get(package.base)) is not None:
|
||||
_, status = current
|
||||
else:
|
||||
status = BuildStatus()
|
||||
self.known[package.base] = (package, status)
|
||||
self._cache_load()
|
||||
|
||||
|
@ -24,7 +24,7 @@ import requests
|
||||
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Generator, Optional, Union
|
||||
from typing import Any, Dict, Generator, Iterable, Optional, Union
|
||||
|
||||
from ahriman.core.exceptions import InvalidOption, UnsafeRun
|
||||
|
||||
@ -78,6 +78,16 @@ def exception_response_text(exception: requests.exceptions.HTTPError) -> str:
|
||||
return result
|
||||
|
||||
|
||||
def filter_json(source: Dict[str, Any], known_fields: Iterable[str]) -> Dict[str, Any]:
|
||||
"""
|
||||
filter json object by fields used for json-to-object conversion
|
||||
:param source: raw json object
|
||||
:param known_fields: list of fields which have to be known for the target object
|
||||
:return: json object without unknown and empty fields
|
||||
"""
|
||||
return {key: value for key, value in source.items() if key in known_fields and value is not None}
|
||||
|
||||
|
||||
def package_like(filename: Path) -> bool:
|
||||
"""
|
||||
check if file looks like package
|
||||
@ -88,13 +98,17 @@ def package_like(filename: Path) -> bool:
|
||||
return ".pkg." in name and not name.endswith(".sig")
|
||||
|
||||
|
||||
def pretty_datetime(timestamp: Optional[Union[float, int]]) -> str:
|
||||
def pretty_datetime(timestamp: Optional[Union[datetime.datetime, float, int]]) -> str:
|
||||
"""
|
||||
convert datetime object to string
|
||||
:param timestamp: datetime to convert
|
||||
:return: pretty printable datetime as string
|
||||
"""
|
||||
return "" if timestamp is None else datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||
if timestamp is None:
|
||||
return ""
|
||||
if isinstance(timestamp, (int, float)):
|
||||
timestamp = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
return timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def pretty_size(size: Optional[float], level: int = 0) -> str:
|
||||
|
@ -21,10 +21,11 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from dataclasses import dataclass, fields
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional, Type, Union
|
||||
from typing import Any, Dict, Type
|
||||
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.core.util import filter_json, pretty_datetime
|
||||
|
||||
|
||||
class BuildStatusEnum(Enum):
|
||||
@ -74,6 +75,7 @@ class BuildStatusEnum(Enum):
|
||||
return "secondary"
|
||||
|
||||
|
||||
@dataclass
|
||||
class BuildStatus:
|
||||
"""
|
||||
build status holder
|
||||
@ -81,15 +83,14 @@ class BuildStatus:
|
||||
:ivar timestamp: build status update time
|
||||
"""
|
||||
|
||||
def __init__(self, status: Union[BuildStatusEnum, str, None] = None,
|
||||
timestamp: Optional[int] = None) -> None:
|
||||
status: BuildStatusEnum = BuildStatusEnum.Unknown
|
||||
timestamp: int = int(datetime.datetime.utcnow().timestamp())
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param status: current build status if known. `BuildStatusEnum.Unknown` will be used if not set
|
||||
:param timestamp: build status timestamp. Current timestamp will be used if not set
|
||||
convert status to enum type
|
||||
"""
|
||||
self.status = BuildStatusEnum(status) if status else BuildStatusEnum.Unknown
|
||||
self.timestamp = timestamp or int(datetime.datetime.utcnow().timestamp())
|
||||
self.status = BuildStatusEnum(self.status)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls: Type[BuildStatus], dump: Dict[str, Any]) -> BuildStatus:
|
||||
@ -98,7 +99,8 @@ class BuildStatus:
|
||||
:param dump: json dump body
|
||||
:return: status properties
|
||||
"""
|
||||
return cls(dump.get("status"), dump.get("timestamp"))
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
return cls(**filter_json(dump, known_fields))
|
||||
|
||||
def pretty_print(self) -> str:
|
||||
"""
|
||||
@ -116,20 +118,3 @@ class BuildStatus:
|
||||
"status": self.status.value,
|
||||
"timestamp": self.timestamp
|
||||
}
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""
|
||||
compare object to other
|
||||
:param other: other object to compare
|
||||
:return: True in case if objects are equal
|
||||
"""
|
||||
if not isinstance(other, BuildStatus):
|
||||
return False
|
||||
return self.status == other.status and self.timestamp == other.timestamp
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
generate string representation of object
|
||||
:return: unique string representation
|
||||
"""
|
||||
return f"BuildStatus(status={self.status.value}, timestamp={self.timestamp})"
|
||||
|
@ -22,6 +22,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass, fields
|
||||
from typing import Any, Dict, List, Tuple, Type
|
||||
|
||||
from ahriman.core.util import filter_json
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
|
||||
@ -54,8 +55,7 @@ class Counters:
|
||||
"""
|
||||
# filter to only known fields
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
dump = {key: value for key, value in dump.items() if key in known_fields}
|
||||
return cls(**dump)
|
||||
return cls(**filter_json(dump, known_fields))
|
||||
|
||||
@classmethod
|
||||
def from_packages(cls: Type[Counters], packages: List[Tuple[Package, BuildStatus]]) -> Counters:
|
||||
|
@ -24,6 +24,8 @@ from pathlib import Path
|
||||
from pyalpm import Package # type: ignore
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
from ahriman.core.util import filter_json
|
||||
|
||||
|
||||
@dataclass
|
||||
class PackageDescription:
|
||||
@ -70,8 +72,7 @@ class PackageDescription:
|
||||
"""
|
||||
# filter to only known fields
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
dump = {key: value for key, value in dump.items() if key in known_fields}
|
||||
return cls(**dump)
|
||||
return cls(**filter_json(dump, known_fields))
|
||||
|
||||
@classmethod
|
||||
def from_package(cls: Type[PackageDescription], package: Package, path: Path) -> PackageDescription:
|
||||
|
31
src/ahriman/models/property.py
Normal file
31
src/ahriman/models/property.py
Normal file
@ -0,0 +1,31 @@
|
||||
# (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 dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Property:
|
||||
"""
|
||||
holder of object properties descriptor
|
||||
:ivar name: name of the property
|
||||
:ivar value: property value
|
||||
:ivar is_required: if set to True then this property is required
|
||||
"""
|
||||
|
||||
name: str
|
||||
value: Any
|
||||
is_required: bool = False
|
@ -81,8 +81,7 @@ def auth_handler() -> MiddlewareType:
|
||||
"""
|
||||
@middleware
|
||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
||||
permission_method = getattr(handler, "get_permission", None)
|
||||
if permission_method is not None:
|
||||
if (permission_method := getattr(handler, "get_permission", None)) is not None:
|
||||
permission = await permission_method(request)
|
||||
elif isinstance(handler, types.MethodType): # additional wrapper for static resources
|
||||
handler_instance = getattr(handler, "__self__", None)
|
||||
|
@ -45,11 +45,11 @@ class LoginView(BaseView):
|
||||
"""
|
||||
from ahriman.core.auth.oauth import OAuth
|
||||
|
||||
code = self.request.query.getone("code", default=None)
|
||||
oauth_provider = self.validator
|
||||
if not isinstance(oauth_provider, OAuth): # there is actually property, but mypy does not like it anyway
|
||||
raise HTTPMethodNotAllowed(self.request.method, ["POST"])
|
||||
|
||||
code = self.request.query.getone("code", default=None)
|
||||
if not code:
|
||||
raise HTTPFound(oauth_provider.get_oauth_url())
|
||||
|
||||
|
Reference in New Issue
Block a user