pyrmute 0.2.0__py3-none-any.whl → 0.4.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 +13 -11
- pyrmute/_migration_manager.py +517 -132
- pyrmute/_schema_manager.py +17 -7
- pyrmute/_version.py +2 -2
- pyrmute/migration_testing.py +8 -5
- pyrmute/model_manager.py +46 -66
- pyrmute/types.py +11 -4
- {pyrmute-0.2.0.dist-info → pyrmute-0.4.0.dist-info}/METADATA +229 -70
- pyrmute-0.4.0.dist-info/RECORD +17 -0
- pyrmute-0.2.0.dist-info/RECORD +0 -17
- {pyrmute-0.2.0.dist-info → pyrmute-0.4.0.dist-info}/WHEEL +0 -0
- {pyrmute-0.2.0.dist-info → pyrmute-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {pyrmute-0.2.0.dist-info → pyrmute-0.4.0.dist-info}/top_level.txt +0 -0
pyrmute/_schema_manager.py
CHANGED
@@ -16,6 +16,7 @@ from .types import (
|
|
16
16
|
JsonValue,
|
17
17
|
ModelMetadata,
|
18
18
|
ModelName,
|
19
|
+
NestedModelInfo,
|
19
20
|
)
|
20
21
|
|
21
22
|
|
@@ -285,7 +286,7 @@ class SchemaManager:
|
|
285
286
|
self: Self,
|
286
287
|
name: ModelName,
|
287
288
|
version: str | ModelVersion,
|
288
|
-
) -> list[
|
289
|
+
) -> list[NestedModelInfo]:
|
289
290
|
"""Get all nested models referenced by a model.
|
290
291
|
|
291
292
|
Args:
|
@@ -293,19 +294,28 @@ class SchemaManager:
|
|
293
294
|
version: Semantic version.
|
294
295
|
|
295
296
|
Returns:
|
296
|
-
List of
|
297
|
+
List of NestedModelInfo.
|
297
298
|
"""
|
298
299
|
ver = ModelVersion.parse(version) if isinstance(version, str) else version
|
299
300
|
model = self.registry.get_model(name, ver)
|
300
301
|
|
301
|
-
nested: list[
|
302
|
+
nested: list[NestedModelInfo] = []
|
302
303
|
|
303
304
|
for field_info in model.model_fields.values():
|
304
305
|
model_type = self._get_model_type_from_field(field_info)
|
305
|
-
if model_type:
|
306
|
-
|
307
|
-
|
308
|
-
|
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)
|
309
319
|
|
310
320
|
return nested
|
311
321
|
|
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.
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
31
|
+
__version__ = version = '0.4.0'
|
32
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
pyrmute/migration_testing.py
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
from collections.abc import Iterator
|
4
4
|
from dataclasses import dataclass
|
5
|
-
from typing import Self
|
5
|
+
from typing import Self, TypeAlias
|
6
6
|
|
7
|
-
from .types import
|
7
|
+
from .types import ModelData
|
8
8
|
|
9
9
|
|
10
10
|
@dataclass
|
@@ -28,8 +28,8 @@ class MigrationTestCase:
|
|
28
28
|
... )
|
29
29
|
"""
|
30
30
|
|
31
|
-
source:
|
32
|
-
target:
|
31
|
+
source: ModelData
|
32
|
+
target: ModelData | None = None
|
33
33
|
description: str = ""
|
34
34
|
|
35
35
|
|
@@ -55,7 +55,7 @@ class MigrationTestResult:
|
|
55
55
|
"""
|
56
56
|
|
57
57
|
test_case: MigrationTestCase
|
58
|
-
actual:
|
58
|
+
actual: ModelData
|
59
59
|
passed: bool
|
60
60
|
error: str | None = None
|
61
61
|
|
@@ -159,3 +159,6 @@ class MigrationTestResults:
|
|
159
159
|
f"✗ {len(self.failures)} of {total_count} test(s) failed "
|
160
160
|
f"({passed_count} passed)"
|
161
161
|
)
|
162
|
+
|
163
|
+
|
164
|
+
MigrationTestCases: TypeAlias = list[tuple[ModelData, ModelData] | MigrationTestCase]
|
pyrmute/model_manager.py
CHANGED
@@ -13,6 +13,7 @@ from ._schema_manager import SchemaManager
|
|
13
13
|
from .exceptions import MigrationError, ModelNotFoundError
|
14
14
|
from .migration_testing import (
|
15
15
|
MigrationTestCase,
|
16
|
+
MigrationTestCases,
|
16
17
|
MigrationTestResult,
|
17
18
|
MigrationTestResults,
|
18
19
|
)
|
@@ -22,9 +23,9 @@ from .types import (
|
|
22
23
|
DecoratedBaseModel,
|
23
24
|
JsonSchema,
|
24
25
|
JsonSchemaGenerator,
|
25
|
-
MigrationData,
|
26
26
|
MigrationFunc,
|
27
|
-
|
27
|
+
ModelData,
|
28
|
+
NestedModelInfo,
|
28
29
|
)
|
29
30
|
|
30
31
|
|
@@ -55,7 +56,7 @@ class ModelManager:
|
|
55
56
|
>>>
|
56
57
|
>>> # Define migration between versions
|
57
58
|
>>> @manager.migration("User", "1.0.0", "2.0.0")
|
58
|
-
... def migrate(data:
|
59
|
+
... def migrate(data: ModelData) -> ModelData:
|
59
60
|
... return {**data, "email": "unknown@example.com"}
|
60
61
|
>>>
|
61
62
|
>>> # Migrate legacy data
|
@@ -91,9 +92,9 @@ class ModelManager:
|
|
91
92
|
|
92
93
|
def __init__(self: Self) -> None:
|
93
94
|
"""Initialize the versioned model manager."""
|
94
|
-
self.
|
95
|
-
self.
|
96
|
-
self.
|
95
|
+
self._registry = Registry()
|
96
|
+
self._migration_manager = MigrationManager(self._registry)
|
97
|
+
self._schema_manager = SchemaManager(self._registry)
|
97
98
|
|
98
99
|
def model(
|
99
100
|
self: Self,
|
@@ -129,7 +130,7 @@ class ModelManager:
|
|
129
130
|
... class CityV1(BaseModel):
|
130
131
|
... city: City
|
131
132
|
"""
|
132
|
-
return self.
|
133
|
+
return self._registry.register(
|
133
134
|
name, version, schema_generator, enable_ref, backward_compatible
|
134
135
|
)
|
135
136
|
|
@@ -149,11 +150,11 @@ class ModelManager:
|
|
149
150
|
Returns:
|
150
151
|
Decorator function for migration function.
|
151
152
|
"""
|
152
|
-
return self.
|
153
|
+
return self._migration_manager.register_migration(
|
154
|
+
name, from_version, to_version
|
155
|
+
)
|
153
156
|
|
154
|
-
def get(
|
155
|
-
self: Self, name: str, version: str | ModelVersion | None = None
|
156
|
-
) -> type[BaseModel]:
|
157
|
+
def get(self: Self, name: str, version: str | ModelVersion) -> type[BaseModel]:
|
157
158
|
"""Get a model by name and version.
|
158
159
|
|
159
160
|
Args:
|
@@ -163,9 +164,18 @@ class ModelManager:
|
|
163
164
|
Returns:
|
164
165
|
Model class.
|
165
166
|
"""
|
166
|
-
|
167
|
-
|
168
|
-
|
167
|
+
return self._registry.get_model(name, version)
|
168
|
+
|
169
|
+
def get_latest(self: Self, name: str) -> type[BaseModel]:
|
170
|
+
"""Get the latest version of a model by name.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
name: Name of the model.
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
Model class.
|
177
|
+
"""
|
178
|
+
return self._registry.get_latest(name)
|
169
179
|
|
170
180
|
def has_migration_path(
|
171
181
|
self: Self,
|
@@ -200,14 +210,14 @@ class ModelManager:
|
|
200
210
|
else to_version
|
201
211
|
)
|
202
212
|
try:
|
203
|
-
self.
|
213
|
+
self._migration_manager.validate_migration_path(name, from_ver, to_ver)
|
204
214
|
return True
|
205
215
|
except (KeyError, ModelNotFoundError, MigrationError):
|
206
216
|
return False
|
207
217
|
|
208
218
|
def validate_data(
|
209
219
|
self: Self,
|
210
|
-
data:
|
220
|
+
data: ModelData,
|
211
221
|
name: str,
|
212
222
|
version: str | ModelVersion,
|
213
223
|
) -> bool:
|
@@ -241,7 +251,7 @@ class ModelManager:
|
|
241
251
|
|
242
252
|
def migrate(
|
243
253
|
self: Self,
|
244
|
-
data:
|
254
|
+
data: ModelData,
|
245
255
|
name: str,
|
246
256
|
from_version: str | ModelVersion,
|
247
257
|
to_version: str | ModelVersion,
|
@@ -263,11 +273,11 @@ class ModelManager:
|
|
263
273
|
|
264
274
|
def migrate_data(
|
265
275
|
self: Self,
|
266
|
-
data:
|
276
|
+
data: ModelData,
|
267
277
|
name: str,
|
268
278
|
from_version: str | ModelVersion,
|
269
279
|
to_version: str | ModelVersion,
|
270
|
-
) ->
|
280
|
+
) -> ModelData:
|
271
281
|
"""Migrate data between versions.
|
272
282
|
|
273
283
|
Args:
|
@@ -279,11 +289,11 @@ class ModelManager:
|
|
279
289
|
Returns:
|
280
290
|
Raw migrated dictionary.
|
281
291
|
"""
|
282
|
-
return self.
|
292
|
+
return self._migration_manager.migrate(data, name, from_version, to_version)
|
283
293
|
|
284
294
|
def migrate_batch( # noqa: PLR0913
|
285
295
|
self: Self,
|
286
|
-
data_list: Iterable[
|
296
|
+
data_list: Iterable[ModelData],
|
287
297
|
name: str,
|
288
298
|
from_version: str | ModelVersion,
|
289
299
|
to_version: str | ModelVersion,
|
@@ -341,14 +351,14 @@ class ModelManager:
|
|
341
351
|
|
342
352
|
def migrate_batch_data( # noqa: PLR0913
|
343
353
|
self: Self,
|
344
|
-
data_list: Iterable[
|
354
|
+
data_list: Iterable[ModelData],
|
345
355
|
name: str,
|
346
356
|
from_version: str | ModelVersion,
|
347
357
|
to_version: str | ModelVersion,
|
348
358
|
parallel: bool = False,
|
349
359
|
max_workers: int | None = None,
|
350
360
|
use_processes: bool = False,
|
351
|
-
) -> list[
|
361
|
+
) -> list[ModelData]:
|
352
362
|
"""Migrate multiple data items between versions, returning raw dictionaries.
|
353
363
|
|
354
364
|
Args:
|
@@ -393,7 +403,7 @@ class ModelManager:
|
|
393
403
|
|
394
404
|
def migrate_batch_streaming(
|
395
405
|
self: Self,
|
396
|
-
data_list: Iterable[
|
406
|
+
data_list: Iterable[ModelData],
|
397
407
|
name: str,
|
398
408
|
from_version: str | ModelVersion,
|
399
409
|
to_version: str | ModelVersion,
|
@@ -439,12 +449,12 @@ class ModelManager:
|
|
439
449
|
|
440
450
|
def migrate_batch_data_streaming(
|
441
451
|
self: Self,
|
442
|
-
data_list: Iterable[
|
452
|
+
data_list: Iterable[ModelData],
|
443
453
|
name: str,
|
444
454
|
from_version: str | ModelVersion,
|
445
455
|
to_version: str | ModelVersion,
|
446
456
|
chunk_size: int = 100,
|
447
|
-
) -> Iterable[
|
457
|
+
) -> Iterable[ModelData]:
|
448
458
|
"""Migrate data in chunks, yielding raw dictionaries as they complete.
|
449
459
|
|
450
460
|
Useful for large datasets where you want to start processing results before all
|
@@ -548,7 +558,7 @@ class ModelManager:
|
|
548
558
|
Returns:
|
549
559
|
JSON schema dictionary.
|
550
560
|
"""
|
551
|
-
return self.
|
561
|
+
return self._schema_manager.get_schema(name, version, **kwargs)
|
552
562
|
|
553
563
|
def list_models(self: Self) -> list[str]:
|
554
564
|
"""Get list of all registered models.
|
@@ -556,7 +566,7 @@ class ModelManager:
|
|
556
566
|
Returns:
|
557
567
|
List of model names.
|
558
568
|
"""
|
559
|
-
return self.
|
569
|
+
return self._registry.list_models()
|
560
570
|
|
561
571
|
def list_versions(self: Self, name: str) -> list[ModelVersion]:
|
562
572
|
"""Get all versions for a model.
|
@@ -567,7 +577,7 @@ class ModelManager:
|
|
567
577
|
Returns:
|
568
578
|
Sorted list of versions.
|
569
579
|
"""
|
570
|
-
return self.
|
580
|
+
return self._registry.get_versions(name)
|
571
581
|
|
572
582
|
def dump_schemas(
|
573
583
|
self: Self,
|
@@ -582,7 +592,8 @@ class ModelManager:
|
|
582
592
|
output_dir: Directory path for output.
|
583
593
|
indent: JSON indentation level.
|
584
594
|
separate_definitions: If True, create separate schema files for nested
|
585
|
-
models and use $ref to reference them.
|
595
|
+
models and use $ref to reference them. Only applies to models with
|
596
|
+
'enable_ref=True'.
|
586
597
|
ref_template: Template for $ref URLs when separate_definitions=True.
|
587
598
|
Defaults to relative file references if not provided.
|
588
599
|
|
@@ -600,46 +611,15 @@ class ModelManager:
|
|
600
611
|
... ref_template="https://example.com/schemas/{model}_v{version}.json"
|
601
612
|
... )
|
602
613
|
"""
|
603
|
-
self.
|
614
|
+
self._schema_manager.dump_schemas(
|
604
615
|
output_dir, indent, separate_definitions, ref_template
|
605
616
|
)
|
606
617
|
|
607
|
-
def dump_schemas_with_refs(
|
608
|
-
self: Self,
|
609
|
-
output_dir: str | Path,
|
610
|
-
ref_template: str | None = None,
|
611
|
-
indent: int = 2,
|
612
|
-
) -> None:
|
613
|
-
"""Export schemas with separate files for nested models.
|
614
|
-
|
615
|
-
This is a convenience method that calls dump_schemas with
|
616
|
-
separate_definitions=True.
|
617
|
-
|
618
|
-
Args:
|
619
|
-
output_dir: Directory path for output.
|
620
|
-
ref_template: Template for $ref URLs. Supports {model} and {version}
|
621
|
-
placeholders. Defaults to relative file refs.
|
622
|
-
indent: JSON indentation level.
|
623
|
-
|
624
|
-
Example:
|
625
|
-
>>> # Relative file references (default)
|
626
|
-
>>> manager.dump_schemas_with_refs("schemas/")
|
627
|
-
>>>
|
628
|
-
>>> # Absolute URL references
|
629
|
-
>>> manager.dump_schemas_with_refs(
|
630
|
-
... "schemas/",
|
631
|
-
... ref_template="https://example.com/schemas/{model}_v{version}.json"
|
632
|
-
... )
|
633
|
-
"""
|
634
|
-
self.schema_manager.dump_schemas(
|
635
|
-
output_dir, indent, separate_definitions=True, ref_template=ref_template
|
636
|
-
)
|
637
|
-
|
638
618
|
def get_nested_models(
|
639
619
|
self: Self,
|
640
620
|
name: str,
|
641
621
|
version: str | ModelVersion,
|
642
|
-
) -> list[
|
622
|
+
) -> list[NestedModelInfo]:
|
643
623
|
"""Get all nested models used by a model.
|
644
624
|
|
645
625
|
Args:
|
@@ -647,16 +627,16 @@ class ModelManager:
|
|
647
627
|
version: Semantic version.
|
648
628
|
|
649
629
|
Returns:
|
650
|
-
List of
|
630
|
+
List of NestedModelInfo.
|
651
631
|
"""
|
652
|
-
return self.
|
632
|
+
return self._schema_manager.get_nested_models(name, version)
|
653
633
|
|
654
634
|
def test_migration(
|
655
635
|
self: Self,
|
656
636
|
name: str,
|
657
637
|
from_version: str | ModelVersion,
|
658
638
|
to_version: str | ModelVersion,
|
659
|
-
test_cases:
|
639
|
+
test_cases: MigrationTestCases,
|
660
640
|
) -> MigrationTestResults:
|
661
641
|
"""Test a migration with multiple test cases.
|
662
642
|
|
pyrmute/types.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
from collections.abc import Callable
|
6
|
+
from dataclasses import dataclass
|
6
7
|
from typing import Any, TypeAlias, TypeVar
|
7
8
|
|
8
9
|
from pydantic import BaseModel
|
@@ -19,13 +20,19 @@ JsonSchemaDefinitions: TypeAlias = dict[str, JsonValue]
|
|
19
20
|
JsonSchemaGenerator: TypeAlias = Callable[[type[BaseModel]], JsonSchema]
|
20
21
|
SchemaGenerators: TypeAlias = dict[ModelVersion, JsonSchemaGenerator]
|
21
22
|
|
22
|
-
|
23
|
-
MigrationFunc: TypeAlias = Callable[[
|
24
|
-
|
25
|
-
|
23
|
+
ModelData: TypeAlias = dict[str, Any]
|
24
|
+
MigrationFunc: TypeAlias = Callable[[ModelData], ModelData]
|
26
25
|
MigrationKey: TypeAlias = tuple[ModelVersion, ModelVersion]
|
27
26
|
MigrationMap: TypeAlias = dict[MigrationKey, MigrationFunc]
|
28
27
|
|
29
28
|
ModelName: TypeAlias = str
|
30
29
|
ModelMetadata: TypeAlias = tuple[ModelName, ModelVersion]
|
31
30
|
VersionedModels: TypeAlias = dict[ModelVersion, type[BaseModel]]
|
31
|
+
|
32
|
+
|
33
|
+
@dataclass
|
34
|
+
class NestedModelInfo:
|
35
|
+
"""Contains information about nested models."""
|
36
|
+
|
37
|
+
name: str
|
38
|
+
version: ModelVersion
|