schemez 0.2.2__tar.gz → 0.2.3__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.
- {schemez-0.2.2 → schemez-0.2.3}/PKG-INFO +1 -1
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/__init__.py +1 -1
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/pydantic_types.py +12 -2
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/schema.py +12 -2
- schemez-0.2.3/src/schemez/schemadef/schemadef.py +255 -0
- schemez-0.2.3/tests/test_enum_support.py +192 -0
- schemez-0.2.3/tests/test_schema_field.py +467 -0
- schemez-0.2.2/src/schemez/schemadef/schemadef.py +0 -120
- {schemez-0.2.2 → schemez-0.2.3}/.copier-answers.yml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/.github/FUNDING.yml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/.github/copilot-instructions.md +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/.github/dependabot.yml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/.github/workflows/build.yml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/.github/workflows/documentation.yml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/.gitignore +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/.pre-commit-config.yaml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/LICENSE +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/README.md +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/docs/.empty +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/duties.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/mkdocs.yml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/overrides/_dummy.txt +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/pyproject.toml +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/code.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/convert.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/docstrings.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/helpers.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/py.typed +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/src/schemez/schemadef/__init__.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/tests/__init__.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/tests/conftest.py +0 -0
- {schemez-0.2.2 → schemez-0.2.3}/tests/test_schema.py +0 -0
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import Annotated
|
5
|
+
from typing import Annotated, Any
|
6
6
|
|
7
|
-
from pydantic import Field
|
7
|
+
from pydantic import BaseModel, Field
|
8
8
|
|
9
9
|
|
10
10
|
ModelIdentifier = Annotated[
|
@@ -40,3 +40,13 @@ MimeType = Annotated[
|
|
40
40
|
description="Standard MIME type identifying file formats and content types",
|
41
41
|
),
|
42
42
|
]
|
43
|
+
|
44
|
+
|
45
|
+
def get_field_type(model: type[BaseModel], field_name: str) -> dict[str, Any]:
|
46
|
+
"""Extract field_type metadata from a model field."""
|
47
|
+
field_info = model.model_fields[field_name]
|
48
|
+
metadata = {}
|
49
|
+
if field_info.json_schema_extra and isinstance(field_info.json_schema_extra, dict):
|
50
|
+
metadata.update(field_info.json_schema_extra)
|
51
|
+
|
52
|
+
return metadata
|
@@ -216,11 +216,21 @@ class Schema(BaseModel):
|
|
216
216
|
|
217
217
|
return get_ctor_basemodel(target_cls)
|
218
218
|
|
219
|
-
def model_dump_yaml(
|
219
|
+
def model_dump_yaml(
|
220
|
+
self,
|
221
|
+
exclude_none: bool = True,
|
222
|
+
exclude_defaults: bool = False,
|
223
|
+
exclude_unset: bool = False,
|
224
|
+
) -> str:
|
220
225
|
"""Dump configuration to YAML string."""
|
221
226
|
import yamling
|
222
227
|
|
223
|
-
|
228
|
+
text = self.model_dump(
|
229
|
+
exclude_none=exclude_none,
|
230
|
+
exclude_defaults=exclude_defaults,
|
231
|
+
exclude_unset=exclude_unset,
|
232
|
+
)
|
233
|
+
return yamling.dump_yaml(text)
|
224
234
|
|
225
235
|
def save(self, path: StrPath, overwrite: bool = False) -> None:
|
226
236
|
"""Save configuration to a YAML file.
|
@@ -0,0 +1,255 @@
|
|
1
|
+
"""Models for schema fields and definitions."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
from typing import Annotated, Any, Literal
|
7
|
+
|
8
|
+
from pydantic import BaseModel, Field, create_model
|
9
|
+
|
10
|
+
from schemez import Schema, helpers
|
11
|
+
|
12
|
+
|
13
|
+
class SchemaField(Schema):
|
14
|
+
"""Field definition for inline response types.
|
15
|
+
|
16
|
+
Defines a single field in an inline response definition, including:
|
17
|
+
- Data type specification
|
18
|
+
- Optional description
|
19
|
+
- Validation constraints
|
20
|
+
- Enum values (when type is 'enum')
|
21
|
+
|
22
|
+
Used by InlineSchemaDef to structure response fields.
|
23
|
+
"""
|
24
|
+
|
25
|
+
type: str
|
26
|
+
"""Data type of the response field"""
|
27
|
+
|
28
|
+
description: str | None = None
|
29
|
+
"""Optional description of what this field represents"""
|
30
|
+
|
31
|
+
values: list[Any] | None = None
|
32
|
+
"""Values for enum type fields"""
|
33
|
+
|
34
|
+
# Common validation constraints
|
35
|
+
default: Any | None = None
|
36
|
+
"""Default value for the field"""
|
37
|
+
|
38
|
+
title: str | None = None
|
39
|
+
"""Title for the field in generated JSON Schema"""
|
40
|
+
|
41
|
+
pattern: str | None = None
|
42
|
+
"""Regex pattern for string validation"""
|
43
|
+
|
44
|
+
min_length: int | None = None
|
45
|
+
"""Minimum length for collections"""
|
46
|
+
|
47
|
+
max_length: int | None = None
|
48
|
+
"""Maximum length for collections"""
|
49
|
+
|
50
|
+
gt: float | None = None
|
51
|
+
"""Greater than (exclusive) validation for numbers"""
|
52
|
+
|
53
|
+
ge: float | None = None
|
54
|
+
"""Greater than or equal (inclusive) validation for numbers"""
|
55
|
+
|
56
|
+
lt: float | None = None
|
57
|
+
"""Less than (exclusive) validation for numbers"""
|
58
|
+
|
59
|
+
le: float | None = None
|
60
|
+
"""Less than or equal (inclusive) validation for numbers"""
|
61
|
+
|
62
|
+
multiple_of: float | None = None
|
63
|
+
"""Number must be a multiple of this value"""
|
64
|
+
|
65
|
+
literal_value: Any | None = None
|
66
|
+
"""Value for Literal type constraint, makes field accept only this specific value"""
|
67
|
+
|
68
|
+
examples: list[Any] | None = None
|
69
|
+
"""Examples for this field in JSON Schema"""
|
70
|
+
|
71
|
+
optional: bool = False
|
72
|
+
"""Whether this field is optional (None value allowed)"""
|
73
|
+
|
74
|
+
json_schema_extra: dict[str, Any] | None = None
|
75
|
+
"""Additional JSON Schema information"""
|
76
|
+
|
77
|
+
field_config: dict[str, Any] | None = None
|
78
|
+
"""Configuration for Pydantic model fields"""
|
79
|
+
|
80
|
+
# Extensibility for future or custom constraints
|
81
|
+
constraints: dict[str, Any] = Field(default_factory=dict)
|
82
|
+
"""Additional constraints not covered by explicit fields"""
|
83
|
+
|
84
|
+
|
85
|
+
class BaseSchemaDef(Schema):
|
86
|
+
"""Base class for response definitions."""
|
87
|
+
|
88
|
+
type: str = Field(init=False)
|
89
|
+
|
90
|
+
description: str | None = None
|
91
|
+
"""A description for this response definition."""
|
92
|
+
|
93
|
+
|
94
|
+
class InlineSchemaDef(BaseSchemaDef):
|
95
|
+
"""Inline definition of schema.
|
96
|
+
|
97
|
+
Allows defining response types directly in the configuration using:
|
98
|
+
- Field definitions with types and descriptions
|
99
|
+
- Optional validation constraints
|
100
|
+
- Custom field descriptions
|
101
|
+
|
102
|
+
Example:
|
103
|
+
schemas:
|
104
|
+
BasicResult:
|
105
|
+
type: inline
|
106
|
+
fields:
|
107
|
+
success: {type: bool, description: "Operation success"}
|
108
|
+
message: {type: str, description: "Result details"}
|
109
|
+
"""
|
110
|
+
|
111
|
+
type: Literal["inline"] = Field("inline", init=False)
|
112
|
+
"""Inline response definition."""
|
113
|
+
|
114
|
+
fields: dict[str, SchemaField]
|
115
|
+
"""A dictionary containing all fields."""
|
116
|
+
|
117
|
+
def get_schema(self) -> type[Schema]: # type: ignore
|
118
|
+
"""Create Pydantic model from inline definition."""
|
119
|
+
fields = {}
|
120
|
+
for name, field in self.fields.items():
|
121
|
+
# Initialize constraint dictionary
|
122
|
+
field_constraints = {}
|
123
|
+
|
124
|
+
# Handle enum type
|
125
|
+
if field.type == "enum":
|
126
|
+
if not field.values:
|
127
|
+
msg = f"Field '{name}' has type 'enum' but no values defined"
|
128
|
+
raise ValueError(msg)
|
129
|
+
|
130
|
+
# Create dynamic Enum class
|
131
|
+
enum_name = f"{name.capitalize()}Enum"
|
132
|
+
|
133
|
+
# Create enum members dictionary
|
134
|
+
enum_members = {}
|
135
|
+
for i, value in enumerate(field.values):
|
136
|
+
if isinstance(value, str) and value.isidentifier():
|
137
|
+
# If value is a valid Python identifier, use it as is
|
138
|
+
key = value
|
139
|
+
else:
|
140
|
+
# Otherwise, create a synthetic name
|
141
|
+
key = f"VALUE_{i}"
|
142
|
+
enum_members[key] = value
|
143
|
+
|
144
|
+
# Create the enum class
|
145
|
+
enum_class = Enum(enum_name, enum_members)
|
146
|
+
python_type: Any = enum_class
|
147
|
+
|
148
|
+
# Handle enum default value specially
|
149
|
+
if field.default is not None:
|
150
|
+
# Store default value as the enum value string
|
151
|
+
# Pydantic v2 will convert it to the enum instance
|
152
|
+
if field.default in list(field.values):
|
153
|
+
field_constraints["default"] = field.default
|
154
|
+
else:
|
155
|
+
msg = (
|
156
|
+
f"Default value {field.default!r} not found "
|
157
|
+
f"in enum values for field {name!r}"
|
158
|
+
)
|
159
|
+
raise ValueError(msg)
|
160
|
+
else:
|
161
|
+
python_type = helpers.resolve_type_string(field.type)
|
162
|
+
if not python_type:
|
163
|
+
msg = f"Unsupported field type: {field.type}"
|
164
|
+
raise ValueError(msg)
|
165
|
+
|
166
|
+
# Handle literal constraint if provided
|
167
|
+
if field.literal_value is not None:
|
168
|
+
from typing import Literal as LiteralType
|
169
|
+
|
170
|
+
python_type = LiteralType[field.literal_value]
|
171
|
+
|
172
|
+
# Handle optional fields (allowing None)
|
173
|
+
if field.optional:
|
174
|
+
python_type = python_type | None # type: ignore
|
175
|
+
|
176
|
+
# Add standard Pydantic constraints
|
177
|
+
# Collect all constraint values
|
178
|
+
for constraint in [
|
179
|
+
"default",
|
180
|
+
"title",
|
181
|
+
"min_length",
|
182
|
+
"max_length",
|
183
|
+
"pattern",
|
184
|
+
"min_length",
|
185
|
+
"max_length",
|
186
|
+
"gt",
|
187
|
+
"ge",
|
188
|
+
"lt",
|
189
|
+
"le",
|
190
|
+
"multiple_of",
|
191
|
+
]:
|
192
|
+
value = getattr(field, constraint, None)
|
193
|
+
if value is not None:
|
194
|
+
field_constraints[constraint] = value
|
195
|
+
|
196
|
+
# Handle examples separately (Pydantic v2 way)
|
197
|
+
if field.examples:
|
198
|
+
if field.json_schema_extra is None:
|
199
|
+
field.json_schema_extra = {}
|
200
|
+
field.json_schema_extra["examples"] = field.examples
|
201
|
+
|
202
|
+
# Add json_schema_extra if provided
|
203
|
+
if field.json_schema_extra:
|
204
|
+
field_constraints["json_schema_extra"] = field.json_schema_extra
|
205
|
+
|
206
|
+
# Add any additional constraints
|
207
|
+
field_constraints.update(field.constraints)
|
208
|
+
|
209
|
+
field_info = Field(description=field.description, **field_constraints)
|
210
|
+
fields[name] = (python_type, field_info)
|
211
|
+
|
212
|
+
cls_name = self.description or "ResponseType"
|
213
|
+
return create_model(cls_name, **fields, __base__=Schema, __doc__=self.description) # type: ignore[call-overload]
|
214
|
+
|
215
|
+
|
216
|
+
class ImportedSchemaDef(BaseSchemaDef):
|
217
|
+
"""Response definition that imports an existing Pydantic model.
|
218
|
+
|
219
|
+
Allows using externally defined Pydantic models as response types.
|
220
|
+
Benefits:
|
221
|
+
- Reuse existing model definitions
|
222
|
+
- Full Python type support
|
223
|
+
- Complex validation logic
|
224
|
+
- IDE support for imported types
|
225
|
+
|
226
|
+
Example:
|
227
|
+
responses:
|
228
|
+
AnalysisResult:
|
229
|
+
type: import
|
230
|
+
import_path: myapp.models.AnalysisResult
|
231
|
+
"""
|
232
|
+
|
233
|
+
type: Literal["import"] = Field("import", init=False)
|
234
|
+
"""Import-path based response definition."""
|
235
|
+
|
236
|
+
import_path: str
|
237
|
+
"""The path to the pydantic model to use as the response type."""
|
238
|
+
|
239
|
+
# mypy is confused about "type"
|
240
|
+
# TODO: convert BaseModel to Schema?
|
241
|
+
def get_schema(self) -> type[BaseModel]: # type: ignore
|
242
|
+
"""Import and return the model class."""
|
243
|
+
try:
|
244
|
+
model_class = helpers.import_class(self.import_path)
|
245
|
+
if not issubclass(model_class, BaseModel):
|
246
|
+
msg = f"{self.import_path} must be a Pydantic model"
|
247
|
+
raise TypeError(msg) # noqa: TRY301
|
248
|
+
except Exception as e:
|
249
|
+
msg = f"Failed to import response type {self.import_path}"
|
250
|
+
raise ValueError(msg) from e
|
251
|
+
else:
|
252
|
+
return model_class
|
253
|
+
|
254
|
+
|
255
|
+
SchemaDef = Annotated[InlineSchemaDef | ImportedSchemaDef, Field(discriminator="type")]
|
@@ -0,0 +1,192 @@
|
|
1
|
+
"""Test enum support in SchemaField."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
|
7
|
+
from pydantic import ValidationError
|
8
|
+
import pytest
|
9
|
+
|
10
|
+
from schemez.schemadef.schemadef import InlineSchemaDef, SchemaField
|
11
|
+
|
12
|
+
|
13
|
+
def test_explicit_constraints():
|
14
|
+
"""Test that explicit constraints work correctly."""
|
15
|
+
fields = {
|
16
|
+
"name": SchemaField(
|
17
|
+
type="str",
|
18
|
+
min_length=3,
|
19
|
+
max_length=50,
|
20
|
+
pattern=r"^[a-zA-Z0-9_]+$",
|
21
|
+
),
|
22
|
+
"age": SchemaField(type="int", description="User age", ge=18, lt=120),
|
23
|
+
"score": SchemaField(
|
24
|
+
type="float",
|
25
|
+
description="Score value",
|
26
|
+
ge=0.0,
|
27
|
+
le=100.0,
|
28
|
+
multiple_of=0.5,
|
29
|
+
),
|
30
|
+
"tags": SchemaField(type="set[str]", min_length=1, max_length=10),
|
31
|
+
}
|
32
|
+
schema_def = InlineSchemaDef(description="Test Schema Constraints", fields=fields)
|
33
|
+
model = schema_def.get_schema()
|
34
|
+
valid_instance = model(
|
35
|
+
name="user123", # type: ignore
|
36
|
+
age=30, # type: ignore
|
37
|
+
score=95.5, # type: ignore
|
38
|
+
tags=["developer", "python"], # type: ignore
|
39
|
+
)
|
40
|
+
assert valid_instance.name == "user123" # type: ignore
|
41
|
+
assert valid_instance.age == 30 # type: ignore # noqa: PLR2004
|
42
|
+
assert valid_instance.score == 95.5 # type: ignore # noqa: PLR2004
|
43
|
+
assert valid_instance.tags == {"developer", "python"} # type: ignore
|
44
|
+
|
45
|
+
# Test min_length constraint
|
46
|
+
with pytest.raises(ValidationError):
|
47
|
+
model(
|
48
|
+
name="ab", # Too short # type: ignore
|
49
|
+
age=30, # type: ignore
|
50
|
+
score=95.5, # type: ignore
|
51
|
+
tags=["developer"], # type: ignore
|
52
|
+
)
|
53
|
+
|
54
|
+
# Test pattern constraint
|
55
|
+
with pytest.raises(ValidationError):
|
56
|
+
model(
|
57
|
+
name="user-name", # Invalid character # type: ignore
|
58
|
+
age=30, # type: ignore
|
59
|
+
score=95.5, # type: ignore
|
60
|
+
tags=["developer"], # type: ignore
|
61
|
+
)
|
62
|
+
|
63
|
+
# Test ge constraint
|
64
|
+
with pytest.raises(ValidationError):
|
65
|
+
model(
|
66
|
+
name="user123", # type: ignore
|
67
|
+
age=17, # Too young # type: ignore
|
68
|
+
score=95.5, # type: ignore
|
69
|
+
tags=["developer"], # type: ignore
|
70
|
+
)
|
71
|
+
|
72
|
+
# Test multiple_of constraint
|
73
|
+
with pytest.raises(ValidationError):
|
74
|
+
model(
|
75
|
+
name="user123", # type: ignore
|
76
|
+
age=30, # type: ignore
|
77
|
+
score=95.7, # Not a multiple of 0.5 # type: ignore
|
78
|
+
tags=["developer"], # type: ignore
|
79
|
+
)
|
80
|
+
|
81
|
+
# Test that set automatically enforces uniqueness
|
82
|
+
instance = model(
|
83
|
+
name="user123", # type: ignore
|
84
|
+
age=30, # type: ignore
|
85
|
+
score=95.5, # type: ignore
|
86
|
+
# Duplicates get automatically removed
|
87
|
+
tags=["developer", "developer", "python"], # type: ignore
|
88
|
+
)
|
89
|
+
assert len(instance.tags) == 2 # type: ignore # noqa: PLR2004
|
90
|
+
assert "developer" in instance.tags # type: ignore
|
91
|
+
assert "python" in instance.tags # type: ignore
|
92
|
+
|
93
|
+
|
94
|
+
def test_enum_type():
|
95
|
+
"""Test that enum type creates an Enum type."""
|
96
|
+
fields = {
|
97
|
+
"color": SchemaField(
|
98
|
+
type="enum",
|
99
|
+
description="Color selection",
|
100
|
+
values=["red", "green", "blue"],
|
101
|
+
)
|
102
|
+
}
|
103
|
+
schema_def = InlineSchemaDef(description="Test Schema", fields=fields)
|
104
|
+
|
105
|
+
model = schema_def.get_schema()
|
106
|
+
|
107
|
+
# Check that the model has the color field with an Enum type
|
108
|
+
color_field = model.model_fields["color"]
|
109
|
+
assert color_field.description == "Color selection"
|
110
|
+
|
111
|
+
# The field should be an Enum type
|
112
|
+
color_value = model(color="red").color # type: ignore
|
113
|
+
assert isinstance(color_value, Enum)
|
114
|
+
assert color_value.value == "red"
|
115
|
+
|
116
|
+
# Check all values work
|
117
|
+
assert model(color="green").color.value == "green" # type: ignore
|
118
|
+
assert model(color="blue").color.value == "blue" # type: ignore
|
119
|
+
|
120
|
+
# Try with invalid value
|
121
|
+
with pytest.raises(ValidationError):
|
122
|
+
model(color="purple") # type: ignore
|
123
|
+
|
124
|
+
|
125
|
+
def test_enum_with_numeric_values():
|
126
|
+
"""Test that enum type works with numeric values."""
|
127
|
+
fields = {
|
128
|
+
"priority": SchemaField(
|
129
|
+
type="enum",
|
130
|
+
description="Task priority",
|
131
|
+
values=[1, 2, 3, 5, 8],
|
132
|
+
)
|
133
|
+
}
|
134
|
+
schema_def = InlineSchemaDef(description="Test Schema", fields=fields)
|
135
|
+
model = schema_def.get_schema()
|
136
|
+
|
137
|
+
# Create a valid instance
|
138
|
+
instance = model(priority=5) # type: ignore
|
139
|
+
assert instance.priority.value == 5 # type: ignore # noqa: PLR2004
|
140
|
+
# Try with invalid value
|
141
|
+
with pytest.raises(ValidationError):
|
142
|
+
model(priority=4) # type: ignore
|
143
|
+
|
144
|
+
|
145
|
+
def test_mixed_enum_values():
|
146
|
+
"""Test that enum type works with mixed value types."""
|
147
|
+
fields = {
|
148
|
+
"value": SchemaField(
|
149
|
+
type="enum",
|
150
|
+
description="Mixed values",
|
151
|
+
values=[1, "text", True],
|
152
|
+
)
|
153
|
+
}
|
154
|
+
schema_def = InlineSchemaDef(description="Test Schema", fields=fields)
|
155
|
+
model = schema_def.get_schema()
|
156
|
+
|
157
|
+
# Create valid instances
|
158
|
+
assert model(value=1).value.value == 1 # type: ignore
|
159
|
+
assert model(value="text").value.value == "text" # type: ignore
|
160
|
+
assert model(value=True).value.value # type: ignore
|
161
|
+
|
162
|
+
# Try with invalid value
|
163
|
+
with pytest.raises(ValidationError):
|
164
|
+
model(value="invalid") # type: ignore
|
165
|
+
|
166
|
+
|
167
|
+
def test_missing_enum_values():
|
168
|
+
"""Test that enum type raises error when values are missing."""
|
169
|
+
fields = {"status": SchemaField(type="enum", description="Status with no vals")}
|
170
|
+
schema_def = InlineSchemaDef(description="Test Schema", fields=fields)
|
171
|
+
# Should raise an error when creating the schema
|
172
|
+
with pytest.raises(ValueError, match="has type 'enum' but no values defined"):
|
173
|
+
schema_def.get_schema()
|
174
|
+
|
175
|
+
|
176
|
+
def test_enum_with_default():
|
177
|
+
"""Test that enum type works with default value."""
|
178
|
+
fields = {
|
179
|
+
"status": SchemaField(
|
180
|
+
type="enum",
|
181
|
+
description="Status with default",
|
182
|
+
values=["pending", "active", "completed"],
|
183
|
+
default="pending",
|
184
|
+
)
|
185
|
+
}
|
186
|
+
schema_def = InlineSchemaDef(description="Test Schema", fields=fields)
|
187
|
+
|
188
|
+
model = schema_def.get_schema()
|
189
|
+
|
190
|
+
# Test default value
|
191
|
+
instance = model()
|
192
|
+
assert instance.status == "pending" # type: ignore
|
@@ -0,0 +1,467 @@
|
|
1
|
+
"""Test SchemaField functionality comprehensively."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
|
7
|
+
from pydantic import ValidationError
|
8
|
+
import pytest
|
9
|
+
|
10
|
+
from schemez.schemadef.schemadef import InlineSchemaDef, SchemaField
|
11
|
+
|
12
|
+
|
13
|
+
def test_basic_types():
|
14
|
+
"""Test that basic types are resolved correctly."""
|
15
|
+
schema_def = InlineSchemaDef(
|
16
|
+
description="Test Schema with Basic Types",
|
17
|
+
fields={
|
18
|
+
"string_field": SchemaField(type="str", description="A string field"),
|
19
|
+
"int_field": SchemaField(type="int", description="An integer field"),
|
20
|
+
"float_field": SchemaField(type="float", description="A float field"),
|
21
|
+
"bool_field": SchemaField(type="bool", description="A boolean field"),
|
22
|
+
"dict_field": SchemaField(type="dict", description="A dictionary field"),
|
23
|
+
"list_field": SchemaField(type="list", description="A list field"),
|
24
|
+
},
|
25
|
+
)
|
26
|
+
|
27
|
+
model = schema_def.get_schema()
|
28
|
+
|
29
|
+
# Check field types
|
30
|
+
assert model.model_fields["string_field"].annotation is str
|
31
|
+
assert model.model_fields["int_field"].annotation is int
|
32
|
+
assert model.model_fields["float_field"].annotation is float
|
33
|
+
assert model.model_fields["bool_field"].annotation is bool
|
34
|
+
assert model.model_fields["dict_field"].annotation is dict
|
35
|
+
assert model.model_fields["list_field"].annotation is list
|
36
|
+
|
37
|
+
# Check descriptions
|
38
|
+
assert model.model_fields["string_field"].description == "A string field"
|
39
|
+
|
40
|
+
|
41
|
+
def test_generic_types():
|
42
|
+
"""Test that generic types like list[str] are resolved correctly."""
|
43
|
+
schema_def = InlineSchemaDef(
|
44
|
+
description="Test Schema with Generic Types",
|
45
|
+
fields={
|
46
|
+
"str_list": SchemaField(type="list[str]", description="List of strings"),
|
47
|
+
"int_list": SchemaField(type="list[int]", description="List of integers"),
|
48
|
+
"str_dict": SchemaField(
|
49
|
+
type="dict[str, int]", description="String to int mapping"
|
50
|
+
),
|
51
|
+
},
|
52
|
+
)
|
53
|
+
|
54
|
+
model = schema_def.get_schema()
|
55
|
+
|
56
|
+
# Create valid instances
|
57
|
+
instance = model(
|
58
|
+
str_list=["a", "b", "c"], # type: ignore
|
59
|
+
int_list=[1, 2, 3], # type: ignore
|
60
|
+
str_dict={"a": 1, "b": 2}, # type: ignore
|
61
|
+
)
|
62
|
+
|
63
|
+
assert instance.str_list == ["a", "b", "c"] # type: ignore
|
64
|
+
assert instance.int_list == [1, 2, 3] # type: ignore
|
65
|
+
assert instance.str_dict == {"a": 1, "b": 2} # type: ignore
|
66
|
+
|
67
|
+
# Test type validation
|
68
|
+
with pytest.raises(ValidationError):
|
69
|
+
model(str_list=[1, 2, 3]) # type: ignore
|
70
|
+
|
71
|
+
with pytest.raises(ValidationError):
|
72
|
+
model(int_list=["a", "b", "c"]) # type: ignore
|
73
|
+
|
74
|
+
with pytest.raises(ValidationError):
|
75
|
+
model(str_dict={1: "a"}) # type: ignore
|
76
|
+
|
77
|
+
|
78
|
+
def test_string_constraints():
|
79
|
+
"""Test string validation constraints."""
|
80
|
+
schema_def = InlineSchemaDef(
|
81
|
+
description="Test Schema with String Constraints",
|
82
|
+
fields={
|
83
|
+
"username": SchemaField(
|
84
|
+
type="str",
|
85
|
+
description="Username",
|
86
|
+
min_length=3,
|
87
|
+
max_length=20,
|
88
|
+
pattern=r"^[a-z0-9_]+$",
|
89
|
+
),
|
90
|
+
},
|
91
|
+
)
|
92
|
+
|
93
|
+
model = schema_def.get_schema()
|
94
|
+
|
95
|
+
# Valid username
|
96
|
+
assert model(username="user_123").username == "user_123" # type: ignore
|
97
|
+
|
98
|
+
# Too short
|
99
|
+
with pytest.raises(ValidationError):
|
100
|
+
model(username="ab") # type: ignore
|
101
|
+
|
102
|
+
# Too long
|
103
|
+
with pytest.raises(ValidationError):
|
104
|
+
model(username="a" * 21) # type: ignore
|
105
|
+
|
106
|
+
# Invalid pattern
|
107
|
+
with pytest.raises(ValidationError):
|
108
|
+
model(username="User-Name") # type: ignore
|
109
|
+
|
110
|
+
|
111
|
+
def test_numeric_constraints():
|
112
|
+
"""Test numeric validation constraints."""
|
113
|
+
schema_def = InlineSchemaDef(
|
114
|
+
description="Test Schema with Numeric Constraints",
|
115
|
+
fields={
|
116
|
+
"age": SchemaField(
|
117
|
+
type="int",
|
118
|
+
description="Age",
|
119
|
+
ge=18,
|
120
|
+
lt=120,
|
121
|
+
),
|
122
|
+
"score": SchemaField(
|
123
|
+
type="float",
|
124
|
+
description="Score",
|
125
|
+
gt=0.0,
|
126
|
+
le=100.0,
|
127
|
+
multiple_of=0.5,
|
128
|
+
),
|
129
|
+
},
|
130
|
+
)
|
131
|
+
|
132
|
+
model = schema_def.get_schema()
|
133
|
+
|
134
|
+
# Valid values
|
135
|
+
instance = model(age=18, score=99.5) # type: ignore
|
136
|
+
assert instance.age == 18 # type: ignore # noqa: PLR2004
|
137
|
+
assert instance.score == 99.5 # type: ignore # noqa: PLR2004
|
138
|
+
|
139
|
+
# Test ge constraint
|
140
|
+
with pytest.raises(ValidationError):
|
141
|
+
model(age=17, score=50.0) # type: ignore
|
142
|
+
|
143
|
+
# Test lt constraint
|
144
|
+
with pytest.raises(ValidationError):
|
145
|
+
model(age=120, score=50.0) # type: ignore
|
146
|
+
|
147
|
+
# Test gt constraint
|
148
|
+
with pytest.raises(ValidationError):
|
149
|
+
model(age=18, score=0.0) # type: ignore
|
150
|
+
|
151
|
+
# Test le constraint
|
152
|
+
with pytest.raises(ValidationError):
|
153
|
+
model(age=18, score=100.5) # type: ignore
|
154
|
+
|
155
|
+
# Test multiple_of constraint
|
156
|
+
with pytest.raises(ValidationError):
|
157
|
+
model(age=18, score=50.1) # type: ignore
|
158
|
+
|
159
|
+
|
160
|
+
def test_collection_constraints():
|
161
|
+
"""Test collection validation constraints."""
|
162
|
+
schema_def = InlineSchemaDef(
|
163
|
+
description="Test Schema with Collection Constraints",
|
164
|
+
fields={
|
165
|
+
"tags": SchemaField(
|
166
|
+
type="list[str]",
|
167
|
+
description="Tags",
|
168
|
+
min_length=1,
|
169
|
+
max_length=5,
|
170
|
+
),
|
171
|
+
"unique_tags": SchemaField(
|
172
|
+
type="set[str]",
|
173
|
+
description="Tags with unique values",
|
174
|
+
min_length=1,
|
175
|
+
max_length=5,
|
176
|
+
),
|
177
|
+
},
|
178
|
+
)
|
179
|
+
|
180
|
+
model = schema_def.get_schema()
|
181
|
+
|
182
|
+
# Valid - provide both required fields
|
183
|
+
instance = model(
|
184
|
+
tags=["one", "two"], # type: ignore
|
185
|
+
unique_tags={"one", "two"}, # type: ignore
|
186
|
+
)
|
187
|
+
assert instance.tags == ["one", "two"] # type: ignore
|
188
|
+
assert instance.unique_tags == {"one", "two"} # type: ignore
|
189
|
+
|
190
|
+
# Too few items - need to provide both fields
|
191
|
+
with pytest.raises(ValidationError):
|
192
|
+
model(
|
193
|
+
tags=[], # type: ignore
|
194
|
+
unique_tags={"one", "two"}, # type: ignore
|
195
|
+
)
|
196
|
+
|
197
|
+
with pytest.raises(ValidationError):
|
198
|
+
model(
|
199
|
+
tags=["one", "two"], # type: ignore
|
200
|
+
unique_tags=set(), # type: ignore
|
201
|
+
)
|
202
|
+
|
203
|
+
# Too many items
|
204
|
+
with pytest.raises(ValidationError):
|
205
|
+
model(
|
206
|
+
tags=["one", "two", "three", "four", "five", "six"], # type: ignore
|
207
|
+
unique_tags={"one", "two"}, # type: ignore
|
208
|
+
)
|
209
|
+
|
210
|
+
with pytest.raises(ValidationError):
|
211
|
+
model(
|
212
|
+
tags=["one", "two"], # type: ignore
|
213
|
+
unique_tags={"one", "two", "three", "four", "five", "six"}, # type: ignore
|
214
|
+
)
|
215
|
+
|
216
|
+
# Non-unique items are allowed in list but automatically deduplicated in set
|
217
|
+
instance = model(
|
218
|
+
tags=["one", "one", "two"], # type: ignore
|
219
|
+
unique_tags={"one", "two"}, # type: ignore
|
220
|
+
)
|
221
|
+
assert instance.tags == ["one", "one", "two"] # type: ignore
|
222
|
+
|
223
|
+
# Attempting to pass non-unique items to a set field will automatically deduplicate
|
224
|
+
instance = model(
|
225
|
+
tags=["one", "two"], # type: ignore
|
226
|
+
unique_tags=["one", "one", "two"], # type: ignore # Will be converted to set
|
227
|
+
)
|
228
|
+
assert instance.unique_tags == {"one", "two"} # type: ignore
|
229
|
+
|
230
|
+
|
231
|
+
def test_default_values():
|
232
|
+
"""Test default values for fields."""
|
233
|
+
schema_def = InlineSchemaDef(
|
234
|
+
description="Test Schema with Default Values",
|
235
|
+
fields={
|
236
|
+
"name": SchemaField(
|
237
|
+
type="str",
|
238
|
+
description="Name",
|
239
|
+
default="Anonymous",
|
240
|
+
),
|
241
|
+
"active": SchemaField(
|
242
|
+
type="bool",
|
243
|
+
description="Active status",
|
244
|
+
default=True,
|
245
|
+
),
|
246
|
+
"count": SchemaField(
|
247
|
+
type="int",
|
248
|
+
description="Count",
|
249
|
+
default=0,
|
250
|
+
),
|
251
|
+
},
|
252
|
+
)
|
253
|
+
|
254
|
+
model = schema_def.get_schema()
|
255
|
+
|
256
|
+
# Test defaults when not provided
|
257
|
+
instance = model()
|
258
|
+
assert instance.name == "Anonymous" # type: ignore
|
259
|
+
assert instance.active is True # type: ignore
|
260
|
+
assert instance.count == 0 # type: ignore
|
261
|
+
|
262
|
+
# Test overriding defaults
|
263
|
+
instance = model(name="User", active=False, count=10) # type: ignore
|
264
|
+
assert instance.name == "User" # type: ignore
|
265
|
+
assert instance.active is False # type: ignore
|
266
|
+
assert instance.count == 10 # type: ignore # noqa: PLR2004
|
267
|
+
|
268
|
+
|
269
|
+
def test_const_constraint():
|
270
|
+
"""Test const constraint."""
|
271
|
+
schema_def = InlineSchemaDef(
|
272
|
+
description="Test Schema with Const Constraint",
|
273
|
+
fields={
|
274
|
+
"version": SchemaField(
|
275
|
+
type="str",
|
276
|
+
description="API Version",
|
277
|
+
literal_value="1.0",
|
278
|
+
),
|
279
|
+
},
|
280
|
+
)
|
281
|
+
|
282
|
+
model = schema_def.get_schema()
|
283
|
+
|
284
|
+
# Valid
|
285
|
+
assert model(version="1.0").version == "1.0" # type: ignore
|
286
|
+
|
287
|
+
# Invalid
|
288
|
+
with pytest.raises(ValidationError):
|
289
|
+
model(version="2.0") # type: ignore
|
290
|
+
|
291
|
+
|
292
|
+
def test_enum_type():
|
293
|
+
"""Test enum type field."""
|
294
|
+
schema_def = InlineSchemaDef(
|
295
|
+
description="Test Schema with Enum",
|
296
|
+
fields={
|
297
|
+
"status": SchemaField(
|
298
|
+
type="enum",
|
299
|
+
description="Status",
|
300
|
+
values=["pending", "active", "completed"],
|
301
|
+
),
|
302
|
+
},
|
303
|
+
)
|
304
|
+
|
305
|
+
model = schema_def.get_schema()
|
306
|
+
|
307
|
+
# The field should use an Enum type
|
308
|
+
status_field = model.model_fields["status"]
|
309
|
+
assert issubclass(status_field.annotation, Enum) # type: ignore
|
310
|
+
|
311
|
+
# Valid values
|
312
|
+
status = model(status="pending").status # type: ignore
|
313
|
+
assert isinstance(status, Enum)
|
314
|
+
assert status.value == "pending"
|
315
|
+
|
316
|
+
# Invalid value
|
317
|
+
with pytest.raises(ValidationError):
|
318
|
+
model(status="invalid") # type: ignore
|
319
|
+
|
320
|
+
|
321
|
+
def test_enum_with_default():
|
322
|
+
"""Test enum with default value."""
|
323
|
+
schema_def = InlineSchemaDef(
|
324
|
+
description="Test Schema with Enum Default",
|
325
|
+
fields={
|
326
|
+
"role": SchemaField(
|
327
|
+
type="enum",
|
328
|
+
description="User Role",
|
329
|
+
values=["admin", "user", "guest"],
|
330
|
+
default="user",
|
331
|
+
),
|
332
|
+
},
|
333
|
+
)
|
334
|
+
|
335
|
+
model = schema_def.get_schema()
|
336
|
+
|
337
|
+
# Test default
|
338
|
+
instance = model()
|
339
|
+
assert instance.role == "user" # type: ignore
|
340
|
+
|
341
|
+
# Override default
|
342
|
+
instance = model(role="admin") # type: ignore
|
343
|
+
assert instance.role.value == "admin" # type: ignore
|
344
|
+
|
345
|
+
|
346
|
+
def test_nullable_constraint():
|
347
|
+
"""Test nullable constraint."""
|
348
|
+
schema_def = InlineSchemaDef(
|
349
|
+
description="Test Schema with Nullable Fields",
|
350
|
+
fields={
|
351
|
+
"name": SchemaField(
|
352
|
+
type="str",
|
353
|
+
description="Optional name",
|
354
|
+
optional=True,
|
355
|
+
),
|
356
|
+
},
|
357
|
+
)
|
358
|
+
|
359
|
+
model = schema_def.get_schema()
|
360
|
+
|
361
|
+
# Test null value
|
362
|
+
instance = model(name=None) # type: ignore
|
363
|
+
assert instance.name is None # type: ignore
|
364
|
+
|
365
|
+
|
366
|
+
def test_additional_constraints():
|
367
|
+
"""Test additional constraints via the constraints dict."""
|
368
|
+
schema_def = InlineSchemaDef(
|
369
|
+
description="Test Schema with Additional Constraints",
|
370
|
+
fields={
|
371
|
+
"custom_field": SchemaField(
|
372
|
+
type="str",
|
373
|
+
description="Field with custom constraint",
|
374
|
+
examples=["example1", "example2"],
|
375
|
+
),
|
376
|
+
},
|
377
|
+
)
|
378
|
+
|
379
|
+
model = schema_def.get_schema()
|
380
|
+
assert model(custom_field="test").custom_field == "test" # type: ignore
|
381
|
+
|
382
|
+
|
383
|
+
def test_title_constraint():
|
384
|
+
"""Test title constraint for JSON Schema generation."""
|
385
|
+
schema_def = InlineSchemaDef(
|
386
|
+
description="Test Schema with Title",
|
387
|
+
fields={
|
388
|
+
"user_id": SchemaField(
|
389
|
+
type="str",
|
390
|
+
description="User ID",
|
391
|
+
title="User Identifier",
|
392
|
+
),
|
393
|
+
},
|
394
|
+
)
|
395
|
+
|
396
|
+
model = schema_def.get_schema()
|
397
|
+
|
398
|
+
# Check that the title was set
|
399
|
+
field = model.model_fields["user_id"]
|
400
|
+
assert field.title == "User Identifier"
|
401
|
+
|
402
|
+
|
403
|
+
def test_complex_nested_structure():
|
404
|
+
"""Test complex nested structure with various constraints."""
|
405
|
+
schema_def = InlineSchemaDef(
|
406
|
+
description="Complex Nested Schema",
|
407
|
+
fields={
|
408
|
+
"user": SchemaField(
|
409
|
+
type="dict[str, Any]",
|
410
|
+
description="User information",
|
411
|
+
json_schema_extra={
|
412
|
+
"required": ["name", "email"],
|
413
|
+
},
|
414
|
+
),
|
415
|
+
"settings": SchemaField(
|
416
|
+
type="dict[str, bool]",
|
417
|
+
description="User settings",
|
418
|
+
default={},
|
419
|
+
),
|
420
|
+
"metadata": SchemaField(
|
421
|
+
type="dict",
|
422
|
+
description="Additional metadata",
|
423
|
+
optional=True,
|
424
|
+
),
|
425
|
+
},
|
426
|
+
)
|
427
|
+
|
428
|
+
model = schema_def.get_schema()
|
429
|
+
|
430
|
+
# Test valid complex structure
|
431
|
+
instance = model(
|
432
|
+
user={"name": "Test User", "email": "test@example.com", "age": 30}, # type: ignore
|
433
|
+
settings={"notifications": True, "darkMode": False}, # type: ignore
|
434
|
+
metadata={"created": "2023-01-01", "source": "API"}, # type: ignore
|
435
|
+
)
|
436
|
+
|
437
|
+
assert instance.user["name"] == "Test User" # type: ignore
|
438
|
+
assert instance.settings["notifications"] is True # type: ignore
|
439
|
+
assert instance.metadata["source"] == "API" # type: ignore
|
440
|
+
|
441
|
+
# Test with null metadata
|
442
|
+
instance = model(
|
443
|
+
user={"name": "Test User", "email": "test@example.com"}, # type: ignore
|
444
|
+
settings={"notifications": True}, # type: ignore
|
445
|
+
metadata=None, # type: ignore
|
446
|
+
)
|
447
|
+
# Test optional field with None value
|
448
|
+
assert instance.metadata is None # type: ignore
|
449
|
+
|
450
|
+
# Test with default empty settings
|
451
|
+
instance = model(
|
452
|
+
user={"name": "Test User", "email": "test@example.com"}, # type: ignore
|
453
|
+
metadata=None, # type: ignore
|
454
|
+
)
|
455
|
+
assert instance.settings == {} # type: ignore
|
456
|
+
|
457
|
+
|
458
|
+
if __name__ == "__main__":
|
459
|
+
test_basic_types()
|
460
|
+
test_generic_types()
|
461
|
+
test_string_constraints()
|
462
|
+
test_numeric_constraints()
|
463
|
+
test_collection_constraints()
|
464
|
+
test_default_values()
|
465
|
+
test_const_constraint()
|
466
|
+
test_enum_type()
|
467
|
+
test_enum_with_default()
|
@@ -1,120 +0,0 @@
|
|
1
|
-
"""Models for schema fields and definitions."""
|
2
|
-
|
3
|
-
from __future__ import annotations
|
4
|
-
|
5
|
-
from typing import Annotated, Any, Literal
|
6
|
-
|
7
|
-
from pydantic import BaseModel, Field, create_model
|
8
|
-
|
9
|
-
from schemez import Schema, helpers
|
10
|
-
|
11
|
-
|
12
|
-
class SchemaField(Schema):
|
13
|
-
"""Field definition for inline response types.
|
14
|
-
|
15
|
-
Defines a single field in an inline response definition, including:
|
16
|
-
- Data type specification
|
17
|
-
- Optional description
|
18
|
-
- Validation constraints
|
19
|
-
|
20
|
-
Used by InlineSchemaDef to structure response fields.
|
21
|
-
"""
|
22
|
-
|
23
|
-
type: str
|
24
|
-
"""Data type of the response field"""
|
25
|
-
|
26
|
-
description: str | None = None
|
27
|
-
"""Optional description of what this field represents"""
|
28
|
-
|
29
|
-
constraints: dict[str, Any] = Field(default_factory=dict)
|
30
|
-
"""Optional validation constraints for the field"""
|
31
|
-
|
32
|
-
|
33
|
-
class BaseSchemaDef(Schema):
|
34
|
-
"""Base class for response definitions."""
|
35
|
-
|
36
|
-
type: str = Field(init=False)
|
37
|
-
|
38
|
-
description: str | None = None
|
39
|
-
"""A description for this response definition."""
|
40
|
-
|
41
|
-
|
42
|
-
class InlineSchemaDef(BaseSchemaDef):
|
43
|
-
"""Inline definition of schema.
|
44
|
-
|
45
|
-
Allows defining response types directly in the configuration using:
|
46
|
-
- Field definitions with types and descriptions
|
47
|
-
- Optional validation constraints
|
48
|
-
- Custom field descriptions
|
49
|
-
|
50
|
-
Example:
|
51
|
-
schemas:
|
52
|
-
BasicResult:
|
53
|
-
type: inline
|
54
|
-
fields:
|
55
|
-
success: {type: bool, description: "Operation success"}
|
56
|
-
message: {type: str, description: "Result details"}
|
57
|
-
"""
|
58
|
-
|
59
|
-
type: Literal["inline"] = Field("inline", init=False)
|
60
|
-
"""Inline response definition."""
|
61
|
-
|
62
|
-
fields: dict[str, SchemaField]
|
63
|
-
"""A dictionary containing all fields."""
|
64
|
-
|
65
|
-
def get_schema(self) -> type[Schema]: # type: ignore
|
66
|
-
"""Create Pydantic model from inline definition."""
|
67
|
-
fields = {}
|
68
|
-
for name, field in self.fields.items():
|
69
|
-
python_type = helpers.resolve_type_string(field.type)
|
70
|
-
if not python_type:
|
71
|
-
msg = f"Unsupported field type: {field.type}"
|
72
|
-
raise ValueError(msg)
|
73
|
-
|
74
|
-
field_info = Field(description=field.description, **(field.constraints))
|
75
|
-
fields[name] = (python_type, field_info)
|
76
|
-
|
77
|
-
cls_name = self.description or "ResponseType"
|
78
|
-
return create_model(cls_name, **fields, __base__=Schema, __doc__=self.description) # type: ignore[call-overload]
|
79
|
-
|
80
|
-
|
81
|
-
class ImportedSchemaDef(BaseSchemaDef):
|
82
|
-
"""Response definition that imports an existing Pydantic model.
|
83
|
-
|
84
|
-
Allows using externally defined Pydantic models as response types.
|
85
|
-
Benefits:
|
86
|
-
- Reuse existing model definitions
|
87
|
-
- Full Python type support
|
88
|
-
- Complex validation logic
|
89
|
-
- IDE support for imported types
|
90
|
-
|
91
|
-
Example:
|
92
|
-
responses:
|
93
|
-
AnalysisResult:
|
94
|
-
type: import
|
95
|
-
import_path: myapp.models.AnalysisResult
|
96
|
-
"""
|
97
|
-
|
98
|
-
type: Literal["import"] = Field("import", init=False)
|
99
|
-
"""Import-path based response definition."""
|
100
|
-
|
101
|
-
import_path: str
|
102
|
-
"""The path to the pydantic model to use as the response type."""
|
103
|
-
|
104
|
-
# mypy is confused about "type"
|
105
|
-
# TODO: convert BaseModel to Schema?
|
106
|
-
def get_schema(self) -> type[BaseModel]: # type: ignore
|
107
|
-
"""Import and return the model class."""
|
108
|
-
try:
|
109
|
-
model_class = helpers.import_class(self.import_path)
|
110
|
-
if not issubclass(model_class, BaseModel):
|
111
|
-
msg = f"{self.import_path} must be a Pydantic model"
|
112
|
-
raise TypeError(msg) # noqa: TRY301
|
113
|
-
except Exception as e:
|
114
|
-
msg = f"Failed to import response type {self.import_path}"
|
115
|
-
raise ValueError(msg) from e
|
116
|
-
else:
|
117
|
-
return model_class
|
118
|
-
|
119
|
-
|
120
|
-
SchemaDef = Annotated[InlineSchemaDef | ImportedSchemaDef, Field(discriminator="type")]
|
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
|