unitysvc-services 0.1.10__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.

@@ -190,7 +190,7 @@ class ServiceDataPublisher(UnitySvcAPI):
190
190
  return result
191
191
 
192
192
  async def post( # type: ignore[override]
193
- 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
194
194
  ) -> tuple[dict[str, Any], int]:
195
195
  """Make a POST request to the backend API with automatic curl fallback.
196
196
 
@@ -201,6 +201,7 @@ class ServiceDataPublisher(UnitySvcAPI):
201
201
  endpoint: API endpoint path (e.g., "/publish/seller")
202
202
  data: JSON data to post
203
203
  check_status: Whether to raise on non-2xx status codes (default: True)
204
+ dryrun: If True, adds dryrun=true as query parameter
204
205
 
205
206
  Returns:
206
207
  Tuple of (JSON response, HTTP status code)
@@ -208,16 +209,19 @@ class ServiceDataPublisher(UnitySvcAPI):
208
209
  Raises:
209
210
  RuntimeError: If both httpx and curl fail
210
211
  """
212
+ # Build query parameters
213
+ params = {"dryrun": "true"} if dryrun else None
214
+
211
215
  # Use base class client (self.client from UnitySvcQuery) with automatic curl fallback
212
216
  # If we already know curl is needed, use it directly
213
217
  if self.use_curl_fallback:
214
218
  # Use base class curl fallback method
215
- response_json = await super().post(endpoint, json_data=data)
219
+ response_json = await super().post(endpoint, json_data=data, params=params)
216
220
  # Curl POST doesn't return status code separately, assume 2xx if no exception
217
221
  status_code = 200
218
222
  else:
219
223
  try:
220
- 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)
221
225
  status_code = response.status_code
222
226
 
223
227
  if check_status:
@@ -227,7 +231,7 @@ class ServiceDataPublisher(UnitySvcAPI):
227
231
  except (httpx.ConnectError, OSError):
228
232
  # Connection failed - switch to curl fallback and retry
229
233
  self.use_curl_fallback = True
230
- response_json = await super().post(endpoint, json_data=data)
234
+ response_json = await super().post(endpoint, json_data=data, params=params)
231
235
  status_code = 200 # Assume success if curl didn't raise
232
236
 
233
237
  return (response_json, status_code)
@@ -240,6 +244,7 @@ class ServiceDataPublisher(UnitySvcAPI):
240
244
  entity_name: str,
241
245
  context_info: str = "",
242
246
  max_retries: int = 3,
247
+ dryrun: bool = False,
243
248
  ) -> dict[str, Any]:
