unitysvc-services 0.1.0__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 +139 -0
- unitysvc_services/models/listing_v1.py +23 -3
- unitysvc_services/models/provider_v1.py +23 -2
- unitysvc_services/models/seller_v1.py +12 -6
- unitysvc_services/models/service_v1.py +8 -1
- unitysvc_services/populate.py +2 -6
- unitysvc_services/publisher.py +732 -467
- unitysvc_services/py.typed +0 -0
- unitysvc_services/query.py +521 -318
- unitysvc_services/update.py +10 -14
- unitysvc_services/utils.py +105 -7
- unitysvc_services/validator.py +194 -10
- {unitysvc_services-0.1.0.dist-info → unitysvc_services-0.1.4.dist-info}/METADATA +42 -39
- unitysvc_services-0.1.4.dist-info/RECORD +25 -0
- unitysvc_services-0.1.0.dist-info/RECORD +0 -23
- {unitysvc_services-0.1.0.dist-info → unitysvc_services-0.1.4.dist-info}/WHEEL +0 -0
- {unitysvc_services-0.1.0.dist-info → unitysvc_services-0.1.4.dist-info}/entry_points.txt +0 -0
- {unitysvc_services-0.1.0.dist-info → unitysvc_services-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {unitysvc_services-0.1.0.dist-info → unitysvc_services-0.1.4.dist-info}/top_level.txt +0 -0
unitysvc_services/query.py
CHANGED
@@ -1,172 +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("API key not provided. Use --api-key or set UNITYSVC_API_KEY env var.")
|
35
|
-
|
36
|
-
self.base_url = base_url.rstrip("/")
|
37
|
-
self.api_key = api_key
|
38
|
-
self.client = httpx.Client(
|
39
|
-
headers={
|
40
|
-
"X-API-Key": api_key,
|
41
|
-
"Content-Type": "application/json",
|
42
|
-
},
|
43
|
-
timeout=30.0,
|
44
|
-
)
|
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).
|
45
28
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
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}))
|
51
34
|
return result.get("data", result) if isinstance(result, dict) else result
|
52
35
|
|
53
|
-
def list_service_listings(self) -> list[dict[str, Any]]:
|
54
|
-
"""List all service listings from the backend.
|
55
|
-
|
56
|
-
|
57
|
-
|
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}))
|
58
44
|
return result.get("data", result) if isinstance(result, dict) else result
|
59
45
|
|
60
|
-
def list_providers(self) -> list[dict[str, Any]]:
|
61
|
-
"""List all providers from the backend.
|
62
|
-
|
63
|
-
|
64
|
-
|
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}))
|
65
54
|
return result.get("data", result) if isinstance(result, dict) else result
|
66
55
|
|
67
|
-
def list_sellers(self) -> list[dict[str, Any]]:
|
68
|
-
"""List all sellers from the backend.
|
69
|
-
|
70
|
-
|
71
|
-
|
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}))
|
72
64
|
return result.get("data", result) if isinstance(result, dict) else result
|
73
65
|
|
74
|
-
def
|
75
|
-
"""
|
76
|
-
response = self.client.get(f"{self.base_url}/private/access_interfaces")
|
77
|
-
response.raise_for_status()
|
78
|
-
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.
|
79
68
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
response.raise_for_status()
|
84
|
-
return response.json()
|
69
|
+
Args:
|
70
|
+
endpoint: API endpoint path
|
71
|
+
params: Query parameters
|
85
72
|
|
86
|
-
|
87
|
-
|
88
|
-
|
73
|
+
Returns:
|
74
|
+
JSON response as dictionary
|
75
|
+
"""
|
76
|
+
return asyncio.run(super().get(endpoint, params))
|
89
77
|
|
90
|
-
def
|
91
|
-
|
92
|
-
|
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.
|
93
82
|
|
94
|
-
|
95
|
-
|
96
|
-
|
83
|
+
Args:
|
84
|
+
endpoint: API endpoint path
|
85
|
+
json_data: JSON body data
|
86
|
+
params: Query parameters
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
JSON response as dictionary
|
90
|
+
"""
|
91
|
+
return asyncio.run(super().post(endpoint, json_data, params))
|
97
92
|
|
98
|
-
|
99
|
-
|
100
|
-
backend_url: str | None = None, api_key: str | None = None
|
101
|
-
) -> "ServiceDataQuery":
|
102
|
-
"""Create ServiceDataQuery from environment variables or arguments.
|
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.
|
103
95
|
|
104
96
|
Args:
|
105
|
-
|
106
|
-
|
97
|
+
task_id: Celery task ID to poll
|
98
|
+
poll_interval: Seconds between status checks
|
99
|
+
timeout: Maximum seconds to wait
|
107
100
|
|
108
101
|
Returns:
|
109
|
-
|
102
|
+
Task result dictionary
|
110
103
|
|
111
104
|
Raises:
|
112
|
-
ValueError: If
|
105
|
+
ValueError: If task fails or times out
|
113
106
|
"""
|
114
|
-
|
115
|
-
|
116
|
-
|
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())
|
117
116
|
|
118
117
|
|
119
118
|
@app.command("sellers")
|
120
119
|
def query_sellers(
|
121
|
-
backend_url: str | None = typer.Option(
|
122
|
-
None,
|
123
|
-
"--backend-url",
|
124
|
-
"-u",
|
125
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
126
|
-
),
|
127
|
-
api_key: str | None = typer.Option(
|
128
|
-
None,
|
129
|
-
"--api-key",
|
130
|
-
"-k",
|
131
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
132
|
-
),
|
133
120
|
format: str = typer.Option(
|
134
121
|
"table",
|
135
122
|
"--format",
|
136
123
|
"-f",
|
137
124
|
help="Output format: table, json",
|
138
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
|
+
),
|
139
147
|
):
|
140
|
-
"""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
|
+
|
141
198
|
try:
|
142
|
-
with ServiceDataQuery
|
143
|
-
sellers = query.list_sellers()
|
199
|
+
with ServiceDataQuery() as query:
|
200
|
+
sellers = query.list_sellers(skip=skip, limit=limit)
|
144
201
|
|
145
202
|
if format == "json":
|
146
|
-
|
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))
|
147
209
|
else:
|
148
210
|
if not sellers:
|
149
211
|
console.print("[yellow]No sellers found.[/yellow]")
|
150
212
|
else:
|
151
213
|
table = Table(title="Sellers")
|
152
|
-
table.add_column("ID", style="cyan")
|
153
|
-
table.add_column("Name", style="green")
|
154
|
-
table.add_column("Display Name", style="blue")
|
155
|
-
table.add_column("Type", style="magenta")
|
156
|
-
table.add_column("Contact Email", style="yellow")
|
157
|
-
table.add_column("Active", style="white")
|
158
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
|
159
258
|
for seller in sellers:
|
160
|
-
|
161
|
-
|
162
|
-
seller.get(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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)
|
168
269
|
|
169
270
|
console.print(table)
|
271
|
+
console.print(f"\n[green]Total:[/green] {len(sellers)} seller(s)")
|
170
272
|
except ValueError as e:
|
171
273
|
console.print(f"[red]✗[/red] {e}", style="bold red")
|
172
274
|
raise typer.Exit(code=1)
|
@@ -177,51 +279,146 @@ def query_sellers(
|
|
177
279
|
|
178
280
|
@app.command("providers")
|
179
281
|
def query_providers(
|
180
|
-
backend_url: str | None = typer.Option(
|
181
|
-
None,
|
182
|
-
"--backend-url",
|
183
|
-
"-u",
|
184
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
185
|
-
),
|
186
|
-
api_key: str | None = typer.Option(
|
187
|
-
None,
|
188
|
-
"--api-key",
|
189
|
-
"-k",
|
190
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
191
|
-
),
|
192
282
|
format: str = typer.Option(
|
193
283
|
"table",
|
194
284
|
"--format",
|
195
285
|
"-f",
|
196
286
|
help="Output format: table, json",
|
197
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
|
+
),
|
198
307
|
):
|
199
|
-
"""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
|
+
|
200
354
|
try:
|
201
|
-
with ServiceDataQuery
|
202
|
-
providers = query.list_providers()
|
355
|
+
with ServiceDataQuery() as query:
|
356
|
+
providers = query.list_providers(skip=skip, limit=limit)
|
203
357
|
|
204
358
|
if format == "json":
|
205
|
-
|
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))
|
206
367
|
else:
|
207
368
|
if not providers:
|
208
369
|
console.print("[yellow]No providers found.[/yellow]")
|
209
370
|
else:
|
210
371
|
table = Table(title="Providers")
|
211
|
-
table.add_column("ID", style="cyan")
|
212
|
-
table.add_column("Name", style="green")
|
213
|
-
table.add_column("Display Name", style="blue")
|
214
|
-
table.add_column("Time Created", style="magenta")
|
215
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
|
216
408
|
for provider in providers:
|
217
|
-
|
218
|
-
|
219
|
-
provider.get(
|
220
|
-
|
221
|
-
|
222
|
-
|
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)
|
223
419
|
|
224
420
|
console.print(table)
|
421
|
+
console.print(f"\n[green]Total:[/green] {len(providers)} provider(s)")
|
225
422
|
except ValueError as e:
|
226
423
|
console.print(f"[red]✗[/red] {e}", style="bold red")
|
227
424
|
raise typer.Exit(code=1)
|
@@ -232,53 +429,113 @@ def query_providers(
|
|
232
429
|
|
233
430
|
@app.command("offerings")
|
234
431
|
def query_offerings(
|
235
|
-
backend_url: str | None = typer.Option(
|
236
|
-
None,
|
237
|
-
"--backend-url",
|
238
|
-
"-u",
|
239
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
240
|
-
),
|
241
|
-
api_key: str | None = typer.Option(
|
242
|
-
None,
|
243
|
-
"--api-key",
|
244
|
-
"-k",
|
245
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
246
|
-
),
|
247
432
|
format: str = typer.Option(
|
248
433
|
"table",
|
249
434
|
"--format",
|
250
435
|
"-f",
|
251
436
|
help="Output format: table, json",
|
252
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
|
+
),
|
253
457
|
):
|
254
|
-
"""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
|
+
|
255
502
|
try:
|
256
|
-
with ServiceDataQuery
|
257
|
-
offerings = query.list_service_offerings()
|
503
|
+
with ServiceDataQuery() as query:
|
504
|
+
offerings = query.list_service_offerings(skip=skip, limit=limit)
|
258
505
|
|
259
506
|
if format == "json":
|
260
|
-
|
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))
|
261
515
|
else:
|
262
516
|
if not offerings:
|
263
517
|
console.print("[yellow]No service offerings found.[/yellow]")
|
264
518
|
else:
|
265
|
-
table = Table(title="Service Offerings"
|
266
|
-
table.add_column("ID", style="cyan")
|
267
|
-
table.add_column("Name", style="green")
|
268
|
-
table.add_column("Display Name", style="blue")
|
269
|
-
table.add_column("Type", style="magenta")
|
270
|
-
table.add_column("Status", style="yellow")
|
271
|
-
table.add_column("Version")
|
519
|
+
table = Table(title="Service Offerings")
|
272
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)
|
526
|
+
|
527
|
+
# Add rows
|
273
528
|
for offering in offerings:
|
274
|
-
|
275
|
-
|
276
|
-
offering.get(
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
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)
|
282
539
|
|
283
540
|
console.print(table)
|
284
541
|
console.print(f"\n[green]Total:[/green] {len(offerings)} service offering(s)")
|
@@ -292,52 +549,120 @@ def query_offerings(
|
|
292
549
|
|
293
550
|
@app.command("listings")
|
294
551
|
def query_listings(
|
295
|
-
backend_url: str | None = typer.Option(
|
296
|
-
None,
|
297
|
-
"--backend-url",
|
298
|
-
"-u",
|
299
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
300
|
-
),
|
301
|
-
api_key: str | None = typer.Option(
|
302
|
-
None,
|
303
|
-
"--api-key",
|
304
|
-
"-k",
|
305
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
306
|
-
),
|
307
552
|
format: str = typer.Option(
|
308
553
|
"table",
|
309
554
|
"--format",
|
310
555
|
"-f",
|
311
556
|
help="Output format: table, json",
|
312
557
|
),
|
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
|
+
),
|
567
|
+
),
|
568
|
+
skip: int = typer.Option(
|
569
|
+
0,
|
570
|
+
"--skip",
|
571
|
+
help="Number of records to skip (for pagination)",
|
572
|
+
),
|
573
|
+
limit: int = typer.Option(
|
574
|
+
100,
|
575
|
+
"--limit",
|
576
|
+
help="Maximum number of records to return (default: 100)",
|
577
|
+
),
|
313
578
|
):
|
314
|
-
"""Query all service listings from UnitySVC backend.
|
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:
|
624
|
+
console.print(
|
625
|
+
f"[red]Error:[/red] Invalid field(s): {', '.join(invalid_fields)}",
|
626
|
+
style="bold red",
|
627
|
+
)
|
628
|
+
console.print(f"[yellow]Available fields:[/yellow] {', '.join(sorted(allowed_fields))}")
|
629
|
+
raise typer.Exit(code=1)
|
630
|
+
|
315
631
|
try:
|
316
|
-
with ServiceDataQuery
|
317
|
-
listings = query.list_service_listings()
|
632
|
+
with ServiceDataQuery() as query:
|
633
|
+
listings = query.list_service_listings(skip=skip, limit=limit)
|
318
634
|
|
319
635
|
if format == "json":
|
320
|
-
|
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))
|
321
642
|
else:
|
322
643
|
if not listings:
|
323
644
|
console.print("[yellow]No service listings found.[/yellow]")
|
324
645
|
else:
|
325
|
-
table = Table(title="Service Listings"
|
326
|
-
table.add_column("ID", style="cyan")
|
327
|
-
table.add_column("Service ID", style="blue")
|
328
|
-
table.add_column("Seller", style="green")
|
329
|
-
table.add_column("Status", style="yellow")
|
330
|
-
table.add_column("Interfaces")
|
646
|
+
table = Table(title="Service Listings")
|
331
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
|
332
655
|
for listing in listings:
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
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)
|
341
666
|
|
342
667
|
console.print(table)
|
343
668
|
console.print(f"\n[green]Total:[/green] {len(listings)} service listing(s)")
|
@@ -347,125 +672,3 @@ def query_listings(
|
|
347
672
|
except Exception as e:
|
348
673
|
console.print(f"[red]✗[/red] Failed to query service listings: {e}", style="bold red")
|
349
674
|
raise typer.Exit(code=1)
|
350
|
-
|
351
|
-
|
352
|
-
@app.command("interfaces")
|
353
|
-
def query_interfaces(
|
354
|
-
backend_url: str | None = typer.Option(
|
355
|
-
None,
|
356
|
-
"--backend-url",
|
357
|
-
"-u",
|
358
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
359
|
-
),
|
360
|
-
api_key: str | None = typer.Option(
|
361
|
-
None,
|
362
|
-
"--api-key",
|
363
|
-
"-k",
|
364
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
365
|
-
),
|
366
|
-
format: str = typer.Option(
|
367
|
-
"table",
|
368
|
-
"--format",
|
369
|
-
"-f",
|
370
|
-
help="Output format: table, json",
|
371
|
-
),
|
372
|
-
):
|
373
|
-
"""Query all access interfaces from UnitySVC backend (private endpoint)."""
|
374
|
-
try:
|
375
|
-
with ServiceDataQuery.from_env(backend_url, api_key) as query:
|
376
|
-
data = query.list_access_interfaces()
|
377
|
-
|
378
|
-
if format == "json":
|
379
|
-
console.print(json.dumps(data, indent=2))
|
380
|
-
else:
|
381
|
-
interfaces = data.get("data", [])
|
382
|
-
if not interfaces:
|
383
|
-
console.print("[yellow]No access interfaces found.[/yellow]")
|
384
|
-
else:
|
385
|
-
table = Table(title="Access Interfaces", show_lines=True)
|
386
|
-
table.add_column("ID", style="cyan")
|
387
|
-
table.add_column("Name", style="green")
|
388
|
-
table.add_column("Context", style="blue")
|
389
|
-
table.add_column("Entity ID", style="yellow")
|
390
|
-
table.add_column("Method", style="magenta")
|
391
|
-
table.add_column("Active", style="green")
|
392
|
-
|
393
|
-
for interface in interfaces:
|
394
|
-
table.add_row(
|
395
|
-
str(interface.get("id", "N/A"))[:8] + "...",
|
396
|
-
interface.get("name", "N/A"),
|
397
|
-
interface.get("context_type", "N/A"),
|
398
|
-
str(interface.get("entity_id", "N/A"))[:8] + "...",
|
399
|
-
interface.get("access_method", "N/A"),
|
400
|
-
"✓" if interface.get("is_active") else "✗",
|
401
|
-
)
|
402
|
-
|
403
|
-
console.print(table)
|
404
|
-
console.print(f"\n[green]Total:[/green] {data.get('count', 0)} access interface(s)")
|
405
|
-
except ValueError as e:
|
406
|
-
console.print(f"[red]✗[/red] {e}", style="bold red")
|
407
|
-
raise typer.Exit(code=1)
|
408
|
-
except Exception as e:
|
409
|
-
console.print(f"[red]✗[/red] Failed to query access interfaces: {e}", style="bold red")
|
410
|
-
raise typer.Exit(code=1)
|
411
|
-
|
412
|
-
|
413
|
-
@app.command("documents")
|
414
|
-
def query_documents(
|
415
|
-
backend_url: str | None = typer.Option(
|
416
|
-
None,
|
417
|
-
"--backend-url",
|
418
|
-
"-u",
|
419
|
-
help="UnitySVC backend URL (default: from UNITYSVC_BACKEND_URL env var)",
|
420
|
-
),
|
421
|
-
api_key: str | None = typer.Option(
|
422
|
-
None,
|
423
|
-
"--api-key",
|
424
|
-
"-k",
|
425
|
-
help="API key for authentication (default: from UNITYSVC_API_KEY env var)",
|
426
|
-
),
|
427
|
-
format: str = typer.Option(
|
428
|
-
"table",
|
429
|
-
"--format",
|
430
|
-
"-f",
|
431
|
-
help="Output format: table, json",
|
432
|
-
),
|
433
|
-
):
|
434
|
-
"""Query all documents from UnitySVC backend (private endpoint)."""
|
435
|
-
try:
|
436
|
-
with ServiceDataQuery.from_env(backend_url, api_key) as query:
|
437
|
-
data = query.list_documents()
|
438
|
-
|
439
|
-
if format == "json":
|
440
|
-
console.print(json.dumps(data, indent=2))
|
441
|
-
else:
|
442
|
-
documents = data.get("data", [])
|
443
|
-
if not documents:
|
444
|
-
console.print("[yellow]No documents found.[/yellow]")
|
445
|
-
else:
|
446
|
-
table = Table(title="Documents", show_lines=True)
|
447
|
-
table.add_column("ID", style="cyan")
|
448
|
-
table.add_column("Title", style="green")
|
449
|
-
table.add_column("Category", style="blue")
|
450
|
-
table.add_column("MIME Type", style="yellow")
|
451
|
-
table.add_column("Context", style="magenta")
|
452
|
-
table.add_column("Public", style="red")
|
453
|
-
|
454
|
-
for doc in documents:
|
455
|
-
table.add_row(
|
456
|
-
str(doc.get("id", "N/A"))[:8] + "...",
|
457
|
-
doc.get("title", "N/A")[:40],
|
458
|
-
doc.get("category", "N/A"),
|
459
|
-
doc.get("mime_type", "N/A"),
|
460
|
-
doc.get("context_type", "N/A"),
|
461
|
-
"✓" if doc.get("is_public") else "✗",
|
462
|
-
)
|
463
|
-
|
464
|
-
console.print(table)
|
465
|
-
console.print(f"\n[green]Total:[/green] {data.get('count', 0)} document(s)")
|
466
|
-
except ValueError as e:
|
467
|
-
console.print(f"[red]✗[/red] {e}", style="bold red")
|
468
|
-
raise typer.Exit(code=1)
|
469
|
-
except Exception as e:
|
470
|
-
console.print(f"[red]✗[/red] Failed to query documents: {e}", style="bold red")
|
471
|
-
raise typer.Exit(code=1)
|