pyrmute 0.1.0__py3-none-any.whl → 0.3.0__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.
pyrmute/__init__.py CHANGED
@@ -1,20 +1,47 @@
1
- """pyrmute - versioned Pydantic models and schemas with migrations.
2
-
3
- A package for managing versioned Pydantic models with automatic migrations
4
- and schema management.
5
- """
1
+ """pyrmute - versioned Pydantic models and schemas with migrations."""
6
2
 
3
+ from ._migration_manager import MigrationManager
4
+ from ._registry import Registry
5
+ from ._schema_manager import SchemaManager
7
6
  from ._version import __version__
7
+ from .exceptions import (
8
+ InvalidVersionError,
9
+ MigrationError,
10
+ ModelNotFoundError,
11
+ VersionedModelError,
12
+ )
13
+ from .migration_testing import (
14
+ MigrationTestCase,
15
+ MigrationTestResult,
16
+ MigrationTestResults,
17
+ )
18
+ from .model_diff import ModelDiff
8
19
  from .model_manager import ModelManager
9
20
  from .model_version import ModelVersion
10
- from .types import JsonSchema, MigrationData, MigrationFunc, ModelMetadata
21
+ from .types import (
22
+ JsonSchema,
23
+ MigrationFunc,
24
+ ModelData,
25
+ NestedModelInfo,
26
+ )
11
27
 
12
28
  __all__ = [
29
+ "InvalidVersionError",
13
30
  "JsonSchema",
14
- "MigrationData",
31
+ "MigrationError",
15
32
  "MigrationFunc",
33
+ "MigrationManager",
34
+ "MigrationTestCase",
35
+ "MigrationTestResult",
36
+ "MigrationTestResults",
37
+ "ModelData",
38
+ "ModelDiff",
16
39
  "ModelManager",
17
- "ModelMetadata",
40
+ "ModelNotFoundError",
18
41
  "ModelVersion",
42
+ "NestedModelInfo",
43
+ "Registry",
44
+ "SchemaManager",
45
+ "VersionedModelError",
19
46
  "__version__",
20
47
  ]
@@ -1,24 +1,27 @@
1
1
  """Migrations manager."""
2
2
 
3
+ import contextlib
3
4
  from collections.abc import Callable
4
5
  from typing import Any, Self, get_args, get_origin
5
6
 
6
7
  from pydantic import BaseModel
7
8
  from pydantic.fields import FieldInfo
9
+ from pydantic_core import PydanticUndefined
8
10
 
9
11
  from ._registry import Registry
12
+ from .exceptions import MigrationError, ModelNotFoundError
10
13
  from .model_version import ModelVersion
11
- from .types import MigrationData, MigrationFunc, ModelName
14
+ from .types import MigrationFunc, ModelData, ModelName
12
15
 
13
16
 
14
17
  class MigrationManager:
15
18
  """Manager for data migrations between model versions.
16
19
 
17
- Handles registration and execution of migration functions, including
18
- support for nested Pydantic models.
20
+ Handles registration and execution of migration functions, including support for
21
+ nested Pydantic models.
19
22
 
20
23
  Attributes:
21
- registry: Reference to the VersionedModelRegistry.
24
+ registry: Reference to the Registry.
22
25
  """
23
26
 
24
27
  def __init__(self: Self, registry: Registry) -> None:
@@ -70,11 +73,11 @@ class MigrationManager:
70
73
 
71
74
  def migrate(
72
75
  self: Self,
73
- data: MigrationData,
76
+ data: ModelData,
74
77
  name: ModelName,
75
78
  from_version: str | ModelVersion,
76
79
  to_version: str | ModelVersion,
77
- ) -> MigrationData:
80
+ ) -> ModelData:
78
81
  """Migrate data from one version to another.
79
82
 
80
83
  Args:
@@ -87,7 +90,8 @@ class MigrationManager:
87
90
  Migrated data dictionary.
88
91
 
89
92
  Raises:
90
- ValueError: If migration path cannot be found.
93
+ ModelNotFoundError: If model or versions don't exist.
94
+ MigrationError: If migration path cannot be found.
91
95
  """
