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