unitysvc-services 0.1.8__tar.gz → 0.1.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {unitysvc_services-0.1.8/src/unitysvc_services.egg-info → unitysvc_services-0.1.10}/PKG-INFO +1 -1
  2. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/pyproject.toml +1 -1
  3. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/publisher.py +150 -28
  4. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/test.py +47 -16
  5. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10/src/unitysvc_services.egg-info}/PKG-INFO +1 -1
  6. unitysvc_services-0.1.10/tests/example_data/provider1/README.md +1 -0
  7. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider1/services/service1/service.toml +1 -1
  8. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider1/services/service1/svcreseller.toml +1 -1
  9. unitysvc_services-0.1.10/tests/example_data/provider2/README.md +1 -0
  10. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider2/services/service2/service.json +1 -1
  11. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider2/services/service2/svcreseller.json +2 -2
  12. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/seller.json +6 -6
  13. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/test_utils.py +5 -8
  14. unitysvc_services-0.1.8/tests/example_data/provider1/README.md +0 -0
  15. unitysvc_services-0.1.8/tests/example_data/provider2/README.md +0 -0
  16. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/CONTRIBUTING.md +0 -0
  17. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/HISTORY.md +0 -0
  18. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/LICENSE +0 -0
  19. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/MANIFEST.in +0 -0
  20. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/README.md +0 -0
  21. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/api-reference.md +0 -0
  22. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/cli-reference.md +0 -0
  23. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/code-examples.md +0 -0
  24. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/contributing.md +0 -0
  25. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/data-structure.md +0 -0
  26. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/development.md +0 -0
  27. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/documenting-services.md +0 -0
  28. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/file-schemas.md +0 -0
  29. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/getting-started.md +0 -0
  30. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/index.md +0 -0
  31. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/installation.md +0 -0
  32. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/usage.md +0 -0
  33. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/docs/workflows.md +0 -0
  34. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/setup.cfg +0 -0
  35. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/__init__.py +0 -0
  36. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/api.py +0 -0
  37. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/cli.py +0 -0
  38. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/format_data.py +0 -0
  39. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/list.py +0 -0
  40. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/models/__init__.py +0 -0
  41. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/models/base.py +0 -0
  42. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/models/listing_v1.py +0 -0
  43. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/models/provider_v1.py +0 -0
  44. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/models/seller_v1.py +0 -0
  45. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/models/service_v1.py +0 -0
  46. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/populate.py +0 -0
  47. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/py.typed +0 -0
  48. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/query.py +0 -0
  49. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/scaffold.py +0 -0
  50. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/schema/base.json +0 -0
  51. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/schema/listing_v1.json +0 -0
  52. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/schema/provider_v1.json +0 -0
  53. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/schema/seller_v1.json +0 -0
  54. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/schema/service_v1.json +0 -0
  55. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/update.py +0 -0
  56. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/utils.py +0 -0
  57. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services/validator.py +0 -0
  58. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services.egg-info/SOURCES.txt +0 -0
  59. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services.egg-info/dependency_links.txt +0 -0
  60. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services.egg-info/entry_points.txt +0 -0
  61. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services.egg-info/requires.txt +0 -0
  62. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/src/unitysvc_services.egg-info/top_level.txt +0 -0
  63. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/__init__.py +0 -0
  64. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/README.md +0 -0
  65. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider1/provider.toml +0 -0
  66. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider1/services/service1/code-example.md +0 -0
  67. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider1/terms-of-service.md +0 -0
  68. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider2/provider.json +0 -0
  69. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider2/services/service2/code-example.md +0 -0
  70. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/example_data/provider2/terms-of-service.md +0 -0
  71. {unitysvc_services-0.1.8 → unitysvc_services-0.1.10}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitysvc-services
3
- Version: 0.1.8
3
+ Version: 0.1.10
4
4
  Summary: SDK for digital service providers on UnitySVC
5
5
  Author-email: Bo Peng <bo.peng@unitysvc.com>
6
6
  Maintainer-email: Bo Peng <bo.peng@unitysvc.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "unitysvc-services"
