unitysvc-services 0.1.9__py3-none-any.whl → 0.1.11__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.
Potentially problematic release.
This version of unitysvc-services might be problematic. Click here for more details.
- unitysvc_services/api.py +2 -2
- unitysvc_services/interactive_prompt.py +1129 -0
- unitysvc_services/populate.py +11 -4
- unitysvc_services/publisher.py +337 -87
- unitysvc_services/scaffold.py +143 -66
- unitysvc_services/test.py +21 -43
- {unitysvc_services-0.1.9.dist-info → unitysvc_services-0.1.11.dist-info}/METADATA +1 -1
- {unitysvc_services-0.1.9.dist-info → unitysvc_services-0.1.11.dist-info}/RECORD +12 -11
- {unitysvc_services-0.1.9.dist-info → unitysvc_services-0.1.11.dist-info}/WHEEL +0 -0
- {unitysvc_services-0.1.9.dist-info → unitysvc_services-0.1.11.dist-info}/entry_points.txt +0 -0
- {unitysvc_services-0.1.9.dist-info → unitysvc_services-0.1.11.dist-info}/licenses/LICENSE +0 -0
- {unitysvc_services-0.1.9.dist-info → unitysvc_services-0.1.11.dist-info}/top_level.txt +0 -0
unitysvc_services/publisher.py
CHANGED
|
@@ -11,6 +11,9 @@ from typing import Any
|
|
|
11
11
|
import httpx
|
|
12
12
|
import typer
|
|
13
13
|
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
import unitysvc_services
|
|
14
17
|
|
|
15
18
|
from .api import UnitySvcAPI
|
|
16
19
|
from .models.base import ProviderStatusEnum, SellerStatusEnum
|
|
@@ -187,7 +190,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
187
190
|
return result
|
|
188
191
|
|
|
189
192
|
async def post( # type: ignore[override]
|
|
190
|
-
self, endpoint: str, data: dict[str, Any], check_status: bool = True
|
|
193
|
+
self, endpoint: str, data: dict[str, Any], check_status: bool = True, dryrun: bool = False
|
|
191
194
|
) -> tuple[dict[str, Any], int]:
|
|
192
195
|
"""Make a POST request to the backend API with automatic curl fallback.
|
|
193
196
|
|
|
@@ -198,6 +201,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
198
201
|
endpoint: API endpoint path (e.g., "/publish/seller")
|
|
199
202
|
data: JSON data to post
|
|
200
203
|
check_status: Whether to raise on non-2xx status codes (default: True)
|
|
204
|
+
dryrun: If True, adds dryrun=true as query parameter
|
|
201
205
|
|
|
202
206
|
Returns:
|
|
203
207
|
Tuple of (JSON response, HTTP status code)
|
|
@@ -205,16 +209,19 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
205
209
|
Raises:
|
|
206
210
|
RuntimeError: If both httpx and curl fail
|
|
207
211
|
"""
|
|
212
|
+
# Build query parameters
|
|
213
|
+
params = {"dryrun": "true"} if dryrun else None
|
|
214
|
+
|
|
208
215
|
# Use base class client (self.client from UnitySvcQuery) with automatic curl fallback
|
|
209
216
|
# If we already know curl is needed, use it directly
|
|
210
217
|
if self.use_curl_fallback:
|
|
211
218
|
# Use base class curl fallback method
|
|
212
|
-
response_json = await super().post(endpoint, json_data=data)
|
|
219
|
+
response_json = await super().post(endpoint, json_data=data, params=params)
|
|
213
220
|
# Curl POST doesn't return status code separately, assume 2xx if no exception
|
|
214
221
|
status_code = 200
|
|
215
222
|
else:
|
|
216
223
|
try:
|
|
217
|
-
response = await self.client.post(f"{self.base_url}{endpoint}", json=data)
|
|
224
|
+
response = await self.client.post(f"{self.base_url}{endpoint}", json=data, params=params)
|
|
218
225
|
status_code = response.status_code
|
|
219
226
|
|
|
220
227
|
if check_status:
|
|
@@ -224,7 +231,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
224
231
|
except (httpx.ConnectError, OSError):
|
|
225
232
|
# Connection failed - switch to curl fallback and retry
|
|
226
233
|
self.use_curl_fallback = True
|
|
227
|
-
response_json = await super().post(endpoint, json_data=data)
|
|
234
|
+
response_json = await super().post(endpoint, json_data=data, params=params)
|
|
228
235
|
status_code = 200 # Assume success if curl didn't raise
|
|
229
236
|
|
|
230
237
|
return (response_json, status_code)
|
|
@@ -237,6 +244,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
237
244
|
entity_name: str,
|
|
238
245
|
context_info: str = "",
|
|
239
246
|
max_retries: int = 3,
|
|
247
|
+
dryrun: bool = False,
|
|
240
248
|
) -> dict[str, Any]:
|
|
241
249
|
"""
|
|
242
250
|
Generic retry wrapper for posting data to backend API with task polling.
|
|
@@ -254,6 +262,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
254
262
|
entity_name: Name of the entity being published (for error messages)
|
|
255
263
|
context_info: Additional context for error messages (e.g., provider, service info)
|
|
256
264
|
max_retries: Maximum number of retry attempts
|
|
265
|
+
dryrun: If True, runs in dry run mode (no actual changes)
|
|
257
266
|
|
|
258
267
|
Returns:
|
|
259
268
|
Response JSON from successful API call
|
|
@@ -265,7 +274,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
265
274
|
for attempt in range(max_retries):
|
|
266
275
|
try:
|
|
267
276
|
# Use the public post() method with automatic curl fallback
|
|
268
|
-
response_json, status_code = await self.post(endpoint, data, check_status=False)
|
|
277
|
+
response_json, status_code = await self.post(endpoint, data, check_status=False, dryrun=dryrun)
|
|
269
278
|
|
|
270
279
|
# Handle task-based response (HTTP 202)
|
|
271
280
|
if status_code == 202:
|
|
@@ -330,7 +339,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
330
339
|
raise last_exception
|
|
331
340
|
raise ValueError("Unexpected error in retry logic")
|
|
332
341
|
|
|
333
|
-
async def post_service_listing_async(
|
|
342
|
+
async def post_service_listing_async(
|
|
343
|
+
self, listing_file: Path, max_retries: int = 3, dryrun: bool = False
|
|
344
|
+
) -> dict[str, Any]:
|
|
334
345
|
"""Async version of post_service_listing for concurrent publishing with retry logic."""
|
|
335
346
|
# Load the listing data file
|
|
336
347
|
data = self.load_data_file(listing_file)
|
|
@@ -466,6 +477,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
466
477
|
entity_name=data.get("name", "unknown"),
|
|
467
478
|
context_info=context_info,
|
|
468
479
|
max_retries=max_retries,
|
|
480
|
+
dryrun=dryrun,
|
|
469
481
|
)
|
|
470
482
|
|
|
471
483
|
# Add local metadata to result for display purposes
|
|
@@ -475,7 +487,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
475
487
|
|
|
476
488
|
return result
|
|
477
489
|
|
|
478
|
-
async def post_service_offering_async(
|
|
490
|
+
async def post_service_offering_async(
|
|
491
|
+
self, data_file: Path, max_retries: int = 3, dryrun: bool = False
|
|
492
|
+
) -> dict[str, Any]:
|
|
479
493
|
"""Async version of post_service_offering for concurrent publishing with retry logic."""
|
|
480
494
|
# Load the data file
|
|
481
495
|
data = self.load_data_file(data_file)
|
|
@@ -536,6 +550,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
536
550
|
entity_name=data.get("name", "unknown"),
|
|
537
551
|
context_info=context_info,
|
|
538
552
|
max_retries=max_retries,
|
|
553
|
+
dryrun=dryrun,
|
|
539
554
|
)
|
|
540
555
|
|
|
541
556
|
# Add local metadata to result for display purposes
|
|
@@ -543,7 +558,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
543
558
|
|
|
544
559
|
return result
|
|
545
560
|
|
|
546
|
-
async def post_provider_async(self, data_file: Path, max_retries: int = 3) -> dict[str, Any]:
|
|
561
|
+
async def post_provider_async(self, data_file: Path, max_retries: int = 3, dryrun: bool = False) -> dict[str, Any]:
|
|
547
562
|
"""Async version of post_provider for concurrent publishing with retry logic."""
|
|
548
563
|
# Load the data file
|
|
549
564
|
data = self.load_data_file(data_file)
|
|
@@ -581,9 +596,10 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
581
596
|
entity_type="provider",
|
|
582
597
|
entity_name=data.get("name", "unknown"),
|
|
583
598
|
max_retries=max_retries,
|
|
599
|
+
dryrun=dryrun,
|
|
584
600
|
)
|
|
585
601
|
|
|
586
|
-
async def post_seller_async(self, data_file: Path, max_retries: int = 3) -> dict[str, Any]:
|
|
602
|
+
async def post_seller_async(self, data_file: Path, max_retries: int = 3, dryrun: bool = False) -> dict[str, Any]:
|
|
587
603
|
"""Async version of post_seller for concurrent publishing with retry logic."""
|
|
588
604
|
# Load the data file
|
|
589
605
|
data = self.load_data_file(data_file)
|
|
@@ -619,6 +635,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
619
635
|
entity_type="seller",
|
|
620
636
|
entity_name=data.get("name", "unknown"),
|
|
621
637
|
max_retries=max_retries,
|
|
638
|
+
dryrun=dryrun,
|
|
622
639
|
)
|
|
623
640
|
|
|
624
641
|
def find_offering_files(self, data_dir: Path) -> list[Path]:
|
|
@@ -641,8 +658,20 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
641
658
|
files = find_files_by_schema(data_dir, "seller_v1")
|
|
642
659
|
return sorted([f[0] for f in files])
|
|
643
660
|
|
|
661
|
+
@staticmethod
|
|
662
|
+
def _get_status_display(status: str) -> tuple[str, str]:
|
|
663
|
+
"""Get color and symbol for status display."""
|
|
664
|
+
status_map = {
|
|
665
|
+
"created": ("[green]+[/green]", "green"),
|
|
666
|
+
"updated": ("[blue]~[/blue]", "blue"),
|
|
667
|
+
"unchanged": ("[dim]=[/dim]", "dim"),
|
|
668
|
+
"create": ("[yellow]?[/yellow]", "yellow"), # Dryrun: would be created
|
|
669
|
+
"update": ("[cyan]?[/cyan]", "cyan"), # Dryrun: would be updated
|
|
670
|
+
}
|
|
671
|
+
return status_map.get(status, ("[green]✓[/green]", "green"))
|
|
672
|
+
|
|
644
673
|
async def _publish_offering_task(
|
|
645
|
-
self, offering_file: Path, console: Console, semaphore: asyncio.Semaphore
|
|
674
|
+
self, offering_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
|
|
646
675
|
) -> tuple[Path, dict[str, Any] | Exception]:
|
|
647
676
|
"""
|
|
648
677
|
Async task to publish a single offering with concurrency control.
|
|
@@ -656,7 +685,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
656
685
|
offering_name = data.get("name", offering_file.stem)
|
|
657
686
|
|
|
658
687
|
# Publish the offering
|
|
659
|
-
result = await self.post_service_offering_async(offering_file)
|
|
688
|
+
result = await self.post_service_offering_async(offering_file, dryrun=dryrun)
|
|
660
689
|
|
|
661
690
|
# Print complete statement after publication
|
|
662
691
|
if result.get("skipped"):
|
|
@@ -664,8 +693,10 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
664
693
|
console.print(f" [yellow]⊘[/yellow] Skipped offering: [cyan]{offering_name}[/cyan] - {reason}")
|
|
665
694
|
else:
|
|
666
695
|
provider_name = result.get("provider_name")
|
|
696
|
+
status = result.get("status", "created")
|
|
697
|
+
symbol, color = self._get_status_display(status)
|
|
667
698
|
console.print(
|
|
668
|
-
f" [
|
|
699
|
+
f" {symbol} [{color}]{status.capitalize()}[/{color}] offering: [cyan]{offering_name}[/cyan] "
|
|
669
700
|
f"(provider: {provider_name})"
|
|
670
701
|
)
|
|
671
702
|
|
|
@@ -676,15 +707,20 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
676
707
|
console.print(f" [red]✗[/red] Failed to publish offering: [cyan]{offering_name}[/cyan] - {str(e)}")
|
|
677
708
|
return (offering_file, e)
|
|
678
709
|
|
|
679
|
-
async def publish_all_offerings(self, data_dir: Path) -> dict[str, Any]:
|
|
710
|
+
async def publish_all_offerings(self, data_dir: Path, dryrun: bool = False) -> dict[str, Any]:
|
|
680
711
|
"""
|
|
681
712
|
Publish all service offerings found in a directory tree concurrently.
|
|
682
713
|
|
|
683
714
|
Validates data consistency before publishing.
|
|
684
715
|
Returns a summary of successes and failures.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
data_dir: Directory to search for offering files
|
|
719
|
+
dryrun: If True, runs in dry run mode (no actual changes)
|
|
685
720
|
"""
|
|
686
721
|
# Validate all service directories first
|
|
687
|
-
|
|
722
|
+
schema_dir = Path(unitysvc_services.__file__).parent / "schema"
|
|
723
|
+
validator = DataValidator(data_dir, schema_dir)
|
|
688
724
|
validation_errors = validator.validate_all_service_directories(data_dir)
|
|
689
725
|
if validation_errors:
|
|
690
726
|
return {
|
|
@@ -699,6 +735,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
699
735
|
"total": len(offering_files),
|
|
700
736
|
"success": 0,
|
|
701
737
|
"failed": 0,
|
|
738
|
+
"created": 0,
|
|
739
|
+
"updated": 0,
|
|
740
|
+
"unchanged": 0,
|
|
702
741
|
"errors": [],
|
|
703
742
|
}
|
|
704
743
|
|
|
@@ -710,7 +749,10 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
710
749
|
# Run all offering publications concurrently with rate limiting
|
|
711
750
|
# Create semaphore to limit concurrent requests
|
|
712
751
|
semaphore = asyncio.Semaphore(self.max_concurrent_requests)
|
|
713
|
-
tasks = [
|
|
752
|
+
tasks = [
|
|
753
|
+
self._publish_offering_task(offering_file, console, semaphore, dryrun=dryrun)
|
|
754
|
+
for offering_file in offering_files
|
|
755
|
+
]
|
|
714
756
|
task_results = await asyncio.gather(*tasks)
|
|
715
757
|
|
|
716
758
|
# Process results
|
|
@@ -720,11 +762,19 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
720
762
|
results["errors"].append({"file": str(offering_file), "error": str(result)})
|
|
721
763
|
else:
|
|
722
764
|
results["success"] += 1
|
|
765
|
+
# Track status counts (handle both normal and dryrun statuses)
|
|
766
|
+
status = result.get("status", "created")
|
|
767
|
+
if status in ("created", "create"): # "create" is dryrun mode
|
|
768
|
+
results["created"] += 1
|
|
769
|
+
elif status in ("updated", "update"): # "update" is dryrun mode
|
|
770
|
+
results["updated"] += 1
|
|
771
|
+
elif status == "unchanged":
|
|
772
|
+
results["unchanged"] += 1
|
|
723
773
|
|
|
724
774
|
return results
|
|
725
775
|
|
|
726
776
|
async def _publish_listing_task(
|
|
727
|
-
self, listing_file: Path, console: Console, semaphore: asyncio.Semaphore
|
|
777
|
+
self, listing_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
|
|
728
778
|
) -> tuple[Path, dict[str, Any] | Exception]:
|
|
729
779
|
"""
|
|
730
780
|
Async task to publish a single listing with concurrency control.
|
|
@@ -738,7 +788,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
738
788
|
listing_name = data.get("name", listing_file.stem)
|
|
739
789
|
|
|
740
790
|
# Publish the listing
|
|
741
|
-
result = await self.post_service_listing_async(listing_file)
|
|
791
|
+
result = await self.post_service_listing_async(listing_file, dryrun=dryrun)
|
|
742
792
|
|
|
743
793
|
# Print complete statement after publication
|
|
744
794
|
if result.get("skipped"):
|
|
@@ -747,8 +797,10 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
747
797
|
else:
|
|
748
798
|
service_name = result.get("service_name")
|
|
749
799
|
provider_name = result.get("provider_name")
|
|
800
|
+
status = result.get("status", "created")
|
|
801
|
+
symbol, color = self._get_status_display(status)
|
|
750
802
|
console.print(
|
|
751
|
-
f" [
|
|
803
|
+
f" {symbol} [{color}]{status.capitalize()}[/{color}] listing: [cyan]{listing_name}[/cyan] "
|
|
752
804
|
f"(service: {service_name}, provider: {provider_name})"
|
|
753
805
|
)
|
|
754
806
|
|
|
@@ -759,7 +811,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
759
811
|
console.print(f" [red]✗[/red] Failed to publish listing: [cyan]{listing_file}[/cyan] - {str(e)}")
|
|
760
812
|
return (listing_file, e)
|
|
761
813
|
|
|
762
|
-
async def publish_all_listings(self, data_dir: Path) -> dict[str, Any]:
|
|
814
|
+
async def publish_all_listings(self, data_dir: Path, dryrun: bool = False) -> dict[str, Any]:
|
|
763
815
|
"""
|
|
764
816
|
Publish all service listings found in a directory tree concurrently.
|
|
765
817
|
|
|
@@ -767,7 +819,8 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
767
819
|
Returns a summary of successes and failures.
|
|
768
820
|
"""
|
|
769
821
|
# Validate all service directories first
|
|
770
|
-
|
|
822
|
+
schema_dir = Path(unitysvc_services.__file__).parent / "schema"
|
|
823
|
+
validator = DataValidator(data_dir, schema_dir)
|
|
771
824
|
validation_errors = validator.validate_all_service_directories(data_dir)
|
|
772
825
|
if validation_errors:
|
|
773
826
|
return {
|
|
@@ -782,6 +835,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
782
835
|
"total": len(listing_files),
|
|
783
836
|
"success": 0,
|
|
784
837
|
"failed": 0,
|
|
838
|
+
"created": 0,
|
|
839
|
+
"updated": 0,
|
|
840
|
+
"unchanged": 0,
|
|
785
841
|
"errors": [],
|
|
786
842
|
}
|
|
787
843
|
|
|
@@ -793,7 +849,10 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
793
849
|
# Run all listing publications concurrently with rate limiting
|
|
794
850
|
# Create semaphore to limit concurrent requests
|
|
795
851
|
semaphore = asyncio.Semaphore(self.max_concurrent_requests)
|
|
796
|
-
tasks = [
|
|
852
|
+
tasks = [
|
|
853
|
+
self._publish_listing_task(listing_file, console, semaphore, dryrun=dryrun)
|
|
854
|
+
for listing_file in listing_files
|
|
855
|
+
]
|
|
797
856
|
task_results = await asyncio.gather(*tasks)
|
|
798
857
|
|
|
799
858
|
# Process results
|
|
@@ -803,11 +862,19 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
803
862
|
results["errors"].append({"file": str(listing_file), "error": str(result)})
|
|
804
863
|
else:
|
|
805
864
|
results["success"] += 1
|
|
865
|
+
# Track status counts (handle both normal and dryrun statuses)
|
|
866
|
+
status = result.get("status", "created")
|
|
867
|
+
if status in ("created", "create"): # "create" is dryrun mode
|
|
868
|
+
results["created"] += 1
|
|
869
|
+
elif status in ("updated", "update"): # "update" is dryrun mode
|
|
870
|
+
results["updated"] += 1
|
|
871
|
+
elif status == "unchanged":
|
|
872
|
+
results["unchanged"] += 1
|
|
806
873
|
|
|
807
874
|
return results
|
|
808
875
|
|
|
809
876
|
async def _publish_provider_task(
|
|
810
|
-
self, provider_file: Path, console: Console, semaphore: asyncio.Semaphore
|
|
877
|
+
self, provider_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
|
|
811
878
|
) -> tuple[Path, dict[str, Any] | Exception]:
|
|
812
879
|
"""
|
|
813
880
|
Async task to publish a single provider with concurrency control.
|
|
@@ -821,14 +888,18 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
821
888
|
provider_name = data.get("name", provider_file.stem)
|
|
822
889
|
|
|
823
890
|
# Publish the provider
|
|
824
|
-
result = await self.post_provider_async(provider_file)
|
|
891
|
+
result = await self.post_provider_async(provider_file, dryrun=dryrun)
|
|
825
892
|
|
|
826
893
|
# Print complete statement after publication
|
|
827
894
|
if result.get("skipped"):
|
|
828
895
|
reason = result.get("reason", "unknown")
|
|
829
896
|
console.print(f" [yellow]⊘[/yellow] Skipped provider: [cyan]{provider_name}[/cyan] - {reason}")
|
|
830
897
|
else:
|
|
831
|
-
|
|
898
|
+
status = result.get("status", "created")
|
|
899
|
+
symbol, color = self._get_status_display(status)
|
|
900
|
+
console.print(
|
|
901
|
+
f" {symbol} [{color}]{status.capitalize()}[/{color}] provider: [cyan]{provider_name}[/cyan]"
|
|
902
|
+
)
|
|
832
903
|
|
|
833
904
|
return (provider_file, result)
|
|
834
905
|
except Exception as e:
|
|
@@ -837,7 +908,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
837
908
|
console.print(f" [red]✗[/red] Failed to publish provider: [cyan]{provider_name}[/cyan] - {str(e)}")
|
|
838
909
|
return (provider_file, e)
|
|
839
910
|
|
|
840
|
-
async def publish_all_providers(self, data_dir: Path) -> dict[str, Any]:
|
|
911
|
+
async def publish_all_providers(self, data_dir: Path, dryrun: bool = False) -> dict[str, Any]:
|
|
841
912
|
"""
|
|
842
913
|
Publish all providers found in a directory tree concurrently.
|
|
843
914
|
|
|
@@ -848,6 +919,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
848
919
|
"total": len(provider_files),
|
|
849
920
|
"success": 0,
|
|
850
921
|
"failed": 0,
|
|
922
|
+
"created": 0,
|
|
923
|
+
"updated": 0,
|
|
924
|
+
"unchanged": 0,
|
|
851
925
|
"errors": [],
|
|
852
926
|
}
|
|
853
927
|
|
|
@@ -859,7 +933,10 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
859
933
|
# Run all provider publications concurrently with rate limiting
|
|
860
934
|
# Create semaphore to limit concurrent requests
|
|
861
935
|
semaphore = asyncio.Semaphore(self.max_concurrent_requests)
|
|
862
|
-
tasks = [
|
|
936
|
+
tasks = [
|
|
937
|
+
self._publish_provider_task(provider_file, console, semaphore, dryrun=dryrun)
|
|
938
|
+
for provider_file in provider_files
|
|
939
|
+
]
|
|
863
940
|
task_results = await asyncio.gather(*tasks)
|
|
864
941
|
|
|
865
942
|
# Process results
|
|
@@ -869,11 +946,19 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
869
946
|
results["errors"].append({"file": str(provider_file), "error": str(result)})
|
|
870
947
|
else:
|
|
871
948
|
results["success"] += 1
|
|
949
|
+
# Track status counts (handle both normal and dryrun statuses)
|
|
950
|
+
status = result.get("status", "created")
|
|
951
|
+
if status in ("created", "create"): # "create" is dryrun mode
|
|
952
|
+
results["created"] += 1
|
|
953
|
+
elif status in ("updated", "update"): # "update" is dryrun mode
|
|
954
|
+
results["updated"] += 1
|
|
955
|
+
elif status == "unchanged":
|
|
956
|
+
results["unchanged"] += 1
|
|
872
957
|
|
|
873
958
|
return results
|
|
874
959
|
|
|
875
960
|
async def _publish_seller_task(
|
|
876
|
-
self, seller_file: Path, console: Console, semaphore: asyncio.Semaphore
|
|
961
|
+
self, seller_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
|
|
877
962
|
) -> tuple[Path, dict[str, Any] | Exception]:
|
|
878
963
|
"""
|
|
879
964
|
Async task to publish a single seller with concurrency control.
|
|
@@ -887,14 +972,18 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
887
972
|
seller_name = data.get("name", seller_file.stem)
|
|
888
973
|
|
|
889
974
|
# Publish the seller
|
|
890
|
-
result = await self.post_seller_async(seller_file)
|
|
975
|
+
result = await self.post_seller_async(seller_file, dryrun=dryrun)
|
|
891
976
|
|
|
892
977
|
# Print complete statement after publication
|
|
893
978
|
if result.get("skipped"):
|
|
894
979
|
reason = result.get("reason", "unknown")
|
|
895
980
|
console.print(f" [yellow]⊘[/yellow] Skipped seller: [cyan]{seller_name}[/cyan] - {reason}")
|
|
896
981
|
else:
|
|
897
|
-
|
|
982
|
+
status = result.get("status", "created")
|
|
983
|
+
symbol, color = self._get_status_display(status)
|
|
984
|
+
console.print(
|
|
985
|
+
f" {symbol} [{color}]{status.capitalize()}[/{color}] seller: [cyan]{seller_name}[/cyan]"
|
|
986
|
+
)
|
|
898
987
|
|
|
899
988
|
return (seller_file, result)
|
|
900
989
|
except Exception as e:
|
|
@@ -903,7 +992,7 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
903
992
|
console.print(f" [red]✗[/red] Failed to publish seller: [cyan]{seller_name}[/cyan] - {str(e)}")
|
|
904
993
|
return (seller_file, e)
|
|
905
994
|
|
|
906
|
-
async def publish_all_sellers(self, data_dir: Path) -> dict[str, Any]:
|
|
995
|
+
async def publish_all_sellers(self, data_dir: Path, dryrun: bool = False) -> dict[str, Any]:
|
|
907
996
|
"""
|
|
908
997
|
Publish all sellers found in a directory tree concurrently.
|
|
909
998
|
|
|
@@ -914,6 +1003,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
914
1003
|
"total": len(seller_files),
|
|
915
1004
|
"success": 0,
|
|
916
1005
|
"failed": 0,
|
|
1006
|
+
"created": 0,
|
|
1007
|
+
"updated": 0,
|
|
1008
|
+
"unchanged": 0,
|
|
917
1009
|
"errors": [],
|
|
918
1010
|
}
|
|
919
1011
|
|
|
@@ -925,7 +1017,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
925
1017
|
# Run all seller publications concurrently with rate limiting
|
|
926
1018
|
# Create semaphore to limit concurrent requests
|
|
927
1019
|
semaphore = asyncio.Semaphore(self.max_concurrent_requests)
|
|
928
|
-
tasks = [
|
|
1020
|
+
tasks = [
|
|
1021
|
+
self._publish_seller_task(seller_file, console, semaphore, dryrun=dryrun) for seller_file in seller_files
|
|
1022
|
+
]
|
|
929
1023
|
task_results = await asyncio.gather(*tasks)
|
|
930
1024
|
|
|
931
1025
|
# Process results
|
|
@@ -935,10 +1029,18 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
935
1029
|
results["errors"].append({"file": str(seller_file), "error": str(result)})
|
|
936
1030
|
else:
|
|
937
1031
|
results["success"] += 1
|
|
1032
|
+
# Track status counts (handle both normal and dryrun statuses)
|
|
1033
|
+
status = result.get("status", "created")
|
|
1034
|
+
if status in ("created", "create"): # "create" is dryrun mode
|
|
1035
|
+
results["created"] += 1
|
|
1036
|
+
elif status in ("updated", "update"): # "update" is dryrun mode
|
|
1037
|
+
results["updated"] += 1
|
|
1038
|
+
elif status == "unchanged":
|
|
1039
|
+
results["unchanged"] += 1
|
|
938
1040
|
|
|
939
1041
|
return results
|
|
940
1042
|
|
|
941
|
-
async def publish_all_models(self, data_dir: Path) -> dict[str, Any]:
|
|
1043
|
+
async def publish_all_models(self, data_dir: Path, dryrun: bool = False) -> dict[str, Any]:
|
|
942
1044
|
"""
|
|
943
1045
|
Publish all data types in the correct order.
|
|
944
1046
|
|
|
@@ -958,6 +1060,9 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
958
1060
|
"total_success": 0,
|
|
959
1061
|
"total_failed": 0,
|
|
960
1062
|
"total_found": 0,
|
|
1063
|
+
"total_created": 0,
|
|
1064
|
+
"total_updated": 0,
|
|
1065
|
+
"total_unchanged": 0,
|
|
961
1066
|
}
|
|
962
1067
|
|
|
963
1068
|
# Publish in order: sellers -> providers -> offerings -> listings
|
|
@@ -970,11 +1075,14 @@ class ServiceDataPublisher(UnitySvcAPI):
|
|
|
970
1075
|
|
|
971
1076
|
for data_type, publish_method in publish_order:
|
|
972
1077
|
try:
|
|
973
|
-
results = await publish_method(data_dir)
|
|
1078
|
+
results = await publish_method(data_dir, dryrun=dryrun)
|
|
974
1079
|
all_results[data_type] = results
|
|
975
1080
|
all_results["total_success"] += results["success"]
|
|
976
1081
|
all_results["total_failed"] += results["failed"]
|
|
977
1082
|
all_results["total_found"] += results["total"]
|
|
1083
|
+
all_results["total_created"] += results.get("created", 0)
|
|
1084
|
+
all_results["total_updated"] += results.get("updated", 0)
|
|
1085
|
+
all_results["total_unchanged"] += results.get("unchanged", 0)
|
|
978
1086
|
except Exception as e:
|
|
979
1087
|
# If a publish method fails catastrophically, record the error
|
|
980
1088
|
all_results[data_type] = {
|
|
@@ -1002,6 +1110,11 @@ def publish_callback(
|
|
|
1002
1110
|
"-d",
|
|
1003
1111
|
help="Path to data directory (default: current directory)",
|
|
1004
1112
|
),
|
|
1113
|
+
dryrun: bool = typer.Option(
|
|
1114
|
+
False,
|
|
1115
|
+
"--dryrun",
|
|
1116
|
+
help="Run in dry run mode (no actual changes)",
|
|
1117
|
+
),
|
|
1005
1118
|
):
|
|
1006
1119
|
"""
|
|
1007
1120
|
Publish data to backend.
|
|
@@ -1040,48 +1153,77 @@ def publish_callback(
|
|
|
1040
1153
|
|
|
1041
1154
|
async def _publish_all_async():
|
|
1042
1155
|
async with ServiceDataPublisher() as publisher:
|
|
1043
|
-
return await publisher.publish_all_models(data_path)
|
|
1156
|
+
return await publisher.publish_all_models(data_path, dryrun=dryrun)
|
|
1044
1157
|
|
|
1045
1158
|
try:
|
|
1046
1159
|
all_results = asyncio.run(_publish_all_async())
|
|
1047
1160
|
|
|
1048
|
-
#
|
|
1161
|
+
# Create summary table
|
|
1162
|
+
console.print("\n[bold cyan]Publishing Summary[/bold cyan]")
|
|
1163
|
+
|
|
1164
|
+
table = Table(show_header=True, header_style="bold cyan", border_style="cyan")
|
|
1165
|
+
table.add_column("Type", style="cyan", no_wrap=True)
|
|
1166
|
+
table.add_column("Found", justify="right")
|
|
1167
|
+
table.add_column("Success", justify="right", style="green")
|
|
1168
|
+
table.add_column("Failed", justify="right", style="red")
|
|
1169
|
+
table.add_column("Created", justify="right", style="green")
|
|
1170
|
+
table.add_column("Updated", justify="right", style="blue")
|
|
1171
|
+
table.add_column("Unchanged", justify="right", style="dim")
|
|
1172
|
+
|
|
1049
1173
|
data_type_display_names = {
|
|
1050
1174
|
"sellers": "Sellers",
|
|
1051
1175
|
"providers": "Providers",
|
|
1052
|
-
"offerings": "
|
|
1053
|
-
"listings": "
|
|
1176
|
+
"offerings": "Offerings",
|
|
1177
|
+
"listings": "Listings",
|
|
1054
1178
|
}
|
|
1055
1179
|
|
|
1180
|
+
# Add rows for each data type
|
|
1056
1181
|
for data_type in ["sellers", "providers", "offerings", "listings"]:
|
|
1057
1182
|
display_name = data_type_display_names[data_type]
|
|
1058
1183
|
results = all_results[data_type]
|
|
1059
1184
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1185
|
+
table.add_row(
|
|
1186
|
+
display_name,
|
|
1187
|
+
str(results["total"]),
|
|
1188
|
+
str(results["success"]),
|
|
1189
|
+
str(results["failed"]) if results["failed"] > 0 else "",
|
|
1190
|
+
str(results.get("created", 0)) if results.get("created", 0) > 0 else "",
|
|
1191
|
+
str(results.get("updated", 0)) if results.get("updated", 0) > 0 else "",
|
|
1192
|
+
str(results.get("unchanged", 0)) if results.get("unchanged", 0) > 0 else "",
|
|
1193
|
+
)
|
|
1063
1194
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1195
|
+
# Add separator and total row
|
|
1196
|
+
table.add_section()
|
|
1197
|
+
table.add_row(
|
|
1198
|
+
"[bold]Total[/bold]",
|
|
1199
|
+
f"[bold]{all_results['total_found']}[/bold]",
|
|
1200
|
+
f"[bold green]{all_results['total_success']}[/bold green]",
|
|
1201
|
+
f"[bold red]{all_results['total_failed']}[/bold red]" if all_results["total_failed"] > 0 else "",
|
|
1202
|
+
f"[bold green]{all_results['total_created']}[/bold green]" if all_results["total_created"] > 0 else "",
|
|
1203
|
+
f"[bold blue]{all_results['total_updated']}[/bold blue]" if all_results["total_updated"] > 0 else "",
|
|
1204
|
+
f"[bold]{all_results['total_unchanged']}[/bold]" if all_results["total_unchanged"] > 0 else "",
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
console.print(table)
|
|
1208
|
+
|
|
1209
|
+
# Display errors if any
|
|
1210
|
+
has_errors = False
|
|
1211
|
+
for data_type in ["sellers", "providers", "offerings", "listings"]:
|
|
1212
|
+
display_name = data_type_display_names[data_type]
|
|
1213
|
+
results = all_results[data_type]
|
|
1067
1214
|
|
|
1068
|
-
# Display errors if any
|
|
1069
1215
|
if results.get("errors"):
|
|
1070
|
-
|
|
1216
|
+
if not has_errors:
|
|
1217
|
+
console.print("\n[bold red]Errors:[/bold red]")
|
|
1218
|
+
has_errors = True
|
|
1219
|
+
|
|
1220
|
+
console.print(f"\n [bold red]{display_name}:[/bold red]")
|
|
1071
1221
|
for error in results["errors"]:
|
|
1072
1222
|
# Check if this is a skipped item
|
|
1073
1223
|
if isinstance(error, dict) and error.get("error", "").startswith("skipped"):
|
|
1074
1224
|
continue
|
|
1075
|
-
console.print(f"
|
|
1076
|
-
console.print(f"
|
|
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']}")
|
|
1225
|
+
console.print(f" [red]✗[/red] {error.get('file', 'unknown')}")
|
|
1226
|
+
console.print(f" {error.get('error', 'unknown error')}")
|
|
1085
1227
|
|
|
1086
1228
|
if all_results["total_failed"] > 0:
|
|
1087
1229
|
console.print(
|
|
@@ -1090,10 +1232,16 @@ def publish_callback(
|
|
|
1090
1232
|
)
|
|
1091
1233
|
raise typer.Exit(code=1)
|
|
1092
1234
|
else:
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1235
|
+
if dryrun:
|
|
1236
|
+
console.print(
|
|
1237
|
+
"\n[green]✓[/green] Dry run completed successfully - no changes made!",
|
|
1238
|
+
style="bold green",
|
|
1239
|
+
)
|
|
1240
|
+
else:
|
|
1241
|
+
console.print(
|
|
1242
|
+
"\n[green]✓[/green] All data published successfully!",
|
|
1243
|
+
style="bold green",
|
|
1244
|
+
)
|
|
1097
1245
|
|
|
1098
1246
|
except typer.Exit:
|
|
1099
1247
|
raise
|
|
@@ -1110,6 +1258,11 @@ def publish_providers(
|
|
|
1110
1258
|
"-d",
|
|
1111
1259
|
help="Path to provider file or directory (default: current directory)",
|
|
1112
1260
|
),
|
|
1261
|
+
dryrun: bool = typer.Option(
|
|
1262
|
+
False,
|
|
1263
|
+
"--dryrun",
|
|
1264
|
+
help="Run in dry run mode (no actual changes)",
|
|
1265
|
+
),
|
|
1113
1266
|
):
|
|
1114
1267
|
"""Publish provider(s) from a file or directory."""
|
|
1115
1268
|
|
|
@@ -1136,10 +1289,10 @@ def publish_providers(
|
|
|
1136
1289
|
async with ServiceDataPublisher() as publisher:
|
|
1137
1290
|
# Handle single file
|
|
1138
1291
|
if data_path.is_file():
|
|
1139
|
-
return await publisher.post_provider_async(data_path), True
|
|
1292
|
+
return await publisher.post_provider_async(data_path, dryrun=dryrun), True
|
|
1140
1293
|
# Handle directory
|
|
1141
1294
|
else:
|
|
1142
|
-
return await publisher.publish_all_providers(data_path), False
|
|
1295
|
+
return await publisher.publish_all_providers(data_path, dryrun=dryrun), False
|
|
1143
1296
|
|
|
1144
1297
|
try:
|
|
1145
1298
|
result, is_single = asyncio.run(_publish_providers_async())
|
|
@@ -1149,10 +1302,27 @@ def publish_providers(
|
|
|
1149
1302
|
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
|
1150
1303
|
else:
|
|
1151
1304
|
# Display summary
|
|
1152
|
-
console.print("\n[bold]Publishing Summary
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1305
|
+
console.print("\n[bold cyan]Publishing Summary[/bold cyan]")
|
|
1306
|
+
table = Table(show_header=True, header_style="bold cyan", border_style="cyan")
|
|
1307
|
+
table.add_column("Type", style="cyan")
|
|
1308
|
+
table.add_column("Found", justify="right")
|
|
1309
|
+
table.add_column("Success", justify="right")
|
|
1310
|
+
table.add_column("Failed", justify="right")
|
|
1311
|
+
table.add_column("Created", justify="right")
|
|
1312
|
+
table.add_column("Updated", justify="right")
|
|
1313
|
+
table.add_column("Unchanged", justify="right")
|
|
1314
|
+
|
|
1315
|
+
table.add_row(
|
|
1316
|
+
"Providers",
|
|
1317
|
+
str(result["total"]),
|
|
1318
|
+
f"[green]{result['success']}[/green]",
|
|
1319
|
+
f"[red]{result['failed']}[/red]" if result["failed"] > 0 else "",
|
|
1320
|
+
f"[green]{result['created']}[/green]" if result["created"] > 0 else "",
|
|
1321
|
+
f"[blue]{result['updated']}[/blue]" if result["updated"] > 0 else "",
|
|
1322
|
+
f"[dim]{result['unchanged']}[/dim]" if result["unchanged"] > 0 else "",
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
console.print(table)
|
|
1156
1326
|
|
|
1157
1327
|
# Display errors if any
|
|
1158
1328
|
if result["errors"]:
|
|
@@ -1163,6 +1333,11 @@ def publish_providers(
|
|
|
1163
1333
|
|
|
1164
1334
|
if result["failed"] > 0:
|
|
1165
1335
|
raise typer.Exit(code=1)
|
|
1336
|
+
else:
|
|
1337
|
+
if dryrun:
|
|
1338
|
+
console.print("\n[green]✓[/green] Dry run completed successfully - no changes made!")
|
|
1339
|
+
else:
|
|
1340
|
+
console.print("\n[green]✓[/green] All providers published successfully!")
|
|
1166
1341
|
|
|
1167
1342
|
except typer.Exit:
|
|
1168
1343
|
raise
|
|
@@ -1179,6 +1354,11 @@ def publish_sellers(
|
|
|
1179
1354
|
"-d",
|
|
1180
1355
|
help="Path to seller file or directory (default: current directory)",
|
|
1181
1356
|
),
|
|
1357
|
+
dryrun: bool = typer.Option(
|
|
1358
|
+
False,
|
|
1359
|
+
"--dryrun",
|
|
1360
|
+
help="Run in dry run mode (no actual changes)",
|
|
1361
|
+
),
|
|
1182
1362
|
):
|
|
1183
1363
|
"""Publish seller(s) from a file or directory."""
|
|
1184
1364
|
# Set data path
|
|
@@ -1204,10 +1384,10 @@ def publish_sellers(
|
|
|
1204
1384
|
async with ServiceDataPublisher() as publisher:
|
|
1205
1385
|
# Handle single file
|
|
1206
1386
|
if data_path.is_file():
|
|
1207
|
-
return await publisher.post_seller_async(data_path), True
|
|
1387
|
+
return await publisher.post_seller_async(data_path, dryrun=dryrun), True
|
|
1208
1388
|
# Handle directory
|
|
1209
1389
|
else:
|
|
1210
|
-
return await publisher.publish_all_sellers(data_path), False
|
|
1390
|
+
return await publisher.publish_all_sellers(data_path, dryrun=dryrun), False
|
|
1211
1391
|
|
|
1212
1392
|
try:
|
|
1213
1393
|
result, is_single = asyncio.run(_publish_sellers_async())
|
|
@@ -1216,10 +1396,27 @@ def publish_sellers(
|
|
|
1216
1396
|
console.print("[green]✓[/green] Seller published successfully!")
|
|
1217
1397
|
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
|
1218
1398
|
else:
|
|
1219
|
-
console.print("\n[bold]Publishing Summary
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1399
|
+
console.print("\n[bold cyan]Publishing Summary[/bold cyan]")
|
|
1400
|
+
table = Table(show_header=True, header_style="bold cyan", border_style="cyan")
|
|
1401
|
+
table.add_column("Type", style="cyan")
|
|
1402
|
+
table.add_column("Found", justify="right")
|
|
1403
|
+
table.add_column("Success", justify="right")
|
|
1404
|
+
table.add_column("Failed", justify="right")
|
|
1405
|
+
table.add_column("Created", justify="right")
|
|
1406
|
+
table.add_column("Updated", justify="right")
|
|
1407
|
+
table.add_column("Unchanged", justify="right")
|
|
1408
|
+
|
|
1409
|
+
table.add_row(
|
|
1410
|
+
"Sellers",
|
|
1411
|
+
str(result["total"]),
|
|
1412
|
+
f"[green]{result['success']}[/green]",
|
|
1413
|
+
f"[red]{result['failed']}[/red]" if result["failed"] > 0 else "",
|
|
1414
|
+
f"[green]{result['created']}[/green]" if result["created"] > 0 else "",
|
|
1415
|
+
f"[blue]{result['updated']}[/blue]" if result["updated"] > 0 else "",
|
|
1416
|
+
f"[dim]{result['unchanged']}[/dim]" if result["unchanged"] > 0 else "",
|
|
1417
|
+
)
|
|
1418
|
+
|
|
1419
|
+
console.print(table)
|
|
1223
1420
|
|
|
1224
1421
|
if result["errors"]:
|
|
1225
1422
|
console.print("\n[bold red]Errors:[/bold red]")
|
|
@@ -1228,7 +1425,10 @@ def publish_sellers(
|
|
|
1228
1425
|
console.print(f" {error['error']}")
|
|
1229
1426
|
raise typer.Exit(code=1)
|
|
1230
1427
|
else:
|
|
1231
|
-
|
|
1428
|
+
if dryrun:
|
|
1429
|
+
console.print("\n[green]✓[/green] Dry run completed successfully - no changes made!")
|
|
1430
|
+
else:
|
|
1431
|
+
console.print("\n[green]✓[/green] All sellers published successfully!")
|
|
1232
1432
|
|
|
1233
1433
|
except typer.Exit:
|
|
1234
1434
|
raise
|
|
@@ -1245,6 +1445,11 @@ def publish_offerings(
|
|
|
1245
1445
|
"-d",
|
|
1246
1446
|
help="Path to service offering file or directory (default: current directory)",
|
|
1247
1447
|
),
|
|
1448
|
+
dryrun: bool = typer.Option(
|
|
1449
|
+
False,
|
|
1450
|
+
"--dryrun",
|
|
1451
|
+
help="Run in dry run mode (no actual changes)",
|
|
1452
|
+
),
|
|
1248
1453
|
):
|
|
1249
1454
|
"""Publish service offering(s) from a file or directory."""
|
|
1250
1455
|
# Set data path
|
|
@@ -1264,16 +1469,16 @@ def publish_offerings(
|
|
|
1264
1469
|
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
|
1265
1470
|
else:
|
|
1266
1471
|
console.print(f"[blue]Scanning for service offerings in:[/blue] {data_path}")
|
|
1267
|
-
console.print(f"[blue]Backend URL:[/
|
|
1472
|
+
console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
|
|
1268
1473
|
|
|
1269
1474
|
async def _publish_offerings_async():
|
|
1270
1475
|
async with ServiceDataPublisher() as publisher:
|
|
1271
1476
|
# Handle single file
|
|
1272
1477
|
if data_path.is_file():
|
|
1273
|
-
return await publisher.post_service_offering_async(data_path), True
|
|
1478
|
+
return await publisher.post_service_offering_async(data_path, dryrun=dryrun), True
|
|
1274
1479
|
# Handle directory
|
|
1275
1480
|
else:
|
|
1276
|
-
return await publisher.publish_all_offerings(data_path), False
|
|
1481
|
+
return await publisher.publish_all_offerings(data_path, dryrun=dryrun), False
|
|
1277
1482
|
|
|
1278
1483
|
try:
|
|
1279
1484
|
result, is_single = asyncio.run(_publish_offerings_async())
|
|
@@ -1282,10 +1487,27 @@ def publish_offerings(
|
|
|
1282
1487
|
console.print("[green]✓[/green] Service offering published successfully!")
|
|
1283
1488
|
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
|
1284
1489
|
else:
|
|
1285
|
-
console.print("\n[bold]Publishing Summary
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1490
|
+
console.print("\n[bold cyan]Publishing Summary[/bold cyan]")
|
|
1491
|
+
table = Table(show_header=True, header_style="bold cyan", border_style="cyan")
|
|
1492
|
+
table.add_column("Type", style="cyan")
|
|
1493
|
+
table.add_column("Found", justify="right")
|
|
1494
|
+
table.add_column("Success", justify="right")
|
|
1495
|
+
table.add_column("Failed", justify="right")
|
|
1496
|
+
table.add_column("Created", justify="right")
|
|
1497
|
+
table.add_column("Updated", justify="right")
|
|
1498
|
+
table.add_column("Unchanged", justify="right")
|
|
1499
|
+
|
|
1500
|
+
table.add_row(
|
|
1501
|
+
"Offerings",
|
|
1502
|
+
str(result["total"]),
|
|
1503
|
+
f"[green]{result['success']}[/green]",
|
|
1504
|
+
f"[red]{result['failed']}[/red]" if result["failed"] > 0 else "",
|
|
1505
|
+
f"[green]{result['created']}[/green]" if result["created"] > 0 else "",
|
|
1506
|
+
f"[blue]{result['updated']}[/blue]" if result["updated"] > 0 else "",
|
|
1507
|
+
f"[dim]{result['unchanged']}[/dim]" if result["unchanged"] > 0 else "",
|
|
1508
|
+
)
|
|
1509
|
+
|
|
1510
|
+
console.print(table)
|
|
1289
1511
|
|
|
1290
1512
|
if result["errors"]:
|
|
1291
1513
|
console.print("\n[bold red]Errors:[/bold red]")
|
|
@@ -1294,7 +1516,10 @@ def publish_offerings(
|
|
|
1294
1516
|
console.print(f" {error['error']}")
|
|
1295
1517
|
raise typer.Exit(code=1)
|
|
1296
1518
|
else:
|
|
1297
|
-
|
|
1519
|
+
if dryrun:
|
|
1520
|
+
console.print("\n[green]✓[/green] Dry run completed successfully - no changes made!")
|
|
1521
|
+
else:
|
|
1522
|
+
console.print("\n[green]✓[/green] All service offerings published successfully!")
|
|
1298
1523
|
|
|
1299
1524
|
except typer.Exit:
|
|
1300
1525
|
raise
|
|
@@ -1311,6 +1536,11 @@ def publish_listings(
|
|
|
1311
1536
|
"-d",
|
|
1312
1537
|
help="Path to service listing file or directory (default: current directory)",
|
|
1313
1538
|
),
|
|
1539
|
+
dryrun: bool = typer.Option(
|
|
1540
|
+
False,
|
|
1541
|
+
"--dryrun",
|
|
1542
|
+
help="Run in dry run mode (no actual changes)",
|
|
1543
|
+
),
|
|
1314
1544
|
):
|
|
1315
1545
|
"""Publish service listing(s) from a file or directory."""
|
|
1316
1546
|
|
|
@@ -1337,10 +1567,10 @@ def publish_listings(
|
|
|
1337
1567
|
async with ServiceDataPublisher() as publisher:
|
|
1338
1568
|
# Handle single file
|
|
1339
1569
|
if data_path.is_file():
|
|
1340
|
-
return await publisher.post_service_listing_async(data_path), True
|
|
1570
|
+
return await publisher.post_service_listing_async(data_path, dryrun=dryrun), True
|
|
1341
1571
|
# Handle directory
|
|
1342
1572
|
else:
|
|
1343
|
-
return await publisher.publish_all_listings(data_path), False
|
|
1573
|
+
return await publisher.publish_all_listings(data_path, dryrun=dryrun), False
|
|
1344
1574
|
|
|
1345
1575
|
try:
|
|
1346
1576
|
result, is_single = asyncio.run(_publish_listings_async())
|
|
@@ -1349,10 +1579,27 @@ def publish_listings(
|
|
|
1349
1579
|
console.print("[green]✓[/green] Service listing published successfully!")
|
|
1350
1580
|
console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
|
|
1351
1581
|
else:
|
|
1352
|
-
console.print("\n[bold]Publishing Summary
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1582
|
+
console.print("\n[bold cyan]Publishing Summary[/bold cyan]")
|
|
1583
|
+
table = Table(show_header=True, header_style="bold cyan", border_style="cyan")
|
|
1584
|
+
table.add_column("Type", style="cyan")
|
|
1585
|
+
table.add_column("Found", justify="right")
|
|
1586
|
+
table.add_column("Success", justify="right")
|
|
1587
|
+
table.add_column("Failed", justify="right")
|
|
1588
|
+
table.add_column("Created", justify="right")
|
|
1589
|
+
table.add_column("Updated", justify="right")
|
|
1590
|
+
table.add_column("Unchanged", justify="right")
|
|
1591
|
+
|
|
1592
|
+
table.add_row(
|
|
1593
|
+
"Listings",
|
|
1594
|
+
str(result["total"]),
|
|
1595
|
+
f"[green]{result['success']}[/green]",
|
|
1596
|
+
f"[red]{result['failed']}[/red]" if result["failed"] > 0 else "",
|
|
1597
|
+
f"[green]{result['created']}[/green]" if result["created"] > 0 else "",
|
|
1598
|
+
f"[blue]{result['updated']}[/blue]" if result["updated"] > 0 else "",
|
|
1599
|
+
f"[dim]{result['unchanged']}[/dim]" if result["unchanged"] > 0 else "",
|
|
1600
|
+
)
|
|
1601
|
+
|
|
1602
|
+
console.print(table)
|
|
1356
1603
|
|
|
1357
1604
|
if result["errors"]:
|
|
1358
1605
|
console.print("\n[bold red]Errors:[/bold red]")
|
|
@@ -1361,7 +1608,10 @@ def publish_listings(
|
|
|
1361
1608
|
console.print(f" {error['error']}")
|
|
1362
1609
|
raise typer.Exit(code=1)
|
|
1363
1610
|
else:
|
|
1364
|
-
|
|
1611
|
+
if dryrun:
|
|
1612
|
+
console.print("\n[green]✓[/green] Dry run completed successfully - no changes made!")
|
|
1613
|
+
else:
|
|
1614
|
+
console.print("\n[green]✓[/green] All service listings published successfully!")
|
|
1365
1615
|
|
|
1366
1616
|
except typer.Exit:
|
|
1367
1617
|
raise
|