unitysvc-services 0.1.1__py3-none-any.whl → 0.1.4__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.
- unitysvc_services/api.py +278 -0
- unitysvc_services/format_data.py +2 -7
- unitysvc_services/list.py +14 -43
- unitysvc_services/models/base.py +157 -102
- unitysvc_services/models/listing_v1.py +25 -9
- unitysvc_services/models/provider_v1.py +19 -8
- unitysvc_services/models/seller_v1.py +10 -8
- unitysvc_services/models/service_v1.py +8 -1
- unitysvc_services/populate.py +2 -6
- unitysvc_services/publisher.py +676 -371
- unitysvc_services/py.typed +0 -0
- unitysvc_services/query.py +522 -337
- unitysvc_services/update.py +4 -13
- unitysvc_services/utils.py +2 -6
- unitysvc_services/validator.py +98 -79
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/METADATA +41 -39
- unitysvc_services-0.1.4.dist-info/RECORD +25 -0
- unitysvc_services-0.1.1.dist-info/RECORD +0 -23
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/WHEEL +0 -0
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/entry_points.txt +0 -0
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {unitysvc_services-0.1.1.dist-info → unitysvc_services-0.1.4.dist-info}/top_level.txt +0 -0
unitysvc_services/models/base.py
CHANGED
@@ -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
|
|
@@ -228,16 +231,10 @@ class Document(BaseModel):
|
|
228
231
|
# fields that will be stored in backend database
|
229
232
|
#
|
230
233
|
title: str = Field(min_length=5, max_length=255, description="Document title")
|
231
|
-
description: str | None = Field(
|
232
|
-
default=None, max_length=500, description="Document description"
|
233
|
-
)
|
234
|
+
description: str | None = Field(default=None, max_length=500, description="Document description")
|
234
235
|
mime_type: MimeTypeEnum = Field(description="Document MIME type")
|
235
|
-
version: str | None = Field(
|
236
|
-
|
237
|
-
)
|
238
|
-
category: DocumentCategoryEnum = Field(
|
239
|
-
description="Document category for organization and filtering"
|
240
|
-
)
|
236
|
+
version: str | None = Field(default=None, max_length=50, description="Document version")
|
237
|
+
category: DocumentCategoryEnum = Field(description="Document category for organization and filtering")
|
241
238
|
meta: dict[str, Any] | None = Field(
|
242
239
|
default=None,
|
243
240
|
description="JSON containing operation stats",
|
@@ -271,12 +268,8 @@ class RateLimit(BaseModel):
|
|
271
268
|
window: TimeWindowEnum = Field(description="Time window for the limit")
|
272
269
|
|
273
270
|
# Optional additional info
|
274
|
-
description: str | None = Field(
|
275
|
-
|
276
|
-
)
|
277
|
-
burst_limit: int | None = Field(
|
278
|
-
default=None, description="Short-term burst allowance"
|
279
|
-
)
|
271
|
+
description: str | None = Field(default=None, max_length=255, description="Human-readable description")
|
272
|
+
burst_limit: int | None = Field(default=None, description="Short-term burst allowance")
|
280
273
|
|
281
274
|
# Status
|
282
275
|
is_active: bool = Field(default=True, description="Whether rate limit is active")
|
@@ -286,105 +279,57 @@ class ServiceConstraints(BaseModel):
|
|
286
279
|
model_config = ConfigDict(extra="forbid")
|
287
280
|
|
288
281
|
# Usage Quotas & Billing
|
289
|
-
monthly_quota: int | None = Field(
|
290
|
-
|
291
|
-
)
|
292
|
-
|
293
|
-
|
294
|
-
)
|
295
|
-
quota_unit: RateLimitUnitEnum | None = Field(
|
296
|
-
default=None, description="Unit for quota limits"
|
297
|
-
)
|
298
|
-
quota_reset_cycle: QuotaResetCycleEnum | None = Field(
|
299
|
-
default=None, description="How often quotas reset"
|
300
|
-
)
|
301
|
-
overage_policy: OveragePolicyEnum | None = Field(
|
302
|
-
default=None, description="What happens when quota is exceeded"
|
303
|
-
)
|
282
|
+
monthly_quota: int | None = Field(default=None, description="Monthly usage quota (requests, tokens, etc.)")
|
283
|
+
daily_quota: int | None = Field(default=None, description="Daily usage quota (requests, tokens, etc.)")
|
284
|
+
quota_unit: RateLimitUnitEnum | None = Field(default=None, description="Unit for quota limits")
|
285
|
+
quota_reset_cycle: QuotaResetCycleEnum | None = Field(default=None, description="How often quotas reset")
|
286
|
+
overage_policy: OveragePolicyEnum | None = Field(default=None, description="What happens when quota is exceeded")
|
304
287
|
|
305
288
|
# Authentication & Security
|
306
|
-
auth_methods: list[AuthMethodEnum] | None = Field(
|
307
|
-
|
308
|
-
)
|
309
|
-
ip_whitelist_required: bool | None = Field(
|
310
|
-
default=None, description="Whether IP whitelisting is required"
|
311
|
-
)
|
312
|
-
tls_version_min: str | None = Field(
|
313
|
-
default=None, description="Minimum TLS version required"
|
314
|
-
)
|
289
|
+
auth_methods: list[AuthMethodEnum] | None = Field(default=None, description="Supported authentication methods")
|
290
|
+
ip_whitelist_required: bool | None = Field(default=None, description="Whether IP whitelisting is required")
|
291
|
+
tls_version_min: str | None = Field(default=None, description="Minimum TLS version required")
|
315
292
|
|
316
293
|
# Request/Response Constraints
|
317
|
-
max_request_size_bytes: int | None = Field(
|
318
|
-
|
319
|
-
)
|
320
|
-
|
321
|
-
default=None, description="Maximum response payload size in bytes"
|
322
|
-
)
|
323
|
-
timeout_seconds: int | None = Field(
|
324
|
-
default=None, description="Request timeout in seconds"
|
325
|
-
)
|
326
|
-
max_batch_size: int | None = Field(
|
327
|
-
default=None, description="Maximum number of items in batch requests"
|
328
|
-
)
|
294
|
+
max_request_size_bytes: int | None = Field(default=None, description="Maximum request payload size in bytes")
|
295
|
+
max_response_size_bytes: int | None = Field(default=None, description="Maximum response payload size in bytes")
|
296
|
+
timeout_seconds: int | None = Field(default=None, description="Request timeout in seconds")
|
297
|
+
max_batch_size: int | None = Field(default=None, description="Maximum number of items in batch requests")
|
329
298
|
|
330
299
|
# Content & Model Restrictions
|
331
300
|
content_filters: list[ContentFilterEnum] | None = Field(
|
332
301
|
default=None, description="Active content filtering policies"
|
333
302
|
)
|
334
|
-
input_languages: list[str] | None = Field(
|
335
|
-
|
336
|
-
)
|
337
|
-
output_languages: list[str] | None = Field(
|
338
|
-
default=None, description="Supported output languages (ISO 639-1 codes)"
|
339
|
-
)
|
340
|
-
max_context_length: int | None = Field(
|
341
|
-
default=None, description="Maximum context length in tokens"
|
342
|
-
)
|
303
|
+
input_languages: list[str] | None = Field(default=None, description="Supported input languages (ISO 639-1 codes)")
|
304
|
+
output_languages: list[str] | None = Field(default=None, description="Supported output languages (ISO 639-1 codes)")
|
305
|
+
max_context_length: int | None = Field(default=None, description="Maximum context length in tokens")
|
343
306
|
region_restrictions: list[str] | None = Field(
|
344
307
|
default=None, description="Geographic restrictions (ISO country codes)"
|
345
308
|
)
|
346
309
|
|
347
310
|
# Availability & SLA
|
348
|
-
uptime_sla_percent: float | None = Field(
|
349
|
-
|
350
|
-
)
|
351
|
-
response_time_sla_ms: int | None = Field(
|
352
|
-
default=None, description="Response time SLA in milliseconds"
|
353
|
-
)
|
354
|
-
maintenance_windows: list[str] | None = Field(
|
355
|
-
default=None, description="Scheduled maintenance windows"
|
356
|
-
)
|
311
|
+
uptime_sla_percent: float | None = Field(default=None, description="Uptime SLA percentage (e.g., 99.9)")
|
312
|
+
response_time_sla_ms: int | None = Field(default=None, description="Response time SLA in milliseconds")
|
313
|
+
maintenance_windows: list[str] | None = Field(default=None, description="Scheduled maintenance windows")
|
357
314
|
|
358
315
|
# Concurrency & Connection Limits
|
359
|
-
max_concurrent_requests: int | None = Field(
|
360
|
-
|
361
|
-
)
|
362
|
-
connection_timeout_seconds: int | None = Field(
|
363
|
-
default=None, description="Connection timeout in seconds"
|
364
|
-
)
|
365
|
-
max_connections_per_ip: int | None = Field(
|
366
|
-
default=None, description="Maximum connections per IP address"
|
367
|
-
)
|
316
|
+
max_concurrent_requests: int | None = Field(default=None, description="Maximum concurrent requests allowed")
|
317
|
+
connection_timeout_seconds: int | None = Field(default=None, description="Connection timeout in seconds")
|
318
|
+
max_connections_per_ip: int | None = Field(default=None, description="Maximum connections per IP address")
|
368
319
|
|
369
320
|
|
370
321
|
class AccessInterface(BaseModel):
|
371
322
|
model_config = ConfigDict(extra="allow")
|
372
323
|
|
373
|
-
access_method: AccessMethodEnum = Field(
|
374
|
-
default=AccessMethodEnum.http, description="Type of access method"
|
375
|
-
)
|
324
|
+
access_method: AccessMethodEnum = Field(default=AccessMethodEnum.http, description="Type of access method")
|
376
325
|
|
377
326
|
api_endpoint: str = Field(max_length=500, description="API endpoint URL")
|
378
327
|
|
379
|
-
api_key: str | None = Field(
|
380
|
-
default=None, max_length=2000, description="API key if required"
|
381
|
-
)
|
328
|
+
api_key: str | None = Field(default=None, max_length=2000, description="API key if required")
|
382
329
|
|
383
330
|
name: str | None = Field(default=None, max_length=100, description="Interface name")
|
384
331
|
|
385
|
-
description: str | None = Field(
|
386
|
-
default=None, max_length=500, description="Interface description"
|
387
|
-
)
|
332
|
+
description: str | None = Field(default=None, max_length=500, description="Interface description")
|
388
333
|
|
389
334
|
request_transformer: dict[RequestTransformEnum, dict[str, Any]] | None = Field(
|
390
335
|
default=None, description="Request transformation configuration"
|
@@ -398,13 +343,9 @@ class AccessInterface(BaseModel):
|
|
398
343
|
default=None,
|
399
344
|
description="Rate limit",
|
400
345
|
)
|
401
|
-
constraint: ServiceConstraints | None = Field(
|
402
|
-
default=None, description="Service constraints and conditions"
|
403
|
-
)
|
346
|
+
constraint: ServiceConstraints | None = Field(default=None, description="Service constraints and conditions")
|
404
347
|
is_active: bool = Field(default=True, description="Whether interface is active")
|
405
|
-
is_primary: bool = Field(
|
406
|
-
default=False, description="Whether this is the primary interface"
|
407
|
-
)
|
348
|
+
is_primary: bool = Field(default=False, description="Whether this is the primary interface")
|
408
349
|
sort_order: int = Field(default=0, description="Display order")
|
409
350
|
|
410
351
|
|
@@ -412,13 +353,9 @@ class Pricing(BaseModel):
|
|
412
353
|
model_config = ConfigDict(extra="forbid")
|
413
354
|
|
414
355
|
# Pricing tier name (Basic, Pro, Enterprise, etc.)
|
415
|
-
name: str | None = Field(
|
416
|
-
default=None, description="Pricing tier name (e.g., Basic, Pro, Enterprise)"
|
417
|
-
)
|
356
|
+
name: str | None = Field(default=None, description="Pricing tier name (e.g., Basic, Pro, Enterprise)")
|
418
357
|
|
419
|
-
description: str | None = Field(
|
420
|
-
default=None, description="Pricing model description"
|
421
|
-
)
|
358
|
+
description: str | None = Field(default=None, description="Pricing model description")
|
422
359
|
|
423
360
|
# Currency and description
|
424
361
|
currency: str | None = Field(default=None, description="Currency code (e.g., USD)")
|
@@ -431,6 +368,124 @@ class Pricing(BaseModel):
|
|
431
368
|
)
|
432
369
|
|
433
370
|
# Optional reference to upstream pricing
|
434
|
-
reference: str | None = Field(
|
435
|
-
|
436
|
-
|
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,13 +1,14 @@
|
|
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
6
|
from unitysvc_services.models.base import (
|
7
7
|
AccessInterface,
|
8
8
|
Document,
|
9
9
|
ListingStatusEnum,
|
10
10
|
Pricing,
|
11
|
+
validate_name,
|
11
12
|
)
|
12
13
|
|
13
14
|
|
@@ -17,9 +18,7 @@ class ListingV1(BaseModel):
|
|
17
18
|
#
|
18
19
|
# fields for business data collection and maintenance
|
19
20
|
#
|
20
|
-
schema_version: str = Field(
|
21
|
-
default="listing_v1", description="Schema identifier", alias="schema"
|
22
|
-
)
|
21
|
+
schema_version: str = Field(default="listing_v1", description="Schema identifier", alias="schema")
|
23
22
|
time_created: datetime
|
24
23
|
|
25
24
|
#
|
@@ -32,8 +31,19 @@ class ListingV1(BaseModel):
|
|
32
31
|
),
|
33
32
|
)
|
34
33
|
|
35
|
-
seller_name: str | None = Field(
|
36
|
-
|
34
|
+
seller_name: str | None = Field(default=None, description="Name of the seller offering this service listing")
|
35
|
+
|
36
|
+
name: str | None = Field(
|
37
|
+
default=None,
|
38
|
+
max_length=255,
|
39
|
+
description="Name identifier for the service listing, default to filename",
|
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')",
|
37
47
|
)
|
38
48
|
|
39
49
|
# unique name for each provider, usually following upstream naming convention
|
@@ -50,9 +60,7 @@ class ListingV1(BaseModel):
|
|
50
60
|
# - code_examples
|
51
61
|
# multiple access interfaces can be provided, for example, if the service
|
52
62
|
# is available through multiple interfaces or service groups
|
53
|
-
user_access_interfaces: list[AccessInterface] = Field(
|
54
|
-
description="Dictionary of user access interfaces"
|
55
|
-
)
|
63
|
+
user_access_interfaces: list[AccessInterface] = Field(description="Dictionary of user access interfaces")
|
56
64
|
|
57
65
|
#
|
58
66
|
# how upstream charges for their services, which can include
|
@@ -74,3 +82,11 @@ class ListingV1(BaseModel):
|
|
74
82
|
user_parameters_ui_schema: dict[str, Any] | None = Field(
|
75
83
|
default=None, description="Dictionary of user parameters UI schema"
|
76
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):
|
@@ -12,17 +12,13 @@ class ProviderV1(BaseModel):
|
|
12
12
|
#
|
13
13
|
# fields for business data collection and maintenance
|
14
14
|
#
|
15
|
-
schema_version: str = Field(
|
16
|
-
default="provider_v1", description="Schema identifier", alias="schema"
|
17
|
-
)
|
15
|
+
schema_version: str = Field(default="provider_v1", description="Schema identifier", alias="schema")
|
18
16
|
time_created: datetime
|
19
17
|
# how to automatically populate service data, if available
|
20
18
|
services_populator: dict[str, Any] | None = None
|
21
19
|
# parameters for accessing service provider, which typically
|
22
20
|
# include "api_endpoint" and "api_key"
|
23
|
-
provider_access_info: AccessInterface = Field(
|
24
|
-
description="Dictionary of upstream access interface"
|
25
|
-
)
|
21
|
+
provider_access_info: AccessInterface = Field(description="Dictionary of upstream access interface")
|
26
22
|
#
|
27
23
|
# fields that will be stored in backend database
|
28
24
|
#
|
@@ -30,6 +26,13 @@ class ProviderV1(BaseModel):
|
|
30
26
|
# name of the provider should be the same as directory name
|
31
27
|
name: str
|
32
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
|
+
|
33
36
|
# this field is added for convenience. It will be converted to
|
34
37
|
# documents during importing.
|
35
38
|
logo: str | HttpUrl | None = None
|
@@ -61,3 +64,11 @@ class ProviderV1(BaseModel):
|
|
61
64
|
default=ProviderStatusEnum.active,
|
62
65
|
description="Provider status: active, disabled, or incomplete",
|
63
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):
|
@@ -17,9 +17,7 @@ class SellerV1(BaseModel):
|
|
17
17
|
#
|
18
18
|
# fields for business data collection and maintenance
|
19
19
|
#
|
20
|
-
schema_version: str = Field(
|
21
|
-
default="seller_v1", description="Schema identifier", alias="schema"
|
22
|
-
)
|
20
|
+
schema_version: str = Field(default="seller_v1", description="Schema identifier", alias="schema")
|
23
21
|
time_created: datetime
|
24
22
|
|
25
23
|
#
|
@@ -49,9 +47,7 @@ class SellerV1(BaseModel):
|
|
49
47
|
# Contact information
|
50
48
|
contact_email: EmailStr = Field(description="Primary contact email for the seller")
|
51
49
|
|
52
|
-
secondary_contact_email: EmailStr | None = Field(
|
53
|
-
default=None, description="Secondary contact email"
|
54
|
-
)
|
50
|
+
secondary_contact_email: EmailStr | None = Field(default=None, description="Secondary contact email")
|
55
51
|
|
56
52
|
# Account manager
|
57
53
|
account_manager: str | None = Field(
|
@@ -112,3 +108,9 @@ class SellerV1(BaseModel):
|
|
112
108
|
default=False,
|
113
109
|
description="Whether the seller has been verified (KYC/business verification)",
|
114
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)
|
unitysvc_services/populate.py
CHANGED
@@ -19,7 +19,7 @@ console = Console()
|
|
19
19
|
def populate(
|
20
20
|
data_dir: Path | None = typer.Argument(
|
21
21
|
None,
|
22
|
-
help="Directory containing provider data files (default:
|
22
|
+
help="Directory containing provider data files (default: current directory)",
|
23
23
|
),
|
24
24
|
provider_name: str | None = typer.Option(
|
25
25
|
None,
|
@@ -41,11 +41,7 @@ def populate(
|
|
41
41
|
"""
|
42
42
|
# Set data directory
|
43
43
|
if data_dir is None:
|
44
|
-
|
45
|
-
if data_dir_str:
|
46
|
-
data_dir = Path(data_dir_str)
|
47
|
-
else:
|
48
|
-
data_dir = Path.cwd() / "data"
|
44
|
+
data_dir = Path.cwd()
|
49
45
|
|
50
46
|
if not data_dir.is_absolute():
|
51
47
|
data_dir = Path.cwd() / data_dir
|