-
Notifications
You must be signed in to change notification settings - Fork 3
Allow targeting categories with subtypes #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v0.x.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -5,13 +5,23 @@ | |||
|
||||
import json | ||||
from datetime import datetime, timedelta, timezone | ||||
from sre_constants import IN | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I bet this was Tab-oriented-programming + auto-import 😆
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it was more desperation, but this is a draft after all :P |
||||
from typing import Any, Literal, cast | ||||
|
||||
import asyncclick as click | ||||
import parsedatetime # type: ignore | ||||
from tzlocal import get_localzone | ||||
|
||||
from frequenz.client.common.microgrid.components import ComponentCategory | ||||
from frequenz.client.dispatch.types import ( | ||||
BatteryType, | ||||
CategoryAndType, | ||||
EvChargerType, | ||||
InverterType, | ||||
TargetCategories, | ||||
TargetCategoryAndTypes, | ||||
TargetIds, | ||||
) | ||||
|
||||
# Disable a false positive from pylint | ||||
# pylint: disable=inconsistent-return-statements | ||||
|
@@ -140,7 +150,7 @@ class TargetComponentParamType(click.ParamType): | |||
|
||||
def convert( | ||||
self, value: Any, param: click.Parameter | None, ctx: click.Context | None | ||||
) -> list[ComponentCategory] | list[int]: | ||||
) -> TargetIds | TargetCategories | TargetCategoryAndTypes: | ||||
"""Convert the input value into a list of ComponentCategory or IDs. | ||||
|
||||
Args: | ||||
|
@@ -149,9 +159,13 @@ def convert( | |||
ctx: The Click context object. | ||||
|
||||
Returns: | ||||
A list of component ids or component categories. | ||||
A list of targets, either as component IDs or component categories. | ||||
""" | ||||
if isinstance(value, list): # Already a list | ||||
if ( | ||||
isinstance(value, TargetIds) | ||||
or isinstance(value, TargetCategories) | ||||
or isinstance(value, TargetCategoryAndTypes) | ||||
): | ||||
return value | ||||
|
||||
values = value.split(",") | ||||
|
@@ -162,20 +176,67 @@ def convert( | |||
error: Exception | None = None | ||||
# Attempt to parse component ids | ||||
try: | ||||
return [int(id) for id in values] | ||||
return TargetIds([int(id) for id in values]) | ||||
except ValueError as e: | ||||
error = e | ||||
|
||||
# Attempt to parse as component categories, trim whitespace | ||||
try: | ||||
return [ComponentCategory[cat.strip().upper()] for cat in values] | ||||
return TargetCategories( | ||||
[ComponentCategory[cat.strip().upper()] for cat in values] | ||||
) | ||||
except KeyError as e: | ||||
error = e | ||||
|
||||
def valid_short_types() -> ( | ||||
dict[str, BatteryType | InverterType | EvChargerType] | ||||
): | ||||
"""All valid types in short form.""" | ||||
str_to_enum = {} | ||||
|
||||
for enum in BatteryType: | ||||
short_name = enum.name.split("_")[-1] | ||||
str_to_enum[short_name] = enum | ||||
|
||||
for enum in InverterType: | ||||
short_name = enum.name.split("_")[-1] | ||||
str_to_enum[short_name] = enum | ||||
|
||||
for enum in EvChargerType: | ||||
short_name = enum.name.split("_")[-1] | ||||
str_to_enum[short_name] = enum | ||||
|
||||
return str_to_enum | ||||
|
||||
short_types = valid_short_types() | ||||
|
||||
# Attempt to parse as component categories with types | ||||
try: | ||||
return TargetCategoryAndTypes( | ||||
[ | ||||
CategoryAndType( | ||||
ComponentCategory[cat.strip().upper()], | ||||
short_types[type.strip().upper()] | ||||
if type in short_types | ||||
else None, | ||||
) | ||||
for cat, type in (cat.split(":") for cat in values) | ||||
] | ||||
) | ||||
except KeyError as e: | ||||
error = e | ||||
|
||||
self.fail( | ||||
f'Invalid component category list or ID list: "{value}".\n' | ||||
f'Error: "{error}"\n\n' | ||||
"Possible categories: BATTERY, GRID, METER, INVERTER, EV_CHARGER, CHP ", | ||||
"Valid formats:\n" | ||||
"- 1,2,3 # A list of component IDs\n" | ||||
"- METER,INVERTER # A list of component categories\n" | ||||
"- BATTERY:NA_ION,INVERTER:SOLAR # A list of component categories with types\n" | ||||
"Valid component categories:\n" | ||||
f"{', '.join([cat.name for cat in ComponentCategory])}\n" | ||||
"Valid component types:\n" # using prepared short_types for better readability | ||||
f"{', '.join([f'{cat}:{type}' for cat, type in short_types.items()])}", | ||||
param, | ||||
ctx, | ||||
) | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,17 @@ | |
|
||
from .._internal_types import rounded_start_time | ||
from ..recurrence import EndCriteria, Frequency, RecurrenceRule, Weekday | ||
from ..types import Dispatch | ||
from ..types import ( | ||
BatteryType, | ||
CategoryAndType, | ||
Dispatch, | ||
EvChargerType, | ||
InverterType, | ||
TargetCategories, | ||
TargetCategoryAndTypes, | ||
TargetComponents, | ||
TargetIds, | ||
) | ||
|
||
|
||
class DispatchGenerator: | ||
|
@@ -66,6 +76,30 @@ def generate_recurrence_rule(self) -> RecurrenceRule: | |
], | ||
) | ||
|
||
def generate_target_cat_and_type(self) -> CategoryAndType: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: This is already pretty long, I wouldn't save the 5 extra chars from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're worse than copilot |
||
"""Generate a random category and type. | ||
|
||
Returns: | ||
a random category and type | ||
""" | ||
category = self._rng.choice(list(ComponentCategory)[1:]) | ||
type = None | ||
|
||
match category: | ||
case ComponentCategory.BATTERY: | ||
type = self._rng.choice(list(BatteryType)[1:]) | ||
case ComponentCategory.INVERTER: | ||
type = self._rng.choice(list(InverterType)[1:]) | ||
case ComponentCategory.EV_CHARGER: | ||
type = self._rng.choice(list(EvChargerType)[1:]) | ||
case _: | ||
type = None | ||
|
||
return CategoryAndType( | ||
category=category, | ||
type=type, | ||
) | ||
|
||
def generate_dispatch(self) -> Dispatch: | ||
"""Generate a random dispatch instance. | ||
|
||
|
@@ -94,14 +128,24 @@ def generate_dispatch(self) -> Dispatch: | |
), | ||
target=self._rng.choice( # type: ignore | ||
[ | ||
[ | ||
self._rng.choice(list(ComponentCategory)[1:]) | ||
for _ in range(self._rng.randint(1, 10)) | ||
], | ||
[ | ||
self._rng.randint(1, 100) | ||
for _ in range(self._rng.randint(1, 10)) | ||
], | ||
TargetCategories( | ||
[ | ||
self._rng.choice(list(ComponentCategory)[1:]) | ||
for _ in range(self._rng.randint(1, 10)) | ||
] | ||
), | ||
TargetIds( | ||
[ | ||
self._rng.randint(1, 100) | ||
for _ in range(self._rng.randint(1, 10)) | ||
] | ||
), | ||
TargetCategoryAndTypes( | ||
[ | ||
self.generate_target_cat_and_type() | ||
for _ in range(self._rng.randint(1, 10)) | ||
] | ||
), | ||
] | ||
), | ||
active=self._rng.choice([True, False]), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is because
target
is a frozenset nowThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, but why can't use use
[0]
for something frozen? You should be able, or is it because it is aset
now?Also what the hell does
el
stands for? It look it is pretty cryptic. Also if you need to clarify in the PR, maybe it is best to add a comment in the code itself for the future ourselves looking at this code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You cannot index-access a set, it has no order :)
el
stands forelement
.. sure I'll make it more clear 😄I was surprised as well, but this seems to be the easiest way to access the only element of a set with size 1