1010from orca_cli .core .context import OrcaContext
1111from orca_cli .core .output import console , output_options , print_detail , print_list
1212from orca_cli .core .validators import validate_id
13-
14-
15- def _url (client ) -> str :
16- return client .rating_url
13+ from orca_cli .services .rating import RatingService
1714
1815
1916def _parse_iso (value : str | None ) -> str | None :
@@ -45,7 +42,8 @@ def rating(ctx: click.Context) -> None:
4542def rating_info (ctx : click .Context ) -> None :
4643 """Show CloudKitty configuration (collector, metrics, fetcher)."""
4744 client = ctx .find_object (OrcaContext ).ensure_client ()
48- data = client .get (f"{ _url (client )} /v1/info/config" )
45+ svc = RatingService (client )
46+ data = svc .get_config ()
4947 console .print_json (json .dumps (data , indent = 2 ))
5048
5149
@@ -55,7 +53,8 @@ def rating_info(ctx: click.Context) -> None:
5553def rating_metric_list (ctx , output_format , columns , fit_width , max_width , noindent ):
5654 """List metrics that CloudKitty is configured to rate."""
5755 client = ctx .find_object (OrcaContext ).ensure_client ()
58- items = client .get (f"{ _url (client )} /v1/info/metrics" ).get ("metrics" , [])
56+ svc = RatingService (client )
57+ items = svc .find_metrics ()
5958 col_defs = [
6059 ("Metric" , "metric_id" , {"style" : "bold" }),
6160 ("Unit" , "unit" ),
@@ -73,7 +72,8 @@ def rating_metric_list(ctx, output_format, columns, fit_width, max_width, noinde
7372def rating_metric_show (ctx , metric_id , output_format , columns , fit_width , max_width , noindent ):
7473 """Show collection details for a rated metric."""
7574 client = ctx .find_object (OrcaContext ).ensure_client ()
76- data = client .get (f"{ _url (client )} /v1/info/metrics/{ metric_id } " )
75+ svc = RatingService (client )
76+ data = svc .get_metric (metric_id )
7777 fields = [
7878 ("Metric" , data .get ("metric_id" , "" )),
7979 ("Unit" , data .get ("unit" , "" )),
@@ -109,6 +109,7 @@ def rating_summary(ctx, begin, end, groupby, filters,
109109 orca rating summary --begin 2026-01-01T00:00:00 --end 2026-02-01T00:00:00 --groupby project_id
110110 """
111111 client = ctx .find_object (OrcaContext ).ensure_client ()
112+ svc = RatingService (client )
112113 if not begin and not end :
113114 begin , end = _default_window ()
114115 params : dict = {}
@@ -124,7 +125,7 @@ def rating_summary(ctx, begin, end, groupby, filters,
124125 k , v = f .split ("=" , 1 )
125126 params [f"filters[{ k } ]" ] = v
126127
127- data = client . get ( f" { _url ( client ) } /v2/summary" , params = params )
128+ data = svc . get_summary ( params = params )
128129 results = data .get ("results" , []) or []
129130 cols = data .get ("columns" , []) or []
130131 if not results :
@@ -149,6 +150,7 @@ def rating_dataframes(ctx, begin, end, limit):
149150 Falls back to v1 storage if the v2 endpoint is not exposed.
150151 """
151152 client = ctx .find_object (OrcaContext ).ensure_client ()
153+ svc = RatingService (client )
152154 if not begin and not end :
153155 # Tighter default than summary: last 24 h, dataframes can be huge.
154156 now = datetime .now (timezone .utc )
@@ -166,9 +168,9 @@ def rating_dataframes(ctx, begin, end, limit):
166168 # v2 is the preferred API but many clouds (e.g. Infomaniak) only expose
167169 # the v1 storage endpoint. Try v2, fall back to v1 on 404.
168170 try :
169- data = client . get ( f" { _url ( client ) } /v2/dataframes" , params = params )
171+ data = svc . find_dataframes ( v2 = True , params = params )
170172 except Exception :
171- data = client . get ( f" { _url ( client ) } /v1/storage/dataframes" , params = params )
173+ data = svc . find_dataframes ( v2 = False , params = params )
172174
173175 frames = data .get ("dataframes" , []) or data .get ("results" , [])
174176 if not frames :
@@ -198,14 +200,15 @@ def rating_quote(ctx, resources):
198200 orca rating quote --resource '{"service":"instance","desc":{"flavor_id":"2"},"volume":"1"}'
199201 """
200202 client = ctx .find_object (OrcaContext ).ensure_client ()
203+ svc = RatingService (client )
201204 body_items : list [dict ] = []
202205 for raw in resources :
203206 try :
204207 body_items .append (json .loads (raw ))
205208 except json .JSONDecodeError as exc :
206209 raise click .BadParameter (f"Invalid JSON: { exc } " , param_hint = "--resource" ) from exc
207210 payload = {"resources" : body_items }
208- data = client . post ( f" { _url ( client ) } /v1/rating/quote" , json = payload )
211+ data = svc . create_quote ( payload )
209212 if isinstance (data , (int , float , str )):
210213 console .print (f"Estimated price: [bold]{ data } [/bold]" )
211214 else :
@@ -222,8 +225,8 @@ def rating_quote(ctx, resources):
222225def rating_module_list (ctx , output_format , columns , fit_width , max_width , noindent ):
223226 """List rating modules (hashmap, pyscripts, noop, …). Admin only."""
224227 client = ctx .find_object (OrcaContext ).ensure_client ()
225- data = client . get ( f" { _url ( client ) } /v1/rating/modules" )
226- items = data . get ( "modules" , []) if isinstance ( data , dict ) else data
228+ svc = RatingService ( client )
229+ items = svc . find_modules ()
227230 col_defs = [
228231 ("Module" , "module_id" , {"style" : "bold" }),
229232 ("Enabled" , "enabled" ),
@@ -242,7 +245,8 @@ def rating_module_list(ctx, output_format, columns, fit_width, max_width, noinde
242245def rating_module_show (ctx , module_id , output_format , columns , fit_width , max_width , noindent ):
243246 """Show a rating module. Admin only."""
244247 client = ctx .find_object (OrcaContext ).ensure_client ()
245- data = client .get (f"{ _url (client )} /v1/rating/modules/{ module_id } " )
248+ svc = RatingService (client )
249+ data = svc .get_module (module_id )
246250 fields = [
247251 ("Module" , data .get ("module_id" , "" )),
248252 ("Enabled" , data .get ("enabled" , "" )),
@@ -254,15 +258,15 @@ def rating_module_show(ctx, module_id, output_format, columns, fit_width, max_wi
254258 fit_width = fit_width , max_width = max_width , noindent = noindent )
255259
256260
257- def _module_put (client , module_id : str , patch : dict ) -> None :
261+ def _module_put (svc : RatingService , module_id : str , patch : dict ) -> None :
258262 """PUT the full module representation with the given patch applied.
259263
260264 CloudKitty's module PUT is a full-replacement; fetch-merge-send.
261265 """
262- current = client . get ( f" { _url ( client ) } /v1/rating/modules/ { module_id } " )
266+ current = svc . get_module ( module_id )
263267 body = {k : v for k , v in current .items () if k != "module_id" }
264268 body .update (patch )
265- client . put ( f" { _url ( client ) } /v1/rating/modules/ { module_id } " , json = body )
269+ svc . update_module ( module_id , body )
266270
267271
268272@rating .command ("module-enable" )
@@ -271,7 +275,8 @@ def _module_put(client, module_id: str, patch: dict) -> None:
271275def rating_module_enable (ctx , module_id ):
272276 """Enable a rating module. Admin only."""
273277 client = ctx .find_object (OrcaContext ).ensure_client ()
274- _module_put (client , module_id , {"enabled" : True })
278+ svc = RatingService (client )
279+ _module_put (svc , module_id , {"enabled" : True })
275280 console .print (f"Rating module [bold]{ module_id } [/bold] enabled." )
276281
277282
@@ -281,7 +286,8 @@ def rating_module_enable(ctx, module_id):
281286def rating_module_disable (ctx , module_id ):
282287 """Disable a rating module. Admin only."""
283288 client = ctx .find_object (OrcaContext ).ensure_client ()
284- _module_put (client , module_id , {"enabled" : False })
289+ svc = RatingService (client )
290+ _module_put (svc , module_id , {"enabled" : False })
285291 console .print (f"Rating module [bold]{ module_id } [/bold] disabled." )
286292
287293
@@ -292,17 +298,15 @@ def rating_module_disable(ctx, module_id):
292298def rating_module_set_priority (ctx , module_id , priority ):
293299 """Set module priority (higher runs first). Admin only."""
294300 client = ctx .find_object (OrcaContext ).ensure_client ()
295- _module_put (client , module_id , {"priority" : priority })
301+ svc = RatingService (client )
302+ _module_put (svc , module_id , {"priority" : priority })
296303 console .print (f"Rating module [bold]{ module_id } [/bold] priority set to [bold]{ priority } [/bold]." )
297304
298305
299306# ══════════════════════════════════════════════════════════════════════════════
300307# rating hashmap — services / fields / mappings / thresholds / groups
301308# ══════════════════════════════════════════════════════════════════════════════
302309
303- _HM = "/v1/rating/module_config/hashmap"
304-
305-
306310@rating .group ("hashmap" )
307311def rating_hashmap () -> None :
308312 """Configure the HashMap rating module. Admin only."""
@@ -316,8 +320,8 @@ def rating_hashmap() -> None:
316320def hm_service_list (ctx , output_format , columns , fit_width , max_width , noindent ):
317321 """List HashMap services (one per rated metric)."""
318322 client = ctx .find_object (OrcaContext ).ensure_client ()
319- data = client . get ( f" { _url ( client ) } { _HM } /services" )
320- items = data . get ( "services" , []) if isinstance ( data , dict ) else data
323+ svc = RatingService ( client )
324+ items = svc . find_hashmap_services ()
321325 print_list (
322326 items ,
323327 [("Service ID" , "service_id" , {"style" : "cyan" }),
@@ -334,7 +338,8 @@ def hm_service_list(ctx, output_format, columns, fit_width, max_width, noindent)
334338def hm_service_create (ctx , name ):
335339 """Create a HashMap service (one per rated metric)."""
336340 client = ctx .find_object (OrcaContext ).ensure_client ()
337- data = client .post (f"{ _url (client )} { _HM } /services" , json = {"name" : name })
341+ svc = RatingService (client )
342+ data = svc .create_hashmap_service (name )
338343 console .print (f"HashMap service [bold]{ name } [/bold] created "
339344 f"(ID: { data .get ('service_id' , '?' )} )." )
340345
@@ -348,7 +353,8 @@ def hm_service_delete(ctx, service_id, yes):
348353 if not yes :
349354 click .confirm (f"Delete HashMap service { service_id } ?" , abort = True )
350355 client = ctx .find_object (OrcaContext ).ensure_client ()
351- client .delete (f"{ _url (client )} { _HM } /services/{ service_id } " )
356+ svc = RatingService (client )
357+ svc .delete_hashmap_service (service_id )
352358 console .print (f"HashMap service [bold]{ service_id } [/bold] deleted." )
353359
354360
@@ -362,9 +368,9 @@ def hm_service_delete(ctx, service_id, yes):
362368def hm_field_list (ctx , service_id , output_format , columns , fit_width , max_width , noindent ):
363369 """List HashMap fields (metadata keys a service is rated on)."""
364370 client = ctx .find_object (OrcaContext ).ensure_client ()
371+ svc = RatingService (client )
365372 params = {"service_id" : service_id } if service_id else {}
366- data = client .get (f"{ _url (client )} { _HM } /fields" , params = params )
367- items = data .get ("fields" , []) if isinstance (data , dict ) else data
373+ items = svc .find_hashmap_fields (params = params )
368374 print_list (
369375 items ,
370376 [("Field ID" , "field_id" , {"style" : "cyan" }),
@@ -383,8 +389,8 @@ def hm_field_list(ctx, service_id, output_format, columns, fit_width, max_width,
383389def hm_field_create (ctx , service_id , name ):
384390 """Create a HashMap field under a service."""
385391 client = ctx .find_object (OrcaContext ).ensure_client ()
386- data = client . post ( f" { _url ( client )} { _HM } /fields" ,
387- json = {"service_id" : service_id , "name" : name })
392+ svc = RatingService ( client )
393+ data = svc . create_hashmap_field ( {"service_id" : service_id , "name" : name })
388394 console .print (f"HashMap field [bold]{ name } [/bold] created "
389395 f"(ID: { data .get ('field_id' , '?' )} )." )
390396
@@ -398,7 +404,8 @@ def hm_field_delete(ctx, field_id, yes):
398404 if not yes :
399405 click .confirm (f"Delete HashMap field { field_id } ?" , abort = True )
400406 client = ctx .find_object (OrcaContext ).ensure_client ()
401- client .delete (f"{ _url (client )} { _HM } /fields/{ field_id } " )
407+ svc = RatingService (client )
408+ svc .delete_hashmap_field (field_id )
402409 console .print (f"HashMap field [bold]{ field_id } [/bold] deleted." )
403410
404411
@@ -414,15 +421,15 @@ def hm_mapping_list(ctx, service_id, field_id, group_id,
414421 output_format , columns , fit_width , max_width , noindent ):
415422 """List HashMap mappings (value → price)."""
416423 client = ctx .find_object (OrcaContext ).ensure_client ()
424+ svc = RatingService (client )
417425 params = {}
418426 if service_id :
419427 params ["service_id" ] = service_id
420428 if field_id :
421429 params ["field_id" ] = field_id
422430 if group_id :
423431 params ["group_id" ] = group_id
424- data = client .get (f"{ _url (client )} { _HM } /mappings" , params = params )
425- items = data .get ("mappings" , []) if isinstance (data , dict ) else data
432+ items = svc .find_hashmap_mappings (params = params )
426433 print_list (
427434 items ,
428435 [("Mapping ID" , "mapping_id" , {"style" : "cyan" }),
@@ -465,6 +472,7 @@ def hm_mapping_create(ctx, field_id, service_id, value, cost, mapping_type, grou
465472 if not field_id and not service_id :
466473 raise click .UsageError ("Provide either --field-id or --service-id." )
467474 client = ctx .find_object (OrcaContext ).ensure_client ()
475+ svc = RatingService (client )
468476 body : dict = {"cost" : cost , "type" : mapping_type }
469477 if field_id :
470478 body ["field_id" ] = field_id
@@ -474,7 +482,7 @@ def hm_mapping_create(ctx, field_id, service_id, value, cost, mapping_type, grou
474482 body ["value" ] = value
475483 if group_id :
476484 body ["group_id" ] = group_id
477- data = client . post ( f" { _url ( client ) } { _HM } /mappings" , json = body )
485+ data = svc . create_hashmap_mapping ( body )
478486 console .print (f"HashMap mapping created (ID: [bold]{ data .get ('mapping_id' , '?' )} [/bold])." )
479487
480488
@@ -487,7 +495,8 @@ def hm_mapping_delete(ctx, mapping_id, yes):
487495 if not yes :
488496 click .confirm (f"Delete HashMap mapping { mapping_id } ?" , abort = True )
489497 client = ctx .find_object (OrcaContext ).ensure_client ()
490- client .delete (f"{ _url (client )} { _HM } /mappings/{ mapping_id } " )
498+ svc = RatingService (client )
499+ svc .delete_hashmap_mapping (mapping_id )
491500 console .print (f"HashMap mapping [bold]{ mapping_id } [/bold] deleted." )
492501
493502
@@ -503,15 +512,15 @@ def hm_threshold_list(ctx, service_id, field_id, group_id,
503512 output_format , columns , fit_width , max_width , noindent ):
504513 """List HashMap thresholds."""
505514 client = ctx .find_object (OrcaContext ).ensure_client ()
515+ svc = RatingService (client )
506516 params = {}
507517 if service_id :
508518 params ["service_id" ] = service_id
509519 if field_id :
510520 params ["field_id" ] = field_id
511521 if group_id :
512522 params ["group_id" ] = group_id
513- data = client .get (f"{ _url (client )} { _HM } /thresholds" , params = params )
514- items = data .get ("thresholds" , []) if isinstance (data , dict ) else data
523+ items = svc .find_hashmap_thresholds (params = params )
515524 print_list (
516525 items ,
517526 [("Threshold ID" , "threshold_id" , {"style" : "cyan" }),
@@ -539,14 +548,15 @@ def hm_threshold_create(ctx, field_id, service_id, level, cost, threshold_type,
539548 if not field_id and not service_id :
540549 raise click .UsageError ("Provide either --field-id or --service-id." )
541550 client = ctx .find_object (OrcaContext ).ensure_client ()
551+ svc = RatingService (client )
542552 body : dict = {"level" : level , "cost" : cost , "type" : threshold_type }
543553 if field_id :
544554 body ["field_id" ] = field_id
545555 if service_id :
546556 body ["service_id" ] = service_id
547557 if group_id :
548558 body ["group_id" ] = group_id
549- data = client . post ( f" { _url ( client ) } { _HM } /thresholds" , json = body )
559+ data = svc . create_hashmap_threshold ( body )
550560 console .print (f"HashMap threshold created (ID: [bold]{ data .get ('threshold_id' , '?' )} [/bold])." )
551561
552562
@@ -559,7 +569,8 @@ def hm_threshold_delete(ctx, threshold_id, yes):
559569 if not yes :
560570 click .confirm (f"Delete HashMap threshold { threshold_id } ?" , abort = True )
561571 client = ctx .find_object (OrcaContext ).ensure_client ()
562- client .delete (f"{ _url (client )} { _HM } /thresholds/{ threshold_id } " )
572+ svc = RatingService (client )
573+ svc .delete_hashmap_threshold (threshold_id )
563574 console .print (f"HashMap threshold [bold]{ threshold_id } [/bold] deleted." )
564575
565576
@@ -571,8 +582,8 @@ def hm_threshold_delete(ctx, threshold_id, yes):
571582def hm_group_list (ctx , output_format , columns , fit_width , max_width , noindent ):
572583 """List HashMap groups (shared metadata across mappings)."""
573584 client = ctx .find_object (OrcaContext ).ensure_client ()
574- data = client . get ( f" { _url ( client ) } { _HM } /groups" )
575- items = data . get ( "groups" , []) if isinstance ( data , dict ) else data
585+ svc = RatingService ( client )
586+ items = svc . find_hashmap_groups ()
576587 print_list (
577588 items ,
578589 [("Group ID" , "group_id" , {"style" : "cyan" }),
@@ -589,7 +600,8 @@ def hm_group_list(ctx, output_format, columns, fit_width, max_width, noindent):
589600def hm_group_create (ctx , name ):
590601 """Create a HashMap group."""
591602 client = ctx .find_object (OrcaContext ).ensure_client ()
592- data = client .post (f"{ _url (client )} { _HM } /groups" , json = {"name" : name })
603+ svc = RatingService (client )
604+ data = svc .create_hashmap_group (name )
593605 console .print (f"HashMap group [bold]{ name } [/bold] created "
594606 f"(ID: { data .get ('group_id' , '?' )} )." )
595607
@@ -603,5 +615,6 @@ def hm_group_delete(ctx, group_id, yes):
603615 if not yes :
604616 click .confirm (f"Delete HashMap group { group_id } ?" , abort = True )
605617 client = ctx .find_object (OrcaContext ).ensure_client ()
606- client .delete (f"{ _url (client )} { _HM } /groups/{ group_id } " )
618+ svc = RatingService (client )
619+ svc .delete_hashmap_group (group_id )
607620 console .print (f"HashMap group [bold]{ group_id } [/bold] deleted." )
0 commit comments