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.
Files changed (37) hide show
  1. unitysvc_services/__init__.py +4 -0
  2. unitysvc_services/api.py +421 -0
  3. unitysvc_services/cli.py +23 -0
  4. unitysvc_services/format_data.py +140 -0
  5. unitysvc_services/interactive_prompt.py +1132 -0
  6. unitysvc_services/list.py +216 -0
  7. unitysvc_services/models/__init__.py +71 -0
  8. unitysvc_services/models/base.py +1375 -0
  9. unitysvc_services/models/listing_data.py +118 -0
  10. unitysvc_services/models/listing_v1.py +56 -0
  11. unitysvc_services/models/provider_data.py +79 -0
  12. unitysvc_services/models/provider_v1.py +54 -0
  13. unitysvc_services/models/seller_data.py +120 -0
  14. unitysvc_services/models/seller_v1.py +42 -0
  15. unitysvc_services/models/service_data.py +114 -0
  16. unitysvc_services/models/service_v1.py +81 -0
  17. unitysvc_services/populate.py +207 -0
  18. unitysvc_services/publisher.py +1628 -0
  19. unitysvc_services/py.typed +0 -0
  20. unitysvc_services/query.py +688 -0
  21. unitysvc_services/scaffold.py +1103 -0
  22. unitysvc_services/schema/base.json +777 -0
  23. unitysvc_services/schema/listing_v1.json +1286 -0
  24. unitysvc_services/schema/provider_v1.json +952 -0
  25. unitysvc_services/schema/seller_v1.json +379 -0
  26. unitysvc_services/schema/service_v1.json +1306 -0
  27. unitysvc_services/test.py +965 -0
  28. unitysvc_services/unpublisher.py +505 -0
  29. unitysvc_services/update.py +287 -0
  30. unitysvc_services/utils.py +533 -0
  31. unitysvc_services/validator.py +731 -0
  32. unitysvc_services-0.1.24.dist-info/METADATA +184 -0
  33. unitysvc_services-0.1.24.dist-info/RECORD +37 -0
  34. unitysvc_services-0.1.24.dist-info/WHEEL +5 -0
  35. unitysvc_services-0.1.24.dist-info/entry_points.txt +3 -0
  36. unitysvc_services-0.1.24.dist-info/licenses/LICENSE +21 -0
  37. unitysvc_services-0.1.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,287 @@