7
- version = "0.1.8"
7
+ version = "0.1.10"
8
8
  description = "SDK for digital service providers on UnitySVC"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Bo Peng", email = "bo.peng@unitysvc.com" }]
@@ -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
@@ -641,6 +644,16 @@ class ServiceDataPublisher(UnitySvcAPI):
641
644
  files = find_files_by_schema(data_dir, "seller_v1")
642
645
  return sorted([f[0] for f in files])
643
646
 
647
+ @staticmethod
648
+ def _get_status_display(status: str) -> tuple[str, str]:
649
+ """Get color and symbol for status display."""
650
+ status_map = {
651
+ "created": ("[green]+[/green]", "green"),
652
+ "updated": ("[blue]~[/blue]", "blue"),
653
+ "unchanged": ("[dim]=[/dim]", "dim"),
654
+ }
655
+ return status_map.get(status, ("[green]✓[/green]", "green"))
656
+
644
657
  async def _publish_offering_task(
645
658
  self, offering_file: Path, console: Console, semaphore: asyncio.Semaphore
646
659
  ) -> tuple[Path, dict[str, Any] | Exception]:
@@ -664,8 +677,10 @@ class ServiceDataPublisher(UnitySvcAPI):
664
677
  console.print(f" [yellow]⊘[/yellow] Skipped offering: [cyan]{offering_name}[/cyan] - {reason}")
665
678
  else:
666
679
  provider_name = result.get("provider_name")
680
+ status = result.get("status", "created")
681
+ symbol, color = self._get_status_display(status)
667
682
  console.print(
668
- f" [green][/green] Published offering: [cyan]{offering_name}[/cyan] "
683
+ f" {symbol} [{color}]{status.capitalize()}[/{color}] offering: [cyan]{offering_name}[/cyan] "
669
684
  f"(provider: {provider_name})"
670
685
  )
671
686
 
@@ -684,7 +699,8 @@ class ServiceDataPublisher(UnitySvcAPI):
684
699
  Returns a summary of successes and failures.
685
700
  """
686
701
  # Validate all service directories first
687
- validator = DataValidator(data_dir, data_dir.parent / "schema")
702
+ schema_dir = Path(unitysvc_services.__file__).parent / "schema"
703
+ validator = DataValidator(data_dir, schema_dir)
688
704
  validation_errors = validator.validate_all_service_directories(data_dir)
689
705
  if validation_errors:
690
706
  return {
@@ -699,6 +715,9 @@ class ServiceDataPublisher(UnitySvcAPI):
699
715
  "total": len(offering_files),
700
716
  "success": 0,
701
717
  "failed": 0,
718
+ "created": 0,
719
+ "updated": 0,
720
+ "unchanged": 0,
702
721
  "errors": [],
703
722
  }
704
723
 
@@ -720,6 +739,14 @@ class ServiceDataPublisher(UnitySvcAPI):
720
739
  results["errors"].append({"file": str(offering_file), "error": str(result)})
721
740
  else:
722
741
  results["success"] += 1
742
+ # Track status counts
743
+ status = result.get("status", "created")
744
+ if status == "created":
745
+ results["created"] += 1
746
+ elif status == "updated":
747
+ results["updated"] += 1
748
+ elif status == "unchanged":
749
+ results["unchanged"] += 1
723
750
 
724
751
  return results
725
752
 
@@ -747,8 +774,10 @@ class ServiceDataPublisher(UnitySvcAPI):
747
774
  else:
748
775
  service_name = result.get("service_name")
749
776
  provider_name = result.get("provider_name")
777
+ status = result.get("status", "created")
778
+ symbol, color = self._get_status_display(status)
750
779
  console.print(
751
- f" [green][/green] Published listing: [cyan]{listing_name}[/cyan] "
780
+ f" {symbol} [{color}]{status.capitalize()}[/{color}] listing: [cyan]{listing_name}[/cyan] "
752
781
  f"(service: {service_name}, provider: {provider_name})"
753
782
  )
754
783
 
@@ -767,7 +796,8 @@ class ServiceDataPublisher(UnitySvcAPI):
767
796
  Returns a summary of successes and failures.
768
797
  """
