unitysvc-services 0.2.6__tar.gz → 0.2.7__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 (60) hide show
  1. {unitysvc_services-0.2.6/src/unitysvc_services.egg-info → unitysvc_services-0.2.7}/PKG-INFO +1 -1
  2. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/pyproject.toml +1 -1
  3. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/models/base.py +123 -0
  4. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/models/listing_v1.py +24 -2
  5. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/models/provider_v1.py +17 -2
  6. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/models/seller_v1.py +8 -2
  7. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/models/service_v1.py +8 -1
  8. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/publisher.py +413 -137
  9. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/query.py +3 -3
  10. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/validator.py +79 -23
  11. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7/src/unitysvc_services.egg-info}/PKG-INFO +1 -1
  12. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/CONTRIBUTING.md +0 -0
  13. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/HISTORY.md +0 -0
  14. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/LICENSE +0 -0
  15. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/MANIFEST.in +0 -0
  16. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/README.md +0 -0
  17. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/api-reference.md +0 -0
  18. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/cli-reference.md +0 -0
  19. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/contributing.md +0 -0
  20. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/data-structure.md +0 -0
  21. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/development.md +0 -0
  22. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/file-schemas.md +0 -0
  23. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/getting-started.md +0 -0
  24. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/index.md +0 -0
  25. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/installation.md +0 -0
  26. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/usage.md +0 -0
  27. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/docs/workflows.md +0 -0
  28. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/setup.cfg +0 -0
  29. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/__init__.py +0 -0
  30. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/cli.py +0 -0
  31. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/format_data.py +0 -0
  32. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/list.py +0 -0
  33. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/models/__init__.py +0 -0
  34. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/populate.py +0 -0
  35. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/py.typed +0 -0
  36. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/scaffold.py +0 -0
  37. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/update.py +0 -0
  38. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services/utils.py +0 -0
  39. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services.egg-info/SOURCES.txt +0 -0
  40. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services.egg-info/dependency_links.txt +0 -0
  41. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services.egg-info/entry_points.txt +0 -0
  42. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services.egg-info/requires.txt +0 -0
  43. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/src/unitysvc_services.egg-info/top_level.txt +0 -0
  44. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/__init__.py +0 -0
  45. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/README.md +0 -0
  46. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider1/README.md +0 -0
  47. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider1/provider.toml +0 -0
  48. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider1/services/service1/code-example.md +0 -0
  49. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider1/services/service1/service.toml +0 -0
  50. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider1/services/service1/svcreseller.toml +0 -0
  51. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider1/terms-of-service.md +0 -0
  52. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider2/README.md +0 -0
  53. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider2/provider.json +0 -0
  54. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider2/services/service2/code-example.md +0 -0
  55. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider2/services/service2/service.json +0 -0
  56. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider2/services/service2/svcreseller.json +0 -0
  57. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/provider2/terms-of-service.md +0 -0
  58. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/example_data/seller.json +0 -0
  59. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/test_utils.py +0 -0
  60. {unitysvc_services-0.2.6 → unitysvc_services-0.2.7}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitysvc-services
3
- Version: 0.2.6
3
+ Version: 0.2.7
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unitysvc-services"
3
- version = "0.2.6"
3
+ version = "0.2.7"
4
4
  description = "SDK for digital service providers on UnitySVC"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Bo Peng", email = "bo.peng@unitysvc.com" }]
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from enum import StrEnum
2
3
  from typing import Any
3
4
 
@@ -210,6 +211,7 @@ class ProviderStatusEnum(StrEnum):
210
211
  """Provider status enum."""
211
212
 
212
213
  active = "active"
214
+ pending = "pending"
213
215
  disabled = "disabled"
214
216
  incomplete = "incomplete" # Provider information is incomplete
215
217
 
@@ -218,6 +220,7 @@ class SellerStatusEnum(StrEnum):
218
220
  """Seller status enum."""
219
221
 
220
222
  active = "active"
