diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..591be5b0 --- /dev/null +++ b/.flake8 @@ -0,0 +1,12 @@ +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or +# https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: 2023 Maxwell G + +[flake8] +extend-ignore = E203 +count = true +max-complexity = 10 +# black's max-line-length is 89, but it doesn't touch long string literals. +max-line-length = 100 +statistics = true diff --git a/.github/workflows/antsibull-docs.yml b/.github/workflows/antsibull-docs.yml index 6b5e0851..8c2b9dd3 100644 --- a/.github/workflows/antsibull-docs.yml +++ b/.github/workflows/antsibull-docs.yml @@ -59,28 +59,24 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install poetry - sed -i -e 's/^python = .*/python = "^${{ matrix.python }}"/' pyproject.toml - poetry install - poetry update + pip install -e .[coverage] ../antsibull-core working-directory: antsibull-docs - name: Use antsibull-docs sphinx-init run: | - poetry run coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs sphinx-init --lenient --dest-dir . ${{ matrix.options }} + coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs sphinx-init --lenient --dest-dir . ${{ matrix.options }} working-directory: antsibull-docs - name: Patch build.sh to supply code coverage run: | - sed -i build.sh -e 's!antsibull-docs !poetry run coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs !g' - sed -i build.sh -e 's!sphinx-build !poetry run coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m sphinx.cmd.build !g' + sed -i build.sh -e 's!antsibull-docs !coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs !g' + sed -i build.sh -e 's!sphinx-build !coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m sphinx.cmd.build !g' cat build.sh working-directory: antsibull-docs - name: Install dependencies run: | - poetry run pip install ansible-core - poetry run pip install -r requirements.txt + pip install ansible-core -r requirements.txt working-directory: antsibull-docs - name: Install collections @@ -93,9 +89,9 @@ jobs: - name: Lint collection docs run: | - poetry run coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m antsibull_docs.cli.antsibull_docs lint-collection-docs ~/.ansible/collections/ansible_collections/community/docker --plugin-docs - poetry run coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m antsibull_docs.cli.antsibull_docs lint-collection-docs ~/.ansible/collections/ansible_collections/community/crypto - poetry run coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m antsibull_docs.cli.antsibull_docs lint-collection-docs ~/.ansible/collections/ansible_collections/sensu/sensu_go + coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m antsibull_docs.cli.antsibull_docs lint-collection-docs ~/.ansible/collections/ansible_collections/community/docker --plugin-docs + coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m antsibull_docs.cli.antsibull_docs lint-collection-docs ~/.ansible/collections/ansible_collections/community/crypto + coverage run -p --source antsibull_docs --source sphinx_antsibull_ext -m antsibull_docs.cli.antsibull_docs lint-collection-docs ~/.ansible/collections/ansible_collections/sensu/sensu_go working-directory: antsibull-docs if: contains(matrix.options, '--use-current') @@ -105,24 +101,29 @@ jobs: working-directory: antsibull-docs - name: Validate HTML - run: - python .github/workflows/validate-html.py build/html/ + run: | + pip install html5lib + python tests/validate-html.py build/html/ working-directory: antsibull-docs - name: Test plugin rendering - run: - poetry run coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs plugin --plugin-type module --dest-dir . community.crypto.acme_account_info + run: | + coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs plugin --plugin-type module --dest-dir . community.crypto.acme_account_info working-directory: antsibull-docs if: contains(matrix.options, '--use-current') - - name: Combine and upload coverage stats + - name: Combine coverage stats run: | - poetry run coverage combine .coverage.* - poetry run coverage report - poetry run coverage xml -i - poetry run codecov + coverage combine .coverage.* + coverage report + coverage xml -i working-directory: antsibull-docs + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + working-directory: antsibull-docs + build-stable: name: 'Build stable docsite' runs-on: ubuntu-latest @@ -147,10 +148,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install poetry - sed -i -e 's/^python = .*/python = "^3.11"/' pyproject.toml - poetry install - poetry update + pip install -e .[coverage] ../antsibull-core working-directory: antsibull-docs - name: Get hold of deps file @@ -161,17 +159,21 @@ jobs: - name: Build stable docs RST files run: | mkdir stable-docs - poetry run coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs stable --deps-file ansible.deps --dest-dir stable-docs --no-breadcrumbs --no-indexes + coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs stable --deps-file ansible.deps --dest-dir stable-docs --no-breadcrumbs --no-indexes working-directory: antsibull-docs - - name: Combine and upload coverage stats + - name: Combine coverage stats run: | - poetry run coverage combine .coverage.* - poetry run coverage report - poetry run coverage xml -i - poetry run codecov + coverage combine .coverage.* + coverage report + coverage xml -i working-directory: antsibull-docs + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + working-directory: antsibull-docs + build-devel: name: 'Build devel docsite' runs-on: ubuntu-latest @@ -196,10 +198,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install poetry - sed -i -e 's/^python = .*/python = "^3.11"/' pyproject.toml - poetry install - poetry update + pip install -e .[coverage] ../antsibull-core working-directory: antsibull-docs - name: Get hold of ansible.in file @@ -210,13 +209,17 @@ jobs: - name: Build devel docs RST files run: | mkdir devel-docs - poetry run coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs devel --pieces-file ansible.in --dest-dir devel-docs + coverage run -p --source antsibull_docs -m antsibull_docs.cli.antsibull_docs devel --pieces-file ansible.in --dest-dir devel-docs working-directory: antsibull-docs - - name: Combine and upload coverage stats + - name: Combine coverage stats run: | - poetry run coverage combine .coverage.* - poetry run coverage report - poetry run coverage xml -i - poetry run codecov + coverage combine .coverage.* + coverage report + coverage xml -i working-directory: antsibull-docs + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + working-directory: antsibull-docs diff --git a/.github/workflows/nox.yml b/.github/workflows/nox.yml new file mode 100644 index 00000000..0c89bb6f --- /dev/null +++ b/.github/workflows/nox.yml @@ -0,0 +1,72 @@ +--- +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or +# https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: 2023 Maxwell G - - ${{ contains(fromJson('["3.6", "3.7", "3.8"]'), matrix.python-version) && 'stable-1' || 'main' }} - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install poetry - if [ "${{ matrix.python-version }}" != "3.6" ]; then - # ^3.6 collides with 3.6.1 lower bound in antsibull-core - sed -i -e 's/^python = .*/python = "^${{ matrix.override-pyproject-python || matrix.python-version }}"/' pyproject.toml - fi - poetry install - poetry update - working-directory: antsibull-docs - - - name: Test with pytest and upload coverage stats - run: | - ./test-pytest.sh ${{ matrix.skip-is-error && '--error-for-skips' || '' }} - poetry run coverage xml -i - poetry run codecov - working-directory: antsibull-docs - - # Poetry won't install dependencies for antsibull-docs anymore on 3.6. So let's do this differently. - build-3-6: - runs-on: ubuntu-20.04 - strategy: - matrix: - python-version: - - 3.6 - - steps: - - name: Check out antsibull-docs - uses: actions/checkout@v3 - with: - path: antsibull-docs - - - name: Check out dependent project antsibull-core - uses: actions/checkout@v3 - with: - repository: ansible-community/antsibull-core - path: antsibull-core - ref: stable-1 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install --use-pep517 -e . -e ../antsibull-core \ - asynctest cryptography pytest "pytest-asyncio>=0.12" - working-directory: antsibull-docs - - - name: Test with pytest - run: >- - python -W 'ignore:"@coroutine" decorator is deprecated::asynctest.case' -m pytest -vv tests - working-directory: antsibull-docs diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml deleted file mode 100644 index d44c17fb..00000000 --- a/.github/workflows/reuse.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -name: Verify REUSE - -on: - push: - branches: - - main - - stable-* - pull_request: - branches: - - main - - stable-* - # Run once per week (Friday at 06:00 UTC) - schedule: - - cron: '0 6 * * 5' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Install dependencies - run: | - pip install reuse - - - name: Check REUSE compliance - run: | - reuse lint diff --git a/.mypy.ini b/.mypy.ini deleted file mode 100644 index b5aaf808..00000000 --- a/.mypy.ini +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -[mypy] - -[mypy-sh.*] -ignore_missing_imports = True - -[mypy-aiofiles.*] -ignore_missing_imports = True - -[mypy-semantic_version.*] -ignore_missing_imports = True diff --git a/.pylintrc.automated b/.pylintrc.automated index db9e18eb..d878f0c4 100644 --- a/.pylintrc.automated +++ b/.pylintrc.automated @@ -68,7 +68,6 @@ disable= duplicate-code, fixme, invalid-name, - locally-enabled, locally-disabled, missing-class-docstring, missing-function-docstring, @@ -253,13 +252,6 @@ max-line-length=100 # Maximum number of lines in a module. max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no diff --git a/README.md b/README.md index daf3b54d..b6eb7fec 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later # antsibull-docs -- Ansible Documentation Build Scripts [![Discuss on Matrix at #docs:ansible.com](https://img.shields.io/matrix/docs:ansible.com.svg?server_fqdn=ansible-accounts.ems.host&label=Discuss%20on%20Matrix%20at%20%23docs:ansible.com&logo=matrix)](https://matrix.to/#/#docs:ansible.com) -[![Python linting badge](https://github.com/ansible-community/antsibull-docs/workflows/Python%20linting/badge.svg?event=push&branch=main)](https://github.com/ansible-community/antsibull-docs/actions?query=workflow%3A%22Python+linting%22+branch%3Amain) -[![Python testing badge](https://github.com/ansible-community/antsibull-docs/workflows/Python%20testing/badge.svg?event=push&branch=main)](https://github.com/ansible-community/antsibull-docs/actions?query=workflow%3A%22Python+testing%22+branch%3Amain) +[![Nox badge](https://github.com/ansible-community/antsibull-docs/actions/workflows/nox.yml/badge.svg)](https://github.com/ansible-community/antsibull-docs/actions/workflows/nox.yml) [![Build docs testing badge](https://github.com/ansible-community/antsibull-docs/workflows/antsibull-docs%20tests/badge.svg?event=push&branch=main)](https://github.com/ansible-community/antsibull-docs/actions?query=workflow%3A%22antsibull-docs+tests%22+branch%3Amain) [![Build CSS testing badge](https://github.com/ansible-community/antsibull-docs/workflows/Build%20CSS/badge.svg?event=push&branch=main)](https://github.com/ansible-community/antsibull-docs/actions?query=workflow%3A%22Build+CSS%22+branch%3Amain) [![Codecov badge](https://img.shields.io/codecov/c/github/ansible-community/antsibull-docs)](https://codecov.io/gh/ansible-community/antsibull-docs) @@ -33,26 +32,6 @@ From version 1.0.0 on, antsibull-docs sticks to semantic versioning and aims at We explicitly exclude code compatibility. **antsibull-docs is not supposed to be used as a library.** The only exception are dependencies with other antsibull projects (currently, only [antsibull](https://github.com/ansible-community/antsibull/) itself). If you want to use a certain part of antsibull-docs as a library, please create an issue so we can discuss whether we add a stable interface for **parts** of the Python code. We do not promise that this will actually happen though. -## Running from source - -Please note that to run antsibull-docs from source, you need to install some related projects adjacent to the antsibull-docs checkout. More precisely, assuming you checked out the antsibull-docs repository in a directory `./antsibull-docs/`, you need to check out the following projects in the following locations: - -- [antsibull-core](https://github.com/ansible-community/antsibull-core/) needs to be checked out in `./antsibull-core/`. - -This can be done as follows: - - git clone https://github.com/ansible-community/antsibull-core.git - git clone https://github.com/ansible-community/antsibull-docs.git - cd antsibull-docs - -Scripts are created by poetry at build time. So if you want to run from a checkout, you'll have to run them under poetry:: - - python3 -m pip install poetry - poetry install # Installs dependencies into a virtualenv - poetry run antsibull-docs --help - -Note: When installing a package published by poetry, it is best to use pip >= 19.0. Installing with pip-18.1 and below could create scripts which use pkg_resources which can slow down startup time (in some environments by quite a large amount). - ## Using the Sphinx extension Include it in your Sphinx configuration ``conf.py``:: @@ -83,20 +62,61 @@ apt-get install sassc npm install -g autoprefixer cssnano postcss postcss-cli ``` +## Development + +Install and run `nox` to run all tests. That's it for simple contributions! +`nox` will create virtual environments in `.nox` inside the checked out project +and install the requirements needed to run the tests there. + + +--- + +antsibull-docs depends on the sister antsibull-core project. +By default, `nox` will install a development version of this project from +Github. +If you're hacking on antsibull-core alongside antsibull-docs, nox will automatically +install the project from `../antsibull-core` when running tests if those paths +exist. +You can change this behavior through the `OTHER_ANTSIBULL_MODE` env var: + +- `OTHER_ANTSIBULL_MODE=auto` — the default behavior described above +- `OTHER_ANTSIBULL_MODE=local` — install the project from `../antsibull-core`. + Fail if that paths doesn't exist. +- `OTHER_ANTSIBULL_MODE=git` — install the projects from the Github main branch +- `OTHER_ANTSIBULL_MODE=pypi` — install the latest version from PyPI + + +To run specific tests: + +1. `nox -e test` to only run unit tests; +2. `nox -e lint` to run all linters; +3. `nox -e codeqa` to run `flake8`, `pylint`, and `reuse lint`; +4. `nox -e typing` to run `mypy` and `pyre`. + +To create a more complete local development env: + +``` console +git clone https://github.com/ansible-community/antsibull-core.git +git clone https://github.com/ansible-community/antsibull-docs.git +cd antsibull-docs +python3 -m venv venv +. ./venv/bin/activate +pip install -e '.[dev]' -e ../antsibull-core +[...] +nox +``` + ## Creating a new release: -If you want to create a new release:: - - vim pyproject.toml # Make sure version number is correct - vim changelogs/fragment/$VERSION_NUMBER.yml # create 'release_summary:' fragment - antsibull-changelog release --version $VERSION_NUMBER - git add CHANGELOG.rst changelogs - git commit -m "Release $VERSION_NUMBER." - poetry build - poetry publish # Uploads to pypi. Be sure you really want to do this - - git tag $VERSION_NUMBER - git push --tags - vim pyproject.toml # Bump the version number to X.Y.Z.post0 - git commit -m 'Update the version number for the next release' pyproject.toml - git push +1. Run `nox -e bump -- `. This: + * Bumps the package version in `pyproject.toml`. + * Creates `changelogs/fragments/.yml` with a `release_summary` section. + * Runs `antsibull-changelog release` and adds the changed files to git. + * Commits with message `Release .` and runs `git tag -a -m 'antsibull-docs ' `. + * Runs `hatch build`. +2. Run `git push` to the appropriate remotes. +3. Once CI passes on GitHub, run `nox -e publish`. This: + * Runs `hatch publish`; + * Bumps the version to `.post0`; + * Adds the changed file to git and run `git commit -m 'Post-release version bump.'`; +4. Run `git push --follow-tags` to the appropriate remotes and create a GitHub release. diff --git a/changelogs/fragments/hatchling.yml b/changelogs/fragments/hatchling.yml new file mode 100644 index 00000000..ff1bc97b --- /dev/null +++ b/changelogs/fragments/hatchling.yml @@ -0,0 +1,9 @@ +--- +major_changes: + - Change pyproject build backend from ``poetry-core`` to ``hatchling``. + ``pip install antsibull-docs`` works exactly the same as before, + but some users may be affected depending on how they build/install the + project + (https://github.com/ansible-community/antsibull-docs/pull/115). +breaking_changes: + - Drop support for Python 3.6, 3.7, and 3.8 (https://github.com/ansible-community/antsibull-docs/pull/115)." diff --git a/lint-flake8.sh b/lint-flake8.sh deleted file mode 100755 index c9e7f727..00000000 --- a/lint-flake8.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -set -e -poetry run flake8 src/antsibull_docs src/sphinx_antsibull_ext --count --max-complexity=10 --max-line-length=100 --statistics "$@" diff --git a/lint-mypy.sh b/lint-mypy.sh deleted file mode 100755 index 65b8583d..00000000 --- a/lint-mypy.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -set -e -MYPYPATH=stubs/ poetry run mypy src/antsibull_docs src/sphinx_antsibull_ext "$@" diff --git a/lint-pylint.sh b/lint-pylint.sh deleted file mode 100755 index 94cc20b3..00000000 --- a/lint-pylint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -set -e -poetry run pylint --rcfile .pylintrc.automated src/antsibull_docs src/sphinx_antsibull_ext "$@" diff --git a/lint-pyre.sh b/lint-pyre.sh deleted file mode 100755 index c48ef62c..00000000 --- a/lint-pyre.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -set -e - -PURELIB=$(poetry run python -c 'import sysconfig; print(sysconfig.get_path("purelib"))') -PLATLIB=$(poetry run python -c 'import sysconfig; print(sysconfig.get_path("platlib"))') -poetry run pyre --source-directory src --search-path ../antsibull-core/src/ --search-path "$PURELIB" --search-path "$PLATLIB" --search-path stubs/ "$@" diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 172d7578..00000000 --- a/mypy.ini +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -[mypy] - -[mypy-sh.*] -ignore_missing_imports = True - -[mypy-semantic_version.*] -ignore_missing_imports = True diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..a92df65a --- /dev/null +++ b/noxfile.py @@ -0,0 +1,267 @@ +# Copyright (C) 2023 Maxwell G +# SPDX-License-Identifier: GPL-3.0-or-later +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or +# https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + +import os +from pathlib import Path + +import nox + +DEFAULT_MODE = os.environ.get("OTHER_ANTSIBULL_MODE", "auto") +IN_CI = "GITHUB_ACTIONS" in os.environ +ALLOW_EDITABLE = os.environ.get("ALLOW_EDITABLE", str(not IN_CI)).lower() in ( + "1", + "true", +) + +# Always install latest pip version +os.environ["VIRTUALENV_DOWNLOAD"] = "1" +nox.options.sessions = "lint", "test" + + +def install(session: nox.Session, *args, editable=False, **kwargs): + # nox --no-venv + if isinstance(session.virtualenv, nox.virtualenv.PassthroughEnv): + session.warn(f"No venv. Skipping installation of {args}") + return + # Don't install in editable mode in CI or if it's explicitly disabled. + # This ensures that the wheel contains all of the correct files. + if editable and ALLOW_EDITABLE: + args = ("-e", *args) + session.install(*(str(arg) for arg in args), "-U", **kwargs) + + +def other_antsibull( + mode: str | None = None, +) -> list[str | Path]: + if mode is None: + mode = DEFAULT_MODE + to_install: list[str | Path] = [] + args = ("antsibull-core", ) + for project in args: + path = Path("../", project) + path_exists = path.is_dir() + if mode == "auto": + if path_exists: + mode = "local" + else: + mode = "git" + if mode == "local": + if not path_exists: + raise ValueError(f"Cannot install {project}! {path} does not exist!") + if ALLOW_EDITABLE: + to_install.append("-e") + to_install.append(path) + elif mode == "git": + to_install.append( + f"{project} @ " + f"https://github.com/ansible-community/{project}/archive/main.tar.gz" + ) + elif mode == "pypi": + to_install.append(project) + else: + raise ValueError(f"install_other_antsibull: invalid argument mode={mode!r}") + return to_install + + +@nox.session(python=["3.9", "3.10", "3.11"]) +def test(session: nox.Session): + install( + session, + ".[test, coverage]", + *other_antsibull(), + editable=True, + ) + covfile = Path(session.create_tmp(), ".coverage") + more_args = [] + if session.python == "3.11": + more_args.append("--error-for-skips") + session.run( + "pytest", + "--cov-branch", + "--cov=antsibull_docs", + "--cov=sphinx_antsibull_ext", + "--cov-report", + "term-missing", + *more_args, + *session.posargs, + env={"COVERAGE_FILE": f"{covfile}", **os.environ}, + ) + + +@nox.session +def coverage(session: nox.Session): + install(session, "coverage[toml]") + combined = map(str, Path().glob(".nox/test*/tmp/.coverage")) + # Combine the results into a single .coverage file in the root + session.run("coverage", "combine", "--keep", *combined) + # Create a coverage.xml for codecov + session.run("coverage", "xml") + # Display the combined results to the user + session.run("coverage", "report", "-m") + + +@nox.session +def lint(session: nox.Session): + session.notify("codeqa") + session.notify("typing") + + +@nox.session +def codeqa(session: nox.Session): + install(session, ".[codeqa]", *other_antsibull(), editable=True) + session.run("flake8", "src/antsibull_docs", "src/sphinx_antsibull_ext", *session.posargs) + session.run("pylint", "--rcfile", ".pylintrc.automated", "src/antsibull_docs", "src/sphinx_antsibull_ext") + session.run("reuse", "lint") + + +@nox.session +def typing(session: nox.Session): + others = other_antsibull() + install(session, ".[typing]", *others, editable=True) + session.run("mypy", "src/antsibull_docs", "src/sphinx_antsibull_ext") + + additional_libraries = [] + for path in others: + if isinstance(path, Path): + additional_libraries.extend(("--search-path", str(path / "src"))) + + purelib = session.run( + "python", + "-c", + "import sysconfig; print(sysconfig.get_path('purelib'))", + silent=True, + ).strip() + platlib = session.run( + "python", + "-c", + "import sysconfig; print(sysconfig.get_path('platlib'))", + silent=True, + ).strip() + session.run( + "pyre", + "--source-directory", + "src", + "--search-path", + purelib, + "--search-path", + platlib, + "--search-path", + "stubs/", + *additional_libraries, + ) + + +def _repl_version(session: nox.Session, new_version: str): + with open("pyproject.toml", "r+") as fp: + lines = tuple(fp) + fp.seek(0) + for line in lines: + if line.startswith("version = "): + line = f'version = "{new_version}"\n' + fp.write(line) + fp.truncate() + + +def check_no_modifications(session: nox.Session) -> None: + modified = session.run( + "git", + "status", + "--porcelain=v1", + "--untracked=normal", + external=True, + silent=True, + ) + if modified: + session.error( + "There are modified or untracked files. " + "Commit, restore, or remove them before running this" + ) + + +@nox.session +def bump(session: nox.Session): + check_no_modifications(session) + if len(session.posargs) not in (1, 2): + session.error( + "Must specify 1-2 positional arguments: nox -e bump -- " + "[ ]." + " If release_summary_message has not been specified, " + "a file changelogs/fragments/.yml must exist" + ) + version = session.posargs[0] + fragment_file = Path(f"changelogs/fragments/{version}.yml") + if len(session.posargs) == 1: + if not fragment_file.is_file(): + session.error( + f"Either {fragment_file} must already exist, " + "or two positional arguments must be provided." + ) + install(session, "antsibull-changelog", "hatch", "tomli ; python_version < '3.11'") + _repl_version(session, version) + if len(session.posargs) > 1: + fragment = session.run( + "python", + "-c", + "import yaml ; " + f"print(yaml.dump(dict(release_summary={repr(session.posargs[1])})))", + silent=True, + ) + with open(fragment_file, "w") as fp: + print(fragment, file=fp) + session.run("git", "add", "pyproject.toml", fragment_file, external=True) + session.run("git", "commit", "-m", f"Prepare {version}.", external=True) + session.run("antsibull-changelog", "release") + session.run( + "git", + "add", + "CHANGELOG.rst", + "changelogs/changelog.yaml", + "changelogs/fragments/", + external=True, + ) + install(session, ".") # Smoke test + session.run("git", "commit", "-m", f"Release {version}.", external=True) + session.run( + "git", + "tag", + "-a", + "-m", + f"antsibull-docs {version}", + "--edit", + version, + external=True, + ) + session.run("hatch", "build", "--clean") + + +@nox.session +def publish(session: nox.Session): + check_no_modifications(session) + install(session, "hatch") + session.run("hatch", "publish", *session.posargs) + version = session.run("hatch", "version", silent=True).strip() + _repl_version(session, f"{version}.post0") + session.run("git", "commit", "pyproject.toml", external=True) + session.run("git", "commit", "-m", "Post-release version bump.", external=True) + + +@nox.session +def install_env(session: nox.Session): + """ + Install antsibull-docs and the other project in the the local environment. + Invoke with `nox -e install_env --no-venv` + """ + session.run( + "pip", + "install", + "-U", + ".", + *other_antsibull(), + *session.posargs, + external=True, + editable=True, + ) diff --git a/pyproject.toml b/pyproject.toml index d2d3fd25..f579ba31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,78 +3,129 @@ # SPDX-License-Identifier: GPL-3.0-or-later [build-system] -requires = ["poetry-core>=1.0.7"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" -[tool.poetry] +[project] name = "antsibull-docs" -version = "1.11.0.post0" +version = "2.0.0.a1" description = "Tools for building Ansible documentation" -authors = ["Toshio Kuratomi ", "Felix Fontein "] license = "GPL-3.0-or-later" +license-files = {globs=["LICENSES/*.txt"]} readme = "README.md" -repository = "https://github.com/ansible-community/antsibull-docs" -packages = [ - { include = "antsibull_docs", from="src" }, - { include = "sphinx_antsibull_ext", from="src" }, - { include = "tests", format = "sdist" } -] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Framework :: Ansible", - "Intended Audience :: Developers" + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Typing :: Typed", +] +requires-python = ">=3.9" +dependencies = [ + "ansible-pygments", + "antsibull-core >= 1.2.0, < 3.0.0", + "asyncio-pool", + "docutils", + "jinja2 >= 3.0", + "packaging", + "rstcheck >= 3.0.0, < 7.0.0", + "sphinx", + # sh v2 has breaking changes. + # https://github.com/ansible-community/antsibull-core/issues/34 + "sh >= 1.0.0, < 2.0.0", + # pydantic v2 is a major rewrite + "pydantic >= 1.0.0, < 2.0.0", + "semantic_version", + "aiohttp >= 3.0.0", + "twiggy", + "PyYAML", ] -[tool.poetry.urls] +[[project.authors]] +name = "Toshio Kuratomi" +email = "a.badger@gmail.com" + +[[project.authors]] +name = "Felix Fontein" +email = "felix@fontein.de" + +[[project.maintainers]] +name = "Felix Fontein" +email = "felix@fontein.de" + +[[project.maintainers]] +name = "Maxwell G" +email = "maxwell@gtmx.me" + +[project.urls] +"Source code" = "https://github.com/ansible-community/antsibull-docs" "Code of Conduct" = "https://docs.ansible.com/ansible/latest/community/code_of_conduct.html" "Bug tracker" = "https://github.com/ansible-community/antsibull-docs/issues" -[tool.poetry.scripts] +[project.scripts] antsibull-docs = "antsibull_docs.cli.antsibull_docs:main" -[tool.poetry.dependencies] -python = "^3.6.1" -ansible-pygments = "*" -antsibull-core = ">= 1.2.0, < 3.0.0" -asyncio-pool = "*" -docutils = "*" -jinja2 = ">= 3.0" -packaging = "*" -rstcheck = ">= 3.0.0, < 7.0.0" -sphinx = "*" -# sh v2 has breaking changes. -# https://github.com/ansible-community/antsibull-core/issues/34 -sh = ">= 1.0.0 < 2.0.0" -# pydantic v2 is a major rewrite -pydantic = ">= 1.0.0 < 2.0.0" -semantic_version = "*" -aiohttp = ">= 3.0.0" -twiggy = "*" -PyYAML = "*" +[project.optional-dependencies] +codeqa = [ + "flake8 >= 3.8.0", + "pylint >= 2.17.2", + "reuse", +] +coverage = [ + "coverage[toml]", +] +test = [ + "ansible-core >= 2.14.0", + "asynctest", + "cryptography", + "pytest", + "pytest-asyncio >= 0.12", + "pytest-cov", + "pytest-error-for-skips", +] +typing = [ + "mypy", + # https://github.com/facebook/pyre-check/issues/398 + "pyre-check >= 0.9.17", + "types-aiofiles", + "types-docutils", + "types-PyYAML", +] +dev = [ + # Used by nox sessions + "antsibull-docs[codeqa]", + "antsibull-docs[test]", + "antsibull-docs[typing]", + # misc + "nox", +] -[tool.poetry.dev-dependencies] -ansible-core = {version = ">= 2.14.0b1", python = ">=3.9"} -asynctest = "*" -cryptography = "*" -codecov = "*" -flake8 = ">= 3.8.0" -mypy = "*" -# https://github.com/facebook/pyre-check/issues/398 -pyre-check = "^0.9.15" -pylint = "^2.12.0" -pytest = "*" -pytest-asyncio = ">= 0.12" -pytest-cov = "*" -pytest-error-for-skips = "*" -# Needed for TypedDict in rstcheck-core stubs -typing-extensions = {version = ">=3.7.4", python = "<3.8"} -types-aiofiles = "*" -types-docutils = "*" -types-PyYAML = "*" -# For development, we install dependent projects under our control in dev mode: -antsibull-core = { path = "../antsibull-core/", develop = true } +[tool.hatch.build.targets.wheel] +packages = ["src/antsibull_docs", "src/sphinx_antsibull_ext"] [tool.isort] -line_length = 100 +# Match black +profile = "black" +line_length = 89 +# multi_line_output = 3 include_trailing_comma = true + +[tool.coverage.paths] +source = [ + "src", + "*/site-packages", +] + +[tool.mypy] +mypy_path = "stubs/" + +[[tool.mypy.overrides]] +module = "sh" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "semantic_version" +ignore_missing_imports = true diff --git a/src/antsibull_docs/cli/doc_commands/collection.py b/src/antsibull_docs/cli/doc_commands/collection.py index e81b2ade..3fdc79ad 100644 --- a/src/antsibull_docs/cli/doc_commands/collection.py +++ b/src/antsibull_docs/cli/doc_commands/collection.py @@ -24,7 +24,7 @@ from .stable import generate_docs_for_all_collections if t.TYPE_CHECKING: - import semantic_version as semver # pylint:disable=unused-import + import semantic_version as semver mlog = log.fields(mod=__name__) diff --git a/src/antsibull_docs/cli/doc_commands/devel.py b/src/antsibull_docs/cli/doc_commands/devel.py index 5475f0f9..87598b45 100644 --- a/src/antsibull_docs/cli/doc_commands/devel.py +++ b/src/antsibull_docs/cli/doc_commands/devel.py @@ -25,7 +25,7 @@ from .stable import generate_docs_for_all_collections if t.TYPE_CHECKING: - import semantic_version as semver # pylint:disable=unused-import + import semantic_version as semver mlog = log.fields(mod=__name__) diff --git a/src/antsibull_docs/cli/doc_commands/sphinx_init.py b/src/antsibull_docs/cli/doc_commands/sphinx_init.py index 64d0b030..b565a8f2 100644 --- a/src/antsibull_docs/cli/doc_commands/sphinx_init.py +++ b/src/antsibull_docs/cli/doc_commands/sphinx_init.py @@ -70,7 +70,7 @@ def toperky(value: t.Any) -> str: if all(ch not in value for ch in '\\={}[]') and value.strip() == value: return value return f'"{value}"' - raise Exception(f'toperky filter cannot handle type {type(value)}') + raise RuntimeError(f'toperky filter cannot handle type {type(value)}') def python_repr(value: t.Any) -> str: diff --git a/src/antsibull_docs/cli/doc_commands/stable.py b/src/antsibull_docs/cli/doc_commands/stable.py index 9a904d3d..33140151 100644 --- a/src/antsibull_docs/cli/doc_commands/stable.py +++ b/src/antsibull_docs/cli/doc_commands/stable.py @@ -50,7 +50,7 @@ from ...write_docs.plugins import output_all_plugin_rst if t.TYPE_CHECKING: - import semantic_version as semver # pylint:disable=unused-import + import semantic_version as semver mlog = log.fields(mod=__name__) diff --git a/src/antsibull_docs/collection_links.py b/src/antsibull_docs/collection_links.py index 5061397e..98176f1a 100644 --- a/src/antsibull_docs/collection_links.py +++ b/src/antsibull_docs/collection_links.py @@ -31,34 +31,34 @@ mlog = log.fields(mod=__name__) -_ANSIBLE_CORE_METADATA = dict( - edit_on_github=dict( - repository='ansible/ansible', - branch='devel', - path_prefix='lib/ansible/', - ), - authors=['Ansible, Inc.'], - description='These are all modules and plugins contained in ansible-core.', - links=[ - dict(description='Issue Tracker', url='https://github.com/ansible/ansible/issues'), - dict(description='Repository (Sources)', url='https://github.com/ansible/ansible'), +_ANSIBLE_CORE_METADATA = { + 'edit_on_github': { + 'repository': 'ansible/ansible', + 'branch': 'devel', + 'path_prefix': 'lib/ansible/', + }, + 'authors': ['Ansible, Inc.'], + 'description': 'These are all modules and plugins contained in ansible-core.', + 'links': [ + {'description': 'Issue Tracker', 'url': 'https://github.com/ansible/ansible/issues'}, + {'description': 'Repository (Sources)', 'url': 'https://github.com/ansible/ansible'}, ], - communication=dict( - irc_channels=[dict( - topic='General usage and support questions', - network='Libera', - channel='#ansible', - )], - matrix_rooms=[dict( - topic='General usage and support questions', - room='#users:ansible.im', - )], - mailing_lists=[dict( - topic='Ansible Project List', - url='https://groups.google.com/g/ansible-project', - )], - ), -) + 'communication': { + 'irc_channels': [{ + 'topic': 'General usage and support questions', + 'network': 'Libera', + 'channel': '#ansible', + }], + 'matrix_rooms': [{ + 'topic': 'General usage and support questions', + 'room': '#users:ansible.im', + }], + 'mailing_lists': [{ + 'topic': 'Ansible Project List', + 'url': 'https://groups.google.com/g/ansible-project', + }], + }, +} def _extract_authors(data: t.Dict) -> t.List[str]: @@ -85,7 +85,7 @@ def extract(key: str, desc: str, if data.get(other_key) == url: return if isinstance(url, str): - result.append(Link.parse_obj(dict(description=desc, url=url))) + result.append(Link.parse_obj({'description': desc, 'url': url})) # extract('documentation', 'Documentation') extract('issues', 'Issue Tracker') @@ -220,7 +220,7 @@ def lint_collection_links(collection_path: str) -> t.List[t.Tuple[str, int, int, CollectionEditOnGitHub, Link, IRCChannel, MatrixRoom, MailingList, Communication, CollectionLinks, ): - cls.__config__.extra = Extra.forbid + cls.__config__.extra = Extra.forbid # type: ignore[attr-defined] try: index_path = os.path.join(collection_path, 'docs', 'docsite', 'links.yml') diff --git a/src/antsibull_docs/data/collection-enum.py b/src/antsibull_docs/data/collection-enum.py index bc40bb4f..3a5668e6 100644 --- a/src/antsibull_docs/data/collection-enum.py +++ b/src/antsibull_docs/data/collection-enum.py @@ -10,6 +10,7 @@ """Enumerate collections and all their plugin's docs.""" # pylint:disable=protected-access +# pylint:disable=import-error # Parts taken from Ansible's ansible-doc sources @@ -55,7 +56,7 @@ def load_plugin(loader, plugin_type, plugin): documentation, plainexamples, returndocs, metadata = get_docstring( filename, fragment_loader, verbose=False, - collection_name=collection_name, is_module=(plugin_type == 'module')) + collection_name=collection_name, is_module=plugin_type == 'module') if documentation is None: result['error'] = 'No valid documentation found' diff --git a/src/antsibull_docs/docs_parsing/ansible_doc.py b/src/antsibull_docs/docs_parsing/ansible_doc.py index 9dc0a4b8..1c1d02ce 100644 --- a/src/antsibull_docs/docs_parsing/ansible_doc.py +++ b/src/antsibull_docs/docs_parsing/ansible_doc.py @@ -26,7 +26,7 @@ from .fqcn import get_fqcn_parts if t.TYPE_CHECKING: - from antsibull_core.venv import FakeVenvRunner, VenvRunner # pylint:disable=unused-import + from antsibull_core.venv import FakeVenvRunner, VenvRunner mlog = log.fields(mod=__name__) @@ -177,10 +177,10 @@ def _extract_ansible_builtin_metadata(stdout: str) -> AnsibleCollectionMetadata: version = match.group(1) break if path is None: - raise Exception( + raise RuntimeError( f'Cannot extract module location path from ansible --version output: {stdout}') if version is None: - raise Exception( + raise RuntimeError( f'Cannot extract ansible-core version from ansible --version output: {stdout}') return AnsibleCollectionMetadata(path=path, version=version) diff --git a/src/antsibull_docs/docs_parsing/ansible_doc_core_213.py b/src/antsibull_docs/docs_parsing/ansible_doc_core_213.py index e7bc4f34..481905ea 100644 --- a/src/antsibull_docs/docs_parsing/ansible_doc_core_213.py +++ b/src/antsibull_docs/docs_parsing/ansible_doc_core_213.py @@ -17,7 +17,7 @@ from .fqcn import get_fqcn_parts if t.TYPE_CHECKING: - from antsibull_core.venv import FakeVenvRunner, VenvRunner # pylint:disable=unused-import + from antsibull_core.venv import FakeVenvRunner, VenvRunner mlog = log.fields(mod=__name__) diff --git a/src/antsibull_docs/docs_parsing/ansible_internal.py b/src/antsibull_docs/docs_parsing/ansible_internal.py index 42bd9cf1..35267de9 100644 --- a/src/antsibull_docs/docs_parsing/ansible_internal.py +++ b/src/antsibull_docs/docs_parsing/ansible_internal.py @@ -18,7 +18,7 @@ from . import AnsibleCollectionMetadata, _get_environment if t.TYPE_CHECKING: - from antsibull_core.venv import FakeVenvRunner, VenvRunner # pylint:disable=unused-import + from antsibull_core.venv import FakeVenvRunner, VenvRunner mlog = log.fields(mod=__name__) diff --git a/src/antsibull_docs/docs_parsing/parsing.py b/src/antsibull_docs/docs_parsing/parsing.py index 27956597..d005a78e 100644 --- a/src/antsibull_docs/docs_parsing/parsing.py +++ b/src/antsibull_docs/docs_parsing/parsing.py @@ -19,7 +19,7 @@ from .ansible_internal import get_ansible_plugin_info as ansible_internal_get_ansible_plugin_info if t.TYPE_CHECKING: - from antsibull_core.venv import FakeVenvRunner, VenvRunner # pylint:disable=unused-import + from antsibull_core.venv import FakeVenvRunner, VenvRunner mlog = log.fields(mod=__name__) @@ -74,4 +74,4 @@ async def get_ansible_plugin_info(venv: t.Union['VenvRunner', 'FakeVenvRunner'], return await ansible_doc_core_213_get_ansible_plugin_info( venv, collection_dir, collection_names=collection_names) - raise Exception(f'Invalid value for doc_parsing_backend: {doc_parsing_backend}') + raise RuntimeError(f'Invalid value for doc_parsing_backend: {doc_parsing_backend}') diff --git a/src/antsibull_docs/lint_helpers.py b/src/antsibull_docs/lint_helpers.py index 014b0376..474088a5 100644 --- a/src/antsibull_docs/lint_helpers.py +++ b/src/antsibull_docs/lint_helpers.py @@ -26,7 +26,7 @@ def load_collection_info(path_to_collection: str) -> t.Dict[str, t.Any]: galaxy_yml = load_yaml_file(galaxy_yml_path) return galaxy_yml - raise Exception(f'Cannot find files {manifest_json_path} and {galaxy_yml_path}') + raise RuntimeError(f'Cannot find files {manifest_json_path} and {galaxy_yml_path}') def load_collection_name(path_to_collection: str) -> str: diff --git a/src/antsibull_docs/markup/format.py b/src/antsibull_docs/markup/format.py index 71cd214c..3affc535 100644 --- a/src/antsibull_docs/markup/format.py +++ b/src/antsibull_docs/markup/format.py @@ -7,6 +7,8 @@ Flexible formatting of DOM. """ +# pylint:disable=useless-option-value + import abc import typing as t diff --git a/src/antsibull_docs/rstcheck.py b/src/antsibull_docs/rstcheck.py index a8e7cf86..cb9c1e33 100644 --- a/src/antsibull_docs/rstcheck.py +++ b/src/antsibull_docs/rstcheck.py @@ -46,11 +46,14 @@ def check_rst_content(content: str, filename: t.Optional[str] = None, return [(result['line_number'], 0, result['message']) for result in core_results] else: if ignore_directives or ignore_roles: + # pylint: disable-next=no-member,used-before-assignment rstcheck.ignore_directives_and_roles( ignore_directives or [], ignore_roles or []) + # pylint: disable-next=no-member results = rstcheck.check( content, filename=filename, + # pylint: disable-next=used-before-assignment report_level=docutils.utils.Reporter.WARNING_LEVEL, ) return [(result[0], 0, result[1]) for result in results] diff --git a/src/antsibull_docs/schemas/collection_links.py b/src/antsibull_docs/schemas/collection_links.py index 90281299..6f8d00ae 100644 --- a/src/antsibull_docs/schemas/collection_links.py +++ b/src/antsibull_docs/schemas/collection_links.py @@ -31,7 +31,7 @@ class CollectionEditOnGitHub(p.BaseModel): path_prefix: str = '' @p.validator('path_prefix', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def ensure_trailing_slash(cls, obj): if isinstance(obj, str): obj = obj.rstrip('/') @@ -62,7 +62,7 @@ class MailingList(p.BaseModel): subscribe: t.Optional[str] = None @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def add_subscribe(cls, values): """If 'subscribe' is not provided, try to deduce it from the URL.""" @@ -88,7 +88,7 @@ def empty(self): class CollectionLinks(p.BaseModel): edit_on_github: t.Optional[CollectionEditOnGitHub] = None authors: t.List[str] = [] - description: t.Optional[str] + description: t.Optional[str] = None issue_tracker: t.Optional[str] = None links: t.List[Link] = [] extra_links: t.List[Link] = [] diff --git a/src/antsibull_docs/schemas/docs/base.py b/src/antsibull_docs/schemas/docs/base.py index f0c0e813..2d9b9213 100644 --- a/src/antsibull_docs/schemas/docs/base.py +++ b/src/antsibull_docs/schemas/docs/base.py @@ -284,7 +284,7 @@ def normalize_return_type_names(obj): return obj -TYPE_CHECKERS = { +TYPE_CHECKERS: dict[str, t.Callable[[t.Any], t.Any]] = { 'str': check_type_str, 'list': check_type_list, 'dict': check_type_dict, @@ -370,7 +370,7 @@ class DeprecationSchema(BaseModel): alternative: str = '' @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def rename_version(cls, values): """Make deprecations at this level match the toplevel name.""" version = values.get('version', _SENTINEL) @@ -385,7 +385,7 @@ def rename_version(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def rename_date(cls, values): """Make deprecations at this level match the toplevel name.""" date = values.get('date', _SENTINEL) @@ -400,7 +400,7 @@ def rename_date(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def rename_collection_name(cls, values): """Make deprecations at this level match the toplevel name.""" collection_name = values.get('collection_name', _SENTINEL) @@ -415,7 +415,7 @@ def rename_collection_name(cls, values): return values @p.root_validator - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def require_version_xor_date(cls, values): """Make sure either removed_in or removed_at_date are specified, but not both.""" # This should be changed to a way that also works in the JSON schema; see @@ -431,7 +431,7 @@ def require_version_xor_date(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def merge_typo_names(cls, values): alternatives = values.get('alternatives', _SENTINEL) @@ -458,19 +458,19 @@ class OptionsSchema(BaseModel): version_added_collection: str = COLLECTION_NAME_F @p.validator('aliases', 'description', 'choices', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def list_from_scalars(cls, obj): return list_from_scalars(obj) @p.validator('default', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def is_json_value(cls, obj): if not is_json_value(obj): raise ValueError('`default` must be a JSON value') return obj @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def get_rid_of_name(cls, values): """ Remove name from this schema. @@ -483,7 +483,7 @@ def get_rid_of_name(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def merge_typo_names(cls, values): element_type = values.get('element_type', _SENTINEL) @@ -506,12 +506,12 @@ def merge_typo_names(cls, values): return values @p.validator('type', 'elements', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def normalize_option_type(cls, obj): return normalize_option_type_names(obj) @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def normalize_default_choices(cls, values): if isinstance(values.get('choices'), dict): for k, v in values['choices'].items(): @@ -555,7 +555,7 @@ class AttributeSchemaBase(BaseModel, metaclass=abc.ABCMeta): version_added_collection: str = COLLECTION_NAME_F @p.validator('description', 'details', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def list_from_scalars(cls, obj): return list_from_scalars(obj) @@ -568,7 +568,7 @@ class AttributeSchemaActionGroup(AttributeSchemaBase): # for 'action_group' membership: t.List[str] @p.validator('membership', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def list_from_scalars_sub(cls, obj): return list_from_scalars_comma_separated(obj) @@ -577,7 +577,7 @@ class AttributeSchemaPlatform(AttributeSchemaBase): # for 'platform' platforms: t.List[str] @p.validator('platforms', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def list_from_scalars_sub(cls, obj): return list_from_scalars_comma_separated(obj) @@ -607,12 +607,12 @@ class DocSchema(BaseModel): @p.validator('author', 'description', 'extends_documentation_fragment', 'notes', 'requirements', 'todo', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def list_from_scalars(cls, obj): return list_from_scalars(obj) @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def remove_plugin_type(cls, values): """ Remove the plugin_type field from the doc. @@ -627,7 +627,7 @@ def remove_plugin_type(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def merge_plugin_names(cls, values): """ Normalize the field which plugin names are in. @@ -658,7 +658,7 @@ def merge_plugin_names(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def merge_typo_names(cls, values): cb_type = values.get('callback_type', _SENTINEL) diff --git a/src/antsibull_docs/schemas/docs/module.py b/src/antsibull_docs/schemas/docs/module.py index 926a0e3c..afb0df3b 100644 --- a/src/antsibull_docs/schemas/docs/module.py +++ b/src/antsibull_docs/schemas/docs/module.py @@ -17,7 +17,7 @@ class InnerModuleOptionsSchema(OptionsSchema): suboptions: t.Dict[str, 'InnerModuleOptionsSchema'] = {} @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def allow_description_to_be_optional(cls, values): # Doing this in a validator so that the json-schema will still flag it as an error if 'description' not in values: diff --git a/src/antsibull_docs/schemas/docs/plugin.py b/src/antsibull_docs/schemas/docs/plugin.py index ed0028b3..e681bff7 100644 --- a/src/antsibull_docs/schemas/docs/plugin.py +++ b/src/antsibull_docs/schemas/docs/plugin.py @@ -41,7 +41,7 @@ class OptionCliSchema(BaseModel): version_added_collection: str = COLLECTION_NAME_F @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def add_option(cls, values): """ Add option if not present @@ -96,24 +96,24 @@ class ReturnSchema(BaseModel): version_added_collection: str = COLLECTION_NAME_F @p.validator('description', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def list_from_scalars(cls, obj): return list_from_scalars(obj) @p.validator('sample', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def is_json_value(cls, obj): if not is_json_value(obj): raise ValueError('`sample` must be a JSON value') return obj @p.validator('type', 'elements', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def normalize_types(cls, obj): return normalize_return_type_names(obj) @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def remove_example(cls, values): """ Remove example in favor of sample. @@ -136,7 +136,7 @@ def remove_example(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def normalize_sample(cls, values): try: normalize_value(values, 'sample') @@ -145,7 +145,7 @@ def normalize_sample(cls, values): return values @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def normalize_choices(cls, values): if isinstance(values.get('choices'), dict): for k, v in values['choices'].items(): @@ -161,7 +161,7 @@ class InnerReturnSchema(ReturnSchema): contains: t.Dict[str, 'InnerReturnSchema'] = {} @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def allow_description_to_be_optional(cls, values): # Doing this in a validator so that the json-schema will still flag it as an error if 'description' not in values: @@ -203,7 +203,7 @@ class PluginExamplesSchema(BaseModel): examples: str = '' @p.validator('examples', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def normalize_examples(cls, value): if value is None: value = '' @@ -222,7 +222,7 @@ class Config(LocalConfig): return_: t.Dict[str, OuterReturnSchema] = {} @p.validator('return_', pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def transform_return(cls, obj): return transform_return_docs(obj) diff --git a/src/antsibull_docs/schemas/docs/positional.py b/src/antsibull_docs/schemas/docs/positional.py index fc131bc5..f3032e37 100644 --- a/src/antsibull_docs/schemas/docs/positional.py +++ b/src/antsibull_docs/schemas/docs/positional.py @@ -22,7 +22,7 @@ class InnerPositionalDocSchema(InnerDocSchema): positional: t.List[str] = [] @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def add_default_positional(cls, values): """ Remove example in favor of sample. diff --git a/src/antsibull_docs/schemas/docs/role.py b/src/antsibull_docs/schemas/docs/role.py index 94fd14cb..12f7fe87 100644 --- a/src/antsibull_docs/schemas/docs/role.py +++ b/src/antsibull_docs/schemas/docs/role.py @@ -32,7 +32,7 @@ class InnerRoleOptionsSchema(OptionsSchema): options: t.Dict[str, 'InnerRoleOptionsSchema'] = {} @p.root_validator(pre=True) - # pylint:disable=no-self-argument,no-self-use + # pylint:disable=no-self-argument def allow_description_to_be_optional(cls, values): # Doing this in a validator so that the json-schema will still flag it as an error if 'description' not in values: diff --git a/src/antsibull_docs/utils/collection_name_transformer.py b/src/antsibull_docs/utils/collection_name_transformer.py index 0a940386..5972f5d7 100644 --- a/src/antsibull_docs/utils/collection_name_transformer.py +++ b/src/antsibull_docs/utils/collection_name_transformer.py @@ -36,7 +36,7 @@ def __call__(self, collection_name: str) -> str: """Transform the given collection name.""" parts = collection_name.split('.', 1) if len(parts) < 2: - raise Exception( + raise RuntimeError( f'Collection name must have at least one period; {collection_name!r} has not') namespace, name = parts if collection_name in self._data: diff --git a/src/antsibull_docs/write_docs/__init__.py b/src/antsibull_docs/write_docs/__init__.py index ad48f382..75e8848a 100644 --- a/src/antsibull_docs/write_docs/__init__.py +++ b/src/antsibull_docs/write_docs/__init__.py @@ -29,4 +29,4 @@ def _render_template(_template: Template, _name: str, **kwargs) -> str: try: return _template.render(**kwargs) except Exception as exc: - raise Exception(f"Error while rendering {_name}") from exc + raise RuntimeError(f"Error while rendering {_name}") from exc diff --git a/src/sphinx_antsibull_ext/__init__.py b/src/sphinx_antsibull_ext/__init__.py index db5f6479..32492299 100644 --- a/src/sphinx_antsibull_ext/__init__.py +++ b/src/sphinx_antsibull_ext/__init__.py @@ -29,8 +29,8 @@ def setup(app): # Add roles setup_roles(app) - return dict( - parallel_read_safe=True, - parallel_write_safe=True, - version=__version__, - ) + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + 'version': __version__, + } diff --git a/src/sphinx_antsibull_ext/assets.py b/src/sphinx_antsibull_ext/assets.py index e09b1fbc..6c2a90a0 100644 --- a/src/sphinx_antsibull_ext/assets.py +++ b/src/sphinx_antsibull_ext/assets.py @@ -25,7 +25,8 @@ def _copy_asset_files(app, exc): # pylint: disable=unused-argument for file in CSS_FILES: data = pkgutil.get_data('sphinx_antsibull_ext', file) if data is None: - raise Exception(f'Internal error: cannot find {file} in sphinx_antsibull_ext package') + raise RuntimeError( + f'Internal error: cannot find {file} in sphinx_antsibull_ext package') ensuredir(os.path.join(app.outdir, '_static')) destination = os.path.join(app.outdir, '_static', file) with open(destination, 'wb') as f: diff --git a/test-pytest.sh b/test-pytest.sh deleted file mode 100755 index c364300a..00000000 --- a/test-pytest.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -set -e -PYTHONPATH=src poetry run python -W 'ignore:"@coroutine" decorator is deprecated::asynctest.case' \ - -m pytest --cov-branch --cov=antsibull_docs --cov=sphinx_antsibull_ext --cov-report term-missing -vv tests "$@" diff --git a/.github/workflows/validate-html.py b/tests/validate-html.py similarity index 100% rename from .github/workflows/validate-html.py rename to tests/validate-html.py