Skip to content

Commit 066a8bd

Browse files
authored
Merge pull request #21 from eduardiazf/feat/initial-client
Implement initial Assets API client with CLI support
1 parent 0218917 commit 066a8bd

File tree

16 files changed

+924
-44
lines changed

16 files changed

+924
-44
lines changed

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# Fallback owner.
55
# These are the default owners for everything in the repo, unless a later match
66
# takes precedence.
7-
* @frequenz-floss/api-assets-team
7+
* @frequenz-floss/api-assets-team @frequenz-floss/python-sdk-team

RELEASE_NOTES.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,43 @@
22

33
## Summary
44

5-
<!-- Here goes a general summary of what this release is about -->
5+
This release introduces a complete Assets API client with CLI support for interacting with Frequenz microgrid assets, including comprehensive error handling and type safety.
66

77
## Upgrading
88

9-
<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
9+
**Breaking Changes:**
10+
11+
- Added new required dependencies: `frequenz-api-assets`, `frequenz-api-common`, `frequenz-client-base`, `grpcio`
12+
13+
**CLI Support:**
14+
Install with `pip install "frequenz-client-assets[cli]"` for command-line functionality.
1015

1116
## New Features
1217

13-
<!-- Here goes the main new features and examples or instructions on how to use them -->
18+
**Assets API Client:**
19+
20+
- Complete gRPC client for Frequenz Assets API
21+
- Extends `BaseApiClient` for authentication and connection management
22+
- `get_microgrid_details()` method for retrieving microgrid information
23+
24+
**Command-Line Interface:**
25+
26+
- `python -m frequenz.client.assets microgrid <id>` command
27+
- Environment variable support for API credentials
28+
- JSON output formatting
29+
30+
**Type System:**
31+
32+
- `Microgrid`, `DeliveryArea`, and `Location` data classes
33+
- Protobuf integration with proper type safety
34+
35+
**Exception Handling:**
36+
37+
- Custom exception hierarchy (`AssetsApiError`, `NotFoundError`, `AuthenticationError`, `ServiceUnavailableError`)
38+
- JSON serialization support for error responses
1439

1540
## Bug Fixes
1641

17-
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
42+
- Improved dependency management with optional dependency groups
43+
- Enhanced gRPC error handling and type safety
44+
- Cleaned up deprecated code

pyproject.toml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,26 @@ classifiers = [
2626
]
2727
requires-python = ">= 3.11, < 4"
2828
dependencies = [
29-
"typing-extensions >= 4.12.2, < 5",
29+
"typing-extensions >= 4.13.0, < 5",
30+
"frequenz-api-assets @ git+https://github.com/frequenz-floss/[email protected]",
31+
"frequenz-api-common >= 0.8.0, < 1",
32+
"frequenz-client-base >= 0.11.0, < 0.12.0",
33+
"frequenz-client-common >= 0.3.2, < 0.4.0",
34+
"grpcio >= 1.73.1, < 2"
3035
]
3136
dynamic = ["version"]
3237

38+
[project.scripts]
39+
assets-cli = "frequenz.client.assets.cli.__main__:main"
40+
3341
[[project.authors]]
3442
name = "Frequenz Energy-as-a-Service GmbH"
3543
3644

