From b6f51fa11dbe93b263cac4c53b013e39a8e03602 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 23 May 2023 14:36:13 +0200 Subject: [PATCH 1/6] Reformat `pyproject.toml` using `taplo` Signed-off-by: Leandro Lucarella --- pyproject.toml | 155 +++++++++++++++++++++++-------------------------- 1 file changed, 72 insertions(+), 83 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f001a4c32..547bf20a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [build-system] requires = [ - "setuptools >= 65.3.0, < 66", - "setuptools_scm[toml] >= 7.0.5, < 8", - "wheel" + "setuptools >= 65.3.0, < 66", + "setuptools_scm[toml] >= 7.0.5, < 8", + "wheel", ] build-backend = "setuptools.build_meta" @@ -11,87 +11,79 @@ name = "frequenz-sdk" description = "Frequenz Python SDK" readme = "README.md" license = { text = "MIT" } -keywords = [ "frequenz", "sdk", "microgrid", "actor" ] +keywords = ["frequenz", "sdk", "microgrid", "actor"] classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Software Development :: Libraries", + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Software Development :: Libraries", ] requires-python = ">= 3.8, < 4" dependencies = [ - "frequenz-api-microgrid >= 0.11.0, < 0.12.0", - "frequenz-channels >= 0.14.0, < 0.15.0", - "google-api-python-client >= 2.71, < 3", - "grpcio >= 1.51.1, < 2", - "grpcio-tools >= 1.51.1, < 2", - "networkx >= 2.8, < 4", - "numpy >= 1.24.2, < 2", - "protobuf >= 4.21.6, < 5", - "pydantic >= 1.9", - "toml >= 0.10", - "tqdm >= 4.38.0, < 5", - "typing_extensions >= 4.4.0, < 5", - "watchfiles >= 0.15.0", + "frequenz-api-microgrid >= 0.11.0, < 0.12.0", + "frequenz-channels >= 0.14.0, < 0.15.0", + "google-api-python-client >= 2.71, < 3", + "grpcio >= 1.51.1, < 2", + "grpcio-tools >= 1.51.1, < 2", + "networkx >= 2.8, < 4", + "numpy >= 1.24.2, < 2", + "protobuf >= 4.21.6, < 5", + "pydantic >= 1.9", + "toml >= 0.10", + "tqdm >= 4.38.0, < 5", + "typing_extensions >= 4.4.0, < 5", + "watchfiles >= 0.15.0", ] -dynamic = [ "version" ] +dynamic = ["version"] [[project.authors]] -name ="Frequenz Energy-as-a-Service GmbH" +name = "Frequenz Energy-as-a-Service GmbH" email = "floss@frequenz.com" [project.optional-dependencies] docs-gen = [ - "mike == 1.1.2", - "mkdocs-gen-files == 0.5.0", - "mkdocs-literate-nav == 0.6.0", - "mkdocs-material == 9.1.14", - "mkdocs-section-index == 0.3.5", - "mkdocstrings[python] == 0.21.2", + "mike == 1.1.2", + "mkdocs-gen-files == 0.5.0", + "mkdocs-literate-nav == 0.6.0", + "mkdocs-material == 9.1.14", + "mkdocs-section-index == 0.3.5", + "mkdocstrings[python] == 0.21.2", ] docs-lint = [ - "pydocstyle == 6.3.0", - "darglint == 1.8.1", - "tomli == 2.0.1", # Needed by pydocstyle to read pyproject.toml -] -format = [ - "black == 23.3.0", - "isort == 5.12.0", -] -nox = [ - "nox == 2023.4.22", - "toml == 0.10.2", + "pydocstyle == 6.3.0", + "darglint == 1.8.1", + "tomli == 2.0.1", # Needed by pydocstyle to read pyproject.toml ] +format = ["black == 23.3.0", "isort == 5.12.0"] +nox = ["nox == 2023.4.22", "toml == 0.10.2"] pytest = [ - "pytest == 7.3.1", - "pytest-cov == 4.0.0", - "pytest-mock == 3.10.0", - "pytest-asyncio == 0.21.0", - "time-machine == 2.9.0", - "async-solipsism == 0.5", - # For checking docstring code examples - "sybil == 5.0.2", - "pylint == 2.17.4", + "pytest == 7.3.1", + "pytest-cov == 4.0.0", + "pytest-mock == 3.10.0", + "pytest-asyncio == 0.21.0", + "time-machine == 2.9.0", + "async-solipsism == 0.5", + # For checking docstring code examples + "sybil == 5.0.2", + "pylint == 2.17.4", ] mypy = [ - "mypy == 1.3.0", - "grpc-stubs == 1.24.12", # This dependency introduces breaking changes in patch releases - # For checking the noxfile, docs/ script, and tests - "frequenz-sdk[docs-gen,nox,pytest]", + "mypy == 1.3.0", + "grpc-stubs == 1.24.12", # This dependency introduces breaking changes in patch releases + # For checking the noxfile, docs/ script, and tests + "frequenz-sdk[docs-gen,nox,pytest]", ] pylint = [ - "pylint == 2.17.4", - # For checking the noxfile, docs/ script, and tests - "frequenz-sdk[docs-gen,nox,pytest]", -] -dev = [ - "frequenz-sdk[docs-gen,docs-lint,format,nox,pytest,mypy,pylint]", + "pylint == 2.17.4", + # For checking the noxfile, docs/ script, and tests + "frequenz-sdk[docs-gen,nox,pytest]", ] +dev = ["frequenz-sdk[docs-gen,docs-lint,format,nox,pytest,mypy,pylint]"] [project.urls] Changelog = "https://github.com/frequenz-floss/frequenz-sdk-python/releases" @@ -111,21 +103,21 @@ target-version = ['py38'] include = '\.pyi?$' [tool.pylint.similarities] -ignore-comments=['yes'] -ignore-docstrings=['yes'] -ignore-imports=['no'] -min-similarity-lines=40 +ignore-comments = ['yes'] +ignore-docstrings = ['yes'] +ignore-imports = ['no'] +min-similarity-lines = 40 [tool.pylint.messages_control] disable = [ - "too-few-public-methods", - # disabled because it conflicts with isort - "wrong-import-order", - "ungrouped-imports" + "too-few-public-methods", + # disabled because it conflicts with isort + "wrong-import-order", + "ungrouped-imports", ] [tool.pylint.design] -max-attributes=12 +max-attributes = 12 [tool.isort] profile = "black" @@ -134,21 +126,18 @@ src_paths = ["src", "examples", "tests"] [tool.pytest.ini_options] asyncio_mode = "auto" -required_plugins = [ "pytest-asyncio", "pytest-mock" ] +required_plugins = ["pytest-asyncio", "pytest-mock"] [[tool.mypy.overrides]] module = [ - "grpc.aio", - "grpc.aio.*", - # There is a stubs package available, but it's not working: - # https://github.com/eggplants/networkx-stubs/issues/1 - "networkx", + "grpc.aio", + "grpc.aio.*", + # There is a stubs package available, but it's not working: + # https://github.com/eggplants/networkx-stubs/issues/1 + "networkx", ] ignore_missing_imports = true [[tool.mypy.overrides]] -module = [ - "async_solipsism", - "async_solipsism.*", -] +module = ["async_solipsism", "async_solipsism.*"] ignore_missing_imports = true From 00c3486e4d9257ef1949b53a70df5e264b6a7f6c Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 23 May 2023 14:40:21 +0200 Subject: [PATCH 2/6] Drop support for Python versions older than 3.11 Also remove version-specific classifiers in `pyproject.toml` as we are already specifying the supported Python versions in `requires-python`. Signed-off-by: Leandro Lucarella --- .github/workflows/ci.yaml | 5 +---- README.md | 3 +-- RELEASE_NOTES.md | 4 +++- pyproject.toml | 5 +---- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4f017b502..38bd50a49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,9 +20,6 @@ jobs: os: - ubuntu-20.04 python: - - "3.8" - - "3.9" - - "3.10" - "3.11" runs-on: ${{ matrix.os }} @@ -60,7 +57,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: '3.11' - name: Install build dependencies run: | diff --git a/README.md b/README.md index d55dd8cd0..ff016e1c7 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,7 @@ A development kit to interact with the Frequenz development platform. ## Supported Python versions -* For x86_64 Python 3.8 - 3.11 are supported (tested). -* For arm64 only Python 3.8 is supported (due to some dependencies that only support 3.8). +* Only Python 3.11 is fully supported (tested). ## Contributing diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d07cf4ec2..37094a891 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,10 +2,12 @@ ## Summary - +This release drops support for Python versions older than 3.11. ## Upgrading +* Now Python 3.11 is the minimum supported version. All users must upgrade to Python 3.11 (including virtual environments used for development). + * Now `float` is used everywhere for representing power (before power metrics were `float` but setting power was done using `int`). * `frequenz.sdk.actor.power_distributing`: the `power` attribute of the `Request` class has been updated from `int` to a `float`. * `frequenz.sdk.microgrid`: the `set_power()` method of both the `MicrogridApiClient` and `MicrogridGrpcClient` classes now expect a `float` value for the `power_w` parameter instead of `int`. diff --git a/pyproject.toml b/pyproject.toml index 547bf20a2..7d34521e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,12 +18,9 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Topic :: Software Development :: Libraries", ] -requires-python = ">= 3.8, < 4" +requires-python = ">= 3.11, < 4" dependencies = [ "frequenz-api-microgrid >= 0.11.0, < 0.12.0", "frequenz-channels >= 0.14.0, < 0.15.0", From 910399980b60a700b65253a7ec13c4eb055c0574 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 23 May 2023 14:55:06 +0200 Subject: [PATCH 3/6] Improve typing annotations We now also make the Sample3Phase.map() method more generic, to return the same type as self. Signed-off-by: Leandro Lucarella --- src/frequenz/sdk/timeseries/_base_types.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/frequenz/sdk/timeseries/_base_types.py b/src/frequenz/sdk/timeseries/_base_types.py index c5cc8d2a6..7ff4163f3 100644 --- a/src/frequenz/sdk/timeseries/_base_types.py +++ b/src/frequenz/sdk/timeseries/_base_types.py @@ -3,12 +3,10 @@ """Timeseries basic types.""" -from __future__ import annotations - import functools from dataclasses import dataclass, field from datetime import datetime, timezone -from typing import Callable, Iterator, Optional, overload +from typing import Callable, Iterator, Self, overload UNIX_EPOCH = datetime.fromtimestamp(0.0, tz=timezone.utc) """The UNIX epoch (in UTC).""" @@ -30,7 +28,7 @@ class Sample: timestamp: datetime = field(compare=True) """The time when this sample was generated.""" - value: Optional[float] = field(compare=False, default=None) + value: float | None = field(compare=False, default=None) """The value of this sample.""" @@ -46,13 +44,13 @@ class Sample3Phase: timestamp: datetime """The time when this sample was generated.""" - value_p1: Optional[float] + value_p1: float | None """The value of the 1st phase in this sample.""" - value_p2: Optional[float] + value_p2: float | None """The value of the 2nd phase in this sample.""" - value_p3: Optional[float] + value_p3: float | None """The value of the 3rd phase in this sample.""" def __iter__(self) -> Iterator[float | None]: @@ -117,7 +115,7 @@ def min(self, default: float | None = None) -> float | None: def map( self, function: Callable[[float], float], default: float | None = None - ) -> Sample3Phase: + ) -> Self: """Apply the given function on each of the phase values and return the result. If a phase value is `None`, replace it with `default` instead. @@ -127,10 +125,10 @@ def map( default: The value to apply if a phase value is `None`. Returns: - A new `Sample3Phase` instance, with the given function applied on values - for each of the phases. + A new instance, with the given function applied on values for each of the + phases. """ - return Sample3Phase( + return self.__class__( timestamp=self.timestamp, value_p1=default if self.value_p1 is None else function(self.value_p1), value_p2=default if self.value_p2 is None else function(self.value_p2), From a6945e8f9fc046c47b1122a7c7a9fe24e667ef7f Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 23 May 2023 15:01:23 +0200 Subject: [PATCH 4/6] Remove Sample comparison hack The Sample object implemented comparisons by only comparing timestamps. This was only done to cope with a limitation of `bisect()` before Python 3.10 (where no key argument could be passed). Now that we are using Python 3.11 as the minimal version, we can remove that hack and make Samples compare using both the datetime and the value as one would expect. Signed-off-by: Leandro Lucarella --- RELEASE_NOTES.md | 2 ++ src/frequenz/sdk/timeseries/_base_types.py | 10 +++------- src/frequenz/sdk/timeseries/_resampling.py | 11 ++++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 37094a891..4fee8cb94 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -14,6 +14,8 @@ This release drops support for Python versions older than 3.11. * The `LogicalMeter` no longer takes a `component_graph` parameter. +* Now `frequenz.sdk.timeseries.Sample` uses a more sensible comparison. Before this release `Sample`s were compared only based on the `timestamp`. This was due to a limitation in Python versions earlier than 3.10. Now that the minimum supported version is 3.11 this hack is not needed anymore and `Sample`s are compared using both `timestamp` and `value` as most people probably expects. + ## New Features diff --git a/src/frequenz/sdk/timeseries/_base_types.py b/src/frequenz/sdk/timeseries/_base_types.py index 7ff4163f3..46f0e7c4e 100644 --- a/src/frequenz/sdk/timeseries/_base_types.py +++ b/src/frequenz/sdk/timeseries/_base_types.py @@ -4,7 +4,7 @@ """Timeseries basic types.""" import functools -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import datetime, timezone from typing import Callable, Iterator, Self, overload @@ -12,10 +12,6 @@ """The UNIX epoch (in UTC).""" -# Ordering by timestamp is a bit arbitrary, and it is not always what might be -# wanted. We are using this order now because usually we need to do binary -# searches on sequences of samples, and the Python `bisect` module doesn't -# support providing a key until Python 3.10. @dataclass(frozen=True, order=True) class Sample: """A measurement taken at a particular point in time. @@ -25,10 +21,10 @@ class Sample: coherent view on a group of component metrics for a particular timestamp. """ - timestamp: datetime = field(compare=True) + timestamp: datetime """The time when this sample was generated.""" - value: float | None = field(compare=False, default=None) + value: float | None = None """The value of this sample.""" diff --git a/src/frequenz/sdk/timeseries/_resampling.py b/src/frequenz/sdk/timeseries/_resampling.py index 24646395c..a12e9e658 100644 --- a/src/frequenz/sdk/timeseries/_resampling.py +++ b/src/frequenz/sdk/timeseries/_resampling.py @@ -705,11 +705,12 @@ def resample(self, timestamp: datetime) -> Sample: ) minimum_relevant_timestamp = timestamp - period * conf.max_data_age_in_periods - # We need to pass a dummy Sample to bisect because it only support - # specifying a key extraction function in Python 3.10, so we need to - # compare samples at the moment. - min_index = bisect(self._buffer, Sample(minimum_relevant_timestamp, None)) - max_index = bisect(self._buffer, Sample(timestamp, None)) + min_index = bisect( + self._buffer, + minimum_relevant_timestamp, + key=lambda s: s.timestamp, + ) + max_index = bisect(self._buffer, timestamp, key=lambda s: s.timestamp) # Using itertools for slicing doesn't look very efficient, but # experiments with a custom (ring) buffer that can slice showed that # it is not that bad. See: From 650d4987924aba5e3b3766ff52ef85f75590fe3f Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 23 May 2023 16:14:20 +0200 Subject: [PATCH 5/6] Add missing default Python version to workflow Signed-off-by: Leandro Lucarella --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 38bd50a49..a79aaa203 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,6 +11,8 @@ on: - 'gh-readonly-queue/**' workflow_dispatch: +env: + DEFAULT_PYTHON_VERSION: '3.11' jobs: test: From b21841d10446b63b863fb7969063a6a2c8678a5f Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 23 May 2023 16:16:11 +0200 Subject: [PATCH 6/6] Enable caching of pip packages in workflow Signed-off-by: Leandro Lucarella --- .github/workflows/ci.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a79aaa203..a50d0591e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,6 +33,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + cache: 'pip' - uses: actions/cache@v3 with: @@ -60,6 +61,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.11' + cache: 'pip' - name: Install build dependencies run: | @@ -90,6 +92,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: 'pip' - name: Install build dependencies run: | @@ -167,6 +170,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: 'pip' - name: Install build dependencies if: steps.mike-metadata.outputs.version