unitysvc-services 0.1.1__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unitysvc_services/api.py +278 -0
- unitysvc_services/format_data.py +2 -7
- unitysvc_services/list.py +14 -43
- unitysvc_services/models/base.py +157 -102
- unitysvc_services/models/listing_v1.py +25 -9
- unitysvc_services/models/provider_v1.py +19 -8
- unitysvc_services/models/seller_v1.py +10 -8
- unitysvc_services/models/service_v1.py +8 -1
- unitysvc_services/populate.py +2 -6
- unitysvc_services/publisher.py +676 -371
- unitysvc_services/py.typed +0 -0
- unitysvc_services/query.py +522 -337
- unitysvc_services/update.py +4 -13
- unitysvc_services/utils.py +2 -6
- unitysvc_services/validator.py +98 -79
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/METADATA +41 -39
- unitysvc_services-0.1.4.dist-info/RECORD +25 -0
- unitysvc_services-0.1.1.dist-info/RECORD +0 -23
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/WHEEL +0 -0
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/entry_points.txt +0 -0
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/top_level.txt +0 -0
unitysvc_services/query.py
CHANGED
@@ -1,174 +1,274 @@
|
|
1
1
|
"""Query command group - query backend API for data."""
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
import json
|
4
|
-
import os
|
5
5
|
from typing import Any
|
6
6
|
|
7
|
-
import httpx
|
8
7
|
import typer
|
9
8
|
from rich.console import Console
|
10
9
|
from rich.table import Table
|
11
10
|
|
11
|
+
from .api import UnitySvcAPI
|
12
|
+
|
12
13
|
app = typer.Typer(help="Query backend API for data")
|
13
14
|
console = Console()
|
14
15
|
|
15
16
|
|
16
|
-
class ServiceDataQuery:
|
17
|
-
"""Query service data from UnitySVC backend endpoints.
|
17
|
+
class ServiceDataQuery(UnitySvcAPI):
|
18
|
+
"""Query service data from UnitySVC backend endpoints.
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
Inherits HTTP methods with automatic curl fallback from UnitySvcAPI.
|
21
|
+
Provides convenient methods for listing public service data.
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
api_key: API key for authentication
|
23
|
+
Provides sync wrapper methods for CLI usage that wrap async base class methods.
|
24
|
+
"""
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
"""
|
29
|
-
if not base_url:
|
30
|
-
raise ValueError(
|
31
|
-
"Backend URL not provided. Use --backend-url or set UNITYSVC_BACKEND_URL env var."
|
32
|
-
)
|
33
|
-
if not api_key:
|
34
|
-
raise ValueError(
|
35
|
-
"API key not provided. Use --api-key or set UNITYSVC_API_KEY env var."
|
36
|
-
)
|
37
|
-
|
38
|
-
self.base_url = base_url.rstrip("/")
|
39
|
-
self.api_key = api_key
|
40
|
-
self.client = httpx.Client(
|
41
|
-
headers={
|
42
|
-
"X-API-Key": api_key,
|
43
|
-
"Content-Type": "application/json",
|
44
|
-
},
|
45
|
-
timeout=30.0,
|
46
|
-
)
|
26
|
+
def list_service_offerings(self, skip: int = 0, limit: int = 100) -> list[dict[str, Any]]:
|
27
|
+
"""List all service offerings from the backend (sync wrapper).
|
47
28
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
result =
|
29
|
+
Args:
|
30
|
+
skip: Number of records to skip (for pagination)
|
31
|
+
limit: Maximum number of records to return
|
32
|
+
"""
|
33
|
+
result: dict[str, Any] = asyncio.run(super().get("/publish/offerings", {"skip": skip, "limit": limit}))
|
53
34
|
return result.get("data", result) if isinstance(result, dict) else result
|
54
35
|
|
55
|
-
def list_service_listings(self) -> list[dict[str, Any]]:
|
56
|
-
"""List all service listings from the backend.
|
57
|
-
|
58
|
-
|
59
|
-
|
36
|
+
def list_service_listings(self, skip: int = 0, limit: int = 100) -> list[dict[str, Any]]:
|
37
|
+
"""List all service listings from the backend (sync wrapper).
|
38
|
+
|
39
|
+
Args:
|
40
|
+
skip: Number of records to skip (for pagination)
|
41
|
+
limit: Maximum number of records to return
|
42
|
+
"""
|
43
|
+
result: dict[str, Any] = asyncio.run(super().get("/publish/listings", {"skip": skip, "limit": limit}))
|
60
44
|
return result.get("data", result) if isinstance(result, dict) else result
|
61
45
|
|
62
|
-
def list_providers(self) -> list[dict[str, Any]]:
|
63
|
-
"""List all providers from the backend.
|
64
|
-
|
65
|
-
|
66
|
-
|
46
|
+
def list_providers(self, skip: int = 0, limit: int = 100) -> list[dict[str, Any]]:
|
47
|
+
"""List all providers from the backend (sync wrapper).
|
48
|
+
|
49
|
+
Args:
|
50
|
+
skip: Number of records to skip (for pagination)
|
51
|
+
limit: Maximum number of records to return
|
52
|
+
"""
|
53
|
+
result: dict[str, Any] = asyncio.run(super().get("/publish/providers", {"skip": skip, "limit": limit}))
|
67
54
|
return result.get("data", result) if isinstance(result, dict) else result
|
68
55
|
|
69
|
-
def list_sellers(self) -> list[dict[str, Any]]:
|
70
|
-
"""List all sellers from the backend.
|
71
|
-
|
72
|
-
|
73
|
-
|
56
|
+
def list_sellers(self, skip: int = 0, limit: int = 100) -> list[dict[str, Any]]:
|
57
|
+
"""List all sellers from the backend (sync wrapper).
|
58
|
+
|
59
|
+
Args:
|
60
|
+
skip: Number of records to skip (for pagination)
|
61
|
+
limit: Maximum number of records to return
|
62
|
+
"""
|
63
|
+
result: dict[str, Any] = asyncio.run(super().get("/publish/sellers", {"skip": skip, "limit": limit}))
|
74
64
|
return result.get("data", result) if isinstance(result, dict) else result
|
75
65
|
|
76
|
-
def
|
77
|
-
"""
|
78
|
-
response = self.client.get(f"{self.base_url}/private/access_interfaces")
|
79
|
-
response.raise_for_status()
|
80
|
-
return response.json()
|
66
|
+
def get(self, endpoint: str, params: dict[str, Any] | None = None) -> dict[str, Any]: # type: ignore[override]
|
67
|
+
"""Sync wrapper for base class async get() method.
|
81
68
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
response.raise_for_status()
|
86
|
-
return response.json()
|
69
|
+
Args:
|
70
|
+
endpoint: API endpoint path
|
71
|
+
params: Query parameters
|
87
72
|
|
88
|
-
|
89
|
-
|
90
|
-
|
73
|
+
Returns:
|
74
|
+
JSON response as dictionary
|
75
|
+
"""
|
76
|
+
return asyncio.run(super().get(endpoint, params))
|
91
77
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
78
|
+
def post( # type: ignore[override]
|
79
|
+
self, endpoint: str, json_data: dict[str, Any] | None = None, params: dict[str, Any] | None = None
|
80
|
+
) -> dict[str, Any]:
|
81
|
+
"""Sync wrapper for base class async post() method.
|
95
82
|
|
96
|
-
|
97
|
-
|
98
|
-
|
83
|
+
Args:
|
84
|
+
endpoint: API endpoint path
|
85
|
+
json_data: JSON body data
|
86
|
+
params: Query parameters
|
99
87
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
88
|
+
Returns:
|
89
|
+
JSON response as dictionary
|
90
|
+
"""
|
91
|
+
return asyncio.run(super().post(endpoint, json_data, params))
|
92
|
+
|
93
|
+
def check_task(self, task_id: str, poll_interval: float = 2.0, timeout: float = 300.0) -> dict[str, Any]: # type: ignore[override]
|
94
|
+
"""Sync wrapper for base class async check_task() method.
|
105
95
|
|
106
96
|
Args:
|
107
|
-
|
108
|
-
|
97
|
+
task_id: Celery task ID to poll
|
98
|
+
poll_interval: Seconds between status checks
|
99
|
+
timeout: Maximum seconds to wait
|
109
100
|
|
110
101
|
Returns:
|
111
|
-
|
102
|
+
Task result dictionary
|
112
103
|
|
113
104
|
Raises:
|
114
|
-
ValueError: If
|
105
|
+
ValueError: If task fails or times out
|
115
106
|
"""
|
116
|
-
|
117
|
-
|
118
|
-
|
107
|
+
return asyncio.run(super().check_task(task_id, poll_interval, timeout))
|
108
|
+
|
109
|
+
def __enter__(self):
|
110
|
+
"""Sync context manager entry for CLI usage."""
|
111
|
+
return self
|
112
|
+
|
113
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
114
|
+
"""Sync context manager exit for CLI usage."""
|
115
|
+
asyncio.run(self.aclose())
|
119
116
|
|
120
117
|
|
121
118
|
@app.command("sellers")
|
122
119
|
def query_sellers(
|
123
|
-
backend_url: str | None = typer.Option(
|
124
|
-
None,
|
125
|
-
"--backend-url",
|
126
|
-
"-u",
|
127
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
128
|
-
),
|
129
|
-
api_key: str | None = typer.Option(
|
130
|
-
None,
|
131
|
-
"--api-key",
|
132
|
-
"-k",
|
133
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
134
|
-
),
|
135
120
|
format: str = typer.Option(
|
136
121
|
"table",
|
137
122
|
"--format",
|
138
123
|
"-f",
|
139
124
|
help="Output format: table, json",
|
140
125
|
),
|
126
|
+
fields: str = typer.Option(
|
127
|
+
"id,name,display_name,seller_type",
|
128
|
+
"--fields",
|
129
|
+
help=(
|
130
|
+
"Comma-separated list of fields to display. Available fields: "
|
131
|
+
"id, name, display_name, seller_type, contact_email, "
|
132
|
+
"secondary_contact_email, homepage, description, "
|
133
|
+
"business_registration, tax_id, account_manager_id, "
|
134
|
+
"created_at, updated_at, status"
|
135
|
+
),
|
136
|
+
),
|
137
|
+
skip: int = typer.Option(
|
138
|
+
0,
|
139
|
+
"--skip",
|
140
|
+
help="Number of records to skip (for pagination)",
|
141
|
+
),
|
142
|
+
limit: int = typer.Option(
|
143
|
+
100,
|
144
|
+
"--limit",
|
145
|
+
help="Maximum number of records to return (default: 100)",
|
146
|
+
),
|
141
147
|
):
|
142
|
-
"""Query all sellers from the backend.
|
148
|
+
"""Query all sellers from the backend.
|
149
|
+
|
150
|
+
Examples:
|
151
|
+
# Use default fields
|
152
|
+
unitysvc_services query sellers
|
153
|
+
|
154
|
+
# Show only specific fields
|
155
|
+
unitysvc_services query sellers --fields id,name,contact_email
|
156
|
+
|
157
|
+
# Retrieve more than 100 records
|
158
|
+
unitysvc_services query sellers --limit 500
|
159
|
+
|
160
|
+
# Pagination: skip first 100, get next 100
|
161
|
+
unitysvc_services query sellers --skip 100 --limit 100
|
162
|
+
|
163
|
+
# Show all available fields
|
164
|
+
unitysvc_services query sellers --fields \\
|
165
|
+
id,name,display_name,seller_type,contact_email,homepage,created_at,updated_at
|
166
|
+
"""
|
167
|
+
# Parse fields list
|
168
|
+
field_list = [f.strip() for f in fields.split(",")]
|
169
|
+
|
170
|
+
# Define allowed fields from SellerPublic model
|
171
|
+
allowed_fields = {
|
172
|
+
"id",
|
173
|
+
"name",
|
174
|
+
"display_name",
|
175
|
+
"seller_type",
|
176
|
+
"contact_email",
|
177
|
+
"secondary_contact_email",
|
178
|
+
"homepage",
|
179
|
+
"description",
|
180
|
+
"business_registration",
|
181
|
+
"tax_id",
|
182
|
+
"account_manager_id",
|
183
|
+
"created_at",
|
184
|
+
"updated_at",
|
185
|
+
"status",
|
186
|
+
}
|
187
|
+
|
188
|
+
# Validate fields
|
189
|
+
invalid_fields = [f for f in field_list if f not in allowed_fields]
|
190
|
+
if invalid_fields:
|
191
|
+
console.print(
|
192
|
+
f"[red]✗[/red] Invalid field(s): {', '.join(invalid_fields)}",
|
193
|
+
style="bold red",
|
194
|
+
)
|
195
|
+
console.print(f"[yellow]Allowed fields:[/yellow] {', '.join(sorted(allowed_fields))}")
|
196
|
+
raise typer.Exit(code=1)
|
197
|
+
|
143
198
|
try:
|
144
|
-
with ServiceDataQuery
|
145
|
-
sellers = query.list_sellers()
|
199
|
+
with ServiceDataQuery() as query:
|
200
|
+
sellers = query.list_sellers(skip=skip, limit=limit)
|
146
201
|
|
147
202
|
if format == "json":
|
148
|
-
|
203
|
+
# For JSON, filter fields if not all are requested
|
204
|
+
if set(field_list) != allowed_fields:
|
205
|
+
filtered_sellers = [{k: v for k, v in seller.items() if k in field_list} for seller in sellers]
|
206
|
+
console.print(json.dumps(filtered_sellers, indent=2))
|
207
|
+
else:
|
208
|
+
console.print(json.dumps(sellers, indent=2))
|
149
209
|
else:
|
150
210
|
if not sellers:
|
151
211
|
console.print("[yellow]No sellers found.[/yellow]")
|
152
212
|
else:
|
153
213
|
table = Table(title="Sellers")
|
154
|
-
table.add_column("ID", style="cyan")
|
155
|
-
table.add_column("Name", style="green")
|
156
|
-
table.add_column("Display Name", style="blue")
|
157
|
-
table.add_column("Type", style="magenta")
|
158
|
-
table.add_column("Contact Email", style="yellow")
|
159
|
-
table.add_column("Active", style="white")
|
160
214
|
|
215
|
+
# Define column styles
|
216
|
+
field_styles = {
|
217
|
+
"id": "cyan",
|
218
|
+
"name": "green",
|
219
|
+
"display_name": "blue",
|
220
|
+
"seller_type": "magenta",
|
221
|
+
"contact_email": "yellow",
|
222
|
+
"secondary_contact_email": "yellow",
|
223
|
+
"homepage": "blue",
|
224
|
+
"description": "white",
|
225
|
+
"business_registration": "white",
|
226
|
+
"tax_id": "white",
|
227
|
+
"account_manager_id": "cyan",
|
228
|
+
"created_at": "white",
|
229
|
+
"updated_at": "white",
|
230
|
+
"status": "green",
|
231
|
+
}
|
232
|
+
|
233
|
+
# Define column headers
|
234
|
+
field_headers = {
|
235
|
+
"id": "ID",
|
236
|
+
"name": "Name",
|
237
|
+
"display_name": "Display Name",
|
238
|
+
"seller_type": "Type",
|
239
|
+
"contact_email": "Contact Email",
|
240
|
+
"secondary_contact_email": "Secondary Email",
|
241
|
+
"homepage": "Homepage",
|
242
|
+
"description": "Description",
|
243
|
+
"business_registration": "Business Reg",
|
244
|
+
"tax_id": "Tax ID",
|
245
|
+
"account_manager_id": "Account Manager ID",
|
246
|
+
"created_at": "Created At",
|
247
|
+
"updated_at": "Updated At",
|
248
|
+
"status": "Status",
|
249
|
+
}
|
250
|
+
|
251
|
+
# Add columns based on requested fields
|
252
|
+
for field in field_list:
|
253
|
+
header = field_headers.get(field, field.title())
|
254
|
+
style = field_styles.get(field, "white")
|
255
|
+
table.add_column(header, style=style)
|
256
|
+
|
257
|
+
# Add rows
|
161
258
|
for seller in sellers:
|
162
|
-
|
163
|
-
|
164
|
-
seller.get(
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
259
|
+
row = []
|
260
|
+
for field in field_list:
|
261
|
+
value = seller.get(field)
|
262
|
+
if value is None:
|
263
|
+
row.append("N/A")
|
264
|
+
elif isinstance(value, dict | list):
|
265
|
+
row.append(str(value)[:50]) # Truncate complex types
|
266
|
+
else:
|
267
|
+
row.append(str(value))
|
268
|
+
table.add_row(*row)
|
170
269
|
|
171
270
|
console.print(table)
|
271
|
+
console.print(f"\n[green]Total:[/green] {len(sellers)} seller(s)")
|
172
272
|
except ValueError as e:
|
173
273
|
console.print(f"[red]✗[/red] {e}", style="bold red")
|
174
274
|
raise typer.Exit(code=1)
|
@@ -179,51 +279,146 @@ def query_sellers(
|
|
179
279
|
|
180
280
|
@app.command("providers")
|
181
281
|
def query_providers(
|
182
|
-
backend_url: str | None = typer.Option(
|
183
|
-
None,
|
184
|
-
"--backend-url",
|
185
|
-
"-u",
|
186
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
187
|
-
),
|
188
|
-
api_key: str | None = typer.Option(
|
189
|
-
None,
|
190
|
-
"--api-key",
|
191
|
-
"-k",
|
192
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
193
|
-
),
|
194
282
|
format: str = typer.Option(
|
195
283
|
"table",
|
196
284
|
"--format",
|
197
285
|
"-f",
|
198
286
|
help="Output format: table, json",
|
199
287
|
),
|
288
|
+
fields: str = typer.Option(
|
289
|
+
"id,name,display_name,status",
|
290
|
+
"--fields",
|
291
|
+
help=(
|
292
|
+
"Comma-separated list of fields to display. Available fields: "
|
293
|
+
"id, name, display_name, contact_email, secondary_contact_email, "
|
294
|
+
"homepage, description, status, created_at, updated_at"
|
295
|
+
),
|
296
|
+
),
|
297
|
+
skip: int = typer.Option(
|
298
|
+
0,
|
299
|
+
"--skip",
|
300
|
+
help="Number of records to skip (for pagination)",
|
301
|
+
),
|
302
|
+
limit: int = typer.Option(
|
303
|
+
100,
|
304
|
+
"--limit",
|
305
|
+
help="Maximum number of records to return (default: 100)",
|
306
|
+
),
|
200
307
|
):
|
201
|
-
"""Query all providers from the backend.
|
308
|
+
"""Query all providers from the backend.
|
309
|
+
|
310
|
+
Examples:
|
311
|
+
# Use default fields
|
312
|
+
unitysvc_services query providers
|
313
|
+
|
314
|
+
# Retrieve more than 100 records
|
315
|
+
unitysvc_services query providers --limit 500
|
316
|
+
|
317
|
+
# Pagination: skip first 100, get next 100
|
318
|
+
unitysvc_services query providers --skip 100 --limit 100
|
319
|
+
|
320
|
+
# Show only specific fields
|
321
|
+
unitysvc_services query providers --fields id,name,contact_email
|
322
|
+
|
323
|
+
# Show all available fields
|
324
|
+
unitysvc_services query providers --fields \\
|
325
|
+
id,name,display_name,contact_email,homepage,status,created_at,updated_at
|
326
|
+
"""
|
327
|
+
# Parse fields list
|
328
|
+
field_list = [f.strip() for f in fields.split(",")]
|
329
|
+
|
330
|
+
# Define allowed fields from ProviderPublic model
|
331
|
+
allowed_fields = {
|
332
|
+
"id",
|
333
|
+
"name",
|
334
|
+
"display_name",
|
335
|
+
"contact_email",
|
336
|
+
"secondary_contact_email",
|
337
|
+
"homepage",
|
338
|
+
"description",
|
339
|
+
"status",
|
340
|
+
"created_at",
|
341
|
+
"updated_at",
|
342
|
+
}
|
343
|
+
|
344
|
+
# Validate fields
|
345
|
+
invalid_fields = [f for f in field_list if f not in allowed_fields]
|
346
|
+
if invalid_fields:
|
347
|
+
console.print(
|
348
|
+
f"[red]✗[/red] Invalid field(s): {', '.join(invalid_fields)}",
|
349
|
+
style="bold red",
|
350
|
+
)
|
351
|
+
console.print(f"[yellow]Allowed fields:[/yellow] {', '.join(sorted(allowed_fields))}")
|
352
|
+
raise typer.Exit(code=1)
|
353
|
+
|
202
354
|
try:
|
203
|
-
with ServiceDataQuery
|
204
|
-
providers = query.list_providers()
|
355
|
+
with ServiceDataQuery() as query:
|
356
|
+
providers = query.list_providers(skip=skip, limit=limit)
|
205
357
|
|
206
358
|
if format == "json":
|
207
|
-
|
359
|
+
# For JSON, filter fields if not all are requested
|
360
|
+
if set(field_list) != allowed_fields:
|
361
|
+
filtered_providers = [
|
362
|
+
{k: v for k, v in provider.items() if k in field_list} for provider in providers
|
363
|
+
]
|
364
|
+
console.print(json.dumps(filtered_providers, indent=2))
|
365
|
+
else:
|
366
|
+
console.print(json.dumps(providers, indent=2))
|
208
367
|
else:
|
209
368
|
if not providers:
|
210
369
|
console.print("[yellow]No providers found.[/yellow]")
|
211
370
|
else:
|
212
371
|
table = Table(title="Providers")
|
213
|
-
table.add_column("ID", style="cyan")
|
214
|
-
table.add_column("Name", style="green")
|
215
|
-
table.add_column("Display Name", style="blue")
|
216
|
-
table.add_column("Time Created", style="magenta")
|
217
372
|
|
373
|
+
# Define column styles
|
374
|
+
field_styles = {
|
375
|
+
"id": "cyan",
|
376
|
+
"name": "green",
|
377
|
+
"display_name": "blue",
|
378
|
+
"contact_email": "yellow",
|
379
|
+
"secondary_contact_email": "yellow",
|
380
|
+
"homepage": "blue",
|
381
|
+
"description": "white",
|
382
|
+
"status": "green",
|
383
|
+
"created_at": "magenta",
|
384
|
+
"updated_at": "magenta",
|
385
|
+
}
|
386
|
+
|
387
|
+
# Define column headers
|
388
|
+
field_headers = {
|
389
|
+
"id": "ID",
|
390
|
+
"name": "Name",
|
391
|
+
"display_name": "Display Name",
|
392
|
+
"contact_email": "Contact Email",
|
393
|
+
"secondary_contact_email": "Secondary Email",
|
394
|
+
"homepage": "Homepage",
|
395
|
+
"description": "Description",
|
396
|
+
"status": "Status",
|
397
|
+
"created_at": "Created At",
|
398
|
+
"updated_at": "Updated At",
|
399
|
+
}
|
400
|
+
|
401
|
+
# Add columns based on requested fields
|
402
|
+
for field in field_list:
|
403
|
+
header = field_headers.get(field, field.title())
|
404
|
+
style = field_styles.get(field, "white")
|
405
|
+
table.add_column(header, style=style)
|
406
|
+
|
407
|
+
# Add rows
|
218
408
|
for provider in providers:
|
219
|
-
|
220
|
-
|
221
|
-
provider.get(
|
222
|
-
|
223
|
-
|
224
|
-
|
409
|
+
row = []
|
410
|
+
for field in field_list:
|
411
|
+
value = provider.get(field)
|
412
|
+
if value is None:
|
413
|
+
row.append("N/A")
|
414
|
+
elif isinstance(value, dict | list):
|
415
|
+
row.append(str(value)[:50]) # Truncate complex types
|
416
|
+
else:
|
417
|
+
row.append(str(value))
|
418
|
+
table.add_row(*row)
|
225
419
|
|
226
420
|
console.print(table)
|
421
|
+
console.print(f"\n[green]Total:[/green] {len(providers)} provider(s)")
|
227
422
|
except ValueError as e:
|
228
423
|
console.print(f"[red]✗[/red] {e}", style="bold red")
|
229
424
|
raise typer.Exit(code=1)
|
@@ -234,256 +429,246 @@ def query_providers(
|
|
234
429
|
|
235
430
|
@app.command("offerings")
|
236
431
|
def query_offerings(
|
237
|
-
backend_url: str | None = typer.Option(
|
238
|
-
None,
|
239
|
-
"--backend-url",
|
240
|
-
"-u",
|
241
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
242
|
-
),
|
243
|
-
api_key: str | None = typer.Option(
|
244
|
-
None,
|
245
|
-
"--api-key",
|
246
|
-
"-k",
|
247
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
248
|
-
),
|
249
432
|
format: str = typer.Option(
|
250
433
|
"table",
|
251
434
|
"--format",
|
252
435
|
"-f",
|
253
436
|
help="Output format: table, json",
|
254
437
|
),
|
438
|
+
fields: str = typer.Option(
|
439
|
+
"id,name,service_type,provider_name,status",
|
440
|
+
"--fields",
|
441
|
+
help=(
|
442
|
+
"Comma-separated list of fields to display. Available fields: "
|
443
|
+
"id, definition_id, provider_id, status, price, service_name, "
|
444
|
+
"service_type, provider_name"
|
445
|
+
),
|
446
|
+
),
|
447
|
+
skip: int = typer.Option(
|
448
|
+
0,
|
449
|
+
"--skip",
|
450
|
+
help="Number of records to skip (for pagination)",
|
451
|
+
),
|
452
|
+
limit: int = typer.Option(
|
453
|
+
100,
|
454
|
+
"--limit",
|
455
|
+
help="Maximum number of records to return (default: 100)",
|
456
|
+
),
|
255
457
|
):
|
256
|
-
"""Query all service offerings from UnitySVC backend.
|
458
|
+
"""Query all service offerings from UnitySVC backend.
|
459
|
+
|
460
|
+
Examples:
|
461
|
+
# Use default fields
|
462
|
+
unitysvc_services query offerings
|
463
|
+
|
464
|
+
# Show only specific fields
|
465
|
+
unitysvc_services query offerings --fields id,name,status
|
466
|
+
|
467
|
+
# Retrieve more than 100 records
|
468
|
+
unitysvc_services query offerings --limit 500
|
469
|
+
|
470
|
+
# Pagination: skip first 100, get next 100
|
471
|
+
unitysvc_services query offerings --skip 100 --limit 100
|
472
|
+
|
473
|
+
# Show all available fields
|
474
|
+
unitysvc_services query offerings --fields \\
|
475
|
+
id,service_name,service_type,provider_name,status,price,definition_id,provider_id
|
476
|
+
"""
|
477
|
+
# Parse fields list
|
478
|
+
field_list = [f.strip() for f in fields.split(",")]
|
479
|
+
|
480
|
+
# Define allowed fields from ServiceOfferingPublic model
|
481
|
+
allowed_fields = {
|
482
|
+
"id",
|
483
|
+
"definition_id",
|
484
|
+
"provider_id",
|
485
|
+
"status",
|
486
|
+
"price",
|
487
|
+
"name",
|
488
|
+
"service_type",
|
489
|
+
"provider_name",
|
490
|
+
}
|
491
|
+
|
492
|
+
# Validate fields
|
493
|
+
invalid_fields = [f for f in field_list if f not in allowed_fields]
|
494
|
+
if invalid_fields:
|
495
|
+
console.print(
|
496
|
+
f"[red]Error:[/red] Invalid field(s): {', '.join(invalid_fields)}",
|
497
|
+
style="bold red",
|
498
|
+
)
|
499
|
+
console.print(f"[yellow]Available fields:[/yellow] {', '.join(sorted(allowed_fields))}")
|
500
|
+
raise typer.Exit(code=1)
|
501
|
+
|
257
502
|
try:
|
258
|
-
with ServiceDataQuery
|
259
|
-
offerings = query.list_service_offerings()
|
503
|
+
with ServiceDataQuery() as query:
|
504
|
+
offerings = query.list_service_offerings(skip=skip, limit=limit)
|
260
505
|
|
261
506
|
if format == "json":
|
262
|
-
|
507
|
+
# For JSON, filter fields if not all are requested
|
508
|
+
if set(field_list) != allowed_fields:
|
509
|
+
filtered_offerings = [
|
510
|
+
{k: v for k, v in offering.items() if k in field_list} for offering in offerings
|
511
|
+
]
|
512
|
+
console.print(json.dumps(filtered_offerings, indent=2))
|
513
|
+
else:
|
514
|
+
console.print(json.dumps(offerings, indent=2))
|
263
515
|
else:
|
264
516
|
if not offerings:
|
265
517
|
console.print("[yellow]No service offerings found.[/yellow]")
|
266
518
|
else:
|
267
|
-
table = Table(title="Service Offerings"
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
519
|
+
table = Table(title="Service Offerings")
|
520
|
+
|
521
|
+
# Add columns dynamically based on selected fields
|
522
|
+
for field in field_list:
|
523
|
+
# Capitalize and format field names for display
|
524
|
+
column_name = field.replace("_", " ").title()
|
525
|
+
table.add_column(column_name)
|
274
526
|
|
527
|
+
# Add rows
|
275
528
|
for offering in offerings:
|
276
|
-
|
277
|
-
|
278
|
-
offering.get(
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
529
|
+
row = []
|
530
|
+
for field in field_list:
|
531
|
+
value = offering.get(field)
|
532
|
+
if value is None:
|
533
|
+
row.append("N/A")
|
534
|
+
elif isinstance(value, dict | list):
|
535
|
+
row.append(str(value)[:50]) # Truncate complex types
|
536
|
+
else:
|
537
|
+
row.append(str(value))
|
538
|
+
table.add_row(*row)
|
284
539
|
|
285
540
|
console.print(table)
|
286
|
-
console.print(
|
287
|
-
f"\n[green]Total:[/green] {len(offerings)} service offering(s)"
|
288
|
-
)
|
541
|
+
console.print(f"\n[green]Total:[/green] {len(offerings)} service offering(s)")
|
289
542
|
except ValueError as e:
|
290
543
|
console.print(f"[red]✗[/red] {e}", style="bold red")
|
291
544
|
raise typer.Exit(code=1)
|
292
545
|
except Exception as e:
|
293
|
-
console.print(
|
294
|
-
f"[red]✗[/red] Failed to query service offerings: {e}", style="bold red"
|
295
|
-
)
|
546
|
+
console.print(f"[red]✗[/red] Failed to query service offerings: {e}", style="bold red")
|
296
547
|
raise typer.Exit(code=1)
|
297
548
|
|
298
549
|
|
299
550
|
@app.command("listings")
|
300
551
|
def query_listings(
|
301
|
-
backend_url: str | None = typer.Option(
|
302
|
-
None,
|
303
|
-
"--backend-url",
|
304
|
-
"-u",
|
305
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
306
|
-
),
|
307
|
-
api_key: str | None = typer.Option(
|
308
|
-
None,
|
309
|
-
"--api-key",
|
310
|
-
"-k",
|
311
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
312
|
-
),
|
313
552
|
format: str = typer.Option(
|
314
553
|
"table",
|
315
554
|
"--format",
|
316
555
|
"-f",
|
317
556
|
help="Output format: table, json",
|
318
557
|
),
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
if not listings:
|
329
|
-
console.print("[yellow]No service listings found.[/yellow]")
|
330
|
-
else:
|
331
|
-
table = Table(title="Service Listings", show_lines=True)
|
332
|
-
table.add_column("ID", style="cyan")
|
333
|
-
table.add_column("Service ID", style="blue")
|
334
|
-
table.add_column("Seller", style="green")
|
335
|
-
table.add_column("Status", style="yellow")
|
336
|
-
table.add_column("Interfaces")
|
337
|
-
|
338
|
-
for listing in listings:
|
339
|
-
interfaces_count = len(
|
340
|
-
listing.get("user_access_interfaces", [])
|
341
|
-
)
|
342
|
-
table.add_row(
|
343
|
-
str(listing.get("id", "N/A")),
|
344
|
-
str(listing.get("service_id", "N/A")),
|
345
|
-
listing.get("seller_name", "N/A"),
|
346
|
-
listing.get("listing_status", "N/A"),
|
347
|
-
str(interfaces_count),
|
348
|
-
)
|
349
|
-
|
350
|
-
console.print(table)
|
351
|
-
console.print(
|
352
|
-
f"\n[green]Total:[/green] {len(listings)} service listing(s)"
|
353
|
-
)
|
354
|
-
except ValueError as e:
|
355
|
-
console.print(f"[red]✗[/red] {e}", style="bold red")
|
356
|
-
raise typer.Exit(code=1)
|
357
|
-
except Exception as e:
|
358
|
-
console.print(
|
359
|
-
f"[red]✗[/red] Failed to query service listings: {e}", style="bold red"
|
360
|
-
)
|
361
|
-
raise typer.Exit(code=1)
|
362
|
-
|
363
|
-
|
364
|
-
@app.command("interfaces")
|
365
|
-
def query_interfaces(
|
366
|
-
backend_url: str | None = typer.Option(
|
367
|
-
None,
|
368
|
-
"--backend-url",
|
369
|
-
"-u",
|
370
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
558
|
+
fields: str = typer.Option(
|
559
|
+
"id,service_name,service_type,seller_name,listing_type,status",
|
560
|
+
"--fields",
|
561
|
+
help=(
|
562
|
+
"Comma-separated list of fields to display. Available fields: "
|
563
|
+
"id, offering_id, offering_status, seller_id, status, created_at, updated_at, "
|
564
|
+
"parameters_schema, parameters_ui_schema, tags, service_name, "
|
565
|
+
"service_type, provider_name, seller_name, listing_type"
|
566
|
+
),
|
371
567
|
),
|
372
|
-
|
373
|
-
|
374
|
-
"--
|
375
|
-
"
|
376
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
568
|
+
skip: int = typer.Option(
|
569
|
+
0,
|
570
|
+
"--skip",
|
571
|
+
help="Number of records to skip (for pagination)",
|
377
572
|
),
|
378
|
-
|
379
|
-
|
380
|
-
"--
|
381
|
-
"
|
382
|
-
help="Output format: table, json",
|
573
|
+
limit: int = typer.Option(
|
574
|
+
100,
|
575
|
+
"--limit",
|
576
|
+
help="Maximum number of records to return (default: 100)",
|
383
577
|
),
|
384
578
|
):
|
385
|
-
"""Query all
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
579
|
+
"""Query all service listings from UnitySVC backend.
|
580
|
+
|
581
|
+
Examples:
|
582
|
+
# Use default fields
|
583
|
+
unitysvc_services query listings
|
584
|
+
|
585
|
+
# Show only specific fields
|
586
|
+
unitysvc_services query listings --fields id,service_name,status
|
587
|
+
|
588
|
+
# Retrieve more than 100 records
|
589
|
+
unitysvc_services query listings --limit 500
|
590
|
+
|
591
|
+
# Pagination: skip first 100, get next 100
|
592
|
+
unitysvc_services query listings --skip 100 --limit 100
|
593
|
+
|
594
|
+
# Show all available fields
|
595
|
+
unitysvc_services query listings --fields \\
|
596
|
+
id,name,service_name,service_type,seller_name,listing_type,status,provider_name
|
597
|
+
"""
|
598
|
+
# Parse fields list
|
599
|
+
field_list = [f.strip() for f in fields.split(",")]
|
600
|
+
|
601
|
+
# Define allowed fields from ServiceListingPublic model
|
602
|
+
allowed_fields = {
|
603
|
+
"id",
|
604
|
+
"name",
|
605
|
+
"offering_id",
|
606
|
+
"offering_status",
|
607
|
+
"seller_id",
|
608
|
+
"status",
|
609
|
+
"created_at",
|
610
|
+
"updated_at",
|
611
|
+
"parameters_schema",
|
612
|
+
"parameters_ui_schema",
|
613
|
+
"tags",
|
614
|
+
"service_name",
|
615
|
+
"service_type",
|
616
|
+
"provider_name",
|
617
|
+
"seller_name",
|
618
|
+
"listing_type",
|
619
|
+
}
|
620
|
+
|
621
|
+
# Validate fields
|
622
|
+
invalid_fields = [f for f in field_list if f not in allowed_fields]
|
623
|
+
if invalid_fields:
|
423
624
|
console.print(
|
424
|
-
f"[red]
|
625
|
+
f"[red]Error:[/red] Invalid field(s): {', '.join(invalid_fields)}",
|
626
|
+
style="bold red",
|
425
627
|
)
|
628
|
+
console.print(f"[yellow]Available fields:[/yellow] {', '.join(sorted(allowed_fields))}")
|
426
629
|
raise typer.Exit(code=1)
|
427
630
|
|
428
|
-
|
429
|
-
@app.command("documents")
|
430
|
-
def query_documents(
|
431
|
-
backend_url: str | None = typer.Option(
|
432
|
-
None,
|
433
|
-
"--backend-url",
|
434
|
-
"-u",
|
435
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
436
|
-
),
|
437
|
-
api_key: str | None = typer.Option(
|
438
|
-
None,
|
439
|
-
"--api-key",
|
440
|
-
"-k",
|
441
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
442
|
-
),
|
443
|
-
format: str = typer.Option(
|
444
|
-
"table",
|
445
|
-
"--format",
|
446
|
-
"-f",
|
447
|
-
help="Output format: table, json",
|
448
|
-
),
|
449
|
-
):
|
450
|
-
"""Query all documents from UnitySVC backend (private endpoint)."""
|
451
631
|
try:
|
452
|
-
with ServiceDataQuery
|
453
|
-
|
632
|
+
with ServiceDataQuery() as query:
|
633
|
+
listings = query.list_service_listings(skip=skip, limit=limit)
|
454
634
|
|
455
635
|
if format == "json":
|
456
|
-
|
636
|
+
# For JSON, filter fields if not all are requested
|
637
|
+
if set(field_list) != allowed_fields:
|
638
|
+
filtered_listings = [{k: v for k, v in listing.items() if k in field_list} for listing in listings]
|
639
|
+
console.print(json.dumps(filtered_listings, indent=2))
|
640
|
+
else:
|
641
|
+
console.print(json.dumps(listings, indent=2))
|
457
642
|
else:
|
458
|
-
|
459
|
-
|
460
|
-
console.print("[yellow]No documents found.[/yellow]")
|
643
|
+
if not listings:
|
644
|
+
console.print("[yellow]No service listings found.[/yellow]")
|
461
645
|
else:
|
462
|
-
table = Table(title="
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
646
|
+
table = Table(title="Service Listings")
|
647
|
+
|
648
|
+
# Add columns dynamically based on selected fields
|
649
|
+
for field in field_list:
|
650
|
+
# Capitalize and format field names for display
|
651
|
+
column_name = field.replace("_", " ").title()
|
652
|
+
table.add_column(column_name)
|
653
|
+
|
654
|
+
# Add rows
|
655
|
+
for listing in listings:
|
656
|
+
row = []
|
657
|
+
for field in field_list:
|
658
|
+
value = listing.get(field)
|
659
|
+
if value is None:
|
660
|
+
row.append("N/A")
|
661
|
+
elif isinstance(value, dict | list):
|
662
|
+
row.append(str(value)[:50]) # Truncate complex types
|
663
|
+
else:
|
664
|
+
row.append(str(value))
|
665
|
+
table.add_row(*row)
|
479
666
|
|
480
667
|
console.print(table)
|
481
|
-
console.print(
|
482
|
-
f"\n[green]Total:[/green] {data.get('count', 0)} document(s)"
|
483
|
-
)
|
668
|
+
console.print(f"\n[green]Total:[/green] {len(listings)} service listing(s)")
|
484
669
|
except ValueError as e:
|
485
670
|
console.print(f"[red]✗[/red] {e}", style="bold red")
|
486
671
|
raise typer.Exit(code=1)
|
487
672
|
except Exception as e:
|
488
|
-
console.print(f"[red]✗[/red] Failed to query
|
673
|
+
console.print(f"[red]✗[/red] Failed to query service listings: {e}", style="bold red")
|
489
674
|
raise typer.Exit(code=1)
|