223
+ pending = "pending"
221
224
  disabled = "disabled"
222
225
  incomplete = "incomplete" # Seller information is incomplete
223
226
 
@@ -366,3 +369,123 @@ class Pricing(BaseModel):
366
369
 
367
370
  # Optional reference to upstream pricing
368
371
  reference: str | None = Field(default=None, description="Reference URL to upstream pricing")
372
+
373
+
374
+ def validate_name(name: str, entity_type: str, display_name: str | None = None, *, allow_slash: bool = False) -> str:
375
+ """
376
+ Validate that a name field uses valid identifiers.
377
+
378
+ Name format rules:
379
+ - Only letters (upper/lowercase), numbers, dots, dashes, and underscores allowed
380
+ - If allow_slash=True, slashes are also allowed for hierarchical names
381
+ - Must start and end with alphanumeric characters (not special characters)
382
+ - Cannot have consecutive slashes (when allow_slash=True)
383
+ - Cannot be empty
384
+
385
+ Args:
386
+ name: The name value to validate
387
+ entity_type: Type of entity (provider, seller, service, listing) for error messages
388
+ display_name: Optional display name to suggest a valid name from
389
+ allow_slash: Whether to allow slashes for hierarchical names (default: False)
390
+
391
+ Returns:
392
+ The validated name (unchanged if valid)
393
+
394
+ Raises:
395
+ ValueError: If the name doesn't match the required pattern
396
+
397
+ Examples:
398
+ Without slashes (providers, sellers):
399
+ - name='amazon-bedrock' or name='Amazon-Bedrock'
400
+ - name='fireworks.ai' or name='Fireworks.ai'
401
+ - name='llama-3.1' or name='Llama-3.1'
402
+
403
+ With slashes (services, listings):
404
+ - name='gpt-4' or name='GPT-4'
405
+ - name='models/gpt-4' or name='models/GPT-4'
406
+ - name='black-forest-labs/FLUX.1-dev'
407
+ - name='api/v1/completion'
408
+ """
409
+ # Build pattern based on allow_slash parameter
410
+ if allow_slash:
411
+ # Pattern: starts with alphanumeric, can contain alphanumeric/dot/dash/underscore/slash, ends with alphanumeric
412
+ name_pattern = r"^[a-zA-Z0-9]([a-zA-Z0-9._/-]*[a-zA-Z0-9])?$"
413
+ allowed_chars = "letters, numbers, dots, dashes, underscores, and slashes"
414
+ else:
415
+ # Pattern: starts with alphanumeric, can contain alphanumeric/dot/dash/underscore, ends with alphanumeric
416
+ name_pattern = r"^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$"
417
+ allowed_chars = "letters, numbers, dots, dashes, and underscores"
418
+
419
+ # Check for consecutive slashes if slashes are allowed
420
+ if allow_slash and "//" in name:
421
+ raise ValueError(f"Invalid {entity_type} name '{name}'. Name cannot contain consecutive slashes.")
422
+
423
+ if not re.match(name_pattern, name):
424
+ # Build helpful error message
425
+ error_msg = (
426
+ f"Invalid {entity_type} name '{name}'. "
427
+ f"Name must contain only {allowed_chars}. "
428
+ f"It must start and end with an alphanumeric character.\n"
429
+ )
430
+
431
+ # Suggest a valid name based on display_name if available
432
+ if display_name:
433
+ suggested_name = suggest_valid_name(display_name, allow_slash=allow_slash)
434
+ if suggested_name and suggested_name != name:
435
+ error_msg += f" Suggestion: Set name='{suggested_name}' and display_name='{display_name}'\n"
436
+
437
+ # Add appropriate examples based on allow_slash
438
+ if allow_slash:
439
+ error_msg += (
440
+ " Examples:\n"
441
+ " - name='gpt-4' or name='GPT-4'\n"
442
+ " - name='models/gpt-4' or name='models/GPT-4'\n"
443
+ " - name='black-forest-labs/FLUX.1-dev'\n"
444
+ " - name='api/v1/completion'"
445
+ )
446
+ else:
447
+ error_msg += (
448
+ " Note: Use 'display_name' field for brand names with spaces and special characters.\n"
449
+ " Examples:\n"
450
+ " - name='amazon-bedrock' or name='Amazon-Bedrock'\n"
451
+ " - name='fireworks.ai' or name='Fireworks.ai'\n"
452
+ " - name='llama-3.1' or name='Llama-3.1'"
453
+ )
454
+
455
+ raise ValueError(error_msg)
456
+
457
+ return name
458
+
459
+
460
+ def suggest_valid_name(display_name: str, *, allow_slash: bool = False) -> str:
461
+ """
462
+ Suggest a valid name based on a display name.
463
+
464
+ Replaces invalid characters with hyphens and ensures it follows the naming rules.
465
+ Preserves the original case.
466
+
467
+ Args:
468
+ display_name: The display name to convert
469
+ allow_slash: Whether to allow slashes for hierarchical names (default: False)
470
+
471
+ Returns:
472
+ A suggested valid name
473
+ """
474
+ if allow_slash:
475
+ # Replace characters that aren't alphanumeric, dot, dash, underscore, or slash with hyphens
476
+ suggested = re.sub(r"[^a-zA-Z0-9._/-]+", "-", display_name)
477
+ # Remove leading/trailing special characters
478
+ suggested = suggested.strip("._/-")
479
+ # Collapse multiple consecutive dashes
480
+ suggested = re.sub(r"-+", "-", suggested)
481
+ # Remove consecutive slashes
482
+ suggested = re.sub(r"/+", "/", suggested)
483
+ else:
484
+ # Replace characters that aren't alphanumeric, dot, dash, or underscore with hyphens
485
+ suggested = re.sub(r"[^a-zA-Z0-9._-]+", "-", display_name)
486
+ # Remove leading/trailing dots, dashes, or underscores
487
+ suggested = suggested.strip("._-")
488
+ # Collapse multiple consecutive dashes
489
+ suggested = re.sub(r"-+", "-", suggested)
490
+
491
+ return suggested
@@ -1,9 +1,15 @@
1
1
  from datetime import datetime
