1717from prompt_toolkit .patch_stdout import patch_stdout
1818from prompt_toolkit .shortcuts import CompleteStyle
1919
20- from ._cli_types import FuzzyDateTime , FuzzyIntRange , FuzzyTimeDelta , SelectorParamType
20+ from frequenz .client .dispatch .types import (
21+ EndCriteria ,
22+ Frequency ,
23+ RecurrenceRule ,
24+ Weekday ,
25+ )
26+
27+ from ._cli_types import (
28+ FuzzyDateTime ,
29+ FuzzyIntRange ,
30+ FuzzyTimeDelta ,
31+ JsonDictParamType ,
32+ SelectorParamType ,
33+ )
2134from ._client import Client
2235
2336DEFAULT_DISPATCH_API_HOST = "88.99.25.81"
@@ -92,6 +105,57 @@ async def list_(ctx: click.Context, /, **filters: Any) -> None:
92105 click .echo (f"{ num_dispatches } dispatches total." )
93106
94107
108+ def parse_recurrence (kwargs : dict [str , Any ]) -> RecurrenceRule | None :
109+ """Parse recurrence rule from kwargs."""
110+ interval = kwargs .pop ("interval" , 0 )
111+ by_minute = list (kwargs .pop ("by_minute" , []))
112+ by_hour = list (kwargs .pop ("by_hour" , []))
113+ by_weekday = [Weekday [weekday .upper ()] for weekday in kwargs .pop ("by_weekday" , [])]
114+ by_monthday = list (kwargs .pop ("by_monthday" , []))
115+
116+ if not kwargs .get ("frequency" ):
117+ return None
118+
119+ return RecurrenceRule (
120+ frequency = Frequency [kwargs .pop ("frequency" )],
121+ interval = interval ,
122+ end_criteria = (
123+ EndCriteria (
124+ count = kwargs .pop ("count" , None ),
125+ until = kwargs .pop ("until" , None ),
126+ )
127+ if kwargs .get ("count" ) or kwargs .get ("until" )
128+ else None
129+ ),
130+ byminutes = by_minute ,
131+ byhours = by_hour ,
132+ byweekdays = by_weekday ,
133+ bymonthdays = by_monthday ,
134+ )
135+
136+
137+ def validate_reccurance (ctx : click .Context , param : click .Parameter , value : Any ) -> Any :
138+ """Validate recurrence rule."""
139+ if param .name == "frequency" :
140+ return value
141+
142+ count_param = param .name == "count" and value
143+ until_param = param .name == "until" and value
144+
145+ if (
146+ count_param
147+ and ctx .params .get ("until" ) is not None
148+ or until_param
149+ and ctx .params .get ("count" ) is not None
150+ ):
151+ raise click .BadArgumentUsage ("Only count or until can be set, not both." )
152+
153+ if value and ctx .params .get ("frequency" ) is None :
154+ raise click .BadArgumentUsage (f"Frequency must be set to use { param .name } ." )
155+
156+ return value
157+
158+
95159@cli .command ()
96160@click .argument ("microgrid-id" , required = True , type = int )
97161@click .argument (
@@ -102,11 +166,76 @@ async def list_(ctx: click.Context, /, **filters: Any) -> None:
102166@click .argument ("start-time" , required = True , type = FuzzyDateTime ())
103167@click .argument ("duration" , required = True , type = FuzzyTimeDelta ())
104168@click .argument ("selector" , required = True , type = SelectorParamType ())
105- @click .option ("--active" , type = bool , default = True )
106- @click .option ("--dry-run" , type = bool , default = False )
107- @click .option ("--payload" , type = str , help = "JSON payload for the dispatch" )
108- @click .option ("--recurrence" , type = str , help = "Recurrence rule (see documentation)" )
169+ @click .option ("--active" , "-a" , type = bool , default = True )
170+ @click .option ("--dry-run" , "-d" , type = bool , default = False )
171+ @click .option (
172+ "--payload" , "-p" , type = JsonDictParamType (), help = "JSON payload for the dispatch"
173+ )
109174@click .pass_context
175+ @click .option (
176+ "--frequency" ,
177+ "-f" ,
178+ type = click .Choice (
179+ [
180+ frequency .name
181+ for frequency in Frequency
182+ if frequency != Frequency .UNSPECIFIED
183+ ],
184+ case_sensitive = False ,
185+ ),
186+ help = "Frequency of the dispatch" ,
187+ callback = validate_reccurance ,
188+ is_eager = True ,
189+ )
190+ @click .option (
191+ "--interval" ,
192+ type = int ,
193+ help = "Interval of the dispatch, based on frequency" ,
194+ default = 0 ,
195+ )
196+ @click .option (
197+ "--count" ,
198+ type = int ,
199+ help = "Number of occurrences of the dispatch" ,
200+ callback = validate_reccurance ,
201+ )
202+ @click .option (
203+ "--until" ,
204+ type = FuzzyDateTime (),
205+ help = "End time of the dispatch" ,
206+ callback = validate_reccurance ,
207+ )
208+ @click .option (
209+ "--by-minute" ,
210+ type = int ,
211+ help = "Minute of the hour for the dispatch" ,
212+ multiple = True ,
213+ callback = validate_reccurance ,
214+ )
215+ @click .option (
216+ "--by-hour" ,
217+ type = int ,
218+ help = "Hour of the day for the dispatch" ,
219+ multiple = True ,
220+ callback = validate_reccurance ,
221+ )
222+ @click .option (
223+ "--by-weekday" ,
224+ type = click .Choice (
225+ [weekday .name for weekday in Weekday if weekday != Weekday .UNSPECIFIED ],
226+ case_sensitive = False ,
227+ ),
228+ help = "Day of the week for the dispatch" ,
229+ multiple = True ,
230+ callback = validate_reccurance ,
231+ )
232+ @click .option (
233+ "--by-monthday" ,
234+ type = int ,
235+ help = "Day of the month for the dispatch" ,
236+ multiple = True ,
237+ callback = validate_reccurance ,
238+ )
110239async def create (
111240 ctx : click .Context ,
112241 / ,
@@ -120,8 +249,12 @@ async def create(
120249 SELECTOR is either one of the following: BATTERY, GRID, METER, INVERTER,
121250 EV_CHARGER, CHP or a list of component IDs separated by commas, e.g. "1,2,3".
122251 """
252+ # Remove keys with `None` value
253+ kwargs = {k : v for k , v in kwargs .items () if v is not None }
254+
123255 dispatch = await ctx .obj ["client" ].create (
124256 _type = kwargs .pop ("type" ),
257+ recurrence = parse_recurrence (kwargs ),
125258 ** kwargs ,
126259 )
127260 click .echo (pformat (dispatch , compact = True ))
@@ -148,9 +281,6 @@ async def update(
148281 raise click .BadArgumentUsage ("At least one field must be given to update." )
149282
150283 try :
151- # if duration := new_fields.get("duration"):
152- # new_fields.pop("duration")
153- # new_fields["duration"] = timedelta(seconds=int(duration))
154284 await ctx .obj ["client" ].update (dispatch_id = dispatch_id , new_fields = new_fields )
155285 click .echo ("Dispatch updated." )
156286 except grpc .RpcError as e :
@@ -161,7 +291,7 @@ async def update(
161291@click .argument ("dispatch_ids" , type = int , nargs = - 1 ) # Allow multiple IDs
162292@click .pass_context
163293async def get (ctx : click .Context , dispatch_ids : List [int ]) -> None :
164- """Get multiple dispatches."""
294+ """Get one or multiple dispatches."""
165295 num_failed = 0
166296
167297 for dispatch_id in dispatch_ids :
0 commit comments