244
249
  """
245
250
  Generic retry wrapper for posting data to backend API with task polling.
@@ -257,6 +262,7 @@ class ServiceDataPublisher(UnitySvcAPI):
257
262
  entity_name: Name of the entity being published (for error messages)
258
263
  context_info: Additional context for error messages (e.g., provider, service info)
259
264
  max_retries: Maximum number of retry attempts
265
+ dryrun: If True, runs in dry run mode (no actual changes)
260
266
 
261
267
  Returns:
262
268
  Response JSON from successful API call
@@ -268,7 +274,7 @@ class ServiceDataPublisher(UnitySvcAPI):
268
274
  for attempt in range(max_retries):
269
275
  try:
270
276
  # Use the public post() method with automatic curl fallback
271
- 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)
272
278
 
273
279
  # Handle task-based response (HTTP 202)
274
280
  if status_code == 202:
@@ -333,7 +339,9 @@ class ServiceDataPublisher(UnitySvcAPI):
333
339
  raise last_exception
334
340
  raise ValueError("Unexpected error in retry logic")
335
341
 
336
- async def post_service_listing_async(self, listing_file: Path, max_retries: int = 3) -> dict[str, Any]:
342
+ async def post_service_listing_async(
343
+ self, listing_file: Path, max_retries: int = 3, dryrun: bool = False
344
+ ) -> dict[str, Any]:
337
345
  """Async version of post_service_listing for concurrent publishing with retry logic."""
338
346
  # Load the listing data file
339
347
  data = self.load_data_file(listing_file)
@@ -469,6 +477,7 @@ class ServiceDataPublisher(UnitySvcAPI):
469
477
  entity_name=data.get("name", "unknown"),
470
478
  context_info=context_info,
471
479
  max_retries=max_retries,
480
+ dryrun=dryrun,
472
481
  )
473
482
 
474
483
  # Add local metadata to result for display purposes
@@ -478,7 +487,9 @@ class ServiceDataPublisher(UnitySvcAPI):
478
487
 
479
488
  return result
480
489
 
481
- async def post_service_offering_async(self, data_file: Path, max_retries: int = 3) -> dict[str, Any]:
490
+ async def post_service_offering_async(
491
+ self, data_file: Path, max_retries: int = 3, dryrun: bool = False
492
+ ) -> dict[str, Any]:
482
493
  """Async version of post_service_offering for concurrent publishing with retry logic."""
483
494
  # Load the data file
484
495
  data = self.load_data_file(data_file)
@@ -539,6 +550,7 @@ class ServiceDataPublisher(UnitySvcAPI):
539
550
  entity_name=data.get("name", "unknown"),
540
551
  context_info=context_info,
541
552
  max_retries=max_retries,
553
+ dryrun=dryrun,
542
554
  )
543
555
 
544
556
  # Add local metadata to result for display purposes
@@ -546,7 +558,7 @@ class ServiceDataPublisher(UnitySvcAPI):
546
558
 
547
559
  return result
548
560
 
549
- 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]:
550
562
  """Async version of post_provider for concurrent publishing with retry logic."""
551
563
  # Load the data file
552
564
  data = self.load_data_file(data_file)
@@ -584,9 +596,10 @@ class ServiceDataPublisher(UnitySvcAPI):
584
596
  entity_type="provider",
585
597
  entity_name=data.get("name", "unknown"),
586
598
  max_retries=max_retries,
599
+ dryrun=dryrun,
587
600
  )
588
601
 
589
- 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]:
590
603
  """Async version of post_seller for concurrent publishing with retry logic."""
591
604
  # Load the data file
592
605
  data = self.load_data_file(data_file)
@@ -622,6 +635,7 @@ class ServiceDataPublisher(UnitySvcAPI):
622
635
  entity_type="seller",
623
636
  entity_name=data.get("name", "unknown"),
624
637
  max_retries=max_retries,
638
+ dryrun=dryrun,
625
639
  )
626
640
 
627
641
  def find_offering_files(self, data_dir: Path) -> list[Path]:
@@ -651,11 +665,13 @@ class ServiceDataPublisher(UnitySvcAPI):
651
665
  "created": ("[green]+[/green]", "green"),
652
666
  "updated": ("[blue]~[/blue]", "blue"),
653
667
  "unchanged": ("[dim]=[/dim]", "dim"),
668
+ "create": ("[yellow]?[/yellow]", "yellow"), # Dryrun: would be created
669
+ "update": ("[cyan]?[/cyan]", "cyan"), # Dryrun: would be updated
654
670
  }
655
671
  return status_map.get(status, ("[green]✓[/green]", "green"))
656
672
 
657
673
  async def _publish_offering_task(
658
- self, offering_file: Path, console: Console, semaphore: asyncio.Semaphore
674
+ self, offering_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
659
675
  ) -> tuple[Path, dict[str, Any] | Exception]:
660
676
  """
661
677
  Async task to publish a single offering with concurrency control.
@@ -669,7 +685,7 @@ class ServiceDataPublisher(UnitySvcAPI):
669
685
  offering_name = data.get("name", offering_file.stem)
670
686
 
671
687
  # Publish the offering