769
798
  # Validate all service directories first
770
- validator = DataValidator(data_dir, data_dir.parent / "schema")
799
+ schema_dir = Path(unitysvc_services.__file__).parent / "schema"
800
+ validator = DataValidator(data_dir, schema_dir)
771
801
  validation_errors = validator.validate_all_service_directories(data_dir)
772
802
  if validation_errors:
773
803
  return {
@@ -782,6 +812,9 @@ class ServiceDataPublisher(UnitySvcAPI):
782
812
  "total": len(listing_files),
783
813
  "success": 0,
784
814
  "failed": 0,
815
+ "created": 0,
816
+ "updated": 0,
817
+ "unchanged": 0,
785
818
  "errors": [],
786
819
  }
787
820
 
@@ -803,6 +836,14 @@ class ServiceDataPublisher(UnitySvcAPI):
803
836
  results["errors"].append({"file": str(listing_file), "error": str(result)})
804
837
  else:
805
838
  results["success"] += 1
839
+ # Track status counts
840
+ status = result.get("status", "created")
841
+ if status == "created":
842
+ results["created"] += 1
843
+ elif status == "updated":
844
+ results["updated"] += 1
845
+ elif status == "unchanged":
846
+ results["unchanged"] += 1
806
847
 
807
848
  return results
808
849
 
@@ -828,7 +869,11 @@ class ServiceDataPublisher(UnitySvcAPI):
828
869
  reason = result.get("reason", "unknown")
829
870
  console.print(f" [yellow]⊘[/yellow] Skipped provider: [cyan]{provider_name}[/cyan] - {reason}")
830
871
  else:
831
- console.print(f" [green]✓[/green] Published provider: [cyan]{provider_name}[/cyan]")
872
+ status = result.get("status", "created")
873
+ symbol, color = self._get_status_display(status)
874
+ console.print(
875
+ f" {symbol} [{color}]{status.capitalize()}[/{color}] provider: [cyan]{provider_name}[/cyan]"
876
+ )
832
877
 
833
878
  return (provider_file, result)
834
879
  except Exception as e:
@@ -848,6 +893,9 @@ class ServiceDataPublisher(UnitySvcAPI):
848
893
  "total": len(provider_files),
849
894
  "success": 0,
850
895
  "failed": 0,
896
+ "created": 0,
897
+ "updated": 0,
898
+ "unchanged": 0,
851
899
  "errors": [],
852
900
  }
853
901
 
@@ -869,6 +917,14 @@ class ServiceDataPublisher(UnitySvcAPI):
869
917
  results["errors"].append({"file": str(provider_file), "error": str(result)})
870
918
  else:
871
919
  results["success"] += 1
920
+ # Track status counts
921
+ status = result.get("status", "created")
922
+ if status == "created":
923
+ results["created"] += 1
924
+ elif status == "updated":
925
+ results["updated"] += 1
926
+ elif status == "unchanged":
927
+ results["unchanged"] += 1
872
928
 
873
929
  return results
874
930
 
@@ -894,7 +950,11 @@ class ServiceDataPublisher(UnitySvcAPI):
894
950
  reason = result.get("reason", "unknown")
895
951
  console.print(f" [yellow]⊘[/yellow] Skipped seller: [cyan]{seller_name}[/cyan] - {reason}")
896
952
  else:
897
- console.print(f" [green]✓[/green] Published seller: [cyan]{seller_name}[/cyan]")
953
+ status = result.get("status", "created")
954
+ symbol, color = self._get_status_display(status)
955
+ console.print(
956
+ f" {symbol} [{color}]{status.capitalize()}[/{color}] seller: [cyan]{seller_name}[/cyan]"
957
+ )
898
958
 
899
959
  return (seller_file, result)
900
960
  except Exception as e:
@@ -914,6 +974,9 @@ class ServiceDataPublisher(UnitySvcAPI):
914
974
  "total": len(seller_files),
915
975
  "success": 0,
916
976
  "failed": 0,
977
+ "created": 0,
978
+ "updated": 0,
979
+ "unchanged": 0,
917
980
  "errors": [],
