Skip to content

Commit 9f2c436

Browse files
committed
refactor(rating): route CloudKitty via RatingService
commands/rating.py drops the _url() helper and the duplicated hashmap URL prefix constant, routing every CloudKitty call through RatingService — info, summary, dataframes (with v2→v1 fallback), quotes, rating modules (fetch-merge-put for set), and the hashmap sub-API (services/fields/mappings/thresholds/ groups).
1 parent 2955c7e commit 9f2c436

1 file changed

Lines changed: 57 additions & 44 deletions

File tree

orca_cli/commands/rating.py

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
from orca_cli.core.context import OrcaContext
1111
from orca_cli.core.output import console, output_options, print_detail, print_list
1212
from 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

1916
def _parse_iso(value: str | None) -> str | None:
@@ -45,7 +42,8 @@ def rating(ctx: click.Context) -> None:
4542
def 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:
5553
def 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
7372
def 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):
222225
def 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
242245
def 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:
271275
def 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):
281286
def 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):
292298
def 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")
307311
def rating_hashmap() -> None:
308312
"""Configure the HashMap rating module. Admin only."""
@@ -316,8 +320,8 @@ def rating_hashmap() -> None:
316320
def 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)
334338
def 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):
362368
def 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,
383389
def 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):
571582
def 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):
589600
def 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

Comments
 (0)