Skip to content

Commit 7d351bc

Browse files
committed
Refactor: Move Recurrence types in their own public module
Signed-off-by: Mathias L. Baumann <[email protected]>
1 parent eae538a commit 7d351bc

File tree

8 files changed

+187
-163
lines changed

8 files changed

+187
-163
lines changed

src/frequenz/client/dispatch/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from prompt_toolkit.patch_stdout import patch_stdout
1717
from prompt_toolkit.shortcuts import CompleteStyle
1818

19-
from frequenz.client.dispatch.types import (
19+
from frequenz.client.dispatch.recurrence import (
2020
EndCriteria,
2121
Frequency,
2222
RecurrenceRule,

src/frequenz/client/dispatch/_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@
4040
from frequenz.client.base.streaming import GrpcStreamBroadcaster
4141

4242
from ._internal_types import DispatchCreateRequest
43+
from .recurrence import RecurrenceRule
4344
from .types import (
4445
ComponentSelector,
4546
Dispatch,
4647
DispatchEvent,
47-
RecurrenceRule,
4848
component_selector_to_protobuf,
4949
)
5050

src/frequenz/client/dispatch/_internal_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

1919
from frequenz.client.base.conversion import to_datetime, to_timestamp
2020

21+
from .recurrence import RecurrenceRule
2122
from .types import (
2223
ComponentSelector,
23-
RecurrenceRule,
2424
component_selector_from_protobuf,
2525
component_selector_to_protobuf,
2626
)
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Types for recurrence rules."""
5+
6+
from dataclasses import dataclass, field
7+
from datetime import datetime
8+
from enum import IntEnum
9+
10+
from dateutil import rrule
11+
12+
# pylint: disable=no-name-in-module
13+
from frequenz.api.dispatch.v1.dispatch_pb2 import RecurrenceRule as PBRecurrenceRule
14+
15+
from frequenz.client.base.conversion import to_datetime, to_timestamp
16+
17+
# pylint: enable=no-name-in-module
18+
19+
20+
class Weekday(IntEnum):
21+
"""Enum representing the day of the week."""
22+
23+
UNSPECIFIED = PBRecurrenceRule.WEEKDAY_UNSPECIFIED
24+
MONDAY = PBRecurrenceRule.WEEKDAY_MONDAY
25+
TUESDAY = PBRecurrenceRule.WEEKDAY_TUESDAY
26+
WEDNESDAY = PBRecurrenceRule.WEEKDAY_WEDNESDAY
27+
THURSDAY = PBRecurrenceRule.WEEKDAY_THURSDAY
28+
FRIDAY = PBRecurrenceRule.WEEKDAY_FRIDAY
29+
SATURDAY = PBRecurrenceRule.WEEKDAY_SATURDAY
30+
SUNDAY = PBRecurrenceRule.WEEKDAY_SUNDAY
31+
32+
33+
class Frequency(IntEnum):
34+
"""Enum representing the frequency of the recurrence."""
35+
36+
UNSPECIFIED = PBRecurrenceRule.FREQUENCY_UNSPECIFIED
37+
MINUTELY = PBRecurrenceRule.FREQUENCY_MINUTELY
38+
HOURLY = PBRecurrenceRule.FREQUENCY_HOURLY
39+
DAILY = PBRecurrenceRule.FREQUENCY_DAILY
40+
WEEKLY = PBRecurrenceRule.FREQUENCY_WEEKLY
41+
MONTHLY = PBRecurrenceRule.FREQUENCY_MONTHLY
42+
YEARLY = PBRecurrenceRule.FREQUENCY_YEARLY
43+
44+
45+
@dataclass(kw_only=True)
46+
class EndCriteria:
47+
"""Controls when a recurring dispatch should end."""
48+
49+
count: int | None = None
50+
"""The number of times this dispatch should recur."""
51+
until: datetime | None = None
52+
"""The end time of this dispatch in UTC."""
53+
54+
@classmethod
55+
def from_protobuf(cls, pb_criteria: PBRecurrenceRule.EndCriteria) -> "EndCriteria":
56+
"""Convert a protobuf end criteria to an end criteria.
57+
58+
Args:
59+
pb_criteria: The protobuf end criteria to convert.
60+
61+
Returns:
62+
The converted end criteria.
63+
"""
64+
instance = cls()
65+
66+
match pb_criteria.WhichOneof("count_or_until"):
67+
case "count":
68+
instance.count = pb_criteria.count
69+
case "until":
70+
instance.until = to_datetime(pb_criteria.until)
71+
return instance
72+
73+
def to_protobuf(self) -> PBRecurrenceRule.EndCriteria:
74+
"""Convert an end criteria to a protobuf end criteria.
75+
76+
Returns:
77+
The converted protobuf end criteria.
78+
"""
79+
pb_criteria = PBRecurrenceRule.EndCriteria()
80+
81+
if self.count is not None:
82+
pb_criteria.count = self.count
83+
elif self.until is not None:
84+
pb_criteria.until.CopyFrom(to_timestamp(self.until))
85+
86+
return pb_criteria
87+
88+
89+
# pylint: disable=too-many-instance-attributes
90+
@dataclass(kw_only=True)
91+
class RecurrenceRule:
92+
"""Ruleset governing when and how a dispatch should re-occur.
93+
94+
Attributes follow the iCalendar specification (RFC5545) for recurrence rules.
95+
"""
96+
97+
frequency: Frequency = Frequency.UNSPECIFIED
98+
"""The frequency specifier of this recurring dispatch."""
99+
100+
interval: int = 0
101+
"""How often this dispatch should recur, based on the frequency."""
102+
103+
end_criteria: EndCriteria | None = None
104+
"""When this dispatch should end.
105+
106+
Can recur a fixed number of times or until a given timestamp."""
107+
108+
byminutes: list[int] = field(default_factory=list)
109+
"""On which minute(s) of the hour the event occurs."""
110+
111+
byhours: list[int] = field(default_factory=list)
112+
"""On which hour(s) of the day the event occurs."""
113+
114+
byweekdays: list[Weekday] = field(default_factory=list)
115+
"""On which day(s) of the week the event occurs."""
116+
117+
bymonthdays: list[int] = field(default_factory=list)
118+
"""On which day(s) of the month the event occurs."""
119+
120+
bymonths: list[int] = field(default_factory=list)
121+
"""On which month(s) of the year the event occurs."""
122+
123+
@classmethod
124+
def from_protobuf(cls, pb_rule: PBRecurrenceRule) -> "RecurrenceRule":
125+
"""Convert a protobuf recurrence rule to a recurrence rule.
126+
127+
Args:
128+
pb_rule: The protobuf recurrence rule to convert.
129+
130+
Returns:
131+
The converted recurrence rule.
132+
"""
133+
return RecurrenceRule(
134+
frequency=Frequency(pb_rule.freq),
135+
interval=pb_rule.interval,
136+
end_criteria=(
137+
EndCriteria.from_protobuf(pb_rule.end_criteria)
138+
if pb_rule.HasField("end_criteria")
139+
else None
140+
),
141+
byminutes=list(pb_rule.byminutes),
142+
byhours=list(pb_rule.byhours),
143+
byweekdays=[Weekday(day) for day in pb_rule.byweekdays],
144+
bymonthdays=list(pb_rule.bymonthdays),
145+
bymonths=list(pb_rule.bymonths),
146+
)
147+
148+
def to_protobuf(self) -> PBRecurrenceRule:
149+
"""Convert a recurrence rule to a protobuf recurrence rule.
150+
151+
Returns:
152+
The converted protobuf recurrence rule.
153+
"""
154+
pb_rule = PBRecurrenceRule()
155+
156+
pb_rule.freq = self.frequency.value
157+
pb_rule.interval = self.interval
158+
if self.end_criteria is not None:
159+
pb_rule.end_criteria.CopyFrom(self.end_criteria.to_protobuf())
160+
pb_rule.byminutes.extend(self.byminutes)
161+
pb_rule.byhours.extend(self.byhours)
162+
pb_rule.byweekdays.extend([day.value for day in self.byweekdays])
163+
pb_rule.bymonthdays.extend(self.bymonthdays)
164+
pb_rule.bymonths.extend(self.bymonths)
165+
166+
return pb_rule

src/frequenz/client/dispatch/test/generator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from frequenz.client.common.microgrid.components import ComponentCategory
1010

1111
from .._internal_types import rounded_start_time
12-
from ..types import Dispatch, EndCriteria, Frequency, RecurrenceRule, Weekday
12+
from ..recurrence import EndCriteria, Frequency, RecurrenceRule, Weekday
13+
from ..types import Dispatch
1314

1415

1516
class DispatchGenerator:

0 commit comments

Comments
 (0)