918
981
  }
919
982
 
@@ -935,6 +998,14 @@ class ServiceDataPublisher(UnitySvcAPI):
935
998
  results["errors"].append({"file": str(seller_file), "error": str(result)})
936
999
  else:
937
1000
  results["success"] += 1
1001
+ # Track status counts
1002
+ status = result.get("status", "created")
1003
+ if status == "created":
1004
+ results["created"] += 1
1005
+ elif status == "updated":
1006
+ results["updated"] += 1
1007
+ elif status == "unchanged":
1008
+ results["unchanged"] += 1
938
1009
 
939
1010
  return results
940
1011
 
@@ -958,6 +1029,9 @@ class ServiceDataPublisher(UnitySvcAPI):
958
1029
  "total_success": 0,
959
1030
  "total_failed": 0,
960
1031
  "total_found": 0,
1032
+ "total_created": 0,
1033
+ "total_updated": 0,
1034
+ "total_unchanged": 0,
961
1035
  }
962
1036
 
963
1037
  # Publish in order: sellers -> providers -> offerings -> listings
@@ -975,6 +1049,9 @@ class ServiceDataPublisher(UnitySvcAPI):
975
1049
  all_results["total_success"] += results["success"]
976
1050
  all_results["total_failed"] += results["failed"]
977
1051
  all_results["total_found"] += results["total"]
1052
+ all_results["total_created"] += results.get("created", 0)
1053
+ all_results["total_updated"] += results.get("updated", 0)
1054
+ all_results["total_unchanged"] += results.get("unchanged", 0)
978
1055
  except Exception as e:
979
1056
  # If a publish method fails catastrophically, record the error
980
1057
  all_results[data_type] = {
@@ -1045,43 +1122,72 @@ def publish_callback(
1045
1122
  try:
1046
1123
  all_results = asyncio.run(_publish_all_async())
1047
1124
 
1048
- # Display results for each data type
1125
+ # Create summary table
1126
+ console.print("\n[bold cyan]Publishing Summary[/bold cyan]")
1127
+
1128
+ table = Table(show_header=True, header_style="bold cyan", border_style="cyan")
1129
+ table.add_column("Type", style="cyan", no_wrap=True)
1130
+ table.add_column("Found", justify="right")
1131
+ table.add_column("Success", justify="right", style="green")
1132
+ table.add_column("Failed", justify="right", style="red")
1133
+ table.add_column("Created", justify="right", style="green")
1134
+ table.add_column("Updated", justify="right", style="blue")
1135
+ table.add_column("Unchanged", justify="right", style="dim")
1136
+
1049
1137
  data_type_display_names = {
1050
1138
  "sellers": "Sellers",
1051
1139
  "providers": "Providers",
1052
- "offerings": "Service Offerings",
1053
- "listings": "Service Listings",
1140
+ "offerings": "Offerings",
1141
+ "listings": "Listings",
1054
1142
  }
1055
1143
 
1144
+ # Add rows for each data type
1056
1145
  for data_type in ["sellers", "providers", "offerings", "listings"]:
1057
1146
  display_name = data_type_display_names[data_type]
1058
1147
  results = all_results[data_type]
1059
1148
 
1060
- console.print(f"\n[bold cyan]{'=' * 60}[/bold cyan]")
1061
- console.print(f"[bold cyan]{display_name}[/bold cyan]")
1062
- console.print(f"[bold cyan]{'=' * 60}[/bold cyan]\n")
1149
+ table.add_row(
1150
+ 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)),
1157
+ )
1158
+
1159
+ # Add separator and total row
1160
+ table.add_section()
1161
+ table.add_row(
1162
+ "[bold]Total[/bold]",
1163
+ f"[bold]{all_results['total_found']}[/bold]",
1164
+ 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]",
1169
+ )
1063
1170
 
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']}")
1171
+ console.print(table)
1172
+
1173
+ # Display errors if any
1174
+ has_errors = False
1175
+ for data_type in ["sellers", "providers", "offerings", "listings"]:
1176
+ display_name = data_type_display_names[data_type]
1177
+ results = all_results[data_type]
1067
1178
 