672
- result = await self.post_service_offering_async(offering_file)
688
+ result = await self.post_service_offering_async(offering_file, dryrun=dryrun)
673
689
 
674
690
  # Print complete statement after publication
675
691
  if result.get("skipped"):
@@ -691,12 +707,16 @@ class ServiceDataPublisher(UnitySvcAPI):
691
707
  console.print(f" [red]✗[/red] Failed to publish offering: [cyan]{offering_name}[/cyan] - {str(e)}")
692
708
  return (offering_file, e)
693
709
 
694
- 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]:
695
711
  """
696
712
  Publish all service offerings found in a directory tree concurrently.
697
713
 
698
714
  Validates data consistency before publishing.
699
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)
700
720
  """
701
721
  # Validate all service directories first
702
722
  schema_dir = Path(unitysvc_services.__file__).parent / "schema"
@@ -729,7 +749,10 @@ class ServiceDataPublisher(UnitySvcAPI):
729
749
  # Run all offering publications concurrently with rate limiting
730
750
  # Create semaphore to limit concurrent requests
731
751
  semaphore = asyncio.Semaphore(self.max_concurrent_requests)
732
- tasks = [self._publish_offering_task(offering_file, console, semaphore) for offering_file in offering_files]
752
+ tasks = [
753
+ self._publish_offering_task(offering_file, console, semaphore, dryrun=dryrun)
754
+ for offering_file in offering_files
755
+ ]
733
756
  task_results = await asyncio.gather(*tasks)
734
757
 
735
758
  # Process results
@@ -739,11 +762,11 @@ class ServiceDataPublisher(UnitySvcAPI):
739
762
  results["errors"].append({"file": str(offering_file), "error": str(result)})
740
763
  else:
741
764
  results["success"] += 1
742
- # Track status counts
765
+ # Track status counts (handle both normal and dryrun statuses)
743
766
  status = result.get("status", "created")
744
- if status == "created":
767
+ if status in ("created", "create"): # "create" is dryrun mode
745
768
  results["created"] += 1
746
- elif status == "updated":
769
+ elif status in ("updated", "update"): # "update" is dryrun mode
747
770
  results["updated"] += 1
748
771
  elif status == "unchanged":
749
772
  results["unchanged"] += 1
@@ -751,7 +774,7 @@ class ServiceDataPublisher(UnitySvcAPI):
751
774
  return results
752
775
 
753
776
  async def _publish_listing_task(
754
- self, listing_file: Path, console: Console, semaphore: asyncio.Semaphore
777
+ self, listing_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
755
778
  ) -> tuple[Path, dict[str, Any] | Exception]:
756
779
  """
757
780
  Async task to publish a single listing with concurrency control.
@@ -765,7 +788,7 @@ class ServiceDataPublisher(UnitySvcAPI):
765
788
  listing_name = data.get("name", listing_file.stem)
766
789
 
767
790
  # Publish the listing
768
- result = await self.post_service_listing_async(listing_file)
791
+ result = await self.post_service_listing_async(listing_file, dryrun=dryrun)
769
792
 
770
793
  # Print complete statement after publication
771
794
  if result.get("skipped"):
@@ -788,7 +811,7 @@ class ServiceDataPublisher(UnitySvcAPI):
788
811
  console.print(f" [red]✗[/red] Failed to publish listing: [cyan]{listing_file}[/cyan] - {str(e)}")
789
812
  return (listing_file, e)
790
813
 
791
- 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]:
792
815
  """
793
816
  Publish all service listings found in a directory tree concurrently.
794
817
 
@@ -826,7 +849,10 @@ class ServiceDataPublisher(UnitySvcAPI):
826
849
  # Run all listing publications concurrently with rate limiting
827
850
  # Create semaphore to limit concurrent requests
828
851
  semaphore = asyncio.Semaphore(self.max_concurrent_requests)
