Compare commits

...

39 Commits

Author SHA1 Message Date
aac3ccfc30 migration to pkgctl 2023-05-09 15:31:50 +03:00
8c55438140 disable debugpanel for now 2023-05-04 14:30:27 +03:00
277d40d231 do not assign path in context manager as it is deprectated 2023-05-04 14:29:39 +03:00
2ff56965d9 PEP-673 use Self as return type for classmethods (#94)
* PEP-673 use Self as return type for classmethods

* add dummy test file

* remove python3.10 compat
2023-05-04 03:28:08 +03:00
9dc6d56a8d fix flapping tests for oauth
Original implementation sends requests to httpbin which sometimes might
not be available. With proposed changes we are blocking redirects and
just check request itself
2023-05-03 14:53:35 +03:00
d59a0629c5 PEP-561 complaint: add py.typed marker 2023-04-17 18:56:49 +03:00
31f47b8069 fix some pycharm warnings 2023-04-17 02:23:50 +03:00
e42ca95789 suppress status errors also if option is set 2023-04-15 05:05:55 +03:00
ff917281a2 add referencne to api docs to docs 2023-04-14 05:06:53 +03:00
f7e260e1f4 add reference to api documentation from main page 2023-04-12 04:22:49 +03:00
abf29083c4 PEP-585 complaint: remove type aliases (#93) 2023-04-12 04:09:28 +03:00
54d36ccf36 docs update 2023-04-10 21:13:43 +03:00
2cd61b0a20 Release 2.8.0 2023-04-09 13:06:54 +03:00
9503a9f2ae try to remove unknown packages from api 2023-04-06 18:14:36 +03:00
39fde7cd5f hide cookie key and salt from config output 2023-04-06 00:31:50 +03:00
58379e7bf3 optimize imports 2023-04-06 00:24:39 +03:00
3c068edf4f argument annootation update 2023-04-06 00:24:39 +03:00
1106ff6482 fix license url 2023-04-06 00:24:39 +03:00
e08ab2db10 extract schemas automatically from views 2023-04-06 00:24:39 +03:00
8f4a2547e8 use api generated docs instead of comments (#92) 2023-04-06 00:24:39 +03:00
7f5e541120 execute request in context methods instead of handling them each time
manually
2023-03-23 12:43:04 +02:00
ec0550a275 Release 2.7.1 2023-03-06 01:15:47 +02:00
df23be9269 gracefully terminate web server
In previous revisions server was terminated by itself, thus no lock or
socket was removed. In new version, graceful termination of the queue
has been added as well as server now handles singals
2023-03-06 01:13:41 +02:00
a8c40a6b87 replace InitializeException with InitializeError in docs 2023-03-02 11:07:59 +02:00
a274f91677 simplify login ttl processing 2023-02-24 16:52:55 +02:00
13faf66bdb add more validation rules 2023-02-23 15:18:56 +02:00
4fb9335df9 add ability to read cookie secret from config 2023-02-22 18:47:56 +02:00
d517d8bfbb Release 2.7.0 2023-02-20 03:05:08 +02:00
37e57c13c8 update dependencies before build (#91)
Old implementation has used add step in order to fetch dependencies,
which could lead to build errors in case if dependency list was updated.

New solution uses dependencies which are declared at current version and
fetch them (if required and if enabled) before update process.

Closes #90
2023-02-12 06:02:30 +03:00
19bb19e9f5 handle .gitignore file correctly in remote push trigger 2023-02-11 04:41:24 +02:00
3a4e8f4d97 mask mypy warning
The newest mypy produces the following warning:

src/ahriman/application/handlers/search.py:43: error: Non-overlapping identity check (left operand type: "Union[_DefaultFactory[Any], Literal[_MISSING_TYPE.MISSING]]", right operand type: "Type[List[Any]]")  [comparison-overlap]

which is more likely caused by updated dataclass models to protoocol (however decorators are still calllable). This commit masks problematic line from checking
2023-02-09 22:46:08 +02:00
4db8ad8e8d hide passwords and secrets from repo-config subcommand by default 2023-02-05 16:44:48 +02:00
117f096d41 note about local database update (see #85) 2023-01-31 14:47:58 +02:00
917ec48be5 handle architecture specific fields for dependencies
This change requires srcinfo at least 0.1.2 version. Unfortunatelly aur
api don't support architecture specific arrays for now, so we just leave
it as is

Closes #82
2023-01-31 14:34:09 +02:00
0a2ba4ae07 filter empty packages from database
In some cases (e.g. during addition of the package to build queue) we don't have
full information about package inself; in these cases we produce lines
with empty architecture, which duplicates normal ones.

This commit changes architecture column type to required and also
filters packages which don't have architecture set yet.

Closes #83
2023-01-30 17:57:13 +02:00
9d7f63e549 trim version from provides list
Closes #87
2023-01-30 17:27:34 +02:00
25eee9ca5e add ability to suppress http logging errors (#86) 2023-01-30 17:19:01 +02:00
5af84955ac calculate dependencies based on package information (#89) 2023-01-30 17:28:05 +03:00
d3ad4c3c08 remove debug line 2023-01-27 16:41:41 +02:00
314 changed files with 9923 additions and 6984 deletions

View File

@ -1 +1 @@
skips: ['B101', 'B105', 'B106', 'B404'] skips: ['B101', 'B104', 'B105', 'B106', 'B404']

View File

@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = http://repo.arcanis.me/$arch\nSigLevel = Never'
# refresh the image # refresh the image
pacman --noconfirm -Syu pacman --noconfirm -Syu
# main dependencies # main dependencies
pacman --noconfirm -Sy base-devel devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-setuptools python-srcinfo sudo pacman --noconfirm -Sy base-devel devtools-git-poc git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo sudo
# make dependencies # make dependencies
pacman --noconfirm -Sy python-build python-installer python-wheel pacman --noconfirm -Sy python-build python-installer python-wheel
# optional dependencies # optional dependencies
@ -18,7 +18,7 @@ if [[ -z $MINIMAL_INSTALL ]]; then
# VCS support # VCS support
pacman --noconfirm -Sy breezy darcs mercurial subversion pacman --noconfirm -Sy breezy darcs mercurial subversion
# web server # web server
pacman --noconfirm -Sy python-aioauth-client python-aiohttp python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-cryptography python-jinja pacman --noconfirm -Sy python-aioauth-client python-aiohttp python-aiohttp-apispec-git python-aiohttp-cors python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-cryptography python-jinja
# additional features # additional features
pacman --noconfirm -Sy gnupg python-boto3 rsync pacman --noconfirm -Sy gnupg python-boto3 rsync
fi fi
@ -52,9 +52,9 @@ if [[ -z $MINIMAL_INSTALL ]]; then
fi fi
# add the first package # add the first package
# the build itself does not really work in the container # the build itself does not really work in the container
sudo -u ahriman -- ahriman package-add --now yay sudo -u ahriman -- ahriman package-add --now ahriman
# check if package was actually installed # check if package was actually installed
test -n "$(find "/var/lib/ahriman/repository/x86_64" -name "yay*pkg*")" test -n "$(find "/var/lib/ahriman/repository/x86_64" -name "ahriman*pkg*")"
# run package check # run package check
sudo -u ahriman -- ahriman repo-update sudo -u ahriman -- ahriman repo-update
# stop web service lol # stop web service lol

View File

@ -4,7 +4,7 @@
set -ex set -ex
# install dependencies # install dependencies
pacman --noconfirm -Syu base-devel python-pip python-setuptools python-tox pacman --noconfirm -Syu base-devel python-setuptools python-tox
# run test and check targets # run test and check targets
make check tests make check tests

View File

@ -533,5 +533,5 @@ valid-metaclass-classmethod-first-arg=cls
# Exceptions that will emit a warning when being caught. Defaults to # Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception". # "BaseException, Exception".
overgeneral-exceptions=BaseException, overgeneral-exceptions=builtins.BaseException,
Exception builtins.Exception

View File

@ -90,7 +90,9 @@ Again, the most checks can be performed by `make check` command, though some add
self.instance_attribute = "" self.instance_attribute = ""
``` ```
* Type annotations are the must, even for local functions. * Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated.
* For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typinng.Optional` (e.g. `str | None` instead of `Optional[str]`).
* `classmethod` should always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead.
* Recommended order of function definitions in class: * Recommended order of function definitions in class:
```python ```python
@ -102,7 +104,7 @@ Again, the most checks can be performed by `make check` command, though some add
def property(self) -> Any: ... def property(self) -> Any: ...
@classmethod @classmethod
def class_method(cls: Type[Clazz]) -> Clazz: ... def class_method(cls) -> Self: ...
@staticmethod @staticmethod
def static_method() -> Any: ... def static_method() -> Any: ...
@ -156,6 +158,52 @@ Again, the most checks can be performed by `make check` command, though some add
* No global variable is allowed outside of `ahriman.version` module. `ahriman.core.context` is also special case. * No global variable is allowed outside of `ahriman.version` module. `ahriman.core.context` is also special case.
* Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent. * Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent.
* If your class writes anything to log, the `ahriman.core.log.LazyLogging` trait must be used. * If your class writes anything to log, the `ahriman.core.log.LazyLogging` trait must be used.
* Web API methods must be documented by using `aiohttp_apispec` library. Schema testing mostly should be implemented in related view class tests. Recommended example for documentation (excluding comments):
```python
import aiohttp_apispec
from marshmallow import Schema, fields
from ahriman.web.schemas.auth_schema import AuthSchema
from ahriman.web.schemas.error_schema import ErrorSchema
from ahriman.web.schemas.package_name_schema import PackageNameSchema
from ahriman.web.views.base import BaseView
class RequestSchema(Schema):
field = fields.String(metadata={"description": "Field description", "example": "foo"})
class ResponseSchema(Schema):
field = fields.String(required=True, metadata={"description": "Field description"})
class Foo(BaseView):
POST_PERMISSION = ...
@aiohttp_apispec.docs(
tags=["Tag"],
summary="Do foo",
description="Extended description of the method which does foo",
responses={
200: {"description": "Success response", "schema": ResponseSchema},
204: {"description": "Success response"}, # example without json schema response
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, # exception raised by this method
401: {"description": "Authorization required", "schema": ErrorSchema}, # should be always presented
403: {"description": "Access is forbidden", "schema": ErrorSchema}, # should be always presented
500: {"description": "Internal server error", "schema": ErrorSchema}, # should be always presented
},
security=[{"token": [POST_PERMISSION]}],
)
@aiohttp_apispec.cookies_schema(AuthSchema) # should be always presented
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.json_schema(RequestSchema(many=True))
async def post(self) -> None: ...
```
### Other checks ### Other checks

View File

@ -28,11 +28,11 @@ RUN useradd -m -d "/home/build" -s "/usr/bin/nologin" build && \
COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package" COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
## install package dependencies ## install package dependencies
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size ## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
RUN pacman --noconfirm -Sy devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-setuptools python-srcinfo && \ RUN pacman --noconfirm -Sy devtools-git-poc git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \
pacman --noconfirm -Sy python-build python-installer python-wheel && \ pacman --noconfirm -Sy python-build python-installer python-wheel && \
pacman --noconfirm -Sy breezy mercurial python-aiohttp python-boto3 python-cryptography python-jinja python-requests-unixsocket rsync subversion && \ pacman --noconfirm -Sy breezy mercurial python-aiohttp python-aiohttp-cors python-boto3 python-cryptography python-jinja python-requests-unixsocket rsync subversion && \
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-jinja2 python-aiohttp-debugtoolbar \ runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-jinja2 \
python-aiohttp-session python-aiohttp-security python-aiohttp-debugtoolbar python-aiohttp-session python-aiohttp-security
# cleanup unused # cleanup unused
RUN find "/var/cache/pacman/pkg" -type f -delete RUN find "/var/cache/pacman/pkg" -type f -delete

View File

@ -36,6 +36,6 @@ The application provides reasonable defaults which allow to use it out-of-box; h
## Live demos ## Live demos
* [Build status page](https://ahriman-demo.arcanis.me). You can log in as `demo` user by using `demo` password. However, you will not be able to run tasks. * [Build status page](https://ahriman-demo.arcanis.me). You can log in as `demo` user by using `demo` password. However, you will not be able to run tasks. [HTTP API documentation](https://ahriman-demo.arcanis.me/api-docs) is also available.
* [Repository index](http://repo.arcanis.me/x86_64/index.html). * [Repository index](http://repo.arcanis.me/x86_64/index.html).
* [Telegram feed](https://t.me/arcanisrepo). * [Telegram feed](https://t.me/arcanisrepo).

View File

@ -36,7 +36,6 @@ AHRIMAN_GNUPG_HOME="$(getent passwd "$AHRIMAN_USER" | cut -d : -f 6)/.gnupg"
chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_GNUPG_HOME" chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_GNUPG_HOME"
# run built-in setup command # run built-in setup command
AHRIMAN_SETUP_ARGS=("--build-as-user" "$AHRIMAN_USER")
AHRIMAN_SETUP_ARGS+=("--packager" "$AHRIMAN_PACKAGER") AHRIMAN_SETUP_ARGS+=("--packager" "$AHRIMAN_PACKAGER")
AHRIMAN_SETUP_ARGS+=("--repository" "$AHRIMAN_REPOSITORY") AHRIMAN_SETUP_ARGS+=("--repository" "$AHRIMAN_REPOSITORY")
if [ -z "$AHRIMAN_MULTILIB" ]; then if [ -z "$AHRIMAN_MULTILIB" ]; then

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 673 KiB

After

Width:  |  Height:  |  Size: 750 KiB

View File

@ -1,4 +1,4 @@
.TH AHRIMAN "1" "2023\-01\-25" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2023\-04\-09" "ahriman" "Generated Python Manual"
.SH NAME .SH NAME
ahriman ahriman
.SH SYNOPSIS .SH SYNOPSIS
@ -156,7 +156,7 @@ web server
.SH COMMAND \fI\,'ahriman aur\-search'\/\fR .SH COMMAND \fI\,'ahriman aur\-search'\/\fR
usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info] usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info]
[\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,url,url_path,version}] [\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}]
search [search ...] search [search ...]
search for package in AUR using API search for package in AUR using API
@ -175,7 +175,7 @@ return non\-zero exit status if result is empty
show additional package information (default: False) show additional package information (default: False)
.TP .TP
\fB\-\-sort\-by\fR \fI\,{description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,url,url_path,version}\/\fR \fB\-\-sort\-by\fR \fI\,{description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}\/\fR
sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted 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 by name
@ -215,8 +215,8 @@ usage: ahriman help\-version [\-h]
print application and its dependencies versions print application and its dependencies versions
.SH COMMAND \fI\,'ahriman package\-add'\/\fR .SH COMMAND \fI\,'ahriman package\-add'\/\fR
usage: ahriman package\-add [\-h] [\-e] [\-n] [\-y] [\-s {auto,archive,aur,directory,local,remote,repository}] usage: ahriman package\-add [\-h] [\-\-dependencies | \-\-no\-dependencies] [\-e] [\-n] [\-y]
[\-\-without\-dependencies] [\-s {auto,archive,aur,directory,local,remote,repository}]
package [package ...] package [package ...]
add existing or new package to the build queue add existing or new package to the build queue
@ -226,6 +226,10 @@ add existing or new package to the build queue
package source (base name, path to local files, remote URL) package source (base name, path to local files, remote URL)
.SH OPTIONS \fI\,'ahriman package\-add'\/\fR .SH OPTIONS \fI\,'ahriman package\-add'\/\fR
.TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies (default: True)
.TP .TP
\fB\-e\fR, \fB\-\-exit\-code\fR \fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty return non\-zero exit status if result is empty
@ -242,10 +246,6 @@ download fresh package databases from the mirror before actions, \-yy to force r
\fB\-s\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR, \fB\-\-source\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR \fB\-s\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR, \fB\-\-source\fR \fI\,{auto,archive,aur,directory,local,remote,repository}\/\fR
explicitly specify the package source for this command explicitly specify the package source for this command
.TP
\fB\-\-without\-dependencies\fR
do not add dependencies
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-remove'\/\fR
usage: ahriman package\-remove [\-h] package [package ...] usage: ahriman package\-remove [\-h] package [package ...]
@ -401,8 +401,8 @@ fetch actual version of VCS packages (default: True)
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR .SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-dependencies | \-\-no\-dependencies]
[\-\-vcs | \-\-no\-vcs] [\-y] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-vcs | \-\-no\-vcs] [\-y]
start process which periodically will run update process start process which periodically will run update process
@ -415,6 +415,10 @@ interval between runs in seconds
\fB\-\-aur\fR, \fB\-\-no\-aur\fR \fB\-\-aur\fR, \fB\-\-no\-aur\fR
enable or disable checking for AUR updates (default: True) enable or disable checking for AUR updates (default: True)
.TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies (default: True)
.TP .TP
\fB\-\-local\fR, \fB\-\-no\-local\fR \fB\-\-local\fR, \fB\-\-no\-local\fR
enable or disable checking of local packages for updates (default: True) enable or disable checking of local packages for updates (default: True)
@ -523,8 +527,8 @@ run triggers on empty build result as configured by settings
instead of running all triggers as set by configuration, just process specified ones in order of mention instead of running all triggers as set by configuration, just process specified ones in order of mention
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR .SH COMMAND \fI\,'ahriman repo\-update'\/\fR
usage: ahriman repo\-update [\-h] [\-\-dry\-run] [\-e] [\-\-aur | \-\-no\-aur] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e]
[\-\-vcs | \-\-no\-vcs] [\-y] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-vcs | \-\-no\-vcs] [\-y]
[package ...] [package ...]
check for packages updates and run build process if requested check for packages updates and run build process if requested
@ -534,6 +538,14 @@ check for packages updates and run build process if requested
filter check by package base filter check by package base
.SH OPTIONS \fI\,'ahriman repo\-update'\/\fR .SH OPTIONS \fI\,'ahriman repo\-update'\/\fR
.TP
\fB\-\-aur\fR, \fB\-\-no\-aur\fR
enable or disable checking for AUR updates (default: True)
.TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies (default: True)
.TP .TP
\fB\-\-dry\-run\fR \fB\-\-dry\-run\fR
just perform check for updates, same as check command just perform check for updates, same as check command
@ -542,10 +554,6 @@ just perform check for updates, same as check command
\fB\-e\fR, \fB\-\-exit\-code\fR \fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty return non\-zero exit status if result is empty
.TP
\fB\-\-aur\fR, \fB\-\-no\-aur\fR
enable or disable checking for AUR updates (default: True)
.TP .TP
\fB\-\-local\fR, \fB\-\-no\-local\fR \fB\-\-local\fR, \fB\-\-no\-local\fR
enable or disable checking of local packages for updates (default: True) enable or disable checking of local packages for updates (default: True)
@ -590,10 +598,15 @@ clear directory with built packages (default: False)
clear directory with pacman local database cache (default: False) clear directory with pacman local database cache (default: False)
.SH COMMAND \fI\,'ahriman service\-config'\/\fR .SH COMMAND \fI\,'ahriman service\-config'\/\fR
usage: ahriman service\-config [\-h] usage: ahriman service\-config [\-h] [\-\-secure | \-\-no\-secure]
dump configuration for the specified architecture dump configuration for the specified architecture
.SH OPTIONS \fI\,'ahriman service\-config'\/\fR
.TP
\fB\-\-secure\fR, \fB\-\-no\-secure\fR
hide passwords and secrets from output (default: True)
.SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR .SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR
usage: ahriman service\-config\-validate [\-h] [\-e] usage: ahriman service\-config\-validate [\-h] [\-e]

View File

@ -1,45 +0,0 @@
ahriman.core.database.data package
==================================
Submodules
----------
ahriman.core.database.data.package\_remotes module
--------------------------------------------------
.. automodule:: ahriman.core.database.data.package_remotes
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.data.package\_statuses module
---------------------------------------------------
.. automodule:: ahriman.core.database.data.package_statuses
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.data.patches module
-----------------------------------------
.. automodule:: ahriman.core.database.data.patches
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.data.users module
---------------------------------------
.. automodule:: ahriman.core.database.data.users
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.database.data
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -44,6 +44,22 @@ ahriman.core.database.migrations.m004\_logs module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.migrations.m005\_make\_opt\_depends module
----------------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m005_make_opt_depends
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.migrations.m006\_packages\_architecture\_required module
------------------------------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m006_packages_architecture_required
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -7,7 +7,6 @@ Subpackages
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4
ahriman.core.database.data
ahriman.core.database.migrations ahriman.core.database.migrations
ahriman.core.database.operations ahriman.core.database.operations

View File

@ -196,14 +196,6 @@ ahriman.models.user\_access module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.models.user\_identity module
------------------------------------
.. automodule:: ahriman.models.user_identity
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -8,11 +8,28 @@ Subpackages
:maxdepth: 4 :maxdepth: 4
ahriman.web.middlewares ahriman.web.middlewares
ahriman.web.schemas
ahriman.web.views ahriman.web.views
Submodules Submodules
---------- ----------
ahriman.web.apispec module
--------------------------
.. automodule:: ahriman.web.apispec
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.cors module
-----------------------
.. automodule:: ahriman.web.cors
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.routes module ahriman.web.routes module
------------------------- -------------------------

View File

@ -0,0 +1,165 @@
ahriman.web.schemas package
===========================
Submodules
----------
ahriman.web.schemas.aur\_package\_schema module
-----------------------------------------------
.. automodule:: ahriman.web.schemas.aur_package_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.auth\_schema module
---------------------------------------
.. automodule:: ahriman.web.schemas.auth_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.counters\_schema module
-------------------------------------------
.. automodule:: ahriman.web.schemas.counters_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.error\_schema module
----------------------------------------
.. automodule:: ahriman.web.schemas.error_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.internal\_status\_schema module
---------------------------------------------------
.. automodule:: ahriman.web.schemas.internal_status_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.log\_schema module
--------------------------------------
.. automodule:: ahriman.web.schemas.log_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.login\_schema module
----------------------------------------
.. automodule:: ahriman.web.schemas.login_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.logs\_schema module
---------------------------------------
.. automodule:: ahriman.web.schemas.logs_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.oauth2\_schema module
-----------------------------------------
.. automodule:: ahriman.web.schemas.oauth2_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.package\_name\_schema module
------------------------------------------------
.. automodule:: ahriman.web.schemas.package_name_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.package\_names\_schema module
-------------------------------------------------
.. automodule:: ahriman.web.schemas.package_names_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.package\_properties\_schema module
------------------------------------------------------
.. automodule:: ahriman.web.schemas.package_properties_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.package\_schema module
------------------------------------------
.. automodule:: ahriman.web.schemas.package_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.package\_status\_schema module
--------------------------------------------------
.. automodule:: ahriman.web.schemas.package_status_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.pgp\_key\_id\_schema module
-----------------------------------------------
.. automodule:: ahriman.web.schemas.pgp_key_id_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.pgp\_key\_schema module
-------------------------------------------
.. automodule:: ahriman.web.schemas.pgp_key_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.remote\_schema module
-----------------------------------------
.. automodule:: ahriman.web.schemas.remote_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.search\_schema module
-----------------------------------------
.. automodule:: ahriman.web.schemas.search_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.status\_schema module
-----------------------------------------
.. automodule:: ahriman.web.schemas.status_schema
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.schemas
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -0,0 +1,29 @@
ahriman.web.views.api package
=============================
Submodules
----------
ahriman.web.views.api.docs module
---------------------------------
.. automodule:: ahriman.web.views.api.docs
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.api.swagger module
------------------------------------
.. automodule:: ahriman.web.views.api.swagger
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.api
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -7,6 +7,7 @@ Subpackages
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4
ahriman.web.views.api
ahriman.web.views.service ahriman.web.views.service
ahriman.web.views.status ahriman.web.views.status
ahriman.web.views.user ahriman.web.views.user

View File

@ -114,14 +114,14 @@ Schema and data migrations
The schema migration are applied according to current ``pragma user_info`` values, located at ``ahriman.core.database.migrations`` package and named as ``m000_migration_name.py`` (the preceding ``m`` is required in order to import migration content for tests). Additional class ``ahriman.core.database.migrations.Migrations`` reads all migrations automatically and applies them in alphabetical order. The schema migration are applied according to current ``pragma user_info`` values, located at ``ahriman.core.database.migrations`` package and named as ``m000_migration_name.py`` (the preceding ``m`` is required in order to import migration content for tests). Additional class ``ahriman.core.database.migrations.Migrations`` reads all migrations automatically and applies them in alphabetical order.
There are also data migrations which are located at ``ahriman.core.database.data`` package and move data from old-style (e.g. json files in filesystem, directory trees, etc) to the database. They are also part of migration and (unlike schema migrations) are applied only at specific version breakpoints (e.g. if ``user_version`` is more than 0 no initial migration will be applied). These migrations also contain data migrations. Though the recommended way is to migrate data directly from SQL requests, sometimes it is required to have external data (like packages list) in order to set correct data. To do so, special method `migrate_data` is used.
Type conversions Type conversions
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
By default, it parses rows into python dictionary. In addition, the following pseudo-types are supported: By default, it parses rows into python dictionary. In addition, the following pseudo-types are supported:
* ``Dict[str, Any]``, ``List[Any]`` - for storing JSON data structures in database (technically there is no restriction on types for dictionary keys and values, but it is recommended to use only string keys). The type is stored as ``json`` data type and ``json.loads`` and ``json.dumps`` methods are used in order to read and write from/to database respectively. * ``dict[str, Any]``, ``list[Any]`` - for storing JSON data structures in database (technically there is no restriction on types for dictionary keys and values, but it is recommended to use only string keys). The type is stored as ``json`` data type and ``json.loads`` and ``json.dumps`` methods are used in order to read and write from/to database respectively.
Basic flows Basic flows
----------- -----------
@ -235,7 +235,7 @@ Triggers are extensions which can be used in order to perform any actions on app
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it. The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it.
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``List[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs. The loaded triggers will be called with ``ahriman.models.result.Result`` and ``list[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
In addition triggers can implement ``on_start`` and ``on_stop`` actions which will be called on the application start and right before the application exit. The ``on_start`` action is usually being called from handlers directly in order to make sure that no trigger will be run when it is not required (e.g. on user management). As soon as ``on_start`` action is called, the additional flag will be set; ``ahriman.core.triggers.TriggerLoader`` class implements ``__del__`` method in which, if the flag is set, the ``on_stop`` actions will be called. In addition triggers can implement ``on_start`` and ``on_stop`` actions which will be called on the application start and right before the application exit. The ``on_start`` action is usually being called from handlers directly in order to make sure that no trigger will be run when it is not required (e.g. on user management). As soon as ``on_start`` action is called, the additional flag will be set; ``ahriman.core.triggers.TriggerLoader`` class implements ``__del__`` method in which, if the flag is set, the ``on_stop`` actions will be called.
@ -269,6 +269,7 @@ Web application
Web application requires the following python packages to be installed: Web application requires the following python packages to be installed:
* Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates). * Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates).
* Additional web features also require ``aiohttp-apispec`` (autogenerated documentation), ``aiohttp_cors`` (CORS support, required by documentation)
* In addition, ``aiohttp_debugtoolbar`` is required for debug panel. Please note that this option does not work together with authorization and basically must not be used in production. * In addition, ``aiohttp_debugtoolbar`` is required for debug panel. Please note that this option does not work together with authorization and basically must not be used in production.
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``. * In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library. * In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
@ -279,6 +280,13 @@ Middlewares
Service provides some custom middlewares, e.g. logging every exception (except for user ones) and user authorization. Service provides some custom middlewares, e.g. logging every exception (except for user ones) and user authorization.
HEAD and OPTIONS requests
^^^^^^^^^^^^^^^^^^^^^^^^^
``HEAD`` request is automatically generated by ``ahriman.web.views.base.BaseView`` class. It just calls ``GET`` method, removes any data from body and returns the result. In case if no ``GET`` method available for this view, the ``aiohttp.web.HTTPMethodNotAllowed`` exception will be raised.
On the other side, ``OPTIONS`` method is implemented in the ``ahriman.web.middlewares.exception_handler.exception_handler`` middleware. In case if ``aiohttp.web.HTTPMethodNotAllowed`` exception is raised and original method was ``OPTIONS``, the middleware handles it, converts to valid request and returns response to user.
Web views Web views
^^^^^^^^^ ^^^^^^^^^
@ -288,6 +296,7 @@ REST API supports both form and JSON data, but the last one is recommended.
Different APIs are separated into different packages: Different APIs are separated into different packages:
* ``ahriman.web.views.api`` not a real API, but some views which provide OpenAPI support.
* ``ahriman.web.views.service`` provides views for application controls. * ``ahriman.web.views.service`` provides views for application controls.
* ``ahriman.web.views.status`` package provides REST API for application reporting. * ``ahriman.web.views.status`` package provides REST API for application reporting.
* ``ahriman.web.views.user`` package provides login and logout methods which can be called without authorization. * ``ahriman.web.views.user`` package provides login and logout methods which can be called without authorization.

View File

@ -10,9 +10,9 @@ _shtab_ahriman_help_commands_unsafe_option_strings=('-h' '--help' '--command')
_shtab_ahriman_help_updates_option_strings=('-h' '--help' '-e' '--exit-code') _shtab_ahriman_help_updates_option_strings=('-h' '--help' '-e' '--exit-code')
_shtab_ahriman_help_version_option_strings=('-h' '--help') _shtab_ahriman_help_version_option_strings=('-h' '--help')
_shtab_ahriman_version_option_strings=('-h' '--help') _shtab_ahriman_version_option_strings=('-h' '--help')
_shtab_ahriman_package_add_option_strings=('-h' '--help' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '--without-dependencies') _shtab_ahriman_package_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source')
_shtab_ahriman_add_option_strings=('-h' '--help' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '--without-dependencies') _shtab_ahriman_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source')
_shtab_ahriman_package_update_option_strings=('-h' '--help' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source' '--without-dependencies') _shtab_ahriman_package_update_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '-n' '--now' '-y' '--refresh' '-s' '--source')
_shtab_ahriman_package_remove_option_strings=('-h' '--help') _shtab_ahriman_package_remove_option_strings=('-h' '--help')
_shtab_ahriman_remove_option_strings=('-h' '--help') _shtab_ahriman_remove_option_strings=('-h' '--help')
_shtab_ahriman_package_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status') _shtab_ahriman_package_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status')
@ -27,8 +27,8 @@ _shtab_ahriman_patch_set_add_option_strings=('-h' '--help' '-t' '--track')
_shtab_ahriman_repo_backup_option_strings=('-h' '--help') _shtab_ahriman_repo_backup_option_strings=('-h' '--help')
_shtab_ahriman_repo_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code') _shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code')
_shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code') _shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '-e' '--exit-code')
_shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run') _shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run')
@ -43,14 +43,14 @@ _shtab_ahriman_repo_sync_option_strings=('-h' '--help')
_shtab_ahriman_sync_option_strings=('-h' '--help') _shtab_ahriman_sync_option_strings=('-h' '--help')
_shtab_ahriman_repo_tree_option_strings=('-h' '--help') _shtab_ahriman_repo_tree_option_strings=('-h' '--help')
_shtab_ahriman_repo_triggers_option_strings=('-h' '--help') _shtab_ahriman_repo_triggers_option_strings=('-h' '--help')
_shtab_ahriman_repo_update_option_strings=('-h' '--help' '--dry-run' '-e' '--exit-code' '--aur' '--no-aur' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_update_option_strings=('-h' '--help' '--dry-run' '-e' '--exit-code' '--aur' '--no-aur' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman') _shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
_shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman') _shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
_shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman') _shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
_shtab_ahriman_service_config_option_strings=('-h' '--help') _shtab_ahriman_service_config_option_strings=('-h' '--help' '--secure' '--no-secure')
_shtab_ahriman_config_option_strings=('-h' '--help') _shtab_ahriman_config_option_strings=('-h' '--help' '--secure' '--no-secure')
_shtab_ahriman_repo_config_option_strings=('-h' '--help') _shtab_ahriman_repo_config_option_strings=('-h' '--help' '--secure' '--no-secure')
_shtab_ahriman_service_config_validate_option_strings=('-h' '--help' '-e' '--exit-code') _shtab_ahriman_service_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
_shtab_ahriman_config_validate_option_strings=('-h' '--help' '-e' '--exit-code') _shtab_ahriman_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
_shtab_ahriman_repo_config_validate_option_strings=('-h' '--help' '-e' '--exit-code') _shtab_ahriman_repo_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
@ -71,8 +71,8 @@ _shtab_ahriman_web_option_strings=('-h' '--help')
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web') _shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'url' 'url_path' 'version') _shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
_shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'url' 'url_path' 'version') _shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
_shtab_ahriman_package_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository') _shtab_ahriman_package_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
_shtab_ahriman_package_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository') _shtab_ahriman_package_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
_shtab_ahriman_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository') _shtab_ahriman_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
@ -139,33 +139,36 @@ _shtab_ahriman_version___help_nargs=0
_shtab_ahriman_package_add_pos_0_nargs=+ _shtab_ahriman_package_add_pos_0_nargs=+
_shtab_ahriman_package_add__h_nargs=0 _shtab_ahriman_package_add__h_nargs=0
_shtab_ahriman_package_add___help_nargs=0 _shtab_ahriman_package_add___help_nargs=0
_shtab_ahriman_package_add___dependencies_nargs=0
_shtab_ahriman_package_add___no_dependencies_nargs=0
_shtab_ahriman_package_add__e_nargs=0 _shtab_ahriman_package_add__e_nargs=0
_shtab_ahriman_package_add___exit_code_nargs=0 _shtab_ahriman_package_add___exit_code_nargs=0
_shtab_ahriman_package_add__n_nargs=0 _shtab_ahriman_package_add__n_nargs=0
_shtab_ahriman_package_add___now_nargs=0 _shtab_ahriman_package_add___now_nargs=0
_shtab_ahriman_package_add__y_nargs=0 _shtab_ahriman_package_add__y_nargs=0
_shtab_ahriman_package_add___refresh_nargs=0 _shtab_ahriman_package_add___refresh_nargs=0
_shtab_ahriman_package_add___without_dependencies_nargs=0
_shtab_ahriman_add_pos_0_nargs=+ _shtab_ahriman_add_pos_0_nargs=+
_shtab_ahriman_add__h_nargs=0 _shtab_ahriman_add__h_nargs=0
_shtab_ahriman_add___help_nargs=0 _shtab_ahriman_add___help_nargs=0
_shtab_ahriman_add___dependencies_nargs=0
_shtab_ahriman_add___no_dependencies_nargs=0
_shtab_ahriman_add__e_nargs=0 _shtab_ahriman_add__e_nargs=0
_shtab_ahriman_add___exit_code_nargs=0 _shtab_ahriman_add___exit_code_nargs=0
_shtab_ahriman_add__n_nargs=0 _shtab_ahriman_add__n_nargs=0
_shtab_ahriman_add___now_nargs=0 _shtab_ahriman_add___now_nargs=0
_shtab_ahriman_add__y_nargs=0 _shtab_ahriman_add__y_nargs=0
_shtab_ahriman_add___refresh_nargs=0 _shtab_ahriman_add___refresh_nargs=0
_shtab_ahriman_add___without_dependencies_nargs=0
_shtab_ahriman_package_update_pos_0_nargs=+ _shtab_ahriman_package_update_pos_0_nargs=+
_shtab_ahriman_package_update__h_nargs=0 _shtab_ahriman_package_update__h_nargs=0
_shtab_ahriman_package_update___help_nargs=0 _shtab_ahriman_package_update___help_nargs=0
_shtab_ahriman_package_update___dependencies_nargs=0
_shtab_ahriman_package_update___no_dependencies_nargs=0
_shtab_ahriman_package_update__e_nargs=0 _shtab_ahriman_package_update__e_nargs=0
_shtab_ahriman_package_update___exit_code_nargs=0 _shtab_ahriman_package_update___exit_code_nargs=0
_shtab_ahriman_package_update__n_nargs=0 _shtab_ahriman_package_update__n_nargs=0
_shtab_ahriman_package_update___now_nargs=0 _shtab_ahriman_package_update___now_nargs=0
_shtab_ahriman_package_update__y_nargs=0 _shtab_ahriman_package_update__y_nargs=0
_shtab_ahriman_package_update___refresh_nargs=0 _shtab_ahriman_package_update___refresh_nargs=0
_shtab_ahriman_package_update___without_dependencies_nargs=0
_shtab_ahriman_package_remove_pos_0_nargs=+ _shtab_ahriman_package_remove_pos_0_nargs=+
_shtab_ahriman_package_remove__h_nargs=0 _shtab_ahriman_package_remove__h_nargs=0
_shtab_ahriman_package_remove___help_nargs=0 _shtab_ahriman_package_remove___help_nargs=0
@ -231,6 +234,8 @@ _shtab_ahriman_repo_daemon__h_nargs=0
_shtab_ahriman_repo_daemon___help_nargs=0 _shtab_ahriman_repo_daemon___help_nargs=0
_shtab_ahriman_repo_daemon___aur_nargs=0 _shtab_ahriman_repo_daemon___aur_nargs=0
_shtab_ahriman_repo_daemon___no_aur_nargs=0 _shtab_ahriman_repo_daemon___no_aur_nargs=0
_shtab_ahriman_repo_daemon___dependencies_nargs=0
_shtab_ahriman_repo_daemon___no_dependencies_nargs=0
_shtab_ahriman_repo_daemon___local_nargs=0 _shtab_ahriman_repo_daemon___local_nargs=0
_shtab_ahriman_repo_daemon___no_local_nargs=0 _shtab_ahriman_repo_daemon___no_local_nargs=0
_shtab_ahriman_repo_daemon___manual_nargs=0 _shtab_ahriman_repo_daemon___manual_nargs=0
@ -243,6 +248,8 @@ _shtab_ahriman_daemon__h_nargs=0
_shtab_ahriman_daemon___help_nargs=0 _shtab_ahriman_daemon___help_nargs=0
_shtab_ahriman_daemon___aur_nargs=0 _shtab_ahriman_daemon___aur_nargs=0
_shtab_ahriman_daemon___no_aur_nargs=0 _shtab_ahriman_daemon___no_aur_nargs=0
_shtab_ahriman_daemon___dependencies_nargs=0
_shtab_ahriman_daemon___no_dependencies_nargs=0
_shtab_ahriman_daemon___local_nargs=0 _shtab_ahriman_daemon___local_nargs=0
_shtab_ahriman_daemon___no_local_nargs=0 _shtab_ahriman_daemon___no_local_nargs=0
_shtab_ahriman_daemon___manual_nargs=0 _shtab_ahriman_daemon___manual_nargs=0
@ -295,11 +302,13 @@ _shtab_ahriman_repo_triggers___help_nargs=0
_shtab_ahriman_repo_update_pos_0_nargs=* _shtab_ahriman_repo_update_pos_0_nargs=*
_shtab_ahriman_repo_update__h_nargs=0 _shtab_ahriman_repo_update__h_nargs=0
_shtab_ahriman_repo_update___help_nargs=0 _shtab_ahriman_repo_update___help_nargs=0
_shtab_ahriman_repo_update___aur_nargs=0
_shtab_ahriman_repo_update___no_aur_nargs=0
_shtab_ahriman_repo_update___dependencies_nargs=0
_shtab_ahriman_repo_update___no_dependencies_nargs=0
_shtab_ahriman_repo_update___dry_run_nargs=0 _shtab_ahriman_repo_update___dry_run_nargs=0
_shtab_ahriman_repo_update__e_nargs=0 _shtab_ahriman_repo_update__e_nargs=0
_shtab_ahriman_repo_update___exit_code_nargs=0 _shtab_ahriman_repo_update___exit_code_nargs=0
_shtab_ahriman_repo_update___aur_nargs=0
_shtab_ahriman_repo_update___no_aur_nargs=0
_shtab_ahriman_repo_update___local_nargs=0 _shtab_ahriman_repo_update___local_nargs=0
_shtab_ahriman_repo_update___no_local_nargs=0 _shtab_ahriman_repo_update___no_local_nargs=0
_shtab_ahriman_repo_update___manual_nargs=0 _shtab_ahriman_repo_update___manual_nargs=0
@ -311,11 +320,13 @@ _shtab_ahriman_repo_update___refresh_nargs=0
_shtab_ahriman_update_pos_0_nargs=* _shtab_ahriman_update_pos_0_nargs=*
_shtab_ahriman_update__h_nargs=0 _shtab_ahriman_update__h_nargs=0
_shtab_ahriman_update___help_nargs=0 _shtab_ahriman_update___help_nargs=0
_shtab_ahriman_update___aur_nargs=0
_shtab_ahriman_update___no_aur_nargs=0
_shtab_ahriman_update___dependencies_nargs=0
_shtab_ahriman_update___no_dependencies_nargs=0
_shtab_ahriman_update___dry_run_nargs=0 _shtab_ahriman_update___dry_run_nargs=0
_shtab_ahriman_update__e_nargs=0 _shtab_ahriman_update__e_nargs=0
_shtab_ahriman_update___exit_code_nargs=0 _shtab_ahriman_update___exit_code_nargs=0
_shtab_ahriman_update___aur_nargs=0
_shtab_ahriman_update___no_aur_nargs=0
_shtab_ahriman_update___local_nargs=0 _shtab_ahriman_update___local_nargs=0
_shtab_ahriman_update___no_local_nargs=0 _shtab_ahriman_update___no_local_nargs=0
_shtab_ahriman_update___manual_nargs=0 _shtab_ahriman_update___manual_nargs=0
@ -362,10 +373,16 @@ _shtab_ahriman_repo_clean___pacman_nargs=0
_shtab_ahriman_repo_clean___no_pacman_nargs=0 _shtab_ahriman_repo_clean___no_pacman_nargs=0
_shtab_ahriman_service_config__h_nargs=0 _shtab_ahriman_service_config__h_nargs=0
_shtab_ahriman_service_config___help_nargs=0 _shtab_ahriman_service_config___help_nargs=0
_shtab_ahriman_service_config___secure_nargs=0
_shtab_ahriman_service_config___no_secure_nargs=0
_shtab_ahriman_config__h_nargs=0 _shtab_ahriman_config__h_nargs=0
_shtab_ahriman_config___help_nargs=0 _shtab_ahriman_config___help_nargs=0
_shtab_ahriman_config___secure_nargs=0
_shtab_ahriman_config___no_secure_nargs=0
_shtab_ahriman_repo_config__h_nargs=0 _shtab_ahriman_repo_config__h_nargs=0
_shtab_ahriman_repo_config___help_nargs=0 _shtab_ahriman_repo_config___help_nargs=0
_shtab_ahriman_repo_config___secure_nargs=0
_shtab_ahriman_repo_config___no_secure_nargs=0
_shtab_ahriman_service_config_validate__h_nargs=0 _shtab_ahriman_service_config_validate__h_nargs=0
_shtab_ahriman_service_config_validate___help_nargs=0 _shtab_ahriman_service_config_validate___help_nargs=0
_shtab_ahriman_service_config_validate__e_nargs=0 _shtab_ahriman_service_config_validate__e_nargs=0

View File

@ -87,11 +87,11 @@ _shtab_ahriman_options=(
_shtab_ahriman_add_options=( _shtab_ahriman_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-n,--now}"[run update function after]" {-n,--now}"[run update function after]"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)" {-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
"--without-dependencies[do not add dependencies]"
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
@ -99,7 +99,7 @@ _shtab_ahriman_aur_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:" {--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:"
"--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository url url_path version)" "--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository submitter url url_path version)"
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
@ -122,6 +122,7 @@ _shtab_ahriman_clean_options=(
_shtab_ahriman_config_options=( _shtab_ahriman_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--secure,--no-secure}"[hide passwords and secrets from output (default\: \%(default)s)]:secure:"
) )
_shtab_ahriman_config_validate_options=( _shtab_ahriman_config_validate_options=(
@ -133,6 +134,7 @@ _shtab_ahriman_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds]:interval:" {-i,--interval}"[interval between runs in seconds]:interval:"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:"
@ -182,11 +184,11 @@ _shtab_ahriman_key_import_options=(
_shtab_ahriman_package_add_options=( _shtab_ahriman_package_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-n,--now}"[run update function after]" {-n,--now}"[run update function after]"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)" {-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
"--without-dependencies[do not add dependencies]"
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
@ -217,11 +219,11 @@ _shtab_ahriman_package_status_update_options=(
_shtab_ahriman_package_update_options=( _shtab_ahriman_package_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{-n,--now}"[run update function after]" {-n,--now}"[run update function after]"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date]"
{-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)" {-s,--source}"[explicitly specify the package source for this command]:source:(auto archive aur directory local remote repository)"
"--without-dependencies[do not add dependencies]"
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
@ -293,6 +295,7 @@ _shtab_ahriman_repo_clean_options=(
_shtab_ahriman_repo_config_options=( _shtab_ahriman_repo_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--secure,--no-secure}"[hide passwords and secrets from output (default\: \%(default)s)]:secure:"
) )
_shtab_ahriman_repo_config_validate_options=( _shtab_ahriman_repo_config_validate_options=(
@ -304,6 +307,7 @@ _shtab_ahriman_repo_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds]:interval:" {-i,--interval}"[interval between runs in seconds]:interval:"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:"
@ -390,9 +394,10 @@ _shtab_ahriman_repo_triggers_options=(
_shtab_ahriman_repo_update_options=( _shtab_ahriman_repo_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:"
"--dry-run[just perform check for updates, same as check command]" "--dry-run[just perform check for updates, same as check command]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:"
@ -408,7 +413,7 @@ _shtab_ahriman_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:" {--info,--no-info}"[show additional package information (default\: \%(default)s)]:info:"
"--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository url url_path version)" "--sort-by[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]:sort_by:(description first_submitted id last_modified maintainer name num_votes out_of_date package_base package_base_id popularity repository submitter url url_path version)"
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
@ -423,6 +428,7 @@ _shtab_ahriman_service_clean_options=(
_shtab_ahriman_service_config_options=( _shtab_ahriman_service_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--secure,--no-secure}"[hide passwords and secrets from output (default\: \%(default)s)]:secure:"
) )
_shtab_ahriman_service_config_validate_options=( _shtab_ahriman_service_config_validate_options=(
@ -504,9 +510,10 @@ _shtab_ahriman_sync_options=(
_shtab_ahriman_update_options=( _shtab_ahriman_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: \%(default)s)]:dependencies:"
"--dry-run[just perform check for updates, same as check command]" "--dry-run[just perform check for updates, same as check command]"
{-e,--exit-code}"[return non-zero exit status if result is empty]" {-e,--exit-code}"[return non-zero exit status if result is empty]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: \%(default)s)]:aur:"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates (default\: \%(default)s)]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:" {--manual,--no-manual}"[include or exclude manual updates (default\: \%(default)s)]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: \%(default)s)]:vcs:"

View File

@ -28,6 +28,7 @@ Base configuration settings.
* ``include`` - path to directory with configuration files overrides, string, required. * ``include`` - path to directory with configuration files overrides, string, required.
* ``database`` - path to SQLite database, string, required. * ``database`` - path to SQLite database, string, required.
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference. * ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
* ``suppress_http_log_errors`` - suppress http log errors, boolean, optional, default ``no``. If set to ``yes``, any http log errors (e.g. if web server is not available, but http logging is enabled) will be suppressed.
``alpm`` group ``alpm`` group
-------------- --------------
@ -49,6 +50,7 @@ Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` libr
* ``allow_read_only`` - allow requesting status APIs without authorization, boolean, required. * ``allow_read_only`` - allow requesting status APIs without authorization, boolean, required.
* ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used. * ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used.
* ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used. * ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used.
* ``cookie_secret_key`` - secret key which will be used for cookies encryption, string, optional. It must be 32 url-safe base64-encoded bytes and can be generated as following ``base64.urlsafe_b64encode(os.urandom(32)).decode("utf8")``. If not set, it will be generated automatically; note, however, that in this case, all sessions will be automatically expired during restart.
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days. * ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used. * ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used. * ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
@ -61,13 +63,11 @@ Authorized users are stored inside internal database, if any of external provide
Build related configuration. Group name can refer to architecture, e.g. ``build:x86_64`` can be used for x86_64 architecture specific settings. Build related configuration. Group name can refer to architecture, e.g. ``build:x86_64`` can be used for x86_64 architecture specific settings.
* ``archbuild_flags`` - additional flags passed to ``archbuild`` command, space separated list of strings, optional.
* ``build_command`` - default build command, string, required. * ``build_command`` - default build command, string, required.
* ``build_flags`` - additional ``pkgctl`` flags, space separated list of strings, optional.
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional. * ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
* ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of mention. * ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of mention.
* ``vcs_allowed_age`` - maximal age in seconds of the VCS packages before their version will be updated with its remote source, int, optional, default ``0``. * ``vcs_allowed_age`` - maximal age in seconds of the VCS packages before their version will be updated with its remote source, int, optional, default ``604800``.
``repository`` group ``repository`` group
-------------------- --------------------

View File

@ -878,6 +878,11 @@ How to enable OAuth authorization
#. #.
Restart web service ``systemctl restart ahriman-web@x86_64``. Restart web service ``systemctl restart ahriman-web@x86_64``.
How to implement own interface
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can write your own interface by using API which is provided by the web service. Full autogenerated API documentation is available at ``http://localhost:8080/api-docs``.
Backup and restore Backup and restore
------------------ ------------------

View File

@ -19,7 +19,7 @@ Features
Live demos Live demos
---------- ----------
* `Build status page <https://ahriman-demo.arcanis.me>`_. You can login as ``demo`` user by using ``demo`` password. Note, however, you will not be able to run tasks. * `Build status page <https://ahriman-demo.arcanis.me>`_. You can login as ``demo`` user by using ``demo`` password. Note, however, you will not be able to run tasks. `HTTP API documentation <https://ahriman-demo.arcanis.me/api-docs>`_ is also available.
* `Repository index <http://repo.arcanis.me/x86_64/index.html>`_. * `Repository index <http://repo.arcanis.me/x86_64/index.html>`_.
* `Telegram feed <https://t.me/arcanisrepo>`_. * `Telegram feed <https://t.me/arcanisrepo>`_.

View File

@ -25,45 +25,30 @@ Initial setup
Configure build tools (it is required for correct dependency management system): Configure build tools (it is required for correct dependency management system):
#. #.
Create build command (you can choose any name for command, basically it should be ``{name}-{arch}-build``): Create configuration file ``{repository}.conf``:
.. code-block:: shell .. code-block:: shell
ln -s /usr/bin/archbuild /usr/local/bin/ahriman-x86_64-build cp /usr/local/share/devtools-git-poc/pacman.conf.d/{extra,aur-clone}.conf
#.
Create configuration file (same as previous ``pacman-{name}.conf``):
.. code-block:: shell
cp /usr/share/devtools/pacman-{extra,ahriman}.conf
#. #.
Change configuration file, add your own repository, add multilib repository etc: Change configuration file, add your own repository, add multilib repository etc:
.. code-block:: shell .. code-block:: shell
echo '[multilib]' | tee -a /usr/share/devtools/pacman-ahriman.conf echo '[multilib]' | tee -a /usr/local/share/devtools-git-poc/pacman.conf.d/aur-clone.conf
echo 'Include = /etc/pacman.d/mirrorlist' | tee -a /usr/share/devtools/pacman-ahriman.conf echo 'Include = /etc/pacman.d/mirrorlist' | tee -a /usr/local/share/devtools-git-poc/pacman.conf.d/aur-clone.conf
echo '[aur-clone]' | tee -a /usr/share/devtools/pacman-ahriman.conf echo '[aur-clone]' | tee -a /usr/local/share/devtools-git-poc/pacman.conf.d/aur-clone.conf
echo 'SigLevel = Optional TrustAll' | tee -a /usr/share/devtools/pacman-ahriman.conf echo 'SigLevel = Optional TrustAll' | tee -a /usr/local/share/devtools-git-poc/pacman.conf.d/aur-clone.conf
echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/share/devtools/pacman-ahriman.conf echo 'Server = file:///var/lib/ahriman/repository/$arch' | tee -a /usr/local/share/devtools-git-poc/pacman.conf.d/aur-clone.conf
#. #.
Set ``build_command`` option to point to your command: Configure ``/etc/sudoers.d/ahriman`` to allow running devtools command without a password:
.. code-block:: shell .. code-block:: shell
echo '[build]' | tee -a /etc/ahriman.ini.d/build.ini echo 'Cmnd_Alias CARCHBUILD_CMD = /usr/bin/pkgctl build *' | tee -a /etc/sudoers.d/ahriman
echo 'build_command = ahriman-x86_64-build' | tee -a /etc/ahriman.ini.d/build.ini
#.
Configure ``/etc/sudoers.d/ahriman`` to allow running command without a password:
.. code-block:: shell
echo 'Cmnd_Alias CARCHBUILD_CMD = /usr/local/bin/ahriman-x86_64-build *' | tee -a /etc/sudoers.d/ahriman
echo 'ahriman ALL=(ALL) NOPASSWD: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman echo 'ahriman ALL=(ALL) NOPASSWD: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
chmod 400 /etc/sudoers.d/ahriman chmod 400 /etc/sudoers.d/ahriman
@ -88,4 +73,6 @@ Initial setup
.. code-block:: shell .. code-block:: shell
sudo -u ahriman ahriman -a x86_64 package-add ahriman --now sudo -u ahriman ahriman -a x86_64 package-add ahriman --now --refresh
The ``--refresh`` flag is required in order to handle local database update.

View File

@ -1,19 +1,21 @@
# Maintainer: Evgeniy Alekseev # Maintainer: Evgeniy Alekseev
pkgname='ahriman' pkgname='ahriman'
pkgver=2.6.1 pkgver=2.8.0
pkgrel=1 pkgrel=1
pkgdesc="ArcH linux ReposItory MANager" pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')
url="https://github.com/arcan1s/ahriman" url="https://github.com/arcan1s/ahriman"
license=('GPL3') license=('GPL3')
depends=('devtools' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-setuptools' 'python-srcinfo') depends=('devtools-git-poc' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo')
makedepends=('python-build' 'python-installer' 'python-wheel') makedepends=('python-build' 'python-installer' 'python-wheel')
optdepends=('breezy: -bzr packages support' optdepends=('breezy: -bzr packages support'
'darcs: -darcs packages support' 'darcs: -darcs packages support'
'mercurial: -hg packages support' 'mercurial: -hg packages support'
'python-aioauth-client: web server with OAuth2 authorization' 'python-aioauth-client: web server with OAuth2 authorization'
'python-aiohttp: web server' 'python-aiohttp: web server'
'python-aiohttp-apispec>=3.0.0: web server'
'python-aiohttp-cors: web server'
'python-aiohttp-debugtoolbar: web server with enabled debug panel' 'python-aiohttp-debugtoolbar: web server with enabled debug panel'
'python-aiohttp-jinja2: web server' 'python-aiohttp-jinja2: web server'
'python-aiohttp-security: web server with authorization' 'python-aiohttp-security: web server with authorization'

View File

@ -2,6 +2,7 @@
include = ahriman.ini.d include = ahriman.ini.d
logging = ahriman.ini.d/logging.ini logging = ahriman.ini.d/logging.ini
database = /var/lib/ahriman/ahriman.db database = /var/lib/ahriman/ahriman.db
suppress_http_log_errors = yes
[alpm] [alpm]
database = /var/lib/pacman database = /var/lib/pacman
@ -18,11 +19,9 @@ oauth_scopes = https://www.googleapis.com/auth/userinfo.email
allow_read_only = yes allow_read_only = yes
[build] [build]
archbuild_flags = build_command = pkgctl
build_command = extra-x86_64-build
ignore_packages = ignore_packages =
makechrootpkg_flags = pkgctl_flags =
makepkg_flags = --nocolor --ignorearch
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger
vcs_allowed_age = 604800 vcs_allowed_age = 604800

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ahriman API</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Embed elements Elements via Web Component -->
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css" type="text/css">
<link rel="shortcut icon" href="/static/favicon.ico">
</head>
<body>
<elements-api
apiDescriptionUrl="/api-docs/swagger.json"
router="hash"
layout="sidebar"
/>
</body>
</html>

View File

@ -100,6 +100,7 @@
<li><a id="badge-version" class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li> <li><a id="badge-version" class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li>
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li> <li><a class="nav-link" href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li>
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li> <li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
<li><a class="nav-link" href="/api-docs" title="API documentation">api</a></li>
</ul> </ul>
{% if index_url is not none %} {% if index_url is not none %}

View File

@ -1,10 +1,10 @@
from pathlib import Path from pathlib import Path
from setuptools import find_packages, setup from setuptools import find_packages, setup
from typing import Any, Dict from typing import Any
metadata_path = Path(__file__).resolve().parent / "src/ahriman/version.py" metadata_path = Path(__file__).resolve().parent / "src/ahriman/version.py"
metadata: Dict[str, Any] = {} metadata: dict[str, Any] = {}
with metadata_path.open() as metadata_file: with metadata_path.open() as metadata_file:
exec(metadata_file.read(), metadata) # pylint: disable=exec-used exec(metadata_file.read(), metadata) # pylint: disable=exec-used
@ -25,6 +25,7 @@ setup(
packages=find_packages("src"), packages=find_packages("src"),
package_dir={"": "src"}, package_dir={"": "src"},
package_data={"": ["py.typed"]},
dependency_links=[ dependency_links=[
], ],
@ -33,7 +34,6 @@ setup(
"inflection", "inflection",
"passlib", "passlib",
"requests", "requests",
"setuptools",
"srcinfo", "srcinfo",
], ],
setup_requires=[ setup_requires=[
@ -68,6 +68,7 @@ setup(
]), ]),
# templates # templates
("share/ahriman/templates", [ ("share/ahriman/templates", [
"package/share/ahriman/templates/api.jinja2",
"package/share/ahriman/templates/build-status.jinja2", "package/share/ahriman/templates/build-status.jinja2",
"package/share/ahriman/templates/email-index.jinja2", "package/share/ahriman/templates/email-index.jinja2",
"package/share/ahriman/templates/error.jinja2", "package/share/ahriman/templates/error.jinja2",
@ -140,9 +141,11 @@ setup(
], ],
"web": [ "web": [
"Jinja2", "Jinja2",
"aiohttp",
"aiohttp_jinja2",
"aioauth-client", "aioauth-client",
"aiohttp",
"aiohttp-apispec",
"aiohttp_cors",
"aiohttp_jinja2",
"aiohttp_debugtoolbar", "aiohttp_debugtoolbar",
"aiohttp_session", "aiohttp_session",
"aiohttp_security", "aiohttp_security",

View File

@ -23,7 +23,7 @@ import sys
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from typing import List, TypeVar from typing import TypeVar
from ahriman import version from ahriman import version
from ahriman.application import handlers from ahriman.application import handlers
@ -35,7 +35,7 @@ from ahriman.models.sign_settings import SignSettings
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
__all__: List[str] = [] __all__: list[str] = []
# this workaround is for several things # this workaround is for several things
@ -70,7 +70,8 @@ def _parser() -> argparse.ArgumentParser:
fromfile_prefix_chars="@", formatter_class=_formatter) fromfile_prefix_chars="@", formatter_class=_formatter)
parser.add_argument("-a", "--architecture", help="target architectures. For several subcommands it can be used " parser.add_argument("-a", "--architecture", help="target architectures. For several subcommands it can be used "
"multiple times", action="append") "multiple times", action="append")
parser.add_argument("-c", "--configuration", help="configuration path", type=Path, default=Path("/etc/ahriman.ini")) parser.add_argument("-c", "--configuration", help="configuration path", type=Path,
default=Path("/etc") / "ahriman.ini")
parser.add_argument("--force", help="force run, remove file lock", action="store_true") parser.add_argument("--force", help="force run, remove file lock", action="store_true")
parser.add_argument("-l", "--lock", help="lock file", type=Path, parser.add_argument("-l", "--lock", help="lock file", type=Path,
default=Path(tempfile.gettempdir()) / "ahriman.lock") default=Path(tempfile.gettempdir()) / "ahriman.lock")
@ -245,6 +246,8 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"5) and finally you can add package from AUR.", "5) and finally you can add package from AUR.",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+") parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-n", "--now", help="run update function after", action="store_true") parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
@ -252,7 +255,6 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
action="count", default=False) action="count", default=False)
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command", parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto) type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
parser.set_defaults(handler=handlers.Add) parser.set_defaults(handler=handlers.Add)
return parser return parser
@ -472,7 +474,7 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date", "-yy to force refresh even if up to date",
action="count", default=False) action="count", default=False)
parser.set_defaults(handler=handlers.Update, dry_run=True, aur=True, local=True, manual=False) parser.set_defaults(handler=handlers.Update, dependencies=False, dry_run=True, aur=True, local=True, manual=False)
return parser return parser
@ -492,6 +494,8 @@ def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12) parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12)
parser.add_argument("--aur", help="enable or disable checking for AUR updates", parser.add_argument("--aur", help="enable or disable checking for AUR updates",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--local", help="enable or disable checking of local packages for updates", parser.add_argument("--local", help="enable or disable checking of local packages for updates",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--manual", help="include or exclude manual updates", parser.add_argument("--manual", help="include or exclude manual updates",
@ -691,10 +695,12 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="check for packages updates and run build process if requested", description="check for packages updates and run build process if requested",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("package", help="filter check by package base", nargs="*") parser.add_argument("package", help="filter check by package base", nargs="*")
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--aur", help="enable or disable checking for AUR updates", parser.add_argument("--aur", help="enable or disable checking for AUR updates",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--local", help="enable or disable checking of local packages for updates", parser.add_argument("--local", help="enable or disable checking of local packages for updates",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--manual", help="include or exclude manual updates", parser.add_argument("--manual", help="include or exclude manual updates",
@ -750,6 +756,8 @@ def _set_service_config_parser(root: SubParserAction) -> argparse.ArgumentParser
parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration", parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration",
description="dump configuration for the specified architecture", description="dump configuration for the specified architecture",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("--secure", help="hide passwords and secrets from output",
action=argparse.BooleanOptionalAction, default=True)
parser.set_defaults(handler=handlers.Dump, lock=None, report=False, quiet=True, unsafe=True) parser.set_defaults(handler=handlers.Dump, lock=None, report=False, quiet=True, unsafe=True)
return parser return parser
@ -812,10 +820,9 @@ def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="create initial service configuration, requires root", description="create initial service configuration, requires root",
epilog="Create _minimal_ configuration for the service according to provided options.", epilog="Create _minimal_ configuration for the service according to provided options.",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("--build-as-user", help="force makepkg user to the specific one") parser.add_argument("--build-command", help="path to build command", default=Path("/usr") / "bin" / "pkgctl")
parser.add_argument("--build-command", help="build command prefix", default="ahriman") parser.add_argument("--from-configuration", help="path to default devtools pacman configuration", type=Path,
parser.add_argument("--from-configuration", help="path to default devtools pacman configuration", default=Path("/usr") / "local" / "share" / "devtools-git-poc" / "pacman.conf.d" / "extra.conf")
type=Path, default=Path("/usr/share/devtools/pacman-extra.conf"))
parser.add_argument("--makeflags-jobs", help="append MAKEFLAGS variable with parallelism set to number of cores", parser.add_argument("--makeflags-jobs", help="append MAKEFLAGS variable with parallelism set to number of cores",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist") parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist")

View File

@ -17,10 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import Set from collections.abc import Iterable
from ahriman.application.application.application_packages import ApplicationPackages from ahriman.application.application.application_packages import ApplicationPackages
from ahriman.application.application.application_repository import ApplicationRepository from ahriman.application.application.application_repository import ApplicationRepository
from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result
@ -49,14 +50,14 @@ class Application(ApplicationPackages, ApplicationRepository):
be used instead. be used instead.
""" """
def _known_packages(self) -> Set[str]: def _known_packages(self) -> set[str]:
""" """
load packages from repository and pacman repositories load packages from repository and pacman repositories
Returns: Returns:
Set[str]: list of known packages set[str]: list of known packages
""" """
known_packages: Set[str] = set() known_packages: set[str] = set()
# local set # local set
for base in self.repository.packages(): for base in self.repository.packages():
for package, properties in base.packages.items(): for package, properties in base.packages.items():
@ -87,3 +88,39 @@ class Application(ApplicationPackages, ApplicationRepository):
directly as it will be called after on_start action directly as it will be called after on_start action
""" """
self.repository.triggers.on_stop() self.repository.triggers.on_stop()
def with_dependencies(self, packages: list[Package], *, process_dependencies: bool) -> list[Package]:
"""
add missing dependencies to list of packages
Args:
packages(list[Package]): list of source packages of which dependencies have to be processed
process_dependencies(bool): if no set, dependencies will not be processed
"""
def missing_dependencies(source: Iterable[Package]) -> set[str]:
# build initial list of dependencies
result = set()
for package in source:
result.update(package.depends_build)
# remove ones which are already well-known
result = result.difference(known_packages)
# remove ones which are in this list already
for package in source:
result = result.difference(package.packages_full)
return result
if not process_dependencies or not packages:
return packages
known_packages = self._known_packages()
with_dependencies = {package.base: package for package in packages}
while missing := missing_dependencies(with_dependencies.values()):
for package_name in missing:
package = Package.from_aur(package_name, self.repository.pacman)
with_dependencies[package.base] = package
return list(with_dependencies.values())

View File

@ -20,9 +20,9 @@
import requests import requests
import shutil import shutil
from collections.abc import Iterable
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from typing import Any
from typing import Any, Iterable, Set
from ahriman.application.application.application_properties import ApplicationProperties from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
@ -48,24 +48,18 @@ class ApplicationPackages(ApplicationProperties):
dst = self.repository.paths.packages / local_path.name dst = self.repository.paths.packages / local_path.name
shutil.copy(local_path, dst) shutil.copy(local_path, dst)
def _add_aur(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None: def _add_aur(self, source: str) -> None:
""" """
add package from AUR add package from AUR
Args: Args:
source(str): package base name source(str): package base name
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
""" """
package = Package.from_aur(source, self.repository.pacman) package = Package.from_aur(source, self.repository.pacman)
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
self.database.remote_update(package) self.database.remote_update(package)
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (local_dir := Path(dir_name)):
Sources.load(local_dir, package, self.database.patches_get(package.base), self.repository.paths)
self._process_dependencies(local_dir, known_packages, without_dependencies)
def _add_directory(self, source: str, *_: Any) -> None: def _add_directory(self, source: str, *_: Any) -> None:
""" """
add packages from directory add packages from directory
@ -77,25 +71,21 @@ class ApplicationPackages(ApplicationProperties):
for full_path in filter(package_like, local_dir.iterdir()): for full_path in filter(package_like, local_dir.iterdir()):
self._add_archive(str(full_path)) self._add_archive(str(full_path))
def _add_local(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None: def _add_local(self, source: str) -> None:
""" """
add package from local PKGBUILDs add package from local PKGBUILDs
Args: Args:
source(str): path to directory with local source files source(str): path to directory with local source files
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
""" """
source_dir = Path(source) source_dir = Path(source)
package = Package.from_build(source_dir) package = Package.from_build(source_dir, self.architecture)
cache_dir = self.repository.paths.cache_for(package.base) cache_dir = self.repository.paths.cache_for(package.base)
shutil.copytree(source_dir, cache_dir) # copy package to store in caches shutil.copytree(source_dir, cache_dir) # copy package to store in caches
Sources.init(cache_dir) # we need to run init command in directory where we do have permissions Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
self._process_dependencies(cache_dir, known_packages, without_dependencies)
def _add_remote(self, source: str, *_: Any) -> None: def _add_remote(self, source: str, *_: Any) -> None:
""" """
add package from remote sources (e.g. HTTP) add package from remote sources (e.g. HTTP)
@ -104,7 +94,8 @@ class ApplicationPackages(ApplicationProperties):
source(str): remote URL of the package archive source(str): remote URL of the package archive
""" """
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it? dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
response = requests.get(source, stream=True, timeout=None) # timeout=None to suppress pylint warns # timeout=None to suppress pylint warns. Also suppress bandit warnings
response = requests.get(source, stream=True, timeout=None) # nosec
response.raise_for_status() response.raise_for_status()
with dst.open("wb") as local_file: with dst.open("wb") as local_file:
@ -121,50 +112,19 @@ class ApplicationPackages(ApplicationProperties):
package = Package.from_official(source, self.repository.pacman) package = Package.from_official(source, self.repository.pacman)
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
self.database.remote_update(package) self.database.remote_update(package)
# repository packages must not depend on unknown packages, thus we are not going to process dependencies
def _known_packages(self) -> Set[str]: def add(self, names: Iterable[str], source: PackageSource) -> None:
"""
load packages from repository and pacman repositories
Returns:
Set[str]: list of known packages
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def _process_dependencies(self, local_dir: Path, known_packages: Set[str], without_dependencies: bool) -> None:
"""
process package dependencies
Args:
local_dir(Path): path to local package sources (i.e. cloned AUR repository)
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
if without_dependencies:
return
dependencies = Package.dependencies(local_dir)
self.add(dependencies.difference(known_packages), PackageSource.AUR, without_dependencies)
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:
""" """
add packages for the next build add packages for the next build
Args: Args:
names(Iterable[str]): list of package bases to add names(Iterable[str]): list of package bases to add
source(PackageSource): package source to add source(PackageSource): package source to add
without_dependencies(bool): if set, dependency check will be disabled
""" """
known_packages = self._known_packages() # speedup dependencies processing
for name in names: for name in names:
resolved_source = source.resolve(name) resolved_source = source.resolve(name)
fn = getattr(self, f"_add_{resolved_source.value}") fn = getattr(self, f"_add_{resolved_source.value}")
fn(name, known_packages, without_dependencies) fn(name)
def on_result(self, result: Result) -> None: def on_result(self, result: Result) -> None:
""" """

View File

@ -21,6 +21,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.models.pacman_synchronization import PacmanSynchronization
class ApplicationProperties(LazyLogging): class ApplicationProperties(LazyLogging):
@ -34,8 +35,8 @@ class ApplicationProperties(LazyLogging):
repository(Repository): repository instance repository(Repository): repository instance
""" """
def __init__(self, architecture: str, configuration: Configuration, *, def __init__(self, architecture: str, configuration: Configuration, *, report: bool, unsafe: bool,
report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> None: refresh_pacman_database: PacmanSynchronization = PacmanSynchronization.Disabled) -> None:
""" """
default constructor default constructor
@ -44,8 +45,8 @@ class ApplicationProperties(LazyLogging):
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
unsafe(bool): if set no user check will be performed before path creation unsafe(bool): if set no user check will be performed before path creation
refresh_pacman_database(int, optional): pacman database syncronization level, ``0`` is disabled refresh_pacman_database(PacmanSynchronization, optional): pacman database synchronization level
(Default value = 0) (Default value = PacmanSynchronization.Disabled)
""" """
self.configuration = configuration self.configuration = configuration
self.architecture = architecture self.architecture = architecture

View File

@ -17,8 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Callable, Iterable
from pathlib import Path from pathlib import Path
from typing import Callable, Iterable, List
from ahriman.application.application.application_properties import ApplicationProperties from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
@ -89,19 +89,19 @@ class ApplicationRepository(ApplicationProperties):
# process triggers # process triggers
self.on_result(Result()) self.on_result(Result())
def unknown(self) -> List[str]: def unknown(self) -> list[str]:
""" """
get packages which were not found in AUR get packages which were not found in AUR
Returns: Returns:
List[str]: unknown package archive list list[str]: unknown package archive list
""" """
def has_local(probe: Package) -> bool: def has_local(probe: Package) -> bool:
cache_dir = self.repository.paths.cache_for(probe.base) cache_dir = self.repository.paths.cache_for(probe.base)
return cache_dir.is_dir() and not Sources.has_remotes(cache_dir) return cache_dir.is_dir() and not Sources.has_remotes(cache_dir)
def unknown_aur(probe: Package) -> List[str]: def unknown_aur(probe: Package) -> list[str]:
packages: List[str] = [] packages: list[str] = []
for single in probe.packages: for single in probe.packages:
try: try:
_ = Package.from_aur(single, self.repository.pacman) _ = Package.from_aur(single, self.repository.pacman)
@ -109,9 +109,9 @@ class ApplicationRepository(ApplicationProperties):
packages.append(single) packages.append(single)
return packages return packages
def unknown_local(probe: Package) -> List[str]: def unknown_local(probe: Package) -> list[str]:
cache_dir = self.repository.paths.cache_for(probe.base) cache_dir = self.repository.paths.cache_for(probe.base)
local = Package.from_build(cache_dir) local = Package.from_build(cache_dir, self.architecture)
packages = set(probe.packages.keys()).difference(local.packages.keys()) packages = set(probe.packages.keys()).difference(local.packages.keys())
return list(packages) return list(packages)
@ -145,7 +145,7 @@ class ApplicationRepository(ApplicationProperties):
process_update(packages, build_result) process_update(packages, build_result)
# process manual packages # process manual packages
tree = Tree.resolve(updates, self.repository.paths, self.database) tree = Tree.resolve(updates)
for num, level in enumerate(tree): for num, level in enumerate(tree):
self.logger.info("processing level #%i %s", num, [package.base for package in level]) self.logger.info("processing level #%i %s", num, [package.base for package in level])
build_result = self.repository.process_build(level) build_result = self.repository.process_build(level)
@ -155,7 +155,7 @@ class ApplicationRepository(ApplicationProperties):
return build_result return build_result
def updates(self, filter_packages: Iterable[str], *, def updates(self, filter_packages: Iterable[str], *,
aur: bool, local: bool, manual: bool, vcs: bool, log_fn: Callable[[str], None]) -> List[Package]: aur: bool, local: bool, manual: bool, vcs: bool, log_fn: Callable[[str], None]) -> list[Package]:
""" """
get list of packages to run update process get list of packages to run update process
@ -168,7 +168,7 @@ class ApplicationRepository(ApplicationProperties):
log_fn(Callable[[str], None]): logger function to log updates log_fn(Callable[[str], None]): logger function to log updates
Returns: Returns:
List[Package]: list of out-of-dated packages list[Package]: list of out-of-dated packages
""" """
updates = {} updates = {}
@ -183,7 +183,7 @@ class ApplicationRepository(ApplicationProperties):
updated_packages = [package for _, package in sorted(updates.items())] updated_packages = [package for _, package in sorted(updates.items())]
# reorder updates according to the dependency tree # reorder updates according to the dependency tree
tree = Tree.resolve(updated_packages, self.repository.paths, self.database) tree = Tree.resolve(updated_packages)
for level in tree: for level in tree:
for package in level: for package in level:
UpdatePrinter(package, local_versions.get(package.base)).print( UpdatePrinter(package, local_versions.get(package.base)).print(

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -32,7 +30,7 @@ class Add(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -47,11 +45,12 @@ class Add(Handler):
application = Application(architecture, configuration, application = Application(architecture, configuration,
report=report, unsafe=unsafe, refresh_pacman_database=args.refresh) report=report, unsafe=unsafe, refresh_pacman_database=args.refresh)
application.on_start() application.on_start()
application.add(args.package, args.source, args.without_dependencies) application.add(args.package, args.source)
if not args.now: if not args.now:
return return
packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False, packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False,
log_fn=application.logger.info) log_fn=application.logger.info)
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
result = application.update(packages) result = application.update(packages)
Add.check_if_empty(args.exit_code, result.is_empty) Add.check_if_empty(args.exit_code, result.is_empty)

View File

@ -22,7 +22,6 @@ import pwd
from pathlib import Path from pathlib import Path
from tarfile import TarFile from tarfile import TarFile
from typing import Set, Type
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -37,7 +36,7 @@ class Backup(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -55,7 +54,7 @@ class Backup(Handler):
archive.add(backup_path) archive.add(backup_path)
@staticmethod @staticmethod
def get_paths(configuration: Configuration) -> Set[Path]: def get_paths(configuration: Configuration) -> set[Path]:
""" """
extract paths to back up extract paths to back up
@ -63,7 +62,7 @@ class Backup(Handler):
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
Returns: Returns:
Set[Path]: map of the filesystem paths set[Path]: map of the filesystem paths
""" """
paths = set(configuration.include.glob("*.ini")) paths = set(configuration.include.glob("*.ini"))

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -32,7 +30,7 @@ class Clean(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -20,8 +20,6 @@
import argparse import argparse
import threading import threading
from typing import Type
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -32,7 +30,7 @@ class Daemon(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.formatters import ConfigurationPrinter from ahriman.core.formatters import ConfigurationPrinter
@ -34,7 +32,7 @@ class Dump(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -48,4 +46,4 @@ class Dump(Handler):
""" """
dump = configuration.dump() dump = configuration.dump()
for section, values in sorted(dump.items()): for section, values in sorted(dump.items()):
ConfigurationPrinter(section, values).print(verbose=False, separator=" = ") ConfigurationPrinter(section, values).print(verbose=not args.secure, separator=" = ")

View File

@ -17,13 +17,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from __future__ import annotations
import argparse import argparse
import logging import logging
from multiprocessing import Pool from multiprocessing import Pool
from typing import List, Type
from ahriman.application.lock import Lock from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -53,7 +50,7 @@ class Handler:
ALLOW_MULTI_ARCHITECTURE_RUN = True ALLOW_MULTI_ARCHITECTURE_RUN = True
@classmethod @classmethod
def architectures_extract(cls: Type[Handler], args: argparse.Namespace) -> List[str]: def architectures_extract(cls, args: argparse.Namespace) -> list[str]:
""" """
get known architectures get known architectures
@ -61,7 +58,7 @@ class Handler:
args(argparse.Namespace): command line args args(argparse.Namespace): command line args
Returns: Returns:
List[str]: list of architectures for which tree is created list[str]: list of architectures for which tree is created
Raises: Raises:
MissingArchitecture: if no architecture set and automatic detection is not allowed or failed MissingArchitecture: if no architecture set and automatic detection is not allowed or failed
@ -84,7 +81,7 @@ class Handler:
return sorted(architectures) return sorted(architectures)
@classmethod @classmethod
def call(cls: Type[Handler], args: argparse.Namespace, architecture: str) -> bool: def call(cls, args: argparse.Namespace, architecture: str) -> bool:
""" """
additional function to wrap all calls for multiprocessing library additional function to wrap all calls for multiprocessing library
@ -109,7 +106,7 @@ class Handler:
return False return False
@classmethod @classmethod
def execute(cls: Type[Handler], args: argparse.Namespace) -> int: def execute(cls, args: argparse.Namespace) -> int:
""" """
execute function for all aru execute function for all aru
@ -138,7 +135,7 @@ class Handler:
return 0 if all(result) else 1 return 0 if all(result) else 1
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -33,7 +31,7 @@ class Help(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -34,7 +32,7 @@ class KeyImport(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -21,7 +21,6 @@ import argparse
import sys import sys
from pathlib import Path from pathlib import Path
from typing import List, Optional, Tuple, Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -39,7 +38,7 @@ class Patch(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -58,7 +57,7 @@ class Patch(Handler):
patch = Patch.patch_create_from_function(args.variable, args.patch) patch = Patch.patch_create_from_function(args.variable, args.patch)
Patch.patch_set_create(application, args.package, patch) Patch.patch_set_create(application, args.package, patch)
elif args.action == Action.Update and args.variable is None: elif args.action == Action.Update and args.variable is None:
package_base, patch = Patch.patch_create_from_diff(args.package, args.track) package_base, patch = Patch.patch_create_from_diff(args.package, architecture, args.track)
Patch.patch_set_create(application, package_base, patch) Patch.patch_set_create(application, package_base, patch)
elif args.action == Action.List: elif args.action == Action.List:
Patch.patch_set_list(application, args.package, args.variable, args.exit_code) Patch.patch_set_list(application, args.package, args.variable, args.exit_code)
@ -66,30 +65,31 @@ class Patch(Handler):
Patch.patch_set_remove(application, args.package, args.variable) Patch.patch_set_remove(application, args.package, args.variable)
@staticmethod @staticmethod
def patch_create_from_diff(sources_dir: Path, track: List[str]) -> Tuple[str, PkgbuildPatch]: def patch_create_from_diff(sources_dir: Path, architecture: str, track: list[str]) -> tuple[str, PkgbuildPatch]:
""" """
create PKGBUILD plain diff patches from sources directory create PKGBUILD plain diff patches from sources directory
Args: Args:
sources_dir(Path): path to directory with the package sources sources_dir(Path): path to directory with the package sources
track(List[str]): track files which match the glob before creating the patch architecture(str): repository architecture
track(list[str]): track files which match the glob before creating the patch
Returns: Returns:
Tuple[str, PkgbuildPatch]: package base and created PKGBUILD patch based on the diff from master HEAD tuple[str, PkgbuildPatch]: package base and created PKGBUILD patch based on the diff from master HEAD
to current files to current files
""" """
package = Package.from_build(sources_dir) package = Package.from_build(sources_dir, architecture)
patch = Sources.patch_create(sources_dir, *track) patch = Sources.patch_create(sources_dir, *track)
return package.base, PkgbuildPatch(None, patch) return package.base, PkgbuildPatch(None, patch)
@staticmethod @staticmethod
def patch_create_from_function(variable: str, patch_path: Optional[Path]) -> PkgbuildPatch: def patch_create_from_function(variable: str, patch_path: Path | None) -> PkgbuildPatch:
""" """
create single-function patch set for the package base create single-function patch set for the package base
Args: Args:
variable(str): function or variable name inside PKGBUILD variable(str): function or variable name inside PKGBUILD
patch_path(Path): optional path to patch content. If not set, it will be read from stdin patch_path(Path | None): optional path to patch content. If not set, it will be read from stdin
Returns: Returns:
PkgbuildPatch: created patch for the PKGBUILD function PkgbuildPatch: created patch for the PKGBUILD function
@ -115,15 +115,15 @@ class Patch(Handler):
application.database.patches_insert(package_base, patch) application.database.patches_insert(package_base, patch)
@staticmethod @staticmethod
def patch_set_list(application: Application, package_base: Optional[str], variables: List[str], def patch_set_list(application: Application, package_base: str | None, variables: list[str],
exit_code: bool) -> None: exit_code: bool) -> None:
""" """
list patches available for the package base list patches available for the package base
Args: Args:
application(Application): application instance application(Application): application instance
package_base(Optional[str]): package base package_base(str | None): package base
variables(List[str]): extract patches only for specified PKGBUILD variables variables(list[str]): extract patches only for specified PKGBUILD variables
exit_code(bool): exit with error on empty search result exit_code(bool): exit with error on empty search result
""" """
patches = application.database.patches_list(package_base, variables) patches = application.database.patches_list(package_base, variables)
@ -133,13 +133,13 @@ class Patch(Handler):
PatchPrinter(base, patch).print(verbose=True, separator=" = ") PatchPrinter(base, patch).print(verbose=True, separator=" = ")
@staticmethod @staticmethod
def patch_set_remove(application: Application, package_base: str, variables: List[str]) -> None: def patch_set_remove(application: Application, package_base: str, variables: list[str]) -> None:
""" """
remove patch set for the package base remove patch set for the package base
Args: Args:
application(Application): application instance application(Application): application instance
package_base(str): package base package_base(str): package base
variables(List[str]): remove patches only for specified PKGBUILD variables variables(list[str]): remove patches only for specified PKGBUILD variables
""" """
application.database.patches_remove(package_base, variables) application.database.patches_remove(package_base, variables)

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import List, Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -34,7 +32,7 @@ class Rebuild(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -46,13 +44,11 @@ class Rebuild(Handler):
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
unsafe(bool): if set no user check will be performed before path creation unsafe(bool): if set no user check will be performed before path creation
""" """
depends_on = set(args.depends_on) if args.depends_on else None
application = Application(architecture, configuration, report=report, unsafe=unsafe) application = Application(architecture, configuration, report=report, unsafe=unsafe)
application.on_start() application.on_start()
packages = Rebuild.extract_packages(application, from_database=args.from_database) packages = Rebuild.extract_packages(application, from_database=args.from_database)
updates = application.repository.packages_depend_on(packages, depends_on) updates = application.repository.packages_depend_on(packages, args.depends_on or None)
Rebuild.check_if_empty(args.exit_code, not updates) Rebuild.check_if_empty(args.exit_code, not updates)
if args.dry_run: if args.dry_run:
@ -64,7 +60,7 @@ class Rebuild(Handler):
Rebuild.check_if_empty(args.exit_code, result.is_empty) Rebuild.check_if_empty(args.exit_code, result.is_empty)
@staticmethod @staticmethod
def extract_packages(application: Application, *, from_database: bool) -> List[Package]: def extract_packages(application: Application, *, from_database: bool) -> list[Package]:
""" """
extract packages from database file extract packages from database file
@ -73,7 +69,7 @@ class Rebuild(Handler):
from_database(bool): extract packages from database instead of repository filesystem from_database(bool): extract packages from database instead of repository filesystem
Returns: Returns:
List[Package]: list of packages which were stored in database list[Package]: list of packages which were stored in database
""" """
if from_database: if from_database:
return [package for (package, _) in application.database.packages_get()] return [package for (package, _) in application.database.packages_get()]

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -32,7 +30,7 @@ class Remove(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -33,7 +31,7 @@ class RemoveUnknown(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -51,7 +49,7 @@ class RemoveUnknown(Handler):
if args.dry_run: if args.dry_run:
for package in sorted(unknown_packages): for package in sorted(unknown_packages):
StringPrinter(package).print(False) StringPrinter(package).print(verbose=False)
return return
application.remove(unknown_packages) application.remove(unknown_packages)

View File

@ -19,7 +19,6 @@
# #
import argparse import argparse
from typing import Type
from tarfile import TarFile from tarfile import TarFile
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -34,7 +33,7 @@ class Restore(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -20,7 +20,7 @@
import argparse import argparse
from dataclasses import fields from dataclasses import fields
from typing import Callable, Iterable, List, Tuple, Type from collections.abc import Callable, Iterable
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -36,14 +36,18 @@ class Search(Handler):
packages search handler packages search handler
Attributes: Attributes:
SORT_FIELDS(Set[str]): (class attribute) allowed fields to sort the package list SORT_FIELDS(set[str]): (class attribute) allowed fields to sort the package list
""" """
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
SORT_FIELDS = {field.name for field in fields(AURPackage) if field.default_factory is not list} SORT_FIELDS = {
field.name
for field in fields(AURPackage)
if field.default_factory is not list # type: ignore[comparison-overlap]
}
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -64,10 +68,10 @@ class Search(Handler):
for packages_list in (official_packages_list, aur_packages_list): for packages_list in (official_packages_list, aur_packages_list):
# keep sorting by packages source # keep sorting by packages source
for package in Search.sort(packages_list, args.sort_by): for package in Search.sort(packages_list, args.sort_by):
AurPrinter(package).print(args.info) AurPrinter(package).print(verbose=args.info)
@staticmethod @staticmethod
def sort(packages: Iterable[AURPackage], sort_by: str) -> List[AURPackage]: def sort(packages: Iterable[AURPackage], sort_by: str) -> list[AURPackage]:
""" """
sort package list by specified field sort package list by specified field
@ -76,7 +80,7 @@ class Search(Handler):
sort_by(str): AUR package field name to sort by sort_by(str): AUR package field name to sort by
Returns: Returns:
List[AURPackage]: sorted list for packages list[AURPackage]: sorted list for packages
Raises: Raises:
InvalidOption: if search fields is not in list of allowed ones InvalidOption: if search fields is not in list of allowed ones
@ -85,6 +89,6 @@ class Search(Handler):
raise OptionError(sort_by) raise OptionError(sort_by)
# always sort by package name at the last # always sort by package name at the last
# well technically it is not a string, but we can deal with it # well technically it is not a string, but we can deal with it
comparator: Callable[[AURPackage], Tuple[str, str]] =\ comparator: Callable[[AURPackage], tuple[str, str]] =\
lambda package: (getattr(package, sort_by), package.name) lambda package: (getattr(package, sort_by), package.name)
return sorted(packages, key=comparator) return sorted(packages, key=comparator)

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman import version from ahriman import version
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -37,7 +35,7 @@ class ServiceUpdates(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -21,7 +21,6 @@ import argparse
from pathlib import Path from pathlib import Path
from pwd import getpwuid from pwd import getpwuid
from typing import Optional, Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -34,19 +33,17 @@ class Setup(Handler):
setup handler setup handler
Attributes: Attributes:
ARCHBUILD_COMMAND_PATH(Path): (class attribute) default devtools command
MIRRORLIST_PATH(Path): (class attribute) path to pacman default mirrorlist (used by multilib repository) MIRRORLIST_PATH(Path): (class attribute) path to pacman default mirrorlist (used by multilib repository)
SUDOERS_DIR_PATH(Path): (class attribute) path to sudoers.d includes directory SUDOERS_DIR_PATH(Path): (class attribute) path to sudoers.d includes directory
""" """
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
ARCHBUILD_COMMAND_PATH = Path("/usr/bin/archbuild")
MIRRORLIST_PATH = Path("/etc/pacman.d/mirrorlist") MIRRORLIST_PATH = Path("/etc/pacman.d/mirrorlist")
SUDOERS_DIR_PATH = Path("/etc/sudoers.d") SUDOERS_DIR_PATH = Path("/etc/sudoers.d")
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -64,30 +61,14 @@ class Setup(Handler):
application = Application(architecture, configuration, report=report, unsafe=unsafe) application = Application(architecture, configuration, report=report, unsafe=unsafe)
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths) Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
Setup.executable_create(application.repository.paths, args.build_command, architecture) Setup.configuration_create_devtools(architecture, args.from_configuration, args.mirror,
Setup.configuration_create_devtools(args.build_command, architecture, args.from_configuration, args.mirror,
args.multilib, args.repository, application.repository.paths) args.multilib, args.repository, application.repository.paths)
Setup.configuration_create_sudo(application.repository.paths, args.build_command, architecture) Setup.configuration_create_sudo(args.build_command)
application.repository.repo.init() application.repository.repo.init()
# lazy database sync # lazy database sync
application.repository.pacman.handle # pylint: disable=pointless-statement application.repository.pacman.handle # pylint: disable=pointless-statement
@staticmethod
def build_command(root: Path, prefix: str, architecture: str) -> Path:
"""
generate build command name
Args:
root(Path): root directory for the build command (must be root of the repository)
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
Returns:
Path: valid devtools command name
"""
return root / f"{prefix}-{architecture}-build"
@staticmethod @staticmethod
def configuration_create_ahriman(args: argparse.Namespace, architecture: str, repository: str, def configuration_create_ahriman(args: argparse.Namespace, architecture: str, repository: str,
root: Configuration) -> None: root: Configuration) -> None:
@ -102,12 +83,10 @@ class Setup(Handler):
""" """
configuration = Configuration() configuration = Configuration()
section = Configuration.section_name("build", architecture)
build_command = Setup.build_command(root.repository_paths.root, args.build_command, architecture)
configuration.set_option(section, "build_command", str(build_command))
configuration.set_option("repository", "name", repository) configuration.set_option("repository", "name", repository)
if args.build_as_user is not None:
configuration.set_option(section, "makechrootpkg_flags", f"-U {args.build_as_user}") section = Configuration.section_name("build", architecture)
configuration.set_option(section, "build_command", str(args.build_command))
section = Configuration.section_name("alpm", architecture) section = Configuration.section_name("alpm", architecture)
if args.mirror is not None: if args.mirror is not None:
@ -132,7 +111,7 @@ class Setup(Handler):
configuration.write(ahriman_configuration) configuration.write(ahriman_configuration)
@staticmethod @staticmethod
def configuration_create_devtools(prefix: str, architecture: str, source: Path, mirror: Optional[str], def configuration_create_devtools(architecture: str, source: Path, mirror: str | None,
multilib: bool, repository: str, paths: RepositoryPaths) -> None: multilib: bool, repository: str, paths: RepositoryPaths) -> None:
""" """
create configuration for devtools based on ``source`` configuration create configuration for devtools based on ``source`` configuration
@ -141,10 +120,9 @@ class Setup(Handler):
devtools does not allow to specify the pacman configuration, thus we still have to use configuration in /usr devtools does not allow to specify the pacman configuration, thus we still have to use configuration in /usr
Args: Args:
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture architecture(str): repository architecture
source(Path): path to source configuration file source(Path): path to source configuration file
mirror(Optional[str]): link to package server mirror mirror(str | None): link to package server mirror
multilib(bool): add or do not multilib repository to the configuration multilib(bool): add or do not multilib repository to the configuration
repository(str): repository name repository(str): repository name
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
@ -154,7 +132,7 @@ class Setup(Handler):
configuration = Configuration(allow_no_value=True) configuration = Configuration(allow_no_value=True)
# preserve case # preserve case
# stupid mypy thinks that it is impossible # stupid mypy thinks that it is impossible
configuration.optionxform = lambda key: key # type: ignore configuration.optionxform = lambda key: key # type: ignore[method-assign]
# load default configuration first # load default configuration first
# we cannot use Include here because it will be copied to new chroot, thus no includes there # we cannot use Include here because it will be copied to new chroot, thus no includes there
@ -179,7 +157,7 @@ class Setup(Handler):
configuration.set_option(repository, "SigLevel", "Optional TrustAll") # we don't care configuration.set_option(repository, "SigLevel", "Optional TrustAll") # we don't care
configuration.set_option(repository, "Server", f"file://{paths.repository}") configuration.set_option(repository, "Server", f"file://{paths.repository}")
target = source.parent / f"pacman-{prefix}-{architecture}.conf" target = source.parent / f"{repository}.conf"
with target.open("w") as devtools_configuration: with target.open("w") as devtools_configuration:
configuration.write(devtools_configuration) configuration.write(devtools_configuration)
@ -203,31 +181,13 @@ class Setup(Handler):
(home_dir / ".makepkg.conf").write_text(content, encoding="utf8") (home_dir / ".makepkg.conf").write_text(content, encoding="utf8")
@staticmethod @staticmethod
def configuration_create_sudo(paths: RepositoryPaths, prefix: str, architecture: str) -> None: def configuration_create_sudo(build_command: Path) -> None:
""" """
create configuration to run build command with sudo without password create configuration to run build command with sudo without password
Args: Args:
paths(RepositoryPaths): repository paths instance build_command(Path): path to build command
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
""" """
command = Setup.build_command(paths.root, prefix, architecture) sudoers_file = Setup.SUDOERS_DIR_PATH / f"ahriman-{build_command.name}"
sudoers_file = Setup.build_command(Setup.SUDOERS_DIR_PATH, prefix, architecture) sudoers_file.write_text(f"ahriman ALL=(ALL) NOPASSWD: {build_command} build *\n", encoding="utf8")
sudoers_file.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n", encoding="utf8")
sudoers_file.chmod(0o400) # security! sudoers_file.chmod(0o400) # security!
@staticmethod
def executable_create(paths: RepositoryPaths, prefix: str, architecture: str) -> None:
"""
create executable for the service
Args:
paths(RepositoryPaths): repository paths instance
prefix(str): command prefix in {prefix}-{architecture}-build
architecture(str): repository architecture
"""
command = Setup.build_command(paths.root, prefix, architecture)
command.unlink(missing_ok=True)
command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH)
paths.chown(command) # we would like to keep owner inside ahriman's home

View File

@ -22,7 +22,6 @@ import code
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -38,7 +37,7 @@ class Shell(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False ALLOW_MULTI_ARCHITECTURE_RUN = False
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -56,7 +55,7 @@ class Shell(Handler):
# licensed by https://creativecommons.org/licenses/by-sa/3.0 # licensed by https://creativecommons.org/licenses/by-sa/3.0
path = Path(sys.prefix) / "share" / "ahriman" / "templates" / "shell" path = Path(sys.prefix) / "share" / "ahriman" / "templates" / "shell"
StringPrinter(path.read_text(encoding="utf8")).print(verbose=False) StringPrinter(path.read_text(encoding="utf8")).print(verbose=False)
# we only want to pass application isntance inside # we only want to pass application instance inside
if args.code is None: if args.code is None:
code.interact(local={"application": application}) code.interact(local={"application": application})
else: else:

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -32,7 +30,7 @@ class Sign(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -19,7 +19,7 @@
# #
import argparse import argparse
from typing import Callable, Iterable, Tuple, Type from collections.abc import Callable
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -37,7 +37,7 @@ class Status(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -53,9 +53,9 @@ class Status(Handler):
client = Application(architecture, configuration, report=True, unsafe=unsafe).repository.reporter client = Application(architecture, configuration, report=True, unsafe=unsafe).repository.reporter
if args.ahriman: if args.ahriman:
service_status = client.get_internal() service_status = client.get_internal()
StatusPrinter(service_status.status).print(args.info) StatusPrinter(service_status.status).print(verbose=args.info)
if args.package: if args.package:
packages: Iterable[Tuple[Package, BuildStatus]] = sum( packages: list[tuple[Package, BuildStatus]] = sum(
(client.get(base) for base in args.package), (client.get(base) for base in args.package),
start=[]) start=[])
else: else:
@ -63,8 +63,8 @@ class Status(Handler):
Status.check_if_empty(args.exit_code, not packages) Status.check_if_empty(args.exit_code, not packages)
comparator: Callable[[Tuple[Package, BuildStatus]], str] = lambda item: item[0].base comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda item: item[0].base
filter_fn: Callable[[Tuple[Package, BuildStatus]], bool] =\ filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\
lambda item: args.status is None or item[1].status == args.status lambda item: args.status is None or item[1].status == args.status
for package, package_status in sorted(filter(filter_fn, packages), key=comparator): for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
PackagePrinter(package, package_status).print(args.info) PackagePrinter(package, package_status).print(verbose=args.info)

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -35,7 +33,7 @@ class StatusUpdate(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -36,7 +34,7 @@ class Structure(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -51,6 +49,6 @@ class Structure(Handler):
application = Application(architecture, configuration, report=report, unsafe=unsafe) application = Application(architecture, configuration, report=report, unsafe=unsafe)
packages = application.repository.packages() packages = application.repository.packages()
tree = Tree.resolve(packages, application.repository.paths, application.database) tree = Tree.resolve(packages)
for num, level in enumerate(tree): for num, level in enumerate(tree):
TreePrinter(num, level).print(verbose=True, separator=" ") TreePrinter(num, level).print(verbose=True, separator=" ")

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -33,7 +31,7 @@ class Triggers(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line

View File

@ -20,8 +20,6 @@
import argparse import argparse
import shlex import shlex
from typing import List, Type
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters import StringPrinter
@ -35,7 +33,7 @@ class UnsafeCommands(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -56,20 +54,20 @@ class UnsafeCommands(Handler):
UnsafeCommands.check_unsafe(args.command, unsafe_commands, parser) UnsafeCommands.check_unsafe(args.command, unsafe_commands, parser)
@staticmethod @staticmethod
def check_unsafe(command: str, unsafe_commands: List[str], parser: argparse.ArgumentParser) -> None: def check_unsafe(command: str, unsafe_commands: list[str], parser: argparse.ArgumentParser) -> None:
""" """
check if command is unsafe check if command is unsafe
Args: Args:
command(str): command to check command(str): command to check
unsafe_commands(List[str]): list of unsafe commands unsafe_commands(list[str]): list of unsafe commands
parser(argparse.ArgumentParser): generated argument parser parser(argparse.ArgumentParser): generated argument parser
""" """
args = parser.parse_args(shlex.split(command)) args = parser.parse_args(shlex.split(command))
UnsafeCommands.check_if_empty(True, args.command in unsafe_commands) UnsafeCommands.check_if_empty(True, args.command in unsafe_commands)
@staticmethod @staticmethod
def get_unsafe_commands(parser: argparse.ArgumentParser) -> List[str]: def get_unsafe_commands(parser: argparse.ArgumentParser) -> list[str]:
""" """
extract unsafe commands from argument parser extract unsafe commands from argument parser
@ -77,7 +75,7 @@ class UnsafeCommands(Handler):
parser(argparse.ArgumentParser): generated argument parser parser(argparse.ArgumentParser): generated argument parser
Returns: Returns:
List[str]: list of commands with default unsafe flag list[str]: list of commands with default unsafe flag
""" """
# should never fail # should never fail
# pylint: disable=protected-access # pylint: disable=protected-access

View File

@ -19,7 +19,7 @@
# #
import argparse import argparse
from typing import Callable, Type from collections.abc import Callable
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -32,7 +32,7 @@ class Update(Handler):
""" """
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -53,6 +53,7 @@ class Update(Handler):
if args.dry_run: if args.dry_run:
return return
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
result = application.update(packages) result = application.update(packages)
Update.check_if_empty(args.exit_code, result.is_empty) Update.check_if_empty(args.exit_code, result.is_empty)

View File

@ -21,7 +21,6 @@ import argparse
import getpass import getpass
from pathlib import Path from pathlib import Path
from typing import Optional, Tuple, Type
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -40,7 +39,7 @@ class Users(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -119,7 +118,7 @@ class Users(Handler):
path.chmod(0o600) path.chmod(0o600)
@staticmethod @staticmethod
def get_salt(configuration: Configuration, salt_length: int = 20) -> Tuple[Optional[str], str]: def get_salt(configuration: Configuration, salt_length: int = 20) -> tuple[str | None, str]:
""" """
get salt from configuration or create new string get salt from configuration or create new string
@ -128,7 +127,7 @@ class Users(Handler):
salt_length(int, optional): salt length (Default value = 20) salt_length(int, optional): salt length (Default value = 20)
Returns: Returns:
Tuple[Optional[str], str]: tuple containing salt from configuration if any and actual salt which must be tuple[str | None, str]: tuple containing salt from configuration if any and actual salt which must be
used for password hash used for password hash
""" """
if salt := configuration.get("auth", "salt", fallback=None): if salt := configuration.get("auth", "salt", fallback=None):

View File

@ -20,7 +20,7 @@
import argparse import argparse
import copy import copy
from typing import Any, Dict, Type from typing import Any
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -39,7 +39,7 @@ class Validate(Handler):
ALLOW_AUTO_ARCHITECTURE_RUN = False ALLOW_AUTO_ARCHITECTURE_RUN = False
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -52,7 +52,7 @@ class Validate(Handler):
unsafe(bool): if set no user check will be performed before path creation unsafe(bool): if set no user check will be performed before path creation
""" """
schema = Validate.schema(architecture, configuration) schema = Validate.schema(architecture, configuration)
validator = Validator(instance=configuration, schema=schema) validator = Validator(configuration=configuration, schema=schema)
if validator.validate(configuration.dump()): if validator.validate(configuration.dump()):
return # no errors found return # no errors found
@ -112,17 +112,17 @@ class Validate(Handler):
return schema return schema
@staticmethod @staticmethod
def schema_merge(source: Dict[str, Any], schema: Dict[str, Any]) -> Dict[str, Any]: def schema_merge(source: dict[str, Any], schema: dict[str, Any]) -> dict[str, Any]:
""" """
merge child schema into source. In case if source already contains values, new keys will be added merge child schema into source. In case if source already contains values, new keys will be added
(possibly with overrides - in case if such key already set also) (possibly with overrides - in case if such key already set also)
Args: Args:
source(Dict[str, Any]): source (current) schema into which will be merged source(dict[str, Any]): source (current) schema into which will be merged
schema(Dict[str, Any]): new schema to be merged schema(dict[str, Any]): new schema to be merged
Returns: Returns:
Dict[str, Any]: schema with added elements from source schema if they were set before and not presented dict[str, Any]: schema with added elements from source schema if they were set before and not presented
in the new one. Note, that schema will be modified in-place in the new one. Note, that schema will be modified in-place
""" """
for key, value in source.items(): for key, value in source.items():

View File

@ -18,10 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import argparse import argparse
import pkg_resources import re
import sys import sys
from typing import Dict, List, Tuple, Type from collections.abc import Generator
from importlib import metadata
from ahriman import version from ahriman import version
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
@ -32,12 +33,16 @@ from ahriman.core.formatters import VersionPrinter
class Versions(Handler): class Versions(Handler):
""" """
version handler version handler
Attributes:
PEP423_PACKAGE_NAME(str): (class attribute) special regex for valid PEP423 package name
""" """
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
PEP423_PACKAGE_NAME = re.compile(r"^[A-Za-z0-9._-]+")
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -51,37 +56,43 @@ class Versions(Handler):
""" """
VersionPrinter(f"Module version {version.__version__}", VersionPrinter(f"Module version {version.__version__}",
{"Python": sys.version}).print(verbose=False, separator=" ") {"Python": sys.version}).print(verbose=False, separator=" ")
packages = Versions.package_dependencies("ahriman", ("pacman", "s3", "web")) packages = Versions.package_dependencies("ahriman")
VersionPrinter("Installed packages", packages).print(verbose=False, separator=" ") VersionPrinter("Installed packages", dict(packages)).print(verbose=False, separator=" ")
@staticmethod @staticmethod
def package_dependencies(root: str, root_extras: Tuple[str, ...] = ()) -> Dict[str, str]: def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]:
""" """
extract list of ahriman package dependencies installed into system with their versions extract list of ahriman package dependencies installed into system with their versions
Args: Args:
root(str): root package name root(str): root package name
root_extras(Tuple[str, ...], optional): extras for the root package (Default value = ())
Returns: Returns:
Dict[str, str]: map of installed dependency to its version Generator[tuple[str, str], None, None]: map of installed dependency to its version
""" """
resources: Dict[str, pkg_resources.Distribution] = pkg_resources.working_set.by_key # type: ignore def dependencies_by_key(key: str) -> Generator[str, None, None]:
# in importlib it returns requires in the following format
# ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
try:
requires = metadata.requires(key)
except metadata.PackageNotFoundError:
return
for entry in requires or []:
yield from Versions.PEP423_PACKAGE_NAME.findall(entry)
def dependencies_by_key(key: str, extras: Tuple[str, ...] = ()) -> List[str]: keys: list[str] = []
return [entry.key for entry in resources[key].requires(extras)] portion = set(dependencies_by_key(root))
keys: List[str] = []
portion = {key for key in dependencies_by_key(root, root_extras) if key in resources}
while portion: while portion:
keys.extend(portion) keys.extend(portion)
portion = { portion = {
key key
for key in sum((dependencies_by_key(key) for key in portion), start=[]) for key in sum((list(dependencies_by_key(key)) for key in portion), start=[])
if key not in keys and key in resources if key not in keys
} }
return { for key in keys:
resource.project_name: resource.version try:
for resource in map(lambda key: resources[key], keys) distribution = metadata.distribution(key)
} yield distribution.name, distribution.version
except metadata.PackageNotFoundError:
continue

View File

@ -19,8 +19,6 @@
# #
import argparse import argparse
from typing import Type
from ahriman.application.handlers import Handler from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.spawn import Spawn from ahriman.core.spawn import Spawn
@ -35,7 +33,7 @@ class Web(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *, def run(cls, args: argparse.Namespace, architecture: str, configuration: Configuration, *,
report: bool, unsafe: bool) -> None: report: bool, unsafe: bool) -> None:
""" """
callback for command line callback for command line
@ -55,3 +53,7 @@ class Web(Handler):
application = setup_service(architecture, configuration, spawner) application = setup_service(architecture, configuration, spawner)
run_server(application) run_server(application)
# terminate spawn process at the last
spawner.stop()
spawner.join()

View File

@ -17,12 +17,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from __future__ import annotations
import argparse import argparse
from types import TracebackType from types import TracebackType
from typing import Literal, Optional, Type from typing import Literal, Self
from ahriman import version from ahriman import version
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -68,7 +66,6 @@ class Lock(LazyLogging):
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
""" """
self.path = args.lock.with_stem(f"{args.lock.stem}_{architecture}") if args.lock is not None else None self.path = args.lock.with_stem(f"{args.lock.stem}_{architecture}") if args.lock is not None else None
print(self.path)
self.force = args.force self.force = args.force
self.unsafe = args.unsafe self.unsafe = args.unsafe
@ -112,7 +109,7 @@ class Lock(LazyLogging):
except FileExistsError: except FileExistsError:
raise DuplicateRunError() raise DuplicateRunError()
def __enter__(self) -> Lock: def __enter__(self) -> Self:
""" """
default workflow is the following: default workflow is the following:
@ -121,6 +118,9 @@ class Lock(LazyLogging):
3. Check web status watcher status 3. Check web status watcher status
4. Create lock file 4. Create lock file
5. Report to status page if enabled 5. Report to status page if enabled
Returns:
Self: always instance of self
""" """
self.check_user() self.check_user()
self.check_version() self.check_version()
@ -128,14 +128,14 @@ class Lock(LazyLogging):
self.reporter.update_self(BuildStatusEnum.Building) self.reporter.update_self(BuildStatusEnum.Building)
return self return self
def __exit__(self, exc_type: Optional[Type[Exception]], exc_val: Optional[Exception], def __exit__(self, exc_type: type[Exception] | None, exc_val: Exception | None,
exc_tb: TracebackType) -> Literal[False]: exc_tb: TracebackType) -> Literal[False]:
""" """
remove lock file when done remove lock file when done
Args: Args:
exc_type(Optional[Type[Exception]]): exception type name if any exc_type(type[Exception] | None): exception type name if any
exc_val(Optional[Exception]): exception raised if any exc_val(Exception | None): exception raised if any
exc_tb(TracebackType): exception traceback if any exc_tb(TracebackType): exception traceback if any
Returns: Returns:

View File

@ -17,8 +17,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Iterator
from contextvars import ContextVar from contextvars import ContextVar
from typing import Any, Dict, Iterator, TypeVar from typing import Any, TypeVar
from ahriman.models.context_key import ContextKey from ahriman.models.context_key import ContextKey
@ -35,7 +36,7 @@ class _Context:
""" """
default constructor. Must not be used directly default constructor. Must not be used directly
""" """
self._content: Dict[str, Any] = {} self._content: dict[str, Any] = {}
def get(self, key: ContextKey[T]) -> T: def get(self, key: ContextKey[T]) -> T:
""" """

View File

@ -19,12 +19,15 @@
# #
import shutil import shutil
from collections.abc import Callable, Generator
from pathlib import Path from pathlib import Path
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore[import]
from typing import Any, Callable, Generator, Set from typing import Any
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.util import trim_package
from ahriman.models.pacman_synchronization import PacmanSynchronization
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -38,28 +41,28 @@ class Pacman(LazyLogging):
handle: Handle handle: Handle
def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None: def __init__(self, architecture: str, configuration: Configuration, *,
refresh_database: PacmanSynchronization) -> None:
""" """
default constructor default constructor
Args: Args:
architecture(str): repository architecture architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
refresh_database(int): synchronize local cache to remote. If set to ``0``, no synchronization will be refresh_database(PacmanSynchronization): synchronize local cache to remote
enabled, if set to ``1`` - normal synchronization, if set to ``2`` - force synchronization
""" """
self.__create_handle_fn: Callable[[], Handle] = lambda: self.__create_handle( self.__create_handle_fn: Callable[[], Handle] = lambda: self.__create_handle(
architecture, configuration, refresh_database=refresh_database) architecture, configuration, refresh_database=refresh_database)
def __create_handle(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> Handle: def __create_handle(self, architecture: str, configuration: Configuration, *,
refresh_database: PacmanSynchronization) -> Handle:
""" """
create lazy handle function create lazy handle function
Args: Args:
architecture(str): repository architecture architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
refresh_database(int): synchronize local cache to remote. If set to ``0``, no synchronization will be refresh_database(PacmanSynchronization): synchronize local cache to remote
enabled, if set to ``1`` - normal synchronization, if set to ``2`` - force synchronization
Returns: Returns:
Handle: fully initialized pacman handle Handle: fully initialized pacman handle
@ -77,7 +80,7 @@ class Pacman(LazyLogging):
self.database_copy(handle, database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache) self.database_copy(handle, database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
if use_ahriman_cache and refresh_database: if use_ahriman_cache and refresh_database:
self.database_sync(handle, force=refresh_database > 1) self.database_sync(handle, force=refresh_database == PacmanSynchronization.Force)
return handle return handle
@ -165,18 +168,20 @@ class Pacman(LazyLogging):
continue continue
yield package yield package
def packages(self) -> Set[str]: def packages(self) -> set[str]:
""" """
get list of packages known for alpm get list of packages known for alpm
Returns: Returns:
Set[str]: list of package names set[str]: list of package names
""" """
result: Set[str] = set() result: set[str] = set()
for database in self.handle.get_syncdbs(): for database in self.handle.get_syncdbs():
for package in database.pkgcache: for package in database.pkgcache:
result.add(package.name) # package itself # package itself
result.update(package.provides) # provides list for meta-packages result.add(package.name)
# provides list for meta-packages
result.update(trim_package(provides) for provides in package.provides)
return result return result

View File

@ -19,7 +19,7 @@
# #
import requests import requests
from typing import Any, Dict, List, Type from typing import Any
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import Remote from ahriman.core.alpm.remote import Remote
@ -44,28 +44,8 @@ class AUR(Remote):
DEFAULT_RPC_VERSION = "5" DEFAULT_RPC_VERSION = "5"
DEFAULT_TIMEOUT = 30 DEFAULT_TIMEOUT = 30
@staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
"""
parse RPC response to package list
Args:
response(Dict[str, Any]): RPC response json
Returns:
List[AURPackage]: list of parsed packages
Raises:
InvalidPackageInfo: for error API response
"""
response_type = response["type"]
if response_type == "error":
error_details = response.get("error", "Unknown API error")
raise PackageInfoError(error_details)
return [AURPackage.from_json(package) for package in response["results"]]
@classmethod @classmethod
def remote_git_url(cls: Type[Remote], package_base: str, repository: str) -> str: def remote_git_url(cls, package_base: str, repository: str) -> str:
""" """
generate remote git url from the package base generate remote git url from the package base
@ -79,7 +59,7 @@ class AUR(Remote):
return f"{AUR.DEFAULT_AUR_URL}/{package_base}.git" return f"{AUR.DEFAULT_AUR_URL}/{package_base}.git"
@classmethod @classmethod
def remote_web_url(cls: Type[Remote], package_base: str) -> str: def remote_web_url(cls, package_base: str) -> str:
""" """
generate remote web url from the package base generate remote web url from the package base
@ -91,7 +71,27 @@ class AUR(Remote):
""" """
return f"{AUR.DEFAULT_AUR_URL}/packages/{package_base}" return f"{AUR.DEFAULT_AUR_URL}/packages/{package_base}"
def make_request(self, request_type: str, *args: str, **kwargs: str) -> List[AURPackage]: @staticmethod
def parse_response(response: dict[str, Any]) -> list[AURPackage]:
"""
parse RPC response to package list
Args:
response(dict[str, Any]): RPC response json
Returns:
list[AURPackage]: list of parsed packages
Raises:
InvalidPackageInfo: for error API response
"""
response_type = response["type"]
if response_type == "error":
error_details = response.get("error", "Unknown API error")
raise PackageInfoError(error_details)
return [AURPackage.from_json(package) for package in response["results"]]
def make_request(self, request_type: str, *args: str, **kwargs: str) -> list[AURPackage]:
""" """
perform request to AUR RPC perform request to AUR RPC
@ -101,9 +101,9 @@ class AUR(Remote):
**kwargs(str): list of additional named parameters like by **kwargs(str): list of additional named parameters like by
Returns: Returns:
List[AURPackage]: response parsed to package list list[AURPackage]: response parsed to package list
""" """
query: Dict[str, Any] = { query: dict[str, Any] = {
"type": request_type, "type": request_type,
"v": self.DEFAULT_RPC_VERSION "v": self.DEFAULT_RPC_VERSION
} }
@ -145,7 +145,7 @@ class AUR(Remote):
except StopIteration: except StopIteration:
raise UnknownPackageError(package_name) raise UnknownPackageError(package_name)
def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]: def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
@ -154,6 +154,6 @@ class AUR(Remote):
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
""" """
return self.make_request("search", *keywords, by="name-desc") return self.make_request("search", *keywords, by="name-desc")

View File

@ -19,7 +19,7 @@
# #
import requests import requests
from typing import Any, Dict, List, Type from typing import Any
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import Remote from ahriman.core.alpm.remote import Remote
@ -34,7 +34,7 @@ class Official(Remote):
Attributes: Attributes:
DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url
DEFAULT_SEARCH_REPOSITORIES(List[str]): (class attribute) default list of repositories to search DEFAULT_SEARCH_REPOSITORIES(list[str]): (class attribute) default list of repositories to search
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
""" """
@ -44,26 +44,8 @@ class Official(Remote):
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json" DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
DEFAULT_TIMEOUT = 30 DEFAULT_TIMEOUT = 30
@staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
"""
parse RPC response to package list
Args:
response(Dict[str, Any]): RPC response json
Returns:
List[AURPackage]: list of parsed packages
Raises:
InvalidPackageInfo: for error API response
"""
if not response["valid"]:
raise PackageInfoError("API validation error")
return [AURPackage.from_repo(package) for package in response["results"]]
@classmethod @classmethod
def remote_git_url(cls: Type[Remote], package_base: str, repository: str) -> str: def remote_git_url(cls, package_base: str, repository: str) -> str:
""" """
generate remote git url from the package base generate remote git url from the package base
@ -79,7 +61,7 @@ class Official(Remote):
return "https://github.com/archlinux/svntogit-community.git" return "https://github.com/archlinux/svntogit-community.git"
@classmethod @classmethod
def remote_web_url(cls: Type[Remote], package_base: str) -> str: def remote_web_url(cls, package_base: str) -> str:
""" """
generate remote web url from the package base generate remote web url from the package base
@ -91,7 +73,25 @@ class Official(Remote):
""" """
return f"{Official.DEFAULT_ARCHLINUX_URL}/packages/{package_base}" return f"{Official.DEFAULT_ARCHLINUX_URL}/packages/{package_base}"
def make_request(self, *args: str, by: str) -> List[AURPackage]: @staticmethod
def parse_response(response: dict[str, Any]) -> list[AURPackage]:
"""
parse RPC response to package list
Args:
response(dict[str, Any]): RPC response json
Returns:
list[AURPackage]: list of parsed packages
Raises:
InvalidPackageInfo: for error API response
"""
if not response["valid"]:
raise PackageInfoError("API validation error")
return [AURPackage.from_repo(package) for package in response["results"]]
def make_request(self, *args: str, by: str) -> list[AURPackage]:
""" """
perform request to official repositories RPC perform request to official repositories RPC
@ -100,7 +100,7 @@ class Official(Remote):
by(str): search by the field by(str): search by the field
Returns: Returns:
List[AURPackage]: response parsed to package list list[AURPackage]: response parsed to package list
""" """
try: try:
response = requests.get( response = requests.get(
@ -133,7 +133,7 @@ class Official(Remote):
except StopIteration: except StopIteration:
raise UnknownPackageError(package_name) raise UnknownPackageError(package_name)
def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]: def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
@ -142,6 +142,6 @@ class Official(Remote):
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
""" """
return self.make_request(*keywords, by="q") return self.make_request(*keywords, by="q")

View File

@ -17,10 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from __future__ import annotations
from typing import Dict, List, Type
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
@ -44,7 +40,7 @@ class Remote(LazyLogging):
""" """
@classmethod @classmethod
def info(cls: Type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage: def info(cls, package_name: str, *, pacman: Pacman) -> AURPackage:
""" """
get package info by its name get package info by its name
@ -58,7 +54,7 @@ class Remote(LazyLogging):
return cls().package_info(package_name, pacman=pacman) return cls().package_info(package_name, pacman=pacman)
@classmethod @classmethod
def multisearch(cls: Type[Remote], *keywords: str, pacman: Pacman) -> List[AURPackage]: def multisearch(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]:
""" """
search in remote repository by using API with multiple words. This method is required in order to handle search in remote repository by using API with multiple words. This method is required in order to handle
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
@ -68,10 +64,10 @@ class Remote(LazyLogging):
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages each of them matches all search terms list[AURPackage]: list of packages each of them matches all search terms
""" """
instance = cls() instance = cls()
packages: Dict[str, AURPackage] = {} packages: dict[str, AURPackage] = {}
for term in filter(lambda word: len(word) >= 3, keywords): for term in filter(lambda word: len(word) >= 3, keywords):
portion = instance.search(term, pacman=pacman) portion = instance.search(term, pacman=pacman)
packages = { packages = {
@ -82,7 +78,7 @@ class Remote(LazyLogging):
return list(packages.values()) return list(packages.values())
@classmethod @classmethod
def remote_git_url(cls: Type[Remote], package_base: str, repository: str) -> str: def remote_git_url(cls, package_base: str, repository: str) -> str:
""" """
generate remote git url from the package base generate remote git url from the package base
@ -99,7 +95,7 @@ class Remote(LazyLogging):
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def remote_web_url(cls: Type[Remote], package_base: str) -> str: def remote_web_url(cls, package_base: str) -> str:
""" """
generate remote web url from the package base generate remote web url from the package base
@ -115,7 +111,7 @@ class Remote(LazyLogging):
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def search(cls: Type[Remote], *keywords: str, pacman: Pacman) -> List[AURPackage]: def search(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
@ -124,7 +120,7 @@ class Remote(LazyLogging):
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
""" """
return cls().package_search(*keywords, pacman=pacman) return cls().package_search(*keywords, pacman=pacman)
@ -144,7 +140,7 @@ class Remote(LazyLogging):
""" """
raise NotImplementedError raise NotImplementedError
def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]: def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
@ -153,7 +149,7 @@ class Remote(LazyLogging):
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
Raises: Raises:
NotImplementedError: not implemented method NotImplementedError: not implemented method

View File

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from pathlib import Path from pathlib import Path
from typing import List
from ahriman.core.exceptions import BuildError from ahriman.core.exceptions import BuildError
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
@ -33,20 +32,20 @@ class Repo(LazyLogging):
Attributes: Attributes:
name(str): repository name name(str): repository name
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive sign_args(list[str]): additional args which have to be used to sign repository archive
uid(int): uid of the repository owner user uid(int): uid of the repository owner user
""" """
_check_output = check_output _check_output = check_output
def __init__(self, name: str, paths: RepositoryPaths, sign_args: List[str]) -> None: def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str]) -> None:
""" """
default constructor default constructor
Args: Args:
name(str): repository name name(str): repository name
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
sign_args(List[str]): additional args which have to be used to sign repository archive sign_args(list[str]): additional args which have to be used to sign repository archive
""" """
self.name = name self.name = name
self.paths = paths self.paths = paths

View File

@ -19,8 +19,6 @@
# #
from __future__ import annotations from __future__ import annotations
from typing import Optional, Type
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
@ -64,8 +62,8 @@ class Auth(LazyLogging):
""" """
return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#login-modal" style="text-decoration: none"><i class="bi bi-box-arrow-in-right"></i> login</button>""" return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#login-modal" style="text-decoration: none"><i class="bi bi-box-arrow-in-right"></i> login</button>"""
@classmethod @staticmethod
def load(cls: Type[Auth], configuration: Configuration, database: SQLite) -> Auth: def load(configuration: Configuration, database: SQLite) -> Auth:
""" """
load authorization module from settings load authorization module from settings
@ -83,15 +81,15 @@ class Auth(LazyLogging):
if provider == AuthSettings.OAuth: if provider == AuthSettings.OAuth:
from ahriman.core.auth.oauth import OAuth from ahriman.core.auth.oauth import OAuth
return OAuth(configuration, database) return OAuth(configuration, database)
return cls(configuration) return Auth(configuration)
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool: async def check_credentials(self, username: str | None, password: str | None) -> bool:
""" """
validate user password validate user password
Args: Args:
username(Optional[str]): username username(str | None): username
password(Optional[str]): entered password password(str | None): entered password
Returns: Returns:
bool: True in case if password matches, False otherwise bool: True in case if password matches, False otherwise
@ -99,12 +97,12 @@ class Auth(LazyLogging):
del username, password del username, password
return True return True
async def known_username(self, username: Optional[str]) -> bool: async def known_username(self, username: str | None) -> bool:
""" """
check if user is known check if user is known
Args: Args:
username(Optional[str]): username username(str | None): username
Returns: Returns:
bool: True in case if user is known and can be authorized and False otherwise bool: True in case if user is known and can be authorized and False otherwise
@ -112,14 +110,14 @@ class Auth(LazyLogging):
del username del username
return True return True
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool: async def verify_access(self, username: str, required: UserAccess, context: str | None) -> bool:
""" """
validate if user has access to requested resource validate if user has access to requested resource
Args: Args:
username(str): username username(str): username
required(UserAccess): required access level required(UserAccess): required access level
context(Optional[str]): URI request path context(str | None): URI request path
Returns: Returns:
bool: True in case if user is allowed to do this request and False otherwise bool: True in case if user is allowed to do this request and False otherwise

View File

@ -20,7 +20,7 @@
from typing import Any from typing import Any
try: try:
import aiohttp_security # type: ignore import aiohttp_security # type: ignore[import]
_has_aiohttp_security = True _has_aiohttp_security = True
except ImportError: except ImportError:
_has_aiohttp_security = False _has_aiohttp_security = False

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import Optional
from ahriman.core.auth import Auth from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
@ -50,13 +48,13 @@ class Mapping(Auth):
self.database = database self.database = database
self.salt = configuration.get("auth", "salt") self.salt = configuration.get("auth", "salt")
async def check_credentials(self, username: Optional[str], password: Optional[str]) -> bool: async def check_credentials(self, username: str | None, password: str | None) -> bool:
""" """
validate user password validate user password
Args: Args:
username(Optional[str]): username username(str | None): username
password(Optional[str]): entered password password(str | None): entered password
Returns: Returns:
bool: True in case if password matches, False otherwise bool: True in case if password matches, False otherwise
@ -66,7 +64,7 @@ class Mapping(Auth):
user = self.get_user(username) user = self.get_user(username)
return user is not None and user.check_credentials(password, self.salt) return user is not None and user.check_credentials(password, self.salt)
def get_user(self, username: str) -> Optional[User]: def get_user(self, username: str) -> User | None:
""" """
retrieve user from in-memory mapping retrieve user from in-memory mapping
@ -74,30 +72,30 @@ class Mapping(Auth):
username(str): username username(str): username
Returns: Returns:
Optional[User]: user descriptor if username is known and None otherwise User | None: user descriptor if username is known and None otherwise
""" """
return self.database.user_get(username) return self.database.user_get(username)
async def known_username(self, username: Optional[str]) -> bool: async def known_username(self, username: str | None) -> bool:
""" """
check if user is known check if user is known
Args: Args:
username(Optional[str]): username username(str | None): username
Returns: Returns:
bool: True in case if user is known and can be authorized and False otherwise bool: True in case if user is known and can be authorized and False otherwise
""" """
return username is not None and self.get_user(username) is not None return username is not None and self.get_user(username) is not None
async def verify_access(self, username: str, required: UserAccess, context: Optional[str]) -> bool: async def verify_access(self, username: str, required: UserAccess, context: str | None) -> bool:
""" """
validate if user has access to requested resource validate if user has access to requested resource
Args: Args:
username(str): username username(str): username
required(UserAccess): required access level required(UserAccess): required access level
context(Optional[str]): URI request path context(str | None): URI request path
Returns: Returns:
bool: True in case if user is allowed to do this request and False otherwise bool: True in case if user is allowed to do this request and False otherwise

View File

@ -19,8 +19,6 @@
# #
import aioauth_client import aioauth_client
from typing import Optional, Type
from ahriman.core.auth.mapping import Mapping from ahriman.core.auth.mapping import Mapping
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
@ -72,7 +70,7 @@ class OAuth(Mapping):
return """<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-google"></i> login</a>""" return """<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-google"></i> login</a>"""
@staticmethod @staticmethod
def get_provider(name: str) -> Type[aioauth_client.OAuth2Client]: def get_provider(name: str) -> type[aioauth_client.OAuth2Client]:
""" """
load OAuth2 provider by name load OAuth2 provider by name
@ -80,12 +78,12 @@ class OAuth(Mapping):
name(str): name of the provider. Must be valid class defined in aioauth-client library name(str): name of the provider. Must be valid class defined in aioauth-client library
Returns: Returns:
Type[aioauth_client.OAuth2Client]: loaded provider type type[aioauth_client.OAuth2Client]: loaded provider type
Raises: Raises:
InvalidOption: in case if invalid OAuth provider name supplied InvalidOption: in case if invalid OAuth provider name supplied
""" """
provider: Type[aioauth_client.OAuth2Client] = getattr(aioauth_client, name) provider: type[aioauth_client.OAuth2Client] = getattr(aioauth_client, name)
try: try:
is_oauth2_client = issubclass(provider, aioauth_client.OAuth2Client) is_oauth2_client = issubclass(provider, aioauth_client.OAuth2Client)
except TypeError: # what if it is random string? except TypeError: # what if it is random string?
@ -114,7 +112,7 @@ class OAuth(Mapping):
uri: str = client.get_authorize_url(scope=self.scopes, redirect_uri=self.redirect_uri) uri: str = client.get_authorize_url(scope=self.scopes, redirect_uri=self.redirect_uri)
return uri return uri
async def get_oauth_username(self, code: str) -> Optional[str]: async def get_oauth_username(self, code: str) -> str | None:
""" """
extract OAuth username from remote extract OAuth username from remote
@ -122,7 +120,7 @@ class OAuth(Mapping):
code(str): authorization code provided by external service code(str): authorization code provided by external service
Returns: Returns:
Optional[str]: username as is in OAuth provider str | None: username as is in OAuth provider
""" """
try: try:
client = self.get_client() client = self.get_client()
@ -130,7 +128,7 @@ class OAuth(Mapping):
client.access_token = access_token client.access_token = access_token
user, _ = await client.user_info() user, _ = await client.user_info()
username: str = user.email # type: ignore username: str = user.email # type: ignore[attr-defined]
return username return username
except Exception: except Exception:
self.logger.exception("got exception while performing request") self.logger.exception("got exception while performing request")

View File

@ -20,7 +20,6 @@
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import List, Optional
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.util import check_output, utcnow, walk from ahriman.core.util import check_output, utcnow, walk
@ -44,7 +43,7 @@ class Sources(LazyLogging):
_check_output = check_output _check_output = check_output
@staticmethod @staticmethod
def extend_architectures(sources_dir: Path, architecture: str) -> List[PkgbuildPatch]: def extend_architectures(sources_dir: Path, architecture: str) -> list[PkgbuildPatch]:
""" """
extend existing PKGBUILD with repository architecture extend existing PKGBUILD with repository architecture
@ -53,7 +52,7 @@ class Sources(LazyLogging):
architecture(str): repository architecture architecture(str): repository architecture
Returns: Returns:
List[PkgbuildPatch]: generated patch for PKGBUILD architectures if required list[PkgbuildPatch]: generated patch for PKGBUILD architectures if required
""" """
architectures = Package.supported_architectures(sources_dir) architectures = Package.supported_architectures(sources_dir)
if "any" in architectures: # makepkg does not like when there is any other arch except for any if "any" in architectures: # makepkg does not like when there is any other arch except for any
@ -62,13 +61,13 @@ class Sources(LazyLogging):
return [PkgbuildPatch("arch", list(architectures))] return [PkgbuildPatch("arch", list(architectures))]
@staticmethod @staticmethod
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None: def fetch(sources_dir: Path, remote: RemoteSource | None) -> None:
""" """
either clone repository or update it to origin/``remote.branch`` either clone repository or update it to origin/``remote.branch``
Args: Args:
sources_dir(Path): local path to fetch sources_dir(Path): local path to fetch
remote(Optional[RemoteSource]): remote target (from where to fetch) remote(RemoteSource | None): remote target (from where to fetch)
""" """
instance = Sources() instance = Sources()
# local directory exists and there is .git directory # local directory exists and there is .git directory
@ -127,14 +126,14 @@ class Sources(LazyLogging):
cwd=sources_dir, logger=instance.logger) cwd=sources_dir, logger=instance.logger)
@staticmethod @staticmethod
def load(sources_dir: Path, package: Package, patches: List[PkgbuildPatch], paths: RepositoryPaths) -> None: def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> None:
""" """
fetch sources from remote and apply patches fetch sources from remote and apply patches
Args: Args:
sources_dir(Path): local path to fetch sources_dir(Path): local path to fetch
package(Package): package definitions package(Package): package definitions
patches(List[PkgbuildPatch]): optional patch to be applied patches(list[PkgbuildPatch]): optional patch to be applied
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
""" """
instance = Sources() instance = Sources()
@ -165,7 +164,7 @@ class Sources(LazyLogging):
return f"{diff}\n" # otherwise, patch will be broken return f"{diff}\n" # otherwise, patch will be broken
@staticmethod @staticmethod
def push(sources_dir: Path, remote: RemoteSource, *pattern: str, commit_author: Optional[str] = None) -> None: def push(sources_dir: Path, remote: RemoteSource, *pattern: str, commit_author: str | None = None) -> None:
""" """
commit selected changes and push files to the remote repository commit selected changes and push files to the remote repository
@ -173,7 +172,7 @@ class Sources(LazyLogging):
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
remote(RemoteSource): remote target, branch and url remote(RemoteSource): remote target, branch and url
*pattern(str): glob patterns *pattern(str): glob patterns
commit_author(Optional[str], optional): commit author in form of git config (i.e. ``user <user@host>``) commit_author(str | None, optional): commit author in form of git config (i.e. ``user <user@host>``)
(Default value = None) (Default value = None)
""" """
instance = Sources() instance = Sources()
@ -192,7 +191,7 @@ class Sources(LazyLogging):
--intent-to-add git flag (Default value = False) --intent-to-add git flag (Default value = False)
""" """
# glob directory to find files which match the specified patterns # glob directory to find files which match the specified patterns
found_files: List[Path] = [] found_files: list[Path] = []
for glob in pattern: for glob in pattern:
found_files.extend(sources_dir.glob(glob)) found_files.extend(sources_dir.glob(glob))
if not found_files: if not found_files:
@ -203,15 +202,15 @@ class Sources(LazyLogging):
Sources._check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files], Sources._check_output("git", "add", *args, *[str(fn.relative_to(sources_dir)) for fn in found_files],
cwd=sources_dir, logger=self.logger) cwd=sources_dir, logger=self.logger)
def commit(self, sources_dir: Path, message: Optional[str] = None, author: Optional[str] = None) -> None: def commit(self, sources_dir: Path, message: str | None = None, author: str | None = None) -> None:
""" """
commit changes commit changes
Args: Args:
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
message(Optional[str], optional): optional commit message if any. If none set, message will be generated message(str | None, optional): optional commit message if any. If none set, message will be generated
according to the current timestamp (Default value = None) according to the current timestamp (Default value = None)
author(Optional[str], optional): optional commit author if any (Default value = None) author(str | None, optional): optional commit author if any (Default value = None)
""" """
if message is None: if message is None:
message = f"Autogenerated commit at {utcnow()}" message = f"Autogenerated commit at {utcnow()}"

View File

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from pathlib import Path from pathlib import Path
from typing import List
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -35,6 +34,10 @@ class Task(LazyLogging):
base package build task base package build task
Attributes: Attributes:
architecture(str): repository architecture
build_command(str): build command
build_flags(list[str]): list of additional flags to pkgctl
name(str): repository name
package(Package): package definitions package(Package): package definitions
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
uid(int): uid of the repository owner user uid(int): uid of the repository owner user
@ -53,14 +56,15 @@ class Task(LazyLogging):
""" """
self.package = package self.package = package
self.paths = paths self.paths = paths
_, self.architecture = configuration.check_loaded()
self.uid, _ = paths.root_owner self.uid, _ = paths.root_owner
self.name = configuration.get("repository", "name")
self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[])
self.build_command = configuration.get("build", "build_command") self.build_command = configuration.get("build", "build_command")
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[]) self.build_flags = configuration.getlist("build", "pkgctl_flags", fallback=[])
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
def build(self, sources_dir: Path) -> List[Path]: def build(self, sources_dir: Path) -> list[Path]:
""" """
run package build run package build
@ -68,12 +72,11 @@ class Task(LazyLogging):
sources_dir(Path): path to where sources are sources_dir(Path): path to where sources are
Returns: Returns:
List[Path]: paths of produced packages list[Path]: paths of produced packages
""" """
command = [self.build_command, "-r", str(self.paths.chroot)] command = [self.build_command, "build"] + self.build_flags
command.extend(self.archbuild_flags) command.extend(["--arch", self.architecture])
command.extend(["--"] + self.makechrootpkg_flags) command.extend(["--repo", self.name])
command.extend(["--"] + self.makepkg_flags)
self.logger.info("using %s for %s", command, self.package.base) self.logger.info("using %s for %s", command, self.package.base)
Task._check_output( Task._check_output(

View File

@ -17,14 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from __future__ import annotations
import configparser import configparser
import shlex import shlex
import sys import sys
from collections.abc import Callable
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple, Type from typing import Any, Self
from ahriman.core.exceptions import InitializeError from ahriman.core.exceptions import InitializeError
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -35,11 +34,11 @@ class Configuration(configparser.RawConfigParser):
extension for built-in configuration parser extension for built-in configuration parser
Attributes: Attributes:
ARCHITECTURE_SPECIFIC_SECTIONS(List[str]): (class attribute) known sections which can be architecture specific. ARCHITECTURE_SPECIFIC_SECTIONS(list[str]): (class attribute) known sections which can be architecture specific.
Required by dump and merging functions Required by dump and merging functions
SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
architecture(Optional[str]): repository architecture architecture(str | None): repository architecture
path(Optional[Path]): path to root configuration file path(Path | None): path to root configuration file
Examples: Examples:
Configuration class provides additional method in order to handle application configuration. Since this class is Configuration class provides additional method in order to handle application configuration. Since this class is
@ -56,14 +55,14 @@ class Configuration(configparser.RawConfigParser):
architecture according to the merge rules. Moreover, the architecture names will be removed from section names. architecture according to the merge rules. Moreover, the architecture names will be removed from section names.
In order to get current settings, the ``check_loaded`` method can be used. This method will raise an In order to get current settings, the ``check_loaded`` method can be used. This method will raise an
``InitializeException`` in case if configuration was not yet loaded:: ``InitializeError`` in case if configuration was not yet loaded::
>>> path, architecture = configuration.check_loaded() >>> path, architecture = configuration.check_loaded()
""" """
ARCHITECTURE_SPECIFIC_SECTIONS = ["alpm", "build", "sign", "web"] ARCHITECTURE_SPECIFIC_SECTIONS = ["alpm", "build", "sign", "web"]
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini" SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
converters: Dict[str, Callable[[str], Any]] # typing guard converters: dict[str, Callable[[str], Any]] # typing guard
def __init__(self, allow_no_value: bool = False) -> None: def __init__(self, allow_no_value: bool = False) -> None:
""" """
@ -77,8 +76,8 @@ class Configuration(configparser.RawConfigParser):
"list": shlex.split, "list": shlex.split,
"path": self._convert_path, "path": self._convert_path,
}) })
self.architecture: Optional[str] = None self.architecture: str | None = None
self.path: Optional[Path] = None self.path: Path | None = None
@property @property
def include(self) -> Path: def include(self) -> Path:
@ -112,7 +111,7 @@ class Configuration(configparser.RawConfigParser):
return RepositoryPaths(self.getpath("repository", "root"), architecture) return RepositoryPaths(self.getpath("repository", "root"), architecture)
@classmethod @classmethod
def from_path(cls: Type[Configuration], path: Path, architecture: str) -> Configuration: def from_path(cls, path: Path, architecture: str) -> Self:
""" """
constructor with full object initialization constructor with full object initialization
@ -121,7 +120,7 @@ class Configuration(configparser.RawConfigParser):
architecture(str): repository architecture architecture(str): repository architecture
Returns: Returns:
Configuration: configuration instance Self: configuration instance
""" """
configuration = cls() configuration = cls()
configuration.load(path) configuration.load(path)
@ -157,26 +156,26 @@ class Configuration(configparser.RawConfigParser):
return path return path
return self.path.parent / path return self.path.parent / path
def check_loaded(self) -> Tuple[Path, str]: def check_loaded(self) -> tuple[Path, str]:
""" """
check if service was actually loaded check if service was actually loaded
Returns: Returns:
Tuple[Path, str]: configuration root path and architecture if loaded tuple[Path, str]: configuration root path and architecture if loaded
Raises: Raises:
InitializeException: in case if architecture and/or path are not set InitializeError: in case if architecture and/or path are not set
""" """
if self.path is None or self.architecture is None: if self.path is None or self.architecture is None:
raise InitializeError("Configuration path and/or architecture are not set") raise InitializeError("Configuration path and/or architecture are not set")
return self.path, self.architecture return self.path, self.architecture
def dump(self) -> Dict[str, Dict[str, str]]: def dump(self) -> dict[str, dict[str, str]]:
""" """
dump configuration to dictionary dump configuration to dictionary
Returns: Returns:
Dict[str, Dict[str, str]]: configuration dump for specific architecture dict[str, dict[str, str]]: configuration dump for specific architecture
""" """
return { return {
section: dict(self[section]) section: dict(self[section])
@ -185,11 +184,11 @@ class Configuration(configparser.RawConfigParser):
# pylint and mypy are too stupid to find these methods # pylint and mypy are too stupid to find these methods
# pylint: disable=missing-function-docstring,multiple-statements,unused-argument # pylint: disable=missing-function-docstring,multiple-statements,unused-argument
def getlist(self, *args: Any, **kwargs: Any) -> List[str]: ... # type: ignore def getlist(self, *args: Any, **kwargs: Any) -> list[str]: ... # type: ignore[empty-body]
def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore[empty-body]
def gettype(self, section: str, architecture: str, *, fallback: Optional[str] = None) -> Tuple[str, str]: def gettype(self, section: str, architecture: str, *, fallback: str | None = None) -> tuple[str, str]:
""" """
get type variable with fallback to old logic. Despite the fact that it has same semantics as other get* methods, get type variable with fallback to old logic. Despite the fact that it has same semantics as other get* methods,
but it has different argument list but it has different argument list
@ -197,11 +196,11 @@ class Configuration(configparser.RawConfigParser):
Args: Args:
section(str): section name section(str): section name
architecture(str): repository architecture architecture(str): repository architecture
fallback(Optional[str], optional): optional fallback type if any. If set, second element of the tuple will fallback(str | None, optional): optional fallback type if any. If set, second element of the tuple will
be always set to this value (Default value = None) be always set to this value (Default value = None)
Returns: Returns:
Tuple[str, str]: section name and found type name tuple[str, str]: section name and found type name
Raises: Raises:
configparser.NoSectionError: in case if no section found configparser.NoSectionError: in case if no section found
@ -213,9 +212,9 @@ class Configuration(configparser.RawConfigParser):
if self.has_section(full_section): if self.has_section(full_section):
return full_section, section return full_section, section
# okay lets just use section as type # okay lets just use section as type
if not self.has_section(section): if self.has_section(section):
raise configparser.NoSectionError(section) return section, section
return section, section raise configparser.NoSectionError(section)
def load(self, path: Path) -> None: def load(self, path: Path) -> None:
""" """

View File

@ -17,13 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import Any, Dict from typing import Any
__all__ = ["CONFIGURATION_SCHEMA", "ConfigurationSchema"] __all__ = ["CONFIGURATION_SCHEMA", "ConfigurationSchema"]
ConfigurationSchema = Dict[str, Dict[str, Any]] ConfigurationSchema = dict[str, dict[str, Any]]
CONFIGURATION_SCHEMA: ConfigurationSchema = { CONFIGURATION_SCHEMA: ConfigurationSchema = {
@ -47,6 +47,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"required": True, "required": True,
"path_exists": True, "path_exists": True,
}, },
"suppress_http_log_errors": {
"type": "boolean",
"coerce": "boolean",
}
}, },
}, },
"alpm": { "alpm": {
@ -60,6 +64,7 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"mirror": { "mirror": {
"type": "string", "type": "string",
"required": True, "required": True,
"is_url": [],
}, },
"repositories": { "repositories": {
"type": "list", "type": "list",
@ -105,9 +110,15 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"client_secret": { "client_secret": {
"type": "string", "type": "string",
}, },
"cookie_secret_key": {
"type": "string",
"minlength": 32,
"maxlength": 64, # we cannot verify maxlength, because base64 representation might be longer than bytes
},
"max_age": { "max_age": {
"type": "integer", "type": "integer",
"coerce": "integer", "coerce": "integer",
"min": 0,
}, },
"oauth_provider": { "oauth_provider": {
"type": "string", "type": "string",
@ -123,11 +134,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"build": { "build": {
"type": "dict", "type": "dict",
"schema": { "schema": {
"archbuild_flags": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
"build_command": { "build_command": {
"type": "string", "type": "string",
"required": True, "required": True,
@ -137,12 +143,7 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"coerce": "list", "coerce": "list",
"schema": {"type": "string"}, "schema": {"type": "string"},
}, },
"makepkg_flags": { "pkgctl_flags": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
"makechrootpkg_flags": {
"type": "list", "type": "list",
"coerce": "list", "coerce": "list",
"schema": {"type": "string"}, "schema": {"type": "string"},
@ -155,6 +156,7 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"vcs_allowed_age": { "vcs_allowed_age": {
"type": "integer", "type": "integer",
"coerce": "integer", "coerce": "integer",
"min": 0,
}, },
}, },
}, },
@ -197,6 +199,7 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"schema": { "schema": {
"address": { "address": {
"type": "string", "type": "string",
"is_url": ["http", "https"],
}, },
"debug": { "debug": {
"type": "boolean", "type": "boolean",
@ -213,9 +216,11 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
}, },
"host": { "host": {
"type": "string", "type": "string",
"is_ip_address": ["localhost"],
}, },
"index_url": { "index_url": {
"type": "string", "type": "string",
"is_url": ["http", "https"],
}, },
"password": { "password": {
"type": "string", "type": "string",
@ -251,44 +256,4 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
}, },
}, },
}, },
"remote-pull": {
"type": "dict",
"schema": {
"target": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
},
},
"remote-push": {
"type": "dict",
"schema": {
"target": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
},
},
"report": {
"type": "dict",
"schema": {
"target": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
},
},
"upload": {
"type": "dict",
"schema": {
"target": {
"type": "list",
"coerce": "list",
"schema": {"type": "string"},
},
},
},
} }

View File

@ -17,19 +17,22 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from cerberus import TypeDefinition, Validator as RootValidator # type: ignore import ipaddress
from cerberus import TypeDefinition, Validator as RootValidator # type: ignore[import]
from pathlib import Path from pathlib import Path
from typing import Any, List from typing import Any
from urllib.parse import urlparse
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
class Validator(RootValidator): # type: ignore class Validator(RootValidator):
""" """
class which defines custom validation methods for the service configuration class which defines custom validation methods for the service configuration
Attributes: Attributes:
instance(Configuration): configuration instance configuration(Configuration): configuration instance
""" """
types_mapping = RootValidator.types_mapping.copy() types_mapping = RootValidator.types_mapping.copy()
@ -40,12 +43,12 @@ class Validator(RootValidator): # type: ignore
default constructor default constructor
Args: Args:
instance(Configuration): configuration instance used for extraction configuration(Configuration): configuration instance used for extraction
*args(Any): positional arguments to be passed to base validator *args(Any): positional arguments to be passed to base validator
**kwargs(): keyword arguments to be passed to base validator **kwargs(): keyword arguments to be passed to base validator
""" """
RootValidator.__init__(self, *args, **kwargs) RootValidator.__init__(self, *args, **kwargs)
self.instance: Configuration = kwargs["instance"] self.configuration: Configuration = kwargs["configuration"]
def _normalize_coerce_absolute_path(self, value: str) -> Path: def _normalize_coerce_absolute_path(self, value: str) -> Path:
""" """
@ -57,7 +60,7 @@ class Validator(RootValidator): # type: ignore
Returns: Returns:
Path: value converted to path instance according to configuration rules Path: value converted to path instance according to configuration rules
""" """
converted: Path = self.instance.converters["path"](value) converted: Path = self.configuration.converters["path"](value)
return converted return converted
def _normalize_coerce_boolean(self, value: str) -> bool: def _normalize_coerce_boolean(self, value: str) -> bool:
@ -71,7 +74,7 @@ class Validator(RootValidator): # type: ignore
bool: value converted to boolean according to configuration rules bool: value converted to boolean according to configuration rules
""" """
# pylint: disable=protected-access # pylint: disable=protected-access
converted: bool = self.instance._convert_to_boolean(value) # type: ignore converted: bool = self.configuration._convert_to_boolean(value) # type: ignore[attr-defined]
return converted return converted
def _normalize_coerce_integer(self, value: str) -> int: def _normalize_coerce_integer(self, value: str) -> int:
@ -87,7 +90,7 @@ class Validator(RootValidator): # type: ignore
del self del self
return int(value) return int(value)
def _normalize_coerce_list(self, value: str) -> List[str]: def _normalize_coerce_list(self, value: str) -> list[str]:
""" """
extract string list from string value extract string list from string value
@ -95,11 +98,52 @@ class Validator(RootValidator): # type: ignore
value(str): converting value value(str): converting value
Returns: Returns:
List[str]: value converted to string list instance according to configuration rules list[str]: value converted to string list instance according to configuration rules
""" """
converted: List[str] = self.instance.converters["list"](value) converted: list[str] = self.configuration.converters["list"](value)
return converted return converted
def _validate_is_ip_address(self, constraint: list[str], field: str, value: str) -> None:
"""
check if the specified value is valid ip address
Args:
constraint(list[str]): optional list of allowed special words (e.g. ``localhost``)
field(str): field name to be checked
value(Path): value to be checked
Examples:
The rule's arguments are validated against this schema:
{"type": "list", "schema": {"type": "string"}}
"""
if value in constraint:
return
try:
ipaddress.ip_address(value)
except ValueError:
self._error(field, f"Value {value} must be valid IP address")
def _validate_is_url(self, constraint: list[str], field: str, value: str) -> None:
"""
check if the specified value is a valid url
Args:
constraint(list[str]): optional list of supported schemas. If empty, no schema validation will be performed
field(str): field name to be checked
value(str): value to be checked
Examples:
The rule's arguments are validated against this schema:
{"type": "list", "schema": {"type": "string"}}
"""
url = urlparse(value) # it probably will never rise exceptions on parse
if not url.scheme:
self._error(field, f"Url scheme is not set for {value}")
if not url.netloc and url.scheme not in ("file",):
self._error(field, f"Location must be set for url {value} of scheme {url.scheme}")
if constraint and url.scheme not in constraint:
self._error(field, f"Url {value} scheme must be one of {constraint}")
def _validate_path_exists(self, constraint: bool, field: str, value: Path) -> None: def _validate_path_exists(self, constraint: bool, field: str, value: Path) -> None:
""" """
check if paths exists check if paths exists

View File

@ -1,47 +0,0 @@
#
# Copyright (c) 2021-2023 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 sqlite3 import Connection
from ahriman.core.configuration import Configuration
from ahriman.core.database.data.package_remotes import migrate_package_remotes
from ahriman.core.database.data.package_statuses import migrate_package_statuses
from ahriman.core.database.data.patches import migrate_patches
from ahriman.core.database.data.users import migrate_users_data
from ahriman.models.migration_result import MigrationResult
def migrate_data(result: MigrationResult, connection: Connection, configuration: Configuration) -> None:
"""
perform data migration
Args:
result(MigrationResult): result of the schema migration
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
# initial data migration
repository_paths = configuration.repository_paths
if result.old_version <= 0:
migrate_package_statuses(connection, repository_paths)
migrate_patches(connection, repository_paths)
migrate_users_data(connection, configuration)
if result.old_version <= 1:
migrate_package_remotes(connection, repository_paths)

View File

@ -1,64 +0,0 @@
#
# Copyright (c) 2021-2023 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 sqlite3 import Connection
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths
__all__ = ["migrate_package_remotes"]
# pylint: disable=protected-access
def migrate_package_remotes(connection: Connection, paths: RepositoryPaths) -> None:
"""
perform migration for package remote sources
Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
from ahriman.core.database.operations import PackageOperations
def insert_remote(base: str, remote: RemoteSource) -> None:
connection.execute(
"""
update package_bases set
branch = :branch, git_url = :git_url, path = :path,
web_url = :web_url, source = :source
where package_base = :package_base
""",
dict(
package_base=base,
branch=remote.branch, git_url=remote.git_url, path=remote.path,
web_url=remote.web_url, source=remote.source
)
)
packages = PackageOperations._packages_get_select_package_bases(connection)
for package_base, package in packages.items():
local_cache = paths.cache_for(package_base)
if local_cache.exists() and not package.is_vcs:
continue # skip packages which are not VCS and with local cache
remote_source = RemoteSource.from_source(PackageSource.AUR, package_base, "aur")
if remote_source is None:
continue # should never happen
insert_remote(package_base, remote_source)

View File

@ -1,82 +0,0 @@
#
# Copyright (c) 2021-2023 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 json
from sqlite3 import Connection
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
__all__ = ["migrate_package_statuses"]
def migrate_package_statuses(connection: Connection, paths: RepositoryPaths) -> None:
"""
perform migration for package statuses
Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
def insert_base(metadata: Package, last_status: BuildStatus) -> None:
connection.execute(
"""
insert into package_bases
(package_base, version, aur_url)
values
(:package_base, :version, :aur_url)
""",
dict(package_base=metadata.base, version=metadata.version, aur_url=""))
connection.execute(
"""
insert into package_statuses
(package_base, status, last_updated)
values
(:package_base, :status, :last_updated)""",
dict(package_base=metadata.base, status=last_status.status.value, last_updated=last_status.timestamp))
def insert_packages(metadata: Package) -> None:
package_list = []
for name, description in metadata.packages.items():
package_list.append(dict(package=name, package_base=metadata.base, **description.view()))
connection.executemany(
"""
insert into packages
(package, package_base, architecture, archive_size, build_date, depends, description,
filename, "groups", installed_size, licenses, provides, url)
values
(:package, :package_base, :architecture, :archive_size, :build_date, :depends, :description,
:filename, :groups, :installed_size, :licenses, :provides, :url)
""",
package_list)
cache_path = paths.root / "status_cache.json"
if not cache_path.is_file():
return # no file found
with cache_path.open() as cache:
dump = json.load(cache)
for item in dump.get("packages", []):
package = Package.from_json(item["package"])
status = BuildStatus.from_json(item["status"])
insert_base(package, status)
insert_packages(package)

View File

@ -1,47 +0,0 @@
#
# Copyright (c) 2021-2023 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 sqlite3 import Connection
from ahriman.models.repository_paths import RepositoryPaths
__all__ = ["migrate_patches"]
def migrate_patches(connection: Connection, paths: RepositoryPaths) -> None:
"""
perform migration for patches
Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
root = paths.root / "patches"
if not root.is_dir():
return # no directory found
for package in root.iterdir():
patch_path = package / "00-main.patch"
if not patch_path.is_file():
continue # not exist
content = patch_path.read_text(encoding="utf8")
connection.execute(
"""insert into patches (package_base, patch) values (:package_base, :patch)""",
{"package_base": package.name, "patch": content})

View File

@ -17,16 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from __future__ import annotations from collections.abc import Callable
from importlib import import_module from importlib import import_module
from pathlib import Path from pathlib import Path
from pkgutil import iter_modules from pkgutil import iter_modules
from sqlite3 import Connection from sqlite3 import Connection, Cursor
from typing import List, Type
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database.data import migrate_data
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.models.migration import Migration from ahriman.models.migration import Migration
from ahriman.models.migration_result import MigrationResult from ahriman.models.migration_result import MigrationResult
@ -53,8 +50,8 @@ class Migrations(LazyLogging):
self.connection = connection self.connection = connection
self.configuration = configuration self.configuration = configuration
@classmethod @staticmethod
def migrate(cls: Type[Migrations], connection: Connection, configuration: Configuration) -> MigrationResult: def migrate(connection: Connection, configuration: Configuration) -> MigrationResult:
""" """
perform migrations implicitly perform migrations implicitly
@ -65,25 +62,56 @@ class Migrations(LazyLogging):
Returns: Returns:
MigrationResult: current schema version MigrationResult: current schema version
""" """
return cls(connection, configuration).run() return Migrations(connection, configuration).run()
def migrations(self) -> List[Migration]: def migration(self, cursor: Cursor, migration: Migration) -> None:
"""
perform single migration
Args:
cursor(Cursor): connection cursor
migration(Migration): single migration to perform
"""
self.logger.info("applying table migration %s at index %s", migration.name, migration.index)
for statement in migration.steps:
cursor.execute(statement)
self.logger.info("table migration %s at index %s has been applied", migration.name, migration.index)
self.logger.info("perform data migration %s at index %s", migration.name, migration.index)
migration.migrate_data(self.connection, self.configuration)
self.logger.info(
"data migration %s at index %s has been performed",
migration.name, migration.index)
def migrations(self) -> list[Migration]:
""" """
extract all migrations from the current package extract all migrations from the current package
idea comes from https://julienharbulot.com/python-dynamical-import.html idea comes from https://julienharbulot.com/python-dynamical-import.html
Returns: Returns:
List[Migration]: list of found migrations list[Migration]: list of found migrations
""" """
migrations: List[Migration] = [] migrations: list[Migration] = []
package_dir = Path(__file__).resolve().parent package_dir = Path(__file__).resolve().parent
modules = [module_name for (_, module_name, _) in iter_modules([str(package_dir)])] modules = [module_name for (_, module_name, _) in iter_modules([str(package_dir)])]
for index, module_name in enumerate(sorted(modules)): for index, module_name in enumerate(sorted(modules)):
module = import_module(f"{__name__}.{module_name}") module = import_module(f"{__name__}.{module_name}")
steps: List[str] = getattr(module, "steps", [])
steps: list[str] = getattr(module, "steps", [])
self.logger.debug("found migration %s at index %s with steps count %s", module_name, index, len(steps)) self.logger.debug("found migration %s at index %s with steps count %s", module_name, index, len(steps))
migrations.append(Migration(index=index, name=module_name, steps=steps))
migrate_data: Callable[[Connection, Configuration], None] = \
getattr(module, "migrate_data", lambda *args: None)
migrations.append(
Migration(
index=index,
name=module_name,
steps=steps,
migrate_data=migrate_data
)
)
return migrations return migrations
@ -110,13 +138,7 @@ class Migrations(LazyLogging):
try: try:
cursor.execute("begin exclusive") cursor.execute("begin exclusive")
for migration in migrations[current_version:]: for migration in migrations[current_version:]:
self.logger.info("applying migration %s at index %s", migration.name, migration.index) self.migration(cursor, migration)
for statement in migration.steps:
cursor.execute(statement)
self.logger.info("migration %s at index %s has been applied", migration.name, migration.index)
migrate_data(result, self.connection, self.configuration)
cursor.execute(f"pragma user_version = {expected_version}") # no support for ? placeholders cursor.execute(f"pragma user_version = {expected_version}") # no support for ? placeholders
except Exception: except Exception:
self.logger.exception("migration failed with exception") self.logger.exception("migration failed with exception")

View File

@ -17,7 +17,17 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
__all__ = ["steps"] import json
from sqlite3 import Connection
from ahriman.core.configuration import Configuration
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
__all__ = ["migrate_data", "steps"]
steps = [ steps = [
@ -73,3 +83,109 @@ steps = [
) )
""", """,
] ]
def migrate_data(connection: Connection, configuration: Configuration) -> None:
"""
perform data migration
Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
migrate_package_statuses(connection, configuration.repository_paths)
migrate_patches(connection, configuration.repository_paths)
migrate_users_data(connection, configuration)
def migrate_package_statuses(connection: Connection, paths: RepositoryPaths) -> None:
"""
perform migration for package statuses
Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
def insert_base(metadata: Package, last_status: BuildStatus) -> None:
connection.execute(
"""
insert into package_bases
(package_base, version, aur_url)
values
(:package_base, :version, :aur_url)
""",
{"package_base": metadata.base, "version": metadata.version, "aur_url": ""})
connection.execute(
"""
insert into package_statuses
(package_base, status, last_updated)
values
(:package_base, :status, :last_updated)""",
{"package_base": metadata.base, "status": last_status.status.value, "last_updated": last_status.timestamp})
def insert_packages(metadata: Package) -> None:
package_list = []
for name, description in metadata.packages.items():
package_list.append({"package": name, "package_base": metadata.base, **description.view()})
connection.executemany(
"""
insert into packages
(package, package_base, architecture, archive_size, build_date, depends, description,
filename, "groups", installed_size, licenses, provides, url)
values
(:package, :package_base, :architecture, :archive_size, :build_date, :depends, :description,
:filename, :groups, :installed_size, :licenses, :provides, :url)
""",
package_list)
cache_path = paths.root / "status_cache.json"
if not cache_path.is_file():
return # no file found
with cache_path.open() as cache:
dump = json.load(cache)
for item in dump.get("packages", []):
package = Package.from_json(item["package"])
status = BuildStatus.from_json(item["status"])
insert_base(package, status)
insert_packages(package)
def migrate_patches(connection: Connection, paths: RepositoryPaths) -> None:
"""
perform migration for patches
Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
root = paths.root / "patches"
if not root.is_dir():
return # no directory found
for package in root.iterdir():
patch_path = package / "00-main.patch"
if not patch_path.is_file():
continue # not exist
content = patch_path.read_text(encoding="utf8")
connection.execute(
"""insert into patches (package_base, patch) values (:package_base, :patch)""",
{"package_base": package.name, "patch": content})
def migrate_users_data(connection: Connection, configuration: Configuration) -> None:
"""
perform migration for users
Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
for section in configuration.sections():
for option, value in configuration[section].items():
if not section.startswith("auth:"):
continue
access = section[5:]
connection.execute(
"""insert into users (username, access, password) values (:username, :access, :password)""",
{"username": option.lower(), "access": access, "password": value})

View File

@ -17,7 +17,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
__all__ = ["steps"] from sqlite3 import Connection
from ahriman.core.configuration import Configuration
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths
__all__ = ["migrate_data", "steps"]
steps = [ steps = [
@ -40,3 +48,51 @@ steps = [
alter table package_bases drop column aur_url alter table package_bases drop column aur_url
""", """,
] ]
def migrate_data(connection: Connection, configuration: Configuration) -> None:
"""
perform data migration
Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
migrate_package_remotes(connection, configuration.repository_paths)
# pylint: disable=protected-access
def migrate_package_remotes(connection: Connection, paths: RepositoryPaths) -> None:
"""
perform migration for package remote sources
Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
from ahriman.core.database.operations import PackageOperations
def insert_remote(base: str, remote: RemoteSource) -> None:
connection.execute(
"""
update package_bases set
branch = :branch, git_url = :git_url, path = :path,
web_url = :web_url, source = :source
where package_base = :package_base
""",
{
"package_base": base,
"branch": remote.branch, "git_url": remote.git_url, "path": remote.path,
"web_url": remote.web_url, "source": remote.source
}
)
packages = PackageOperations._packages_get_select_package_bases(connection)
for package_base, package in packages.items():
local_cache = paths.cache_for(package_base)
if local_cache.exists() and not package.is_vcs:
continue # skip packages which are not VCS and with local cache
remote_source = RemoteSource.from_source(PackageSource.AUR, package_base, "aur")
if remote_source is None:
continue # should never happen
insert_remote(package_base, remote_source)

View File

@ -0,0 +1,84 @@
#
# Copyright (c) 2021-2023 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 sqlite3 import Connection
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.configuration import Configuration
from ahriman.core.util import package_like
from ahriman.models.package import Package
from ahriman.models.pacman_synchronization import PacmanSynchronization
__all__ = ["migrate_data", "steps"]
steps = [
"""
alter table packages add column make_depends json
""",
"""
alter table packages add column opt_depends json
""",
]
def migrate_data(connection: Connection, configuration: Configuration) -> None:
"""
perform data migration
Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
migrate_package_depends(connection, configuration)
def migrate_package_depends(connection: Connection, configuration: Configuration) -> None:
"""
migrate package opt and make depends fields
Args:
connection(Connection): database connection
configuration(Configuration): configuration instance
"""
if not configuration.repository_paths.repository.is_dir():
return
_, architecture = configuration.check_loaded()
pacman = Pacman(architecture, configuration, refresh_database=PacmanSynchronization.Disabled)
package_list = []
for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()):
base = Package.from_archive(full_path, pacman, remote=None)
for package, description in base.packages.items():
package_list.append({
"make_depends": description.make_depends,
"opt_depends": description.opt_depends,
"package": package,
})
connection.executemany(
"""
update packages set
make_depends = :make_depends, opt_depends = :opt_depends
where package = :package
""",
package_list
)

View File

@ -0,0 +1,53 @@
#
# Copyright (c) 2021-2023 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/>.
#
__all__ = ["steps"]
steps = [
"""
alter table packages rename to packages_
""",
"""
create table packages (
package text not null,
package_base text not null,
architecture text not null,
archive_size integer,
build_date integer,
depends json,
description text,
filename text,
"groups" json,
installed_size integer,
licenses json,
provides json,
url text,
make_depends json,
opt_depends json,
unique (package, architecture)
)
""",
"""
insert into packages select * from packages_ where architecture is not null;
""",
"""
drop table packages_;
""",
]

View File

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from sqlite3 import Connection from sqlite3 import Connection
from typing import List, Optional
from ahriman.core.database.operations import Operations from ahriman.core.database.operations import Operations
from ahriman.models.user import User from ahriman.models.user import User
@ -30,7 +29,7 @@ class AuthOperations(Operations):
authorization operations authorization operations
""" """
def user_get(self, username: str) -> Optional[User]: def user_get(self, username: str) -> User | None:
""" """
get user by username get user by username
@ -38,25 +37,25 @@ class AuthOperations(Operations):
username(str): username username(str): username
Returns: Returns:
Optional[User]: user if it was found User | None: user if it was found
""" """
return next(iter(self.user_list(username, None)), None) return next(iter(self.user_list(username, None)), None)
def user_list(self, username: Optional[str], access: Optional[UserAccess]) -> List[User]: def user_list(self, username: str | None, access: UserAccess | None) -> list[User]:
""" """
get users by filter get users by filter
Args: Args:
username(Optional[str]): optional filter by username username(str | None): optional filter by username
access(Optional[UserAccess]): optional filter by role access(UserAccess | None): optional filter by role
Returns: Returns:
List[User]: list of users who match criteria list[User]: list of users who match criteria
""" """
username_filter = username.lower() if username is not None else username username_filter = username.lower() if username is not None else username
access_filter = access.value if access is not None else access access_filter = access.value if access is not None else access
def run(connection: Connection) -> List[User]: def run(connection: Connection) -> list[User]:
return [ return [
User(username=cursor["username"], password=cursor["password"], access=UserAccess(cursor["access"])) User(username=cursor["username"], password=cursor["password"], access=UserAccess(cursor["access"]))
for cursor in connection.execute( for cursor in connection.execute(

View File

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from sqlite3 import Connection from sqlite3 import Connection
from typing import List, Optional
from ahriman.core.database.operations import Operations from ahriman.core.database.operations import Operations
from ahriman.models.package import Package from ahriman.models.package import Package
@ -29,12 +28,12 @@ class BuildOperations(Operations):
operations for build queue functions operations for build queue functions
""" """
def build_queue_clear(self, package_base: Optional[str]) -> None: def build_queue_clear(self, package_base: str | None) -> None:
""" """
remove packages from build queue remove packages from build queue
Args: Args:
package_base(Optional[str]): optional filter by package base package_base(str | None): optional filter by package base
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:
connection.execute( connection.execute(
@ -46,14 +45,14 @@ class BuildOperations(Operations):
return self.with_connection(run, commit=True) return self.with_connection(run, commit=True)
def build_queue_get(self) -> List[Package]: def build_queue_get(self) -> list[Package]:
""" """
retrieve packages from build queue retrieve packages from build queue
Return: Return:
List[Package]: list of packages to be built list[Package]: list of packages to be built
""" """
def run(connection: Connection) -> List[Package]: def run(connection: Connection) -> list[Package]:
return [ return [
Package.from_json(row["properties"]) Package.from_json(row["properties"])
for row in connection.execute("""select * from build_queue""") for row in connection.execute("""select * from build_queue""")

View File

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from sqlite3 import Connection from sqlite3 import Connection
from typing import List, Optional
from ahriman.core.database.operations import Operations from ahriman.core.database.operations import Operations
from ahriman.core.util import pretty_datetime from ahriman.core.util import pretty_datetime
@ -40,7 +39,7 @@ class LogsOperations(Operations):
Return: Return:
str: full package log str: full package log
""" """
def run(connection: Connection) -> List[str]: def run(connection: Connection) -> list[str]:
return [ return [
f"""[{pretty_datetime(row["created"])}] {row["record"]}""" f"""[{pretty_datetime(row["created"])}] {row["record"]}"""
for row in connection.execute( for row in connection.execute(
@ -71,23 +70,23 @@ class LogsOperations(Operations):
values values
(:package_base, :process_id, :created, :record) (:package_base, :process_id, :created, :record)
""", """,
dict( {
package_base=log_record_id.package_base, "package_base": log_record_id.package_base,
process_id=log_record_id.process_id, "process_id": log_record_id.process_id,
created=created, "created": created,
record=record "record": record,
) }
) )
return self.with_connection(run, commit=True) return self.with_connection(run, commit=True)
def logs_remove(self, package_base: str, current_process_id: Optional[int]) -> None: def logs_remove(self, package_base: str, current_process_id: int | None) -> None:
""" """
remove log records for the specified package remove log records for the specified package
Args: Args:
package_base(str): package base to remove logs package_base(str): package base to remove logs
current_process_id(Optional[int]): current process id. If set it will remove only logs belonging to another current_process_id(int | None): current process id. If set it will remove only logs belonging to another
process process
""" """
def run(connection: Connection) -> None: def run(connection: Connection) -> None:

View File

@ -19,8 +19,9 @@
# #
import sqlite3 import sqlite3
from collections.abc import Callable
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Tuple, TypeVar, Callable from typing import Any, TypeVar
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
@ -46,16 +47,16 @@ class Operations(LazyLogging):
self.path = path self.path = path
@staticmethod @staticmethod
def factory(cursor: sqlite3.Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]: def factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> dict[str, Any]:
""" """
dictionary factory based on official documentation dictionary factory based on official documentation
Args: Args:
cursor(Cursor): cursor descriptor cursor(Cursor): cursor descriptor
row(Tuple[Any, ...]): fetched row row(tuple[Any, ...]): fetched row
Returns: Returns:
Dict[str, Any]: row converted to dictionary dict[str, Any]: row converted to dictionary
""" """
result = {} result = {}
for index, column in enumerate(cursor.description): for index, column in enumerate(cursor.description):

View File

@ -17,8 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Generator, Iterable
from sqlite3 import Connection from sqlite3 import Connection
from typing import Dict, Generator, Iterable, List, Tuple
from ahriman.core.database.operations import Operations from ahriman.core.database.operations import Operations
from ahriman.models.build_status import BuildStatus from ahriman.models.build_status import BuildStatus
@ -82,15 +82,15 @@ class PackageOperations(Operations):
on conflict (package_base) do update set on conflict (package_base) do update set
version = :version, branch = :branch, git_url = :git_url, path = :path, web_url = :web_url, source = :source version = :version, branch = :branch, git_url = :git_url, path = :path, web_url = :web_url, source = :source
""", """,
dict( {
package_base=package.base, "package_base": package.base,
version=package.version, "version": package.version,
branch=package.remote.branch if package.remote is not None else None, "branch": package.remote.branch if package.remote is not None else None,
git_url=package.remote.git_url if package.remote is not None else None, "git_url": package.remote.git_url if package.remote is not None else None,
path=package.remote.path if package.remote is not None else None, "path": package.remote.path if package.remote is not None else None,
web_url=package.remote.web_url if package.remote is not None else None, "web_url": package.remote.web_url if package.remote is not None else None,
source=package.remote.source.value if package.remote is not None else None, "source": package.remote.source.value if package.remote is not None else None,
) }
) )
@staticmethod @staticmethod
@ -104,21 +104,26 @@ class PackageOperations(Operations):
""" """
package_list = [] package_list = []
for name, description in package.packages.items(): for name, description in package.packages.items():
package_list.append(dict(package=name, package_base=package.base, **description.view())) if description.architecture is None:
continue # architecture is required
package_list.append({"package": name, "package_base": package.base, **description.view()})
connection.executemany( connection.executemany(
""" """
insert into packages insert into packages
(package, package_base, architecture, archive_size, (package, package_base, architecture, archive_size,
build_date, depends, description, filename, build_date, depends, description, filename,
"groups", installed_size, licenses, provides, url) "groups", installed_size, licenses, provides,
url, make_depends, opt_depends)
values values
(:package, :package_base, :architecture, :archive_size, (:package, :package_base, :architecture, :archive_size,
:build_date, :depends, :description, :filename, :build_date, :depends, :description, :filename,
:groups, :installed_size, :licenses, :provides, :url) :groups, :installed_size, :licenses, :provides,
:url, :make_depends, :opt_depends)
on conflict (package, architecture) do update set on conflict (package, architecture) do update set
package_base = :package_base, archive_size = :archive_size, package_base = :package_base, archive_size = :archive_size,
build_date = :build_date, depends = :depends, description = :description, filename = :filename, build_date = :build_date, depends = :depends, description = :description, filename = :filename,
"groups" = :groups, installed_size = :installed_size, licenses = :licenses, provides = :provides, url = :url "groups" = :groups, installed_size = :installed_size, licenses = :licenses, provides = :provides,
url = :url, make_depends = :make_depends, opt_depends = :opt_depends
""", """,
package_list) package_list)
@ -140,10 +145,10 @@ class PackageOperations(Operations):
on conflict (package_base) do update set on conflict (package_base) do update set
status = :status, last_updated = :last_updated status = :status, last_updated = :last_updated
""", """,
dict(package_base=package_base, status=status.status.value, last_updated=status.timestamp)) {"package_base": package_base, "status": status.status.value, "last_updated": status.timestamp})
@staticmethod @staticmethod
def _packages_get_select_package_bases(connection: Connection) -> Dict[str, Package]: def _packages_get_select_package_bases(connection: Connection) -> dict[str, Package]:
""" """
select package bases from the table select package bases from the table
@ -151,7 +156,7 @@ class PackageOperations(Operations):
connection(Connection): database connection connection(Connection): database connection
Returns: Returns:
Dict[str, Package]: map of the package base to its descriptor (without packages themselves) dict[str, Package]: map of the package base to its descriptor (without packages themselves)
""" """
return { return {
row["package_base"]: Package( row["package_base"]: Package(
@ -163,16 +168,16 @@ class PackageOperations(Operations):
} }
@staticmethod @staticmethod
def _packages_get_select_packages(connection: Connection, packages: Dict[str, Package]) -> Dict[str, Package]: def _packages_get_select_packages(connection: Connection, packages: dict[str, Package]) -> dict[str, Package]:
""" """
select packages from the table select packages from the table
Args: Args:
connection(Connection): database connection connection(Connection): database connection
packages(Dict[str, Package]): packages descriptor map packages(dict[str, Package]): packages descriptor map
Returns: Returns:
Dict[str, Package]: map of the package base to its descriptor including individual packages dict[str, Package]: map of the package base to its descriptor including individual packages
""" """
for row in connection.execute("""select * from packages"""): for row in connection.execute("""select * from packages"""):
if row["package_base"] not in packages: if row["package_base"] not in packages:
@ -181,7 +186,7 @@ class PackageOperations(Operations):
return packages return packages
@staticmethod @staticmethod
def _packages_get_select_statuses(connection: Connection) -> Dict[str, BuildStatus]: def _packages_get_select_statuses(connection: Connection) -> dict[str, BuildStatus]:
""" """
select package build statuses from the table select package build statuses from the table
@ -189,7 +194,7 @@ class PackageOperations(Operations):
connection(Connection): database connection connection(Connection): database connection
Returns: Returns:
Dict[str, BuildStatus]: map of the package base to its status dict[str, BuildStatus]: map of the package base to its status
""" """
return { return {
row["package_base"]: BuildStatus.from_json({"status": row["status"], "timestamp": row["last_updated"]}) row["package_base"]: BuildStatus.from_json({"status": row["status"], "timestamp": row["last_updated"]})
@ -225,14 +230,14 @@ class PackageOperations(Operations):
return self.with_connection(run, commit=True) return self.with_connection(run, commit=True)
def packages_get(self) -> List[Tuple[Package, BuildStatus]]: def packages_get(self) -> list[tuple[Package, BuildStatus]]:
""" """
get package list and their build statuses from database get package list and their build statuses from database
Return: Return:
List[Tuple[Package, BuildStatus]]: list of package properties and their statuses list[tuple[Package, BuildStatus]]: list of package properties and their statuses
""" """
def run(connection: Connection) -> Generator[Tuple[Package, BuildStatus], None, None]: def run(connection: Connection) -> Generator[tuple[Package, BuildStatus], None, None]:
packages = self._packages_get_select_package_bases(connection) packages = self._packages_get_select_package_bases(connection)
statuses = self._packages_get_select_statuses(connection) statuses = self._packages_get_select_statuses(connection)
for package_base, package in self._packages_get_select_packages(connection, packages).items(): for package_base, package in self._packages_get_select_packages(connection, packages).items():
@ -251,12 +256,12 @@ class PackageOperations(Operations):
lambda connection: self._package_update_insert_base(connection, package), lambda connection: self._package_update_insert_base(connection, package),
commit=True) commit=True)
def remotes_get(self) -> Dict[str, RemoteSource]: def remotes_get(self) -> dict[str, RemoteSource]:
""" """
get packages remotes based on current settings get packages remotes based on current settings
Returns: Returns:
Dict[str, RemoteSource]: map of package base to its remote sources dict[str, RemoteSource]: map of package base to its remote sources
""" """
packages = self.with_connection(self._packages_get_select_package_bases) packages = self.with_connection(self._packages_get_select_package_bases)
return { return {

View File

@ -20,7 +20,6 @@
from collections import defaultdict from collections import defaultdict
from sqlite3 import Connection from sqlite3 import Connection
from typing import Dict, List, Optional, Tuple
from ahriman.core.database.operations import Operations from ahriman.core.database.operations import Operations
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
@ -31,7 +30,7 @@ class PatchOperations(Operations):
operations for patches operations for patches
""" """
def patches_get(self, package_base: str) -> List[PkgbuildPatch]: def patches_get(self, package_base: str) -> list[PkgbuildPatch]:
""" """
retrieve patches for the package retrieve patches for the package
@ -39,7 +38,7 @@ class PatchOperations(Operations):
package_base(str): package base to search for patches package_base(str): package base to search for patches
Returns: Returns:
List[PkgbuildPatch]: plain text patch for the package list[PkgbuildPatch]: plain text patch for the package
""" """
return self.patches_list(package_base, []).get(package_base, []) return self.patches_list(package_base, []).get(package_base, [])
@ -65,18 +64,18 @@ class PatchOperations(Operations):
return self.with_connection(run, commit=True) return self.with_connection(run, commit=True)
def patches_list(self, package_base: Optional[str], variables: List[str]) -> Dict[str, List[PkgbuildPatch]]: def patches_list(self, package_base: str | None, variables: list[str]) -> dict[str, list[PkgbuildPatch]]:
""" """
extract all patches extract all patches
Args: Args:
package_base(Optional[str]): optional filter by package base package_base(str | None): optional filter by package base
variables(List[str]): extract patches only for specified PKGBUILD variables variables(list[str]): extract patches only for specified PKGBUILD variables
Returns: Returns:
Dict[str, List[PkgbuildPatch]]: map of package base to patch content dict[str, list[PkgbuildPatch]]: map of package base to patch content
""" """
def run(connection: Connection) -> List[Tuple[str, PkgbuildPatch]]: def run(connection: Connection) -> list[tuple[str, PkgbuildPatch]]:
return [ return [
(cursor["package_base"], PkgbuildPatch(cursor["variable"], cursor["patch"])) (cursor["package_base"], PkgbuildPatch(cursor["variable"], cursor["patch"]))
for cursor in connection.execute( for cursor in connection.execute(
@ -85,20 +84,20 @@ class PatchOperations(Operations):
] ]
# we could use itertools & operator but why? # we could use itertools & operator but why?
patches: Dict[str, List[PkgbuildPatch]] = defaultdict(list) patches: dict[str, list[PkgbuildPatch]] = defaultdict(list)
for package, patch in self.with_connection(run): for package, patch in self.with_connection(run):
if variables and patch.key not in variables: if variables and patch.key not in variables:
continue continue
patches[package].append(patch) patches[package].append(patch)
return dict(patches) return dict(patches)
def patches_remove(self, package_base: str, variables: List[str]) -> None: def patches_remove(self, package_base: str, variables: list[str]) -> None:
""" """
remove patch set remove patch set
Args: Args:
package_base(str): package base to clear patches package_base(str): package base to clear patches
variables(List[str]): remove patches only for specified PKGBUILD variables variables(list[str]): remove patches only for specified PKGBUILD variables
""" """
def run_many(connection: Connection) -> None: def run_many(connection: Connection) -> None:
connection.executemany( connection.executemany(

View File

@ -17,13 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from __future__ import annotations
import json import json
import sqlite3 import sqlite3
from pathlib import Path from pathlib import Path
from typing import Type from typing import Self
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database.migrations import Migrations from ahriman.core.database.migrations import Migrations
@ -47,7 +45,7 @@ class SQLite(AuthOperations, BuildOperations, LogsOperations, PackageOperations,
""" """
@classmethod @classmethod
def load(cls: Type[SQLite], configuration: Configuration) -> SQLite: def load(cls, configuration: Configuration) -> Self:
""" """
construct instance from configuration construct instance from configuration
@ -55,7 +53,7 @@ class SQLite(AuthOperations, BuildOperations, LogsOperations, PackageOperations,
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
Returns: Returns:
SQLite: fully initialized instance of the database Self: fully initialized instance of the database
""" """
path = cls.database_path(configuration) path = cls.database_path(configuration)
database = cls(path) database = cls(path)

View File

@ -27,7 +27,7 @@ from ahriman.core.formatters.package_printer import PackagePrinter
from ahriman.core.formatters.patch_printer import PatchPrinter from ahriman.core.formatters.patch_printer import PatchPrinter
from ahriman.core.formatters.status_printer import StatusPrinter from ahriman.core.formatters.status_printer import StatusPrinter
from ahriman.core.formatters.tree_printer import TreePrinter from ahriman.core.formatters.tree_printer import TreePrinter
from ahriman.core.formatters.validation_printer import ValidationPrinter
from ahriman.core.formatters.update_printer import UpdatePrinter from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters.user_printer import UserPrinter from ahriman.core.formatters.user_printer import UserPrinter
from ahriman.core.formatters.validation_printer import ValidationPrinter
from ahriman.core.formatters.version_printer import VersionPrinter from ahriman.core.formatters.version_printer import VersionPrinter

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import List
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters import StringPrinter
from ahriman.core.util import pretty_datetime from ahriman.core.util import pretty_datetime
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
@ -43,12 +41,12 @@ class AurPrinter(StringPrinter):
StringPrinter.__init__(self, f"{package.name} {package.version} ({package.num_votes})") StringPrinter.__init__(self, f"{package.name} {package.version} ({package.num_votes})")
self.package = package self.package = package
def properties(self) -> List[Property]: def properties(self) -> list[Property]:
""" """
convert content into printable data convert content into printable data
Returns: Returns:
List[Property]: list of content properties list[Property]: list of content properties
""" """
return [ return [
Property("Package base", self.package.package_base), Property("Package base", self.package.package_base),

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import Dict, List
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters import StringPrinter
from ahriman.models.property import Property from ahriman.models.property import Property
@ -28,28 +26,39 @@ class ConfigurationPrinter(StringPrinter):
print content of the configuration section print content of the configuration section
Attributes: Attributes:
values(Dict[str, str]): configuration values dictionary HIDE_KEYS(list[str]): (class attribute) hide values for mentioned keys. This list must be used in order to hide
passwords from outputs
values(dict[str, str]): configuration values dictionary
""" """
def __init__(self, section: str, values: Dict[str, str]) -> None: HIDE_KEYS = [
"api_key", # telegram key
"client_secret", # oauth secret
"cookie_secret_key", # cookie secret key
"password", # generic password (github, email, web server, etc)
"salt", # password default salt
"secret_key", # aws secret key
]
def __init__(self, section: str, values: dict[str, str]) -> None:
""" """
default constructor default constructor
Args: Args:
section(str): section name section(str): section name
values(Dict[str, str]): configuration values dictionary values(dict[str, str]): configuration values dictionary
""" """
StringPrinter.__init__(self, f"[{section}]") StringPrinter.__init__(self, f"[{section}]")
self.values = values self.values = values
def properties(self) -> List[Property]: def properties(self) -> list[Property]:
""" """
convert content into printable data convert content into printable data
Returns: Returns:
List[Property]: list of content properties list[Property]: list of content properties
""" """
return [ return [
Property(key, value, is_required=True) Property(key, value, is_required=key not in self.HIDE_KEYS)
for key, value in sorted(self.values.items()) for key, value in sorted(self.values.items())
] ]

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import List
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters import StringPrinter
from ahriman.models.build_status import BuildStatus from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package from ahriman.models.package import Package
@ -46,12 +44,12 @@ class PackagePrinter(StringPrinter):
self.package = package self.package = package
self.status = status self.status = status
def properties(self) -> List[Property]: def properties(self) -> list[Property]:
""" """
convert content into printable data convert content into printable data
Returns: Returns:
List[Property]: list of content properties list[Property]: list of content properties
""" """
return [ return [
Property("Version", self.package.version, is_required=True), Property("Version", self.package.version, is_required=True),

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from typing import List
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters import StringPrinter
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.property import Property from ahriman.models.property import Property
@ -29,26 +27,26 @@ class PatchPrinter(StringPrinter):
print content of the PKGBUILD patch print content of the PKGBUILD patch
Attributes: Attributes:
patches(List[PkgbuildPatch]): PKGBUILD patch object patches(list[PkgbuildPatch]): PKGBUILD patch object
""" """
def __init__(self, package_base: str, patches: List[PkgbuildPatch]) -> None: def __init__(self, package_base: str, patches: list[PkgbuildPatch]) -> None:
""" """
default constructor default constructor
Args: Args:
package_base(str): package base package_base(str): package base
patches(List[PkgbuildPatch]): PKGBUILD patch object patches(list[PkgbuildPatch]): PKGBUILD patch object
""" """
StringPrinter.__init__(self, package_base) StringPrinter.__init__(self, package_base)
self.patches = patches self.patches = patches
def properties(self) -> List[Property]: def properties(self) -> list[Property]:
""" """
convert content into printable data convert content into printable data
Returns: Returns:
List[Property]: list of content properties list[Property]: list of content properties
""" """
return [ return [
Property(patch.key or "Full source diff", patch.value, is_required=True) Property(patch.key or "Full source diff", patch.value, is_required=True)

Some files were not shown because too many files have changed in this diff Show More