2
2
  from typing import Any
3
3
 
4
- from pydantic import BaseModel, ConfigDict, Field
4
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
5
5
 
6
- from unitysvc_services.models.base import AccessInterface, Document, ListingStatusEnum, Pricing
6
+ from unitysvc_services.models.base import (
7
+ AccessInterface,
8
+ Document,
9
+ ListingStatusEnum,
10
+ Pricing,
11
+ validate_name,
12
+ )
7
13
 
8
14
 
9
15
  class ListingV1(BaseModel):
@@ -32,6 +38,14 @@ class ListingV1(BaseModel):
32
38
  max_length=255,
33
39
  description="Name identifier for the service listing, default to filename",
34
40
  )
41
+
42
+ # Display name for UI (human-readable listing name)
43
+ display_name: str | None = Field(
44
+ default=None,
45
+ max_length=200,
46
+ description="Human-readable listing name (e.g., 'Premium GPT-4 Access', 'Enterprise AI Services')",
47
+ )
48
+
35
49
  # unique name for each provider, usually following upstream naming convention
36
50
  # status of the service, public, deprecated etc
37
51
  listing_status: ListingStatusEnum = Field(
@@ -68,3 +82,11 @@ class ListingV1(BaseModel):
68
82
  user_parameters_ui_schema: dict[str, Any] | None = Field(
69
83
  default=None, description="Dictionary of user parameters UI schema"
70
84
  )
85
+
86
+ @field_validator("name")
87
+ @classmethod
88
+ def validate_name_format(cls, v: str | None) -> str | None:
89
+ """Validate that listing name uses valid identifiers (allows slashes for hierarchical names)."""
90
+ if v is None:
91
+ return v
92
+ return validate_name(v, "listing", allow_slash=True)
@@ -1,9 +1,9 @@
1
1
  from datetime import datetime
2
2
  from typing import Any
3
3
 
4
- from pydantic import BaseModel, ConfigDict, EmailStr, Field, HttpUrl
4
+ from pydantic import BaseModel, ConfigDict, EmailStr, Field, HttpUrl, field_validator
5
5
 
6
- from unitysvc_services.models.base import AccessInterface, Document, ProviderStatusEnum
6
+ from unitysvc_services.models.base import AccessInterface, Document, ProviderStatusEnum, validate_name
7
7
 
8
8
 
9
9
  class ProviderV1(BaseModel):
@@ -26,6 +26,13 @@ class ProviderV1(BaseModel):
26
26
  # name of the provider should be the same as directory name
27
27
  name: str
28
28
 
29
+ # Display name for UI (human-readable brand name)
30
+ display_name: str | None = Field(
31
+ default=None,
32
+ max_length=200,
33
+ description="Human-readable provider name (e.g., 'Amazon Bedrock', 'Fireworks.ai')",
34
+ )
35
+
29
36
  # this field is added for convenience. It will be converted to
30
37
  # documents during importing.
31
38
  logo: str | HttpUrl | None = None
@@ -57,3 +64,11 @@ class ProviderV1(BaseModel):
57
64
  default=ProviderStatusEnum.active,
58
65
  description="Provider status: active, disabled, or incomplete",
59
66
  )