1
+ """Update command group - update local data files."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import typer
7
+ from rich.console import Console
8
+
9
+ from .utils import find_file_by_schema_and_name, find_files_by_schema, write_data_file
10
+
11
+ app = typer.Typer(help="Update local data files")
12
+ console = Console()
13
+
14
+
15
+ @app.command("offering")
16
+ def update_offering(
17
+ name: str = typer.Option(
18
+ ...,
19
+ "--name",
20
+ "-n",
21
+ help="Name of the service offering to update (matches 'name' field in service file)",
22
+ ),
23
+ status: str | None = typer.Option(
24
+ None,
25
+ "--status",
26
+ "-s",
27
+ help="New upstream_status (uploading, ready, deprecated)",
28
+ ),
29
+ display_name: str | None = typer.Option(
30
+ None,
31
+ "--display-name",
32
+ help="New display name for the offering",
33
+ ),
34
+ description: str | None = typer.Option(
35
+ None,
36
+ "--description",
37
+ help="New description for the offering",
38
+ ),
39
+ version: str | None = typer.Option(
40
+ None,
41
+ "--version",
42
+ help="New version for the offering",
43
+ ),
44
+ data_dir: Path | None = typer.Option(
45
+ None,
46
+ "--data-dir",
47
+ "-d",
48
+ help="Directory containing data files (default: current directory)",
49
+ ),
50
+ ):
51
+ """
52
+ Update fields in a service offering's local data file.
53
+
54
+ Searches for files with schema 'service_v1' by offering name and updates the specified fields.
55
+
56
+ Allowed upstream_status values:
57
+ - uploading: Service is being uploaded (not ready)
58
+ - ready: Service is ready to be used
59
+ - deprecated: Service is deprecated from upstream
60
+ """
61
+ # Validate status if provided
62
+ if status:
63
+ valid_statuses = ["uploading", "ready", "deprecated"]
64
+ if status not in valid_statuses:
65
+ console.print(
66
+ f"[red]✗[/red] Invalid status: {status}",
67
+ style="bold red",
68
+ )
69
+ console.print(f"[yellow]Allowed statuses:[/yellow] {', '.join(valid_statuses)}")
70
+ raise typer.Exit(code=1)
71
+
72
+ # Check if any update field is provided
73
+ if not any([status, display_name, description, version]):
74
+ console.print(
75
+ (
76
+ "[red]✗[/red] No fields to update. Provide at least one of: "
77
+ "--status, --display-name, --description, --version"
78
+ ),
79
+ style="bold red",
80
+ )
81
+ raise typer.Exit(code=1)
82
+
83
+ # Set data directory
84
+ if data_dir is None:
85
+ data_dir = Path.cwd()
86
+
87
+ if not data_dir.is_absolute():
88
+ data_dir = Path.cwd() / data_dir
89
+
90
+ if not data_dir.exists():
91
+ console.print(f"[red]✗[/red] Data directory not found: {data_dir}", style="bold red")
92
+ raise typer.Exit(code=1)
93
+
94
+ console.print(f"[blue]Searching for offering:[/blue] {name}")
95
+ console.print(f"[blue]In directory:[/blue] {data_dir}\n")
96
+
97
+ # Find the matching offering file
98
+ result = find_file_by_schema_and_name(data_dir, "service_v1", "name", name)
99
+
100
+ if not result:
101
+ console.print(
102
+ f"[red]✗[/red] No offering found with name: {name}",
103
+ style="bold red",
104
+ )
105
+ raise typer.Exit(code=1)
106
+
107
+ matching_file, matching_format, data = result
108
+
109
+ # Update the fields
110
+ try:
111
+ updates: dict[str, tuple[Any, Any]] = {} # field: (old_value, new_value)
112
+
113
+ if status:
114
+ updates["upstream_status"] = (data.get("upstream_status", "unknown"), status)
115
+ data["upstream_status"] = status
116
+
117
+ if display_name:
118
+ updates["display_name"] = (data.get("display_name", ""), display_name)
119
+ data["display_name"] = display_name
120
+
121
+ if description:
122
+ updates["description"] = (data.get("description", ""), description)
123
+ data["description"] = description
124
+
125
+ if version:
126
+ updates["version"] = (data.get("version", ""), version)
127
+ data["version"] = version
128
+
129
+ # Write back in same format
130
+ write_data_file(matching_file, data, matching_format)
131
+
132
+ console.print("[green]✓[/green] Updated offering successfully!")
133
+ console.print(f"[cyan]File:[/cyan] {matching_file.relative_to(data_dir)}")
134
+ console.print(f"[cyan]Format:[/cyan] {matching_format.upper()}\n")
135
+
136
+ for field, (old, new) in updates.items():
137
+ console.print(f"[cyan]{field}:[/cyan]")
138
+ if len(str(old)) > 60 or len(str(new)) > 60:
139
+ console.print(f" [dim]Old:[/dim] {str(old)[:60]}...")
140
+ console.print(f" [dim]New:[/dim] {str(new)[:60]}...")
141
+ else:
142
+ console.print(f" [dim]Old:[/dim] {old}")
143
+ console.print(f" [dim]New:[/dim] {new}")
144
+
145
+ except Exception as e:
146
+ console.print(
147
+ f"[red]✗[/red] Failed to update offering: {e}",
148
+ style="bold red",
149
+ )
150
+ raise typer.Exit(code=1)
151
+
152
+
153
+ @app.command("listing")
154
+ def update_listing(
155
+ services: str = typer.Option(
156
+ ...,
157
+ "--services",
158
+ "-n",
159
+ help="Name of the service (to search for listing files in service directory)",
160
+ ),
161
+ status: str | None = typer.Option(
162
+ None,
163
+ "--status",
164
+ "-s",
165
+ help=(
166
+ "New listing_status (unknown, upstream_ready, downstream_ready, "
167
+ "ready, in_service, upstream_deprecated, deprecated)"
168
+ ),
169
+ ),
170
+ seller: str | None = typer.Option(
171
+ None,
172
+ "--seller",
173
+ help="Seller name to filter listings (updates only matching seller's listing)",
174
+ ),
175
+ data_dir: Path | None = typer.Option(
176
+ None,
177
+ "--data-dir",
178
+ "-d",
179
+ help="Directory containing data files (default: current directory)",
180
+ ),
181
+ ):
182
+ """
183
+ Update fields in service listing(s) local data files.
184
+
185
+ Searches for files with schema 'listing_v1' in the service directory and updates the specified fields.
186
+
187
+ Allowed listing_status values:
188
+ - unknown: Not yet determined
189
+ - upstream_ready: Upstream is ready to be used
190
+ - downstream_ready: Downstream is ready with proper routing, logging, and billing
191
+ - ready: Operationally ready (with docs, metrics, and pricing)
192
+ - in_service: Service is in service
193
+ - upstream_deprecated: Service is deprecated from upstream
194
+ - deprecated: Service is no longer offered to users
195
+ """
196
+ # Validate status if provided
197
+ if status:
198
+ valid_statuses = [
199
+ "unknown",
200
+ "upstream_ready",
201
+ "downstream_ready",
202
+ "ready",
203
+ "in_service",
204
+ "upstream_deprecated",
205
+ "deprecated",
206
+ ]
207
+ if status not in valid_statuses:
208
+ console.print(
209
+ f"[red]✗[/red] Invalid status: {status}",
210
+ style="bold red",
211
+ )
212
+ console.print(f"[yellow]Allowed statuses:[/yellow] {', '.join(valid_statuses)}")
213
+ raise typer.Exit(code=1)
214
+
215
+ # Check if any update field is provided
216
+ if not status:
217
+ console.print(
218
+ "[red]✗[/red] No fields to update. Provide at least one of: --status",
219
+ style="bold red",
220
+ )
221
+ raise typer.Exit(code=1)
222
+
223
+ # Set data directory
224
+ if data_dir is None:
225
+ data_dir = Path.cwd()
226
+
227
+ if not data_dir.is_absolute():
228
+ data_dir = Path.cwd() / data_dir
229
+
230
+ if not data_dir.exists():
231
+ console.print(f"[red]✗[/red] Data directory not found: {data_dir}", style="bold red")
232
+ raise typer.Exit(code=1)
233
+
234
+ console.print(f"[blue]Searching for service:[/blue] {services}")
235
+ console.print(f"[blue]In directory:[/blue] {data_dir}")
236
+ if seller:
237
+ console.print(f"[blue]Filtering by seller:[/blue] {seller}")
238
+ console.print()
239
+
240
+ # Build field filter
241
+ field_filter = {}
242
+ if seller:
243
+ field_filter["seller_name"] = seller
244
+
245
+ # Convert field_filter dict to tuple for caching
246
+ field_filter_tuple = tuple(sorted(field_filter.items())) if field_filter else None
247
+
248
+ # Find listing files matching criteria
249
+ listing_files = find_files_by_schema(data_dir, "listing_v1", path_filter=services, field_filter=field_filter_tuple)
250
+
251
+ if not listing_files:
252
+ console.print(
253
+ "[red]✗[/red] No listing files found matching criteria",
254
+ style="bold red",
255
+ )
256
+ raise typer.Exit(code=1)
257
+
258
+ # Update all matching listings
259
+ updated_count = 0
260
+ for listing_file, file_format, data in listing_files:
261
+ try:
262
+ old_status = data.get("listing_status", "unknown")
263
+ if status:
264
+ data["listing_status"] = status
265
+
266
+ # Write back in same format
267
+ write_data_file(listing_file, data, file_format)
268
+
269
+ console.print(f"[green]✓[/green] Updated: {listing_file.relative_to(data_dir)}")
270
+ console.print(f" [dim]Seller: {data.get('seller_name', 'N/A')}[/dim]")
271
+ console.print(f" [dim]Format: {file_format.upper()}[/dim]")
272
+ if status:
273
+ console.print(f" [dim]Old status: {old_status} → New status: {status}[/dim]")
274
+ console.print()
275
+ updated_count += 1
276
+
277
+ except Exception as e:
278
+ console.print(
279
+ f"[red]✗[/red] Failed to update {listing_file.relative_to(data_dir)}: {e}",
280
+ style="bold red",
281
+ )
282
+
283
+ if updated_count > 0:
284
+ console.print(f"[green]✓[/green] Successfully updated {updated_count} listing(s)")
285
+ else:
286
+ console.print("[red]✗[/red] No listings were updated", style="bold red")
287
+ raise typer.Exit(code=1)