829
- tasks = [self._publish_listing_task(listing_file, console, semaphore) for listing_file in listing_files]
852
+ tasks = [
853
+ self._publish_listing_task(listing_file, console, semaphore, dryrun=dryrun)
854
+ for listing_file in listing_files
855
+ ]
830
856
  task_results = await asyncio.gather(*tasks)
831
857
 
832
858
  # Process results
@@ -836,11 +862,11 @@ class ServiceDataPublisher(UnitySvcAPI):
836
862
  results["errors"].append({"file": str(listing_file), "error": str(result)})
837
863
  else:
838
864
  results["success"] += 1
839
- # Track status counts
865
+ # Track status counts (handle both normal and dryrun statuses)
840
866
  status = result.get("status", "created")
841
- if status == "created":
867
+ if status in ("created", "create"): # "create" is dryrun mode
842
868
  results["created"] += 1
843
- elif status == "updated":
869
+ elif status in ("updated", "update"): # "update" is dryrun mode
844
870
  results["updated"] += 1
845
871
  elif status == "unchanged":
846
872
  results["unchanged"] += 1
@@ -848,7 +874,7 @@ class ServiceDataPublisher(UnitySvcAPI):
848
874
  return results
849
875
 
850
876
  async def _publish_provider_task(
851
- self, provider_file: Path, console: Console, semaphore: asyncio.Semaphore
877
+ self, provider_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
852
878
  ) -> tuple[Path, dict[str, Any] | Exception]:
853
879
  """
854
880
  Async task to publish a single provider with concurrency control.
@@ -862,7 +888,7 @@ class ServiceDataPublisher(UnitySvcAPI):
862
888
  provider_name = data.get("name", provider_file.stem)
863
889
 
864
890
  # Publish the provider
865
- result = await self.post_provider_async(provider_file)
891
+ result = await self.post_provider_async(provider_file, dryrun=dryrun)
866
892
 
867
893
  # Print complete statement after publication
868
894
  if result.get("skipped"):
@@ -882,7 +908,7 @@ class ServiceDataPublisher(UnitySvcAPI):
882
908
  console.print(f" [red]✗[/red] Failed to publish provider: [cyan]{provider_name}[/cyan] - {str(e)}")
883
909
  return (provider_file, e)
884
910
 
885
- 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]:
886
912
  """
887
913
  Publish all providers found in a directory tree concurrently.
888
914
 
@@ -907,7 +933,10 @@ class ServiceDataPublisher(UnitySvcAPI):
907
933
  # Run all provider publications concurrently with rate limiting
908
934
  # Create semaphore to limit concurrent requests
909
935
  semaphore = asyncio.Semaphore(self.max_concurrent_requests)
910
- tasks = [self._publish_provider_task(provider_file, console, semaphore) for provider_file in provider_files]
936
+ tasks = [
937
+ self._publish_provider_task(provider_file, console, semaphore, dryrun=dryrun)
938
+ for provider_file in provider_files
939
+ ]
911
940
  task_results = await asyncio.gather(*tasks)
912
941
 
913
942
  # Process results
@@ -917,11 +946,11 @@ class ServiceDataPublisher(UnitySvcAPI):
917
946
  results["errors"].append({"file": str(provider_file), "error": str(result)})
918
947
  else:
919
948
  results["success"] += 1
920
- # Track status counts
949
+ # Track status counts (handle both normal and dryrun statuses)
921
950
  status = result.get("status", "created")
922
- if status == "created":
951
+ if status in ("created", "create"): # "create" is dryrun mode
923
952
  results["created"] += 1
924
- elif status == "updated":
953
+ elif status in ("updated", "update"): # "update" is dryrun mode
925
954
  results["updated"] += 1
926
955
  elif status == "unchanged":
927
956
  results["unchanged"] += 1
@@ -929,7 +958,7 @@ class ServiceDataPublisher(UnitySvcAPI):
929
958
  return results
930
959
 
