diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 317f3637..5b834901 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,42 +13,45 @@ 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. - # We exclude the `frequenz-repo-config` package from grouping as this - # dependency is still under development (at branch v0.x.x), so minor - # changes typically introduce breaking changes (and patch updates can - # potentially too according to semver). + # We group patch updates as they should always work. + # We also group minor updates, as it works too for most libraries, + # typically except libraries that don't have a stable release yet (v0.x.x + # branch), so we make some exceptions for them. + # Major updates and dependencies excluded by the above groups are still + # managed, but they'll create one PR per dependency, as breakage is + # expected, so it might need manual intervention. + # Finally, we group some dependencies that are related to each other, and + # usually need to be updated together. groups: - required: - dependency-type: "production" + patch: update-types: - - "minor" - "patch" exclude-patterns: - - "frequenz-repo-config" - - "frequenz-client-base*" - - "frequenz-api-microgrid" - optional: - dependency-type: "development" + # pydoclint has shipped breaking changes in patch updates often + - "pydoclint" + minor: update-types: - "minor" - - "patch" exclude-patterns: - - "frequenz-repo-config" - - "frequenz-client-base*" - - "frequenz-api-microgrid" - in-devel-patch: + - "async-solipsism" + - "frequenz-api-common" + - "frequenz-client-base" + - "frequenz-repo-config*" + - "markdown-callouts" + - "mkdocs-gen-files" + - "mkdocs-literate-nav" + - "mkdocstrings*" + - "pydoclint" + - "pytest-asyncio" + # We group repo-config updates as it uses optional dependencies that are + # considered different dependencies otherwise, and will create one PR for + # each if we don't group them. + repo-config: patterns: - - "frequenz-client-base*" - - "frequenz-api-microgrid" - dependency-type: "production" - update-types: - - "patch" + - "frequenz-repo-config*" + mkdocstrings: + patterns: + - "mkdocstrings*" - package-ecosystem: "github-actions" directory: "/" diff --git a/.github/workflows/ci-pr.yaml b/.github/workflows/ci-pr.yaml new file mode 100644 index 00000000..2c74ec0c --- /dev/null +++ b/.github/workflows/ci-pr.yaml @@ -0,0 +1,55 @@ +name: Test PR + +on: + pull_request: + +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 + runs-on: ubuntu-24.04 + + steps: + - name: Run nox + uses: frequenz-floss/gh-action-nox@v1.0.0 + with: + python-version: "3.11" + nox-session: ci_checks_max + + test-docs: + name: Test documentation website generation + runs-on: ubuntu-24.04 + steps: + - name: Setup Git + uses: frequenz-floss/gh-action-setup-git@v1.0.0 + + - name: Fetch sources + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Python + uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + dependencies: .[dev-mkdocs] + + - 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@v4 + with: + name: docs-site + path: site/ + if-no-files-found: error diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5f85ec4f..55dddda6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,7 +2,6 @@ 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. @@ -29,59 +28,27 @@ jobs: strategy: fail-fast: false matrix: + arch: + - amd64 + - arm os: - - ubuntu-20.04 + - ubuntu-24.04 python: - "3.11" + - "3.12" 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 }} + runs-on: ${{ matrix.os }}${{ matrix.arch != 'amd64' && format('-{0}', matrix.arch) || '' }} steps: - - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v0.x.x - - - name: Print environment (debug) - run: env - - - name: Fetch sources - uses: actions/checkout@v4 - with: - submodules: true - - - name: Set up Python - uses: actions/setup-python@v5 + - name: Run nox + uses: frequenz-floss/gh-action-nox@v1.0.0 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 + nox-session: ${{ matrix.nox-session }} # 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 @@ -93,157 +60,34 @@ jobs: needs: ["nox"] # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 env: DEPS_RESULT: ${{ needs.nox.result }} steps: - name: Check matrix job result run: test "$DEPS_RESULT" = "success" - 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: Setup Git - uses: frequenz-floss/gh-action-setup-git@v0.x.x - - - 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@v4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-nox-${{ matrix.arch }}-${{ matrix.os }}-${{ matrix.python }} - - - name: Build image - uses: docker/build-push-action@v6 - 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@v4 - 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 -R -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"] - # We skip this job only if nox-cross-arch was also skipped - if: always() && needs.nox-cross-arch.result != 'skipped' - runs-on: ubuntu-20.04 - env: - DEPS_RESULT: ${{ needs.nox-cross-arch.result }} - steps: - - name: Check matrix job result - run: test "$DEPS_RESULT" = "success" - build: name: Build distribution packages - runs-on: ubuntu-20.04 + # Since this is a pure Python package, we only need to build it once. If it + # had any architecture specific code, we would need to build it for each + # architecture. + runs-on: ubuntu-24.04 + steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v0.x.x + uses: frequenz-floss/gh-action-setup-git@v1.0.0 - name: Fetch sources uses: actions/checkout@v4 with: submodules: true - - name: Set up Python - uses: actions/setup-python@v5 + - name: Setup Python + uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 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 + dependencies: build - name: Build the source and binary distribution run: python -m build @@ -256,12 +100,27 @@ jobs: if-no-files-found: error test-installation: - name: Test package installation in different architectures + name: Test package installation needs: ["build"] - runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + arch: + - amd64 + - arm + os: + - ubuntu-24.04 + python: + - "3.11" + - "3.12" + runs-on: ${{ matrix.os }}${{ matrix.arch != 'amd64' && format('-{0}', matrix.arch) || '' }} + steps: - - name: Fetch sources - uses: actions/checkout@v4 + - name: Setup Git + uses: frequenz-floss/gh-action-setup-git@v1.0.0 + + - name: Print environment (debug) + run: env - name: Download package uses: actions/download-artifact@v4 @@ -269,50 +128,64 @@ jobs: name: dist-packages path: dist - - name: Make Git credentials available to docker + # This is necessary for the `pip` caching in the setup-python action to work + - name: Fetch the pyproject.toml file for this action hash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + REF: ${{ github.sha }} run: | - touch ~/.git-credentials # Ensure the file exists - cp ~/.git-credentials git-credentials || true + set -ux + gh api \ + -X GET \ + -H "Accept: application/vnd.github.raw" \ + "/repos/$REPO/contents/pyproject.toml?ref=$REF" \ + > pyproject.toml - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Setup Python + uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + with: + python-version: ${{ matrix.python }} + dependencies: dist/*.whl - - name: Set up docker-buildx - uses: docker/setup-buildx-action@v3 + - name: Print installed packages (debug) + run: python -m pip freeze - - name: Test Installation - uses: docker/build-push-action@v6 - with: - context: . - file: .github/containers/test-installation/Dockerfile - platforms: linux/amd64,linux/arm64 - tags: localhost/test-installation - push: false + # This job runs if all the `test-installation` 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. + test-installation-all: + # The job name should match the name of the `test-installation` job. + name: Test package installation + needs: ["test-installation"] + # We skip this job only if test-installation was also skipped + if: always() && needs.test-installation.result != 'skipped' + runs-on: ubuntu-24.04 + env: + DEPS_RESULT: ${{ needs.test-installation.result }} + steps: + - name: Check matrix job result + run: test "$DEPS_RESULT" = "success" test-docs: name: Test documentation website generation if: github.event_name != 'push' - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: + - name: Setup Git + uses: frequenz-floss/gh-action-setup-git@v1.0.0 + - 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@v5 + - name: Setup Python + uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 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 + dependencies: .[dev-mkdocs] - name: Generate the documentation env: @@ -330,31 +203,25 @@ jobs: publish-docs: name: Publish documentation website to GitHub pages - needs: ["nox-all", "nox-cross-arch-all", "test-installation"] + needs: ["nox-all", "test-installation-all"] if: github.event_name == 'push' - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 permissions: contents: write steps: + - name: Setup Git + uses: frequenz-floss/gh-action-setup-git@v1.0.0 + - 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@v5 + - name: Setup Python + uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 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 + dependencies: .[dev-mkdocs] - name: Calculate and check version id: mike-version @@ -409,7 +276,7 @@ jobs: # discussions to create the release announcement in the discussion forums contents: write discussions: write - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Download distribution files uses: actions/download-artifact@v4 @@ -451,18 +318,12 @@ jobs: publish-to-pypi: name: Publish packages to PyPI needs: ["create-github-release"] - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 permissions: # For trusted publishing. See: # https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ id-token: write steps: - - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v0.x.x - - - name: Fetch sources - uses: actions/checkout@v4 - - name: Download distribution files uses: actions/download-artifact@v4 with: diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c3baf309..085f031f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,14 +1,17 @@ # Frequenz Microgrid API Client Release Notes +## Summary + + + ## Upgrading -- Now component and microgrid IDs are wrapped in new classes: `ComponentId` and `MicrogridId` respectively. +- Some minimum versions of dependencies have been bumped to support Python 3.12. You might also need to bump these dependencies in your project. + +## New Features - These classes provide type safety and prevent accidental errors by: + - - Making it impossible to mix up microgrid and component IDs (equality comparisons between different ID types always return false). - - Preventing accidental math operations on IDs. - - Providing clear string representations for debugging (MID42, CID42). - - Ensuring proper hash behavior in collections. +## Bug Fixes - To migrate you just need to wrap your `int` IDs with the appropriate class: `0` -> `ComponentId(0)` / `MicrogridId(0)`. + diff --git a/pyproject.toml b/pyproject.toml index 4ce0406c..fce2cc96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ requires = [ "setuptools == 78.1.0", "setuptools_scm[toml] == 8.2.0", - "frequenz-repo-config[lib] == 0.12.3", + "frequenz-repo-config[lib] == 0.13.1", ] build-backend = "setuptools.build_meta" @@ -39,10 +39,10 @@ dependencies = [ "frequenz-api-microgrid >= 0.15.3, < 0.16.0", "frequenz-channels >= 1.0.0-rc1, < 2.0.0", "frequenz-client-base >= 0.8.0, < 0.10.0", - "grpcio >= 1.54.2, < 2", + "grpcio >= 1.59.0, < 2", "protobuf >= 4.21.6, < 7", "timezonefinder >= 6.2.0, < 7", - "typing-extensions >= 4.5.0, < 5", + "typing-extensions >= 4.6.0, < 5", ] dynamic = ["version"] @@ -69,7 +69,7 @@ dev-mkdocs = [ "mkdocs-material == 9.6.9", "mkdocstrings[python] == 0.29.0", "mkdocstrings-python == 1.16.8", - "frequenz-repo-config[lib] == 0.12.3", + "frequenz-repo-config[lib] == 0.13.1", ] dev-mypy = [ "mypy == 1.15.0", @@ -79,7 +79,7 @@ dev-mypy = [ # For checking the noxfile, docs/ script, and tests "frequenz-client-microgrid[dev-mkdocs,dev-noxfile,dev-pytest]", ] -dev-noxfile = ["nox == 2025.2.9", "frequenz-repo-config[lib] == 0.12.3"] +dev-noxfile = ["nox == 2025.2.9", "frequenz-repo-config[lib] == 0.13.1"] dev-pylint = [ "pylint == 3.3.6", # For checking the noxfile, docs/ script, and tests @@ -87,7 +87,7 @@ dev-pylint = [ ] dev-pytest = [ "pytest == 8.3.5", - "frequenz-repo-config[extra-lint-examples] == 0.12.3", + "frequenz-repo-config[extra-lint-examples] == 0.13.1", "pytest-mock == 3.14.0", "pytest-asyncio == 0.26.0", "async-solipsism == 0.7",