diff --git a/.cookiecutter-replay.json b/.cookiecutter-replay.json new file mode 100644 index 0000000..8b3de27 --- /dev/null +++ b/.cookiecutter-replay.json @@ -0,0 +1,45 @@ +{ + "cookiecutter": { + "Introduction": "", + "type": "actor", + "name": "frequenz-dispatch", + "description": "A highlevel interface for the dispatch API", + "title": "Dispatch Highlevel Interface", + "keywords": "dispatch,highlevel,api", + "github_org": "frequenz-floss", + "license": "MIT", + "author_name": "Frequenz Energy-as-a-Service GmbH", + "author_email": "floss@frequenz.com", + "python_package": "frequenz.actor.dispatch", + "pypi_package_name": "frequenz-actor-dispatch", + "github_repo_name": "frequenz-dispatch-python", + "default_codeowners": "@frequenz-floss/api-dispatch-team", + "_template": "gh:frequenz-floss/frequenz-repo-config-python", + "_repo_dir": "/home/marenz/.cookiecutters/frequenz-repo-config-python/cookiecutter" + }, + "_cookiecutter": { + "Introduction": "{{cookiecutter | introduction}}", + "type": [ + "actor", + "api", + "app", + "lib", + "model" + ], + "name": null, + "description": null, + "title": "{{cookiecutter | proj_title}}", + "keywords": "(comma separated: 'frequenz', and are included automatically)", + "github_org": "frequenz-floss", + "license": [ + "MIT", + "Proprietary" + ], + "author_name": "Frequenz Energy-as-a-Service GmbH", + "author_email": "floss@frequenz.com", + "python_package": "{{cookiecutter | python_package}}", + "pypi_package_name": "{{cookiecutter | pypi_package_name}}", + "github_repo_name": "{{cookiecutter | github_repo_name}}", + "default_codeowners": "(like @some-org/some-team; defaults to a team based on the repo type)" + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c7f9e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Set default charset, indent style and trimming of whitespace +[{.editorconfig,CODEOWNERS,LICENSE,*.{in,json,md,proto,py,pyi,toml,yaml,yml}}] +charset = utf-8 +indent_style = space +trim_trailing_whitespace = true + +# 4 space indentation +[*.{py,pyi}] +indent_size = 4 + +# 2 space indentation +[{.editorconfig,CODEOWNERS,LICENSE,*.{in,json,proto,toml,yaml,yml}}] +indent_size = 2 + +# No indentation size specified for *.md because different blocks have +# different indentation rules diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..ff62366 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,66 @@ +# GitHub issue form. For more information see: +# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms + +name: Report something is not working properly 🐛 +description: + Use this if there is something that is not working properly. If you are not + sure or you need help making something work, please ask a question instead. +labels: + - "priority:❓" + - "type:bug" +body: + - type: markdown + attributes: + value: + Thanks for taking the time to fill out this bug report! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Please tell us what happened that shouldn't have. + placeholder: What happened that shouldn't have. + validations: + required: true + - type: textarea + id: what-expected + attributes: + label: What did you expect instead? + description: Please tell us what did you expect to happen. + placeholder: What did you expect to happen. + validations: + required: true + - type: input + id: version + attributes: + label: Affected version(s) + description: + Please add a comma-separated list of the versions affected by this + issue. + placeholder: 'Example: v0.11.0, v0.12.0' + - type: dropdown + id: part + attributes: + label: Affected part(s) + description: + Which parts of the repo are affected by this issue? Select all that + apply. + multiple: true + options: + - I don't know (part:❓) + - Documentation (part:docs) + - Unit, integration and performance tests (part:tests) + - Build script, CI, dependencies, etc. (part:tooling) + # TODO(cookiecutter): Add other parts + # Please have in mind that that the part:xxx labels need to + # be created in the GitHub repository. + validations: + required: true + - type: textarea + id: extra + attributes: + label: Extra information + description: + Please write here any extra information you think it might be relevant, + e.g., if this didn't happen before, or if you suspect where the problem + might be. + placeholder: Any extra information you think it might be relevant. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..825cf27 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +# GitHub issue template chooser. For more information see: +# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser + +blank_issues_enabled: true +contact_links: + - name: Ask a question ❓ + url: https://github.com/frequenz-floss/frequenz-dispatch-python/discussions/new?category=support + # TODO(cookiecutter): Make sure the GitHub repository has a discussion category "Support" + # Rename the "Q&A" category to "Support" and change the emoji to 🆘 (SOS) + about: Use this if you are not sure how to do something, have installation problems, etc. diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000..459dd4b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,61 @@ +# GitHub issue form. For more information see: +# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms + +name: Request a feature or enhancement ✨ +description: Use this if something is missing or could be done better or more easily. +labels: + - "part:❓" + - "priority:❓" + - "type:enhancement" +body: + - type: markdown + attributes: + value: + Thanks for taking the time to fill out this feature or enhancement + request! + - type: textarea + id: whats-needed + attributes: + label: What's needed? + description: + Please tell us what's missing or what could be done better or more easily. + placeholder: What's missing or what could be done better or more easily. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + description: + Please tell us how you think the needs above can be fulfilled. Only + fill this field if it wasn't described above. + placeholder: + How do you think the needs above can be fulfilled. Only fill this field + if it wasn't described above. + - type: textarea + id: use-cases + attributes: + label: Use cases + description: + Please tell us about the main use cases you see for this new feature or + enhancement to help us understand more. + placeholder: + The main use cases you see for this new feature or enhancement to help + us understand more. + - type: textarea + id: alternatives + attributes: + label: Alternatives and workarounds + description: + Please tell us if you tried any alternatives or workarounds for these + use cases and how (un)useful they were. + placeholder: + Any alternatives or workarounds for these use cases and how (un)useful + they were. + - type: textarea + id: additional-context + attributes: + label: Additional context + description: + Please add any additional information here - screenshots, diagrams, etc. + placeholder: Any additional information here - screenshots, diagrams, etc. diff --git a/.github/RELEASE_NOTES.template.md b/.github/RELEASE_NOTES.template.md new file mode 100644 index 0000000..d4546f4 --- /dev/null +++ b/.github/RELEASE_NOTES.template.md @@ -0,0 +1,17 @@ +# Dispatch Highlevel Interface Release Notes + +## Summary + + + +## Upgrading + + + +## New Features + + + +## Bug Fixes + + 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 0000000..4a29eec --- /dev/null +++ b/.github/containers/nox-cross-arch/arm64-ubuntu-20.04-python-3.11.Dockerfile @@ -0,0 +1,33 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH +# This Dockerfile is used to run the tests in arm64, which is not supported by +# GitHub Actions at the moment. + +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 0000000..f344deb --- /dev/null +++ b/.github/containers/nox-cross-arch/entrypoint.bash @@ -0,0 +1,9 @@ +#!/bin/bash +# License: MIT +# Copyright © 2024 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 0000000..4be1515 --- /dev/null +++ b/.github/containers/test-installation/Dockerfile @@ -0,0 +1,17 @@ +# License: MIT +# Copyright © 2024 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 apt-get update -y && \ + apt-get install --no-install-recommends -y \ + git && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + 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 new file mode 100644 index 0000000..3077268 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,41 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + day: "thursday" + labels: + - "part:tooling" + - "type:tech-debt" + # Default versioning-strategy. For other versioning-strategy see: + # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#versioning-strategy + 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: "monthly" + day: "thursday" + labels: + - "part:tooling" + - "type:tech-debt" diff --git a/.github/keylabeler.yml b/.github/keylabeler.yml new file mode 100644 index 0000000..e5abffe --- /dev/null +++ b/.github/keylabeler.yml @@ -0,0 +1,21 @@ +# KeywordLabeler app configuration. For more information check: +# https://github.com/ZeWaka/KeywordLabeler#readme + +# Determines if we search the title (optional). Defaults to true. +matchTitle: true + +# Determines if we search the body (optional). Defaults to true. +matchBody: true + +# Determines if label matching is case sensitive (optional). Defaults to true. +caseSensitive: true + +# Explicit keyword mappings to labels. Form of match:label. Required. +labelMappings: + "part:docs": "part:docs" + "part:tests": "part:tests" + "part:tooling": "part:tooling" + "part:❓": "part:❓" + # TODO(cookiecutter): Add other parts + # Please have in mind that that the part:xxx labels need to + # be created in the GitHub repository. diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..9003502 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,63 @@ +# Configuration for the Labeler GitHub action, executed by +# .github/workflows/labeler.yml. +# +# The basic syntax is [label]: [path patterns]. +# +# For more details on the configuration please see: +# https://github.com/marketplace/actions/labeler + +# TODO(cookiecutter): Add different parts of the source +# For example: +# +# "part:module": +# - changed-files: +# - any-glob-to-any-file: +# - "src/frequenz/actor/frequenz_dispatch/module/**" +# +# "part:other": +# - changed-files: +# - any-glob-to-any-file: +# - "src/frequenz/actor/frequenz_dispatch/other/**" +# +# # For excluding some files (in this example, label "part:complicated" +# # everything inside src/ with a .py suffix, except for src/__init__.py) +# "part:complicated": +# - all: +# - changed-files: +# - any-glob-to-any-file: +# - "src/**/*.py" +# - all-glob-to-all-file: +# - "!src/__init__.py" +# +# Please have in mind that that the part:xxx labels need to +# be created in the GitHub repository. + +"part:docs": + - changed-files: + - any-glob-to-any-file: + - "**/*.md" + - "docs/**" + - "examples/**" + - LICENSE + +"part:tests": + - changed-files: + - any-glob-to-any-file: + - "**/conftest.py" + - "tests/**" + +"part:tooling": + - changed-files: + - any-glob-to-any-file: + - "**/*.ini" + - "**/*.toml" + - "**/*.yaml" + - "**/*.yml" + - "**/conftest.py" + - ".editorconfig" + - ".git*" + - ".git*/**" + - "docs/*.py" + - CODEOWNERS + - MANIFEST.in + - noxfile.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..172374b --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,441 @@ +name: CI + +on: + merge_group: + pull_request: + push: + # We need to explicitly include tags because otherwise when adding + # `branches-ignore` it will only trigger on branches. + tags: + - '*' + branches-ignore: + # Ignore pushes to merge queues. + # 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: + # Please make sure this version is included in the `matrix`, as the + # `matrix` section can't use `env`, so it must be entered manually + DEFAULT_PYTHON_VERSION: '3.11' + # It would be nice to be able to also define a DEFAULT_UBUNTU_VERSION + # but sadly `env` can't be used either in `runs-on`. + +jobs: + nox: + name: Test with nox + strategy: + fail-fast: false + matrix: + os: + - 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: + submodules: true + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + cache: 'pip' + + - name: Install required Python packages + 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 + 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 + with: + submodules: true + + - 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 + steps: + - name: Fetch sources + uses: actions/checkout@v4 + with: + submodules: true + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: 'pip' + + - name: Install required Python packages + 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 + + - name: Upload distribution files + uses: actions/upload-artifact@v3 + with: + name: dist-packages + 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' + runs-on: ubuntu-20.04 + steps: + - name: Fetch sources + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Git user and e-mail + uses: frequenz-floss/setup-git-user@v2 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: 'pip' + + - name: Install build dependencies + run: | + python -m pip install -U pip + python -m pip install .[dev-mkdocs] + pip freeze + + - name: Generate the documentation + env: + MIKE_VERSION: gh-${{ github.job }} + run: | + mike deploy $MIKE_VERSION + mike set-default $MIKE_VERSION + + - name: Upload site + uses: actions/upload-artifact@v3 + with: + name: docs-site + path: site/ + if-no-files-found: error + + publish-docs: + name: Publish documentation website to GitHub pages + needs: ["nox-all", "nox-cross-arch-all", "test-installation"] + if: github.event_name == 'push' + runs-on: ubuntu-20.04 + permissions: + contents: write + steps: + - name: Fetch sources + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Git user and e-mail + uses: frequenz-floss/setup-git-user@v2 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: 'pip' + + - name: Install build dependencies + 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-version.outputs.version + run: git fetch origin gh-pages --depth=1 + + - name: Build site + if: steps.mike-version.outputs.version + env: + 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.mike.sort versions.json + git commit -a -m "Sort versions.json" + + - name: Publish site + if: steps.mike-version.outputs.version + run: | + git push origin gh-pages + + create-github-release: + name: Create GitHub release + needs: ["publish-docs"] + # Create a release only on tags creation + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + permissions: + # We need write permissions on contents to create GitHub releases and on + # discussions to create the release announcement in the discussion forums + contents: write + discussions: write + runs-on: ubuntu-20.04 + steps: + - name: Download distribution files + uses: actions/download-artifact@v3 + with: + name: dist-packages + path: dist + + - name: Download RELEASE_NOTES.md + run: | + set -ux + gh api \ + -X GET \ + -f ref=$REF \ + -H "Accept: application/vnd.github.raw" \ + "/repos/$REPOSITORY/contents/RELEASE_NOTES.md" \ + > RELEASE_NOTES.md + env: + REF: ${{ github.ref }} + REPOSITORY: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub release + run: | + set -ux + extra_opts= + if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + gh release create \ + -R "$REPOSITORY" \ + --notes-file RELEASE_NOTES.md \ + --generate-notes \ + $extra_opts \ + $REF_NAME \ + dist/* + env: + REF_NAME: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-to-pypi: + name: Publish packages to PyPI + needs: ["create-github-release"] + runs-on: ubuntu-20.04 + permissions: + # For trusted publishing. See: + # https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ + id-token: write + steps: + - name: Download distribution files + uses: actions/download-artifact@v3 + with: + name: dist-packages + path: dist + + - name: Publish the Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/dco-merge-queue.yml b/.github/workflows/dco-merge-queue.yml new file mode 100644 index 0000000..fb1cd90 --- /dev/null +++ b/.github/workflows/dco-merge-queue.yml @@ -0,0 +1,11 @@ +# Based on https://github.com/hyperledger/besu/pull/5207/files +name: DCO +on: + merge_group: + +jobs: + DCO: + runs-on: ubuntu-latest + if: ${{ github.actor != 'dependabot[bot]' }} + steps: + - run: echo "This DCO job runs on merge_queue event and doesn't check PR contents" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..c844b8d --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,24 @@ +name: Pull Request Labeler + +on: [pull_request_target] + +jobs: + Label: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Labeler + # XXX: !!! SECURITY WARNING !!! + # pull_request_target has write access to the repo, and can read secrets. We + # need to audit any external actions executed in this workflow and make sure no + # checked out code is run (not even installing dependencies, as installing + # dependencies usually can execute pre/post-install scripts). We should also + # only use hashes to pick the action to execute (instead of tags or branches). + # For more details read: + # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # 5.0.0 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + dot: true diff --git a/.github/workflows/release-notes-check.yml b/.github/workflows/release-notes-check.yml new file mode 100644 index 0000000..cb39183 --- /dev/null +++ b/.github/workflows/release-notes-check.yml @@ -0,0 +1,30 @@ +name: Release Notes Check + +on: + merge_group: + pull_request: + types: + # On by default if you specify no types. + - "opened" + - "reopened" + - "synchronize" + # For `skip-label` only. + - "labeled" + - "unlabeled" + + +jobs: + check-release-notes: + name: Check release notes are updated + runs-on: ubuntu-latest + steps: + - name: Check for a release notes update + if: github.event_name == 'pull_request' + uses: brettcannon/check-for-changed-files@4170644959a21843b31f1181f2a1761d65ef4791 # v1.2.0 + with: + # TODO(cookiecutter): Uncomment the following line for private repositories, otherwise remove it and remove it + # token: ${{ secrets.github_token }} + file-pattern: "RELEASE_NOTES.md" + prereq-pattern: "src/**" + skip-label: "cmd:skip-release-notes" + failure-message: "Missing a release notes update. Please add one or apply the ${skip-label} label to the pull request" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6997f69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,150 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +.vscode + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.htmlcov*/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +# direnv https://github.com/direnv/direnv +.envrc +.direnv/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea + +# Automatically generated documentation +docs/reference/ +site/ diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..db11f95 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,8 @@ +# Each line is a file pattern followed by one or more owners. +# Owners will be requested for review when someone opens a pull request. + +# Fallback owner. +# These are the default owners for everything in the repo, unless a later match +# takes precedence. +# TODO(cookiecutter): Add more specific code-owners, check if the default is correct +* @frequenz-floss/api-dispatch-team diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a30eafc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,183 @@ +# Contributing to Dispatch Highlevel Interface + +## Build + +You can use `build` to simply build the source and binary distribution: + +```sh +python -m pip install build +python -m build +``` + +## Local development + +You can use editable installs to develop the project locally (it will install +all the dependencies too): + +```sh +python -m pip install -e . +``` + +Or you can install all development dependencies (`mypy`, `pylint`, `pytest`, +etc.) in one go too: +```sh +python -m pip install -e .[dev] +``` + +If you don't want to install all the dependencies, you can also use `nox` to +run the tests and other checks creating its own virtual environments: + +```sh +python -m pip install .[dev-noxfile] +nox +``` + +You can also use `nox -R` to reuse the current testing environment to speed up +test at the expense of a higher chance to end up with a dirty test environment. + +### Running tests / checks individually + +For a better development test cycle you can install the runtime and test +dependencies and run `pytest` manually. + +```sh +python -m pip install .[dev-pytest] # included in .[dev] too + +# And for example +pytest tests/test_*.py +``` + +Or you can use `nox`: + +```sh +nox -R -s pytest -- test/test_*.py +``` + +The same appliest to `pylint` or `mypy` for example: + +```sh +nox -R -s pylint -- test/test_*.py +nox -R -s mypy -- test/test_*.py +``` + +### Building the documentation + +To build the documentation, first install the dependencies (if you didn't +install all `dev` dependencies): + +```sh +python -m pip install -e .[dev-mkdocs] +``` + +Then you can build the documentation (it will be written in the `site/` +directory): + +```sh +mkdocs build +``` + +Or you can just serve the documentation without building it using: + +```sh +mkdocs serve +``` + +Your site will be updated **live** when you change your files (provided that +you used `pip install -e .`, beware of a common pitfall of using `pip install` +without `-e`, in that case the API reference won't change unless you do a new +`pip install`). + +To build multi-version documentation, we use +[mike](https://github.com/jimporter/mike). If you want to see how the +multi-version sites looks like locally, you can use: + +```sh +mike deploy my-version +mike set-default my-version +mike serve +``` + +`mike` works in mysterious ways. Some basic information: + +* `mike deploy` will do a `mike build` and write the results to your **local** + `gh-pages` branch. `my-version` is an arbitrary name for the local version + you want to preview. +* `mike set-default` is needed so when you serve the documentation, it goes to + your newly produced documentation by default. +* `mike serve` will serve the contents of your **local** `gh-pages` branch. Be + aware that, unlike `mkdocs serve`, changes to the sources won't be shown + live, as the `mike deploy` step is needed to refresh them. + +Be careful not to use `--push` with `mike deploy`, otherwise it will push your +local `gh-pages` branch to the `origin` remote. + +That said, if you want to test the actual website in **your fork**, you can +always use `mike deploy --push --remote your-fork-remote`, and then access the +GitHub pages produced for your fork. + +## Releasing + +These are the steps to create a new release: + +1. Get the latest head you want to create a release from. + +2. Update the `RELEASE_NOTES.md` file if it is not complete, up to date, and + remove template comments (` + +## Upgrading + + + +## New Features + + + +## Bug Fixes + + diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..ea38c9b --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1 @@ +--8<-- "CONTRIBUTING.md" diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 0000000..3755def --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,3 @@ +* [Home](index.md) +* [API Reference](reference/) +* [Contributing](CONTRIBUTING.md) diff --git a/docs/_css/mkdocstrings.css b/docs/_css/mkdocstrings.css new file mode 100644 index 0000000..572abff --- /dev/null +++ b/docs/_css/mkdocstrings.css @@ -0,0 +1,44 @@ +/* Recommended style from: + * https://mkdocstrings.github.io/python/customization/#recommended-style-material + * With some additions from: + * https://github.com/mkdocstrings/mkdocstrings/blob/master/docs/css/mkdocstrings.css + */ + +/* Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: .05rem solid var(--md-typeset-table-color); +} + +/* Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: 4px solid rgba(230, 230, 230); + margin-bottom: 80px; +} + +/* Avoid breaking parameters name, etc. in table cells. */ +td code { + word-break: normal !important; +} + +/* Mark external links as such. */ +a.autorefs-external::after { + /* https://primer.style/octicons/arrow-up-right-24 */ + background-image: url('data:image/svg+xml,'); + content: ' '; + + display: inline-block; + position: relative; + top: 0.1em; + margin-left: 0.2em; + margin-right: 0.1em; + + height: 1em; + width: 1em; + border-radius: 100%; + background-color: var(--md-typeset-a-color); +} +a.autorefs-external:hover::after { + background-color: var(--md-accent-fg-color); +} diff --git a/docs/_css/style.css b/docs/_css/style.css new file mode 100644 index 0000000..c6b2eac --- /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 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. + * + * 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/_img/logo.png b/docs/_img/logo.png new file mode 100644 index 0000000..7a9db36 Binary files /dev/null and b/docs/_img/logo.png differ diff --git a/docs/_overrides/main.html b/docs/_overrides/main.html new file mode 100644 index 0000000..980f3ab --- /dev/null +++ b/docs/_overrides/main.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block outdated %} + You're not viewing the latest (stable) version. + + Click here to go to latest (stable) version + +{% endblock %} diff --git a/docs/_scripts/macros.py b/docs/_scripts/macros.py new file mode 100644 index 0000000..ea0387c --- /dev/null +++ b/docs/_scripts/macros.py @@ -0,0 +1,83 @@ +# License: MIT +# Copyright © 2024 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/_scripts/mkdocstrings_autoapi.py b/docs/_scripts/mkdocstrings_autoapi.py new file mode 100644 index 0000000..1f7beb9 --- /dev/null +++ b/docs/_scripts/mkdocstrings_autoapi.py @@ -0,0 +1,8 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Generate the code reference pages.""" + +from frequenz.repo.config.mkdocs import api_pages + +api_pages.generate_python_api_pages("src", "reference") diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..612c7a5 --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +--8<-- "README.md" diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..1416315 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,138 @@ +# MkDocs configuration +# For details see: https://www.mkdocs.org/user-guide/configuration/ + +# Project information +site_name: "Dispatch Highlevel Interface" +site_description: "A highlevel interface for the dispatch API" +site_author: "Frequenz Energy-as-a-Service GmbH" +copyright: "Copyright © 2024 Frequenz Energy-as-a-Service GmbH" +repo_name: "frequenz-dispatch-python" +repo_url: "https://github.com/frequenz-floss/frequenz-dispatch-python" +# TODO(cookiecutter): "main" is the GitHub repo default branch, you might want to update it +# if the project uses a different default branch. +edit_uri: "edit/main/docs/" +strict: true # Treat warnings as errors + +# Build directories +theme: + name: "material" + # TODO(cookiecutter): You might want to change the logo, the file is located in "docs/" + logo: _img/logo.png + favicon: _img/logo.png + language: en + icon: + edit: material/file-edit-outline + repo: fontawesome/brands/github + custom_dir: docs/_overrides + features: + - content.code.annotate + - content.code.copy + - navigation.indexes + - navigation.instant + - navigation.footer + - navigation.tabs + - navigation.top + - navigation.tracking + - toc.follow + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: deep purple + toggle: + icon: material/weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: teal + toggle: + icon: material/weather-night + name: Switch to light mode + +extra: + # TODO(cookiecutter): You probably want to update the social links + social: + - icon: fontawesome/brands/github + link: https://github.com/frequenz-floss + - icon: fontawesome/brands/linkedin + link: https://www.linkedin.com/company/frequenz-com + version: + provider: mike + default: latest + +extra_css: + - _css/style.css + - _css/mkdocstrings.css + +# Formatting options +markdown_extensions: + - admonition + - attr_list + - def_list + - footnotes + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.keys + - pymdownx.snippets: + check_paths: true + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed + - pymdownx.tasklist: + custom_checkbox: true + - toc: + permalink: "¤" + +plugins: + - gen-files: + scripts: + - docs/_scripts/mkdocstrings_autoapi.py + - literate-nav: + nav_file: SUMMARY.md + - mike: + alias_type: redirect + canonical_version: latest + - mkdocstrings: + default_handler: python + handlers: + python: + 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: + # TODO(cookiecutter): You might want to add other external references here + # See https://mkdocstrings.github.io/python/usage/#import for details + - https://docs.python.org/3/objects.inv + - https://frequenz-floss.github.io/frequenz-channels-python/v0.16/objects.inv + - https://frequenz-floss.github.io/frequenz-sdk-python/v0.25/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 + +# Preview controls +watch: + - "src" + - README.md + - CONTRIBUTING.md diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..0720f38 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,8 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Configuration file for nox.""" + +from frequenz.repo.config import RepositoryType, nox + +nox.configure(RepositoryType.ACTOR) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9469c7d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,170 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +[build-system] +requires = [ + "setuptools == 68.1.0", + "setuptools_scm[toml] == 7.1.0", + "frequenz-repo-config[actor] == 0.8.0", +] +build-backend = "setuptools.build_meta" + +[project] +name = "frequenz-actor-dispatch" +description = "A highlevel interface for the dispatch API" +readme = "README.md" +license = { text = "MIT" } +keywords = ["frequenz", "python", "actor", "frequenz-dispatch", "dispatch", "highlevel", "api"] +# TODO(cookiecutter): Remove and add more classifiers if appropriate +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development :: Libraries", + "Typing :: Typed", +] +requires-python = ">= 3.11, < 4" +# TODO(cookiecutter): Remove and add more dependencies if appropriate +dependencies = [ + "typing-extensions == 4.5.0", + # Make sure to update the version for cross-referencing also in the + # mkdocs.yml file when changing the version here (look for the config key + # plugins.mkdocstrings.handlers.python.import) + "frequenz-sdk == 0.25.0", +] +dynamic = ["version"] + +[[project.authors]] +name = "Frequenz Energy-as-a-Service GmbH" +email = "floss@frequenz.com" + +# TODO(cookiecutter): Remove and add more optional dependencies if appropriate +[project.optional-dependencies] +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", +] +dev-formatting = ["black == 23.9.1", "isort == 5.12.0"] +dev-mkdocs = [ + "black == 23.9.1", + "Markdown==3.4.4", + "mike == 2.0.0", + "mkdocs-gen-files == 0.5.0", + "mkdocs-literate-nav == 0.6.1", + "mkdocs-macros-plugin == 1.0.4", + "mkdocs-material == 9.3.1", + "mkdocstrings[python] == 0.23.0", + "frequenz-repo-config[actor] == 0.8.0", +] +dev-mypy = [ + "mypy == 1.5.1", + "types-Markdown == 3.4.2.10", + # For checking the noxfile, docs/ script, and tests + "frequenz-actor-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]", +] +dev-noxfile = [ + "nox == 2023.4.22", + "frequenz-repo-config[actor] == 0.8.0", +] +dev-pylint = [ + "pylint == 3.0.2", + # For checking the noxfile, docs/ script, and tests + "frequenz-actor-dispatch[dev-mkdocs,dev-noxfile,dev-pytest]", +] +dev-pytest = [ + "pytest == 8.0.0", + "frequenz-repo-config[extra-lint-examples] == 0.8.0", + "pytest-mock == 3.11.1", + "pytest-asyncio == 0.21.1", + "async-solipsism == 0.5", +] +dev = [ + "frequenz-actor-dispatch[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-dispatch-python/" +Changelog = "https://github.com/frequenz-floss/frequenz-dispatch-python/releases" +Issues = "https://github.com/frequenz-floss/frequenz-dispatch-python/issues" +Repository = "https://github.com/frequenz-floss/frequenz-dispatch-python" +Support = "https://github.com/frequenz-floss/frequenz-dispatch-python/discussions/categories/support" + +[tool.black] +line-length = 88 +target-version = ['py311'] +include = '\.pyi?$' + +[tool.isort] +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'] +ignore-imports = ['no'] +min-similarity-lines = 40 + +[tool.pylint.messages_control] +disable = [ + "too-few-public-methods", + "too-many-return-statements", + # disabled because it conflicts with isort + "wrong-import-order", + "ungrouped-imports", + # 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", + "redefined-outer-name", + "unnecessary-lambda-assignment", + "unused-import", + "unused-variable", +] + +[tool.pytest.ini_options] +testpaths = ["tests", "src"] +asyncio_mode = "auto" +required_plugins = ["pytest-asyncio", "pytest-mock"] + +[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.actor.dispatch"] +strict = true + +[[tool.mypy.overrides]] +module = ["mkdocs_macros.*", "sybil", "sybil.*"] +ignore_missing_imports = true + +[tool.setuptools_scm] +version_scheme = "post-release" diff --git a/src/frequenz/actor/dispatch/__init__.py b/src/frequenz/actor/dispatch/__init__.py new file mode 100644 index 0000000..d1d8611 --- /dev/null +++ b/src/frequenz/actor/dispatch/__init__.py @@ -0,0 +1,25 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""A highlevel interface for the dispatch API. + +TODO(cookiecutter): Add a more descriptive module description. +""" + + +# TODO(cookiecutter): Remove this function +def delete_me(*, blow_up: bool = False) -> bool: + """Do stuff for demonstration purposes. + + Args: + blow_up: If True, raise an exception. + + Returns: + True if no exception was raised. + + Raises: + RuntimeError: if blow_up is True. + """ + if blow_up: + raise RuntimeError("This function should be removed!") + return True diff --git a/src/frequenz/actor/dispatch/conftest.py b/src/frequenz/actor/dispatch/conftest.py new file mode 100644 index 0000000..c84af78 --- /dev/null +++ b/src/frequenz/actor/dispatch/conftest.py @@ -0,0 +1,13 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Validate docstring code examples. + +Code examples are often wrapped in triple backticks (```) within docstrings. +This plugin extracts these code examples and validates them using pylint. +""" + +from frequenz.repo.config.pytest import examples +from sybil import Sybil + +pytest_collect_file = Sybil(**examples.get_sybil_arguments()).pytest() diff --git a/src/frequenz/actor/dispatch/py.typed b/src/frequenz/actor/dispatch/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_frequenz_dispatch.py b/tests/test_frequenz_dispatch.py new file mode 100644 index 0000000..126a38a --- /dev/null +++ b/tests/test_frequenz_dispatch.py @@ -0,0 +1,18 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Tests for the frequenz.actor.dispatch package.""" +import pytest + +from frequenz.actor.dispatch import delete_me + + +def test_frequenz_dispatch_succeeds() -> None: # TODO(cookiecutter): Remove + """Test that the delete_me function succeeds.""" + assert delete_me() is True + + +def test_frequenz_dispatch_fails() -> None: # TODO(cookiecutter): Remove + """Test that the delete_me function fails.""" + with pytest.raises(RuntimeError, match="This function should be removed!"): + delete_me(blow_up=True)