931
960
  async def _publish_seller_task(
932
- self, seller_file: Path, console: Console, semaphore: asyncio.Semaphore
961
+ self, seller_file: Path, console: Console, semaphore: asyncio.Semaphore, dryrun: bool = False
933
962
  ) -> tuple[Path, dict[str, Any] | Exception]:
934
963
  """
935
964
  Async task to publish a single seller with concurrency control.
@@ -943,7 +972,7 @@ class ServiceDataPublisher(UnitySvcAPI):
943
972
  seller_name = data.get("name", seller_file.stem)
944
973
 
945
974
  # Publish the seller
946
- result = await self.post_seller_async(seller_file)
975
+ result = await self.post_seller_async(seller_file, dryrun=dryrun)
947
976
 
948
977
  # Print complete statement after publication
949
978
  if result.get("skipped"):
@@ -963,7 +992,7 @@ class ServiceDataPublisher(UnitySvcAPI):
963
992
  console.print(f" [red]✗[/red] Failed to publish seller: [cyan]{seller_name}[/cyan] - {str(e)}")
964
993
  return (seller_file, e)
965
994
 
966
- 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]:
967
996
  """
968
997
  Publish all sellers found in a directory tree concurrently.
969
998
 
@@ -988,7 +1017,9 @@ class ServiceDataPublisher(UnitySvcAPI):
988
1017
  # Run all seller publications concurrently with rate limiting
989
1018
  # Create semaphore to limit concurrent requests
990
1019
  semaphore = asyncio.Semaphore(self.max_concurrent_requests)
991
- tasks = [self._publish_seller_task(seller_file, console, semaphore) for seller_file in seller_files]
1020
+ tasks = [
1021
+ self._publish_seller_task(seller_file, console, semaphore, dryrun=dryrun) for seller_file in seller_files
1022
+ ]
992
1023
  task_results = await asyncio.gather(*tasks)
993
1024
 
994
1025
  # Process results
@@ -998,18 +1029,18 @@ class ServiceDataPublisher(UnitySvcAPI):
998
1029
  results["errors"].append({"file": str(seller_file), "error": str(result)})
999
1030
  else:
1000
1031
  results["success"] += 1
1001
- # Track status counts
1032
+ # Track status counts (handle both normal and dryrun statuses)
1002
1033
  status = result.get("status", "created")
1003
- if status == "created":
1034
+ if status in ("created", "create"): # "create" is dryrun mode
1004
1035
  results["created"] += 1
1005
- elif status == "updated":
1036
+ elif status in ("updated", "update"): # "update" is dryrun mode
1006
1037
  results["updated"] += 1
1007
1038
  elif status == "unchanged":
1008
1039
  results["unchanged"] += 1
1009
1040
 
1010
1041
  return results
1011
1042
 
1012
- 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]:
1013
1044
  """
1014
1045
  Publish all data types in the correct order.
1015
1046
 
@@ -1044,7 +1075,7 @@ class ServiceDataPublisher(UnitySvcAPI):
1044
1075
 
1045
1076
  for data_type, publish_method in publish_order:
1046
1077
  try:
1047
- results = await publish_method(data_dir)
1078
+ results = await publish_method(data_dir, dryrun=dryrun)
1048
1079
  all_results[data_type] = results
1049
1080
  all_results["total_success"] += results["success"]
1050
1081
  all_results["total_failed"] += results["failed"]
@@ -1079,6 +1110,11 @@ def publish_callback(
1079
1110
  "-d",
1080
1111
  help="Path to data directory (default: current directory)",
1081
1112
  ),
1113
+ dryrun: bool = typer.Option(
1114
+ False,
1115
+ "--dryrun",
1116
+ help="Run in dry run mode (no actual changes)",
1117
+ ),
1082
1118
  ):
1083
1119
  """
1084
1120
  Publish data to backend.
