docs: add check if docs are updated

This commit is contained in:
2025-06-18 17:57:52 +03:00
parent 1f22a27360
commit e119f092b4
17 changed files with 89 additions and 79 deletions

View File

@ -1,6 +0,0 @@
skips:
- B101
- B104
- B105
- B106
- B404

View File

@ -21,18 +21,18 @@ jobs:
packages: write
steps:
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v2
- uses: docker/setup-buildx-action@v3
- name: Login to docker hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to github container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -40,7 +40,7 @@ jobs:
- name: Extract docker metadata
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v5
with:
images: |
arcan1s/ahriman
@ -50,7 +50,7 @@ jobs:
type=edge
- name: Build an image and push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v6
with:
file: docker/Dockerfile
push: true

View File

@ -37,8 +37,6 @@ jobs:
- repo:/var/lib/ahriman
steps:
- uses: actions/checkout@v3
- run: pacman -Sy
- name: Init repository

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Extract version
id: version
@ -27,8 +27,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
filter: 'Release \d+\.\d+\.\d+'
- name: Install dependencies
uses: ConorMacBride/install-package@v1.1.0
- uses: ConorMacBride/install-package@v1.1.0
with:
apt: tox
@ -38,7 +37,7 @@ jobs:
VERSION: ${{ steps.version.outputs.VERSION }}
- name: Publish release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
body: |
${{ steps.changelog.outputs.compareurl }}

View File

@ -24,7 +24,7 @@ jobs:
- ${{ github.workspace }}:/build
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup the minimal service in arch linux container
run: .github/workflows/setup.sh minimal
@ -40,7 +40,7 @@ jobs:
options: --privileged -w /build
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup the service in arch linux container
run: .github/workflows/setup.sh

View File

@ -1,10 +0,0 @@
#!/bin/bash
# Install dependencies and run test in container
set -ex
# install dependencies
pacman --noconfirm -Syyu base-devel python-tox
# run test and check targets
tox

View File

@ -26,7 +26,16 @@ jobs:
- ${{ github.workspace }}:/build
steps:
- uses: actions/checkout@v3
- run: pacman --noconfirm -Syu base-devel git python-tox
- name: Run check and tests in arch linux container
run: .github/workflows/tests.sh
- run: git config --global --add safe.directory *
- uses: actions/checkout@v4
- name: Run check and tests
run: tox
- name: Generate documentation and check if there are untracked changes
run: |
tox -e docs
[ -z "$(git status --porcelain docs/*.rst)" ]

View File

@ -413,10 +413,11 @@ Web application
Web application requires the following python packages to be installed:
* 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).
* Additional web features also require ``aiohttp-apispec`` (autogenerated documentation, optional), ``aiohttp_cors`` (CORS support, required by documentation).
* 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 if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket2`` library is required.
* Application metrics will be automatically enabled after installing ``aiohttp-openmetrics`` package.
Middlewares
^^^^^^^^^^^

View File

@ -1,36 +1,36 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --group ../pyproject.toml:docs --extra s3 --extra validator --extra web --output-file ../docs/requirements.txt ../pyproject.toml
# uv pip compile --group pyproject.toml:docs --extra s3 --extra validator --extra web --output-file docs/requirements.txt pyproject.toml
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.11.18
# via
# ahriman (../pyproject.toml)
# ahriman (pyproject.toml)
# aiohttp-cors
# aiohttp-jinja2
aiohttp-cors==0.8.1
# via ahriman (../pyproject.toml)
# via ahriman (pyproject.toml)
aiohttp-jinja2==1.6
# via ahriman (../pyproject.toml)
# via ahriman (pyproject.toml)
aiosignal==1.3.2
# via aiohttp
alabaster==1.0.0
# via sphinx
argparse-manpage==4.6
# via ahriman (../pyproject.toml:docs)
# via ahriman (pyproject.toml:docs)
attrs==25.3.0
# via aiohttp
babel==2.17.0
# via sphinx
bcrypt==4.3.0
# via ahriman (../pyproject.toml)
# via ahriman (pyproject.toml)
boto3==1.38.11
# via ahriman (../pyproject.toml)
# via ahriman (pyproject.toml)
botocore==1.38.11
# via
# boto3
# s3transfer
cerberus==1.3.7
# via ahriman (../pyproject.toml)
# via ahriman (pyproject.toml)
certifi==2025.4.26
# via requests
charset-normalizer==3.4.2
@ -51,7 +51,7 @@ idna==3.10
imagesize==1.4.1
# via sphinx
inflection==0.5.1
# via ahriman (../pyproject.toml)
# via ahriman (pyproject.toml)
jinja2==3.1.6
# via
# aiohttp-jinja2
@ -73,37 +73,37 @@ propcache==0.3.1
# aiohttp
# yarl
pydeps==3.0.1
# via ahriman (../pyproject.toml:docs)
# via ahriman (pyproject.toml:docs)
pyelftools==0.32
# via ahriman (../pyproject.toml)
# via ahriman (pyproject.toml)
pygments==2.19.1
# via sphinx
python-dateutil==2.9.0.post0
# via botocore
requests==2.32.3
# via
# ahriman (../pyproject.toml)
# ahriman (pyproject.toml)
# sphinx
roman-numerals-py==3.1.0
# via sphinx
s3transfer==0.12.0
# via boto3
shtab==1.7.2
# via ahriman (../pyproject.toml:docs)
# via ahriman (pyproject.toml:docs)
six==1.17.0
# via python-dateutil
snowballstemmer==3.0.0.1
# via sphinx
sphinx==8.2.3
# via
# ahriman (../pyproject.toml:docs)
# ahriman (pyproject.toml:docs)
# sphinx-argparse
# sphinx-rtd-theme
# sphinxcontrib-jquery
sphinx-argparse==0.5.2
# via ahriman (../pyproject.toml:docs)
# via ahriman (pyproject.toml:docs)
sphinx-rtd-theme==3.0.2
# via ahriman (../pyproject.toml:docs)
# via ahriman (pyproject.toml:docs)
sphinxcontrib-applehelp==2.0.0
# via sphinx
sphinxcontrib-devhelp==2.0.0

