unitysvc-services 0.1.24__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/__init__.py +4 -0
- unitysvc_services/api.py +421 -0
- unitysvc_services/cli.py +23 -0
- unitysvc_services/format_data.py +140 -0
- unitysvc_services/interactive_prompt.py +1132 -0
- unitysvc_services/list.py +216 -0
- unitysvc_services/models/__init__.py +71 -0
- unitysvc_services/models/base.py +1375 -0
- unitysvc_services/models/listing_data.py +118 -0
- unitysvc_services/models/listing_v1.py +56 -0
- unitysvc_services/models/provider_data.py +79 -0
- unitysvc_services/models/provider_v1.py +54 -0
- unitysvc_services/models/seller_data.py +120 -0
- unitysvc_services/models/seller_v1.py +42 -0
- unitysvc_services/models/service_data.py +114 -0
- unitysvc_services/models/service_v1.py +81 -0
- unitysvc_services/populate.py +207 -0
- unitysvc_services/publisher.py +1628 -0
- unitysvc_services/py.typed +0 -0
- unitysvc_services/query.py +688 -0
- unitysvc_services/scaffold.py +1103 -0
- unitysvc_services/schema/base.json +777 -0
- unitysvc_services/schema/listing_v1.json +1286 -0
- unitysvc_services/schema/provider_v1.json +952 -0
- unitysvc_services/schema/seller_v1.json +379 -0
- unitysvc_services/schema/service_v1.json +1306 -0
- unitysvc_services/test.py +965 -0
- unitysvc_services/unpublisher.py +505 -0
- unitysvc_services/update.py +287 -0
- unitysvc_services/utils.py +533 -0
- unitysvc_services/validator.py +731 -0
- unitysvc_services-0.1.24.dist-info/METADATA +184 -0
- unitysvc_services-0.1.24.dist-info/RECORD +37 -0
- unitysvc_services-0.1.24.dist-info/WHEEL +5 -0
- unitysvc_services-0.1.24.dist-info/entry_points.txt +3 -0
- unitysvc_services-0.1.24.dist-info/licenses/LICENSE +21 -0
- unitysvc_services-0.1.24.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
"""Data unpublisher module for deleting service data from UnitySVC backend."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from .api import UnitySvcAPI
|
|
12
|
+
from .utils import find_files_by_schema, load_data_file
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="Unpublish (delete) data from backend")
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ServiceDataUnpublisher(UnitySvcAPI):
|
|
19
|
+
"""Unpublishes (deletes) service data from UnitySVC backend endpoints.
|
|
20
|
+
|
|
21
|
+
Inherits base HTTP client with curl fallback from UnitySvcAPI.
|
|
22
|
+
Provides methods for deleting offerings, listings, providers, and sellers.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
async def delete_service_offering(
|
|
26
|
+
self,
|
|
27
|
+
offering_id: str,
|
|
28
|
+
dryrun: bool = False,
|
|
29
|
+
force: bool = False,
|
|
30
|
+
) -> dict[str, Any]:
|
|
31
|
+
"""Delete a service offering from backend.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
offering_id: UUID of the offering to delete
|
|
35
|
+
dryrun: If True, show what would be deleted without actually deleting
|
|
36
|
+
force: If True, force deletion even with active subscriptions
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Response from backend with deletion details
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
httpx.HTTPStatusError: If deletion fails (404, 403, etc.)
|
|
43
|
+
"""
|
|
44
|
+
params = {}
|
|
45
|
+
if dryrun:
|
|
46
|
+
params["dryrun"] = "true"
|
|
47
|
+
if force:
|
|
48
|
+
params["force"] = "true"
|
|
49
|
+
|
|
50
|
+
return await self.delete(f"/publish/offering/{offering_id}", params=params)
|
|
51
|
+
|
|
52
|
+
async def delete_service_listing(
|
|
53
|
+
self,
|
|
54
|
+
listing_id: str,
|
|
55
|
+
dryrun: bool = False,
|
|
56
|
+
force: bool = False,
|
|
57
|
+
) -> dict[str, Any]:
|
|
58
|
+
"""Delete a service listing from backend.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
listing_id: UUID of the listing to delete
|
|
62
|
+
dryrun: If True, show what would be deleted without actually deleting
|
|
63
|
+
force: If True, force deletion even with active subscriptions
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Response from backend with deletion details
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
httpx.HTTPStatusError: If deletion fails (404, 403, etc.)
|
|
70
|
+
"""
|
|
71
|
+
params = {}
|
|
72
|
+
if dryrun:
|
|
73
|
+
params["dryrun"] = "true"
|
|
74
|
+
if force:
|
|
75
|
+
params["force"] = "true"
|
|
76
|
+
|
|
77
|
+
return await self.delete(f"/publish/listing/{listing_id}", params=params)
|
|
78
|
+
|
|
79
|
+
async def delete_provider(
|
|
80
|
+
self,
|
|
81
|
+
provider_name: str,
|
|
82
|
+
dryrun: bool = False,
|
|
83
|
+
force: bool = False,
|
|
84
|
+
) -> dict[str, Any]:
|
|
85
|
+
"""Delete a provider from backend.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
provider_name: Name of the provider to delete
|
|
89
|
+
dryrun: If True, show what would be deleted without actually deleting
|
|
90
|
+
force: If True, force deletion even with active subscriptions
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Response from backend with deletion details
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
httpx.HTTPStatusError: If deletion fails (404, 403, etc.)
|
|
97
|
+
"""
|
|
98
|
+
params = {}
|
|
99
|
+
if dryrun:
|
|
100
|
+
params["dryrun"] = "true"
|
|
101
|
+
if force:
|
|
102
|
+
params["force"] = "true"
|
|
103
|
+
|
|
104
|
+
return await self.delete(f"/publish/provider/{provider_name}", params=params)
|
|
105
|
+
|
|
106
|
+
async def delete_seller(
|
|
107
|
+
self,
|
|
108
|
+
seller_name: str,
|
|
109
|
+
dryrun: bool = False,
|
|
110
|
+
force: bool = False,
|
|
111
|
+
) -> dict[str, Any]:
|
|
112
|
+
"""Delete a seller from backend.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
seller_name: Name of the seller to delete
|
|
116
|
+
dryrun: If True, show what would be deleted without actually deleting
|
|
117
|
+
force: If True, force deletion even with active subscriptions
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Response from backend with deletion details
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
httpx.HTTPStatusError: If deletion fails (404, 403, etc.)
|
|
124
|
+
"""
|
|
125
|
+
params = {}
|
|
126
|
+
if dryrun:
|
|
127
|
+
params["dryrun"] = "true"
|
|
128
|
+
if force:
|
|
129
|
+
params["force"] = "true"
|
|
130
|
+
|
|
131
|
+
return await self.delete(f"/publish/seller/{seller_name}", params=params)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@app.command("offerings")
|
|
135
|
+
def unpublish_offerings(
|
|
136
|
+
data_dir: Path | None = typer.Argument(
|
|
137
|
+
None,
|
|
138
|
+
help="Directory containing service offering files (default: current directory)",
|
|
139
|
+
),
|
|
140
|
+
services: str | None = typer.Option(
|
|
141
|
+
None,
|
|
142
|
+
"--services",
|
|
143
|
+
"-s",
|
|
144
|
+
help="Comma-separated list of service names to unpublish",
|
|
145
|
+
),
|
|
146
|
+
provider_name: str | None = typer.Option(
|
|
147
|
+
None,
|
|
148
|
+
"--provider",
|
|
149
|
+
"-p",
|
|
150
|
+
help="Unpublish offerings from specific provider",
|
|
151
|
+
),
|
|
152
|
+
dryrun: bool = typer.Option(
|
|
153
|
+
False,
|
|
154
|
+
"--dryrun",
|
|
155
|
+
help="Show what would be deleted without actually deleting",
|
|
156
|
+
),
|
|
157
|
+
force: bool = typer.Option(
|
|
158
|
+
False,
|
|
159
|
+
"--force",
|
|
160
|
+
help="Force deletion even with active subscriptions",
|
|
161
|
+
),
|
|
162
|
+
yes: bool = typer.Option(
|
|
163
|
+
False,
|
|
164
|
+
"--yes",
|
|
165
|
+
"-y",
|
|
166
|
+
help="Skip confirmation prompt",
|
|
167
|
+
),
|
|
168
|
+
):
|
|
169
|
+
"""Unpublish (delete) service offerings from backend.
|
|
170
|
+
|
|
171
|
+
This command reads offering files to get offering IDs, then deletes them from the backend.
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
# Dry-run to see what would be deleted
|
|
175
|
+
usvc unpublish offerings --services "gpt-4" --dryrun
|
|
176
|
+
|
|
177
|
+
# Delete specific offering
|
|
178
|
+
usvc unpublish offerings --services "gpt-4"
|
|
179
|
+
|
|
180
|
+
# Delete all offerings from a provider
|
|
181
|
+
usvc unpublish offerings --provider openai
|
|
182
|
+
|
|
183
|
+
# Force delete (ignore active subscriptions)
|
|
184
|
+
usvc unpublish offerings --services "gpt-4" --force --yes
|
|
185
|
+
"""
|
|
186
|
+
if data_dir is None:
|
|
187
|
+
data_dir = Path.cwd()
|
|
188
|
+
|
|
189
|
+
console.print(f"[cyan]Searching for offering files in {data_dir}...[/cyan]\n")
|
|
190
|
+
|
|
191
|
+
# Find all offering files
|
|
192
|
+
offering_files = []
|
|
193
|
+
for result in find_files_by_schema(data_dir, "service_v1"):
|
|
194
|
+
file_path, _format, _data = result
|
|
195
|
+
offering_files.append((file_path, _format))
|
|
196
|
+
|
|
197
|
+
if not offering_files:
|
|
198
|
+
console.print("[yellow]No offering files found[/yellow]")
|
|
199
|
+
raise typer.Exit(code=0)
|
|
200
|
+
|
|
201
|
+
# Load offerings and filter
|
|
202
|
+
offerings_to_delete = []
|
|
203
|
+
for file_path, _format in offering_files:
|
|
204
|
+
data, _ = load_data_file(file_path)
|
|
205
|
+
service_name = data.get("name", "Unknown")
|
|
206
|
+
offering_id = data.get("id")
|
|
207
|
+
provider = data.get("provider_name", "Unknown")
|
|
208
|
+
|
|
209
|
+
# Apply filters
|
|
210
|
+
if services:
|
|
211
|
+
service_list = [s.strip() for s in services.split(",")]
|
|
212
|
+
if service_name not in service_list:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
if provider_name and provider_name.lower() not in provider.lower():
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
if not offering_id:
|
|
219
|
+
console.print(f"[yellow]⚠ No offering ID found in {file_path}, skipping[/yellow]")
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
offerings_to_delete.append(
|
|
223
|
+
{
|
|
224
|
+
"id": offering_id,
|
|
225
|
+
"name": service_name,
|
|
226
|
+
"provider": provider,
|
|
227
|
+
"file_path": str(file_path),
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if not offerings_to_delete:
|
|
232
|
+
console.print("[yellow]No offerings found matching filters[/yellow]")
|
|
233
|
+
raise typer.Exit(code=0)
|
|
234
|
+
|
|
235
|
+
# Display what will be deleted
|
|
236
|
+
table = Table(title="Offerings to Unpublish")
|
|
237
|
+
table.add_column("Service Name", style="cyan")
|
|
238
|
+
table.add_column("Provider", style="blue")
|
|
239
|
+
table.add_column("Offering ID", style="white")
|
|
240
|
+
|
|
241
|
+
for offering in offerings_to_delete:
|
|
242
|
+
table.add_row(offering["name"], offering["provider"], offering["id"])
|
|
243
|
+
|
|
244
|
+
console.print(table)
|
|
245
|
+
console.print()
|
|
246
|
+
|
|
247
|
+
if dryrun:
|
|
248
|
+
console.print("[yellow]Dry-run mode: No actual deletion performed[/yellow]")
|
|
249
|
+
raise typer.Exit(code=0)
|
|
250
|
+
|
|
251
|
+
# Confirmation prompt
|
|
252
|
+
if not yes:
|
|
253
|
+
confirm = typer.confirm(
|
|
254
|
+
f"⚠️ Delete {len(offerings_to_delete)} offering(s) and all associated listings/subscriptions?"
|
|
255
|
+
)
|
|
256
|
+
if not confirm:
|
|
257
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
258
|
+
raise typer.Exit(code=0)
|
|
259
|
+
|
|
260
|
+
# Delete offerings
|
|
261
|
+
async def _delete_all():
|
|
262
|
+
unpublisher = ServiceDataUnpublisher()
|
|
263
|
+
results = []
|
|
264
|
+
for offering in offerings_to_delete:
|
|
265
|
+
try:
|
|
266
|
+
result = await unpublisher.delete_service_offering(
|
|
267
|
+
offering["id"],
|
|
268
|
+
dryrun=dryrun,
|
|
269
|
+
force=force,
|
|
270
|
+
)
|
|
271
|
+
results.append((offering, result, None))
|
|
272
|
+
except Exception as e:
|
|
273
|
+
results.append((offering, None, str(e)))
|
|
274
|
+
return results
|
|
275
|
+
|
|
276
|
+
results = asyncio.run(_delete_all())
|
|
277
|
+
|
|
278
|
+
# Display results
|
|
279
|
+
console.print("\n[cyan]Results:[/cyan]\n")
|
|
280
|
+
success_count = 0
|
|
281
|
+
error_count = 0
|
|
282
|
+
|
|
283
|
+
for offering, result, error in results:
|
|
284
|
+
if error:
|
|
285
|
+
console.print(f"[red]✗ {offering['name']}:[/red] {error}")
|
|
286
|
+
error_count += 1
|
|
287
|
+
else:
|
|
288
|
+
console.print(f"[green]✓ {offering['name']}:[/green] Deleted")
|
|
289
|
+
if result and result.get("cascade_deleted"):
|
|
290
|
+
cascade = result["cascade_deleted"]
|
|
291
|
+
if cascade.get("listings"):
|
|
292
|
+
console.print(f" [dim]→ Deleted {cascade['listings']} listing(s)[/dim]")
|
|
293
|
+
if cascade.get("subscriptions"):
|
|
294
|
+
console.print(f" [dim]→ Deleted {cascade['subscriptions']} subscription(s)[/dim]")
|
|
295
|
+
success_count += 1
|
|
296
|
+
|
|
297
|
+
console.print()
|
|
298
|
+
console.print(f"[green]✓ Success:[/green] {success_count}/{len(results)}")
|
|
299
|
+
if error_count > 0:
|
|
300
|
+
console.print(f"[red]✗ Failed:[/red] {error_count}/{len(results)}")
|
|
301
|
+
raise typer.Exit(code=1)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@app.command("listings")
|
|
305
|
+
def unpublish_listings(
|
|
306
|
+
listing_id: str = typer.Argument(..., help="Listing ID to unpublish"),
|
|
307
|
+
dryrun: bool = typer.Option(
|
|
308
|
+
False,
|
|
309
|
+
"--dryrun",
|
|
310
|
+
help="Show what would be deleted without actually deleting",
|
|
311
|
+
),
|
|
312
|
+
force: bool = typer.Option(
|
|
313
|
+
False,
|
|
314
|
+
"--force",
|
|
315
|
+
help="Force deletion even with active subscriptions",
|
|
316
|
+
),
|
|
317
|
+
yes: bool = typer.Option(
|
|
318
|
+
False,
|
|
319
|
+
"--yes",
|
|
320
|
+
"-y",
|
|
321
|
+
help="Skip confirmation prompt",
|
|
322
|
+
),
|
|
323
|
+
):
|
|
324
|
+
"""Unpublish (delete) a service listing from backend.
|
|
325
|
+
|
|
326
|
+
Examples:
|
|
327
|
+
# Dry-run
|
|
328
|
+
usvc unpublish listings abc-123 --dryrun
|
|
329
|
+
|
|
330
|
+
# Delete listing
|
|
331
|
+
usvc unpublish listings abc-123
|
|
332
|
+
|
|
333
|
+
# Force delete
|
|
334
|
+
usvc unpublish listings abc-123 --force --yes
|
|
335
|
+
"""
|
|
336
|
+
console.print(f"[cyan]Unpublishing listing {listing_id}...[/cyan]\n")
|
|
337
|
+
|
|
338
|
+
if not yes and not dryrun:
|
|
339
|
+
confirm = typer.confirm("⚠️ Delete this listing and all associated subscriptions?")
|
|
340
|
+
if not confirm:
|
|
341
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
342
|
+
raise typer.Exit(code=0)
|
|
343
|
+
|
|
344
|
+
async def _delete():
|
|
345
|
+
unpublisher = ServiceDataUnpublisher()
|
|
346
|
+
return await unpublisher.delete_service_listing(listing_id, dryrun=dryrun, force=force)
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
result = asyncio.run(_delete())
|
|
350
|
+
|
|
351
|
+
if dryrun:
|
|
352
|
+
console.print("[yellow]Dry-run mode: No actual deletion performed[/yellow]")
|
|
353
|
+
console.print(f"[dim]Would delete: {result}[/dim]")
|
|
354
|
+
else:
|
|
355
|
+
console.print(f"[green]✓ Successfully deleted listing {listing_id}[/green]")
|
|
356
|
+
if result.get("cascade_deleted"):
|
|
357
|
+
cascade = result["cascade_deleted"]
|
|
358
|
+
if cascade.get("subscriptions"):
|
|
359
|
+
console.print(f" [dim]→ Deleted {cascade['subscriptions']} subscription(s)[/dim]")
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
console.print(f"[red]✗ Failed to delete listing:[/red] {e}")
|
|
363
|
+
raise typer.Exit(code=1)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@app.command("providers")
|
|
367
|
+
def unpublish_providers(
|
|
368
|
+
provider_name: str = typer.Argument(..., help="Provider name to unpublish"),
|
|
369
|
+
dryrun: bool = typer.Option(
|
|
370
|
+
False,
|
|
371
|
+
"--dryrun",
|
|
372
|
+
help="Show what would be deleted without actually deleting",
|
|
373
|
+
),
|
|
374
|
+
force: bool = typer.Option(
|
|
375
|
+
False,
|
|
376
|
+
"--force",
|
|
377
|
+
help="Force deletion even with active subscriptions",
|
|
378
|
+
),
|
|
379
|
+
yes: bool = typer.Option(
|
|
380
|
+
False,
|
|
381
|
+
"--yes",
|
|
382
|
+
"-y",
|
|
383
|
+
help="Skip confirmation prompt",
|
|
384
|
+
),
|
|
385
|
+
):
|
|
386
|
+
"""Unpublish (delete) a provider from backend.
|
|
387
|
+
|
|
388
|
+
This will delete the provider and ALL associated offerings, listings, and subscriptions.
|
|
389
|
+
|
|
390
|
+
Examples:
|
|
391
|
+
# Dry-run
|
|
392
|
+
usvc unpublish providers openai --dryrun
|
|
393
|
+
|
|
394
|
+
# Delete provider
|
|
395
|
+
usvc unpublish providers openai
|
|
396
|
+
|
|
397
|
+
# Force delete
|
|
398
|
+
usvc unpublish providers openai --force --yes
|
|
399
|
+
"""
|
|
400
|
+
console.print(f"[cyan]Unpublishing provider {provider_name}...[/cyan]\n")
|
|
401
|
+
|
|
402
|
+
if not yes and not dryrun:
|
|
403
|
+
confirm = typer.confirm(
|
|
404
|
+
f"⚠️ Delete provider '{provider_name}' and ALL associated offerings/listings/subscriptions?"
|
|
405
|
+
)
|
|
406
|
+
if not confirm:
|
|
407
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
408
|
+
raise typer.Exit(code=0)
|
|
409
|
+
|
|
410
|
+
async def _delete():
|
|
411
|
+
unpublisher = ServiceDataUnpublisher()
|
|
412
|
+
return await unpublisher.delete_provider(provider_name, dryrun=dryrun, force=force)
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
result = asyncio.run(_delete())
|
|
416
|
+
|
|
417
|
+
if dryrun:
|
|
418
|
+
console.print("[yellow]Dry-run mode: No actual deletion performed[/yellow]")
|
|
419
|
+
console.print(f"[dim]Would delete: {result}[/dim]")
|
|
420
|
+
else:
|
|
421
|
+
console.print(f"[green]✓ Successfully deleted provider {provider_name}[/green]")
|
|
422
|
+
if result.get("cascade_deleted"):
|
|
423
|
+
cascade = result["cascade_deleted"]
|
|
424
|
+
if cascade.get("offerings"):
|
|
425
|
+
console.print(f" [dim]→ Deleted {cascade['offerings']} offering(s)[/dim]")
|
|
426
|
+
if cascade.get("listings"):
|
|
427
|
+
console.print(f" [dim]→ Deleted {cascade['listings']} listing(s)[/dim]")
|
|
428
|
+
if cascade.get("subscriptions"):
|
|
429
|
+
console.print(f" [dim]→ Deleted {cascade['subscriptions']} subscription(s)[/dim]")
|
|
430
|
+
|
|
431
|
+
except Exception as e:
|
|
432
|
+
console.print(f"[red]✗ Failed to delete provider:[/red] {e}")
|
|
433
|
+
raise typer.Exit(code=1)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@app.command("sellers")
|
|
437
|
+
def unpublish_sellers(
|
|
438
|
+
seller_name: str = typer.Argument(..., help="Seller name to unpublish"),
|
|
439
|
+
dryrun: bool = typer.Option(
|
|
440
|
+
False,
|
|
441
|
+
"--dryrun",
|
|
442
|
+
help="Show what would be deleted without actually deleting",
|
|
443
|
+
),
|
|
444
|
+
force: bool = typer.Option(
|
|
445
|
+
False,
|
|
446
|
+
"--force",
|
|
447
|
+
help="Force deletion even with active subscriptions",
|
|
448
|
+
),
|
|
449
|
+
yes: bool = typer.Option(
|
|
450
|
+
False,
|
|
451
|
+
"--yes",
|
|
452
|
+
"-y",
|
|
453
|
+
help="Skip confirmation prompt",
|
|
454
|
+
),
|
|
455
|
+
):
|
|
456
|
+
"""Unpublish (delete) a seller from backend.
|
|
457
|
+
|
|
458
|
+
This will delete the seller and ALL associated providers, offerings, listings, and subscriptions.
|
|
459
|
+
|
|
460
|
+
Examples:
|
|
461
|
+
# Dry-run
|
|
462
|
+
usvc unpublish sellers my-company --dryrun
|
|
463
|
+
|
|
464
|
+
# Delete seller
|
|
465
|
+
usvc unpublish sellers my-company
|
|
466
|
+
|
|
467
|
+
# Force delete
|
|
468
|
+
usvc unpublish sellers my-company --force --yes
|
|
469
|
+
"""
|
|
470
|
+
console.print(f"[cyan]Unpublishing seller {seller_name}...[/cyan]\n")
|
|
471
|
+
|
|
472
|
+
if not yes and not dryrun:
|
|
473
|
+
confirm = typer.confirm(
|
|
474
|
+
f"⚠️ Delete seller '{seller_name}' and ALL associated providers/offerings/listings/subscriptions?"
|
|
475
|
+
)
|
|
476
|
+
if not confirm:
|
|
477
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
478
|
+
raise typer.Exit(code=0)
|
|
479
|
+
|
|
480
|
+
async def _delete():
|
|
481
|
+
unpublisher = ServiceDataUnpublisher()
|
|
482
|
+
return await unpublisher.delete_seller(seller_name, dryrun=dryrun, force=force)
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
result = asyncio.run(_delete())
|
|
486
|
+
|
|
487
|
+
if dryrun:
|
|
488
|
+
console.print("[yellow]Dry-run mode: No actual deletion performed[/yellow]")
|
|
489
|
+
console.print(f"[dim]Would delete: {result}[/dim]")
|
|
490
|
+
else:
|
|
491
|
+
console.print(f"[green]✓ Successfully deleted seller {seller_name}[/green]")
|
|
492
|
+
if result.get("cascade_deleted"):
|
|
493
|
+
cascade = result["cascade_deleted"]
|
|
494
|
+
if cascade.get("providers"):
|
|
495
|
+
console.print(f" [dim]→ Deleted {cascade['providers']} provider(s)[/dim]")
|
|
496
|
+
if cascade.get("offerings"):
|
|
497
|
+
console.print(f" [dim]→ Deleted {cascade['offerings']} offering(s)[/dim]")
|
|
498
|
+
if cascade.get("listings"):
|
|
499
|
+
console.print(f" [dim]→ Deleted {cascade['listings']} listing(s)[/dim]")
|
|
500
|
+
if cascade.get("subscriptions"):
|
|
501
|
+
console.print(f" [dim]→ Deleted {cascade['subscriptions']} subscription(s)[/dim]")
|
|
502
|
+
|
|
503
|
+
except Exception as e:
|
|
504
|
+
console.print(f"[red]✗ Failed to delete seller:[/red] {e}")
|
|
505
|
+
raise typer.Exit(code=1)
|