unitysvc-services 0.1.4__py3-none-any.whl → 0.1.6__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 +58 -15
- unitysvc_services/cli.py +2 -1
- unitysvc_services/populate.py +18 -0
- unitysvc_services/publisher.py +379 -199
- unitysvc_services/query.py +310 -302
- unitysvc_services/test.py +796 -0
- unitysvc_services/utils.py +118 -2
- unitysvc_services/validator.py +19 -7
- {unitysvc_services-0.1.4.dist-info → unitysvc_services-0.1.6.dist-info}/METADATA +40 -33
- {unitysvc_services-0.1.4.dist-info → unitysvc_services-0.1.6.dist-info}/RECORD +14 -13
- {unitysvc_services-0.1.4.dist-info → unitysvc_services-0.1.6.dist-info}/entry_points.txt +1 -0
- {unitysvc_services-0.1.4.dist-info → unitysvc_services-0.1.6.dist-info}/WHEEL +0 -0
- {unitysvc_services-0.1.4.dist-info → unitysvc_services-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {unitysvc_services-0.1.4.dist-info → unitysvc_services-0.1.6.dist-info}/top_level.txt +0 -0
unitysvc_services/publisher.py
CHANGED
@@ -14,7 +14,7 @@ from rich.console import Console
|
|
14
14
|
|
15
15
|
from .api import UnitySvcAPI
|
16
16
|
from .models.base import ProviderStatusEnum, SellerStatusEnum
|
17
|
-
from .utils import convert_convenience_fields_to_documents, find_files_by_schema
|
17
|
+
from .utils import convert_convenience_fields_to_documents, find_files_by_schema, render_template_file
|
18
18
|
from .validator import DataValidator
|
19
19
|
|
20
20
|
|
@@ -59,34 +59,131 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
59
59
|
with open(full_path, "rb") as f:
|
60
60
|
return base64.b64encode(f.read()).decode("ascii")
|
61
61
|
|
62
|
-
def resolve_file_references(
|
63
|
-
|
62
|
+
def resolve_file_references(
|
63
|
+
self,
|
64
|
+
data: dict[str, Any],
|
65
|
+
base_path: Path,
|
66
|
+
listing: dict[str, Any] | None = None,
|
67
|
+
offering: dict[str, Any] | None = None,
|
68
|
+
provider: dict[str, Any] | None = None,
|
69
|
+
seller: dict[str, Any] | None = None,
|
70
|
+
listing_filename: str | None = None,
|
71
|
+
) -> dict[str, Any]:
|
72
|
+
"""Recursively resolve file references and include content in data.
|
73
|
+
|
74
|
+
For Jinja2 template files (.j2), renders the template with provided context
|
75
|
+
and strips the .j2 extension from file_path.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
data: Data dictionary potentially containing file_path references
|
79
|
+
base_path: Base path for resolving relative file paths
|
80
|
+
listing: Listing data for template rendering (optional)
|
81
|
+
offering: Offering data for template rendering (optional)
|
82
|
+
provider: Provider data for template rendering (optional)
|
83
|
+
seller: Seller data for template rendering (optional)
|
84
|
+
listing_filename: Listing filename for constructing output filenames (optional)
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
Data with file references resolved and content loaded
|
88
|
+
"""
|
64
89
|
result: dict[str, Any] = {}
|
65
90
|
|
66
91
|
for key, value in data.items():
|
67
92
|
if isinstance(value, dict):
|
68
93
|
# Recursively process nested dictionaries
|
69
|
-
result[key] = self.resolve_file_references(
|
94
|
+
result[key] = self.resolve_file_references(
|
95
|
+
value,
|
96
|
+
base_path,
|
97
|
+
listing=listing,
|
98
|
+
offering=offering,
|
99
|
+
provider=provider,
|
100
|
+
seller=seller,
|
101
|
+
listing_filename=listing_filename,
|
102
|
+
)
|
70
103
|
elif isinstance(value, list):
|
71
104
|
# Process lists
|
72
105
|
result[key] = [
|
73
|
-
(
|
106
|
+
(
|
107
|
+
self.resolve_file_references(
|
108
|
+
item,
|
109
|
+
base_path,
|
110
|
+
listing=listing,
|
111
|
+
offering=offering,
|
112
|
+
provider=provider,
|
113
|
+
seller=seller,
|
114
|
+
listing_filename=listing_filename,
|
115
|
+
)
|
116
|
+
if isinstance(item, dict)
|
117
|
+
else item
|
118
|
+
)
|
74
119
|
for item in value
|
75
120
|
]
|
76
121
|
elif key == "file_path" and isinstance(value, str):
|
77
|
-
# This is a file reference - load the content
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
122
|
+
# This is a file reference - load the content and render if template
|
123
|
+
full_path = base_path / value if not Path(value).is_absolute() else Path(value)
|
124
|
+
|
125
|
+
if not full_path.exists():
|
126
|
+
raise FileNotFoundError(f"File not found: {full_path}")
|
127
|
+
|
128
|
+
# Render template if applicable
|
129
|
+
try:
|
130
|
+
content, actual_filename = render_template_file(
|
131
|
+
full_path,
|
132
|
+
listing=listing,
|
133
|
+
offering=offering,
|
134
|
+
provider=provider,
|
135
|
+
seller=seller,
|
136
|
+
)
|
137
|
+
result["file_content"] = content
|
138
|
+
|
139
|
+
# Update file_path to remove .j2 extension if it was a template
|
140
|
+
if full_path.name.endswith(".j2"):
|
141
|
+
# Strip .j2 from the path
|
142
|
+
new_path = str(value)[:-3] # Remove last 3 characters (.j2)
|
143
|
+
result[key] = new_path
|
144
|
+
else:
|
145
|
+
result[key] = value
|
146
|
+
|
147
|
+
except Exception as e:
|
148
|
+
raise ValueError(f"Failed to load/render file content from '{value}': {e}")
|
87
149
|
else:
|
88
150
|
result[key] = value
|
89
151
|
|
152
|
+
# After processing all fields, check if this is a code_examples document
|
153
|
+
# If so, try to load corresponding .out file and add to meta.output
|
154
|
+
if result.get("category") == "code_examples" and result.get("file_content") and listing_filename:
|
155
|
+
# Get the actual filename (after .j2 stripping if applicable)
|
156
|
+
# If file_path was updated (e.g., from "test.py.j2" to "test.py"), use that
|
157
|
+
# Otherwise, extract basename from original file_path
|
158
|
+
output_base_filename: str | None = None
|
159
|
+
|
160
|
+
# Check if file_path was modified (original might have had .j2)
|
161
|
+
file_path_value = result.get("file_path", "")
|
162
|
+
if file_path_value:
|
163
|
+
output_base_filename = Path(file_path_value).name
|
164
|
+
|
165
|
+
if output_base_filename:
|
166
|
+
# Construct output filename: {listing_stem}_{output_base_filename}.out
|
167
|
+
# e.g., "svclisting_test.py.out" for svclisting.json and test.py
|
168
|
+
listing_stem = Path(listing_filename).stem
|
169
|
+
output_filename = f"{listing_stem}_{output_base_filename}.out"
|
170
|
+
|
171
|
+
# Try to find the .out file in base_path (listing directory)
|
172
|
+
output_path = base_path / output_filename
|
173
|
+
|
174
|
+
if output_path.exists():
|
175
|
+
try:
|
176
|
+
with open(output_path, encoding="utf-8") as f:
|
177
|
+
output_content = f.read()
|
178
|
+
|
179
|
+
# Add output to meta field
|
180
|
+
if "meta" not in result or result["meta"] is None:
|
181
|
+
result["meta"] = {}
|
182
|
+
result["meta"]["output"] = output_content
|
183
|
+
except Exception:
|
184
|
+
# Don't fail if output file can't be read, just skip it
|
185
|
+
pass
|
186
|
+
|
90
187
|
return result
|
91
188
|
|
92
189
|
async def post( # type: ignore[override]
|
@@ -242,16 +339,15 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
242
339
|
if "name" not in data or not data.get("name"):
|
243
340
|
data["name"] = listing_file.stem
|
244
341
|
|
245
|
-
#
|
246
|
-
base_path = listing_file.parent
|
247
|
-
data_with_content = self.resolve_file_references(data, base_path)
|
248
|
-
|
249
|
-
# Extract provider_name from directory structure
|
342
|
+
# Extract provider_name from directory structure (needed before loading provider data)
|
250
343
|
parts = listing_file.parts
|
251
344
|
try:
|
252
345
|
services_idx = parts.index("services")
|
253
346
|
provider_name = parts[services_idx - 1]
|
254
|
-
|
347
|
+
data["provider_name"] = provider_name
|
348
|
+
|
349
|
+
# Find provider directory to load provider data
|
350
|
+
provider_dir = Path(*parts[:services_idx])
|
255
351
|
except (ValueError, IndexError):
|
256
352
|
raise ValueError(
|
257
353
|
f"Cannot extract provider_name from path: {listing_file}. "
|
@@ -259,7 +355,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
259
355
|
)
|
260
356
|
|
261
357
|
# If service_name is not in listing data, find it from service files in the same directory
|
262
|
-
if "service_name" not in
|
358
|
+
if "service_name" not in data or not data["service_name"]:
|
263
359
|
# Find all service files in the same directory
|
264
360
|
service_files = find_files_by_schema(listing_file.parent, "service_v1")
|
265
361
|
|
@@ -269,7 +365,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
269
365
|
f"Listing files must be in the same directory as a service definition."
|
270
366
|
)
|
271
367
|
elif len(service_files) > 1:
|
272
|
-
service_names = [
|
368
|
+
service_names = [svc_data.get("name", "unknown") for _, _, svc_data in service_files]
|
273
369
|
raise ValueError(
|
274
370
|
f"Multiple services found in {listing_file.parent}: {', '.join(service_names)}. "
|
275
371
|
f"Please add 'service_name' field to {listing_file.name} to specify which "
|
@@ -278,11 +374,11 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
278
374
|
else:
|
279
375
|
# Exactly one service found - use it
|
280
376
|
_service_file, _format, service_data = service_files[0]
|
281
|
-
|
282
|
-
|
377
|
+
data["service_name"] = service_data.get("name")
|
378
|
+
data["service_version"] = service_data.get("version")
|
283
379
|
else:
|
284
380
|
# service_name is provided in listing data, find the matching service to get version
|
285
|
-
service_name =
|
381
|
+
service_name = data["service_name"]
|
286
382
|
service_files = find_files_by_schema(
|
287
383
|
listing_file.parent, "service_v1", field_filter=(("name", service_name),)
|
288
384
|
)
|
@@ -294,7 +390,14 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
294
390
|
|
295
391
|
# Get version from the found service
|
296
392
|
_service_file, _format, service_data = service_files[0]
|
297
|
-
|
393
|
+
data["service_version"] = service_data.get("version")
|
394
|
+
|
395
|
+
# Load provider data for template rendering
|
396
|
+
provider_files = find_files_by_schema(provider_dir, "provider_v1")
|
397
|
+
if provider_files:
|
398
|
+
_provider_file, _format, provider_data = provider_files[0]
|
399
|
+
else:
|
400
|
+
provider_data = {}
|
298
401
|
|
299
402
|
# Find seller_name from seller definition in the data directory
|
300
403
|
# Navigate up to find the data directory and look for seller file
|
@@ -332,11 +435,23 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
332
435
|
if not seller_name:
|
333
436
|
raise ValueError("Seller data missing 'name' field")
|
334
437
|
|
335
|
-
|
438
|
+
data["seller_name"] = seller_name
|
336
439
|
|
337
440
|
# Map listing_status to status if present
|
338
|
-
if "listing_status" in
|
339
|
-
|
441
|
+
if "listing_status" in data:
|
442
|
+
data["status"] = data.pop("listing_status")
|
443
|
+
|
444
|
+
# NOW resolve file references with all context (listing, offering, provider, seller)
|
445
|
+
base_path = listing_file.parent
|
446
|
+
data_with_content = self.resolve_file_references(
|
447
|
+
data,
|
448
|
+
base_path,
|
449
|
+
listing=data,
|
450
|
+
offering=service_data,
|
451
|
+
provider=provider_data,
|
452
|
+
seller=seller_data,
|
453
|
+
listing_filename=listing_file.name,
|
454
|
+
)
|
340
455
|
|
341
456
|
# Post to the endpoint using retry helper
|
342
457
|
context_info = (
|
@@ -344,7 +459,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
344
459
|
f"provider: {data_with_content.get('provider_name')}, "
|
345
460
|
f"seller: {data_with_content.get('seller_name')}"
|
346
461
|
)
|
347
|
-
|
462
|
+
result = await self._post_with_retry(
|
348
463
|
endpoint="/publish/listing",
|
349
464
|
data=data_with_content,
|
350
465
|
entity_type="listing",
|
@@ -353,29 +468,33 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
353
468
|
max_retries=max_retries,
|
354
469
|
)
|
355
470
|
|
471
|
+
# Add local metadata to result for display purposes
|
472
|
+
result["service_name"] = data_with_content.get("service_name")
|
473
|
+
result["provider_name"] = data_with_content.get("provider_name")
|
474
|
+
result["seller_name"] = data_with_content.get("seller_name")
|
475
|
+
|
476
|
+
return result
|
477
|
+
|
356
478
|
async def post_service_offering_async(self, data_file: Path, max_retries: int = 3) -> dict[str, Any]:
|
357
479
|
"""Async version of post_service_offering for concurrent publishing with retry logic."""
|
358
480
|
# Load the data file
|
359
481
|
data = self.load_data_file(data_file)
|
360
482
|
|
361
|
-
#
|
483
|
+
# Convert convenience fields first
|
362
484
|
base_path = data_file.parent
|
363
485
|
data = convert_convenience_fields_to_documents(
|
364
486
|
data, base_path, logo_field="logo", terms_field="terms_of_service"
|
365
487
|
)
|
366
488
|
|
367
|
-
# Resolve file references and include content
|
368
|
-
data_with_content = self.resolve_file_references(data, base_path)
|
369
|
-
|
370
489
|
# Extract provider_name from directory structure
|
371
490
|
# Find the 'services' directory and use its parent as provider_name
|
372
491
|
parts = data_file.parts
|
373
492
|
try:
|
374
493
|
services_idx = parts.index("services")
|
375
494
|
provider_name = parts[services_idx - 1]
|
376
|
-
|
495
|
+
data["provider_name"] = provider_name
|
377
496
|
|
378
|
-
# Find provider directory to check status
|
497
|
+
# Find provider directory to check status and load data
|
379
498
|
provider_dir = Path(*parts[:services_idx])
|
380
499
|
except (ValueError, IndexError):
|
381
500
|
raise ValueError(
|
@@ -383,7 +502,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
383
502
|
f"Expected path to contain .../{{provider_name}}/services/..."
|
384
503
|
)
|
385
504
|
|
386
|
-
#
|
505
|
+
# Load provider data for status check and template rendering
|
387
506
|
provider_files = find_files_by_schema(provider_dir, "provider_v1")
|
388
507
|
if provider_files:
|
389
508
|
# Should only be one provider file in the directory
|
@@ -395,10 +514,22 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
395
514
|
"reason": f"Provider status is '{provider_status}' - not publishing offering to backend",
|
396
515
|
"name": data.get("name", "unknown"),
|
397
516
|
}
|
517
|
+
else:
|
518
|
+
provider_data = {}
|
519
|
+
|
520
|
+
# NOW resolve file references with all context (offering, provider)
|
521
|
+
data_with_content = self.resolve_file_references(
|
522
|
+
data,
|
523
|
+
base_path,
|
524
|
+
listing=None,
|
525
|
+
offering=data,
|
526
|
+
provider=provider_data,
|
527
|
+
seller=None,
|
528
|
+
)
|
398
529
|
|
399
530
|
# Post to the endpoint using retry helper
|
400
531
|
context_info = f"provider: {data_with_content.get('provider_name')}"
|
401
|
-
|
532
|
+
result = await self._post_with_retry(
|
402
533
|
endpoint="/publish/offering",
|
403
534
|
data=data_with_content,
|
404
535
|
entity_type="offering",
|
@@ -407,6 +538,11 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
407
538
|
max_retries=max_retries,
|
408
539
|
)
|
409
540
|
|
541
|
+
# Add local metadata to result for display purposes
|
542
|
+
result["provider_name"] = data_with_content.get("provider_name")
|
543
|
+
|
544
|
+
return result
|
545
|
+
|
410
546
|
async def post_provider_async(self, data_file: Path, max_retries: int = 3) -> dict[str, Any]:
|
411
547
|
"""Async version of post_provider for concurrent publishing with retry logic."""
|
412
548
|
# Load the data file
|
@@ -428,8 +564,15 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
428
564
|
data, base_path, logo_field="logo", terms_field="terms_of_service"
|
429
565
|
)
|
430
566
|
|
431
|
-
# Resolve file references and include content
|
432
|
-
data_with_content = self.resolve_file_references(
|
567
|
+
# Resolve file references and include content with provider context
|
568
|
+
data_with_content = self.resolve_file_references(
|
569
|
+
data,
|
570
|
+
base_path,
|
571
|
+
listing=None,
|
572
|
+
offering=None,
|
573
|
+
provider=data,
|
574
|
+
seller=None,
|
575
|
+
)
|
433
576
|
|
434
577
|
# Post to the endpoint using retry helper
|
435
578
|
return await self._post_with_retry(
|
@@ -459,8 +602,15 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
459
602
|
base_path = data_file.parent
|
460
603
|
data = convert_convenience_fields_to_documents(data, base_path, logo_field="logo", terms_field=None)
|
461
604
|
|
462
|
-
# Resolve file references and include content
|
463
|
-
data_with_content = self.resolve_file_references(
|
605
|
+
# Resolve file references and include content with seller context
|
606
|
+
data_with_content = self.resolve_file_references(
|
607
|
+
data,
|
608
|
+
base_path,
|
609
|
+
listing=None,
|
610
|
+
offering=None,
|
611
|
+
provider=None,
|
612
|
+
seller=data,
|
613
|
+
)
|
464
614
|
|
465
615
|
# Post to the endpoint using retry helper
|
466
616
|
return await self._post_with_retry(
|
@@ -837,14 +987,6 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
837
987
|
|
838
988
|
return all_results
|
839
989
|
|
840
|
-
def __enter__(self):
|
841
|
-
"""Sync context manager entry for CLI usage."""
|
842
|
-
return self
|
843
|
-
|
844
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
845
|
-
"""Sync context manager exit for CLI usage."""
|
846
|
-
asyncio.run(self.aclose())
|
847
|
-
|
848
990
|
|
849
991
|
# CLI commands for publishing
|
850
992
|
app = typer.Typer(help="Publish data to backend")
|
@@ -896,60 +1038,62 @@ def publish_callback(
|
|
896
1038
|
console.print(f"[bold blue]Publishing all data from:[/bold blue] {data_path}")
|
897
1039
|
console.print(f"[bold blue]Backend URL:[/bold blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
898
1040
|
|
1041
|
+
async def _publish_all_async():
|
1042
|
+
async with ServiceDataPublisher() as publisher:
|
1043
|
+
return await publisher.publish_all_models(data_path)
|
1044
|
+
|
899
1045
|
try:
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
1046
|
+
all_results = asyncio.run(_publish_all_async())
|
1047
|
+
|
1048
|
+
# Display results for each data type
|
1049
|
+
data_type_display_names = {
|
1050
|
+
"sellers": "Sellers",
|
1051
|
+
"providers": "Providers",
|
1052
|
+
"offerings": "Service Offerings",
|
1053
|
+
"listings": "Service Listings",
|
1054
|
+
}
|
1055
|
+
|
1056
|
+
for data_type in ["sellers", "providers", "offerings", "listings"]:
|
1057
|
+
display_name = data_type_display_names[data_type]
|
1058
|
+
results = all_results[data_type]
|
911
1059
|
|
912
|
-
for data_type in ["sellers", "providers", "offerings", "listings"]:
|
913
|
-
display_name = data_type_display_names[data_type]
|
914
|
-
results = all_results[data_type]
|
915
|
-
|
916
|
-
console.print(f"\n[bold cyan]{'=' * 60}[/bold cyan]")
|
917
|
-
console.print(f"[bold cyan]{display_name}[/bold cyan]")
|
918
|
-
console.print(f"[bold cyan]{'=' * 60}[/bold cyan]\n")
|
919
|
-
|
920
|
-
console.print(f" Total found: {results['total']}")
|
921
|
-
console.print(f" [green]✓ Success:[/green] {results['success']}")
|
922
|
-
console.print(f" [red]✗ Failed:[/red] {results['failed']}")
|
923
|
-
|
924
|
-
# Display errors if any
|
925
|
-
if results.get("errors"):
|
926
|
-
console.print(f"\n[bold red]Errors in {display_name}:[/bold red]")
|
927
|
-
for error in results["errors"]:
|
928
|
-
# Check if this is a skipped item
|
929
|
-
if isinstance(error, dict) and error.get("error", "").startswith("skipped"):
|
930
|
-
continue
|
931
|
-
console.print(f" [red]✗[/red] {error.get('file', 'unknown')}")
|
932
|
-
console.print(f" {error.get('error', 'unknown error')}")
|
933
|
-
|
934
|
-
# Final summary
|
935
1060
|
console.print(f"\n[bold cyan]{'=' * 60}[/bold cyan]")
|
936
|
-
console.print("[bold]
|
1061
|
+
console.print(f"[bold cyan]{display_name}[/bold cyan]")
|
937
1062
|
console.print(f"[bold cyan]{'=' * 60}[/bold cyan]\n")
|
938
|
-
|
939
|
-
console.print(f"
|
940
|
-
console.print(f" [
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
"
|
951
|
-
|
952
|
-
|
1063
|
+
|
1064
|
+
console.print(f" Total found: {results['total']}")
|
1065
|
+
console.print(f" [green]✓ Success:[/green] {results['success']}")
|
1066
|
+
console.print(f" [red]✗ Failed:[/red] {results['failed']}")
|
1067
|
+
|
1068
|
+
# Display errors if any
|
1069
|
+
if results.get("errors"):
|
1070
|
+
console.print(f"\n[bold red]Errors in {display_name}:[/bold red]")
|
1071
|
+
for error in results["errors"]:
|
1072
|
+
# Check if this is a skipped item
|
1073
|
+
if isinstance(error, dict) and error.get("error", "").startswith("skipped"):
|
1074
|
+
continue
|
1075
|
+
console.print(f" [red]✗[/red] {error.get('file', 'unknown')}")
|
1076
|
+
console.print(f" {error.get('error', 'unknown error')}")
|
1077
|
+
|
1078
|
+
# Final summary
|
1079
|
+
console.print(f"\n[bold cyan]{'=' * 60}[/bold cyan]")
|
1080
|
+
console.print("[bold]Final Publishing Summary[/bold]")
|
1081
|
+
console.print(f"[bold cyan]{'=' * 60}[/bold cyan]\n")
|
1082
|
+
console.print(f" Total found: {all_results['total_found']}")
|
1083
|
+
console.print(f" [green]✓ Success:[/green] {all_results['total_success']}")
|
1084
|
+
console.print(f" [red]✗ Failed:[/red] {all_results['total_failed']}")
|
1085
|
+
|
1086
|
+
if all_results["total_failed"] > 0:
|
1087
|
+
console.print(
|
1088
|
+
f"\n[yellow]⚠[/yellow] Completed with {all_results['total_failed']} failure(s)",
|
1089
|
+
style="bold yellow",
|
1090
|
+
)
|
1091
|
+
raise typer.Exit(code=1)
|
1092
|
+
else:
|
1093
|
+
console.print(
|
1094
|
+
"\n[green]✓[/green] All data published successfully!",
|
1095
|
+
style="bold green",
|
1096
|
+
)
|
953
1097
|
|
954
1098
|
except typer.Exit:
|
955
1099
|
raise
|
@@ -980,36 +1124,45 @@ def publish_providers(
|
|
980
1124
|
console.print(f"[red]✗[/red] Path not found: {data_path}", style="bold red")
|
981
1125
|
raise typer.Exit(code=1)
|
982
1126
|
|
983
|
-
|
984
|
-
|
1127
|
+
# Handle single file
|
1128
|
+
if data_path.is_file():
|
1129
|
+
console.print(f"[blue]Publishing provider:[/blue] {data_path}")
|
1130
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1131
|
+
else:
|
1132
|
+
console.print(f"[blue]Scanning for providers in:[/blue] {data_path}")
|
1133
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1134
|
+
|
1135
|
+
async def _publish_providers_async():
|
1136
|
+
async with ServiceDataPublisher() as publisher:
|
985
1137
|
# Handle single file
|
986
1138
|
if data_path.is_file():
|
987
|
-
|
988
|
-
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
989
|
-
result = asyncio.run(publisher.post_provider_async(data_path))
|
990
|
-
console.print("[green]✓[/green] Provider published successfully!")
|
991
|
-
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1139
|
+
return await publisher.post_provider_async(data_path), True
|
992
1140
|
# Handle directory
|
993
1141
|
else:
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1142
|
+
return await publisher.publish_all_providers(data_path), False
|
1143
|
+
|
1144
|
+
try:
|
1145
|
+
result, is_single = asyncio.run(_publish_providers_async())
|
1146
|
+
|
1147
|
+
if is_single:
|
1148
|
+
console.print("[green]✓[/green] Provider published successfully!")
|
1149
|
+
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1150
|
+
else:
|
1151
|
+
# Display summary
|
1152
|
+
console.print("\n[bold]Publishing Summary:[/bold]")
|
1153
|
+
console.print(f" Total found: {result['total']}")
|
1154
|
+
console.print(f" [green]✓ Success:[/green] {result['success']}")
|
1155
|
+
console.print(f" [red]✗ Failed:[/red] {result['failed']}")
|
1156
|
+
|
1157
|
+
# Display errors if any
|
1158
|
+
if result["errors"]:
|
1159
|
+
console.print("\n[bold red]Errors:[/bold red]")
|
1160
|
+
for error in result["errors"]:
|
1161
|
+
console.print(f" [red]✗[/red] {error['file']}")
|
1162
|
+
console.print(f" {error['error']}")
|
1163
|
+
|
1164
|
+
if result["failed"] > 0:
|
1165
|
+
raise typer.Exit(code=1)
|
1013
1166
|
|
1014
1167
|
except typer.Exit:
|
1015
1168
|
raise
|
@@ -1039,34 +1192,43 @@ def publish_sellers(
|
|
1039
1192
|
console.print(f"[red]✗[/red] Path not found: {data_path}", style="bold red")
|
1040
1193
|
raise typer.Exit(code=1)
|
1041
1194
|
|
1042
|
-
|
1043
|
-
|
1195
|
+
# Handle single file
|
1196
|
+
if data_path.is_file():
|
1197
|
+
console.print(f"[blue]Publishing seller:[/blue] {data_path}")
|
1198
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1199
|
+
else:
|
1200
|
+
console.print(f"[blue]Scanning for sellers in:[/blue] {data_path}")
|
1201
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1202
|
+
|
1203
|
+
async def _publish_sellers_async():
|
1204
|
+
async with ServiceDataPublisher() as publisher:
|
1044
1205
|
# Handle single file
|
1045
1206
|
if data_path.is_file():
|
1046
|
-
|
1047
|
-
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1048
|
-
result = asyncio.run(publisher.post_seller_async(data_path))
|
1049
|
-
console.print("[green]✓[/green] Seller published successfully!")
|
1050
|
-
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1207
|
+
return await publisher.post_seller_async(data_path), True
|
1051
1208
|
# Handle directory
|
1052
1209
|
else:
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1210
|
+
return await publisher.publish_all_sellers(data_path), False
|
1211
|
+
|
1212
|
+
try:
|
1213
|
+
result, is_single = asyncio.run(_publish_sellers_async())
|
1214
|
+
|
1215
|
+
if is_single:
|
1216
|
+
console.print("[green]✓[/green] Seller published successfully!")
|
1217
|
+
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1218
|
+
else:
|
1219
|
+
console.print("\n[bold]Publishing Summary:[/bold]")
|
1220
|
+
console.print(f" Total found: {result['total']}")
|
1221
|
+
console.print(f" [green]✓ Success: {result['success']}[/green]")
|
1222
|
+
console.print(f" [red]✗ Failed: {result['failed']}[/red]")
|
1223
|
+
|
1224
|
+
if result["errors"]:
|
1225
|
+
console.print("\n[bold red]Errors:[/bold red]")
|
1226
|
+
for error in result["errors"]:
|
1227
|
+
console.print(f" [red]✗[/red] {error['file']}")
|
1228
|
+
console.print(f" {error['error']}")
|
1229
|
+
raise typer.Exit(code=1)
|
1230
|
+
else:
|
1231
|
+
console.print("\n[green]✓[/green] All sellers published successfully!")
|
1070
1232
|
|
1071
1233
|
except typer.Exit:
|
1072
1234
|
raise
|
@@ -1096,34 +1258,43 @@ def publish_offerings(
|
|
1096
1258
|
console.print(f"[red]✗[/red] Path not found: {data_path}", style="bold red")
|
1097
1259
|
raise typer.Exit(code=1)
|
1098
1260
|
|
1099
|
-
|
1100
|
-
|
1261
|
+
# Handle single file
|
1262
|
+
if data_path.is_file():
|
1263
|
+
console.print(f"[blue]Publishing service offering:[/blue] {data_path}")
|
1264
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1265
|
+
else:
|
1266
|
+
console.print(f"[blue]Scanning for service offerings in:[/blue] {data_path}")
|
1267
|
+
console.print(f"[blue]Backend URL:[/bold blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1268
|
+
|
1269
|
+
async def _publish_offerings_async():
|
1270
|
+
async with ServiceDataPublisher() as publisher:
|
1101
1271
|
# Handle single file
|
1102
1272
|
if data_path.is_file():
|
1103
|
-
|
1104
|
-
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1105
|
-
result = asyncio.run(publisher.post_service_offering_async(data_path))
|
1106
|
-
console.print("[green]✓[/green] Service offering published successfully!")
|
1107
|
-
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1273
|
+
return await publisher.post_service_offering_async(data_path), True
|
1108
1274
|
# Handle directory
|
1109
1275
|
else:
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1276
|
+
return await publisher.publish_all_offerings(data_path), False
|
1277
|
+
|
1278
|
+
try:
|
1279
|
+
result, is_single = asyncio.run(_publish_offerings_async())
|
1280
|
+
|
1281
|
+
if is_single:
|
1282
|
+
console.print("[green]✓[/green] Service offering published successfully!")
|
1283
|
+
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1284
|
+
else:
|
1285
|
+
console.print("\n[bold]Publishing Summary:[/bold]")
|
1286
|
+
console.print(f" Total found: {result['total']}")
|
1287
|
+
console.print(f" [green]✓ Success: {result['success']}[/green]")
|
1288
|
+
console.print(f" [red]✗ Failed: {result['failed']}[/red]")
|
1289
|
+
|
1290
|
+
if result["errors"]:
|
1291
|
+
console.print("\n[bold red]Errors:[/bold red]")
|
1292
|
+
for error in result["errors"]:
|
1293
|
+
console.print(f" [red]✗[/red] {error['file']}")
|
1294
|
+
console.print(f" {error['error']}")
|
1295
|
+
raise typer.Exit(code=1)
|
1296
|
+
else:
|
1297
|
+
console.print("\n[green]✓[/green] All service offerings published successfully!")
|
1127
1298
|
|
1128
1299
|
except typer.Exit:
|
1129
1300
|
raise
|
@@ -1154,34 +1325,43 @@ def publish_listings(
|
|
1154
1325
|
console.print(f"[red]✗[/red] Path not found: {data_path}", style="bold red")
|
1155
1326
|
raise typer.Exit(code=1)
|
1156
1327
|
|
1157
|
-
|
1158
|
-
|
1328
|
+
# Handle single file
|
1329
|
+
if data_path.is_file():
|
1330
|
+
console.print(f"[blue]Publishing service listing:[/blue] {data_path}")
|
1331
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1332
|
+
else:
|
1333
|
+
console.print(f"[blue]Scanning for service listings in:[/blue] {data_path}")
|
1334
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1335
|
+
|
1336
|
+
async def _publish_listings_async():
|
1337
|
+
async with ServiceDataPublisher() as publisher:
|
1159
1338
|
# Handle single file
|
1160
1339
|
if data_path.is_file():
|
1161
|
-
|
1162
|
-
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
1163
|
-
result = asyncio.run(publisher.post_service_listing_async(data_path))
|
1164
|
-
console.print("[green]✓[/green] Service listing published successfully!")
|
1165
|
-
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1340
|
+
return await publisher.post_service_listing_async(data_path), True
|
1166
1341
|
# Handle directory
|
1167
1342
|
else:
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1343
|
+
return await publisher.publish_all_listings(data_path), False
|
1344
|
+
|
1345
|
+
try:
|
1346
|
+
result, is_single = asyncio.run(_publish_listings_async())
|
1347
|
+
|
1348
|
+
if is_single:
|
1349
|
+
console.print("[green]✓[/green] Service listing published successfully!")
|
1350
|
+
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
1351
|
+
else:
|
1352
|
+
console.print("\n[bold]Publishing Summary:[/bold]")
|
1353
|
+
console.print(f" Total found: {result['total']}")
|
1354
|
+
console.print(f" [green]✓ Success: {result['success']}[/green]")
|
1355
|
+
console.print(f" [red]✗ Failed: {result['failed']}[/red]")
|
1356
|
+
|
1357
|
+
if result["errors"]:
|
1358
|
+
console.print("\n[bold red]Errors:[/bold red]")
|
1359
|
+
for error in result["errors"]:
|
1360
|
+
console.print(f" [red]✗[/red] {error['file']}")
|
1361
|
+
console.print(f" {error['error']}")
|
1362
|
+
raise typer.Exit(code=1)
|
1363
|
+
else:
|
1364
|
+
console.print("\n[green]✓[/green] All service listings published successfully!")
|
1185
1365
|
|
1186
1366
|
except typer.Exit:
|
1187
1367
|
raise
|