Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ from datetime import datetime, timezone
from frequenz.client.marketmetering import MarketMeteringApiClient
from frequenz.client.marketmetering.types import (
EnergyFlowDirection,
MarketArea,
MarketLocationId,
MarketLocationIdType,
MarketLocationRef,
Expand All @@ -55,6 +56,7 @@ client = MarketMeteringApiClient(
# Define a Market Location (e.g., German MaLo)
market_location = MarketLocationRef(
enterprise_id=42,
market_area=MarketArea.EU_DE,
market_location_id=MarketLocationId(
value="DE01234567890",
type=MarketLocationIdType.MALO_ID,
Expand All @@ -80,10 +82,10 @@ export MARKETMETERING_API_URL="grpc://marketmetering.example.com"
export MARKETMETERING_API_AUTH_KEY="your-api-key"

# Stream samples from a German Market Location
marketmetering-cli stream 42:DE01234567890:MALO_ID
marketmetering-cli stream 42:EU_DE:DE01234567890:MALO_ID

# Stream with specific options
marketmetering-cli stream 42:DE01234567890:MALO_ID \
marketmetering-cli stream 42:EU_DE:DE01234567890:MALO_ID \
--direction IMPORT \
--metric ACTIVE_ENERGY \
--start-time "2025-01-01T00:00:00" \
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ classifiers = [
requires-python = ">= 3.11, < 4"
dependencies = [
"typing-extensions >= 4.13.0, < 5",
"frequenz-api-marketmetering @ git+https://github.com/frequenz-floss/frequenz-api-market-metering.git@cf9a3783314a046947faa995d68e11f4bc7f19ed",
"frequenz-api-marketmetering @ git+https://github.com/frequenz-floss/frequenz-api-market-metering.git@d9a23f31446eeb62fe6599af23c49e563d49f0f3",
"frequenz-client-base >= 0.11.0, < 0.12.0",
"grpcio >= 1.76.0, < 2",
]
Expand Down Expand Up @@ -72,7 +72,7 @@ dev-mkdocs = [
"mike == 2.1.3",
"mkdocs-gen-files == 0.6.0",
"mkdocs-literate-nav == 0.6.2",
"frequenz-api-marketmetering @ git+https://github.com/frequenz-floss/frequenz-api-market-metering.git@cf9a3783314a046947faa995d68e11f4bc7f19ed",
"frequenz-api-marketmetering @ git+https://github.com/frequenz-floss/frequenz-api-market-metering.git@d9a23f31446eeb62fe6599af23c49e563d49f0f3",
"mkdocs-macros-plugin == 1.5.0",
"mkdocs-material == 9.7.3",
"mkdocstrings[python] == 1.0.0",
Expand All @@ -91,7 +91,7 @@ dev-pylint = [
"pylint == 4.0.4",
# For checking the noxfile, docs/ script, and tests
"frequenz-client-marketmetering[cli,dev-mkdocs,dev-noxfile,dev-pytest]",
"frequenz-api-marketmetering @ git+https://github.com/frequenz-floss/frequenz-api-market-metering.git@cf9a3783314a046947faa995d68e11f4bc7f19ed",
"frequenz-api-marketmetering @ git+https://github.com/frequenz-floss/frequenz-api-market-metering.git@d9a23f31446eeb62fe6599af23c49e563d49f0f3",
]
dev-pytest = [
"pytest == 9.0.2",
Expand Down
6 changes: 6 additions & 0 deletions src/frequenz/client/marketmetering/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from frequenz.client.marketmetering import MarketMeteringApiClient
from frequenz.client.marketmetering.types import (
EnergyFlowDirection,
MarketArea,
MarketLocationId,
MarketLocationIdType,
MarketLocationRef,
Expand All @@ -27,6 +28,7 @@

market_location = MarketLocationRef(
enterprise_id=42,
market_area=MarketArea.EU_DE,
market_location_id=MarketLocationId(
value="DE01234567890",
type=MarketLocationIdType.MALO_ID,
Expand Down Expand Up @@ -57,6 +59,7 @@
MarketLocationEntry,
MarketLocationId,
MarketLocationIdType,
MarketLocationOperationError,
MarketLocationOperationErrorCode,
MarketLocationOperationResult,
MarketLocationRef,
Expand All @@ -71,6 +74,7 @@
ResamplingOptions,
RevisionSelection,
RevisionStrategy,
SampleUpsertError,
SampleUpsertErrorCode,
TimeResolution,
UpsertResult,
Expand All @@ -88,6 +92,7 @@
"MarketLocationEntry",
"MarketLocationId",
"MarketLocationIdType",
"MarketLocationOperationError",
"MarketLocationOperationErrorCode",
"MarketLocationOperationResult",
"MarketLocationRef",
Expand All @@ -103,6 +108,7 @@
"ResamplingOptions",
"RevisionSelection",
"RevisionStrategy",
"SampleUpsertError",
"SampleUpsertErrorCode",
"TimeResolution",
"UpsertResult",
Expand Down
56 changes: 28 additions & 28 deletions src/frequenz/client/marketmetering/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def print_detail(detail: MarketLocationDetail, raw: bool = False) -> None:

ml = detail.market_location
click.echo(f" {click.style('Display Name:', fg='cyan')} {ml.display_name}")
click.echo(f" {click.style('Market Area:', fg='cyan')} {ml.market_area.name}")
click.echo(f" {click.style('Market Area:', fg='cyan')} {ref.market_area.name}")
click.echo(
f" {click.style('Directions:', fg='cyan')} "
f"{', '.join(d.name for d in ml.supported_directions)}"
Expand Down Expand Up @@ -142,14 +142,14 @@ def print_operation_result(
f"{ref.enterprise_id}:{ref.market_location_id.value}"
f":{ref.market_location_id.type.name}"
)
if result.success:
if result.error is None:
click.echo(
f" {click.style('OK', fg='green')} {loc_str} (rev {result.revision})"
)
else:
click.echo(
f" {click.style('FAIL', fg='red')} {loc_str}: "
f"{result.error_code.name} - {result.error_message}"
f"{result.error.code.name} - {result.error.message}"
)


Expand Down Expand Up @@ -234,35 +234,44 @@ async def cli(
def parse_market_location(value: str) -> MarketLocationRef:
"""Parse a market location string.

Format: enterprise_id:location_id:type
Example: 42:DE01234567890:MALO_ID
Format: enterprise_id:market_area:location_id:type
Example: 42:EU_DE:DE01234567890:MALO_ID
"""
parts = value.split(":")
if len(parts) != 3:
if len(parts) != 4:
raise click.BadParameter(
f"Invalid market location format: {value}. "
"Expected format: enterprise_id:location_id:type"
"Expected format: enterprise_id:market_area:location_id:type"
)

try:
enterprise_id = int(parts[0])
except ValueError as exc:
raise click.BadParameter(f"Invalid enterprise_id: {parts[0]}") from exc

location_id = parts[1]
try:
market_area = MarketArea[parts[1].upper()]
except KeyError as exc:
valid_areas = ", ".join(a.name for a in MarketArea if a.name != "UNSPECIFIED")
raise click.BadParameter(
f"Invalid market area: {parts[1]}. Valid areas: {valid_areas}"
) from exc

location_id = parts[2]

try:
id_type = MarketLocationIdType[parts[2].upper()]
id_type = MarketLocationIdType[parts[3].upper()]
except KeyError as exc:
valid_types = ", ".join(
t.name for t in MarketLocationIdType if t.name != "UNSPECIFIED"
)
raise click.BadParameter(
f"Invalid location type: {parts[2]}. Valid types: {valid_types}"
f"Invalid location type: {parts[3]}. Valid types: {valid_types}"
) from exc

return MarketLocationRef(
enterprise_id=enterprise_id,
market_area=market_area,
market_location_id=MarketLocationId(value=location_id, type=id_type),
)

Expand Down Expand Up @@ -335,10 +344,10 @@ async def stream_cmd(
) -> None:
"""Stream metering samples from Market Locations.

MARKET_LOCATIONS are specified as: enterprise_id:location_id:type
MARKET_LOCATIONS are specified as: enterprise_id:market_area:location_id:type

Example:
42:DE01234567890:MALO_ID
42:EU_DE:DE01234567890:MALO_ID

Valid types: MALO_ID, MPAN, ESI_ID, NMI, OTHER

Expand Down Expand Up @@ -397,12 +406,6 @@ async def stream_cmd(
required=True,
help="Display name for the Market Location",
)
@click.option(
"--market-area",
required=True,
type=click.Choice([a.name for a in MarketArea if a.name != "UNSPECIFIED"]),
help="Market area / jurisdiction",
)
@click.option(
"--direction",
"-d",
Expand All @@ -422,22 +425,20 @@ async def create_cmd(
ctx: click.Context,
market_location: MarketLocationRef,
name: str,
market_area: str,
direction: tuple[str, ...],
resolution: str,
) -> None:
"""Create a new Market Location.

MARKET_LOCATION is specified as: enterprise_id:location_id:type
MARKET_LOCATION is specified as: enterprise_id:market_area:location_id:type

Example:
create 42:50601159037:MALO_ID --name "My Location" --market-area EU_DE -d IMPORT
create 42:EU_DE:50601159037:MALO_ID --name "My Location" -d IMPORT

Args:
ctx: Click context with client and options.
market_location: Market location reference.
name: Display name for the location.
market_area: Market area jurisdiction.
direction: Supported energy flow directions.
resolution: Time resolution for metering data.
"""
Expand All @@ -446,7 +447,6 @@ async def create_cmd(

ml = MarketLocation(
display_name=name,
market_area=MarketArea[market_area],
supported_directions=[EnergyFlowDirection[d] for d in direction],
time_resolution=TimeResolution[resolution],
payload={},
Expand Down Expand Up @@ -533,10 +533,10 @@ async def activate_cmd(
) -> None:
"""Activate one or more Market Locations.

MARKET_LOCATIONS are specified as: enterprise_id:location_id:type
MARKET_LOCATIONS are specified as: enterprise_id:market_area:location_id:type

Example:
activate 42:50601159037:MALO_ID
activate 42:EU_DE:50601159037:MALO_ID

Args:
ctx: Click context with client and options.
Expand Down Expand Up @@ -566,10 +566,10 @@ async def deactivate_cmd(
) -> None:
"""Deactivate one or more Market Locations.

MARKET_LOCATIONS are specified as: enterprise_id:location_id:type
MARKET_LOCATIONS are specified as: enterprise_id:market_area:location_id:type

Example:
deactivate 42:50601159037:MALO_ID
deactivate 42:EU_DE:50601159037:MALO_ID

Args:
ctx: Click context with client and options.
Expand Down Expand Up @@ -622,7 +622,7 @@ async def update_cmd(
Requires the current revision number (use 'list' to find it).

Example:
update 42:50601159037:MALO_ID --revision 3 --name "New Name"
update 42:EU_DE:50601159037:MALO_ID --revision 3 --name "New Name"

Args:
ctx: Click context with client and options.
Expand Down
2 changes: 2 additions & 0 deletions src/frequenz/client/marketmetering/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class MarketMeteringApiClient(
from frequenz.client.marketmetering import MarketMeteringApiClient
from frequenz.client.marketmetering.types import (
EnergyFlowDirection,
MarketArea,
MarketLocationId,
MarketLocationIdType,
MarketLocationRef,
Expand All @@ -69,6 +70,7 @@ class MarketMeteringApiClient(

market_location = MarketLocationRef(
enterprise_id=42,
market_area=MarketArea.EU_DE,
market_location_id=MarketLocationId(
value="DE01234567890",
type=MarketLocationIdType.MALO_ID,
Expand Down
Loading