unitysvc-services 0.1.24__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.
Files changed (37) hide show
  1. unitysvc_services/__init__.py +4 -0
  2. unitysvc_services/api.py +421 -0
  3. unitysvc_services/cli.py +23 -0
  4. unitysvc_services/format_data.py +140 -0
  5. unitysvc_services/interactive_prompt.py +1132 -0
  6. unitysvc_services/list.py +216 -0
  7. unitysvc_services/models/__init__.py +71 -0
  8. unitysvc_services/models/base.py +1375 -0
  9. unitysvc_services/models/listing_data.py +118 -0
  10. unitysvc_services/models/listing_v1.py +56 -0
  11. unitysvc_services/models/provider_data.py +79 -0
  12. unitysvc_services/models/provider_v1.py +54 -0
  13. unitysvc_services/models/seller_data.py +120 -0
  14. unitysvc_services/models/seller_v1.py +42 -0
  15. unitysvc_services/models/service_data.py +114 -0
  16. unitysvc_services/models/service_v1.py +81 -0
  17. unitysvc_services/populate.py +207 -0
  18. unitysvc_services/publisher.py +1628 -0
  19. unitysvc_services/py.typed +0 -0
  20. unitysvc_services/query.py +688 -0
  21. unitysvc_services/scaffold.py +1103 -0
  22. unitysvc_services/schema/base.json +777 -0
  23. unitysvc_services/schema/listing_v1.json +1286 -0
  24. unitysvc_services/schema/provider_v1.json +952 -0
  25. unitysvc_services/schema/seller_v1.json +379 -0
  26. unitysvc_services/schema/service_v1.json +1306 -0
  27. unitysvc_services/test.py +965 -0
  28. unitysvc_services/unpublisher.py +505 -0
  29. unitysvc_services/update.py +287 -0
  30. unitysvc_services/utils.py +533 -0
  31. unitysvc_services/validator.py +731 -0
  32. unitysvc_services-0.1.24.dist-info/METADATA +184 -0
  33. unitysvc_services-0.1.24.dist-info/RECORD +37 -0
  34. unitysvc_services-0.1.24.dist-info/WHEEL +5 -0
  35. unitysvc_services-0.1.24.dist-info/entry_points.txt +3 -0
  36. unitysvc_services-0.1.24.dist-info/licenses/LICENSE +21 -0
  37. unitysvc_services-0.1.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,118 @@
