From 9b0200b1904c872303693c17081869f773890dbb Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 22:31:10 +0200 Subject: [PATCH 01/11] Upgrade to repo-config v0.7.1 The summary of changes is: - ci: Add extra debug information. - ci: Add meta-jobs (`nox-all` and `nox-cross-arch-all`) to be able to require them in branch protection rules so we don't have to update the protection rules each time we add or remove a job from the matrix. - ci: Add support for cross-arch testing, including testing in arm64. - ci: Ignore dependabot pushes (they are tested via the PR). - ci: Run `nox` sessions concurrently to speed up tests. - ci: Test installation of the package in different platforms. - dependabot: Update dependencies in groups, so we get only one PR to update multiple dependencies. - dependabot: Update dependencies monthly instead of daily. - docs: Add a few handy `mkdocs`/`markdown` extensions. - docs: Add `mkdocs-macros-plugin` to be able to use macros in the documentation. - docs: Fix formatting of mermaid diagrams. - docs: Move some support files for the documentation website to names starting with `_` to make it more clear they are only support files. - docs: Replace the `mkdocs-section-index` plugin (which has caused problems in the past) with the `mkdocs-material` built-in `navigation.indexes` feature. - docs: Show inherited class members in the documentation. - docs: Use a custom style to show code annotation numbers for better ordering. - docs: Use the new documentation website versioning scheme. This means now multiple development branches are exposed, as well as pre-releases. Also the order of the versions is improved. The `next` version is replaced by the multiple `vX.Y-dev` versions. - nox: Add a `flake8` session, mainly needed to run `pydocstyle` but also use it to run `pycodestyle` and `flake8` regular checks. `flake8` is much faster than `pylint`, so when there are conflicting checks we prefer `flake8`. - nox: Replace `darglint` with `pydoclint`. - Remove unnecessary fields from the cookiecutter replay file. - Unify some `pyproject.toml` options and move other tool options (like `mypy`) to this file. - Add link to documentation for PyPI. Signed-off-by: Leandro Lucarella --- .cookiecutter-replay.json | 3 +- .../arm64-ubuntu-20.04-python-3.11.Dockerfile | 33 +++ .../containers/nox-cross-arch/entrypoint.bash | 9 + .../containers/test-installation/Dockerfile | 12 + .github/dependabot.yml | 25 +- .github/workflows/ci.yaml | 268 ++++++++++++++---- CONTRIBUTING.md | 27 ++ MANIFEST.in | 1 - README.md | 8 + docs/{css => _css}/mkdocstrings.css | 0 docs/_css/style.css | 70 +++++ docs/{ => _img}/logo.png | Bin docs/{overrides => _overrides}/main.html | 0 docs/_scripts/macros.py | 83 ++++++ docs/{ => _scripts}/mkdocstrings_autoapi.py | 4 +- docs/css/style.css | 28 -- mkdocs.yml | 33 ++- pyproject.toml | 65 ++++- src/{ => frequenz/channels}/conftest.py | 0 19 files changed, 562 insertions(+), 107 deletions(-) create mode 100644 .github/containers/nox-cross-arch/arm64-ubuntu-20.04-python-3.11.Dockerfile create mode 100755 .github/containers/nox-cross-arch/entrypoint.bash create mode 100644 .github/containers/test-installation/Dockerfile rename docs/{css => _css}/mkdocstrings.css (100%) create mode 100644 docs/_css/style.css rename docs/{ => _img}/logo.png (100%) rename docs/{overrides => _overrides}/main.html (100%) create mode 100644 docs/_scripts/macros.py rename docs/{ => _scripts}/mkdocstrings_autoapi.py (51%) delete mode 100644 docs/css/style.css rename src/{ => frequenz/channels}/conftest.py (100%) diff --git a/.cookiecutter-replay.json b/.cookiecutter-replay.json index c7810be6..00edfe87 100644 --- a/.cookiecutter-replay.json +++ b/.cookiecutter-replay.json @@ -13,7 +13,6 @@ "python_package": "frequenz.channels", "pypi_package_name": "frequenz-channels", "github_repo_name": "frequenz-channels-python", - "default_codeowners": "@frequenz-floss/python-sdk-team", - "_template": "gh:frequenz-floss/frequenz-repo-config-python" + "default_codeowners": "@frequenz-floss/python-sdk-team" } } diff --git a/.github/containers/nox-cross-arch/arm64-ubuntu-20.04-python-3.11.Dockerfile b/.github/containers/nox-cross-arch/arm64-ubuntu-20.04-python-3.11.Dockerfile new file mode 100644 index 00000000..9d688211 --- /dev/null +++ b/.github/containers/nox-cross-arch/arm64-ubuntu-20.04-python-3.11.Dockerfile @@ -0,0 +1,33 @@ +# License: MIT +# Copyright © 2023 Frequenz Energy-as-a-Service GmbH +# This Dockerfile is used to run the tests in architectures not supported by +# GitHub Actions. + +FROM docker.io/library/ubuntu:20.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Install Python 3.11 and curl to install pip later +RUN apt-get update -y && \ + apt-get install --no-install-recommends -y \ + software-properties-common && \ + add-apt-repository ppa:deadsnakes/ppa && \ + apt-get install --no-install-recommends -y \ + ca-certificates \ + curl \ + git \ + python3.11 \ + python3.11-distutils && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install pip +RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python3.11 + +RUN update-alternatives --install \ + /usr/local/bin/python python /usr/bin/python3.11 1 && \ + python -m pip install --upgrade --no-cache-dir pip + +COPY entrypoint.bash /usr/bin/entrypoint.bash + +ENTRYPOINT ["/usr/bin/entrypoint.bash"] diff --git a/.github/containers/nox-cross-arch/entrypoint.bash b/.github/containers/nox-cross-arch/entrypoint.bash new file mode 100755 index 00000000..6980c507 --- /dev/null +++ b/.github/containers/nox-cross-arch/entrypoint.bash @@ -0,0 +1,9 @@ +#!/bin/bash +# License: MIT +# Copyright © 2023 Frequenz Energy-as-a-Service GmbH +set -e + +echo "System details:" $(uname -a) +echo "Machine:" $(uname -m) + +exec "$@" diff --git a/.github/containers/test-installation/Dockerfile b/.github/containers/test-installation/Dockerfile new file mode 100644 index 00000000..bc71d610 --- /dev/null +++ b/.github/containers/test-installation/Dockerfile @@ -0,0 +1,12 @@ +# License: MIT +# Copyright © 2023 Frequenz Energy-as-a-Service GmbH +# This Dockerfile is used to test the installation of the python package in +# multiple platforms in the CI. It is not used to build the package itself. + +FROM --platform=${TARGETPLATFORM} python:3.11-slim + +RUN python -m pip install --upgrade --no-cache-dir pip + +COPY dist dist +RUN pip install dist/*.whl && \ + rm -rf dist diff --git a/.github/dependabot.yml b/.github/dependabot.yml index db6cfbd3..30772682 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,8 +3,8 @@ updates: - package-ecosystem: "pip" directory: "/" schedule: - interval: "daily" - time: "07:00" + interval: "monthly" + day: "thursday" labels: - "part:tooling" - "type:tech-debt" @@ -13,12 +13,29 @@ updates: versioning-strategy: auto # Allow up to 10 open pull requests for updates to dependency versions open-pull-requests-limit: 10 + # We group production and development ("optional" in the context of + # pyproject.toml) dependency updates when they are patch and minor updates, + # so we end up with less PRs being generated. + # Major updates are still managed, but they'll create one PR per + # dependency, as major updates are expected to be breaking, it is better to + # manage them individually. + groups: + required: + dependency-type: "production" + update-types: + - "minor" + - "patch" + optional: + dependency-type: "development" + update-types: + - "minor" + - "patch" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" - time: "06:00" + interval: "monthly" + day: "thursday" labels: - "part:tooling" - "type:tech-debt" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f15a93d2..86f7b957 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,6 +13,7 @@ on: # We only want to test the merge commit (`merge_group` event), the hashes # in the push were already tested by the PR checks - 'gh-readonly-queue/**' + - 'dependabot/**' workflow_dispatch: env: @@ -32,9 +33,17 @@ jobs: - ubuntu-20.04 python: - "3.11" + nox-session: + # To speed things up a bit we use the special ci_checks_max session + # that uses the same venv to run multiple linting sessions + - "ci_checks_max" + - "pytest_min" runs-on: ${{ matrix.os }} steps: + - name: Print environment (debug) + run: env + - name: Fetch sources uses: actions/checkout@v4 with: @@ -50,13 +59,152 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -e .[dev-noxfile] + pip freeze + + - name: Create nox venv + env: + NOX_SESSION: ${{ matrix.nox-session }} + run: nox --install-only -e "$NOX_SESSION" + + - name: Print pip freeze for nox venv (debug) + env: + NOX_SESSION: ${{ matrix.nox-session }} + run: | + . ".nox/$NOX_SESSION/bin/activate" + pip freeze + deactivate - name: Run nox - # To speed things up a bit we use the special ci_checks_max session - # that uses the same venv to run multiple linting sessions - run: nox -e ci_checks_max pytest_min + env: + NOX_SESSION: ${{ matrix.nox-session }} + run: nox -R -e "$NOX_SESSION" timeout-minutes: 10 + # This job runs if all the `nox` matrix jobs ran and succeeded. + # It is only used to have a single job that we can require in branch + # protection rules, so we don't have to update the protection rules each time + # we add or remove a job from the matrix. + nox-all: + # The job name should match the name of the `nox` job. + name: Test with nox + needs: ["nox"] + runs-on: ubuntu-20.04 + steps: + - name: Return true + run: "true" + + nox-cross-arch: + name: Cross-arch tests with nox + if: github.event_name != 'pull_request' + strategy: + fail-fast: false + # Before adding new items to this matrix, make sure that a dockerfile + # exists for the combination of items in the matrix. + # Refer to .github/containers/nox-cross-arch/README.md to learn how to + # add and name new dockerfiles. + matrix: + arch: + - arm64 + os: + - ubuntu-20.04 + python: + - "3.11" + nox-session: + - "pytest_min" + - "pytest_max" + runs-on: ${{ matrix.os }} + + steps: + - name: Fetch sources + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/${{ matrix.arch }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # This is a workaround to prevent the cache from growing indefinitely. + # https://docs.docker.com/build/ci/github-actions/cache/#local-cache + # https://github.com/docker/build-push-action/issues/252 + # https://github.com/moby/buildkit/issues/1896 + - name: Cache container layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-nox-${{ matrix.arch }}-${{ matrix.os }}-${{ matrix.python }} + + - name: Build image + uses: docker/build-push-action@v5 + with: + context: .github/containers/nox-cross-arch + file: .github/containers/nox-cross-arch/${{ matrix.arch }}-${{ matrix.os }}-python-${{ matrix.python }}.Dockerfile + platforms: linux/${{ matrix.arch }} + tags: localhost/nox-cross-arch:latest + push: false + load: true + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + + # Refer to the workaround mentioned above + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + + # Cache pip downloads + - name: Cache pip downloads + uses: actions/cache@v3 + with: + path: /tmp/pip-cache + key: nox-${{ matrix.nox-session }}-${{ matrix.arch }}-${{ matrix.os }}-${{ matrix.python }}-${{ hashFiles('**/pyproject.toml') }} + + # This ensures that the docker container has access to the pip cache. + # Changing the user in the docker-run step causes it to fail due to + # incorrect permissions. Setting the ownership of the pip cache to root + # before running is a workaround to this issue. + - name: Set pip cache owners to root for docker + run: if [[ -e /tmp/pip-cache ]]; then sudo chown -R root:root /tmp/pip-cache; fi + + - name: Run nox + run: | + docker run \ + --rm \ + -v $(pwd):/${{ github.workspace }} \ + -v /tmp/pip-cache:/root/.cache/pip \ + -w ${{ github.workspace }} \ + --net=host \ + --platform linux/${{ matrix.arch }} \ + localhost/nox-cross-arch:latest \ + bash -c "pip install -e .[dev-noxfile]; nox --install-only -e ${{ matrix.nox-session }}; pip freeze; nox -e ${{ matrix.nox-session }}" + timeout-minutes: 30 + + # This ensures that the runner has access to the pip cache. + - name: Reset pip cache ownership + if: always() + run: sudo chown -R $USER:$USER /tmp/pip-cache + + # This job runs if all the `nox-cross-arch` matrix jobs ran and succeeded. + # As the `nox-all` job, its main purpose is to provide a single point of + # reference in branch protection rules, similar to how `nox-all` operates. + # However, there's a crucial difference: the `nox-cross-arch` job is omitted + # in PRs. Without the `nox-cross-arch-all` job, the inner matrix wouldn't be + # expanded in such scenarios. This would lead to the CI indefinitely waiting + # for these jobs to complete due to the branch protection rules, essentially + # causing it to hang. This behavior is tied to a recognized GitHub matrices + # issue when certain jobs are skipped. For a deeper understanding, refer to: + # https://github.com/orgs/community/discussions/9141 + nox-cross-arch-all: + # The job name should match the name of the `nox-cross-arch` job. + name: Cross-arch tests with nox + needs: ["nox-cross-arch"] + runs-on: ubuntu-20.04 + steps: + - name: Return true + run: "true" + build: name: Build distribution packages runs-on: ubuntu-20.04 @@ -76,6 +224,7 @@ jobs: run: | python -m pip install -U pip python -m pip install -U build + pip freeze - name: Build the source and binary distribution run: python -m build @@ -87,6 +236,31 @@ jobs: path: dist/ if-no-files-found: error + test-installation: + name: Test package installation in different architectures + needs: ["build"] + runs-on: ubuntu-20.04 + steps: + - name: Fetch sources + uses: actions/checkout@v4 + - name: Download package + uses: actions/download-artifact@v3 + with: + name: dist-packages + path: dist + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up docker-buildx + uses: docker/setup-buildx-action@v3 + - name: Test Installation + uses: docker/build-push-action@v5 + with: + context: . + file: .github/containers/test-installation/Dockerfile + platforms: linux/amd64,linux/arm64 + tags: localhost/test-installation + push: false + test-docs: name: Test documentation website generation if: github.event_name != 'push' @@ -110,6 +284,7 @@ jobs: run: | python -m pip install -U pip python -m pip install .[dev-mkdocs] + pip freeze - name: Generate the documentation env: @@ -127,83 +302,74 @@ jobs: publish-docs: name: Publish documentation website to GitHub pages - needs: ["nox", "build"] + needs: ["nox-all", "nox-cross-arch-all", "test-installation"] if: github.event_name == 'push' runs-on: ubuntu-20.04 permissions: contents: write steps: - - name: Calculate and check version - id: mike-metadata - env: - REF: ${{ github.ref }} - REF_NAME: ${{ github.ref_name }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - run: | - aliases= - version= - if test "$REF_NAME" = "$DEFAULT_BRANCH" - then - version=next - # A tag that starts with vX.Y or X.Y - elif echo "$REF" | grep -q '^refs/tags' && echo "$REF_NAME" | grep -Pq '^v?\d+\.\d+\.' - then - if echo "$REF_NAME" | grep -Pq -- "-" # pre-release - then - echo "::notice title=Documentation was not published::" \ - "The tag '$REF_NAME' looks like a pre-release." - exit 0 - fi - version=$(echo "$REF_NAME" | sed -r 's/^(v?[0-9]+\.[0-9]+)\..*$/\1/') # vX.Y - major=$(echo "$REF_NAME" | sed -r 's/^(v?[0-9]+)\..*$/\1/') # vX - default_major=$(echo "$DEFAULT_BRANCH" | sed -r 's/^(v?[0-9]+)\..*$/\1/') # vX - aliases=$major - if test "$major" = "$default_major" - then - aliases="$aliases latest" - fi - else - echo "::warning title=Documentation was not published::" \ - "Don't know how to handle '$REF' to make 'mike' version." - exit 0 - fi - echo "version=$version" >> $GITHUB_OUTPUT - echo "aliases=$aliases" >> $GITHUB_OUTPUT - - name: Fetch sources - if: steps.mike-metadata.outputs.version uses: actions/checkout@v4 with: submodules: true - name: Setup Git user and e-mail - if: steps.mike-metadata.outputs.version uses: frequenz-floss/setup-git-user@v2 - name: Set up Python - if: steps.mike-metadata.outputs.version uses: actions/setup-python@v4 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} cache: 'pip' - name: Install build dependencies - if: steps.mike-metadata.outputs.version run: | python -m pip install -U pip python -m pip install .[dev-mkdocs] + pip freeze + + - name: Calculate and check version + id: mike-version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPO: ${{ github.repository }} + GIT_REF: ${{ github.ref }} + GIT_SHA: ${{ github.sha }} + run: | + python -m frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch - if: steps.mike-metadata.outputs.version + if: steps.mike-version.outputs.version run: git fetch origin gh-pages --depth=1 - - name: Publish site - if: steps.mike-metadata.outputs.version + - name: Build site + if: steps.mike-version.outputs.version env: - VERSION: ${{ steps.mike-metadata.outputs.version }} - ALIASES: ${{ steps.mike-metadata.outputs.aliases }} + VERSION: ${{ steps.mike-version.outputs.version }} + TITLE: ${{ steps.mike-version.outputs.title }} + ALIASES: ${{ steps.mike-version.outputs.aliases }} + # This is not ideal, we need to define all these variables here + # because we need to calculate all the repository version information + # to be able to show the correct versions in the documentation when + # building it. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPO: ${{ github.repository }} + GIT_REF: ${{ github.ref }} + GIT_SHA: ${{ github.sha }} + run: | + mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + + - name: Sort site versions + if: steps.mike-version.outputs.version + run: | + git checkout gh-pages + python -m frequenz.repo.config.cli.version.mkdocs.sort versions.json + git commit -a -m "Sort versions.json" + + - name: Publish site + if: steps.mike-version.outputs.version run: | - mike deploy --push --update-aliases "$VERSION" $ALIASES + git push origin gh-pages create-github-release: name: Create GitHub release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81e98c42..f46d683f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -154,3 +154,30 @@ These are the steps to create a new release: eventually too). 7. Celebrate! + +## Cross-Arch Testing + +This project has built-in support for testing across multiple architectures. +Currently, our CI conducts tests on `arm64` machines using QEMU emulation. We +also have the flexibility to expand this support to include additional +architectures in the future. + +This project containers Dockerfiles that can be used in the CI to test the +python package in non-native machine architectures, e.g., `arm64`. The +Dockerfiles exist in the directory `.github/containers/nox-cross-arch`, and +follow a naming scheme so that they can be easily used in build matrices in the +CI, in `nox-cross-arch` job. The naming scheme is: + +``` +--python-.Dockerfile +``` + +E.g., + +``` +arm64-ubuntu-20.04-python-3.11.Dockerfile +``` + +If a Dockerfile for your desired target architecture, OS, and python version +does not exist here, please add one before proceeding to add your options to +the test matrix. diff --git a/MANIFEST.in b/MANIFEST.in index cbd04173..6b2c7c80 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ exclude .cookiecutter-replay.json -exclude .darglint exclude .editorconfig exclude .gitignore exclude CODEOWNERS diff --git a/README.md b/README.md index 541c12de..7633b2fd 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ Frequenz Channels are mostly designed after [Go channels](https://tour.golang.org/concurrency/2) but it also borrows ideas from [Rust channels](https://doc.rust-lang.org/book/ch16-02-message-passing.html). +## Supported Platforms + +The following platforms are officially supported (tested): + +- **Python:** 3.11 +- **Operating System:** Ubuntu Linux 20.04 +- **Architectures:** amd64, arm64 + ## Quick Start We assume you are on a system with Python available. If that is not the case, diff --git a/docs/css/mkdocstrings.css b/docs/_css/mkdocstrings.css similarity index 100% rename from docs/css/mkdocstrings.css rename to docs/_css/mkdocstrings.css diff --git a/docs/_css/style.css b/docs/_css/style.css new file mode 100644 index 00000000..f253894b --- /dev/null +++ b/docs/_css/style.css @@ -0,0 +1,70 @@ +/* Based on: + * https://github.com/mkdocstrings/mkdocstrings/blob/master/docs/css/style.css + */ + +/* Increase logo size */ +.md-header__button.md-logo { + padding-bottom: 0.2rem; + padding-right: 0; +} +.md-header__button.md-logo img { + height: 1.5rem; +} + +/* Mark external links as such (also in nav) */ +a.external:hover::after, a.md-nav__link[href^="https:"]:hover::after { + /* https://primer.style/octicons/link-external-16 */ + background-image: url('data:image/svg+xml,'); + height: 0.8em; + width: 0.8em; + margin-left: 0.2em; + content: ' '; + display: inline-block; +} + +/* More space at the bottom of the page */ +.md-main__inner { + margin-bottom: 1.5rem; +} + +/* Code annotations with numbers. + * + * Normally annothations are shown with a (+) button that expands the + * annotation. To be able to explain code step by step, it is good to have + * annotations with numbers, to be able to follow the notes in a particular + * order. + * + * To do this, we need some custom CSS rules. Before this customization was + * officially supported and documented, but now they are not officially + * supported anymore, so it could eventually break (it already did once). + * + * If that happens we either need to look into how to fix the CSS ourselves or + * remove the feature. To do the customization, this is what we should be able + * to count on: + * + * "you can be sure that the data-md-annotation-id attribute will always be + * present in the source, which means you can always number them in any way you + * like." + * + * Code annotation are described here: + * https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#code-annotations + * + * Here are the original docs on how to enable numbered annotations: + * https://web.archive.org/web/20230724161216/https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#annotations-with-numbers + * + * This is the PR fixing the numbered annotations when they broke: + * https://github.com/frequenz-floss/frequenz-sdk-python/pull/684 + * + * And this is the reported regression when it was decided to drop support for + * numbered annotations officially: + * https://github.com/squidfunk/mkdocs-material/issues/6042 + */ +.md-typeset .md-annotation__index > ::before { + content: attr(data-md-annotation-id); +} +.md-typeset :focus-within > .md-annotation__index > ::before { + transform: none; +} +.md-typeset .md-annotation__index { + width: 4ch; +} diff --git a/docs/logo.png b/docs/_img/logo.png similarity index 100% rename from docs/logo.png rename to docs/_img/logo.png diff --git a/docs/overrides/main.html b/docs/_overrides/main.html similarity index 100% rename from docs/overrides/main.html rename to docs/_overrides/main.html diff --git a/docs/_scripts/macros.py b/docs/_scripts/macros.py new file mode 100644 index 00000000..ff87c718 --- /dev/null +++ b/docs/_scripts/macros.py @@ -0,0 +1,83 @@ +# License: MIT +# Copyright © 2023 Frequenz Energy-as-a-Service GmbH + +"""This module defines macros for use in Markdown files.""" + +from typing import Any + +import markdown as md +from markdown.extensions import toc +from mkdocs_macros import plugin as macros + +_CODE_ANNOTATION_MARKER: str = ( + r'' + r'' + r'' + r"" + r"" +) + + +def _slugify(text: str) -> str: + """Slugify a text. + + Args: + text: The text to slugify. + + Returns: + The slugified text. + """ + # The type of the return value is not defined for the markdown library. + # Also for some reason `mypy` thinks the `toc` module doesn't have a + # `slugify_unicode` function, but it definitely does. + return toc.slugify_unicode(text, "-") # type: ignore[attr-defined,no-any-return] + + +def _hook_macros_plugin(env: macros.MacrosPlugin) -> None: + """Integrate the `mkdocs-macros` plugin into `mkdocstrings`. + + This is a temporary workaround to make `mkdocs-macros` work with + `mkdocstrings` until a proper `mkdocs-macros` *pluglet* is available. See + https://github.com/mkdocstrings/mkdocstrings/issues/615 for details. + + Args: + env: The environment to hook the plugin into. + """ + # get mkdocstrings' Python handler + python_handler = env.conf["plugins"]["mkdocstrings"].get_handler("python") + + # get the `update_env` method of the Python handler + update_env = python_handler.update_env + + # override the `update_env` method of the Python handler + def patched_update_env(markdown: md.Markdown, config: dict[str, Any]) -> None: + update_env(markdown, config) + + # get the `convert_markdown` filter of the env + convert_markdown = python_handler.env.filters["convert_markdown"] + + # build a chimera made of macros+mkdocstrings + def render_convert(markdown: str, *args: Any, **kwargs: Any) -> Any: + return convert_markdown(env.render(markdown), *args, **kwargs) + + # patch the filter + python_handler.env.filters["convert_markdown"] = render_convert + + # patch the method + python_handler.update_env = patched_update_env + + +def define_env(env: macros.MacrosPlugin) -> None: + """Define the hook to create macro functions for use in Markdown. + + Args: + env: The environment to define the macro functions in. + """ + # A variable to easily show an example code annotation from mkdocs-material. + # https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#adding-annotations + env.variables["code_annotation_marker"] = _CODE_ANNOTATION_MARKER + + # TODO(cookiecutter): Add any other macros, variables and filters here. + + # This hook needs to be done at the end of the `define_env` function. + _hook_macros_plugin(env) diff --git a/docs/mkdocstrings_autoapi.py b/docs/_scripts/mkdocstrings_autoapi.py similarity index 51% rename from docs/mkdocstrings_autoapi.py rename to docs/_scripts/mkdocstrings_autoapi.py index b5cd911c..d5a641c8 100644 --- a/docs/mkdocstrings_autoapi.py +++ b/docs/_scripts/mkdocstrings_autoapi.py @@ -3,6 +3,6 @@ """Generate the code reference pages.""" -from frequenz.repo.config import mkdocs +from frequenz.repo.config.mkdocs import api_pages -mkdocs.generate_python_api_pages("src", "reference") +api_pages.generate_python_api_pages("src", "reference") diff --git a/docs/css/style.css b/docs/css/style.css deleted file mode 100644 index bbe472c8..00000000 --- a/docs/css/style.css +++ /dev/null @@ -1,28 +0,0 @@ -/* Based on: - * https://github.com/mkdocstrings/mkdocstrings/blob/master/docs/css/style.css - */ - -/* Increase logo size */ -.md-header__button.md-logo { - padding-bottom: 0.2rem; - padding-right: 0; -} -.md-header__button.md-logo img { - height: 1.5rem; -} - -/* Mark external links as such (also in nav) */ -a.external:hover::after, a.md-nav__link[href^="https:"]:hover::after { - /* https://primer.style/octicons/link-external-16 */ - background-image: url('data:image/svg+xml,'); - height: 0.8em; - width: 0.8em; - margin-left: 0.2em; - content: ' '; - display: inline-block; -} - -/* More space at the bottom of the page */ -.md-main__inner { - margin-bottom: 1.5rem; -} diff --git a/mkdocs.yml b/mkdocs.yml index 98ef0561..3d0f82e3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,17 +14,19 @@ strict: true # Treat warnings as errors # Build directories theme: name: "material" - logo: logo.png - favicon: logo.png + logo: _img/logo.png + favicon: _img/logo.png language: en icon: edit: material/file-edit-outline repo: fontawesome/brands/github - custom_dir: docs/overrides + custom_dir: docs/_overrides features: - content.code.annotate - content.code.copy + - navigation.indexes - navigation.instant + - navigation.footer - navigation.tabs - navigation.top - navigation.tracking @@ -56,13 +58,15 @@ extra: default: latest extra_css: - - css/style.css - - css/mkdocstrings.css + - _css/style.css + - _css/mkdocstrings.css # Formatting options markdown_extensions: - admonition - attr_list + - def_list + - footnotes - pymdownx.details - pymdownx.highlight: anchor_linenums: true @@ -75,16 +79,17 @@ markdown_extensions: custom_fences: - name: mermaid class: mermaid - format: "!!python/name:pymdownx.superfences.fence_code_format" + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed - - pymdownx.tasklist + - pymdownx.tasklist: + custom_checkbox: true - toc: permalink: "¤" plugins: - gen-files: scripts: - - docs/mkdocstrings_autoapi.py + - docs/_scripts/mkdocstrings_autoapi.py - literate-nav: nav_file: SUMMARY.md - mike: @@ -97,17 +102,27 @@ plugins: options: paths: ["src"] docstring_section_style: spacy + inherited_members: true merge_init_into_class: false + separate_signature: true show_category_heading: true show_root_heading: true show_root_members_full_path: true + show_signature_annotations: true show_source: true + signature_crossrefs: true import: # See https://mkdocstrings.github.io/python/usage/#import for details - https://docs.python.org/3/objects.inv - https://typing-extensions.readthedocs.io/en/stable/objects.inv + # Note this plugin must be loaded after mkdocstrings to be able to use macros + # inside docstrings. See the comment in `docs/_scripts/macros.py` for more + # details + - macros: + module_name: docs/_scripts/macros + on_undefined: strict + on_error_fail: true - search - - section-index # Preview controls watch: diff --git a/pyproject.toml b/pyproject.toml index 68ea8c06..7d99b96a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ requires = [ "setuptools == 68.1.0", "setuptools_scm[toml] == 7.1.0", - "frequenz-repo-config[lib] == 0.5.2", + "frequenz-repo-config[lib] == 0.7.1", ] build-backend = "setuptools.build_meta" @@ -36,27 +36,32 @@ name = "Frequenz Energy-as-a-Service GmbH" email = "floss@frequenz.com" [project.optional-dependencies] -dev-docstrings = [ +dev-flake8 = [ + "flake8 == 6.1.0", + "flake8-docstrings == 1.7.0", + "flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml + "pydoclint == 0.3.2", "pydocstyle == 6.3.0", - "darglint == 1.8.1", - "tomli == 2.0.1", # Needed by pydocstyle to read pyproject.toml ] dev-formatting = ["black == 23.10.1", "isort == 5.12.0"] dev-mkdocs = [ + "black == 23.10.1", + "Markdown==3.4.4", "mike == 1.1.2", "mkdocs-gen-files == 0.5.0", "mkdocs-literate-nav == 0.6.1", "mkdocs-material == 9.3.2", - "mkdocs-section-index == 0.3.8", + "mkdocs-macros-plugin == 1.0.4", "mkdocstrings[python] == 0.23.0", - "frequenz-repo-config[lib] == 0.5.2", + "frequenz-repo-config[lib] == 0.7.1", ] dev-mypy = [ "mypy == 1.6.1", + "types-Markdown == 3.4.2.10", # For checking the noxfile, docs/ script, and tests "frequenz-channels[dev-mkdocs,dev-noxfile,dev-pytest]", ] -dev-noxfile = ["nox == 2023.4.22", "frequenz-repo-config[lib] == 0.5.2"] +dev-noxfile = ["nox == 2023.4.22", "frequenz-repo-config[lib] == 0.7.1"] dev-pylint = [ "pylint == 2.17.7", # For checking the noxfile, docs/ script, and tests @@ -66,15 +71,16 @@ dev-pytest = [ "pytest == 7.4.3", "async-solipsism == 0.5", "hypothesis == 6.88.1", + "frequenz-repo-config[extra-lint-examples] == 0.7.1", "pytest-asyncio == 0.21.1", - "frequenz-repo-config[extra-lint-examples] == 0.5.2", "pytest-mock == 3.12.0", ] dev = [ - "frequenz-channels[dev-mkdocs,dev-docstrings,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]", + "frequenz-channels[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]", ] [project.urls] +Documentation = "https://frequenz-floss.github.io/frequenz-channels-python/" Changelog = "https://github.com/frequenz-floss/frequenz-channels-python/releases" Issues = "https://github.com/frequenz-floss/frequenz-channels-python/issues" Repository = "https://github.com/frequenz-floss/frequenz-channels-python" @@ -90,6 +96,23 @@ profile = "black" line_length = 88 src_paths = ["benchmarks", "examples", "src", "tests"] +[tool.flake8] +# We give some flexibility to go over 88, there are cases like long URLs or +# code in documenation that have extra indentation. Black will still take care +# of making everything that can be 88 wide, 88 wide. +max-line-length = 100 +extend-ignore = [ + "E203", # Whitespace before ':' (conflicts with black) + "W503", # Line break before binary operator (conflicts with black) +] +# pydoclint options +style = "google" +check-return-types = false +check-yield-types = false +arg-type-hints-in-docstring = false +arg-type-hints-in-signature = true +allow-init-docstring = true + [tool.pylint.similarities] ignore-comments = ['yes'] ignore-docstrings = ['yes'] @@ -105,6 +128,10 @@ disable = [ # pylint's unsubscriptable check is buggy and is not needed because # it is a type-check, for which we already have mypy. "unsubscriptable-object", + # Checked by flake8 + "line-too-long", + "unused-variable", + "unnecessary-lambda-assignment", ] [tool.pytest.ini_options] @@ -115,8 +142,26 @@ markers = [ "integration: integration tests (deselect with '-m \"not integration\"')", ] +[tool.mypy] +explicit_package_bases = true +namespace_packages = true +# This option disables mypy cache, and it is sometimes useful to enable it if +# you are getting weird intermittent error, or error in the CI but not locally +# (or vice versa). In particular errors saying that type: ignore is not +# used but getting the original ignored error when removing the type: ignore. +# See for example: https://github.com/python/mypy/issues/2960 +#no_incremental = true +packages = ["frequenz.channels"] +strict = true + [[tool.mypy.overrides]] -module = ["async_solipsism", "async_solipsism.*", "sybil", "sybil.*"] +module = [ + "async_solipsism", + "async_solipsism.*", + "mkdocs_macros.*", + "sybil", + "sybil.*", +] ignore_missing_imports = true [tool.setuptools_scm] diff --git a/src/conftest.py b/src/frequenz/channels/conftest.py similarity index 100% rename from src/conftest.py rename to src/frequenz/channels/conftest.py From 87b3e4c873e412256ac29c57a94c4620d0baaa6c Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 23:10:17 +0200 Subject: [PATCH 02/11] Remove types from the docs We don't need them as it is redundant and `mkdocstrings` can extract the type information from the code. This is necessary because now we are checking for docs style in tests and benchmarks too. Signed-off-by: Leandro Lucarella --- benchmarks/benchmark_anycast.py | 18 +++++++++--------- benchmarks/benchmark_broadcast.py | 30 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/benchmark_anycast.py b/benchmarks/benchmark_anycast.py index 2495b4a6..0e60aba9 100644 --- a/benchmarks/benchmark_anycast.py +++ b/benchmarks/benchmark_anycast.py @@ -16,8 +16,8 @@ async def send_msg(num_messages: int, chan: Sender[int]) -> None: """Send messages to the channel continuously. Args: - num_messages (int): Number of messages to send. - chan (Sender[int]): Channel sender to send the messages to. + num_messages: Number of messages to send. + chan: Channel sender to send the messages to. """ # send one message for each receiver for ctr in range(num_messages): @@ -33,13 +33,13 @@ async def benchmark_anycast( """Ensure sent messages are received by one receiver. Args: - num_channels (int): Number of channels to create. - num_messages (int): Number of messages to send per channel. - num_receivers (int): Number of broadcast receivers per channel. - buffer_size (int): Buffer size of each channel. + num_channels: Number of channels to create. + num_messages: Number of messages to send per channel. + num_receivers: Number of broadcast receivers per channel. + buffer_size: Buffer size of each channel. Returns: - int: Total number of messages received by all channels. + Total number of messages received by all channels. """ channels: list[Anycast[int]] = [Anycast(buffer_size) for _ in range(num_channels)] senders = [ @@ -73,10 +73,10 @@ def time_async_task(task: Coroutine[Any, Any, int]) -> tuple[float, Any]: """Run a task and return the time taken and the result. Args: - task (asyncio.Task): Task to run. + task: Task to run. Returns: - (float, Any): Run time in fractional seconds, task return value. + Run time in fractional seconds, task return value. """ start = timeit.default_timer() ret = asyncio.run(task) diff --git a/benchmarks/benchmark_broadcast.py b/benchmarks/benchmark_broadcast.py index 5b1d83b4..dcc49b99 100644 --- a/benchmarks/benchmark_broadcast.py +++ b/benchmarks/benchmark_broadcast.py @@ -17,8 +17,8 @@ async def component_sender(num_messages: int, chan: Sender[int]) -> None: """Send a message to the channel every 0.2 seconds. Args: - num_messages (int): Number of messages to send. - chan (Sender[int]): Channel sender to send the messages to. + num_messages: Number of messages to send. + chan: Channel sender to send the messages to. """ for ctr in range(num_messages): await chan.send(ctr) @@ -29,8 +29,8 @@ async def fast_sender(num_messages: int, chan: Sender[int]) -> None: """Send messages to the channel continuously. Args: - num_messages (int): Number of messages to send. - chan (Sender[int]): Channel sender to send messages to. + num_messages: Number of messages to send. + chan: Channel sender to send messages to. """ for ctr in range(num_messages): await chan.send(ctr) @@ -52,14 +52,14 @@ async def benchmark_broadcast( """Benchmark with senders and receivers running in separate tasks. Args: - send_msg (Callable): Method to use as sender (component_sender or + send_msg: Method to use as sender (component_sender or fast_sender). - num_channels (int): Number of channels to create. - num_messages (int): Number of messages to send per channel. - num_receivers (int): Number of broadcast receivers per channel. + num_channels: Number of channels to create. + num_messages: Number of messages to send per channel. + num_receivers: Number of broadcast receivers per channel. Returns: - int: Total number of messages received by all receivers. + Total number of messages received by all receivers. """ channels: list[Broadcast[int]] = [Broadcast("meter") for _ in range(num_channels)] senders: list[asyncio.Task[Any]] = [ @@ -97,12 +97,12 @@ async def benchmark_single_task_broadcast( """Benchmark with senders and receivers invoked from the same task. Args: - num_channels (int): number of channels to create. - num_messages (int): number of messages to send per channel. - num_receivers (int): number of broadcast receivers per channel. + num_channels: number of channels to create. + num_messages: number of messages to send per channel. + num_receivers: number of broadcast receivers per channel. Returns: - int: Total number of messages received by all receivers. + Total number of messages received by all receivers. """ channels: list[Broadcast[int]] = [Broadcast("meter") for _ in range(num_channels)] senders = [b.new_sender() for b in channels] @@ -127,10 +127,10 @@ def time_async_task(task: Coroutine[Any, Any, int]) -> tuple[float, Any]: """Run a task and return the time taken and the result. Args: - task (asyncio.Task): Task to run. + task: Task to run. Returns: - (float, Any): Run time in fractional seconds, task return value. + Run time in fractional seconds, task return value. """ start = timeit.default_timer() ret = asyncio.run(task) From fe0637241bbdd8296a4403e62e3658e6165986c1 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 23:11:20 +0200 Subject: [PATCH 03/11] Remove empty lines between docstrings and code This is necessary because now we are checking for docs style in tests and benchmarks too. Signed-off-by: Leandro Lucarella --- tests/test_anycast.py | 1 - tests/test_bidirectional.py | 3 --- tests/test_broadcast.py | 1 - 3 files changed, 5 deletions(-) diff --git a/tests/test_anycast.py b/tests/test_anycast.py index 9737a6ff..b5d1d7fd 100644 --- a/tests/test_anycast.py +++ b/tests/test_anycast.py @@ -20,7 +20,6 @@ async def test_anycast() -> None: """Ensure sent messages are received by one receiver.""" - acast: Anycast[int] = Anycast() num_receivers = 5 diff --git a/tests/test_bidirectional.py b/tests/test_bidirectional.py index 7a0dea6e..0d954263 100644 --- a/tests/test_bidirectional.py +++ b/tests/test_bidirectional.py @@ -18,7 +18,6 @@ async def test_request_response() -> None: """Ensure bi-directional communication is possible.""" - req_resp: Bidirectional[int, str] = Bidirectional("test_client", "test_service") async def service(handle: Bidirectional.Handle[str, int]) -> None: @@ -53,7 +52,6 @@ async def service(handle: Bidirectional.Handle[str, int]) -> None: async def test_sender_error_chaining() -> None: """Ensure bi-directional communication is possible.""" - req_resp: Bidirectional[int, str] = Bidirectional("test_client", "test_service") await req_resp._response_channel.close() # pylint: disable=protected-access @@ -70,7 +68,6 @@ async def test_sender_error_chaining() -> None: async def test_consume_error_chaining() -> None: """Ensure bi-directional communication is possible.""" - req_resp: Bidirectional[int, str] = Bidirectional("test_client", "test_service") await req_resp._request_channel.close() # pylint: disable=protected-access diff --git a/tests/test_broadcast.py b/tests/test_broadcast.py index 3b8c68b9..1963ea39 100644 --- a/tests/test_broadcast.py +++ b/tests/test_broadcast.py @@ -21,7 +21,6 @@ async def test_broadcast() -> None: """Ensure sent messages are received by all receivers.""" - bcast: Broadcast[int] = Broadcast("meter_5") num_receivers = 5 From e3f15e8400aac26f7c1bf62f8b6cc18e9e58f271 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 23:11:53 +0200 Subject: [PATCH 04/11] Remove double comment char (#) This is necessary because now we are checking for docs style in tests and benchmarks too. Signed-off-by: Leandro Lucarella --- tests/test_anycast.py | 2 +- tests/test_broadcast.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_anycast.py b/tests/test_anycast.py index b5d1d7fd..b571aead 100644 --- a/tests/test_anycast.py +++ b/tests/test_anycast.py @@ -77,7 +77,7 @@ async def update_tracker_on_receive(receiver_id: int, recv: Receiver[int]) -> No actual_sum = 0 for ctr in recv_trackers: - ## ensure all receivers have got messages + # ensure all receivers have got messages assert ctr > 0 actual_sum += ctr assert actual_sum == expected_sum diff --git a/tests/test_broadcast.py b/tests/test_broadcast.py index 1963ea39..d8a7c49a 100644 --- a/tests/test_broadcast.py +++ b/tests/test_broadcast.py @@ -62,7 +62,7 @@ async def update_tracker_on_receive(receiver_id: int, recv: Receiver[int]) -> No actual_sum = 0 for ctr in recv_trackers: - ## ensure all receivers have got messages + # ensure all receivers have got messages assert ctr > 0 actual_sum += ctr assert actual_sum == expected_sum From b7955da022581c8483e13db17488762cdf6e6951 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 23:13:14 +0200 Subject: [PATCH 05/11] Fix arguments documentation We need to specify the `*` for the catch-all for positional arguments and `**` for the one for keyword arguments. This is necessary because now we are checking for docs style in tests and benchmarks too. Signed-off-by: Leandro Lucarella --- tests/utils/test_file_watcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils/test_file_watcher.py b/tests/utils/test_file_watcher.py index e3eade62..702fc9de 100644 --- a/tests/utils/test_file_watcher.py +++ b/tests/utils/test_file_watcher.py @@ -37,8 +37,8 @@ async def fake_awatch( """Fake awatch function. Args: - paths: Paths to watch. - kwargs: Keyword arguments to pass to the awatch function. + *paths: Paths to watch. + **kwargs: Keyword arguments to pass to the awatch function. """ for change in self.changes: yield {change} From fb7488e9ce2548f29aa655f14dee16118eb53cb5 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 23:13:37 +0200 Subject: [PATCH 06/11] Add missing Yields section to the documentation This is necessary because now we are checking for docs style in tests and benchmarks too. Signed-off-by: Leandro Lucarella --- tests/utils/test_file_watcher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/utils/test_file_watcher.py b/tests/utils/test_file_watcher.py index 702fc9de..bed75bcb 100644 --- a/tests/utils/test_file_watcher.py +++ b/tests/utils/test_file_watcher.py @@ -39,6 +39,9 @@ async def fake_awatch( Args: *paths: Paths to watch. **kwargs: Keyword arguments to pass to the awatch function. + + Yields: + The file changes in the sequence provided to the constructor. """ for change in self.changes: yield {change} From 2e50b98a5465b26296aeef61e3901b434000235d Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 23:14:33 +0200 Subject: [PATCH 07/11] Add missing Returns section to `consume()` Even when the body is not returning anything in this case, the docs should specify what could happen in general. Signed-off-by: Leandro Lucarella --- src/frequenz/channels/_base_classes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/frequenz/channels/_base_classes.py b/src/frequenz/channels/_base_classes.py index 638c3d3b..e993dbb1 100644 --- a/src/frequenz/channels/_base_classes.py +++ b/src/frequenz/channels/_base_classes.py @@ -130,6 +130,10 @@ def into_peekable(self) -> Peekable[T]: Once this function has been called, the receiver will no longer be usable, and calling `receive` on the receiver will raise an exception. + Returns: + A `Peekable` that can be used to peek at the latest value in the + channel. + Raises: NotImplementedError: when a `Receiver` implementation doesn't have a custom `into_peekable` implementation. From 5446aa6427c6e6b9ad305745f22d4c294226a02d Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 26 Oct 2023 23:16:09 +0200 Subject: [PATCH 08/11] Ignore Raises sections without explicit raise statements These are either specifying what sub-classes of a base class could do, or are specifying exceptions that are raised indirectly. Signed-off-by: Leandro Lucarella --- src/frequenz/channels/_base_classes.py | 4 +++- src/frequenz/channels/util/_timer.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/frequenz/channels/_base_classes.py b/src/frequenz/channels/_base_classes.py index e993dbb1..b56ba77c 100644 --- a/src/frequenz/channels/_base_classes.py +++ b/src/frequenz/channels/_base_classes.py @@ -194,7 +194,9 @@ async def ready(self) -> bool: """ return await self._recv.ready() # pylint: disable=protected-access - def consume(self) -> U: + # We need a noqa here because the docs have a Raises section but the code doesn't + # explicitly raise anything. + def consume(self) -> U: # noqa: DOC502 """Return a transformed value once `ready()` is complete. Returns: diff --git a/src/frequenz/channels/util/_timer.py b/src/frequenz/channels/util/_timer.py index f236755e..ef577897 100644 --- a/src/frequenz/channels/util/_timer.py +++ b/src/frequenz/channels/util/_timer.py @@ -482,8 +482,10 @@ def __init__( if auto_start: self.reset(start_delay=start_delay) + # We need a noqa here because the docs have a Raises section but the documented + # exceptions are raised indirectly. @classmethod - def timeout( + def timeout( # noqa: DOC502 cls, delay: timedelta, /, @@ -530,8 +532,10 @@ def timeout( loop=loop, ) + # We need a noqa here because the docs have a Raises section but the documented + # exceptions are raised indirectly. @classmethod - def periodic( + def periodic( # noqa: DOC502 cls, period: timedelta, /, @@ -661,7 +665,9 @@ def stop(self) -> None: # We need to make sure it's not None, otherwise `ready()` will start it self._next_tick_time = self._now() - async def ready(self) -> bool: + # We need a noqa here because the docs have a Raises section but the documented + # exceptions are raised indirectly. + async def ready(self) -> bool: # noqa: DOC502 """Wait until the timer `interval` passed. Once a call to `ready()` has finished, the resulting tick information From d3413142e9385a2c7493a92f6bb253402f0f1780 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Sun, 29 Oct 2023 17:24:25 +0100 Subject: [PATCH 09/11] Remove obsolete `darglint` ignore directives We are not using `darglint` anymore and they render in the docs. Signed-off-by: Leandro Lucarella --- src/frequenz/channels/_base_classes.py | 2 -- src/frequenz/channels/_bidirectional.py | 2 -- src/frequenz/channels/util/_select.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/frequenz/channels/_base_classes.py b/src/frequenz/channels/_base_classes.py index b56ba77c..8745ce87 100644 --- a/src/frequenz/channels/_base_classes.py +++ b/src/frequenz/channels/_base_classes.py @@ -93,8 +93,6 @@ async def receive(self) -> T: Raises: ReceiverStoppedError: if there is some problem with the receiver. ReceiverError: if there is some problem with the receiver. - - # noqa: DAR401 __cause__ (https://github.com/terrencepreilly/darglint/issues/181) """ try: received = await self.__anext__() # pylint: disable=unnecessary-dunder-call diff --git a/src/frequenz/channels/_bidirectional.py b/src/frequenz/channels/_bidirectional.py index 747567b5..a1bfc94f 100644 --- a/src/frequenz/channels/_bidirectional.py +++ b/src/frequenz/channels/_bidirectional.py @@ -95,8 +95,6 @@ def consume(self) -> W: Raises: ReceiverStoppedError: if there is some problem with the receiver. ReceiverError: if there is some problem with the receiver. - - # noqa: DAR401 err (https://github.com/terrencepreilly/darglint/issues/181) """ try: return self._receiver.consume() # pylint: disable=protected-access diff --git a/src/frequenz/channels/util/_select.py b/src/frequenz/channels/util/_select.py index 0196390c..43a0e357 100644 --- a/src/frequenz/channels/util/_select.py +++ b/src/frequenz/channels/util/_select.py @@ -84,8 +84,6 @@ def value(self) -> _T: this should be an [`frequenz.channels.Error`][frequenz.channels.Error] instance, but catches all exceptions in case some receivers can raise anything else. - - # noqa: DAR401 _exception """ if self._exception is not None: raise self._exception From ab6d59ee8c552ef7d7b9f313c9ab50ce10dee866 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 30 Oct 2023 13:49:36 +0100 Subject: [PATCH 10/11] Add release notes Signed-off-by: Leandro Lucarella --- RELEASE_NOTES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cd3b1716..7678e1d8 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -16,6 +16,17 @@ The `Timer` now can be started with a delay. * `Broadcast.resend_latest` is now a public attribute, allowing it to be changed after the channel is created. +* The arm64 architecture is now officially supported. + +* The documentation was improved to: + + - Show signatures with types. + - Show the inherited members. + - Documentation for pre-releases are now published. + - Show the full tag name as the documentation version. + - All development branches now have their documentation published (there is no `next` version anymore). + - Fix the order of the documentation versions. + ## Bug Fixes From a15edb2d0965590f08e8754fbcdd4a17e392749f Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Wed, 1 Nov 2023 12:37:58 +0100 Subject: [PATCH 11/11] Apply typo fixes from review Signed-off-by: Christian Parpart --- CONTRIBUTING.md | 2 +- docs/_css/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f46d683f..437a4e66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -162,7 +162,7 @@ Currently, our CI conducts tests on `arm64` machines using QEMU emulation. We also have the flexibility to expand this support to include additional architectures in the future. -This project containers Dockerfiles that can be used in the CI to test the +This project contains Dockerfiles that can be used in the CI to test the python package in non-native machine architectures, e.g., `arm64`. The Dockerfiles exist in the directory `.github/containers/nox-cross-arch`, and follow a naming scheme so that they can be easily used in build matrices in the diff --git a/docs/_css/style.css b/docs/_css/style.css index f253894b..c6b2eacc 100644 --- a/docs/_css/style.css +++ b/docs/_css/style.css @@ -29,7 +29,7 @@ a.external:hover::after, a.md-nav__link[href^="https:"]:hover::after { /* Code annotations with numbers. * - * Normally annothations are shown with a (+) button that expands the + * Normally annotations are shown with a (+) button that expands the * annotation. To be able to explain code step by step, it is good to have * annotations with numbers, to be able to follow the notes in a particular * order.