@@ -1117,7 +1153,7 @@ def publish_callback(
1117
1153
 
1118
1154
  async def _publish_all_async():
1119
1155
  async with ServiceDataPublisher() as publisher:
1120
- return await publisher.publish_all_models(data_path)
1156
+ return await publisher.publish_all_models(data_path, dryrun=dryrun)
1121
1157
 
1122
1158
  try:
1123
1159
  all_results = asyncio.run(_publish_all_async())
@@ -1148,12 +1184,12 @@ def publish_callback(
1148
1184
 
1149
1185
  table.add_row(
1150
1186
  display_name,
1151
- str(results['total']),
1152
- str(results['success']),
1153
- str(results['failed']),
1154
- str(results.get('created', 0)),
1155
- str(results.get('updated', 0)),
1156
- str(results.get('unchanged', 0)),
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 "",
1157
1193
  )
1158
1194
 
1159
1195
  # Add separator and total row
@@ -1162,10 +1198,10 @@ def publish_callback(
1162
1198
  "[bold]Total[/bold]",
1163
1199
  f"[bold]{all_results['total_found']}[/bold]",
1164
1200
  f"[bold green]{all_results['total_success']}[/bold green]",
1165
- f"[bold red]{all_results['total_failed']}[/bold red]",
1166
- f"[bold green]{all_results['total_created']}[/bold green]",
1167
- f"[bold blue]{all_results['total_updated']}[/bold blue]",
1168
- f"[bold]{all_results['total_unchanged']}[/bold]",
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 "",
1169
1205
  )
1170
1206
 
1171
1207
  console.print(table)
@@ -1196,10 +1232,16 @@ def publish_callback(
1196
1232
  )
1197
1233
  raise typer.Exit(code=1)
1198
1234
  else:
1199
- console.print(
1200
- "\n[green]✓[/green] All data published successfully!",
1201
- style="bold green",
1202
- )
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
+ )
1203
1245
 
1204
1246
  except typer.Exit:
1205
1247
  raise
@@ -1216,6 +1258,11 @@ def publish_providers(
1216
1258
  "-d",
1217
1259
  help="Path to provider file or directory (default: current directory)",
1218
1260
  ),
1261
+ dryrun: bool = typer.Option(
1262
+ False,
1263
+ "--dryrun",
1264
+ help="Run in dry run mode (no actual changes)",
1265
+ ),
1219
1266
  ):
1220
1267
  """Publish provider(s) from a file or directory."""
1221
1268
 