1
+ """Base data models for service listings.
2
+
3
+ This module defines `ServiceListingData`, a base model containing the core fields
4
+ for service listing data that is shared between:
5
+ - unitysvc-services (CLI): Used for file-based listing definitions
6
+ - unitysvc (backend): Used for API payloads and database operations
7
+
8
+ The `ListingV1` model extends this with file-specific fields like `schema_version`
9
+ and `time_created` for data file validation.
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+ from .base import CurrencyEnum, ListingStatusEnum
17
+
18
+
19
+ class ServiceListingData(BaseModel):
20
+ """
21
+ Base data structure for service listing information.
22
+
23
+ This model contains the core fields needed to describe a service listing,
24
+ without file-specific validation fields. It serves as:
25
+
26
+ 1. The base class for `ListingV1` in unitysvc-services (with additional
27
+ schema_version and time_created fields for file validation)
28
+
29
+ 2. The data structure imported by unitysvc backend for:
30
+ - API payload validation
31
+ - Database comparison logic in find_and_compare_service_listing()
32
+ - Publish operations from CLI
33
+
34
+ Key characteristics:
35
+ - Uses string identifiers (service_name, provider_name, seller_name)
36
+ that get resolved to database IDs by the backend
37
+ - Contains all user-provided data without system-generated IDs
38
+ - Does not include permission/audit fields (handled by backend CRUD layer)
39
+ - Uses dict types for nested structures to maintain flexibility between
40
+ file definitions and database operations
41
+ """
42
+
43
+ # Reference to service offering - required for backend resolution
44
+ service_name: str | None = Field(
45
+ default=None,
46
+ description=(
47
+ "Name of the service (ServiceV1.name), optional if only one service is defined under the same directory."
48
+ ),
49
+ )
50
+ service_version: str | None = Field(
51
+ default=None,
52
+ description="Version of the service offering",
53
+ )
54
+ provider_name: str | None = Field(
55
+ default=None,
56
+ description="Provider name (resolved from directory structure if not specified)",
57
+ )
58
+
59
+ # Seller info
60
+ seller_name: str | None = Field(
61
+ default=None,
62
+ description="Name of the seller offering this service listing",
63
+ )
64
+
65
+ # Listing identification
66
+ name: str | None = Field(
67
+ default=None,
68
+ max_length=255,
69
+ description="Name identifier for the service listing (defaults to 'default' if not provided)",
70
+ )
71
+
72
+ # Display name for UI
73
+ display_name: str | None = Field(
74
+ default=None,
75
+ max_length=200,
76
+ description="Human-readable listing name (e.g., 'Premium GPT-4 Access', 'Enterprise AI Services')",
77
+ )
78
+
79
+ # Status - seller-accessible statuses
80
+ listing_status: ListingStatusEnum = Field(
81
+ default=ListingStatusEnum.draft,
82
+ description="Listing status: draft (skip publish), ready (ready for admin review), or deprecated (retired)",
83
+ )
84
+
85
+ # Customer pricing
86
+ customer_price: dict[str, Any] | None = Field(
87
+ default=None,
88
+ description="Customer pricing: What the customer pays for each unit of service usage",
89
+ )
90
+
91
+ # Currency for customer_price
92
+ currency: CurrencyEnum = Field(
93
+ default=CurrencyEnum.USD,
94
+ description="Currency for customer_price (indexed for filtering)",
95
+ )
96
+
97
+ # Access interfaces
98
+ user_access_interfaces: list[dict[str, Any]] | None = Field(
99
+ default=None,
100
+ description="List of user access interfaces for the listing",
101
+ )
102
+
103
+ # Documents
104
+ documents: list[dict[str, Any]] | None = Field(
105
+ default=None,
106
+ description="List of documents associated with the listing (e.g., service level agreements)",
107
+ )
108
+
109
+ # User parameters
110
+ user_parameters_schema: dict[str, Any] | None = Field(
111
+ default=None,
112
+ description="JSON Schema for user parameters",
113
+ )
114
+
115
+ user_parameters_ui_schema: dict[str, Any] | None = Field(
116
+ default=None,
117
+ description="UI schema for user parameters form rendering",
118
+ )
@@ -0,0 +1,56 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import ConfigDict, Field, field_validator
4
+
5
+ from .base import (
6
+ AccessInterface,
7
+ Document,
8
+ Pricing,
9
+ validate_name,
10
+ )
11
+ from .listing_data import ServiceListingData
12
+
13
+
14
+ class ListingV1(ServiceListingData):
15
+ """
16
+ Service listing model for file-based definitions (listing_v1 schema).
17
+
18
+ Extends ServiceListingData with:
19
+ - schema_version: Schema identifier for file validation
20
+ - time_created: Timestamp for file creation
21
+ - Typed models (AccessInterface, Document, Pricing) instead of dicts
22
+ - Field validators for name format
23
+
24
+ This model is used for validating listing.json/listing.toml files
25
+ created by the CLI tool.
26
+ """
27
+
28
+ model_config = ConfigDict(extra="forbid")
29
+
30
+ # File-specific fields for validation
31
+ schema_version: str = Field(default="listing_v1", description="Schema identifier", alias="schema")
32
+ time_created: datetime
33
+
34
+ # Override with typed models instead of dicts for file validation
35
+ # (listing_status, user_parameters_schema, user_parameters_ui_schema are inherited from ServiceListingData)
36
+ user_access_interfaces: list[AccessInterface] = Field( # type: ignore[assignment]
37
+ description="List of user access interfaces for the listing"
38
+ )
39
+
40
+ customer_price: Pricing | None = Field( # type: ignore[assignment]
41
+ default=None,
42
+ description="Customer pricing information",
43
+ )
44
+
45
+ documents: list[Document] | None = Field( # type: ignore[assignment]
46
+ default=None,
47
+ description="List of documents associated with the listing (e.g. service level agreements)",
48
+ )
49
+
50
+ @field_validator("name")
51
+ @classmethod
52
+ def validate_name_format(cls, v: str | None) -> str | None:
53
+ """Validate that listing name uses valid identifiers (allows slashes for hierarchical names)."""
54
+ if v is None:
55
+ return v
56
+ return validate_name(v, "listing", allow_slash=True)
@@ -0,0 +1,79 @@
1
+ """Base data model for providers.
2
+
3
+ This module defines `ProviderData`, a base model containing the core fields
4
+ for provider data that is shared between:
5
+ - unitysvc-services (CLI): Used for file-based provider definitions
6
+ - unitysvc (backend): Used for API payloads and database operations
7
+
8
+ The `ProviderV1` model extends this with file-specific fields like `schema_version`
9
+ and `time_created` for data file validation.
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ from pydantic import BaseModel, EmailStr, Field, HttpUrl
15
+
16
+ from .base import ProviderStatusEnum
17
+
18
+
19
+ class ProviderData(BaseModel):
20
+ """
21
+ Base data structure for provider information.
22
+
23
+ This model contains the core fields needed to describe a provider,
24
+ without file-specific validation fields. It serves as:
25
+
26
+ 1. The base class for `ProviderV1` in unitysvc-services (with additional
27
+ schema_version, time_created, and services_populator fields for file validation)
28
+
29
+ 2. The data structure imported by unitysvc backend for:
30
+ - API payload validation
31
+ - Database comparison logic in find_and_compare_provider()
32
+ - Publish operations from CLI
33
+
34
+ Key characteristics:
35
+ - Uses string identifiers that match database requirements
36
+ - Contains all user-provided data without system-generated IDs
37
+ - Does not include permission/audit fields (handled by backend CRUD layer)
38
+ """
39
+
40
+ # Provider identification
41
+ name: str = Field(
42
+ description="Unique provider identifier (URL-friendly, e.g., 'fireworks', 'anthropic')",
43
+ min_length=2,
44
+ max_length=100,
45
+ )
46
+
47
+ display_name: str | None = Field(
48
+ default=None,
49
+ max_length=200,
50
+ description="Human-readable provider name (e.g., 'Fireworks AI', 'Anthropic')",
51
+ )
52
+
53
+ # Contact information
54
+ contact_email: EmailStr = Field(description="Primary contact email for the provider")
55
+
56
+ secondary_contact_email: EmailStr | None = Field(
57
+ default=None,
58
+ description="Secondary contact email",
59
+ )
60
+
61
+ homepage: HttpUrl = Field(description="Provider's homepage URL")
62
+
63
+ # Provider information
64
+ description: str | None = Field(
65
+ default=None,
66
+ description="Brief description of the provider",
67
+ )
68
+
69
+ # Status
70
+ status: ProviderStatusEnum = Field(
71
+ default=ProviderStatusEnum.active,
72
+ description="Provider status: active, disabled, or draft (skip publish)",
73
+ )
74
+
75
+ # Documents (as dicts for flexibility)
76
+ documents: list[dict[str, Any]] | None = Field(
77
+ default=None,
78
+ description="List of documents associated with the provider",
79
+ )
@@ -0,0 +1,54 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+ from pydantic import ConfigDict, Field, HttpUrl, field_validator
5
+
6
+ from .base import AccessInterface, Document, validate_name
7
+ from .provider_data import ProviderData
8
+
9
+
10
+ class ProviderV1(ProviderData):
11
+ """
12
+ Provider information for service providers (provider_v1 schema).
13
+
14
+ Extends ProviderData with:
15
+ - schema_version: Schema identifier for file validation
16
+ - time_created: Timestamp for file creation
17
+ - services_populator: How to automatically populate service data
18
+ - provider_access_info: Parameters for accessing service provider
19
+ - logo, terms_of_service: Convenience fields (converted to documents during import)
20
+ - Typed Document model instead of dict for file validation
21
+ - Field validators for name format
22
+ """
23
+
24
+ model_config = ConfigDict(extra="forbid")
25
+
26
+ # File-specific fields for validation
27
+ schema_version: str = Field(default="provider_v1", description="Schema identifier", alias="schema")
28
+ time_created: datetime
29
+
30
+ # How to automatically populate service data, if available
31
+ services_populator: dict[str, Any] | None = None
32
+
33
+ # Parameters for accessing service provider (base_url, api_key)
34
+ provider_access_info: AccessInterface = Field(description="Dictionary of upstream access interface")
35
+
36
+ # Convenience fields for logo and terms of service (converted to documents during import)
37
+ logo: str | HttpUrl | None = None
38
+
39
+ terms_of_service: None | str | HttpUrl = Field(
40
+ default=None,
41
+ description="Either a path to a .md file or a URL to terms of service",
42
+ )
43
+
44
+ # Override with typed Document model for file validation
45
+ documents: list[Document] | None = Field( # type: ignore[assignment]
46
+ default=None,
47
+ description="List of documents associated with the provider (e.g. logo)",
48
+ )
49
+
50
+ @field_validator("name")
51
+ @classmethod
52
+ def validate_name_format(cls, v: str) -> str:
53
+ """Validate that provider name uses URL-safe identifiers."""
54
+ return validate_name(v, "provider", allow_slash=False)
@@ -0,0 +1,120 @@
1
+ """Base data model for sellers.
2
+
3
+ This module defines `SellerData`, a base model containing the core fields
4
+ for seller data that is shared between:
5
+ - unitysvc-services (CLI): Used for file-based seller definitions
6
+ - unitysvc (backend): Used for API payloads and database operations
7
+
8
+ The `SellerV1` model extends this with file-specific fields like `schema_version`
9
+ and `time_created` for data file validation.
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ from pydantic import BaseModel, EmailStr, Field, HttpUrl
15
+
16
+ from .base import SellerStatusEnum, SellerTypeEnum
17
+
18
+
19
+ class SellerData(BaseModel):
20
+ """
21
+ Base data structure for seller information.
22
+
23
+ This model contains the core fields needed to describe a seller,
24
+ without file-specific validation fields. It serves as:
25
+
26
+ 1. The base class for `SellerV1` in unitysvc-services (with additional
27
+ schema_version and time_created fields for file validation)
28
+
29
+ 2. The data structure imported by unitysvc backend for:
30
+ - API payload validation
31
+ - Database comparison logic in find_and_compare_seller()
32
+ - Publish operations from CLI
33
+
34
+ Key characteristics:
35
+ - Uses string identifiers that match database requirements
36
+ - account_manager is a string (username/email) that gets resolved to account_manager_id
37
+ - Contains all user-provided data without system-generated IDs
38
+ - Does not include permission/audit fields (handled by backend CRUD layer)
39
+ """
40
+
41
+ # Seller identification
42
+ name: str = Field(
43
+ description="Unique seller identifier (URL-friendly, e.g., 'acme-corp', 'john-doe')",
44
+ min_length=2,
45
+ max_length=100,
46
+ )
47
+
48
+ display_name: str | None = Field(
49
+ default=None,
50
+ max_length=200,
51
+ description="Human-readable seller name (e.g., 'ACME Corporation', 'John Doe')",
52
+ )
53
+
54
+ # Seller type
55
+ seller_type: SellerTypeEnum = Field(
56
+ default=SellerTypeEnum.individual,
57
+ description="Type of seller entity",
58
+ )
59
+
60
+ # Contact information
61
+ contact_email: EmailStr = Field(description="Primary contact email for the seller")
62
+
63
+ secondary_contact_email: EmailStr | None = Field(
64
+ default=None,
65
+ description="Secondary contact email",
66
+ )
67
+
68
+ account_manager: str | None = Field(
69
+ default=None,
70
+ max_length=100,
71
+ description="Email or username of the user managing this seller account",
72
+ )
73
+
74
+ homepage: HttpUrl | None = Field(
75
+ default=None,
76
+ description="Seller's homepage URL",
77
+ )
78
+
79
+ # Business information
80
+ description: str | None = Field(
81
+ default=None,
82
+ max_length=1000,
83
+ description="Brief description of the seller",
84
+ )
85
+
86
+ business_registration: str | None = Field(
87
+ default=None,
88
+ max_length=100,
89
+ description="Business registration number (if organization)",
90
+ )
91
+
92
+ tax_id: str | None = Field(
93
+ default=None,
94
+ max_length=100,
95
+ description="Tax identification number (EIN, VAT, etc.)",
96
+ )
97
+
98
+ # Stripe Connect integration
99
+ stripe_connect_id: str | None = Field(
100
+ default=None,
101
+ max_length=255,
102
+ description="Stripe Connect account ID for payment processing",
103
+ )
104
+
105
+ # Status
106
+ status: SellerStatusEnum = Field(
107
+ default=SellerStatusEnum.active,
108
+ description="Seller status: active, disabled, or draft (skip publish)",
109
+ )
110
+
111
+ is_verified: bool = Field(
112
+ default=False,
113
+ description="Whether the seller has been verified (KYC/business verification)",
114
+ )
115
+
116
+ # Documents (as dicts for flexibility)
117
+ documents: list[dict[str, Any]] | None = Field(
118
+ default=None,
119
+ description="List of documents associated with the seller",
120
+ )
@@ -0,0 +1,42 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import ConfigDict, Field, HttpUrl, field_validator
4
+
5
+ from .base import Document, validate_name
6
+ from .seller_data import SellerData
7
+
8
+
9
+ class SellerV1(SellerData):
10
+ """
11
+ Seller information for marketplace sellers (seller_v1 schema).
12
+
13
+ Extends SellerData with:
14
+ - schema_version: Schema identifier for file validation
15
+ - time_created: Timestamp for file creation
16
+ - logo: Convenience field (converted to documents during import)
17
+ - Typed Document model instead of dict for file validation
18
+ - Field validators for name format
19
+
20
+ Each repository can only have one seller.json file at the root of the data directory.
21
+ """
22
+
23
+ model_config = ConfigDict(extra="forbid")
24
+
25
+ # File-specific fields for validation
26
+ schema_version: str = Field(default="seller_v1", description="Schema identifier", alias="schema")
27
+ time_created: datetime
28
+
29
+ # Convenience field for logo (converted to documents during import)
30
+ logo: str | HttpUrl | None = None
31
+
32
+ # Override with typed Document model for file validation
33
+ documents: list[Document] | None = Field( # type: ignore[assignment]
34
+ default=None,
35
+ description="List of documents associated with the seller (e.g. business registration, tax documents)",
36
+ )
37
+
38
+ @field_validator("name")
39
+ @classmethod
40
+ def validate_name_format(cls, v: str) -> str:
41
+ """Validate that seller name uses URL-safe identifiers."""
42
+ return validate_name(v, "seller", allow_slash=False)
@@ -0,0 +1,114 @@
1
+ """Base data model for service offerings.
2
+
3
+ This module defines `ServiceOfferingData`, a base model containing the core fields
4
+ for service offering data that is shared between:
5
+ - unitysvc-services (CLI): Used for file-based service definitions
6
+ - unitysvc (backend): Used for API payloads and database operations
7
+
8
+ The `ServiceV1` model extends this with file-specific fields like `schema_version`
9
+ and `time_created` for data file validation.
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+ from .base import CurrencyEnum, ServiceTypeEnum, UpstreamStatusEnum
17
+
18
+
19
+ class ServiceOfferingData(BaseModel):
20
+ """
21
+ Base data structure for service offering information.
22
+
23
+ This model contains the core fields needed to describe a service offering,
24
+ without file-specific validation fields. It serves as:
25
+
26
+ 1. The base class for `ServiceV1` in unitysvc-services (with additional
27
+ schema_version and time_created fields for file validation)
28
+
29
+ 2. The data structure imported by unitysvc backend for:
30
+ - API payload validation
31
+ - Database comparison logic in find_and_compare_service_offering()
32
+ - Publish operations from CLI
33
+
34
+ Key characteristics:
35
+ - Uses string identifiers (provider_name) that get resolved to database IDs
36
+ - upstream_status maps to database 'status' field
37
+ - Contains all user-provided data without system-generated IDs
38
+ - Does not include permission/audit fields (handled by backend CRUD layer)
39
+ """
40
+
41
+ # Service identification
42
+ name: str = Field(
43
+ description="Technical service name (e.g., 'gpt-4')",
44
+ max_length=100,
45
+ )
46
+
47
+ display_name: str | None = Field(
48
+ default=None,
49
+ max_length=150,
50
+ description="Human-friendly common name (e.g., 'GPT-4 Turbo')",
51
+ )
52
+
53
+ version: str | None = Field(
54
+ default=None,
55
+ max_length=50,
56
+ description="Service version if applicable",
57
+ )
58
+
59
+ service_type: ServiceTypeEnum = Field(
60
+ default=ServiceTypeEnum.llm,
61
+ description="Category for grouping/comparison",
62
+ )
63
+
64
+ description: str | None = Field(
65
+ default=None,
66
+ description="Service description",
67
+ )
68
+
69
+ tagline: str | None = Field(
70
+ default=None,
71
+ description="Short elevator pitch or description for the service",
72
+ )
73
+
74
+ # Provider info (resolved by publish layer)
75
+ provider_name: str | None = Field(
76
+ default=None,
77
+ description="Provider name (resolved from directory structure if not specified)",
78
+ )
79
+
80
+ # Status
81
+ upstream_status: UpstreamStatusEnum = Field(
82
+ default=UpstreamStatusEnum.ready,
83
+ description="Status of the service from upstream service provider",
84
+ )
85
+
86
+ # Technical details
87
+ details: dict[str, Any] | None = Field(
88
+ default=None,
89
+ description="Static technical specifications and features",
90
+ )
91
+
92
+ # Pricing
93
+ seller_price: dict[str, Any] | None = Field(
94
+ default=None,
95
+ description="Seller pricing: The agreed rate between seller and UnitySVC",
96
+ )
97
+
98
+ # Access interface
99
+ upstream_access_interface: dict[str, Any] | None = Field(
100
+ default=None,
101
+ description="How to access the service from upstream",
102
+ )
103
+
104
+ # Documents
105
+ documents: list[dict[str, Any]] | None = Field(
106
+ default=None,
107
+ description="List of documents associated with the service",
108
+ )
109
+
110
+ # Currency for seller_price
111
+ currency: CurrencyEnum = Field(
112
+ default=CurrencyEnum.USD,
113
+ description="Currency for seller_price",
114
+ )
@@ -0,0 +1,81 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+ from pydantic import ConfigDict, Field, HttpUrl, field_validator
5
+
6
+ from .base import (
7
+ AccessInterface,
8
+ Document,
9
+ Pricing,
10
+ TagEnum,
11
+ validate_name,
12
+ )
13
+ from .service_data import ServiceOfferingData
14
+
15
+
16
+ class ServiceV1(ServiceOfferingData):
17
+ """
18
+ Service offering model for file-based definitions (service_v1 schema).
19
+
20
+ Extends ServiceOfferingData with:
21
+ - schema_version: Schema identifier for file validation
22
+ - time_created: Timestamp for file creation
23
+ - logo: Convenience field (converted to documents during import)
24
+ - tags: Tags for the service (e.g., bring your own API key)
25
+ - Typed models (AccessInterface, Document, Pricing) instead of dicts
26
+ - Field validators for name format
27
+
28
+ This model is used for validating service.json/service.toml files
29
+ created by the CLI tool.
30
+ """
31
+
32
+ model_config = ConfigDict(extra="forbid")
33
+
34
+ # File-specific fields for validation
35
+ schema_version: str = Field(default="service_v1", description="Schema identifier", alias="schema")
36
+ time_created: datetime
37
+
38
+ # Override to make required in file validation (base has Optional for API flexibility)
39
+ display_name: str = Field( # type: ignore[assignment]
40
+ max_length=150,
41
+ description="Human-friendly common name (e.g., 'GPT-4 Turbo')",
42
+ )
43
+
44
+ description: str = Field( # type: ignore[assignment]
45
+ description="Service description",
46
+ )
47
+
48
+ # Required in file for static information
49
+ details: dict[str, Any] = Field( # type: ignore[assignment]
50
+ description="Dictionary of static features and information",
51
+ )
52
+
53
+ # Convenience field for logo (converted to documents during import)
54
+ logo: str | HttpUrl | None = None
55
+
56
+ # Tags for the service
57
+ tags: list[TagEnum] | None = Field(
58
+ default=None,
59
+ description="List of tags for the service, e.g., bring your own API key",
60
+ )
61
+
62
+ # Override with typed models for file validation
63
+ upstream_access_interface: AccessInterface = Field( # type: ignore[assignment]
64
+ description="Dictionary of upstream access interface",
65
+ )
66
+
67
+ documents: list[Document] | None = Field( # type: ignore[assignment]
68
+ default=None,
69
+ description="List of documents associated with the service (e.g. tech spec.)",
70
+ )
71
+
72
+ seller_price: Pricing | None = Field( # type: ignore[assignment]
73
+ default=None,
74
+ description="Seller pricing information",
75
+ )
76
+
77
+ @field_validator("name")
78
+ @classmethod
79
+ def validate_name_format(cls, v: str) -> str:
80
+ """Validate that service name uses valid identifiers (allows slashes for hierarchical names)."""
81
+ return validate_name(v, "service", allow_slash=True)