View File

@ -6,6 +6,7 @@ from pytest_mock import MockerFixture
from ahriman.application.application import Application
from ahriman.application.handlers.copy import Copy
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package
@ -30,11 +31,12 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
package_ahriman: Package, mocker: MockerFixture) -> None:
database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must run command
"""
args = _default_args(args)
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman])
application_mock = mocker.patch("ahriman.application.handlers.copy.Copy.copy_package")
@ -51,12 +53,13 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
def test_run_remove(args: argparse.Namespace, configuration: Configuration, repository: Repository,
package_ahriman: Package, mocker: MockerFixture) -> None:
database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must run command and remove packages afterward
"""
args = _default_args(args)
args.remove = True
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman])
mocker.patch("ahriman.application.handlers.copy.Copy.copy_package")
@ -69,12 +72,13 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
database: SQLite, mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty result
"""
args = _default_args(args)
args.exit_code = True
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[])
mocker.patch("ahriman.application.application.Application.update")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")

View File

@ -9,6 +9,7 @@ from urllib.parse import quote_plus as url_encode
from ahriman.application.handlers.setup import Setup
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import MissingArchitectureError
from ahriman.core.repository import Repository
from ahriman.models.repository_id import RepositoryId
@ -44,11 +45,12 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
database: SQLite, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must run command
"""
args = _default_args(args)
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman")
devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools")
@ -88,12 +90,13 @@ def test_run_no_architecture_or_repository(configuration: Configuration) -> None
def test_run_with_server(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
database: SQLite, mocker: MockerFixture) -> None:
"""
must run command with server specified
"""
args = _default_args(args)
args.server = "server"
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman")
mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg")

View File

@ -51,7 +51,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, database: S
update_mock.assert_called_once_with(user)
def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration, database: SQLite,
mocker: MockerFixture) -> None:
"""
must process users with empty password salt
"""
@ -59,6 +60,7 @@ def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration,
args = _default_args(args)
user = User(username=args.username, password=args.password, access=args.role,
packager_id=args.packager, key=args.key)
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.models.user.User.hash_password", return_value=user)
create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user)
update_mock = mocker.patch("ahriman.core.database.SQLite.user_update")

View File

@ -1,6 +1,8 @@
import datetime
import pytest
import tempfile
from collections.abc import Generator
from pathlib import Path
from pytest_mock import MockerFixture
from typing import Any, TypeVar
@ -265,16 +267,19 @@ def configuration(repository_id: RepositoryId, resource_path_root: Path) -> Conf
@pytest.fixture
def database(configuration: Configuration) -> SQLite:
def database(configuration: Configuration) -> Generator[SQLite, None, None]:
"""
database fixture
Args:
configuration(Configuration): configuration fixture
Returns:
Yields:
SQLite: database test instance
"""
database_file = tempfile.mktemp(dir=configuration.repository_paths.root) # nosec
configuration.set_option("settings", "database", database_file)
database = SQLite.load(configuration)
yield database
database.path.unlink()

View File

@ -62,8 +62,8 @@ def test_validate_is_ip_address(validator: Validator, mocker: MockerFixture) ->
validator._validate_is_ip_address([], "field", "localhost")
validator._validate_is_ip_address([], "field", "127.0.0.1")
validator._validate_is_ip_address([], "field", "::")
validator._validate_is_ip_address([], "field", "0.0.0.0")
validator._validate_is_ip_address([], "field", "::") # nosec
validator._validate_is_ip_address([], "field", "0.0.0.0") # nosec
validator._validate_is_ip_address([], "field", "random string")

View File

