Skip to content

Commit ccdb1c2

Browse files
committed
Implement market metering client for ReceiveMarketLocationSamplesStream
- Add MarketMeteringApiClient with stream_samples() and stream() methods - Define types: MarketLocationSample, MarketLocationSeries, MarketLocationRef, etc. - Add CLI with 'stream' command and interactive REPL mode - Restructure package to frequenz.client.marketmetering (matching dispatch pattern) Signed-off-by: Mathias L. Baumann <[email protected]>
1 parent c9e4370 commit ccdb1c2

File tree

14 files changed

+1558
-118
lines changed

14 files changed

+1558
-118
lines changed

README.md

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,123 @@
1-
# Frequenz Client Marketmetering Library
1+
# Frequenz Market Metering Client
22

33
[![Build Status](https://github.com/frequenz-floss/frequenz-client-marketmetering-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/frequenz-floss/frequenz-client-marketmetering-python/actions/workflows/ci.yaml)
44
[![PyPI Package](https://img.shields.io/pypi/v/frequenz-client-marketmetering)](https://pypi.org/project/frequenz-client-marketmetering/)
55
[![Docs](https://img.shields.io/badge/docs-latest-informational)](https://frequenz-floss.github.io/frequenz-client-marketmetering-python/)
66

77
## Introduction
88

9-
Market Metering API client for Python
9+
A Python client for the Frequenz Market Metering API, providing access to
10+
historical and real-time metering samples from Market Locations across various
11+
energy markets (Germany, UK, US, Australia, etc.).
1012

11-
TODO(cookiecutter): Improve the README file
13+
## Features
1214

13-
## Supported Platforms
15+
- Stream metering samples from Market Locations
16+
- Support for multiple market identifier types (MaLo-ID, MPAN, ESI-ID, NMI)
17+
- Filtering by energy flow direction (import/export)
18+
- Multiple metric types (active energy, active power, reactive energy/power)
19+
- Optional resampling for time-series aggregation
20+
- CLI tool for quick access to metering data
21+
22+
## Installation
23+
24+
```bash
25+
pip install frequenz-client-marketmetering
26+
```
27+
28+
For CLI support:
29+
30+
```bash
31+
pip install "frequenz-client-marketmetering[cli]"
32+
```
33+
34+
## Quick Start
35+
36+
### Python API
37+
38+
```python
39+
from datetime import datetime, timezone
40+
from frequenz.client.marketmetering import MarketMeteringApiClient
41+
from frequenz.client.marketmetering.types import (
42+
EnergyFlowDirection,
43+
MarketLocationId,
44+
MarketLocationIdType,
45+
MarketLocationRef,
46+
MetricType,
47+
)
48+
49+
# Create client
50+
client = MarketMeteringApiClient(
51+
server_url="grpc://marketmetering.example.com",
52+
auth_key="your-api-key",
53+
)
54+
55+
# Define a Market Location (e.g., German MaLo)
56+
market_location = MarketLocationRef(
57+
enterprise_id=42,
58+
market_location_id=MarketLocationId(
59+
value="DE01234567890",
60+
type=MarketLocationIdType.MALO_ID,
61+
),
62+
)
63+
64+
# Stream metering samples
65+
async for series in client.stream_samples(
66+
market_locations=[market_location],
67+
directions=[EnergyFlowDirection.IMPORT],
68+
metric_types=[MetricType.ACTIVE_ENERGY],
69+
start_time=datetime(2025, 1, 1, tzinfo=timezone.utc),
70+
):
71+
for sample in series.samples:
72+
print(f"{sample.sample_time}: {sample.value} {series.metric_unit.name}")
73+
```
74+
75+
### CLI
1476

15-
The following platforms are officially supported (tested):
77+
```bash
78+
# Set environment variables
79+
export MARKETMETERING_API_URL="grpc://marketmetering.example.com"
80+
export MARKETMETERING_API_AUTH_KEY="your-api-key"
1681

17-
- **Python:** 3.11
18-
- **Operating System:** Ubuntu Linux 20.04
82+
# Stream samples from a German Market Location
83+
marketmetering-cli stream 42:DE01234567890:MALO_ID
84+
85+
# Stream with specific options
86+
marketmetering-cli stream 42:DE01234567890:MALO_ID \
87+
--direction IMPORT \
88+
--metric ACTIVE_ENERGY \
89+
--start-time "2025-01-01T00:00:00" \
90+
--resolution MIN_15
91+
```
92+
93+
## Market Location Types
94+
95+
The client supports various market identifier types:
96+
97+
| Type | Market | Example |
98+
|------|--------|---------|
99+
| `MALO_ID` | Germany | `DE01234567890` |
100+
| `MPAN` | United Kingdom | `18106612345678901234` |
101+
| `ESI_ID` | US (ERCOT) | `10443720000012345` |
102+
| `NMI` | Australia | `NEM1203456A` |
103+
104+
## Supported Platforms
105+
106+
- **Python:** 3.11+
107+
- **Operating System:** Ubuntu Linux 20.04+
19108
- **Architectures:** amd64, arm64
20109

110+
## Documentation
111+
112+
For detailed documentation, please visit:
113+
https://frequenz-floss.github.io/frequenz-client-marketmetering-python/
114+
21115
## Contributing
22116

23117
If you want to know how to build this project and contribute to it, please
24118
check out the [Contributing Guide](CONTRIBUTING.md).
119+
120+
## License
121+
122+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
123+
for details.

RELEASE_NOTES.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
# Frequenz Client Marketmetering Library Release Notes
1+
# Frequenz Market Metering Client Release Notes
22

33
## Summary
44

5-
<!-- Here goes a general summary of what this release is about -->
6-
7-
## Upgrading
8-
9-
<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
5+
Initial release of the Frequenz Market Metering API client for Python.
106

117
## New Features
128

13-
<!-- Here goes the main new features and examples or instructions on how to use them -->
14-
15-
## Bug Fixes
16-
17-
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
9+
- `MarketMeteringApiClient`: Main client class for connecting to the Market Metering service
10+
- `stream_samples()`: Async iterator for streaming metering samples from Market Locations
11+
- `stream()`: Channel-based receiver for streaming with automatic reconnection
12+
- CLI tool (`marketmetering-cli`) for quick access to metering data
13+
- Support for multiple market identifier types:
14+
- MaLo-ID (Germany)
15+
- MPAN (United Kingdom)
16+
- ESI-ID (US ERCOT)
17+
- NMI (Australia)
18+
- Filtering by energy flow direction (IMPORT/EXPORT)
19+
- Multiple metric types (active energy, active power, reactive energy/power)
20+
- Optional resampling for time-series aggregation

pyproject.toml

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
[build-system]
55
requires = [
6-
"setuptools == 75.8.0",
7-
"setuptools_scm[toml] == 8.1.0",
8-
"frequenz-repo-config[lib] == 0.13.1",
6+
"setuptools == 80.9.0",
7+
"setuptools_scm[toml] == 9.2.2",
8+
"frequenz-repo-config[lib] == 0.13.8",
99
]
1010
build-backend = "setuptools.build_meta"
1111

@@ -14,8 +14,19 @@ name = "frequenz-client-marketmetering"
1414
description = "Market Metering API client for Python"
1515
readme = "README.md"
1616
license = { text = "MIT" }
17-
keywords = ["frequenz", "python", "lib", "library", "client-marketmetering", "marketmetering", "client", "api", "grpc"]
18-
# TODO(cookiecutter): Remove and add more classifiers if appropriate
17+
keywords = [
18+
"frequenz",
19+
"python",
20+
"lib",
21+
"library",
22+
"marketmetering-client",
23+
"marketmetering",
24+
"client",
25+
"api",
26+
"grpc",
27+
"energy",
28+
"metering",
29+
]
1930
classifiers = [
2031
"Development Status :: 3 - Alpha",
2132
"Intended Audience :: Developers",
@@ -26,60 +37,69 @@ classifiers = [
2637
"Typing :: Typed",
2738
]
2839
requires-python = ">= 3.11, < 4"
29-
# TODO(cookiecutter): Remove and add more dependencies if appropriate
3040
dependencies = [
31-
"typing-extensions >= 4.14.1, < 5",
41+
"typing-extensions >= 4.13.0, < 5",
42+
"frequenz-api-market-metering >= 0.1.0, < 1",
43+
"frequenz-client-base >= 0.11.0, < 0.12.0",
44+
"grpcio >= 1.72.1, < 2",
3245
]
3346
dynamic = ["version"]
3447

48+
[project.scripts]
49+
marketmetering-cli = "frequenz.client.marketmetering.__main__:main"
50+
3551
[[project.authors]]
3652
name = "Frequenz Energy-as-a-Service GmbH"
3753
3854

39-
# TODO(cookiecutter): Remove and add more optional dependencies if appropriate
4055
[project.optional-dependencies]
56+
cli = [
57+
"asyncclick == 8.3.0.7",
58+
"prompt-toolkit == 3.0.52",
59+
]
60+
4161
dev-flake8 = [
4262
"flake8 == 7.3.0",
4363
"flake8-docstrings == 1.7.0",
4464
"flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml
45-
"pydoclint == 0.6.10",
65+
"pydoclint == 0.7.6",
4666
"pydocstyle == 6.3.0",
4767
]
48-
dev-formatting = ["black == 25.1.0", "isort == 6.0.0"]
68+
dev-formatting = ["black == 25.9.0", "isort == 6.0.1"]
4969
dev-mkdocs = [
50-
"Markdown == 3.8.2",
51-
"black == 25.1.0",
70+
"black == 25.9.0",
71+
"Markdown == 3.9",
5272
"mike == 2.1.3",
5373
"mkdocs-gen-files == 0.5.0",
5474
"mkdocs-literate-nav == 0.6.2",
55-
"mkdocs-macros-plugin == 1.3.9",
56-
"mkdocs-material == 9.6.18",
57-
"mkdocstrings[python] == 1.0.0",
58-
"mkdocstrings-python == 2.0.1",
59-
"frequenz-repo-config[lib] == 0.13.1",
75+
"frequenz-api-market-metering >= 0.1.0, < 1",
76+
"mkdocs-macros-plugin == 1.4.1",
77+
"mkdocs-material == 9.6.23",
78+
"mkdocstrings[python] == 0.30.1",
79+
"frequenz-repo-config[lib] == 0.13.8",
6080
]
6181
dev-mypy = [
62-
"mypy == 1.9.0",
63-
"types-Markdown == 3.8.0.20250809",
82+
"mypy == 1.18.2",
83+
"types-Markdown == 3.9.0.20250906",
6484
# For checking the noxfile, docs/ script, and tests
65-
"frequenz-client-marketmetering[dev-mkdocs,dev-noxfile,dev-pytest]",
66-
]
67-
dev-noxfile = [
68-
"nox == 2025.5.1",
69-
"frequenz-repo-config[lib] == 0.13.1",
85+
"frequenz-client-marketmetering[cli,dev-mkdocs,dev-noxfile,dev-pytest]",
86+
"grpc-stubs == 1.53.0.6",
87+
"types-protobuf == 6.32.1.20250918",
7088
]
89+
dev-noxfile = ["nox == 2025.10.16", "frequenz-repo-config[lib] == 0.13.8"]
7190
dev-pylint = [
72-
# dev-pytest already defines a dependency to pylint because of the examples
91+
"pylint == 3.3.9",
7392
# For checking the noxfile, docs/ script, and tests
74-
"frequenz-client-marketmetering[dev-mkdocs,dev-noxfile,dev-pytest]",
93+
"frequenz-client-marketmetering[cli,dev-mkdocs,dev-noxfile,dev-pytest]",
94+
"frequenz-api-market-metering >= 0.1.0, < 1",
7595
]
7696
dev-pytest = [
77-
"pytest == 8.4.1",
78-
"pylint == 3.3.8", # We need this to check for the examples
79-
"frequenz-repo-config[extra-lint-examples] == 0.13.1",
80-
"pytest-mock == 3.14.0",
81-
"pytest-asyncio == 1.1.0",
97+
"pytest == 8.4.2",
98+
"frequenz-repo-config[extra-lint-examples] == 0.13.8",
99+
"pytest-mock == 3.15.1",
100+
"pytest-asyncio == 1.2.0",
82101
"async-solipsism == 0.8",
102+
"frequenz-client-marketmetering[cli]",
83103
]
84104
dev = [
85105
"frequenz-client-marketmetering[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
@@ -118,7 +138,6 @@ check-yield-types = false
118138
arg-type-hints-in-docstring = false
119139
arg-type-hints-in-signature = true
120140
allow-init-docstring = true
121-
check-class-attributes = false
122141

123142
[tool.pylint.similarities]
124143
ignore-comments = ['yes']
@@ -133,34 +152,33 @@ disable = [
133152
# disabled because it conflicts with isort
134153
"wrong-import-order",
135154
"ungrouped-imports",
136-
# Checked by mypy (and pylint is very flaky checking these)
155+
# pylint's unsubscriptable check is buggy and is not needed because
156+
# it is a type-check, for which we already have mypy.
137157
"unsubscriptable-object",
158+
# Checked by mypy
138159
"no-member",
139-
"no-name-in-module",
140160
"possibly-used-before-assignment",
161+
"no-name-in-module",
141162
# Checked by flake8
142-
"f-string-without-interpolation",
163+
"redefined-outer-name",
164+
"unused-import",
143165
"line-too-long",
144-
"missing-function-docstring",
145166
"redefined-outer-name",
146167
"unnecessary-lambda-assignment",
147168
"unused-import",
148169
"unused-variable",
149170
]
150171

151172
[tool.pytest.ini_options]
152-
addopts = "-vv"
153173
filterwarnings = [
154174
"error",
155175
"once::DeprecationWarning",
156176
"once::PendingDeprecationWarning",
157-
# We ignore warnings about protobuf gencode version being one version older
158-
# than the current version, as this is supported by protobuf, and we expect to
159-
# have such cases. If we go too far, we will get a proper error anyways.
160-
# We use a raw string (single quotes) to avoid the need to escape special
161-
# characters as this is a regex.
177+
# We use a raw string (single quote) to avoid the need to escape special
178+
# chars as this is a regex
162179
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
163180
]
181+
addopts = "-vv"
164182
testpaths = ["tests", "src"]
165183
asyncio_mode = "auto"
166184
asyncio_default_fixture_loop_scope = "function"
@@ -175,11 +193,11 @@ namespace_packages = true
175193
# used but getting the original ignored error when removing the type: ignore.
176194
# See for example: https://github.com/python/mypy/issues/2960
177195
#no_incremental = true
178-
packages = ["frequenz.client_marketmetering"]
196+
packages = ["frequenz.client.marketmetering"]
179197
strict = true
180198

181199
[[tool.mypy.overrides]]
182-
module = ["mkdocs_macros.*", "sybil", "sybil.*"]
200+
module = ["mkdocs_macros.*"]
183201
ignore_missing_imports = true
184202

185203
[tool.setuptools_scm]

src/frequenz/client/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Pytest configuration for frequenz.client namespace."""

0 commit comments

Comments
 (0)