pydantic-marshmallow 1.0.0__py3-none-any.whl → 1.0.1__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.
@@ -51,16 +51,22 @@ Features:
51
51
 
52
52
  # Re-export Marshmallow's validators - these work with PydanticSchema
53
53
  # Also export hooks for convenience
54
+ # Note: We re-export Marshmallow's @validates and @validates_schema above.
55
+ # Our bridge's _do_load calls Marshmallow's native validator system,
56
+ # so `from marshmallow import validates` works correctly with PydanticSchema.
57
+ # Version is managed by setuptools-scm from git tags
58
+ from importlib.metadata import version as _version
59
+
54
60
  from marshmallow import EXCLUDE, INCLUDE, RAISE, post_dump, post_load, pre_dump, pre_load, validates, validates_schema
55
61
 
56
62
  from .bridge import HybridModel, PydanticSchema, pydantic_schema, schema_for
57
63
  from .errors import BridgeValidationError
58
64
 
59
- # Note: We re-export Marshmallow's @validates and @validates_schema above.
60
- # Our bridge's _do_load calls Marshmallow's native validator system,
61
- # so `from marshmallow import validates` works correctly with PydanticSchema.
62
-
63
- __version__ = "0.1.0"
65
+ try:
66
+ __version__ = _version("pydantic-marshmallow")
67
+ except Exception:
68
+ # Fallback for development or environments without installed package metadata
69
+ __version__ = "0.0.0.dev0"
64
70
  __all__ = [
65
71
  "EXCLUDE",
66
72
  "INCLUDE",
@@ -7,7 +7,9 @@ Flow: Input → Marshmallow pre_load → PYDANTIC VALIDATES → Marshmallow post
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import threading
10
11
  from collections.abc import Callable, Sequence, Set as AbstractSet
12
+ from functools import lru_cache
11
13
  from typing import Any, ClassVar, Generic, TypeVar, cast, get_args, get_origin
12
14
 
13
15
  from marshmallow import EXCLUDE, INCLUDE, RAISE, Schema, fields as ma_fields
@@ -15,6 +17,7 @@ from marshmallow.decorators import VALIDATES, VALIDATES_SCHEMA
15
17
  from marshmallow.error_store import ErrorStore
16
18
  from marshmallow.exceptions import ValidationError as MarshmallowValidationError
17
19
  from marshmallow.schema import SchemaMeta
20
+ from marshmallow.utils import missing as ma_missing
18
21
  from pydantic import BaseModel, ConfigDict, ValidationError as PydanticValidationError
19
22
 
20
23
  from .errors import BridgeValidationError, convert_pydantic_errors, format_pydantic_error
@@ -23,14 +26,51 @@ from .validators import cache_validators
23
26
 
24
27
  M = TypeVar("M", bound=BaseModel)
25
28
 
29
+ # =============================================================================
30
+ # Module-level Caches
31
+ # =============================================================================
32
+ # Thread Safety: All caches use thread-safe implementations to support both
33
+ # GIL-enabled Python and free-threaded Python 3.14+ (PEP 703).
34
+ # - @lru_cache: Inherently thread-safe (uses internal lock)
35
+ # - Dict caches: Protected by threading.Lock for write operations
36
+ #
37
+ # Cache Invalidation: These caches assume Pydantic models are immutable after
38
+ # definition. Dynamic model modification at runtime (monkey-patching, adding
39
+ # fields) will result in stale cached data. This is an accepted trade-off for
40
+ # performance gains.
41
+ # =============================================================================
42
+
43
+ # Thread lock for cache writes (supports free-threaded Python 3.14+)
44
+ # Use RLock (reentrant) since from_model() may be called recursively
45
+ _cache_lock = threading.RLock()
46
+
26
47
  # Module-level cache for HybridModel schemas
27
48
  _hybrid_schema_cache: dict[type[Any], type[PydanticSchema[Any]]] = {}
28
49
 
50
+ # Cache for schema_for/from_model - key is (model, schema_name, frozen options)
51
+ # This avoids recreating schema classes for the same model+options
52
+ _schema_class_cache: dict[tuple[type[Any], str | None, tuple[tuple[str, Any], ...]], type[Any]] = {}
53
+
29
54
  # Field validator registry: maps (schema_class, field_name) -> list of validator functions
30
55
  _field_validators: dict[tuple[type[Any], str], list[Callable[..., Any]]] = {}
31
56
  _schema_validators: dict[type[Any], list[Callable[..., Any]]] = {}
32
57
 
33
58
 
59
+ @lru_cache(maxsize=1024)
60
+ def _get_model_field_names_with_aliases(model_class: type[BaseModel]) -> frozenset[str]:
61
+ """
62
+ Get cached frozenset of model field names including aliases.
63
+
64
+ Thread-safe via @lru_cache. Caches the result per model class to avoid
65
+ repeated computation in the load() hot path.
66
+ """
67
+ field_names = set(model_class.model_fields.keys())
68
+ for field_info in model_class.model_fields.values():
69
+ if field_info.alias:
70
+ field_names.add(field_info.alias)
71
+ return frozenset(field_names)
72
+
73
+
34
74
  class PydanticSchemaMeta(SchemaMeta):
35
75
  """
36
76
  Custom metaclass that adds Pydantic model fields BEFORE Marshmallow processes them.
@@ -389,6 +429,7 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
389
429
  data: dict[str, Any],
390
430
  partial: bool | Sequence[str] | AbstractSet[str] | None = None,
391
431
  original_data: Any | None = None,
432
+ skip_model_dump: bool = False,
392
433
  ) -> tuple[dict[str, Any], M | None]:
393
434
  """
394
435
  Use Pydantic to validate and coerce the input data.
@@ -396,6 +437,13 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
396
437
  Filters out marshmallow.missing sentinel values before Pydantic validation,
397
438
  allowing Pydantic to use its own defaults for missing fields.
398
439
 
440
+ Args:
441
+ data: Input data to validate
442
+ partial: Partial validation mode
443
+ original_data: Original input data for error reporting
444
+ skip_model_dump: If True and not partial, skip model_dump() and return
445
+ empty dict. Use when validators don't need the dict.
446
+
399
447
  Returns:
400
448
  Tuple of (validated_data_dict, model_instance)
401
449
  The instance is returned to avoid redundant validation later.
@@ -404,8 +452,6 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
404
452
  return data, None
405
453
 
406
454
  # Filter out marshmallow.missing values - Pydantic should use its defaults
407
- from marshmallow.utils import missing as ma_missing
408
-
409
455
  clean_data = {
410
456
  k: v for k, v in data.items()
411
457
  if v is not ma_missing
@@ -420,6 +466,9 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
420
466
  else:
421
467
  # Let Pydantic do all the validation - KEEP THE INSTANCE
422
468
  instance = self._model_class.model_validate(clean_data)
469
+ # OPTIMIZATION: Skip model_dump if not needed for validators
470
+ if skip_model_dump:
471
+ return {}, cast(M, instance)
423
472
  # Return both the dict (for validators) and instance (for result)
424
473
  validated_data = instance.model_dump(by_alias=False)
425
474
  # Cast to M since model_validate returns the correct model type
@@ -545,6 +594,11 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
545
594
 
546
595
  This ensures 100% Marshmallow hook compatibility.
547
596
  """
597
+ # PERFORMANCE: Hoist frequently accessed attributes to local variables
598
+ # This avoids repeated self.__dict__ lookups in the hot path
599
+ model_class = self._model_class
600
+ hooks = self._hooks
601
+
548
602
  # Resolve settings
549
603
  if many is None:
550
604
  many = self.many
@@ -576,8 +630,8 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
576
630
 
577
631
  # Step 1: Run pre_load hooks ONLY if they exist (PERFORMANCE OPTIMIZATION)
578
632
  # Skipping _invoke_load_processors when empty saves ~5ms per 10k loads
579
- if self._hooks.get("pre_load"):
580
- processed_data = self._invoke_load_processors(
633
+ if hooks.get("pre_load"):
634
+ processed_data_raw = self._invoke_load_processors(
581
635
  "pre_load",
582
636
  data,
583
637
  many=False,
@@ -585,17 +639,17 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
585
639
  partial=partial,
586
640
  )
587
641
  else:
588
- processed_data = data
642
+ processed_data_raw = data
589
643
 
590
- # Step 2: Handle unknown fields based on setting
591
- if self._model_class:
592
- model_fields = set(self._model_class.model_fields.keys())
593
- # Also include aliases in known fields
594
- for _field_name, field_info in self._model_class.model_fields.items():
595
- if field_info.alias:
596
- model_fields.add(field_info.alias)
644
+ # Type narrowing: at this point (many=False path), data is always a dict
645
+ processed_data: dict[str, Any] = cast(dict[str, Any], processed_data_raw)
597
646
 
598
- unkn_fields = set(processed_data.keys()) - model_fields
647
+ # Step 2: Handle unknown fields based on setting
648
+ # PERFORMANCE: Use cached field names instead of computing every time
649
+ model_field_names: frozenset[str] | None = None
650
+ if model_class:
651
+ model_field_names = _get_model_field_names_with_aliases(model_class)
652
+ unkn_fields = set(processed_data.keys()) - model_field_names
599
653
 
600
654
  if unkn_fields:
601
655
  if unknown_setting == RAISE:
@@ -604,17 +658,36 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
604
658
  if unknown_setting == EXCLUDE:
605
659
  # Remove unknown fields
606
660
  processed_data = {
607
- k: v for k, v in processed_data.items() if k in model_fields
661
+ k: v for k, v in processed_data.items() if k in model_field_names
608
662
  }
609
663
  # INCLUDE: keep unknown fields in the result (handled below)
610
664
 
611
665
  # Step 3: Pydantic validates the transformed data
612
666
  # Returns (validated_dict, instance) - instance reused to avoid double validation
613
- pydantic_instance = None
614
- if self._model_class:
667
+ pydantic_instance: M | None = None
668
+ validated_data: dict[str, Any]
669
+
670
+ # OPTIMIZATION: Determine if we need model_dump() for validators/result
671
+ # Skip expensive model_dump() when not needed
672
+ has_validators = bool(
673
+ hooks[VALIDATES]
674
+ or hooks[VALIDATES_SCHEMA]
675
+ or self._field_validators_cache
676
+ or self._schema_validators_cache
677
+ )
678
+ needs_dict = (
679
+ not return_instance # Need dict for result
680
+ or unknown_setting == INCLUDE # Need dict to merge unknown fields
681
+ or has_validators # Need dict for validators
682
+ )
683
+
684
+ if model_class:
615
685
  try:
616
686
  validated_data, pydantic_instance = self._validate_with_pydantic(
617
- processed_data, partial=partial, original_data=data
687
+ processed_data,
688
+ partial=partial,
689
+ original_data=data,
690
+ skip_model_dump=not needs_dict,
618
691
  )
619
692
  except MarshmallowValidationError as pydantic_error:
620
693
  # Call handle_error for Pydantic validation errors
@@ -623,8 +696,8 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
623
696
  raise
624
697
 
625
698
  # If INCLUDE, add unknown fields back to validated data
626
- if unknown_setting == INCLUDE and self._model_class:
627
- for field in (set(processed_data.keys()) - model_fields):
699
+ if unknown_setting == INCLUDE and model_field_names is not None:
700
+ for field in (set(processed_data.keys()) - model_field_names):
628
701
  validated_data[field] = processed_data[field]
629
702
  else:
630
703
  validated_data = processed_data
@@ -635,8 +708,8 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
635
708
  error_store_cls: Callable[[], Any] = cast(Callable[[], Any], ErrorStore)
636
709
  error_store: Any = error_store_cls()
637
710
 
638
- # 4a: Run Marshmallow's native @validates decorators (from _hooks)
639
- if self._hooks[VALIDATES]:
711
+ # 4a: Run Marshmallow's native @validates decorators (from hooks)
712
+ if hooks[VALIDATES]:
640
713
  self._invoke_field_validators(
641
714
  error_store=error_store,
642
715
  data=validated_data,
@@ -656,7 +729,7 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
656
729
 
657
730
  # Step 5: Run schema validators (BOTH Marshmallow native AND our custom)
658
731
  # 5a: Run Marshmallow's native @validates_schema decorators
659
- if self._hooks[VALIDATES_SCHEMA]:
732
+ if hooks[VALIDATES_SCHEMA]:
660
733
  self._invoke_schema_validators(
661
734
  error_store=error_store,
662
735
  pass_many=True,
@@ -690,7 +763,7 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
690
763
  self.handle_error(error, data, many=False)
691
764
 
692
765
  # Step 6: Prepare result based on return_instance flag
693
- if self._model_class and return_instance:
766
+ if model_class and return_instance:
694
767
  if not partial:
695
768
  # OPTIMIZATION: Reuse the instance from _validate_with_pydantic
696
769
  result = pydantic_instance if pydantic_instance is not None else validated_data
@@ -701,7 +774,7 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
701
774
  construct_data = {}
702
775
  fields_set = set()
703
776
 
704
- for field_name, field_info in self._model_class.model_fields.items():
777
+ for field_name, field_info in model_class.model_fields.items():
705
778
  if field_name in validated_data:
706
779
  construct_data[field_name] = validated_data[field_name]
707
780
  fields_set.add(field_name)
@@ -718,14 +791,14 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
718
791
  construct_data[field_name] = None
719
792
 
720
793
  result = cast(
721
- M, self._model_class.model_construct(_fields_set=fields_set, **construct_data)
794
+ M, model_class.model_construct(_fields_set=fields_set, **construct_data)
722
795
  )
723
796
  else:
724
797
  # Return dict instead of instance
725
798
  result = validated_data
726
799
 
727
800
  # Step 7: Run post_load hooks ONLY if they exist (PERFORMANCE OPTIMIZATION)
728
- if postprocess and self._hooks.get("post_load"):
801
+ if postprocess and hooks.get("post_load"):
729
802
  result = self._invoke_load_processors(
730
803
  "post_load",
731
804
  result,
@@ -989,7 +1062,7 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
989
1062
  obj = obj.model_dump(by_alias=False)
990
1063
 
991
1064
  # Let Marshmallow handle the standard dump
992
- result: dict[str, Any] = super().dump(obj, many=False)
1065
+ result: dict[str, Any] = cast(dict[str, Any], super().dump(obj, many=False))
993
1066
 
994
1067
  # Apply field exclusions
995
1068
  if fields_to_exclude:
@@ -1032,6 +1105,20 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
1032
1105
  Returns:
1033
1106
  A PydanticSchema subclass
1034
1107
  """
1108
+ # Build cache key from model, schema_name, and options
1109
+ # Sort options to ensure consistent keys
1110
+ # Note: schema_name must be included since it affects the generated class name
1111
+ cache_key: tuple[type[Any], str | None, tuple[tuple[str, Any], ...]] | None = None
1112
+ try:
1113
+ cache_key = (model, schema_name, tuple(sorted(meta_options.items())))
1114
+ # Thread-safe read (atomic dict lookup)
1115
+ cached = _schema_class_cache.get(cache_key)
1116
+ if cached is not None:
1117
+ return cached
1118
+ except TypeError:
1119
+ # Unhashable options (e.g., list values) - skip cache
1120
+ cache_key = None
1121
+
1035
1122
  name = schema_name or f"{model.__name__}Schema"
1036
1123
 
1037
1124
  # Extract field filtering options
@@ -1063,6 +1150,12 @@ class PydanticSchema(Schema, Generic[M], metaclass=PydanticSchemaMeta):
1063
1150
  class_dict: dict[str, Any] = {"Meta": Meta, **fields}
1064
1151
 
1065
1152
  schema_cls = type(name, (cls,), class_dict)
1153
+
1154
+ # Thread-safe cache write (supports free-threaded Python 3.14+)
1155
+ if cache_key is not None:
1156
+ with _cache_lock:
1157
+ _schema_class_cache[cache_key] = schema_cls
1158
+
1066
1159
  return schema_cls
1067
1160
 
1068
1161
 
@@ -1155,10 +1248,17 @@ class HybridModel(BaseModel):
1155
1248
 
1156
1249
  @classmethod
1157
1250
  def marshmallow_schema(cls) -> type[PydanticSchema[Any]]:
1158
- """Get or create the Marshmallow schema for this model."""
1159
- if cls not in _hybrid_schema_cache:
1160
- _hybrid_schema_cache[cls] = PydanticSchema.from_model(cls)
1161
- return _hybrid_schema_cache[cls]
1251
+ """Get or create the Marshmallow schema for this model (thread-safe)."""
1252
+ # Thread-safe read
1253
+ cached = _hybrid_schema_cache.get(cls)
1254
+ if cached is not None:
1255
+ return cached
1256
+ # Thread-safe write
1257
+ with _cache_lock:
1258
+ # Double-check after acquiring lock
1259
+ if cls not in _hybrid_schema_cache:
1260
+ _hybrid_schema_cache[cls] = PydanticSchema.from_model(cls)
1261
+ return _hybrid_schema_cache[cls]
1162
1262
 
1163
1263
  @classmethod
1164
1264
  def ma_load(cls, data: dict[str, Any], **kwargs: Any) -> HybridModel:
@@ -5,8 +5,9 @@ This module provides type-to-field conversions, leveraging Marshmallow's native
5
5
  TYPE_MAPPING for basic types and adding support for generic collections.
6
6
  """
7
7
  from enum import Enum as PyEnum
8
+ from functools import lru_cache
8
9
  from types import UnionType
9
- from typing import Any, Literal, Union, get_args, get_origin
10
+ from typing import Any, Literal, Union, cast, get_args, get_origin
10
11
 
11
12
  from marshmallow import Schema, fields as ma_fields
12
13
  from pydantic import BaseModel
@@ -15,6 +16,20 @@ from pydantic import BaseModel
15
16
  _processing_models: set[type[Any]] = set()
16
17
 
17
18
 
19
+ # Cache for simple type -> field class lookups (str, int, datetime, etc.)
20
+ # These are the most common types and benefit most from caching.
21
+ # maxsize=512 handles large codebases with ~80KB memory overhead.
22
+ @lru_cache(maxsize=512)
23
+ def _get_simple_field_class(type_hint: type) -> type[ma_fields.Field]:
24
+ """
25
+ Cached lookup for simple, hashable types in Marshmallow's TYPE_MAPPING.
26
+
27
+ This avoids repeated dict lookups and isinstance checks for common types
28
+ like str, int, float, bool, datetime, etc.
29
+ """
30
+ return Schema.TYPE_MAPPING.get(type_hint, ma_fields.Raw)
31
+
32
+
18
33
  def type_to_marshmallow_field(type_hint: Any) -> ma_fields.Field:
19
34
  """
20
35
  Map a Python type to a Marshmallow field instance.
@@ -36,6 +51,18 @@ def type_to_marshmallow_field(type_hint: Any) -> ma_fields.Field:
36
51
  Returns:
37
52
  An appropriate Marshmallow field instance
38
53
  """
54
+ # FAST PATH: Simple types (str, int, float, bool, datetime, etc.)
55
+ # Check TYPE_MAPPING first to skip all the isinstance/origin checks.
56
+ # This handles ~60-80% of fields in typical models, but we must not
57
+ # short-circuit for Enums or Pydantic models, which have specialized handling.
58
+ if (
59
+ isinstance(type_hint, type)
60
+ and type_hint in Schema.TYPE_MAPPING
61
+ and not issubclass(type_hint, (PyEnum, BaseModel))
62
+ ):
63
+ # Cast needed for mypy: type_hint is confirmed to be a type at this point
64
+ return _get_simple_field_class(cast(type, type_hint))()
65
+
39
66
  origin = get_origin(type_hint)
40
67
  args = get_args(type_hint)
41
68
 
@@ -134,5 +161,6 @@ def type_to_marshmallow_field(type_hint: Any) -> ma_fields.Field:
134
161
  # Use Marshmallow's native TYPE_MAPPING for basic types
135
162
  # This ensures we stay in sync with Marshmallow's type handling
136
163
  resolved = origin if origin else type_hint
137
- field_cls = Schema.TYPE_MAPPING.get(resolved, ma_fields.Raw)
138
- return field_cls()
164
+ if isinstance(resolved, type):
165
+ return _get_simple_field_class(resolved)()
166
+ return ma_fields.Raw()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-marshmallow
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Bring Pydantic's power to Marshmallow: type annotations, validators, and automatic schema generation
5
5
  Author-email: Your Name <your.email@example.com>
6
6
  License: MIT
@@ -24,8 +24,8 @@ Classifier: Typing :: Typed
24
24
  Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
- Requires-Dist: marshmallow>=3.18.0
28
- Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: marshmallow<5.0.0,>=3.18.0
28
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
29
29
  Requires-Dist: typing-extensions>=4.0.0
30
30
  Requires-Dist: orjson>=3.9.0
31
31
  Provides-Extra: dev
@@ -67,6 +67,29 @@ Bridge Pydantic's power with Marshmallow's ecosystem. Use Pydantic models for va
67
67
 
68
68
  📖 **[Documentation](https://mockodin.github.io/pydantic-marshmallow)** | 🐙 **[GitHub](https://github.com/mockodin/pydantic-marshmallow)**
69
69
 
70
+ ## Why pydantic-marshmallow?
71
+
72
+ Get the best of both worlds: **Pydantic's speed** with **Marshmallow's ecosystem**.
73
+
74
+ ### Performance
75
+
76
+ pydantic-marshmallow uses Pydantic's Rust-powered validation engine under the hood, delivering significant performance improvements over native Marshmallow—especially for nested data structures:
77
+
78
+ | Operation | pydantic-marshmallow | Marshmallow | Speedup |
79
+ |-----------|---------------------|-------------|---------|
80
+ | Simple load | 3.0 µs | 5.3 µs | **1.8x faster** |
81
+ | Nested model | 3.5 µs | 11.6 µs | **3.3x faster** |
82
+ | Deep nested (4 levels) | 5.6 µs | 32.3 µs | **5.8x faster** |
83
+ | Batch (100 items) | 255 µs | 474 µs | **1.9x faster** |
84
+
85
+ *Benchmarks run on Python 3.11. Run `python -m benchmarks.run_benchmarks` to reproduce.*
86
+
87
+ ### Why it matters
88
+
89
+ - **Existing Marshmallow projects**: Incrementally adopt Pydantic validation without rewriting your API layer
90
+ - **Flask/webargs/apispec users**: Keep your integrations, get faster validation
91
+ - **Performance-sensitive APIs**: Nested model validation is 3-6x faster than native Marshmallow
92
+
70
93
  ## Features
71
94
 
72
95
  - **Pydantic Validation**: Leverage Pydantic's Rust-powered validation engine
@@ -213,7 +236,7 @@ class User(BaseModel):
213
236
  first: str
214
237
  last: str
215
238
 
216
- @computed_field
239
+ @computed_field # type: ignore[misc]
217
240
  @property
218
241
  def full_name(self) -> str:
219
242
  return f"{self.first} {self.last}"
@@ -237,6 +260,21 @@ schema.dump(user, exclude_unset=True)
237
260
  schema.dump(user, exclude_defaults=True)
238
261
  ```
239
262
 
263
+ ## JSON Serialization
264
+
265
+ Direct JSON string support:
266
+
267
+ ```python
268
+ # Deserialize from JSON string
269
+ user = schema.loads('{"name": "Alice", "email": "alice@example.com", "age": 30}')
270
+
271
+ # Serialize to JSON string
272
+ json_str = schema.dumps(user)
273
+
274
+ # Batch operations
275
+ users = schema.loads('[{"name": "Alice", ...}, {"name": "Bob", ...}]', many=True)
276
+ ```
277
+
240
278
  ## Flask-Marshmallow Integration
241
279
 
242
280
  ```python
@@ -288,6 +326,99 @@ spec = APISpec(
288
326
  spec.components.schema("User", schema=UserSchema)
289
327
  ```
290
328
 
329
+ ## flask-smorest Integration
330
+
331
+ Build REST APIs with automatic OpenAPI documentation:
332
+
333
+ ```python
334
+ from flask import Flask
335
+ from flask_smorest import Api, Blueprint
336
+ from pydantic import BaseModel, Field
337
+ from pydantic_marshmallow import schema_for
338
+
339
+ app = Flask(__name__)
340
+ app.config["API_TITLE"] = "My API"
341
+ app.config["API_VERSION"] = "v1"
342
+ app.config["OPENAPI_VERSION"] = "3.0.2"
343
+
344
+ api = Api(app)
345
+ blp = Blueprint("users", __name__, url_prefix="/users")
346
+
347
+ class UserCreate(BaseModel):
348
+ name: str = Field(min_length=1)
349
+ email: str
350
+
351
+ UserCreateSchema = schema_for(UserCreate)
352
+ UserSchema = schema_for(User)
353
+
354
+ @blp.post("/")
355
+ @blp.arguments(UserCreateSchema)
356
+ @blp.response(201, UserSchema)
357
+ def create_user(data):
358
+ # data is a Pydantic UserCreate instance
359
+ user = User(id=1, name=data.name, email=data.email)
360
+ return UserSchema().dump(user)
361
+
362
+ api.register_blueprint(blp)
363
+ ```
364
+
365
+ ## flask-rebar Integration
366
+
367
+ Build REST APIs with automatic Swagger documentation:
368
+
369
+ ```python
370
+ from flask import Flask
371
+ from flask_rebar import Rebar, get_validated_body
372
+ from pydantic_marshmallow import schema_for
373
+
374
+ app = Flask(__name__)
375
+ rebar = Rebar()
376
+ registry = rebar.create_handler_registry()
377
+
378
+ UserCreateSchema = schema_for(UserCreate)
379
+ UserSchema = schema_for(User)
380
+
381
+ @registry.handles(
382
+ rule="/users",
383
+ method="POST",
384
+ request_body_schema=UserCreateSchema(),
385
+ response_body_schema=UserSchema(),
386
+ )
387
+ def create_user():
388
+ data = get_validated_body() # Pydantic UserCreate instance
389
+ user = User(id=1, name=data.name, email=data.email)
390
+ return UserSchema().dump(user)
391
+
392
+ rebar.init_app(app)
393
+ ```
394
+
395
+ ## SQLAlchemy Pattern
396
+
397
+ Use Pydantic for API validation alongside SQLAlchemy ORM:
398
+
399
+ ```python
400
+ from sqlalchemy.orm import Session
401
+ from pydantic_marshmallow import schema_for
402
+
403
+ # Pydantic model for API validation
404
+ class UserCreate(BaseModel):
405
+ name: str = Field(min_length=1)
406
+ email: str
407
+
408
+ UserCreateSchema = schema_for(UserCreate)
409
+
410
+ def create_user(session: Session, data: dict):
411
+ # Validate API input with Pydantic
412
+ schema = UserCreateSchema()
413
+ validated = schema.load(data) # Returns Pydantic model
414
+
415
+ # Create ORM object from validated data
416
+ orm_user = UserModel(name=validated.name, email=validated.email)
417
+ session.add(orm_user)
418
+ session.commit()
419
+ return orm_user
420
+ ```
421
+
291
422
  ## HybridModel
292
423
 
293
424
  For models that need both Pydantic and Marshmallow APIs:
@@ -0,0 +1,12 @@
1
+ pydantic_marshmallow/__init__.py,sha256=EQON4HpiOFDtC33MrrE6WFW68GBtDlx1IqbDWxbqghU,3100
2
+ pydantic_marshmallow/bridge.py,sha256=MOBwr0casMgaPjP5GAcYjnG8bGgNpmpEa-zagjkClEY,52147
3
+ pydantic_marshmallow/errors.py,sha256=slWs9m6Iid32ZUg8uxzNbFoZWFqh7ZuiG5qPlcOaBuc,5304
4
+ pydantic_marshmallow/field_conversion.py,sha256=xFQtR63DwTDq-hGA_8Xe8sO6lFW6BCZNmnk5oxz5B6w,4262
5
+ pydantic_marshmallow/py.typed,sha256=GehYepOFQ5fvmwg3UbxoFj6w91p2kWD3KUBXL_rJRUI,86
6
+ pydantic_marshmallow/type_mapping.py,sha256=NYTJdmTVL-EjNW93ysb2KN9ASAj0mQu8rQpa_vOgqbU,6529
7
+ pydantic_marshmallow/validators.py,sha256=hg7ZTrjGHcyzfOtGy6T1LLGx4puRD3VSCLf0KN0PE8g,6686
8
+ pydantic_marshmallow-1.0.1.dist-info/licenses/LICENSE,sha256=y1B14nDO7pR7JdBNaWTLDt9MPBVRVuKZTUiRkw_eVgg,1071
9
+ pydantic_marshmallow-1.0.1.dist-info/METADATA,sha256=yNbwhXPJSg0WZAc8tfFo4U6fy57gX0eVNRf4JPLHO4g,14302
10
+ pydantic_marshmallow-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ pydantic_marshmallow-1.0.1.dist-info/top_level.txt,sha256=sQCtMe_dpBvCrrBM6YIz1UQmGSf-XjdoBUeA2uyuoh4,21
12
+ pydantic_marshmallow-1.0.1.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- pydantic_marshmallow/__init__.py,sha256=IeZH-ZcLxyy_OMfIn-IxbPRxskOnPxY94E7FLJEEBcA,2831
2
- pydantic_marshmallow/bridge.py,sha256=JkDF-BozWZtzTl31RMqRHPEV_eKbeybnsuEPNdrU--Q,47688
3
- pydantic_marshmallow/errors.py,sha256=slWs9m6Iid32ZUg8uxzNbFoZWFqh7ZuiG5qPlcOaBuc,5304
4
- pydantic_marshmallow/field_conversion.py,sha256=xFQtR63DwTDq-hGA_8Xe8sO6lFW6BCZNmnk5oxz5B6w,4262
5
- pydantic_marshmallow/py.typed,sha256=GehYepOFQ5fvmwg3UbxoFj6w91p2kWD3KUBXL_rJRUI,86
6
- pydantic_marshmallow/type_mapping.py,sha256=4gFVZV7WVywyugTyiE-y7saWaHmcaPeCs0vQjBQBQ8I,5285
7
- pydantic_marshmallow/validators.py,sha256=hg7ZTrjGHcyzfOtGy6T1LLGx4puRD3VSCLf0KN0PE8g,6686
8
- pydantic_marshmallow-1.0.0.dist-info/licenses/LICENSE,sha256=y1B14nDO7pR7JdBNaWTLDt9MPBVRVuKZTUiRkw_eVgg,1071
9
- pydantic_marshmallow-1.0.0.dist-info/METADATA,sha256=gVyLe10AtxyW4hJ9NTe43gIyPPBwigeu_BLWyopYfdg,10556
10
- pydantic_marshmallow-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
- pydantic_marshmallow-1.0.0.dist-info/top_level.txt,sha256=sQCtMe_dpBvCrrBM6YIz1UQmGSf-XjdoBUeA2uyuoh4,21
12
- pydantic_marshmallow-1.0.0.dist-info/RECORD,,