unitysvc-core 0.2.0__tar.gz → 0.2.2__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.
- {unitysvc_core-0.2.0/src/unitysvc_core.egg-info → unitysvc_core-0.2.2}/PKG-INFO +1 -1
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/pyproject.toml +1 -1
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/__init__.py +2 -4
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/base.py +5 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/listing_data.py +4 -8
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/promotion_data.py +0 -5
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/service.py +73 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/service_group_data.py +0 -5
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/schema/listing_v1.json +0 -14
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/utils.py +0 -36
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2/src/unitysvc_core.egg-info}/PKG-INFO +1 -1
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core.egg-info/SOURCES.txt +1 -0
- unitysvc_core-0.2.2/tests/test_service_data.py +49 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/tests/test_utils.py +0 -26
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/LICENSE +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/README.md +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/setup.cfg +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/__init__.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/documents.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/listing_v1.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/offering_data.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/offering_v1.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/pricing.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/provider_data.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/provider_v1.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/models/validators.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/py.typed +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/schema/base.json +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/schema/offering_v1.json +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/schema/promotion_v1.json +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/schema/provider_v1.json +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/schema/service_group_v1.json +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core/validator.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core.egg-info/dependency_links.txt +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core.egg-info/requires.txt +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/src/unitysvc_core.egg-info/top_level.txt +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/tests/test_pricing.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/tests/test_service_group.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/tests/test_validator.py +0 -0
- {unitysvc_core-0.2.0 → unitysvc_core-0.2.2}/tests/test_validators.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unitysvc-core"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Shared data models and validation helpers for UnitySVC packages"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Bo Peng", email = "bo.peng@unitysvc.com" }]
|
|
@@ -61,7 +61,6 @@ from .promotion_data import (
|
|
|
61
61
|
PromotionData,
|
|
62
62
|
PromotionV1,
|
|
63
63
|
describe_scope,
|
|
64
|
-
is_promotion_file,
|
|
65
64
|
strip_schema_field,
|
|
66
65
|
validate_promotion,
|
|
67
66
|
)
|
|
@@ -71,13 +70,13 @@ from .service import (
|
|
|
71
70
|
AccessInterfaceData,
|
|
72
71
|
RateLimit,
|
|
73
72
|
ServiceConstraints,
|
|
73
|
+
ServiceData,
|
|
74
74
|
UpstreamAccessConfigData,
|
|
75
75
|
)
|
|
76
76
|
from .service_group_data import (
|
|
77
77
|
SERVICE_GROUP_SCHEMA_VERSION,
|
|
78
78
|
ServiceGroupData,
|
|
79
79
|
ServiceGroupV1,
|
|
80
|
-
is_service_group_file,
|
|
81
80
|
validate_service_group,
|
|
82
81
|
)
|
|
83
82
|
from .validators import (
|
|
@@ -111,6 +110,7 @@ __all__ = [
|
|
|
111
110
|
"UpstreamAccessConfigData",
|
|
112
111
|
"RateLimit",
|
|
113
112
|
"ServiceConstraints",
|
|
113
|
+
"ServiceData",
|
|
114
114
|
# Enums
|
|
115
115
|
"AccessMethodEnum",
|
|
116
116
|
"AuthMethodEnum",
|
|
@@ -176,12 +176,10 @@ __all__ = [
|
|
|
176
176
|
"suggest_valid_name",
|
|
177
177
|
# Promotions
|
|
178
178
|
"PROMOTION_SCHEMA_VERSION",
|
|
179
|
-
"is_promotion_file",
|
|
180
179
|
"describe_scope",
|
|
181
180
|
"strip_schema_field",
|
|
182
181
|
"validate_promotion",
|
|
183
182
|
# Service Groups
|
|
184
183
|
"SERVICE_GROUP_SCHEMA_VERSION",
|
|
185
|
-
"is_service_group_file",
|
|
186
184
|
"validate_service_group",
|
|
187
185
|
]
|
|
@@ -241,6 +241,11 @@ class RequestTransformEnum(StrEnum):
|
|
|
241
241
|
body_transformer = "body_transformer"
|
|
242
242
|
# Simple body replacement from rendered enrollment data
|
|
243
243
|
set_body = "set_body"
|
|
244
|
+
# Translate the request/response between the client's LLM API format and the
|
|
245
|
+
# format the upstream provider accepts (e.g. Anthropic Messages -> OpenAI
|
|
246
|
+
# Chat). Config carries ``upstream_format``; the gateway performs the
|
|
247
|
+
# conversion in-process (#923/#929).
|
|
248
|
+
llm_translate = "llm_translate"
|
|
244
249
|
|
|
245
250
|
|
|
246
251
|
class ServiceTypeEnum(StrEnum):
|
|
@@ -10,7 +10,6 @@ and `time_created` for data file validation.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from typing import Any
|
|
13
|
-
from uuid import UUID
|
|
14
13
|
|
|
15
14
|
from pydantic import BaseModel, Field
|
|
16
15
|
|
|
@@ -43,13 +42,10 @@ class ServiceListingData(BaseModel):
|
|
|
43
42
|
|
|
44
43
|
model_config = {"extra": "ignore"}
|
|
45
44
|
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
description="Service ID from previous publish. If provided, updates existing service. "
|
|
51
|
-
"Stored in override file (e.g., listing.override.json) by SDK after first publish.",
|
|
52
|
-
)
|
|
45
|
+
# NOTE: a listing carries no service_id. Service identity is backend-
|
|
46
|
+
# assigned provenance, not authored listing content — it lives in
|
|
47
|
+
# ``service.json`` and travels as the top-level ``ServiceDataInput.service_id``
|
|
48
|
+
# field on upload/revision, never embedded in the listing.
|
|
53
49
|
|
|
54
50
|
# Listing identification
|
|
55
51
|
name: str | None = Field(
|
|
@@ -100,11 +100,6 @@ class PromotionData(BaseModel):
|
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
def is_promotion_file(data: dict[str, Any]) -> bool:
|
|
104
|
-
"""Check if a data dict is a promotion file (by schema version)."""
|
|
105
|
-
return data.get("schema") == PROMOTION_SCHEMA_VERSION
|
|
106
|
-
|
|
107
|
-
|
|
108
103
|
def strip_schema_field(data: dict[str, Any]) -> dict[str, Any]:
|
|
109
104
|
"""Return a copy of the data dict without the ``schema`` field.
|
|
110
105
|
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from datetime import datetime
|
|
5
6
|
from typing import Any
|
|
7
|
+
from uuid import UUID
|
|
6
8
|
|
|
7
9
|
from pydantic import BaseModel, ConfigDict, Field
|
|
8
10
|
|
|
@@ -140,3 +142,74 @@ class UpstreamAccessConfigData(AccessInterfaceData):
|
|
|
140
142
|
"credential on the source header."
|
|
141
143
|
),
|
|
142
144
|
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ServiceData(BaseModel):
|
|
148
|
+
"""Backend-assigned service identity record (the ``service.json`` file).
|
|
149
|
+
|
|
150
|
+
A service's ``provider_data`` / ``offering_data`` / ``listing_data`` are
|
|
151
|
+
authored by the seller; this is the *other* half — the record the backend
|
|
152
|
+
materializes once a service exists. It is the **round-trip** structure:
|
|
153
|
+
the ingest task returns it, the seller stores it in ``service.json`` beside
|
|
154
|
+
the spec files, and it travels back as the top-level ``service_data`` field
|
|
155
|
+
on the next upload/revision so the backend can match the upload to the
|
|
156
|
+
existing service.
|
|
157
|
+
|
|
158
|
+
Of these fields, only ``service_id`` is consumed on the way *in* (it
|
|
159
|
+
targets revise/replace vs create); the rest are populated on the way *out*
|
|
160
|
+
and are informational provenance for the seller. ``extra="ignore"`` keeps
|
|
161
|
+
unknown keys from breaking either direction.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
model_config = ConfigDict(extra="ignore")
|
|
165
|
+
|
|
166
|
+
service_id: UUID | None = Field(
|
|
167
|
+
default=None,
|
|
168
|
+
description=(
|
|
169
|
+
"Backend-assigned service id from a previous publish. When present "
|
|
170
|
+
"on upload, the request targets the existing service (revise/"
|
|
171
|
+
"replace) instead of creating a new one."
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
revision_of: UUID | None = Field(
|
|
176
|
+
default=None,
|
|
177
|
+
description=(
|
|
178
|
+
"Set when this record describes a revision: the canonical service "
|
|
179
|
+
"id the revision derives from. ``service_id`` is then the revision's "
|
|
180
|
+
"own id."
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
status: str | None = Field(
|
|
185
|
+
default=None,
|
|
186
|
+
description="Service identity status as resolved by the last ingest.",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
name: str | None = Field(
|
|
190
|
+
default=None,
|
|
191
|
+
description="Backend-derived service name (from listing/offering).",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
display_name: str | None = Field(
|
|
195
|
+
default=None,
|
|
196
|
+
description="Backend-derived human-readable service name.",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
time_created: datetime | None = Field(
|
|
200
|
+
default=None,
|
|
201
|
+
description="When the service was first created (informational provenance).",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
template_instance_id: UUID | None = Field(
|
|
205
|
+
default=None,
|
|
206
|
+
description=(
|
|
207
|
+
"Set when the service was published from a seller TemplateInstance "
|
|
208
|
+
"(the seller-instances flow). Like ``service_id`` it is consumed on "
|
|
209
|
+
"the way *in* to declare the operation — with no ``service_id`` it "
|
|
210
|
+
"creates a service from the template (the backend pins the form's "
|
|
211
|
+
"service_id); with one it updates an existing template-generated "
|
|
212
|
+
"service — and echoed on the way *out* so ``service.json`` records "
|
|
213
|
+
"the template association. Absent for plain (non-template) services."
|
|
214
|
+
),
|
|
215
|
+
)
|
|
@@ -131,11 +131,6 @@ class ServiceGroupData(BaseModel):
|
|
|
131
131
|
SERVICE_GROUP_SCHEMA_VERSION = "service_group_v1"
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
def is_service_group_file(data: dict[str, Any]) -> bool:
|
|
135
|
-
"""Check if a data dict is a service group file (by schema version)."""
|
|
136
|
-
return data.get("schema") == SERVICE_GROUP_SCHEMA_VERSION
|
|
137
|
-
|
|
138
|
-
|
|
139
134
|
def strip_schema_field(data: dict[str, Any]) -> dict[str, Any]:
|
|
140
135
|
"""Return a copy of the data dict without the ``schema`` field."""
|
|
141
136
|
return {k: v for k, v in data.items() if k != "schema"}
|
|
@@ -1953,20 +1953,6 @@
|
|
|
1953
1953
|
"title": "Name",
|
|
1954
1954
|
"type": "string"
|
|
1955
1955
|
},
|
|
1956
|
-
"service_id": {
|
|
1957
|
-
"anyOf": [
|
|
1958
|
-
{
|
|
1959
|
-
"format": "uuid",
|
|
1960
|
-
"type": "string"
|
|
1961
|
-
},
|
|
1962
|
-
{
|
|
1963
|
-
"type": "null"
|
|
1964
|
-
}
|
|
1965
|
-
],
|
|
1966
|
-
"default": null,
|
|
1967
|
-
"description": "Service ID from previous publish. If provided, updates existing service. Stored in override file (e.g., listing.override.json) by SDK after first publish.",
|
|
1968
|
-
"title": "Service Id"
|
|
1969
|
-
},
|
|
1970
1956
|
"service_options": {
|
|
1971
1957
|
"anyOf": [
|
|
1972
1958
|
{
|
|
@@ -408,42 +408,6 @@ def schema_for_path(path: Path) -> str | None:
|
|
|
408
408
|
return STEM_TO_SCHEMA.get(path.stem)
|
|
409
409
|
|
|
410
410
|
|
|
411
|
-
def find_file_by_schema_and_name(
|
|
412
|
-
data_dir: Path, schema: str, name_field: str, name_value: str
|
|
413
|
-
) -> tuple[Path, str, dict[str, Any]] | None:
|
|
414
|
-
"""
|
|
415
|
-
Find a data file by schema type and name field value.
|
|
416
|
-
|
|
417
|
-
Args:
|
|
418
|
-
data_dir: Directory to search
|
|
419
|
-
schema: Schema identifier (e.g., "offering_v1", "listing_v1")
|
|
420
|
-
name_field: Field name to match (e.g., "name", "seller_name")
|
|
421
|
-
name_value: Value to match in the name field
|
|
422
|
-
|
|
423
|
-
Returns:
|
|
424
|
-
Tuple of (file_path, format, data) if found, None otherwise
|
|
425
|
-
"""
|
|
426
|
-
data_files = find_data_files(data_dir)
|
|
427
|
-
|
|
428
|
-
for data_file in data_files:
|
|
429
|
-
try:
|
|
430
|
-
data, file_format = load_data_file(data_file)
|
|
431
|
-
except Exception as exc:
|
|
432
|
-
_warn_skipped(data_file, exc)
|
|
433
|
-
continue
|
|
434
|
-
# Discovery walks every ``*.json`` / ``*.toml`` under ``data_dir``,
|
|
435
|
-
# which sweeps in things like ``package-lock.json`` (top-level dict
|
|
436
|
-
# but no ``schema`` field) and openapi/mypy-cache files (top-level
|
|
437
|
-
# list). The schema check ignores the former, but the latter
|
|
438
|
-
# crashes ``.get`` — skip non-dict roots instead.
|
|
439
|
-
if not isinstance(data, dict):
|
|
440
|
-
continue
|
|
441
|
-
if data_file.stem == SCHEMA_TO_STEM.get(schema) and data.get(name_field) == name_value:
|
|
442
|
-
return data_file, file_format, data
|
|
443
|
-
|
|
444
|
-
return None
|
|
445
|
-
|
|
446
|
-
|
|
447
411
|
@lru_cache(maxsize=256)
|
|
448
412
|
def find_files_by_schema(
|
|
449
413
|
data_dir: Path,
|
|
@@ -31,6 +31,7 @@ src/unitysvc_core/schema/promotion_v1.json
|
|
|
31
31
|
src/unitysvc_core/schema/provider_v1.json
|
|
32
32
|
src/unitysvc_core/schema/service_group_v1.json
|
|
33
33
|
tests/test_pricing.py
|
|
34
|
+
tests/test_service_data.py
|
|
34
35
|
tests/test_service_group.py
|
|
35
36
|
tests/test_utils.py
|
|
36
37
|
tests/test_validator.py
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Tests for the ServiceData model (the service.json provenance structure)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from unitysvc_core.models import ServiceData
|
|
9
|
+
|
|
10
|
+
_SID = "2c1f9e7a-0000-0000-0000-000000000000"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_empty_service_data_has_no_identity() -> None:
|
|
14
|
+
sd = ServiceData()
|
|
15
|
+
assert sd.service_id is None
|
|
16
|
+
assert sd.time_created is None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_service_id_coerced_to_uuid() -> None:
|
|
20
|
+
sd = ServiceData.model_validate({"service_id": _SID})
|
|
21
|
+
assert sd.service_id == UUID(_SID)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_time_created_and_extra_fields() -> None:
|
|
25
|
+
# time_created is a recognized provenance field; unknown extras are
|
|
26
|
+
# tolerated (extra="ignore") rather than rejected.
|
|
27
|
+
sd = ServiceData.model_validate({"service_id": _SID, "time_created": "2026-01-01T00:00:00", "note": "x"})
|
|
28
|
+
assert sd.time_created == datetime(2026, 1, 1, 0, 0)
|
|
29
|
+
assert "note" not in sd.model_dump()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_full_identity_record_round_trips() -> None:
|
|
33
|
+
# The shape the ingest task returns and the seller persists to service.json.
|
|
34
|
+
rev = "11111111-0000-0000-0000-000000000000"
|
|
35
|
+
sd = ServiceData.model_validate(
|
|
36
|
+
{
|
|
37
|
+
"service_id": _SID,
|
|
38
|
+
"revision_of": rev,
|
|
39
|
+
"status": "revision_created",
|
|
40
|
+
"name": "acme/widget",
|
|
41
|
+
"display_name": "ACME Widget",
|
|
42
|
+
"time_created": "2026-01-01T00:00:00",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
assert sd.service_id == UUID(_SID)
|
|
46
|
+
assert sd.revision_of == UUID(rev)
|
|
47
|
+
assert sd.status == "revision_created"
|
|
48
|
+
assert sd.name == "acme/widget"
|
|
49
|
+
assert sd.display_name == "ACME Widget"
|
|
@@ -11,7 +11,6 @@ from unitysvc_core.utils import (
|
|
|
11
11
|
compute_file_hash,
|
|
12
12
|
deep_merge_dicts,
|
|
13
13
|
expand_presets,
|
|
14
|
-
find_file_by_schema_and_name,
|
|
15
14
|
find_files_by_schema,
|
|
16
15
|
generate_content_based_key,
|
|
17
16
|
get_basename,
|
|
@@ -175,19 +174,6 @@ def test_find_files_by_schema_skips_non_dict_roots(tmp_path: Path) -> None:
|
|
|
175
174
|
assert {Path(fp).relative_to(tmp_path).as_posix() for fp, _fmt, _data in files} == {"provider.json"}
|
|
176
175
|
|
|
177
176
|
|
|
178
|
-
def test_find_file_by_schema_and_name_skips_non_dict_roots(tmp_path: Path) -> None:
|
|
179
|
-
"""Sibling discovery helper has the same surface — pin both."""
|
|
180
|
-
(tmp_path / "listing.json").write_text(json.dumps({"name": "wanted"}))
|
|
181
|
-
(tmp_path / "bad").mkdir()
|
|
182
|
-
(tmp_path / "bad" / "listing.json").write_text(json.dumps([{"x": 1}]))
|
|
183
|
-
|
|
184
|
-
result = find_file_by_schema_and_name(tmp_path, "listing_v1", "name", "wanted")
|
|
185
|
-
assert result is not None
|
|
186
|
-
fp, _fmt, data = result
|
|
187
|
-
assert Path(fp).name == "listing.json"
|
|
188
|
-
assert data["name"] == "wanted"
|
|
189
|
-
|
|
190
|
-
|
|
191
177
|
# =============================================================================
|
|
192
178
|
# $preset sentinel expansion
|
|
193
179
|
# =============================================================================
|
|
@@ -375,18 +361,6 @@ def test_find_files_by_schema_warns_on_malformed_json(tmp_path: Path) -> None:
|
|
|
375
361
|
assert results == []
|
|
376
362
|
|
|
377
363
|
|
|
378
|
-
def test_find_file_by_schema_and_name_warns_on_load_failure(tmp_path: Path) -> None:
|
|
379
|
-
"""A malformed file in the search directory must surface as a warning,
|
|
380
|
-
not as a silent ``None`` (which would be indistinguishable from
|
|
381
|
-
"the name didn't match anything")."""
|
|
382
|
-
bad = tmp_path / "provider.json" # spec-named so discovery loads it
|
|
383
|
-
bad.write_text("{ this is not json")
|
|
384
|
-
|
|
385
|
-
with pytest.warns(DataFileLoadWarning, match="provider.json"):
|
|
386
|
-
result = find_file_by_schema_and_name(tmp_path, "provider_v1", "name", "p")
|
|
387
|
-
assert result is None
|
|
388
|
-
|
|
389
|
-
|
|
390
364
|
def test_find_files_by_schema_can_be_promoted_to_error(tmp_path: Path) -> None:
|
|
391
365
|
"""Callers that want fail-fast can promote the warning to an exception."""
|
|
392
366
|
import warnings as _warnings
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|