@ -12,6 +12,8 @@ def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
must correctly load instance
"""
init_mock = mocker.patch("ahriman.core.database.SQLite.init")
configuration.set_option("settings", "database", "ahriman.db")
SQLite.load(configuration)
init_mock.assert_called_once_with()

View File

@ -1,7 +1,6 @@
[settings]
include = .
logging = logging.ini
database = ../../../ahriman-test.db
[alpm]
database = /var/lib/pacman

40
tox.ini
View File

@ -6,8 +6,14 @@ labels =
dependencies = -e .[journald,pacman,reports,s3,shell,stats,unixsocket,validator,web,web_api-docs,web_auth,web_oauth2,web_metrics]
project_name = ahriman
[mypy]
flags = --implicit-reexport --strict --allow-untyped-decorators --allow-subclassing-any
[flags]
autopep8 = --max-line-length 120 -aa --in-place
bandit = --configfile .bandit.yml
manpage = --author "ahriman team" --author-email "" --description "ArcH linux ReposItory MANager" --manual-title "ArcH linux ReposItory MANager" --project-name ahriman --url https://github.com/arcan1s/ahriman
mypy = --implicit-reexport --strict --allow-untyped-decorators --allow-subclassing-any
pydeps = --no-config --cluster
pylint = --rcfile .pylint.toml
shtab = --prefix ahriman --prog ahriman ahriman.application.ahriman._parser
[pytest]
addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec
@ -33,19 +39,17 @@ setenv =
CFLAGS="-Wno-unterminated-string-initialization"
MYPYPATH=src
commands =
autopep8 --exit-code --max-line-length 120 -aa -i -j 0 -r "src/{[tox]project_name}" "tests/{[tox]project_name}"
pylint --rcfile=.pylint.toml "src/{[tox]project_name}"
bandit -c .bandit.yml -r "src/{[tox]project_name}"
bandit -c .bandit-test.yml -r "tests/{[tox]project_name}"
mypy {[mypy]flags} -p "{[tox]project_name}" --install-types --non-interactive
autopep8 {[flags]autopep8} --exit-code --jobs 0 --recursive "src/{[tox]project_name}" "tests/{[tox]project_name}"
pylint {[flags]pylint} "src/{[tox]project_name}"
bandit {[flags]bandit} --recursive "src/{[tox]project_name}"
bandit {[flags]bandit} --skip B101,B105,B106 --recursive "tests/{[tox]project_name}"
mypy {[flags]mypy} --install-types --non-interactive --package "{[tox]project_name}"
[testenv:docs]
description = Generate source files for documentation
allowlist_externals =
bash
find
mv
changedir = src
dependency_groups =
docs
depends =
@ -55,18 +59,18 @@ deps =
uv
pip_pre = true
setenv =
PYTHONPATH=src
SPHINX_APIDOC_OPTIONS=members,no-undoc-members,show-inheritance
commands =
bash -c 'shtab --shell bash --prefix ahriman --prog ahriman ahriman.application.ahriman._parser > ../package/share/bash-completion/completions/_ahriman'
bash -c 'shtab --shell zsh --prefix ahriman --prog ahriman ahriman.application.ahriman._parser > ../package/share/zsh/site-functions/_ahriman'
argparse-manpage --module ahriman.application.ahriman --function _parser --author "ahriman team" --project-name ahriman --author-email "" --url https://github.com/arcan1s/ahriman --output ../package/share/man/man1/ahriman.1
pydeps ahriman --no-output --show-dot --dot-output architecture.dot --no-config --cluster
mv architecture.dot ../docs/_static/architecture.dot
bash -c 'shtab {[flags]shtab} --shell bash > package/share/bash-completion/completions/_ahriman'
bash -c 'shtab {[flags]shtab} --shell zsh > package/share/zsh/site-functions/_ahriman'
argparse-manpage {[flags]manpage} --module ahriman.application.ahriman --function _parser --output ../package/share/man/man1/ahriman.1
pydeps {[flags]pydeps} --no-output --show-dot --dot-output {tox_root}{/}docs/_static/architecture.dot src/ahriman
# remove autogenerated modules rst files
find ../docs -type f -name "{[tox]project_name}*.rst" -delete
sphinx-apidoc -o ../docs .
find docs -type f -name "{[tox]project_name}*.rst" -delete
sphinx-apidoc --output-dir docs src
# compile list of dependencies for rtd.io
uv pip compile --group ../pyproject.toml:docs --extra s3 --extra validator --extra web --output-file ../docs/requirements.txt --quiet ../pyproject.toml
uv pip compile --group pyproject.toml:docs --extra s3 --extra validator --extra web --output-file docs/requirements.txt --quiet pyproject.toml
[testenv:html]
description = Generate html documentation
@ -77,7 +81,7 @@ deps =
pip_pre = true
recreate = true
commands =
sphinx-build -b html -a -j auto -W docs {envtmpdir}{/}html
sphinx-build --builder html --write-all --jobs auto --fail-on-warning docs {envtmpdir}{/}html
[testenv:publish]
description = Create and publish release to GitHub