92
96
  from_ver = (
93
97
  ModelVersion.parse(from_version)
@@ -103,7 +107,7 @@ class MigrationManager:
103
107
  if from_ver == to_ver:
104
108
  return data
105
109
 
106
- path = self._find_migration_path(name, from_ver, to_ver)
110
+ path = self.find_migration_path(name, from_ver, to_ver)
107
111
 
108
112
  current_data = data
109
113
  for i in range(len(path) - 1):
@@ -111,15 +115,41 @@ class MigrationManager:
111
115
 
112
116
  if migration_key in self.registry._migrations[name]:
113
117
  migration_func = self.registry._migrations[name][migration_key]
114
- current_data = migration_func(current_data)
118
+ try:
119
+ current_data = migration_func(current_data)
120
+ except Exception as e:
121
+ raise MigrationError(
122
+ name,
123
+ str(path[i]),
124
+ str(path[i + 1]),
125
+ f"Migration function raised: {type(e).__name__}: {e}",
126
+ ) from e
127
+ elif path[i + 1] in self.registry._backward_compatible_enabled[name]:
128
+ try:
129
+ current_data = self._auto_migrate(
130
+ current_data, name, path[i], path[i + 1]
131
+ )
132
+ except Exception as e:
133
+ raise MigrationError(
134
+ name,
135
+ str(path[i]),
136
+ str(path[i + 1]),
137
+ f"Auto-migration failed: {type(e).__name__}: {e}",
138
+ ) from e
115
139
  else:
116
- current_data = self._auto_migrate(
117
- current_data, name, path[i], path[i + 1]
140
+ raise MigrationError(
141
+ name,
142
+ str(path[i]),
143
+ str(path[i + 1]),
144
+ (
145
+ "No migration path found. Define a migration function or mark "
146
+ "the target version as backward_compatible."
147
+ ),
118
148
  )
119
149
 
120
150
  return current_data
121
151
 
122
- def _find_migration_path(
152
+ def find_migration_path(
123
153
  self: Self,
124
154
  name: ModelName,
125
155
  from_ver: ModelVersion,
@@ -134,11 +164,16 @@ class MigrationManager:
134
164
 
135
165
  Returns:
136
166
  List of versions forming the migration path.
167
+
168
+ Raises:
169
+ ModelNotFoundError: If the model or versions don't exist.
137
170
  """
138
171
  versions = sorted(self.registry.get_versions(name))
139
172
 
140
- if from_ver not in versions or to_ver not in versions:
141
- raise ValueError(f"Invalid version range for {name}")
173
+ if from_ver not in versions:
174
+ raise ModelNotFoundError(name, str(from_ver))
175
+ if to_ver not in versions:
176
+ raise ModelNotFoundError(name, str(to_ver))
142
177
 
143
178
  from_idx = versions.index(from_ver)
144
179
  to_idx = versions.index(to_ver)
@@ -147,17 +182,57 @@ class MigrationManager:
147
182
  return versions[from_idx : to_idx + 1]
148
183
  return versions[to_idx : from_idx + 1][::-1]
149
184
 
185
+ def validate_migration_path(
186
+ self: Self,
187
+ name: ModelName,
188
+ from_ver: ModelVersion,
189
+ to_ver: ModelVersion,
190
+ ) -> None:
191
+ """Validate that a migration path exists and all steps are valid.
192
+
193
+ Args:
194
+ name: Name of the model.
195
+ from_ver: Source version.
196
+ to_ver: Target version.
197
+
198
+ Raises:
199
+ ModelNotFoundError: If the model or versions don't exist.
200
+ MigrationError: If any step in the migration path is invalid.
201
+ """
202
+ path = self.find_migration_path(name, from_ver, to_ver)
203
+
204
+ for i in range(len(path) - 1):
205
+ current_ver = path[i]
206
+ next_ver = path[i + 1]
207
+ migration_key = (current_ver, next_ver)
208
+
209
+ has_explicit = migration_key in self.registry._migrations.get(name, {})
210
+ has_auto = next_ver in self.registry._backward_compatible_enabled.get(
211
+ name, set()
212
+ )
213
+
214
+ if not has_explicit and not has_auto:
215
+ raise MigrationError(
216
+ name,
217
+ str(current_ver),
218
+ str(next_ver),
219
+ (
220
+ "No migration path found. Define a migration function or mark "
221
+ "the target version as backward_compatible."
222
+ ),
223
+ )
224
+
150
225
  def _auto_migrate(
151
226
  self: Self,
152
- data: MigrationData,
227
+ data: ModelData,
153
228
  name: ModelName,
154
229
  from_ver: ModelVersion,
155
230
  to_ver: ModelVersion,
156
- ) -> MigrationData:
231
+ ) -> ModelData:
157
232
  """Automatically migrate data when no explicit migration exists.
158
233
 
159
- This method handles nested Pydantic models recursively, migrating
160
- them to their corresponding versions.
234
+ This method handles nested Pydantic models recursively, migrating them to their
235
+ corresponding versions.
161
236
 
162
237
  Args:
163
238
  data: Data dictionary to migrate.
@@ -174,21 +249,28 @@ class MigrationManager:
174
249
  from_fields = from_model.model_fields
175
250
  to_fields = to_model.model_fields
176
251
 
177
- result: MigrationData = {}
252
+ result: ModelData = {}
178
253
 
179
254
  for field_name, to_field_info in to_fields.items():
180
- if field_name not in data:
181
- continue
182
-
183
- value = data[field_name]
255
+ # Field exists in data, migrate it
256
+ if field_name in data:
257
+ value = data[field_name]
258
+ from_field_info = from_fields.get(field_name)
259
+ result[field_name] = self._migrate_field_value(
260
+ value, from_field_info, to_field_info
261
+ )
184
262
 
185
- # Get corresponding from_field if it exists
186
- from_field_info = from_fields.get(field_name)
263
+ # Field missing from data, use default if available
264
+ elif to_field_info.default is not PydanticUndefined:
265
+ result[field_name] = to_field_info.default
266
+ elif to_field_info.default_factory is not None:
267
+ with contextlib.suppress(Exception):
268
+ result[field_name] = to_field_info.default_factory() # type: ignore
187
269
 
188
- # Migrate the field value (handles nested models)
189
- result[field_name] = self._migrate_field_value(
190
- value, from_field_info, to_field_info
191
- )
270
+ # Migrate all extra data not in the field, too
271
+ for field_name, value in data.items():
272
+ if field_name not in to_fields:
273
+ result[field_name] = value
192
274
 
193
275
  return result
194
276
 
@@ -211,20 +293,17 @@ class MigrationManager:
211
293
  if value is None:
212
294
  return None
213
295
 
214
- # Check if this is a nested Pydantic model
215
296
  if isinstance(value, dict):
216
297
  nested_info = self._extract_nested_model_info(value, from_field, to_field)
217
298
  if nested_info:
218
299
  nested_name, nested_from_ver, nested_to_ver = nested_info
219
300
  return self.migrate(value, nested_name, nested_from_ver, nested_to_ver)
220
301
 
221
- # Try to recursively migrate dict values
222
302
  return {
223
303
  k: self._migrate_field_value(v, from_field, to_field)
224
304
  for k, v in value.items()
225
305
  }
226
306
 
227
- # Handle lists
228
307
  if isinstance(value, list):
229
308
  return [
230
309
  self._migrate_field_value(item, from_field, to_field) for item in value
@@ -234,7 +313,7 @@ class MigrationManager:
234
313
 
235
314
  def _extract_nested_model_info(
236
315
  self: Self,
237
- value: MigrationData,
316
+ value: ModelData,
238
317
  from_field: FieldInfo | None,
239
318
  to_field: FieldInfo,
240
319
  ) -> tuple[ModelName, ModelVersion, ModelVersion] | None:
@@ -249,12 +328,10 @@ class MigrationManager:
249
328
  Tuple of (model_name, from_version, to_version) if this is a
250
329
  versioned nested model, None otherwise.
251
330
  """
252
- # Get the target model type
253
331
  to_model_type = self._get_model_type_from_field(to_field)
254
332
  if not to_model_type or not issubclass(to_model_type, BaseModel):
255
333
  return None
256
334
 
257
- # Check if target model is registered
258
335
  to_info = self.registry.get_model_info(to_model_type)
259
336
  if not to_info:
260
337
  return None
@@ -291,11 +368,9 @@ class MigrationManager:
291
368
  if annotation is None:
292
369
  return None
293
370
 
294
- # Handle direct model types
295
371
  if isinstance(annotation, type) and issubclass(annotation, BaseModel):
296
372
  return annotation
297
373
 
298
- # Handle Optional, List, etc.
299
374
  origin = get_origin(annotation)
300
375
  if origin is not None:
301
376
  args = get_args(annotation)
pyrmute/_registry.py CHANGED
@@ -6,6 +6,7 @@ from typing import Self
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
+ from .exceptions import ModelNotFoundError
9
10
  from .model_version import ModelVersion
10
11
  from .types import (
11
12
  DecoratedBaseModel,
@@ -21,8 +22,8 @@ from .types import (
21
22
  class Registry:
22
23
  """Registry for versioned Pydantic models.
23
24
 
24
- Manages the registration and retrieval of versioned models and their
25
- associated metadata.
25
+ Manages the registration and retrieval of versioned models and their associated
26
+ metadata.
26
27
 
27
28
  Attributes:
28
29
  _models: Dictionary mapping model names to version-model mappings.
@@ -39,6 +40,9 @@ class Registry:
39
40
  self._schema_generators: dict[ModelName, SchemaGenerators] = defaultdict(dict)
40
41
  self._model_metadata: dict[type[BaseModel], ModelMetadata] = {}
41
42
  self._ref_enabled: dict[ModelName, set[ModelVersion]] = defaultdict(set)
43
+ self._backward_compatible_enabled: dict[ModelName, set[ModelVersion]] = (
44
+ defaultdict(set)
45
+ )
42
46
 
43
47
  def register(
44
48
  self: Self,
@@ -46,6 +50,7 @@ class Registry:
46
50
  version: str | ModelVersion,
47
51
  schema_generator: JsonSchemaGenerator | None = None,
48
52
  enable_ref: bool = False,
53
+ backward_compatible: bool = False,
49
54
  ) -> Callable[[type[DecoratedBaseModel]], type[DecoratedBaseModel]]:
50
55
  """Register a versioned model.
51
56
 
@@ -53,8 +58,11 @@ class Registry:
53
58
  name: Name of the model.
54
59
  version: Semantic version string or ModelVersion instance.
55
60
  schema_generator: Optional custom schema generator function.
56
- enable_ref: If True, this model can be referenced via $ref in
57
- separate schema files. If False, it will always be inlined.
61
+ enable_ref: If True, this model can be referenced via $ref in separate
62
+ schema files. If False, it will always be inlined.
63
+ backward_compatible: If True, this model does not need a migration function
64
+ to migrate to the next version. If a migration function is defined it
65
+ will use it.
58
66
 
59
67
  Returns:
60
68
  Decorator function for model class.
@@ -74,6 +82,8 @@ class Registry:
74
82
  self._schema_generators[name][ver] = schema_generator
75
83
  if enable_ref:
76
84
  self._ref_enabled[name].add(ver)
85
+ if backward_compatible:
86
+ self._backward_compatible_enabled[name].add(ver)
77
87
  return cls
78
88
 
79
89
  return decorator
@@ -91,12 +101,15 @@ class Registry:
91
101
  Model class for the specified version.
92
102
 
93
103
  Raises:
94
- ValueError: If model or version not found.
104
+ ModelNotFoundError: If model or version not found.
95
105
  """
96
106
  ver = ModelVersion.parse(version) if isinstance(version, str) else version
97
107
 
98
- if name not in self._models or ver not in self._models[name]:
99
- raise ValueError(f"Model {name} v{ver} not found")
108
+ if name not in self._models:
109
+ raise ModelNotFoundError(name)
110
+
111
+ if ver not in self._models[name]:
112
+ raise ModelNotFoundError(name, str(ver))
100
113
 
101
114
  return self._models[name][ver]
102
115
 
@@ -110,10 +123,10 @@ class Registry:
110
123
  Latest version of the model class.
111
124
 
112
125
  Raises:
113
- ValueError: If model not found.
126
+ ModelNotFoundError: If model not found.
114
127
  """
115
128
  if name not in self._models:
116
- raise ValueError(f"Model {name} not found")
129
+ raise ModelNotFoundError(name)
117
130
 
118
131
  latest_version = max(self._models[name].keys())
119
132
  return self._models[name][latest_version]
@@ -128,10 +141,10 @@ class Registry:
128
141
  Sorted list of available versions.
129
142
 
130
143
  Raises:
131
- ValueError: If model not found.
144
+ ModelNotFoundError: If model not found.
132
145
  """
133
146
  if name not in self._models:
134
- raise ValueError(f"Model {name} not found")
147
+ raise ModelNotFoundError(name)
135
148
 
136
149
  return sorted(self._models[name].keys())
137
150
 
@@ -8,6 +8,7 @@ from pydantic import BaseModel
8
8
  from pydantic.fields import FieldInfo
9
9
 
10
10
  from ._registry import Registry
11
+ from .exceptions import ModelNotFoundError
11
12
  from .model_version import ModelVersion
12
13
  from .types import (
13
14
  JsonSchema,
@@ -15,14 +16,15 @@ from .types import (
15
16
  JsonValue,
16
17
  ModelMetadata,
17
18
  ModelName,
19
+ NestedModelInfo,
18
20
  )
19
21
 
20
22
 
21
23
  class SchemaManager:
22
24
  """Manager for JSON schema generation and export.
23
25
 
24
- Handles schema generation from Pydantic models with support for
25
- custom schema generators and sub-schema references.
26
+ Handles schema generation from Pydantic models with support for custom schema
27
+ generators and sub-schema references.
26
28
 
27
29
  Attributes:
28
30
  registry: Reference to the Registry.
@@ -73,14 +75,14 @@ class SchemaManager:
73
75
  ) -> JsonSchema:
74
76
  """Get JSON schema with separate definition files for nested models.
75
77
 
76
- This creates a schema where nested Pydantic models are referenced
77
- as external JSON schema files rather than inline definitions.
78
+ This creates a schema where nested Pydantic models are referenced as external
79
+ JSON schema files rather than inline definitions.
78
80
 
79
81
  Args:
80
82
  name: Name of the model.
81
83
  version: Semantic version.
82
- ref_template: Template for generating $ref URLs. Supports {model}
83
- and {version} placeholders.
84
+ ref_template: Template for generating $ref URLs. Supports {model} and
85
+ {version} placeholders.
84
86
  **schema_kwargs: Additional arguments for schema generation.
85
87
 
86
88
  Returns:
@@ -93,8 +95,6 @@ class SchemaManager:
93
95
  ... )
94
96
  """
95
97
  ver = ModelVersion.parse(version) if isinstance(version, str) else version
96
-
97
- # Get the base schema with definitions
98
98
  schema = self.get_schema(name, ver, **schema_kwargs)
99
99
 
100
100
  # Extract and replace definitions with external references
@@ -136,16 +136,15 @@ class SchemaManager:
136
136
  if "$ref" in value:
137
137
  # Extract the definition name from the ref
138
138
  ref = value["$ref"]
139
- if ref.startswith(("#/$defs/", "#/definitions/")):
139
+ if isinstance(ref, str) and ref.startswith(
140
+ ("#/$defs/", "#/definitions/")
141
+ ):
140
142
  def_name = ref.split("/")[-1]
141
143
 
142
- # Try to find the model info for this definition
143
144
  model_info = self._find_model_for_definition(def_name)
144
-
145
145
  if model_info:
146
146
  model_name, model_version = model_info
147
147
 
148
- # Check if this model is enabled for $ref
149
148
  if self.registry.is_ref_enabled(model_name, model_version):
150
149
  # Replace with external reference
151
150
  return {
@@ -156,7 +155,6 @@ class SchemaManager:
156
155
  # Keep as internal reference (will be inlined)
157
156
  return value
158
157
 
159
- # Recursively process nested dictionaries
160
158
  return {k: process_value(v) for k, v in value.items()}
161
159
  if isinstance(value, list):
162
160
  return [process_value(item) for item in value]
@@ -178,7 +176,6 @@ class SchemaManager:
178
176
  Returns:
179
177
  Dictionary of definitions that weren't converted to external refs.
180
178
  """
181
- # Find all internal refs still in the schema
182
179
  internal_refs: set[str] = set()
183
180
 
184
181
  def find_internal_refs(value: dict[str, Any] | list[Any]) -> None:
@@ -195,8 +192,6 @@ class SchemaManager:
195
192
  find_internal_refs(item)
196
193
 
197
194
  find_internal_refs(schema)
198
-
199
- # Return only definitions that are still referenced internally
200
195
  return {k: v for k, v in original_defs.items() if k in internal_refs}
201
196
 
202
197
  def _find_model_for_definition(self: Self, def_name: str) -> ModelMetadata | None:
@@ -208,7 +203,6 @@ class SchemaManager:
208
203
  Returns:
209
204
  Tuple of (model_name, version) if found, None otherwise.
210
205
  """
211
- # Search through all registered models to find matching class name
212
206
  for name, versions in self.registry._models.items():
213
207
  for version, model_class in versions.items():
214
208
  if model_class.__name__ == def_name:
@@ -225,10 +219,10 @@ class SchemaManager:
225
219
  Dictionary mapping versions to their schemas.
226
220
 
227
221
  Raises:
228
- ValueError: If model not found.
222
+ ModelNotFoundError: If model not found.
229
223
  """
230
224
  if name not in self.registry._models:
231
- raise ValueError(f"Model {name} not found")
225
+ raise ModelNotFoundError(name)
232
226
 
233
227
  return {
234
228
  version: self.get_schema(name, version)
@@ -247,8 +241,8 @@ class SchemaManager:
247
241
  Args:
248
242
  output_dir: Directory path for output files.
249
243
  indent: JSON indentation level.
250
- separate_definitions: If True, create separate schema files for
251
- nested models that have enable_ref=True.
244
+ separate_definitions: If True, create separate schema files for nested
245
+ models that have enable_ref=True.
252
246
  ref_template: Template for $ref URLs when separate_definitions=True.
253
247
  Defaults to relative file references if not provided.
254
248
 
@@ -270,19 +264,15 @@ class SchemaManager:
270
264
  output_path.mkdir(parents=True, exist_ok=True)
271
265
 
272
266
  if not separate_definitions:
273
- # Original behavior: inline definitions
274
267
  for name in self.registry._models:
275
268
  for version, schema in self.get_all_schemas(name).items():
276
269
  file_path = output_path / f"{name}_v{version}.json"
277
270
  with open(file_path, "w", encoding="utf-8") as f:
278
271
  json.dump(schema, f, indent=indent)
279
272
  else:
280
- # New behavior: separate definition files for enable_ref=True models
281
- # Default to relative file references
282
273
  if ref_template is None:
283
274
  ref_template = "{model}_v{version}.json"
284
275
 
285
- # First pass: write all schemas
286
276
  for name in self.registry._models:
287
277
  for version in self.registry._models[name]:
288
278
  schema = self.get_schema_with_separate_defs(
@@ -296,7 +286,7 @@ class SchemaManager:
296
286
  self: Self,
297
287
  name: ModelName,
298
288
  version: str | ModelVersion,
299
- ) -> list[ModelMetadata]:
289
+ ) -> list[NestedModelInfo]:
300
290
  """Get all nested models referenced by a model.
301
291
 
302
292
  Args:
@@ -304,19 +294,28 @@ class SchemaManager:
304
294
  version: Semantic version.
305
295
 
306
296
  Returns:
307
- List of (model_name, model_version) tuples for nested models.
297
+ List of NestedModelInfo.
308
298
  """
309
299
  ver = ModelVersion.parse(version) if isinstance(version, str) else version
310
300
  model = self.registry.get_model(name, ver)
311
301
 
312
- nested: list[ModelMetadata] = []
302
+ nested: list[NestedModelInfo] = []
313
303
 
314
304
  for field_info in model.model_fields.values():
315
305
  model_type = self._get_model_type_from_field(field_info)
316
- if model_type:
317
- model_info = self.registry.get_model_info(model_type)
318
- if model_info and model_info not in nested:
319
- nested.append(model_info)
306
+ if not model_type:
307
+ continue
308
+
309
+ model_info = self.registry.get_model_info(model_type)
310
+
311
+ if not model_info:
312
+ continue
313
+
314
+ name_, version_ = model_info
315
+ nested_model_info = NestedModelInfo(name=name_, version=version_)
316
+
317
+ if nested_model_info not in nested:
318
+ nested.append(nested_model_info)
320
319
 
321
320
  return nested
322
321
 
@@ -335,11 +334,9 @@ class SchemaManager:
335
334
  if annotation is None:
336
335
  return None
337
336
 
338
- # Handle direct model types
339
337
  if isinstance(annotation, type) and issubclass(annotation, BaseModel):
340
338
  return annotation
341
339
 
342
- # Handle Optional, List, etc.
343
340
  origin = get_origin(annotation)
344
341
  if origin is not None:
345
342
  args = get_args(annotation)
pyrmute/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.0'
32
- __version_tuple__ = version_tuple = (0, 1, 0)
31
+ __version__ = version = '0.3.0'
32
+ __version_tuple__ = version_tuple = (0, 3, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None