3745
[project.optional-dependencies]
46+
cli = [
47+
"asyncclick == 8.1.8",
48+
]
3849
dev-flake8 = [
3950
"flake8 == 7.3.0",
4051
"flake8-docstrings == 1.7.0",
@@ -57,9 +68,10 @@ dev-mkdocs = [
5768
]
5869
dev-mypy = [
5970
"mypy == 1.16.1",
71+
"grpc-stubs == 1.53.0.6",
6072
"types-Markdown == 3.8.0.20250415",
6173
# For checking the noxfile, docs/ script, and tests
62-
"frequenz-client-assets[dev-mkdocs,dev-noxfile,dev-pytest]",
74+
"frequenz-client-assets[dev-mkdocs,dev-noxfile,dev-pytest,cli]",
6375
]
6476
dev-noxfile = [
6577
"nox == 2025.5.1",
@@ -68,7 +80,7 @@ dev-noxfile = [
6880
dev-pylint = [
6981
# dev-pytest already defines a dependency to pylint because of the examples
7082
# For checking the noxfile, docs/ script, and tests
71-
"frequenz-client-assets[dev-mkdocs,dev-noxfile,dev-pytest]",
83+
"frequenz-client-assets[dev-mkdocs,dev-noxfile,dev-pytest,cli]",
7284
]
7385
dev-pytest = [
7486
"pytest == 8.4.1",
@@ -101,7 +113,7 @@ src_paths = ["benchmarks", "examples", "src", "tests"]
101113

102114
[tool.flake8]
103115
# We give some flexibility to go over 88, there are cases like long URLs or
104-
# code in documenation that have extra indentation. Black will still take care
116+
# code in documentation that have extra indentation. Black will still take care
105117
# of making everything that can be 88 wide, 88 wide.
106118
max-line-length = 100
107119
extend-ignore = [
Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
11
# License: MIT
22
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
33

4-
"""Assets API client for Python."""
4+
"""Assets API client."""
55

6+
from ._client import AssetsApiClient
67

7-
# TODO(cookiecutter): Remove this function
8-
def delete_me(*, blow_up: bool = False) -> bool:
9-
"""Do stuff for demonstration purposes.
10-
11-
Args:
12-
blow_up: If True, raise an exception.
13-
14-
Returns:
15-
True if no exception was raised.
16-
17-
Raises:
18-
RuntimeError: if blow_up is True.
19-
"""
20-
if blow_up:
21-
raise RuntimeError("This function should be removed!")
22-
return True
8+
__all__ = ["AssetsApiClient"]
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""
5+
Assets API client.
6+
7+
This module provides a client for the Assets API.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
from frequenz.api.assets.v1 import assets_pb2, assets_pb2_grpc
13+
from frequenz.client.base.client import BaseApiClient, call_stub_method
14+
15+
from frequenz.client.assets.types import Microgrid
16+
17+
from .exceptions import ClientNotConnected
18+
19+
20+
class AssetsApiClient(BaseApiClient[assets_pb2_grpc.PlatformAssetsStub]):
21+
"""A client for the Assets API."""
22+
23+
def __init__(
24+
self,
25+
server_url: str,
26+
auth_key: str | None,
27+
sign_secret: str | None,
28+
connect: bool = True,
29+
) -> None:
30+
"""
31+
Initialize the AssetsApiClient.
32+
33+
Args:
34+
server_url: The URL of the server to connect to.
35+
auth_key: The API key to use when connecting to the service.
36+
sign_secret: The secret to use when creating message HMAC.
37+
connect: Whether to connect to the server as soon as a client instance is created.
38+
"""
39+
super().__init__(
40+
server_url,
41+
assets_pb2_grpc.PlatformAssetsStub,
42+
connect=connect,
43+
auth_key=auth_key,
44+
sign_secret=sign_secret,
45+
)
46+
47+
@property
48+
def stub(self) -> assets_pb2_grpc.PlatformAssetsAsyncStub:
49+
"""
50+
The gRPC stub for the Assets API.
51+
52+
Returns:
53+
The gRPC stub for the Assets API.
54+
55+
Raises:
56+
ClientNotConnected: If the client is not connected to the server.
57+
"""
58+
if self._channel is None or self._stub is None:
59+
raise ClientNotConnected(server_url=self.server_url, operation="stub")
60+
# This type: ignore is needed because the stub is a sync stub, but we need to
61+
# use the async stub, so we cast the sync stub to the async stub.
62+
return self._stub # type: ignore
63+
64+
async def get_microgrid_details( # noqa: DOC502 (raises ApiClientError indirectly)
65+
self, microgrid_id: int
66+
) -> Microgrid:
67+
"""
68+
Get the details of a microgrid.
69+
70+
Args:
71+
microgrid_id: The ID of the microgrid to get the details of.
72+
73+
Returns:
74+
The details of the microgrid.
75+
76+
Raises:
77+
ApiClientError: If there are any errors communicating with the Assets API,
78+
most likely a subclass of [GrpcError][frequenz.client.base.exception.GrpcError].
79+
"""
80+
request = assets_pb2.GetMicrogridRequest(microgrid_id=microgrid_id)
81+
response = await call_stub_method(
82+
self,
83+
lambda: self.stub.GetMicrogrid(request),
84+
method_name="GetMicrogrid",
85+
)
86+
87+
return Microgrid.from_protobuf(response.microgrid)

0 commit comments

Comments
 (0)