@@ -1242,10 +1289,10 @@ def publish_providers(
1242
1289
  async with ServiceDataPublisher() as publisher:
1243
1290
  # Handle single file
1244
1291
  if data_path.is_file():
1245
- return await publisher.post_provider_async(data_path), True
1292
+ return await publisher.post_provider_async(data_path, dryrun=dryrun), True
1246
1293
  # Handle directory
1247
1294
  else:
1248
- return await publisher.publish_all_providers(data_path), False
1295
+ return await publisher.publish_all_providers(data_path, dryrun=dryrun), False
1249
1296
 
1250
1297
  try:
1251
1298
  result, is_single = asyncio.run(_publish_providers_async())
@@ -1255,14 +1302,27 @@ def publish_providers(
1255
1302
  console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
1256
1303
  else:
1257
1304
  # Display summary
1258
- console.print("\n[bold]Publishing Summary:[/bold]")
1259
- console.print(f" Total found: {result['total']}")
1260
- console.print(f" [green]✓ Success:[/green] {result['success']}")
1261
- console.print(f" [red]✗ Failed:[/red] {result['failed']}")
1262
- if result["success"] > 0:
1263
- console.print(f" [green]+ Created: {result['created']}[/green]")
1264
- console.print(f" [blue]~ Updated: {result['updated']}[/blue]")
1265
- console.print(f" [dim]= Unchanged: {result['unchanged']}[/dim]")
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)
1266
1326
 
1267
1327
  # Display errors if any
1268
1328
  if result["errors"]:
@@ -1273,6 +1333,11 @@ def publish_providers(
1273
1333
 
1274
1334
  if result["failed"] > 0:
1275
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!")
1276
1341
 
1277
1342
  except typer.Exit:
1278
1343
  raise
@@ -1289,6 +1354,11 @@ def publish_sellers(
1289
1354
  "-d",
1290
1355
  help="Path to seller file or directory (default: current directory)",
1291
1356
  ),
1357
+ dryrun: bool = typer.Option(
1358
+ False,
1359
+ "--dryrun",
1360
+ help="Run in dry run mode (no actual changes)",
1361
+ ),
1292
1362
  ):
1293
1363
  """Publish seller(s) from a file or directory."""
1294
1364
  # Set data path
@@ -1314,10 +1384,10 @@ def publish_sellers(
1314
1384
  async with ServiceDataPublisher() as publisher:
1315
1385
  # Handle single file
1316
1386
  if data_path.is_file():
1317
- return await publisher.post_seller_async(data_path), True
1387
+ return await publisher.post_seller_async(data_path, dryrun=dryrun), True
1318
1388
  # Handle directory
1319
1389
  else:
1320
- return await publisher.publish_all_sellers(data_path), False
1390
+ return await publisher.publish_all_sellers(data_path, dryrun=dryrun), False
1321
1391
 
1322
1392
  try:
1323
1393
  result, is_single = asyncio.run(_publish_sellers_async())
@@ -1326,14 +1396,27 @@ def publish_sellers(
1326
1396
  console.print("[green]✓[/green] Seller published successfully!")
1327
1397
  console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
1328
1398
  else:
1329
- console.print("\n[bold]Publishing Summary:[/bold]")
1330
- console.print(f" Total found: {result['total']}")
1331
- console.print(f" [green]✓ Success: {result['success']}[/green]")
1332
- console.print(f" [red]✗ Failed: {result['failed']}[/red]")
1333
- if result["success"] > 0:
1334
- console.print(f" [green]+ Created: {result['created']}[/green]")
1335
- console.print(f" [blue]~ Updated: {result['updated']}[/blue]")
1336
- console.print(f" [dim]= Unchanged: {result['unchanged']}[/dim]")
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)
1337
1420
 
1338
1421
  if result["errors"]:
1339
1422
  console.print("\n[bold red]Errors:[/bold red]")
@@ -1342,7 +1425,10 @@ def publish_sellers(
1342
1425
  console.print(f" {error['error']}")
1343
1426
  raise typer.Exit(code=1)
1344
1427
  else:
1345
- console.print("\n[green]✓[/green] All sellers published successfully!")
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!")
1346
1432
 
1347
1433
  except typer.Exit:
1348
1434
  raise
@@ -1359,6 +1445,11 @@ def publish_offerings(
1359
1445
  "-d",
1360
1446
  help="Path to service offering file or directory (default: current directory)",
1361
1447
  ),
1448
+ dryrun: bool = typer.Option(
1449
+ False,
1450
+ "--dryrun",
1451
+ help="Run in dry run mode (no actual changes)",
1452
+ ),
1362
1453
  ):
1363
1454
  """Publish service offering(s) from a file or directory."""
1364
1455
  # Set data path
@@ -1384,10 +1475,10 @@ def publish_offerings(
1384
1475
  async with ServiceDataPublisher() as publisher:
1385
1476
  # Handle single file
1386
1477
  if data_path.is_file():
1387
- return await publisher.post_service_offering_async(data_path), True
1478
+ return await publisher.post_service_offering_async(data_path, dryrun=dryrun), True
1388
1479
  # Handle directory
1389
1480
  else:
1390
- return await publisher.publish_all_offerings(data_path), False
1481
+ return await publisher.publish_all_offerings(data_path, dryrun=dryrun), False
1391
1482
 
1392
1483
  try:
1393
1484
  result, is_single = asyncio.run(_publish_offerings_async())
@@ -1396,14 +1487,27 @@ def publish_offerings(
1396
1487
  console.print("[green]✓[/green] Service offering published successfully!")
1397
1488
  console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
1398
1489
  else:
1399
- console.print("\n[bold]Publishing Summary:[/bold]")
1400
- console.print(f" Total found: {result['total']}")
1401
- console.print(f" [green]✓ Success: {result['success']}[/green]")
1402
- console.print(f" [red]✗ Failed: {result['failed']}[/red]")
1403
- if result["success"] > 0:
1404
- console.print(f" [green]+ Created: {result['created']}[/green]")
1405
- console.print(f" [blue]~ Updated: {result['updated']}[/blue]")
1406
- console.print(f" [dim]= Unchanged: {result['unchanged']}[/dim]")
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)
1407
1511
 
1408
1512
  if result["errors"]:
1409
1513
  console.print("\n[bold red]Errors:[/bold red]")
@@ -1412,7 +1516,10 @@ def publish_offerings(
1412
1516
  console.print(f" {error['error']}")
1413
1517
  raise typer.Exit(code=1)
1414
1518
  else:
1415
- console.print("\n[green]✓[/green] All service offerings published successfully!")
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!")
1416
1523
 
1417
1524
  except typer.Exit:
1418
1525
  raise
@@ -1429,6 +1536,11 @@ def publish_listings(
1429
1536
  "-d",
1430
1537
  help="Path to service listing file or directory (default: current directory)",
1431
1538
  ),
1539
+ dryrun: bool = typer.Option(
1540
+ False,
1541
+ "--dryrun",
1542
+ help="Run in dry run mode (no actual changes)",
1543
+ ),
1432
1544
  ):
1433
1545
  """Publish service listing(s) from a file or directory."""
1434
1546
 
@@ -1455,10 +1567,10 @@ def publish_listings(
1455
1567
  async with ServiceDataPublisher() as publisher:
1456
1568
  # Handle single file
1457
1569
  if data_path.is_file():
1458
- return await publisher.post_service_listing_async(data_path), True
1570
+ return await publisher.post_service_listing_async(data_path, dryrun=dryrun), True
1459
1571
  # Handle directory
1460
1572
  else:
1461
- return await publisher.publish_all_listings(data_path), False
1573
+ return await publisher.publish_all_listings(data_path, dryrun=dryrun), False
1462
1574
 
1463
1575
  try:
1464
1576
  result, is_single = asyncio.run(_publish_listings_async())
@@ -1467,14 +1579,27 @@ def publish_listings(
1467
1579
  console.print("[green]✓[/green] Service listing published successfully!")
1468
1580
  console.print(f"[cyan]Response:[/cyan] {json.dumps(result, indent=2)}")
1469
1581
  else:
1470
- console.print("\n[bold]Publishing Summary:[/bold]")
1471
- console.print(f" Total found: {result['total']}")
1472
- console.print(f" [green]✓ Success: {result['success']}[/green]")
1473
- console.print(f" [red]✗ Failed: {result['failed']}[/red]")
1474
- if result["success"] > 0:
1475
- console.print(f" [green]+ Created: {result['created']}[/green]")
1476
- console.print(f" [blue]~ Updated: {result['updated']}[/blue]")
1477
- console.print(f" [dim]= Unchanged: {result['unchanged']}[/dim]")
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)
1478
1603
 
1479
1604
  if result["errors"]:
1480
1605
  console.print("\n[bold red]Errors:[/bold red]")
@@ -1483,7 +1608,10 @@ def publish_listings(
1483
1608
  console.print(f" {error['error']}")
1484
1609
  raise typer.Exit(code=1)
1485
1610
  else:
1486
- console.print("\n[green]✓[/green] All service listings published successfully!")
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!")
1487
1615
 
1488
1616
  except typer.Exit:
1489
1617
  raise