67
+
68
+ @field_validator("name")
69
+ @classmethod
70
+ def validate_name_format(cls, v: str) -> str:
71
+ """Validate that provider name uses URL-safe identifiers."""
72
+ # Note: display_name is not available in the validator context for suggesting
73
+ # Display name will be shown in error if user provides it
74
+ return validate_name(v, "provider", allow_slash=False)
@@ -1,8 +1,8 @@
1
1
  from datetime import datetime
2
2
 
3
- from pydantic import BaseModel, ConfigDict, EmailStr, Field, HttpUrl
3
+ from pydantic import BaseModel, ConfigDict, EmailStr, Field, HttpUrl, field_validator
4
4
 
5
- from unitysvc_services.models.base import Document, SellerStatusEnum, SellerTypeEnum
5
+ from unitysvc_services.models.base import Document, SellerStatusEnum, SellerTypeEnum, validate_name
6
6
 
7
7
 
8
8
  class SellerV1(BaseModel):
@@ -108,3 +108,9 @@ class SellerV1(BaseModel):
108
108
  default=False,
109
109
  description="Whether the seller has been verified (KYC/business verification)",
110
110
  )
111
+
112
+ @field_validator("name")
113
+ @classmethod
114
+ def validate_name_format(cls, v: str) -> str:
115
+ """Validate that seller name uses URL-safe identifiers."""
116
+ return validate_name(v, "seller", allow_slash=False)
@@ -1,7 +1,7 @@
1
1
  from datetime import datetime
2
2
  from typing import Any
3
3
 
4
- from pydantic import BaseModel, ConfigDict, Field, HttpUrl
4
+ from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator
5
5
 
6
6
  from unitysvc_services.models.base import (
7
7
  AccessInterface,
@@ -10,6 +10,7 @@ from unitysvc_services.models.base import (
10
10
  ServiceTypeEnum,
11
11
  TagEnum,
12
12
  UpstreamStatusEnum,
13
+ validate_name,
13
14
  )
14
15
 
15
16
 
@@ -78,3 +79,9 @@ class ServiceV1(BaseModel):
78
79
  # a list of pricing models
79
80
  #
80
81
  upstream_price: Pricing | None = Field(description="List of pricing information")
82
+
83
+ @field_validator("name")
84
+ @classmethod
85
+ def validate_name_format(cls, v: str) -> str:
86
+ """Validate that service name uses valid identifiers (allows slashes for hierarchical names)."""
87
+ return validate_name(v, "service", allow_slash=True)