pyrmute 0.3.0__tar.gz → 0.4.0__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.
Files changed (42) hide show
  1. {pyrmute-0.3.0 → pyrmute-0.4.0}/PKG-INFO +220 -66
  2. {pyrmute-0.3.0 → pyrmute-0.4.0}/README.md +219 -65
  3. pyrmute-0.4.0/examples/advanced_features.py +373 -0
  4. pyrmute-0.4.0/examples/config_file_migration.py +448 -0
  5. pyrmute-0.4.0/examples/etl_data_import.py +714 -0
  6. pyrmute-0.4.0/examples/message_queue_consumer.py +534 -0
  7. pyrmute-0.4.0/examples/ml_inference_pipeline.py +671 -0
  8. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/__init__.py +2 -0
  9. pyrmute-0.4.0/src/pyrmute/_migration_manager.py +766 -0
  10. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/_version.py +3 -3
  11. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/migration_testing.py +4 -1
  12. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/model_manager.py +2 -1
  13. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute.egg-info/PKG-INFO +220 -66
  14. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute.egg-info/SOURCES.txt +5 -0
  15. pyrmute-0.4.0/tests/test_migration_manager.py +3316 -0
  16. pyrmute-0.3.0/src/pyrmute/_migration_manager.py +0 -381
  17. pyrmute-0.3.0/tests/test_migration_manager.py +0 -1217
  18. {pyrmute-0.3.0 → pyrmute-0.4.0}/.github/workflows/ci.yml +0 -0
  19. {pyrmute-0.3.0 → pyrmute-0.4.0}/.github/workflows/publish.yml +0 -0
  20. {pyrmute-0.3.0 → pyrmute-0.4.0}/.gitignore +0 -0
  21. {pyrmute-0.3.0 → pyrmute-0.4.0}/LICENSE +0 -0
  22. {pyrmute-0.3.0 → pyrmute-0.4.0}/pyproject.toml +0 -0
  23. {pyrmute-0.3.0 → pyrmute-0.4.0}/setup.cfg +0 -0
  24. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/_registry.py +0 -0
  25. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/_schema_manager.py +0 -0
  26. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/exceptions.py +0 -0
  27. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/model_diff.py +0 -0
  28. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/model_version.py +0 -0
  29. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/py.typed +0 -0
  30. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute/types.py +0 -0
  31. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute.egg-info/dependency_links.txt +0 -0
  32. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute.egg-info/requires.txt +0 -0
  33. {pyrmute-0.3.0 → pyrmute-0.4.0}/src/pyrmute.egg-info/top_level.txt +0 -0
  34. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/conftest.py +0 -0
  35. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/test_hypothesis.py +0 -0
  36. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/test_migration_testing.py +0 -0
  37. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/test_model_diff.py +0 -0
  38. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/test_model_manager.py +0 -0
  39. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/test_model_version.py +0 -0
  40. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/test_registry.py +0 -0
  41. {pyrmute-0.3.0 → pyrmute-0.4.0}/tests/test_schema_manager.py +0 -0
  42. {pyrmute-0.3.0 → pyrmute-0.4.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrmute
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Pydantic model migrations and schemas
5
5
  Author-email: Matt Ferrera <mattferrera@gmail.com>
6
6
  License: MIT
@@ -56,6 +56,28 @@ through multiple versions.
56
56
  support for large datasets
57
57
  - **Only one dependency** - Pydantic
58
58
 
59
+ ## When to Use pyrmute
60
+
61
+ pyrmute excels at handling schema evolution in production systems:
62
+
63
+ - **Configuration files** - Upgrade user config files as your CLI/desktop app
64
+ evolves (`.apprc`, `config.json`, `settings.yaml`)
65
+ - **Message queues & event streams** - Handle messages from multiple service
66
+ versions publishing different schemas (Kafka, RabbitMQ, SQS)
67
+ - **ETL & data imports** - Import CSV/JSON/Excel files exported over years
68
+ with evolving structures
69
+ - **ML model serving** - Manage feature schema evolution across model versions
70
+ and A/B tests
71
+ - **API versioning** - Support multiple API versions with automatic
72
+ request/response migration
73
+ - **Database migrations** - Transparently migrate legacy data on read without
74
+ downtime
75
+ - **Data archival** - Process historical data dumps with various schema
76
+ versions
77
+
78
+ See the [examples/](examples/) directory for complete, runnable code
79
+ demonstrating these patterns.
80
+
59
81
  ## Help
60
82
 
61
83
  See [documentation](https://mferrera.github.io/pyrmute/) for complete guides
@@ -232,6 +254,71 @@ results = manager.test_migration(
232
254
  assert results.all_passed, f"Migration failed: {results.failures}"
233
255
  ```
234
256
 
257
+ ### Bidirectional Migrations
258
+
259
+ ```python
260
+ # Support both upgrades and downgrades
261
+ @manager.migration("Config", "2.0.0", "1.0.0")
262
+ def downgrade_config(data: ModelData) -> ModelData:
263
+ """Rollback to v1 format."""
264
+ return {k: v for k, v in data.items() if k in ["setting1", "setting2"]}
265
+
266
+ # Useful for:
267
+ # - Rolling back deployments
268
+ # - Normalizing outputs from multiple model versions
269
+ # - Supporting legacy systems during transitions
270
+ ```
271
+
272
+ ### Nested Model Migrations
273
+
274
+ ```python
275
+ # Automatically migrates nested Pydantic models
276
+ @manager.model("Address", "1.0.0")
277
+ class AddressV1(BaseModel):
278
+ street: str
279
+ city: str
280
+
281
+ @manager.model("Address", "2.0.0")
282
+ class AddressV2(BaseModel):
283
+ street: str
284
+ city: str
285
+ postal_code: str
286
+
287
+ @manager.model("User", "2.0.0")
288
+ class UserV2(BaseModel):
289
+ name: str
290
+ address: AddressV2 # Nested model
291
+
292
+ # When migrating User, Address is automatically migrated too
293
+ @manager.migration("Address", "1.0.0", "2.0.0")
294
+ def add_postal_code(data: ModelData) -> ModelData:
295
+ return {**data, "postal_code": "00000"}
296
+ ```
297
+
298
+ ### Discriminated Unions
299
+
300
+ ```python
301
+ from typing import Literal, Union
302
+ from pydantic import Field
303
+
304
+ # Handle complex type hierarchies
305
+ @manager.model("CreditCard", "1.0.0")
306
+ class CreditCardV1(BaseModel):
307
+ type: Literal["credit_card"] = "credit_card"
308
+ card_number: str
309
+
310
+ @manager.model("PayPal", "1.0.0")
311
+ class PayPalV1(BaseModel):
312
+ type: Literal["paypal"] = "paypal"
313
+ email: str
314
+
315
+ @manager.model("Payment", "1.0.0")
316
+ class PaymentV1(BaseModel):
317
+ method: Union[CreditCardV1, PayPalV1] = Field(discriminator="type")
318
+
319
+ # Migrations respect discriminated unions
320
+ ```
321
+
235
322
  ### Export JSON Schemas
236
323
 
237
324
  ```python
@@ -267,79 +354,146 @@ config = manager.migrate({"timeout": 60}, "Config", "1.0.0", "2.0.0")
267
354
  # ConfigV2(timeout=60, retries=3)
268
355
  ```
269
356
 
270
- ## Real-World Example
357
+ ## Real-World Examples
358
+
359
+ ### Configuration File Evolution
271
360
 
272
361
  ```python
273
- from datetime import datetime
274
- from pydantic import BaseModel, EmailStr
275
- from pyrmute import ModelManager, ModelData
362
+ # Your CLI tool evolves over time
363
+ @manager.model("AppConfig", "1.0.0")
364
+ class AppConfigV1(BaseModel):
365
+ api_key: str
366
+ debug: bool = False
367
+
368
+ @manager.model("AppConfig", "2.0.0")
369
+ class AppConfigV2(BaseModel):
370
+ api_key: str
371
+ api_endpoint: str = "https://api.example.com"
372
+ log_level: Literal["DEBUG", "INFO", "ERROR"] = "INFO"
373
+
374
+ @manager.migration("AppConfig", "1.0.0", "2.0.0")
375
+ def upgrade_config(data: dict) -> dict:
376
+ return {
377
+ "api_key": data["api_key"],
378
+ "api_endpoint": "https://api.example.com",
379
+ "log_level": "DEBUG" if data.get("debug") else "INFO",
380
+ }
276
381
 
277
- manager = ModelManager()
382
+ # Load and auto-upgrade user's config file
383
+ def load_config(config_path: Path) -> AppConfigV2:
384
+ with open(config_path) as f:
385
+ data = json.load(f)
278
386
 
387
+ version = data.get("_version", "1.0.0")
279
388
 
280
- # API v1: Basic order
281
- @manager.model("Order", "1.0.0")
282
- class OrderV1(BaseModel):
283
- id: str
284
- items: list[str]
285
- total: float
286
-
287
-
288
- # API v2: Add customer info
289
- @manager.model("Order", "2.0.0")
290
- class OrderV2(BaseModel):
291
- id: str
292
- items: list[str]
293
- total: float
294
- customer_email: EmailStr
295
-
296
-
297
- # API v3: Structured items and timestamps
298
- @manager.model("Order", "3.0.0")
299
- class OrderItemV3(BaseModel):
300
- product_id: str
301
- quantity: int
302
- price: float
303
-
304
-
305
- @manager.model("Order", "3.0.0")
306
- class OrderV3(BaseModel):
307
- id: str
308
- items: list[OrderItemV3]
309
- total: float
310
- customer_email: EmailStr
311
- created_at: datetime
312
-
313
-
314
- # Define migrations
315
- @manager.migration("Order", "1.0.0", "2.0.0")
316
- def add_customer_email(data: ModelData) -> ModelData:
317
- return {**data, "customer_email": "customer@example.com"}
318
-
319
-
320
- @manager.migration("Order", "2.0.0", "3.0.0")
321
- def structure_items(data: ModelData) -> ModelData:
322
- # Convert simple strings to structured items
323
- structured_items = [
324
- {
325
- "product_id": item,
326
- "quantity": 1,
327
- "price": data["total"] / len(data["items"])
328
- }
329
- for item in data["items"]
330
- ]
331
- return {
332
- **data,
333
- "items": structured_items,
334
- "created_at": datetime.now().isoformat()
335
- }
389
+ # Migrate to current version
390
+ config = manager.migrate(
391
+ data,
392
+ "AppConfig",
393
+ from_version=version,
394
+ to_version="2.0.0"
395
+ )
336
396
 
337
- # Migrate old orders from your database
338
- old_order = {"id": "123", "items": ["widget", "gadget"], "total": 29.99}
339
- new_order = manager.migrate(old_order, "Order", "1.0.0", "3.0.0")
340
- database.save(new_order)
397
+ # Save upgraded config with version tag
398
+ with open(config_path, "w") as f:
399
+ json.dump({**config.model_dump(), "_version": "2.0.0"}, f, indent=2)
400
+
401
+ return config
341
402
  ```
342
403
 
404
+ ### Message Queue Consumer
405
+
406
+ ```python
407
+ # Handle messages from multiple service versions
408
+ @manager.model("OrderEvent", "1.0.0")
409
+ class OrderEventV1(BaseModel):
410
+ order_id: str
411
+ customer_email: str
412
+ items: list[dict] # Unstructured
413
+
414
+ @manager.model("OrderEvent", "2.0.0")
415
+ class OrderEventV2(BaseModel):
416
+ order_id: str
417
+ customer_email: str
418
+ items: list[OrderItem] # Structured
419
+ total: Decimal
420
+
421
+ def process_message(message: dict, schema_version: str) -> None:
422
+ # Migrate to current schema regardless of source version
423
+ event = manager.migrate(
424
+ message,
425
+ "OrderEvent",
426
+ from_version=schema_version,
427
+ to_version="2.0.0"
428
+ )
429
+ # Process with current schema only
430
+ fulfill_order(event)
431
+ ```
432
+
433
+ ### ETL Data Import
434
+
435
+ ```python
436
+ # Import historical exports with evolving schemas
437
+ import csv
438
+
439
+ def import_customers(file_path: Path, file_version: str) -> None:
440
+ with open(file_path) as f:
441
+ reader = csv.DictReader(f)
442
+
443
+ # Stream migration for memory efficiency
444
+ for customer in manager.migrate_batch_streaming(
445
+ reader,
446
+ "Customer",
447
+ from_version=file_version,
448
+ to_version="3.0.0",
449
+ chunk_size=1000
450
+ ):
451
+ database.save(customer)
452
+
453
+ # Handle files from different years
454
+ import_customers("exports/2022_customers.csv", "1.0.0")
455
+ import_customers("exports/2023_customers.csv", "2.0.0")
456
+ import_customers("exports/2024_customers.csv", "3.0.0")
457
+ ```
458
+
459
+ ### ML Model Serving
460
+
461
+ ```python
462
+ # Route requests to appropriate model versions
463
+ class InferenceService:
464
+ def predict(self, features: dict, request_version: str) -> BaseModel:
465
+ # Determine target model version (A/B testing, gradual rollout, etc.)
466
+ model_version = self.get_model_version(features["user_id"])
467
+
468
+ # Migrate request to model's expected format
469
+ model_input = manager.migrate(
470
+ features,
471
+ "PredictionRequest",
472
+ from_version=request_version,
473
+ to_version=model_version
474
+ )
475
+
476
+ # Run inference
477
+ prediction = self.models[model_version].predict(model_input)
478
+
479
+ # Normalize output for logging/analytics
480
+ return manager.migrate(
481
+ prediction,
482
+ "PredictionResponse",
483
+ from_version=model_version,
484
+ to_version="3.0.0"
485
+ )
486
+ ```
487
+
488
+ See [examples/](examples/) for complete runnable code:
489
+ - `config_file_migration.py` - CLI/desktop app config file evolution
490
+ - `message_queue_consumer.py` - Kafka/RabbitMQ/SQS consumer handling multiple
491
+ schemas
492
+ - `etl_data_import.py` - CSV/JSON/Excel import pipeline with historical data
493
+ - `ml_inference_pipeline.py` - ML model serving with feature evolution
494
+ - `advanced_features.py` - Complex Pydantic features (unions, nested models,
495
+ validators)
496
+
343
497
  ## Contributing
344
498
 
345
499
  For guidance on setting up a development environment and how to make a
@@ -24,6 +24,28 @@ through multiple versions.
24
24
  support for large datasets
25
25
  - **Only one dependency** - Pydantic
26
26
 
27
+ ## When to Use pyrmute
28
+
29
+ pyrmute excels at handling schema evolution in production systems:
30
+
31
+ - **Configuration files** - Upgrade user config files as your CLI/desktop app
32
+ evolves (`.apprc`, `config.json`, `settings.yaml`)
33
+ - **Message queues & event streams** - Handle messages from multiple service
34
+ versions publishing different schemas (Kafka, RabbitMQ, SQS)
35
+ - **ETL & data imports** - Import CSV/JSON/Excel files exported over years
36
+ with evolving structures
37
+ - **ML model serving** - Manage feature schema evolution across model versions
38
+ and A/B tests
39
+ - **API versioning** - Support multiple API versions with automatic
40
+ request/response migration
41
+ - **Database migrations** - Transparently migrate legacy data on read without
42
+ downtime
43
+ - **Data archival** - Process historical data dumps with various schema
44
+ versions
45
+
46
+ See the [examples/](examples/) directory for complete, runnable code
47
+ demonstrating these patterns.
48
+
27
49
  ## Help
28
50
 
29
51
  See [documentation](https://mferrera.github.io/pyrmute/) for complete guides
@@ -200,6 +222,71 @@ results = manager.test_migration(
200
222
  assert results.all_passed, f"Migration failed: {results.failures}"
201
223
  ```
202
224
 
225
+ ### Bidirectional Migrations
226
+
227
+ ```python
228
+ # Support both upgrades and downgrades
229
+ @manager.migration("Config", "2.0.0", "1.0.0")
230
+ def downgrade_config(data: ModelData) -> ModelData:
231
+ """Rollback to v1 format."""
232
+ return {k: v for k, v in data.items() if k in ["setting1", "setting2"]}
233
+
234
+ # Useful for:
235
+ # - Rolling back deployments
236
+ # - Normalizing outputs from multiple model versions
237
+ # - Supporting legacy systems during transitions
238
+ ```
239
+
240
+ ### Nested Model Migrations
241
+
242
+ ```python
243
+ # Automatically migrates nested Pydantic models
244
+ @manager.model("Address", "1.0.0")
245
+ class AddressV1(BaseModel):
246
+ street: str
247
+ city: str
248
+
249
+ @manager.model("Address", "2.0.0")
250
+ class AddressV2(BaseModel):
251
+ street: str
252
+ city: str
253
+ postal_code: str
254
+
255
+ @manager.model("User", "2.0.0")
256
+ class UserV2(BaseModel):
257
+ name: str
258
+ address: AddressV2 # Nested model
259
+
260
+ # When migrating User, Address is automatically migrated too
261
+ @manager.migration("Address", "1.0.0", "2.0.0")
262
+ def add_postal_code(data: ModelData) -> ModelData:
263
+ return {**data, "postal_code": "00000"}
264
+ ```
265
+
266
+ ### Discriminated Unions
267
+
268
+ ```python
269
+ from typing import Literal, Union
270
+ from pydantic import Field
271
+
272
+ # Handle complex type hierarchies
273
+ @manager.model("CreditCard", "1.0.0")
274
+ class CreditCardV1(BaseModel):
275
+ type: Literal["credit_card"] = "credit_card"
276
+ card_number: str
277
+
278
+ @manager.model("PayPal", "1.0.0")
279
+ class PayPalV1(BaseModel):
280
+ type: Literal["paypal"] = "paypal"
281
+ email: str
282
+
283
+ @manager.model("Payment", "1.0.0")
284
+ class PaymentV1(BaseModel):
285
+ method: Union[CreditCardV1, PayPalV1] = Field(discriminator="type")
286
+
287
+ # Migrations respect discriminated unions
288
+ ```
289
+
203
290
  ### Export JSON Schemas
204
291
 
205
292
  ```python
@@ -235,79 +322,146 @@ config = manager.migrate({"timeout": 60}, "Config", "1.0.0", "2.0.0")
235
322
  # ConfigV2(timeout=60, retries=3)
236
323
  ```
237
324
 
238
- ## Real-World Example
325
+ ## Real-World Examples
326
+
327
+ ### Configuration File Evolution
328
+
329
+ ```python
330
+ # Your CLI tool evolves over time
331
+ @manager.model("AppConfig", "1.0.0")
332
+ class AppConfigV1(BaseModel):
333
+ api_key: str
334
+ debug: bool = False
335
+
336
+ @manager.model("AppConfig", "2.0.0")
337
+ class AppConfigV2(BaseModel):
338
+ api_key: str
339
+ api_endpoint: str = "https://api.example.com"
340
+ log_level: Literal["DEBUG", "INFO", "ERROR"] = "INFO"
341
+
342
+ @manager.migration("AppConfig", "1.0.0", "2.0.0")
343
+ def upgrade_config(data: dict) -> dict:
344
+ return {
345
+ "api_key": data["api_key"],
346
+ "api_endpoint": "https://api.example.com",
347
+ "log_level": "DEBUG" if data.get("debug") else "INFO",
348
+ }
349
+
350
+ # Load and auto-upgrade user's config file
351
+ def load_config(config_path: Path) -> AppConfigV2:
352
+ with open(config_path) as f:
353
+ data = json.load(f)
354
+
355
+ version = data.get("_version", "1.0.0")
356
+
357
+ # Migrate to current version
358
+ config = manager.migrate(
359
+ data,
360
+ "AppConfig",
361
+ from_version=version,
362
+ to_version="2.0.0"
363
+ )
364
+
365
+ # Save upgraded config with version tag
366
+ with open(config_path, "w") as f:
367
+ json.dump({**config.model_dump(), "_version": "2.0.0"}, f, indent=2)
368
+
369
+ return config
370
+ ```
371
+
372
+ ### Message Queue Consumer
239
373
 
240
374
  ```python
241
- from datetime import datetime
242
- from pydantic import BaseModel, EmailStr
243
- from pyrmute import ModelManager, ModelData
375
+ # Handle messages from multiple service versions
376
+ @manager.model("OrderEvent", "1.0.0")
377
+ class OrderEventV1(BaseModel):
378
+ order_id: str
379
+ customer_email: str
380
+ items: list[dict] # Unstructured
381
+
382
+ @manager.model("OrderEvent", "2.0.0")
383
+ class OrderEventV2(BaseModel):
384
+ order_id: str
385
+ customer_email: str
386
+ items: list[OrderItem] # Structured
387
+ total: Decimal
388
+
389
+ def process_message(message: dict, schema_version: str) -> None:
390
+ # Migrate to current schema regardless of source version
391
+ event = manager.migrate(
392
+ message,
393
+ "OrderEvent",
394
+ from_version=schema_version,
395
+ to_version="2.0.0"
396
+ )
397
+ # Process with current schema only
398
+ fulfill_order(event)
399
+ ```
244
400
 
245
- manager = ModelManager()
401
+ ### ETL Data Import
246
402
 
403
+ ```python
404
+ # Import historical exports with evolving schemas
405
+ import csv
406
+
407
+ def import_customers(file_path: Path, file_version: str) -> None:
408
+ with open(file_path) as f:
409
+ reader = csv.DictReader(f)
410
+
411
+ # Stream migration for memory efficiency
412
+ for customer in manager.migrate_batch_streaming(
413
+ reader,
414
+ "Customer",
415
+ from_version=file_version,
416
+ to_version="3.0.0",
417
+ chunk_size=1000
418
+ ):
419
+ database.save(customer)
420
+
421
+ # Handle files from different years
422
+ import_customers("exports/2022_customers.csv", "1.0.0")
423
+ import_customers("exports/2023_customers.csv", "2.0.0")
424
+ import_customers("exports/2024_customers.csv", "3.0.0")
425
+ ```
247
426
 
248
- # API v1: Basic order
249
- @manager.model("Order", "1.0.0")
250
- class OrderV1(BaseModel):
251
- id: str
252
- items: list[str]
253
- total: float
254
-
255
-
256
- # API v2: Add customer info
257
- @manager.model("Order", "2.0.0")
258
- class OrderV2(BaseModel):
259
- id: str
260
- items: list[str]
261
- total: float
262
- customer_email: EmailStr
263
-
264
-
265
- # API v3: Structured items and timestamps
266
- @manager.model("Order", "3.0.0")
267
- class OrderItemV3(BaseModel):
268
- product_id: str
269
- quantity: int
270
- price: float
271
-
272
-
273
- @manager.model("Order", "3.0.0")
274
- class OrderV3(BaseModel):
275
- id: str
276
- items: list[OrderItemV3]
277
- total: float
278
- customer_email: EmailStr
279
- created_at: datetime
280
-
281
-
282
- # Define migrations
283
- @manager.migration("Order", "1.0.0", "2.0.0")
284
- def add_customer_email(data: ModelData) -> ModelData:
285
- return {**data, "customer_email": "customer@example.com"}
286
-
287
-
288
- @manager.migration("Order", "2.0.0", "3.0.0")
289
- def structure_items(data: ModelData) -> ModelData:
290
- # Convert simple strings to structured items
291
- structured_items = [
292
- {
293
- "product_id": item,
294
- "quantity": 1,
295
- "price": data["total"] / len(data["items"])
296
- }
297
- for item in data["items"]
298
- ]
299
- return {
300
- **data,
301
- "items": structured_items,
302
- "created_at": datetime.now().isoformat()
303
- }
427
+ ### ML Model Serving
304
428
 
305
- # Migrate old orders from your database
306
- old_order = {"id": "123", "items": ["widget", "gadget"], "total": 29.99}
307
- new_order = manager.migrate(old_order, "Order", "1.0.0", "3.0.0")
308
- database.save(new_order)
429
+ ```python
430
+ # Route requests to appropriate model versions
431
+ class InferenceService:
432
+ def predict(self, features: dict, request_version: str) -> BaseModel:
433
+ # Determine target model version (A/B testing, gradual rollout, etc.)
434
+ model_version = self.get_model_version(features["user_id"])
435
+
436
+ # Migrate request to model's expected format
437
+ model_input = manager.migrate(
438
+ features,
439
+ "PredictionRequest",
440
+ from_version=request_version,
441
+ to_version=model_version
442
+ )
443
+
444
+ # Run inference
445
+ prediction = self.models[model_version].predict(model_input)
446
+
447
+ # Normalize output for logging/analytics
448
+ return manager.migrate(
449
+ prediction,
450
+ "PredictionResponse",
451
+ from_version=model_version,
452
+ to_version="3.0.0"
453
+ )
309
454
  ```
310
455
 
456
+ See [examples/](examples/) for complete runnable code:
457
+ - `config_file_migration.py` - CLI/desktop app config file evolution
458
+ - `message_queue_consumer.py` - Kafka/RabbitMQ/SQS consumer handling multiple
459
+ schemas
460
+ - `etl_data_import.py` - CSV/JSON/Excel import pipeline with historical data
461
+ - `ml_inference_pipeline.py` - ML model serving with feature evolution
462
+ - `advanced_features.py` - Complex Pydantic features (unions, nested models,
463
+ validators)
464
+
311
465
  ## Contributing
312
466
 
313
467
  For guidance on setting up a development environment and how to make a