1068
- # Display errors if any
1069
1179
  if results.get("errors"):
1070
- console.print(f"\n[bold red]Errors in {display_name}:[/bold red]")
1180
+ if not has_errors:
1181
+ console.print("\n[bold red]Errors:[/bold red]")
1182
+ has_errors = True
1183
+
1184
+ console.print(f"\n [bold red]{display_name}:[/bold red]")
1071
1185
  for error in results["errors"]:
1072
1186
  # Check if this is a skipped item
1073
1187
  if isinstance(error, dict) and error.get("error", "").startswith("skipped"):
1074
1188
  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']}")
1189
+ console.print(f" [red]✗[/red] {error.get('file', 'unknown')}")
1190
+ console.print(f" {error.get('error', 'unknown error')}")
1085
1191
 
1086
1192
  if all_results["total_failed"] > 0:
1087
1193
  console.print(
@@ -1153,6 +1259,10 @@ def publish_providers(
1153
1259
  console.print(f" Total found: {result['total']}")
1154
1260
  console.print(f" [green]✓ Success:[/green] {result['success']}")
1155
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]")
1156
1266
 
1157
1267
  # Display errors if any
1158
1268
  if result["errors"]:
@@ -1220,6 +1330,10 @@ def publish_sellers(
1220
1330
  console.print(f" Total found: {result['total']}")
1221
1331
  console.print(f" [green]✓ Success: {result['success']}[/green]")
1222
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]")
1223
1337
 
1224
1338
  if result["errors"]:
1225
1339
  console.print("\n[bold red]Errors:[/bold red]")
@@ -1264,7 +1378,7 @@ def publish_offerings(
1264
1378
  console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
1265
1379
  else:
1266
1380
  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")
1381
+ console.print(f"[blue]Backend URL:[/blue] {os.getenv('UNITYSVC_BASE_URL', 'N/A')}\n")
1268
1382
 
1269
1383
  async def _publish_offerings_async():
1270
1384
  async with ServiceDataPublisher() as publisher:
@@ -1286,6 +1400,10 @@ def publish_offerings(
1286
1400
  console.print(f" Total found: {result['total']}")
1287
1401
  console.print(f" [green]✓ Success: {result['success']}[/green]")
1288
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]")
1289
1407
 
1290
1408
  if result["errors"]:
1291
1409
  console.print("\n[bold red]Errors:[/bold red]")
@@ -1353,6 +1471,10 @@ def publish_listings(
1353
1471
  console.print(f" Total found: {result['total']}")
1354
1472
  console.print(f" [green]✓ Success: {result['success']}[/green]")
1355
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]")
1356
1478
 
1357
1479
  if result["errors"]:
1358
1480
  console.print("\n[bold red]Errors:[/bold red]")
@@ -720,26 +720,38 @@ def run(
720
720
  if verbose and result["stdout"]:
721
721
  console.print(f" [dim]stdout:[/dim] {result['stdout'][:200]}")
722
722
 
723
- # Save successful test output to .out file
724
- if result.get("stdout") and result.get("listing_file") and result.get("actual_filename"):
723
+ # Save successful test output to .out and .err files
724
+ if result.get("listing_file") and result.get("actual_filename"):
725
725
  listing_file = Path(result["listing_file"])
726
726
  actual_filename = result["actual_filename"]
727
-
728
- # Create filename: {listing_stem}_{actual_filename}.out
729
- # e.g., "svclisting_test.py.out" for svclisting.json and test.py
730
727
  listing_stem = listing_file.stem
731
- output_filename = f"{listing_stem}_{actual_filename}.out"
728
+
729
+ # Create filename pattern: {service_name}_{listing_stem}_{actual_filename}.out/.err
730
+ # e.g., "llama-3-1-405b-instruct_svclisting_test.py.out"
731
+ base_filename = f"{service_name}_{listing_stem}_{actual_filename}"
732
+ out_filename = f"{base_filename}.out"
733
+ err_filename = f"{base_filename}.err"
732
734
 
733
735
  # Save to listing directory
734
- output_path = listing_file.parent / output_filename
736
+ out_path = listing_file.parent / out_filename
737
+ err_path = listing_file.parent / err_filename
735
738
 
736
739
  # Write stdout to .out file
737
740
  try:
738
- with open(output_path, "w", encoding="utf-8") as f:
739
- f.write(result["stdout"])
740
- console.print(f" [dim]→ Output saved to:[/dim] {output_path}")
741
+ with open(out_path, "w", encoding="utf-8") as f:
742
+ f.write(result["stdout"] or "")
743
+ console.print(f" [dim]→ Output saved to:[/dim] {out_path}")
741
744
  except Exception as e:
742
745
  console.print(f" [yellow]⚠ Failed to save output: {e}[/yellow]")
746
+
747
+ # Write stderr to .err file
748
+ try:
749
+ with open(err_path, "w", encoding="utf-8") as f:
750
+ f.write(result["stderr"] or "")
751
+ if result["stderr"]:
752
+ console.print(f" [dim]→ Error output saved to:[/dim] {err_path}")
753
+ except Exception as e:
754
+ console.print(f" [yellow]⚠ Failed to save error output: {e}[/yellow]")
743
755
  else:
744
756
  console.print(f" [red]✗ Failed[/red] - {result['error']}")
745
757
  if verbose:
@@ -748,16 +760,36 @@ def run(
748
760
  if result["stderr"]:
749
761
  console.print(f" [dim]stderr:[/dim] {result['stderr'][:200]}")
750
762
 
751
- # Write failed test content to current directory
752
- if result.get("rendered_content") and result.get("listing_file") and result.get("actual_filename"):
763
+ # Write failed test outputs and script to current directory
764
+ if result.get("listing_file") and result.get("actual_filename"):
753
765
  listing_file = Path(result["listing_file"])
754
766
  actual_filename = result["actual_filename"]
755
767
  listing_stem = listing_file.stem
756
768
 
757
769
  # Create filename: failed_{service_name}_{listing_stem}_{actual_filename}
770
+ # This will be the base name for .out, .err, and the script file
758
771
  failed_filename = f"failed_{service_name}_{listing_stem}_{actual_filename}"
759
772
 
760
- # Prepare content with environment variables as header comments
773
+ # Write stdout to .out file in current directory
774
+ out_filename = f"{failed_filename}.out"
775
+ try:
776
+ with open(out_filename, "w", encoding="utf-8") as f:
777
+ f.write(result["stdout"] or "")
778
+ console.print(f" [yellow]→ Output saved to:[/yellow] {out_filename}")
779
+ except Exception as e:
780
+ console.print(f" [yellow]⚠ Failed to save output: {e}[/yellow]")
781
+
782
+ # Write stderr to .err file in current directory
783
+ err_filename = f"{failed_filename}.err"
784
+ try:
785
+ with open(err_filename, "w", encoding="utf-8") as f:
786
+ f.write(result["stderr"] or "")
787
+ console.print(f" [yellow]→ Error output saved to:[/yellow] {err_filename}")
788
+ except Exception as e:
789
+ console.print(f" [yellow]⚠ Failed to save error output: {e}[/yellow]")
790
+
791
+ # Write failed test script content to current directory (for debugging)
792
+ # rendered_content is always set if we got here (set during template rendering)
761
793
  content_with_env = result["rendered_content"]
762
794
 
763
795
  # Add environment variables as comments at the top
@@ -774,14 +806,13 @@ def run(
774
806
 
775
807
  content_with_env = env_header + content_with_env
776
808
 
777
- # Write to current directory
778
809
  try:
779
810
  with open(failed_filename, "w", encoding="utf-8") as f:
780
811
  f.write(content_with_env)
781
- console.print(f" [yellow]→ Test content saved to:[/yellow] {failed_filename}")
812
+ console.print(f" [yellow]→ Test script saved to:[/yellow] {failed_filename}")
782
813
  console.print(" [dim] (includes environment variables for reproduction)[/dim]")
783
814
  except Exception as e:
784
- console.print(f" [yellow]⚠ Failed to save test content: {e}[/yellow]")
815
+ console.print(f" [yellow]⚠ Failed to save test script: {e}[/yellow]")
785
816
 
786
817
  console.print()
787
818
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitysvc-services
3
- Version: 0.1.8
3
+ Version: 0.1.10
4
4
  Summary: SDK for digital service providers on UnitySVC
5
5
  Author-email: Bo Peng <bo.peng@unitysvc.com>
6
6
  Maintainer-email: Bo Peng <bo.peng@unitysvc.com>
@@ -78,7 +78,7 @@ name = "Fireworks API"
78
78
  title = "Python Code Example"
79
79
  description = "Example code to use the model"
80
80
  mime_type = "python"
81
- category = "code_examples"
81
+ category = "code_example"
82
82
  file_path = "code-example.md"
83
83
  is_active = true
84
84
  is_public = true
@@ -12,7 +12,7 @@ api_endpoint = "${GATEWAY_BASE_URL}/p/example-provider1"
12
12
  title = "Python Code Example"
13
13
  description = "Example code to use the model"
14
14
  mime_type = "python"
15
- category = "code_examples"
15
+ category = "code_example"
16
16
  file_path = "code-example.md"
17
17
  is_active = true
18
18
  is_public = true
@@ -32,7 +32,7 @@
32
32
  "api_key": "your_api_key_here",
33
33
  "documents": [
34
34
  {
35
- "category": "code_examples",
35
+ "category": "code_example",
36
36
  "description": "Example code to use the model",
37
37
  "file_path": "code-example.md",
38
38
  "is_active": true,
@@ -2,7 +2,7 @@
2
2
  "listing_status": "upstream_ready",
3
3
  "schema": "listing_v1",
4
4
  "seller_name": "svcreseller",
5
- "service_name": "example-service-2",
5
+ "service_name": "service2",
6
6
  "time_created": "2024-02-29T05:52:38.739683Z",
7
7
  "user_access_interfaces": [
8
8
  {
@@ -11,7 +11,7 @@
11
11
  "api_key": null,
12
12
  "documents": [
13
13
  {
14
- "category": "code_examples",
14
+ "category": "code_example",
15
15
  "description": "Example code to use the model",
16
16
  "file_path": "code-example.md",
17
17
  "is_active": true,
@@ -1,11 +1,11 @@
1
1
  {
2
- "schema": "seller_v1",
3
- "time_created": "2024-02-29T05:52:38.739683Z",
4
- "name": "svcreseller",
5
- "display_name": "SVC Reseller",
6
- "seller_type": "organization",
7
2
  "contact_email": "contact@svcreseller.com",
8
3
  "description": "Example seller for testing",
4
+ "display_name": "SVC Reseller",
5
+ "is_verified": false,
6
+ "name": "svcreseller",
7
+ "schema": "seller_v1",
8
+ "seller_type": "organization",
9
9
  "status": "active",
10
- "is_verified": false
10
+ "time_created": "2024-02-29T05:52:38.739683Z"
11
11
  }
@@ -4,13 +4,10 @@ from pathlib import Path
4
4
 
5
5
  import pytest
6
6
 
7
- from unitysvc_services.utils import (
8
- convert_convenience_fields_to_documents,
9
- deep_merge_dicts,
10
- load_data_file,
11
- resolve_provider_name,
12
- resolve_service_name_for_listing,
13
- )
7
+ from unitysvc_services.utils import (convert_convenience_fields_to_documents,
8
+ deep_merge_dicts, load_data_file,
9
+ resolve_provider_name,
10
+ resolve_service_name_for_listing)
14
11
 
15
12
 
16
13
  @pytest.fixture
@@ -30,7 +27,7 @@ def test_resolve_service_name_explicit(example_data_dir: Path) -> None:
30
27
  for file_path, _format, data in listing_files:
31
28
  if "provider2" in str(file_path):
32
29
  service_name = resolve_service_name_for_listing(file_path, data)
33
- assert service_name == "example-service-2"
30
+ assert service_name == "service2"
34
31
  break
35
32
  else:
36
33
  pytest.fail("No provider2 listing found")