marshmallow 3.26.1__py3-none-any.whl → 4.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.
marshmallow/fields.py CHANGED
@@ -1,42 +1,43 @@
1
- # ruff: noqa: F841, SLF001
1
+ # ruff: noqa: SLF001
2
2
  from __future__ import annotations
3
3
 
4
+ import abc
4
5
  import collections
5
6
  import copy
6
7
  import datetime as dt
7
8
  import decimal
9
+ import email.utils
8
10
  import ipaddress
9
11
  import math
10
12
  import numbers
11
13
  import typing
12
14
  import uuid
13
- import warnings
14
15
  from collections.abc import Mapping as _Mapping
16
+ from enum import Enum as EnumType
17
+
18
+ try:
19
+ from typing import Unpack
20
+ except ImportError: # Remove when dropping Python 3.10
21
+ from typing_extensions import Unpack
22
+
23
+ # Remove when dropping Python 3.10
24
+ try:
25
+ from backports.datetime_fromisoformat import MonkeyPatch
26
+ except ImportError:
27
+ pass
28
+ else:
29
+ MonkeyPatch.patch_fromisoformat()
15
30
 
16
31
  from marshmallow import class_registry, types, utils, validate
17
- from marshmallow.base import FieldABC
32
+ from marshmallow.constants import missing as missing_
18
33
  from marshmallow.exceptions import (
19
- FieldInstanceResolutionError,
20
34
  StringNotCollectionError,
21
35
  ValidationError,
22
- )
23
- from marshmallow.utils import (
24
- is_aware,
25
- is_collection,
26
- resolve_field_instance,
27
- )
28
- from marshmallow.utils import (
29
- missing as missing_,
36
+ _FieldInstanceResolutionError,
30
37
  )
31
38
  from marshmallow.validate import And, Length
32
- from marshmallow.warnings import (
33
- ChangedInMarshmallow4Warning,
34
- RemovedInMarshmallow4Warning,
35
- )
36
39
 
37
40
  if typing.TYPE_CHECKING:
38
- from enum import Enum as EnumType
39
-
40
41
  from marshmallow.schema import Schema, SchemaMeta
41
42
 
42
43
 
@@ -80,9 +81,40 @@ __all__ = [
80
81
  "Url",
81
82
  ]
82
83
 
84
+ _InternalT = typing.TypeVar("_InternalT")
85
+
86
+
87
+ class _BaseFieldKwargs(typing.TypedDict, total=False):
88
+ load_default: typing.Any
89
+ dump_default: typing.Any
90
+ data_key: str | None
91
+ attribute: str | None
92
+ validate: types.Validator | typing.Iterable[types.Validator] | None
93
+ required: bool
94
+ allow_none: bool | None
95
+ load_only: bool
96
+ dump_only: bool
97
+ error_messages: dict[str, str] | None
98
+ metadata: typing.Mapping[str, typing.Any] | None
83
99
 
84
- class Field(FieldABC):
85
- """Base field from which other fields inherit.
100
+
101
+ def _resolve_field_instance(cls_or_instance: Field | type[Field]) -> Field:
102
+ """Return a Field instance from a Field class or instance.
103
+
104
+ :param cls_or_instance: Field class or instance.
105
+ """
106
+ if isinstance(cls_or_instance, type):
107
+ if not issubclass(cls_or_instance, Field):
108
+ raise _FieldInstanceResolutionError
109
+ return cls_or_instance()
110
+ if not isinstance(cls_or_instance, Field):
111
+ raise _FieldInstanceResolutionError
112
+ return cls_or_instance
113
+
114
+
115
+ class Field(typing.Generic[_InternalT]):
116
+ """Base field from which all other fields inherit.
117
+ This class should not be used directly within Schemas.
86
118
 
87
119
  :param dump_default: If set, this value will be used during serialization if the
88
120
  input value is missing. If not set, the field will be excluded from the
@@ -120,13 +152,13 @@ class Field(FieldABC):
120
152
  .. versionchanged:: 3.0.0b8
121
153
  Add ``data_key`` parameter for the specifying the key in the input and
122
154
  output data. This parameter replaced both ``load_from`` and ``dump_to``.
123
-
124
155
  .. versionchanged:: 3.13.0
125
156
  Replace ``missing`` and ``default`` parameters with ``load_default`` and ``dump_default``.
126
-
127
157
  .. versionchanged:: 3.24.0
128
158
  `Field <marshmallow.fields.Field>` should no longer be used as a field within a `Schema <marshmallow.Schema>`.
129
159
  Use `Raw <marshmallow.fields.Raw>` or another `Field <marshmallow.fields.Field>` subclass instead.
160
+ .. versionchanged:: 4.0.0
161
+ Remove ``context`` property.
130
162
  """
131
163
 
132
164
  # Some fields, such as Method fields and Function fields, are not expected
@@ -147,9 +179,7 @@ class Field(FieldABC):
147
179
  self,
148
180
  *,
149
181
  load_default: typing.Any = missing_,
150
- missing: typing.Any = missing_,
151
182
  dump_default: typing.Any = missing_,
152
- default: typing.Any = missing_,
153
183
  data_key: str | None = None,
154
184
  attribute: str | None = None,
155
185
  validate: types.Validator | typing.Iterable[types.Validator] | None = None,
@@ -159,34 +189,7 @@ class Field(FieldABC):
159
189
  dump_only: bool = False,
160
190
  error_messages: dict[str, str] | None = None,
161
191
  metadata: typing.Mapping[str, typing.Any] | None = None,
162
- **additional_metadata,
163
192
  ) -> None:
164
- if self.__class__ is Field:
165
- warnings.warn(
166
- "`Field` should not be instantiated. Use `fields.Raw` or "
167
- "another field subclass instead.",
168
- ChangedInMarshmallow4Warning,
169
- stacklevel=2,
170
- )
171
- # handle deprecated `default` and `missing` parameters
172
- if default is not missing_:
173
- warnings.warn(
174
- "The 'default' argument to fields is deprecated. "
175
- "Use 'dump_default' instead.",
176
- RemovedInMarshmallow4Warning,
177
- stacklevel=2,
178
- )
179
- if dump_default is missing_:
180
- dump_default = default
181
- if missing is not missing_:
182
- warnings.warn(
183
- "The 'missing' argument to fields is deprecated. "
184
- "Use 'load_default' instead.",
185
- RemovedInMarshmallow4Warning,
186
- stacklevel=2,
187
- )
188
- if load_default is missing_:
189
- load_default = missing
190
193
  self.dump_default = dump_default
191
194
  self.load_default = load_default
192
195
 
@@ -215,16 +218,7 @@ class Field(FieldABC):
215
218
  self.required = required
216
219
 
217
220
  metadata = metadata or {}
218
- self.metadata = {**metadata, **additional_metadata}
219
- if additional_metadata:
220
- warnings.warn(
221
- "Passing field metadata as keyword arguments is deprecated. Use the "
222
- "explicit `metadata=...` argument instead. "
223
- f"Additional metadata: {additional_metadata}",
224
- RemovedInMarshmallow4Warning,
225
- stacklevel=2,
226
- )
227
-
221
+ self.metadata = metadata
228
222
  # Collect default error message from self and parent classes
229
223
  messages: dict[str, str] = {}
230
224
  for cls in reversed(self.__class__.__mro__):
@@ -257,7 +251,7 @@ class Field(FieldABC):
257
251
  typing.Callable[[typing.Any, str, typing.Any], typing.Any] | None
258
252
  ) = None,
259
253
  default: typing.Any = missing_,
260
- ):
254
+ ) -> _InternalT:
261
255
  """Return the value for a given key from an object.
262
256
 
263
257
  :param obj: The object to get the value from.
@@ -269,7 +263,7 @@ class Field(FieldABC):
269
263
  check_key = attr if self.attribute is None else self.attribute
270
264
  return accessor_func(obj, check_key, default)
271
265
 
272
- def _validate(self, value: typing.Any):
266
+ def _validate(self, value: typing.Any) -> None:
273
267
  """Perform validation on ``value``. Raise a :exc:`ValidationError` if validation
274
268
  does not succeed.
275
269
  """
@@ -277,7 +271,7 @@ class Field(FieldABC):
277
271
 
278
272
  @property
279
273
  def _validate_all(self) -> typing.Callable[[typing.Any], None]:
280
- return And(*self.validators, error=self.error_messages["validator_failed"])
274
+ return And(*self.validators)
281
275
 
282
276
  def make_error(self, key: str, **kwargs) -> ValidationError:
283
277
  """Helper method to make a `ValidationError` with an error message
@@ -296,20 +290,6 @@ class Field(FieldABC):
296
290
  msg = msg.format(**kwargs)
297
291
  return ValidationError(msg)
298
292
 
299
- def fail(self, key: str, **kwargs):
300
- """Helper method that raises a `ValidationError` with an error message
301
- from ``self.error_messages``.
302
-
303
- .. deprecated:: 3.0.0
304
- Use `make_error <marshmallow.fields.Field.make_error>` instead.
305
- """
306
- warnings.warn(
307
- f'`Field.fail` is deprecated. Use `raise self.make_error("{key}", ...)` instead.',
308
- RemovedInMarshmallow4Warning,
309
- stacklevel=2,
310
- )
311
- raise self.make_error(key=key, **kwargs)
312
-
313
293
  def _validate_missing(self, value: typing.Any) -> None:
314
294
  """Validate missing values. Raise a :exc:`ValidationError` if
315
295
  `value` should be considered missing.
@@ -347,13 +327,33 @@ class Field(FieldABC):
347
327
  value = None
348
328
  return self._serialize(value, attr, obj, **kwargs)
349
329
 
330
+ # If value is None, None may be returned
331
+ @typing.overload
332
+ def deserialize(
333
+ self,
334
+ value: None,
335
+ attr: str | None = None,
336
+ data: typing.Mapping[str, typing.Any] | None = None,
337
+ **kwargs,
338
+ ) -> None | _InternalT: ...
339
+
340
+ # If value is not None, internal type is returned
341
+ @typing.overload
350
342
  def deserialize(
351
343
  self,
352
344
  value: typing.Any,
353
345
  attr: str | None = None,
354
346
  data: typing.Mapping[str, typing.Any] | None = None,
355
347
  **kwargs,
356
- ):
348
+ ) -> _InternalT: ...
349
+
350
+ def deserialize(
351
+ self,
352
+ value: typing.Any,
353
+ attr: str | None = None,
354
+ data: typing.Mapping[str, typing.Any] | None = None,
355
+ **kwargs,
356
+ ) -> _InternalT | None:
357
357
  """Deserialize ``value``.
358
358
 
359
359
  :param value: The value to deserialize.
@@ -377,21 +377,21 @@ class Field(FieldABC):
377
377
 
378
378
  # Methods for concrete classes to override.
379
379
 
380
- def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
380
+ def _bind_to_schema(self, field_name: str, parent: Schema | Field) -> None:
381
381
  """Update field with values from its parent schema. Called by
382
- `Schema._bind_field <marshmallow.Schema._bind_field>`.
382
+ `Schema._bind_field <marshmallow.Schema._bind_field>`.
383
383
 
384
384
  :param field_name: Field name set in schema.
385
- :param schema: Parent object.
385
+ :param parent: Parent object.
386
386
  """
387
- self.parent = self.parent or schema
387
+ self.parent = self.parent or parent
388
388
  self.name = self.name or field_name
389
389
  self.root = self.root or (
390
- self.parent.root if isinstance(self.parent, FieldABC) else self.parent
390
+ self.parent.root if isinstance(self.parent, Field) else self.parent
391
391
  )
392
392
 
393
393
  def _serialize(
394
- self, value: typing.Any, attr: str | None, obj: typing.Any, **kwargs
394
+ self, value: _InternalT | None, attr: str | None, obj: typing.Any, **kwargs
395
395
  ) -> typing.Any:
396
396
  """Serializes ``value`` to a basic Python datatype. Noop by default.
397
397
  Concrete :class:`Field` classes should implement this method.
@@ -418,7 +418,7 @@ class Field(FieldABC):
418
418
  attr: str | None,
419
419
  data: typing.Mapping[str, typing.Any] | None,
420
420
  **kwargs,
421
- ) -> typing.Any:
421
+ ) -> _InternalT:
422
422
  """Deserialize value. Concrete :class:`Field` classes should implement this method.
423
423
 
424
424
  :param value: The value to be deserialized.
@@ -433,59 +433,8 @@ class Field(FieldABC):
433
433
  """
434
434
  return value
435
435
 
436
- # Properties
437
-
438
- @property
439
- def context(self) -> dict | None:
440
- """The context dictionary for the parent `Schema <marshmallow.Schema>`."""
441
- if self.parent:
442
- return self.parent.context
443
- return None
444
-
445
- # the default and missing properties are provided for compatibility and
446
- # emit warnings when they are accessed and set
447
- @property
448
- def default(self):
449
- warnings.warn(
450
- "The 'default' attribute of fields is deprecated. "
451
- "Use 'dump_default' instead.",
452
- RemovedInMarshmallow4Warning,
453
- stacklevel=2,
454
- )
455
- return self.dump_default
456
-
457
- @default.setter
458
- def default(self, value):
459
- warnings.warn(
460
- "The 'default' attribute of fields is deprecated. "
461
- "Use 'dump_default' instead.",
462
- RemovedInMarshmallow4Warning,
463
- stacklevel=2,
464
- )
465
- self.dump_default = value
466
436
 
467
- @property
468
- def missing(self):
469
- warnings.warn(
470
- "The 'missing' attribute of fields is deprecated. "
471
- "Use 'load_default' instead.",
472
- RemovedInMarshmallow4Warning,
473
- stacklevel=2,
474
- )
475
- return self.load_default
476
-
477
- @missing.setter
478
- def missing(self, value):
479
- warnings.warn(
480
- "The 'missing' attribute of fields is deprecated. "
481
- "Use 'load_default' instead.",
482
- RemovedInMarshmallow4Warning,
483
- stacklevel=2,
484
- )
485
- self.load_default = value
486
-
487
-
488
- class Raw(Field):
437
+ class Raw(Field[typing.Any]):
489
438
  """Field that applies no formatting."""
490
439
 
491
440
 
@@ -551,61 +500,45 @@ class Nested(Field):
551
500
  | typing.Callable[[], Schema | SchemaMeta | dict[str, Field]]
552
501
  ),
553
502
  *,
554
- dump_default: typing.Any = missing_,
555
- default: typing.Any = missing_,
556
503
  only: types.StrSequenceOrSet | None = None,
557
504
  exclude: types.StrSequenceOrSet = (),
558
505
  many: bool = False,
559
- unknown: str | None = None,
560
- **kwargs,
506
+ unknown: types.UnknownOption | None = None,
507
+ **kwargs: Unpack[_BaseFieldKwargs],
561
508
  ):
562
509
  # Raise error if only or exclude is passed as string, not list of strings
563
- if only is not None and not is_collection(only):
510
+ if only is not None and not utils.is_sequence_but_not_string(only):
564
511
  raise StringNotCollectionError('"only" should be a collection of strings.')
565
- if not is_collection(exclude):
512
+ if not utils.is_sequence_but_not_string(exclude):
566
513
  raise StringNotCollectionError(
567
514
  '"exclude" should be a collection of strings.'
568
515
  )
569
- if nested == "self":
570
- warnings.warn(
571
- "Passing 'self' to `Nested` is deprecated. "
572
- "Use `Nested(lambda: MySchema(...))` instead.",
573
- RemovedInMarshmallow4Warning,
574
- stacklevel=2,
575
- )
576
516
  self.nested = nested
577
517
  self.only = only
578
518
  self.exclude = exclude
579
519
  self.many = many
580
520
  self.unknown = unknown
581
521
  self._schema: Schema | None = None # Cached Schema instance
582
- super().__init__(default=default, dump_default=dump_default, **kwargs)
522
+ super().__init__(**kwargs)
583
523
 
584
524
  @property
585
525
  def schema(self) -> Schema:
586
- """The nested `Schema <marshmallow.Schema>` object.
587
-
588
- .. versionchanged:: 1.0.0
589
- Renamed from ``serializer`` to ``schema``.
590
- """
526
+ """The nested Schema object."""
591
527
  if not self._schema:
592
- # Inherit context from parent.
593
- context = getattr(self.parent, "context", {})
594
528
  if callable(self.nested) and not isinstance(self.nested, type):
595
529
  nested = self.nested()
596
530
  else:
597
531
  nested = typing.cast("Schema", self.nested)
598
532
  # defer the import of `marshmallow.schema` to avoid circular imports
599
- from marshmallow.schema import Schema
533
+ from marshmallow.schema import Schema # noqa: PLC0415
600
534
 
601
535
  if isinstance(nested, dict):
602
536
  nested = Schema.from_dict(nested)
603
537
 
604
538
  if isinstance(nested, Schema):
605
539
  self._schema = copy.copy(nested)
606
- self._schema.context.update(context)
607
540
  # Respect only and exclude passed from parent and re-initialize fields
608
- set_class = typing.cast(type[set], self._schema.set_class)
541
+ set_class = typing.cast("type[set]", self._schema.set_class)
609
542
  if self.only is not None:
610
543
  if self._schema.only is not None:
611
544
  original = self._schema.only
@@ -624,15 +557,12 @@ class Nested(Field):
624
557
  "`Nested` fields must be passed a "
625
558
  f"`Schema`, not {nested.__class__}."
626
559
  )
627
- elif nested == "self":
628
- schema_class = typing.cast(Schema, self.root).__class__
629
560
  else:
630
- schema_class = class_registry.get_class(nested, all=False)
561
+ schema_class = class_registry.get_class(nested, all=False) # type: ignore[unreachable]
631
562
  self._schema = schema_class(
632
563
  many=self.many,
633
564
  only=self.only,
634
565
  exclude=self.exclude,
635
- context=context,
636
566
  load_only=self._nested_normalized_option("load_only"),
637
567
  dump_only=self._nested_normalized_option("dump_only"),
638
568
  )
@@ -661,7 +591,9 @@ class Nested(Field):
661
591
  raise self.make_error("type", input=value, type=value.__class__.__name__)
662
592
 
663
593
  def _load(
664
- self, value: typing.Any, partial: bool | types.StrSequenceOrSet | None = None
594
+ self,
595
+ value: typing.Any,
596
+ partial: bool | types.StrSequenceOrSet | None = None, # noqa: FBT001
665
597
  ):
666
598
  try:
667
599
  valid_data = self.schema.load(value, unknown=self.unknown, partial=partial)
@@ -676,9 +608,9 @@ class Nested(Field):
676
608
  value: typing.Any,
677
609
  attr: str | None,
678
610
  data: typing.Mapping[str, typing.Any] | None,
679
- partial: bool | types.StrSequenceOrSet | None = None,
611
+ partial: bool | types.StrSequenceOrSet | None = None, # noqa: FBT001
680
612
  **kwargs,
681
- ) -> typing.Any:
613
+ ):
682
614
  """Same as :meth:`Field._deserialize` with additional ``partial`` argument.
683
615
 
684
616
  :param partial: For nested schemas, the ``partial``
@@ -712,9 +644,8 @@ class Pluck(Nested):
712
644
  loaded = AlbumSchema().load(in_data) # => {'artist': {'id': 42}}
713
645
  dumped = AlbumSchema().dump(loaded) # => {'artist': 42}
714
646
 
715
- :param nested: The Schema class or class name (string)
716
- to nest, or ``"self"`` to nest the `Schema <marshmallow.Schema>` within itself.
717
- :param field_name: The key to pluck a value from.
647
+ :param nested: The Schema class or class name (string) to nest
648
+ :param str field_name: The key to pluck a value from.
718
649
  :param kwargs: The same keyword arguments that :class:`Nested` receives.
719
650
  """
720
651
 
@@ -724,8 +655,8 @@ class Pluck(Nested):
724
655
  field_name: str,
725
656
  *,
726
657
  many: bool = False,
727
- unknown: str | None = None,
728
- **kwargs,
658
+ unknown: types.UnknownOption | None = None,
659
+ **kwargs: Unpack[_BaseFieldKwargs],
729
660
  ):
730
661
  super().__init__(
731
662
  nested, only=(field_name,), many=many, unknown=unknown, **kwargs
@@ -754,7 +685,7 @@ class Pluck(Nested):
754
685
  return self._load(value, partial=partial)
755
686
 
756
687
 
757
- class List(Field):
688
+ class List(Field[list[typing.Optional[_InternalT]]]):
758
689
  """A list field, composed with another `Field` class or
759
690
  instance.
760
691
 
@@ -772,33 +703,37 @@ class List(Field):
772
703
  #: Default error messages.
773
704
  default_error_messages = {"invalid": "Not a valid list."}
774
705
 
775
- def __init__(self, cls_or_instance: Field | type[Field], **kwargs):
706
+ def __init__(
707
+ self,
708
+ cls_or_instance: Field[_InternalT] | type[Field[_InternalT]],
709
+ **kwargs: Unpack[_BaseFieldKwargs],
710
+ ):
776
711
  super().__init__(**kwargs)
777
712
  try:
778
- self.inner = resolve_field_instance(cls_or_instance)
779
- except FieldInstanceResolutionError as error:
713
+ self.inner: Field[_InternalT] = _resolve_field_instance(cls_or_instance)
714
+ except _FieldInstanceResolutionError as error:
780
715
  raise ValueError(
781
716
  "The list elements must be a subclass or instance of "
782
- "marshmallow.base.FieldABC."
717
+ "marshmallow.fields.Field."
783
718
  ) from error
784
719
  if isinstance(self.inner, Nested):
785
720
  self.only = self.inner.only
786
721
  self.exclude = self.inner.exclude
787
722
 
788
- def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
789
- super()._bind_to_schema(field_name, schema)
723
+ def _bind_to_schema(self, field_name: str, parent: Schema | Field) -> None:
724
+ super()._bind_to_schema(field_name, parent)
790
725
  self.inner = copy.deepcopy(self.inner)
791
726
  self.inner._bind_to_schema(field_name, self)
792
727
  if isinstance(self.inner, Nested):
793
728
  self.inner.only = self.only
794
729
  self.inner.exclude = self.exclude
795
730
 
796
- def _serialize(self, value, attr, obj, **kwargs) -> list[typing.Any] | None:
731
+ def _serialize(self, value, attr, obj, **kwargs) -> list[_InternalT] | None:
797
732
  if value is None:
798
733
  return None
799
734
  return [self.inner._serialize(each, attr, obj, **kwargs) for each in value]
800
735
 
801
- def _deserialize(self, value, attr, data, **kwargs) -> list[typing.Any]:
736
+ def _deserialize(self, value, attr, data, **kwargs) -> list[_InternalT | None]:
802
737
  if not utils.is_collection(value):
803
738
  raise self.make_error("invalid")
804
739
 
@@ -809,14 +744,14 @@ class List(Field):
809
744
  result.append(self.inner.deserialize(each, **kwargs))
810
745
  except ValidationError as error:
811
746
  if error.valid_data is not None:
812
- result.append(error.valid_data)
747
+ result.append(typing.cast("_InternalT", error.valid_data))
813
748
  errors.update({idx: error.messages})
814
749
  if errors:
815
750
  raise ValidationError(errors, valid_data=result)
816
751
  return result
817
752
 
818
753
 
819
- class Tuple(Field):
754
+ class Tuple(Field[tuple]):
820
755
  """A tuple field, composed of a fixed number of other `Field` classes or
821
756
  instances
822
757
 
@@ -842,7 +777,7 @@ class Tuple(Field):
842
777
  def __init__(
843
778
  self,
844
779
  tuple_fields: typing.Iterable[Field] | typing.Iterable[type[Field]],
845
- **kwargs,
780
+ **kwargs: Unpack[_BaseFieldKwargs],
846
781
  ):
847
782
  super().__init__(**kwargs)
848
783
  if not utils.is_collection(tuple_fields):
@@ -852,19 +787,19 @@ class Tuple(Field):
852
787
 
853
788
  try:
854
789
  self.tuple_fields = [
855
- resolve_field_instance(cls_or_instance)
790
+ _resolve_field_instance(cls_or_instance)
856
791
  for cls_or_instance in tuple_fields
857
792
  ]
858
- except FieldInstanceResolutionError as error:
793
+ except _FieldInstanceResolutionError as error:
859
794
  raise ValueError(
860
795
  'Elements of "tuple_fields" must be subclasses or '
861
- "instances of marshmallow.base.FieldABC."
796
+ "instances of marshmallow.fields.Field."
862
797
  ) from error
863
798
 
864
799
  self.validate_length = Length(equal=len(self.tuple_fields))
865
800
 
866
- def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
867
- super()._bind_to_schema(field_name, schema)
801
+ def _bind_to_schema(self, field_name: str, parent: Schema | Field) -> None:
802
+ super()._bind_to_schema(field_name, parent)
868
803
  new_tuple_fields = []
869
804
  for field in self.tuple_fields:
870
805
  new_field = copy.deepcopy(field)
@@ -873,7 +808,9 @@ class Tuple(Field):
873
808
 
874
809
  self.tuple_fields = new_tuple_fields
875
810
 
876
- def _serialize(self, value, attr, obj, **kwargs) -> tuple | None:
811
+ def _serialize(
812
+ self, value: tuple | None, attr: str | None, obj: typing.Any, **kwargs
813
+ ) -> tuple | None:
877
814
  if value is None:
878
815
  return None
879
816
 
@@ -882,8 +819,14 @@ class Tuple(Field):
882
819
  for field, each in zip(self.tuple_fields, value)
883
820
  )
884
821
 
885
- def _deserialize(self, value, attr, data, **kwargs) -> tuple:
886
- if not utils.is_collection(value):
822
+ def _deserialize(
823
+ self,
824
+ value: typing.Any,
825
+ attr: str | None,
826
+ data: typing.Mapping[str, typing.Any] | None,
827
+ **kwargs,
828
+ ) -> tuple:
829
+ if not utils.is_sequence_but_not_string(value):
887
830
  raise self.make_error("invalid")
888
831
 
889
832
  self.validate_length(value)
@@ -904,7 +847,7 @@ class Tuple(Field):
904
847
  return tuple(result)
905
848
 
906
849
 
907
- class String(Field):
850
+ class String(Field[str]):
908
851
  """A string field.
909
852
 
910
853
  :param kwargs: The same keyword arguments that :class:`Field` receives.
@@ -921,7 +864,7 @@ class String(Field):
921
864
  return None
922
865
  return utils.ensure_text_type(value)
923
866
 
924
- def _deserialize(self, value, attr, data, **kwargs) -> typing.Any:
867
+ def _deserialize(self, value, attr, data, **kwargs) -> str:
925
868
  if not isinstance(value, (str, bytes)):
926
869
  raise self.make_error("invalid")
927
870
  try:
@@ -930,16 +873,14 @@ class String(Field):
930
873
  raise self.make_error("invalid_utf8") from error
931
874
 
932
875
 
933
- class UUID(String):
876
+ class UUID(Field[uuid.UUID]):
934
877
  """A UUID field."""
935
878
 
936
879
  #: Default error messages.
937
880
  default_error_messages = {"invalid_uuid": "Not a valid UUID."}
938
881
 
939
- def _validated(self, value) -> uuid.UUID | None:
882
+ def _validated(self, value) -> uuid.UUID:
940
883
  """Format the value or raise a :exc:`ValidationError` if an error occurs."""
941
- if value is None:
942
- return None
943
884
  if isinstance(value, uuid.UUID):
944
885
  return value
945
886
  try:
@@ -949,15 +890,20 @@ class UUID(String):
949
890
  except (ValueError, AttributeError, TypeError) as error:
950
891
  raise self.make_error("invalid_uuid") from error
951
892
 
952
- def _deserialize(self, value, attr, data, **kwargs) -> uuid.UUID | None:
893
+ def _serialize(self, value, attr, obj, **kwargs) -> str | None:
894
+ if value is None:
895
+ return None
896
+ return str(value)
897
+
898
+ def _deserialize(self, value, attr, data, **kwargs) -> uuid.UUID:
953
899
  return self._validated(value)
954
900
 
955
901
 
956
- _NumType = typing.TypeVar("_NumType")
902
+ _NumT = typing.TypeVar("_NumT")
957
903
 
958
904
 
959
- class Number(Field, typing.Generic[_NumType]):
960
- """Base class for number fields.
905
+ class Number(Field[_NumT]):
906
+ """Base class for number fields. This class should not be used within schemas.
961
907
 
962
908
  :param as_string: If `True`, format the serialized value as a string.
963
909
  :param kwargs: The same keyword arguments that :class:`Field` receives.
@@ -967,7 +913,7 @@ class Number(Field, typing.Generic[_NumType]):
967
913
  Use `Integer <marshmallow.fields.Integer>`, `Float <marshmallow.fields.Float>`, or `Decimal <marshmallow.fields.Decimal>` instead.
968
914
  """
969
915
 
970
- num_type: type = float
916
+ num_type: type[_NumT]
971
917
 
972
918
  #: Default error messages.
973
919
  default_error_messages = {
@@ -975,21 +921,15 @@ class Number(Field, typing.Generic[_NumType]):
975
921
  "too_large": "Number too large.",
976
922
  }
977
923
 
978
- def __init__(self, *, as_string: bool = False, **kwargs):
979
- if self.__class__ is Number:
980
- warnings.warn(
981
- "`Number` field should not be instantiated. Use `Integer`, `Float`, or `Decimal` instead.",
982
- ChangedInMarshmallow4Warning,
983
- stacklevel=2,
984
- )
924
+ def __init__(self, *, as_string: bool = False, **kwargs: Unpack[_BaseFieldKwargs]):
985
925
  self.as_string = as_string
986
926
  super().__init__(**kwargs)
987
927
 
988
- def _format_num(self, value) -> _NumType:
928
+ def _format_num(self, value) -> _NumT:
989
929
  """Return the number value for value, given this field's `num_type`."""
990
- return self.num_type(value)
930
+ return self.num_type(value) # type: ignore[call-arg]
991
931
 
992
- def _validated(self, value: typing.Any) -> _NumType:
932
+ def _validated(self, value: typing.Any) -> _NumT:
993
933
  """Format the value or raise a :exc:`ValidationError` if an error occurs."""
994
934
  # (value is True or value is False) is ~5x faster than isinstance(value, bool)
995
935
  if value is True or value is False:
@@ -1001,17 +941,17 @@ class Number(Field, typing.Generic[_NumType]):
1001
941
  except OverflowError as error:
1002
942
  raise self.make_error("too_large", input=value) from error
1003
943
 
1004
- def _to_string(self, value: _NumType) -> str:
944
+ def _to_string(self, value: _NumT) -> str:
1005
945
  return str(value)
1006
946
 
1007
- def _serialize(self, value, attr, obj, **kwargs) -> str | _NumType | None:
947
+ def _serialize(self, value, attr, obj, **kwargs) -> str | _NumT | None:
1008
948
  """Return a string if `self.as_string=True`, otherwise return this field's `num_type`."""
1009
949
  if value is None:
1010
950
  return None
1011
- ret: _NumType = self._format_num(value)
951
+ ret: _NumT = self._format_num(value)
1012
952
  return self._to_string(ret) if self.as_string else ret
1013
953
 
1014
- def _deserialize(self, value, attr, data, **kwargs) -> _NumType | None:
954
+ def _deserialize(self, value, attr, data, **kwargs) -> _NumT:
1015
955
  return self._validated(value)
1016
956
 
1017
957
 
@@ -1028,9 +968,15 @@ class Integer(Number[int]):
1028
968
  #: Default error messages.
1029
969
  default_error_messages = {"invalid": "Not a valid integer."}
1030
970
 
1031
- def __init__(self, *, strict: bool = False, **kwargs):
971
+ def __init__(
972
+ self,
973
+ *,
974
+ strict: bool = False,
975
+ as_string: bool = False,
976
+ **kwargs: Unpack[_BaseFieldKwargs],
977
+ ):
1032
978
  self.strict = strict
1033
- super().__init__(**kwargs)
979
+ super().__init__(as_string=as_string, **kwargs)
1034
980
 
1035
981
  # override Number
1036
982
  def _validated(self, value: typing.Any) -> int:
@@ -1055,7 +1001,13 @@ class Float(Number[float]):
1055
1001
  "special": "Special numeric values (nan or infinity) are not permitted."
1056
1002
  }
1057
1003
 
1058
- def __init__(self, *, allow_nan: bool = False, as_string: bool = False, **kwargs):
1004
+ def __init__(
1005
+ self,
1006
+ *,
1007
+ allow_nan: bool = False,
1008
+ as_string: bool = False,
1009
+ **kwargs: Unpack[_BaseFieldKwargs],
1010
+ ):
1059
1011
  self.allow_nan = allow_nan
1060
1012
  super().__init__(as_string=as_string, **kwargs)
1061
1013
 
@@ -1100,8 +1052,6 @@ class Decimal(Number[decimal.Decimal]):
1100
1052
  :param as_string: If `True`, serialize to a string instead of a Python
1101
1053
  `decimal.Decimal` type.
1102
1054
  :param kwargs: The same keyword arguments that :class:`Number` receives.
1103
-
1104
- .. versionadded:: 1.2.0
1105
1055
  """
1106
1056
 
1107
1057
  num_type = decimal.Decimal
@@ -1118,7 +1068,7 @@ class Decimal(Number[decimal.Decimal]):
1118
1068
  *,
1119
1069
  allow_nan: bool = False,
1120
1070
  as_string: bool = False,
1121
- **kwargs,
1071
+ **kwargs: Unpack[_BaseFieldKwargs],
1122
1072
  ):
1123
1073
  self.places = (
1124
1074
  decimal.Decimal((0, (1,), -places)) if places is not None else None
@@ -1152,7 +1102,7 @@ class Decimal(Number[decimal.Decimal]):
1152
1102
  return format(value, "f")
1153
1103
 
1154
1104
 
1155
- class Boolean(Field):
1105
+ class Boolean(Field[bool]):
1156
1106
  """A boolean field.
1157
1107
 
1158
1108
  :param truthy: Values that will (de)serialize to `True`. If an empty
@@ -1213,7 +1163,7 @@ class Boolean(Field):
1213
1163
  *,
1214
1164
  truthy: typing.Iterable | None = None,
1215
1165
  falsy: typing.Iterable | None = None,
1216
- **kwargs,
1166
+ **kwargs: Unpack[_BaseFieldKwargs],
1217
1167
  ):
1218
1168
  super().__init__(**kwargs)
1219
1169
 
@@ -1222,23 +1172,13 @@ class Boolean(Field):
1222
1172
  if falsy is not None:
1223
1173
  self.falsy = set(falsy)
1224
1174
 
1225
- def _serialize(
1226
- self, value: typing.Any, attr: str | None, obj: typing.Any, **kwargs
1227
- ):
1228
- if value is None:
1229
- return None
1230
-
1231
- try:
1232
- if value in self.truthy:
1233
- return True
1234
- if value in self.falsy:
1235
- return False
1236
- except TypeError:
1237
- pass
1238
-
1239
- return bool(value)
1240
-
1241
- def _deserialize(self, value, attr, data, **kwargs):
1175
+ def _deserialize(
1176
+ self,
1177
+ value: typing.Any,
1178
+ attr: str | None,
1179
+ data: typing.Mapping[str, typing.Any] | None,
1180
+ **kwargs,
1181
+ ) -> bool:
1242
1182
  if not self.truthy:
1243
1183
  return bool(value)
1244
1184
  try:
@@ -1251,69 +1191,45 @@ class Boolean(Field):
1251
1191
  raise self.make_error("invalid", input=value)
1252
1192
 
1253
1193
 
1254
- class DateTime(Field):
1255
- """A formatted datetime string.
1194
+ _D = typing.TypeVar("_D", dt.datetime, dt.date, dt.time)
1256
1195
 
1257
- Example: ``'2014-12-22T03:12:58.019077+00:00'``
1258
1196
 
1259
- :param format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601),
1260
- ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp) or a date format string.
1261
- If `None`, defaults to "iso".
1262
- :param kwargs: The same keyword arguments that :class:`Field` receives.
1197
+ class _TemporalField(Field[_D], metaclass=abc.ABCMeta):
1198
+ """Base field for date and time related fields including common (de)serialization logic."""
1263
1199
 
1264
- .. versionchanged:: 3.0.0rc9
1265
- Does not modify timezone information on (de)serialization.
1266
- .. versionchanged:: 3.19
1267
- Add timestamp as a format.
1268
- """
1269
-
1270
- SERIALIZATION_FUNCS: dict[str, typing.Callable[[typing.Any], str | float]] = {
1271
- "iso": utils.isoformat,
1272
- "iso8601": utils.isoformat,
1273
- "rfc": utils.rfcformat,
1274
- "rfc822": utils.rfcformat,
1275
- "timestamp": utils.timestamp,
1276
- "timestamp_ms": utils.timestamp_ms,
1277
- }
1278
-
1279
- DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], typing.Any]] = {
1280
- "iso": utils.from_iso_datetime,
1281
- "iso8601": utils.from_iso_datetime,
1282
- "rfc": utils.from_rfc,
1283
- "rfc822": utils.from_rfc,
1284
- "timestamp": utils.from_timestamp,
1285
- "timestamp_ms": utils.from_timestamp_ms,
1286
- }
1287
-
1288
- DEFAULT_FORMAT = "iso"
1289
-
1290
- OBJ_TYPE = "datetime"
1291
-
1292
- SCHEMA_OPTS_VAR_NAME = "datetimeformat"
1200
+ # Subclasses should define each of these class constants
1201
+ SERIALIZATION_FUNCS: dict[str, typing.Callable[[_D], str | float]]
1202
+ DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], _D]]
1203
+ DEFAULT_FORMAT: str
1204
+ OBJ_TYPE: str
1205
+ SCHEMA_OPTS_VAR_NAME: str
1293
1206
 
1294
- #: Default error messages.
1295
1207
  default_error_messages = {
1296
1208
  "invalid": "Not a valid {obj_type}.",
1297
1209
  "invalid_awareness": "Not a valid {awareness} {obj_type}.",
1298
1210
  "format": '"{input}" cannot be formatted as a {obj_type}.',
1299
1211
  }
1300
1212
 
1301
- def __init__(self, format: str | None = None, **kwargs) -> None: # noqa: A002
1213
+ def __init__(
1214
+ self,
1215
+ format: str | None = None, # noqa: A002
1216
+ **kwargs: Unpack[_BaseFieldKwargs],
1217
+ ) -> None:
1302
1218
  super().__init__(**kwargs)
1303
1219
  # Allow this to be None. It may be set later in the ``_serialize``
1304
1220
  # or ``_deserialize`` methods. This allows a Schema to dynamically set the
1305
1221
  # format, e.g. from a Meta option
1306
1222
  self.format = format
1307
1223
 
1308
- def _bind_to_schema(self, field_name, schema):
1309
- super()._bind_to_schema(field_name, schema)
1224
+ def _bind_to_schema(self, field_name, parent):
1225
+ super()._bind_to_schema(field_name, parent)
1310
1226
  self.format = (
1311
1227
  self.format
1312
1228
  or getattr(self.root.opts, self.SCHEMA_OPTS_VAR_NAME)
1313
1229
  or self.DEFAULT_FORMAT
1314
1230
  )
1315
1231
 
1316
- def _serialize(self, value, attr, obj, **kwargs) -> str | float | None:
1232
+ def _serialize(self, value: _D | None, attr, obj, **kwargs) -> str | float | None:
1317
1233
  if value is None:
1318
1234
  return None
1319
1235
  data_format = self.format or self.DEFAULT_FORMAT
@@ -1322,7 +1238,10 @@ class DateTime(Field):
1322
1238
  return format_func(value)
1323
1239
  return value.strftime(data_format)
1324
1240
 
1325
- def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
1241
+ def _deserialize(self, value, attr, data, **kwargs) -> _D:
1242
+ internal_type: type[_D] = getattr(dt, self.OBJ_TYPE)
1243
+ if isinstance(value, internal_type):
1244
+ return value
1326
1245
  data_format = self.format or self.DEFAULT_FORMAT
1327
1246
  func = self.DESERIALIZATION_FUNCS.get(data_format)
1328
1247
  try:
@@ -1334,6 +1253,51 @@ class DateTime(Field):
1334
1253
  "invalid", input=value, obj_type=self.OBJ_TYPE
1335
1254
  ) from error
1336
1255
 
1256
+ @staticmethod
1257
+ @abc.abstractmethod
1258
+ def _make_object_from_format(value: typing.Any, data_format: str) -> _D: ...
1259
+
1260
+
1261
+ class DateTime(_TemporalField[dt.datetime]):
1262
+ """A formatted datetime string.
1263
+
1264
+ Example: ``'2014-12-22T03:12:58.019077+00:00'``
1265
+
1266
+ :param format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601),
1267
+ ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp) or a date format string.
1268
+ If `None`, defaults to "iso".
1269
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
1270
+
1271
+ .. versionchanged:: 3.0.0rc9
1272
+ Does not modify timezone information on (de)serialization.
1273
+ .. versionchanged:: 3.19
1274
+ Add timestamp as a format.
1275
+ """
1276
+
1277
+ SERIALIZATION_FUNCS: dict[str, typing.Callable[[dt.datetime], str | float]] = {
1278
+ "iso": dt.datetime.isoformat,
1279
+ "iso8601": dt.datetime.isoformat,
1280
+ "rfc": email.utils.format_datetime,
1281
+ "rfc822": email.utils.format_datetime,
1282
+ "timestamp": utils.timestamp,
1283
+ "timestamp_ms": utils.timestamp_ms,
1284
+ }
1285
+
1286
+ DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], dt.datetime]] = {
1287
+ "iso": dt.datetime.fromisoformat,
1288
+ "iso8601": dt.datetime.fromisoformat,
1289
+ "rfc": email.utils.parsedate_to_datetime,
1290
+ "rfc822": email.utils.parsedate_to_datetime,
1291
+ "timestamp": utils.from_timestamp,
1292
+ "timestamp_ms": utils.from_timestamp_ms,
1293
+ }
1294
+
1295
+ DEFAULT_FORMAT = "iso"
1296
+
1297
+ OBJ_TYPE = "datetime"
1298
+
1299
+ SCHEMA_OPTS_VAR_NAME = "datetimeformat"
1300
+
1337
1301
  @staticmethod
1338
1302
  def _make_object_from_format(value, data_format) -> dt.datetime:
1339
1303
  return dt.datetime.strptime(value, data_format)
@@ -1359,14 +1323,14 @@ class NaiveDateTime(DateTime):
1359
1323
  format: str | None = None, # noqa: A002
1360
1324
  *,
1361
1325
  timezone: dt.timezone | None = None,
1362
- **kwargs,
1326
+ **kwargs: Unpack[_BaseFieldKwargs],
1363
1327
  ) -> None:
1364
1328
  super().__init__(format=format, **kwargs)
1365
1329
  self.timezone = timezone
1366
1330
 
1367
1331
  def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
1368
1332
  ret = super()._deserialize(value, attr, data, **kwargs)
1369
- if is_aware(ret):
1333
+ if utils.is_aware(ret):
1370
1334
  if self.timezone is None:
1371
1335
  raise self.make_error(
1372
1336
  "invalid_awareness",
@@ -1396,14 +1360,14 @@ class AwareDateTime(DateTime):
1396
1360
  format: str | None = None, # noqa: A002
1397
1361
  *,
1398
1362
  default_timezone: dt.tzinfo | None = None,
1399
- **kwargs,
1363
+ **kwargs: Unpack[_BaseFieldKwargs],
1400
1364
  ) -> None:
1401
1365
  super().__init__(format=format, **kwargs)
1402
1366
  self.default_timezone = default_timezone
1403
1367
 
1404
1368
  def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
1405
1369
  ret = super()._deserialize(value, attr, data, **kwargs)
1406
- if not is_aware(ret):
1370
+ if not utils.is_aware(ret):
1407
1371
  if self.default_timezone is None:
1408
1372
  raise self.make_error(
1409
1373
  "invalid_awareness",
@@ -1414,7 +1378,7 @@ class AwareDateTime(DateTime):
1414
1378
  return ret
1415
1379
 
1416
1380
 
1417
- class Time(DateTime):
1381
+ class Time(_TemporalField[dt.time]):
1418
1382
  """A formatted time string.
1419
1383
 
1420
1384
  Example: ``'03:12:58.019077'``
@@ -1424,9 +1388,15 @@ class Time(DateTime):
1424
1388
  :param kwargs: The same keyword arguments that :class:`Field` receives.
1425
1389
  """
1426
1390
 
1427
- SERIALIZATION_FUNCS = {"iso": utils.to_iso_time, "iso8601": utils.to_iso_time}
1391
+ SERIALIZATION_FUNCS = {
1392
+ "iso": dt.time.isoformat,
1393
+ "iso8601": dt.time.isoformat,
1394
+ }
1428
1395
 
1429
- DESERIALIZATION_FUNCS = {"iso": utils.from_iso_time, "iso8601": utils.from_iso_time}
1396
+ DESERIALIZATION_FUNCS = {
1397
+ "iso": dt.time.fromisoformat,
1398
+ "iso8601": dt.time.fromisoformat,
1399
+ }
1430
1400
 
1431
1401
  DEFAULT_FORMAT = "iso"
1432
1402
 
@@ -1439,7 +1409,7 @@ class Time(DateTime):
1439
1409
  return dt.datetime.strptime(value, data_format).time()
1440
1410
 
1441
1411
 
1442
- class Date(DateTime):
1412
+ class Date(_TemporalField[dt.date]):
1443
1413
  """ISO8601-formatted date string.
1444
1414
 
1445
1415
  :param format: Either ``"iso"`` (for ISO8601) or a date format string.
@@ -1453,9 +1423,15 @@ class Date(DateTime):
1453
1423
  "format": '"{input}" cannot be formatted as a date.',
1454
1424
  }
1455
1425
 
1456
- SERIALIZATION_FUNCS = {"iso": utils.to_iso_date, "iso8601": utils.to_iso_date}
1426
+ SERIALIZATION_FUNCS = {
1427
+ "iso": dt.date.isoformat,
1428
+ "iso8601": dt.date.isoformat,
1429
+ }
1457
1430
 
1458
- DESERIALIZATION_FUNCS = {"iso": utils.from_iso_date, "iso8601": utils.from_iso_date}
1431
+ DESERIALIZATION_FUNCS = {
1432
+ "iso": dt.date.fromisoformat,
1433
+ "iso8601": dt.date.fromisoformat,
1434
+ }
1459
1435
 
1460
1436
  DEFAULT_FORMAT = "iso"
1461
1437
 
@@ -1468,43 +1444,51 @@ class Date(DateTime):
1468
1444
  return dt.datetime.strptime(value, data_format).date()
1469
1445
 
1470
1446
 
1471
- class TimeDelta(Field):
1472
- """A field that (de)serializes a :class:`datetime.timedelta` object to an
1473
- integer or float and vice versa. The integer or float can represent the
1474
- number of days, seconds or microseconds.
1447
+ class TimeDelta(Field[dt.timedelta]):
1448
+ """A field that (de)serializes a :class:`datetime.timedelta` object to a `float`.
1449
+ The `float` can represent any time unit that the :class:`datetime.timedelta` constructor
1450
+ supports.
1475
1451
 
1476
- :param precision: Influences how the integer or float is interpreted during
1477
- (de)serialization. Must be 'days', 'seconds', 'microseconds',
1478
- 'milliseconds', 'minutes', 'hours' or 'weeks'.
1479
- :param serialization_type: Whether to (de)serialize to a `int` or `float`.
1452
+ :param precision: The time unit used for (de)serialization. Must be one of 'weeks',
1453
+ 'days', 'hours', 'minutes', 'seconds', 'milliseconds' or 'microseconds'.
1480
1454
  :param kwargs: The same keyword arguments that :class:`Field` receives.
1481
1455
 
1482
- Integer Caveats
1483
- ---------------
1484
- Any fractional parts (which depends on the precision used) will be truncated
1485
- when serializing using `int`.
1486
-
1487
1456
  Float Caveats
1488
1457
  -------------
1489
- Use of `float` when (de)serializing may result in data precision loss due
1490
- to the way machines handle floating point values.
1458
+ Precision loss may occur when serializing a highly precise :class:`datetime.timedelta`
1459
+ object using a big ``precision`` unit due to floating point arithmetics.
1491
1460
 
1492
- Regardless of the precision chosen, the fractional part when using `float`
1493
- will always be truncated to microseconds.
1494
- For example, `1.12345` interpreted as microseconds will result in `timedelta(microseconds=1)`.
1461
+ When necessary, the :class:`datetime.timedelta` constructor rounds `float` inputs
1462
+ to whole microseconds during initialization of the object. As a result, deserializing
1463
+ a `float` might be subject to rounding, regardless of `precision`. For example,
1464
+ ``TimeDelta().deserialize("1.1234567") == timedelta(seconds=1, microseconds=123457)``.
1495
1465
 
1496
1466
  .. versionchanged:: 3.17.0
1497
- Allow (de)serialization to `float` through use of a new `serialization_type` parameter.
1498
- `int` is the default to retain previous behaviour.
1467
+ Allow serialization to `float` through use of a new `serialization_type` parameter.
1468
+ Defaults to `int` for backwards compatibility. Also affects deserialization.
1469
+ .. versionchanged:: 4.0.0
1470
+ Remove `serialization_type` parameter and always serialize to float.
1471
+ Value is cast to a `float` upon deserialization.
1499
1472
  """
1500
1473
 
1474
+ WEEKS = "weeks"
1501
1475
  DAYS = "days"
1476
+ HOURS = "hours"
1477
+ MINUTES = "minutes"
1502
1478
  SECONDS = "seconds"
1503
- MICROSECONDS = "microseconds"
1504
1479
  MILLISECONDS = "milliseconds"
1505
- MINUTES = "minutes"
1506
- HOURS = "hours"
1507
- WEEKS = "weeks"
1480
+ MICROSECONDS = "microseconds"
1481
+
1482
+ # cache this mapping on class level for performance
1483
+ _unit_to_microseconds_mapping = {
1484
+ WEEKS: 1000000 * 60 * 60 * 24 * 7,
1485
+ DAYS: 1000000 * 60 * 60 * 24,
1486
+ HOURS: 1000000 * 60 * 60,
1487
+ MINUTES: 1000000 * 60,
1488
+ SECONDS: 1000000,
1489
+ MILLISECONDS: 1000,
1490
+ MICROSECONDS: 1,
1491
+ }
1508
1492
 
1509
1493
  #: Default error messages.
1510
1494
  default_error_messages = {
@@ -1515,49 +1499,32 @@ class TimeDelta(Field):
1515
1499
  def __init__(
1516
1500
  self,
1517
1501
  precision: str = SECONDS,
1518
- serialization_type: type[int | float] = int,
1519
- **kwargs,
1520
- ):
1502
+ **kwargs: Unpack[_BaseFieldKwargs],
1503
+ ) -> None:
1521
1504
  precision = precision.lower()
1522
- units = (
1523
- self.DAYS,
1524
- self.SECONDS,
1525
- self.MICROSECONDS,
1526
- self.MILLISECONDS,
1527
- self.MINUTES,
1528
- self.HOURS,
1529
- self.WEEKS,
1530
- )
1531
1505
 
1532
- if precision not in units:
1533
- msg = 'The precision must be {} or "{}".'.format(
1534
- ", ".join([f'"{each}"' for each in units[:-1]]), units[-1]
1535
- )
1506
+ if precision not in self._unit_to_microseconds_mapping:
1507
+ units = ", ".join(self._unit_to_microseconds_mapping)
1508
+ msg = f"The precision must be one of: {units}."
1536
1509
  raise ValueError(msg)
1537
1510
 
1538
- if serialization_type not in (int, float):
1539
- raise ValueError("The serialization type must be one of int or float")
1540
-
1541
1511
  self.precision = precision
1542
- self.serialization_type = serialization_type
1543
1512
  super().__init__(**kwargs)
1544
1513
 
1545
- def _serialize(self, value, attr, obj, **kwargs):
1514
+ def _serialize(self, value, attr, obj, **kwargs) -> float | None:
1546
1515
  if value is None:
1547
1516
  return None
1548
1517
 
1549
- base_unit = dt.timedelta(**{self.precision: 1})
1550
-
1551
- if self.serialization_type is int:
1552
- delta = utils.timedelta_to_microseconds(value)
1553
- unit = utils.timedelta_to_microseconds(base_unit)
1554
- return delta // unit
1555
- assert self.serialization_type is float # noqa: S101
1556
- return value.total_seconds() / base_unit.total_seconds()
1518
+ # limit float arithmetics to a single division to minimize precision loss
1519
+ microseconds: int = utils.timedelta_to_microseconds(value)
1520
+ microseconds_per_unit: int = self._unit_to_microseconds_mapping[self.precision]
1521
+ return microseconds / microseconds_per_unit
1557
1522
 
1558
- def _deserialize(self, value, attr, data, **kwargs):
1523
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.timedelta:
1524
+ if isinstance(value, dt.timedelta):
1525
+ return value
1559
1526
  try:
1560
- value = self.serialization_type(value)
1527
+ value = float(value)
1561
1528
  except (TypeError, ValueError) as error:
1562
1529
  raise self.make_error("invalid") from error
1563
1530
 
@@ -1569,7 +1536,10 @@ class TimeDelta(Field):
1569
1536
  raise self.make_error("invalid") from error
1570
1537
 
1571
1538
 
1572
- class Mapping(Field):
1539
+ _MappingT = typing.TypeVar("_MappingT", bound=_Mapping)
1540
+
1541
+
1542
+ class Mapping(Field[_MappingT]):
1573
1543
  """An abstract class for objects with key-value pairs. This class should not be used within schemas.
1574
1544
 
1575
1545
  :param keys: A field class or instance for dict keys.
@@ -1586,7 +1556,7 @@ class Mapping(Field):
1586
1556
  Use `Dict <marshmallow.fields.Dict>` instead.
1587
1557
  """
1588
1558
 
1589
- mapping_type = dict
1559
+ mapping_type: type[_MappingT]
1590
1560
 
1591
1561
  #: Default error messages.
1592
1562
  default_error_messages = {"invalid": "Not a valid mapping type."}
@@ -1595,42 +1565,35 @@ class Mapping(Field):
1595
1565
  self,
1596
1566
  keys: Field | type[Field] | None = None,
1597
1567
  values: Field | type[Field] | None = None,
1598
- **kwargs,
1568
+ **kwargs: Unpack[_BaseFieldKwargs],
1599
1569
  ):
1600
- if self.__class__ is Mapping:
1601
- warnings.warn(
1602
- "`Mapping` field should not be instantiated. Use `Dict` instead.",
1603
- ChangedInMarshmallow4Warning,
1604
- stacklevel=2,
1605
- )
1606
1570
  super().__init__(**kwargs)
1607
1571
  if keys is None:
1608
1572
  self.key_field = None
1609
1573
  else:
1610
1574
  try:
1611
- self.key_field = resolve_field_instance(keys)
1612
- except FieldInstanceResolutionError as error:
1575
+ self.key_field = _resolve_field_instance(keys)
1576
+ except _FieldInstanceResolutionError as error:
1613
1577
  raise ValueError(
1614
- '"keys" must be a subclass or instance of '
1615
- "marshmallow.base.FieldABC."
1578
+ '"keys" must be a subclass or instance of marshmallow.fields.Field.'
1616
1579
  ) from error
1617
1580
 
1618
1581
  if values is None:
1619
1582
  self.value_field = None
1620
1583
  else:
1621
1584
  try:
1622
- self.value_field = resolve_field_instance(values)
1623
- except FieldInstanceResolutionError as error:
1585
+ self.value_field = _resolve_field_instance(values)
1586
+ except _FieldInstanceResolutionError as error:
1624
1587
  raise ValueError(
1625
1588
  '"values" must be a subclass or instance of '
1626
- "marshmallow.base.FieldABC."
1589
+ "marshmallow.fields.Field."
1627
1590
  ) from error
1628
1591
  if isinstance(self.value_field, Nested):
1629
1592
  self.only = self.value_field.only
1630
1593
  self.exclude = self.value_field.exclude
1631
1594
 
1632
- def _bind_to_schema(self, field_name, schema):
1633
- super()._bind_to_schema(field_name, schema)
1595
+ def _bind_to_schema(self, field_name, parent):
1596
+ super()._bind_to_schema(field_name, parent)
1634
1597
  if self.value_field:
1635
1598
  self.value_field = copy.deepcopy(self.value_field)
1636
1599
  self.value_field._bind_to_schema(field_name, self)
@@ -1710,9 +1673,8 @@ class Mapping(Field):
1710
1673
  return result
1711
1674
 
1712
1675
 
1713
- class Dict(Mapping):
1714
- """A dict field. Supports dicts and dict-like objects. Extends
1715
- Mapping with dict as the mapping_type.
1676
+ class Dict(Mapping[dict]):
1677
+ """A dict field. Supports dicts and dict-like objects
1716
1678
 
1717
1679
  Example: ::
1718
1680
 
@@ -1748,7 +1710,7 @@ class Url(String):
1748
1710
  absolute: bool = True,
1749
1711
  schemes: types.StrSequenceOrSet | None = None,
1750
1712
  require_tld: bool = True,
1751
- **kwargs,
1713
+ **kwargs: Unpack[_BaseFieldKwargs],
1752
1714
  ):
1753
1715
  super().__init__(**kwargs)
1754
1716
 
@@ -1776,14 +1738,14 @@ class Email(String):
1776
1738
  #: Default error messages.
1777
1739
  default_error_messages = {"invalid": "Not a valid email address."}
1778
1740
 
1779
- def __init__(self, *args, **kwargs) -> None:
1780
- super().__init__(*args, **kwargs)
1741
+ def __init__(self, **kwargs: Unpack[_BaseFieldKwargs]) -> None:
1742
+ super().__init__(**kwargs)
1781
1743
  # Insert validation into self.validators so that multiple errors can be stored.
1782
1744
  validator = validate.Email(error=self.error_messages["invalid"])
1783
1745
  self.validators.insert(0, validator)
1784
1746
 
1785
1747
 
1786
- class IP(Field):
1748
+ class IP(Field[typing.Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]):
1787
1749
  """A IP address field.
1788
1750
 
1789
1751
  :param exploded: If `True`, serialize ipv6 address in long form, ie. with groups
@@ -1796,8 +1758,8 @@ class IP(Field):
1796
1758
 
1797
1759
  DESERIALIZATION_CLASS: type | None = None
1798
1760
 
1799
- def __init__(self, *args, exploded=False, **kwargs):
1800
- super().__init__(*args, **kwargs)
1761
+ def __init__(self, *, exploded: bool = False, **kwargs: Unpack[_BaseFieldKwargs]):
1762
+ super().__init__(**kwargs)
1801
1763
  self.exploded = exploded
1802
1764
 
1803
1765
  def _serialize(self, value, attr, obj, **kwargs) -> str | None:
@@ -1809,9 +1771,7 @@ class IP(Field):
1809
1771
 
1810
1772
  def _deserialize(
1811
1773
  self, value, attr, data, **kwargs
1812
- ) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
1813
- if value is None:
1814
- return None
1774
+ ) -> ipaddress.IPv4Address | ipaddress.IPv6Address:
1815
1775
  try:
1816
1776
  return (self.DESERIALIZATION_CLASS or ipaddress.ip_address)(
1817
1777
  utils.ensure_text_type(value)
@@ -1842,7 +1802,9 @@ class IPv6(IP):
1842
1802
  DESERIALIZATION_CLASS = ipaddress.IPv6Address
1843
1803
 
1844
1804
 
1845
- class IPInterface(Field):
1805
+ class IPInterface(
1806
+ Field[typing.Union[ipaddress.IPv4Interface, ipaddress.IPv6Interface]]
1807
+ ):
1846
1808
  """A IPInterface field.
1847
1809
 
1848
1810
  IP interface is the non-strict form of the IPNetwork type where arbitrary host
@@ -1860,8 +1822,8 @@ class IPInterface(Field):
1860
1822
 
1861
1823
  DESERIALIZATION_CLASS: type | None = None
1862
1824
 
1863
- def __init__(self, *args, exploded: bool = False, **kwargs):
1864
- super().__init__(*args, **kwargs)
1825
+ def __init__(self, *, exploded: bool = False, **kwargs: Unpack[_BaseFieldKwargs]):
1826
+ super().__init__(**kwargs)
1865
1827
  self.exploded = exploded
1866
1828
 
1867
1829
  def _serialize(self, value, attr, obj, **kwargs) -> str | None:
@@ -1871,11 +1833,9 @@ class IPInterface(Field):
1871
1833
  return value.exploded
1872
1834
  return value.compressed
1873
1835
 
1874
- def _deserialize(self, value, attr, data, **kwargs) -> None | (
1875
- ipaddress.IPv4Interface | ipaddress.IPv6Interface
1876
- ):
1877
- if value is None:
1878
- return None
1836
+ def _deserialize(
1837
+ self, value, attr, data, **kwargs
1838
+ ) -> ipaddress.IPv4Interface | ipaddress.IPv6Interface:
1879
1839
  try:
1880
1840
  return (self.DESERIALIZATION_CLASS or ipaddress.ip_interface)(
1881
1841
  utils.ensure_text_type(value)
@@ -1900,7 +1860,10 @@ class IPv6Interface(IPInterface):
1900
1860
  DESERIALIZATION_CLASS = ipaddress.IPv6Interface
1901
1861
 
1902
1862
 
1903
- class Enum(Field):
1863
+ _EnumT = typing.TypeVar("_EnumT", bound=EnumType)
1864
+
1865
+
1866
+ class Enum(Field[_EnumT]):
1904
1867
  """An Enum field (de)serializing enum members by symbol (name) or by value.
1905
1868
 
1906
1869
  :param enum: Enum class
@@ -1908,7 +1871,7 @@ class Enum(Field):
1908
1871
  or Field class or instance to use to (de)serialize by value. Defaults to False.
1909
1872
 
1910
1873
  If `by_value` is `False` (default), enum members are (de)serialized by symbol (name).
1911
- If it is `True`, they are (de)serialized by value using :class:`Raw`.
1874
+ If it is `True`, they are (de)serialized by value using `marshmallow.fields.Raw`.
1912
1875
  If it is a field instance or class, they are (de)serialized by value using this field.
1913
1876
 
1914
1877
  .. versionadded:: 3.18.0
@@ -1920,10 +1883,10 @@ class Enum(Field):
1920
1883
 
1921
1884
  def __init__(
1922
1885
  self,
1923
- enum: type[EnumType],
1886
+ enum: type[_EnumT],
1924
1887
  *,
1925
1888
  by_value: bool | Field | type[Field] = False,
1926
- **kwargs,
1889
+ **kwargs: Unpack[_BaseFieldKwargs],
1927
1890
  ):
1928
1891
  super().__init__(**kwargs)
1929
1892
  self.enum = enum
@@ -1941,17 +1904,19 @@ class Enum(Field):
1941
1904
  self.field = Raw()
1942
1905
  else:
1943
1906
  try:
1944
- self.field = resolve_field_instance(by_value)
1945
- except FieldInstanceResolutionError as error:
1907
+ self.field = _resolve_field_instance(by_value)
1908
+ except _FieldInstanceResolutionError as error:
1946
1909
  raise ValueError(
1947
1910
  '"by_value" must be either a bool or a subclass or instance of '
1948
- "marshmallow.base.FieldABC."
1911
+ "marshmallow.fields.Field."
1949
1912
  ) from error
1950
1913
  self.choices_text = ", ".join(
1951
1914
  str(self.field._serialize(m.value, None, None)) for m in enum
1952
1915
  )
1953
1916
 
1954
- def _serialize(self, value, attr, obj, **kwargs):
1917
+ def _serialize(
1918
+ self, value: _EnumT | None, attr: str | None, obj: typing.Any, **kwargs
1919
+ ) -> typing.Any | None:
1955
1920
  if value is None:
1956
1921
  return None
1957
1922
  if self.by_value:
@@ -1960,7 +1925,9 @@ class Enum(Field):
1960
1925
  val = value.name
1961
1926
  return self.field._serialize(val, attr, obj, **kwargs)
1962
1927
 
1963
- def _deserialize(self, value, attr, data, **kwargs):
1928
+ def _deserialize(self, value, attr, data, **kwargs) -> _EnumT:
1929
+ if isinstance(value, self.enum):
1930
+ return value
1964
1931
  val = self.field._deserialize(value, attr, data, **kwargs)
1965
1932
  if self.by_value:
1966
1933
  try:
@@ -1983,10 +1950,6 @@ class Method(Field):
1983
1950
  a value The method must take a single argument ``value``, which is the
1984
1951
  value to deserialize.
1985
1952
 
1986
- .. versionchanged:: 2.3.0
1987
- Deprecated ``method_name`` parameter in favor of ``serialize`` and allow
1988
- ``serialize`` to not be passed at all.
1989
-
1990
1953
  .. versionchanged:: 3.0.0
1991
1954
  Removed ``method_name`` parameter.
1992
1955
  """
@@ -1997,7 +1960,7 @@ class Method(Field):
1997
1960
  self,
1998
1961
  serialize: str | None = None,
1999
1962
  deserialize: str | None = None,
2000
- **kwargs,
1963
+ **kwargs: Unpack[_BaseFieldKwargs], # FIXME: Omit dump_only and load_only
2001
1964
  ):
2002
1965
  # Set dump_only and load_only based on arguments
2003
1966
  kwargs["dump_only"] = bool(serialize) and not bool(deserialize)
@@ -2008,18 +1971,18 @@ class Method(Field):
2008
1971
  self._serialize_method = None
2009
1972
  self._deserialize_method = None
2010
1973
 
2011
- def _bind_to_schema(self, field_name, schema):
1974
+ def _bind_to_schema(self, field_name, parent):
2012
1975
  if self.serialize_method_name:
2013
1976
  self._serialize_method = utils.callable_or_raise(
2014
- getattr(schema, self.serialize_method_name)
1977
+ getattr(parent, self.serialize_method_name)
2015
1978
  )
2016
1979
 
2017
1980
  if self.deserialize_method_name:
2018
1981
  self._deserialize_method = utils.callable_or_raise(
2019
- getattr(schema, self.deserialize_method_name)
1982
+ getattr(parent, self.deserialize_method_name)
2020
1983
  )
2021
1984
 
2022
- super()._bind_to_schema(field_name, schema)
1985
+ super()._bind_to_schema(field_name, parent)
2023
1986
 
2024
1987
  def _serialize(self, value, attr, obj, **kwargs):
2025
1988
  if self._serialize_method is not None:
@@ -2037,22 +2000,20 @@ class Function(Field):
2037
2000
 
2038
2001
  :param serialize: A callable from which to retrieve the value.
2039
2002
  The function must take a single argument ``obj`` which is the object
2040
- to be serialized. It can also optionally take a ``context`` argument,
2041
- which is a dictionary of context variables passed to the serializer.
2003
+ to be serialized.
2042
2004
  If no callable is provided then the ```load_only``` flag will be set
2043
2005
  to True.
2044
2006
  :param deserialize: A callable from which to retrieve the value.
2045
2007
  The function must take a single argument ``value`` which is the value
2046
- to be deserialized. It can also optionally take a ``context`` argument,
2047
- which is a dictionary of context variables passed to the deserializer.
2008
+ to be deserialized.
2048
2009
  If no callable is provided then ```value``` will be passed through
2049
2010
  unchanged.
2050
2011
 
2051
- .. versionchanged:: 2.3.0
2052
- Deprecated ``func`` parameter in favor of ``serialize``.
2053
-
2054
2012
  .. versionchanged:: 3.0.0a1
2055
2013
  Removed ``func`` parameter.
2014
+
2015
+ .. versionchanged:: 4.0.0
2016
+ Don't pass context to serialization and deserialization functions.
2056
2017
  """
2057
2018
 
2058
2019
  _CHECK_ATTRIBUTE = False
@@ -2069,7 +2030,7 @@ class Function(Field):
2069
2030
  | typing.Callable[[typing.Any, dict], typing.Any]
2070
2031
  | None
2071
2032
  ) = None,
2072
- **kwargs,
2033
+ **kwargs: Unpack[_BaseFieldKwargs], # FIXME: Omit dump_only and load_only
2073
2034
  ):
2074
2035
  # Set dump_only and load_only based on arguments
2075
2036
  kwargs["dump_only"] = bool(serialize) and not bool(deserialize)
@@ -2079,23 +2040,18 @@ class Function(Field):
2079
2040
  self.deserialize_func = deserialize and utils.callable_or_raise(deserialize)
2080
2041
 
2081
2042
  def _serialize(self, value, attr, obj, **kwargs):
2082
- return self._call_or_raise(self.serialize_func, obj, attr)
2043
+ return self.serialize_func(obj)
2083
2044
 
2084
2045
  def _deserialize(self, value, attr, data, **kwargs):
2085
2046
  if self.deserialize_func:
2086
- return self._call_or_raise(self.deserialize_func, value, attr)
2047
+ return self.deserialize_func(value)
2087
2048
  return value
2088
2049
 
2089
- def _call_or_raise(self, func, value, attr):
2090
- if len(utils.get_func_args(func)) > 1:
2091
- if self.parent.context is None:
2092
- msg = f"No context available for Function field {attr!r}"
2093
- raise ValidationError(msg)
2094
- return func(value, self.parent.context)
2095
- return func(value)
2050
+
2051
+ _ContantT = typing.TypeVar("_ContantT")
2096
2052
 
2097
2053
 
2098
- class Constant(Field):
2054
+ class Constant(Field[_ContantT]):
2099
2055
  """A field that (de)serializes to a preset constant. If you only want the
2100
2056
  constant added for serialization or deserialization, you should use
2101
2057
  ``dump_only=True`` or ``load_only=True`` respectively.
@@ -2105,49 +2061,22 @@ class Constant(Field):
2105
2061
 
2106
2062
  _CHECK_ATTRIBUTE = False
2107
2063
 
2108
- def __init__(self, constant: typing.Any, **kwargs):
2064
+ def __init__(self, constant: _ContantT, **kwargs: Unpack[_BaseFieldKwargs]):
2109
2065
  super().__init__(**kwargs)
2110
2066
  self.constant = constant
2111
2067
  self.load_default = constant
2112
2068
  self.dump_default = constant
2113
2069
 
2114
- def _serialize(self, value, *args, **kwargs):
2070
+ def _serialize(self, value, *args, **kwargs) -> _ContantT:
2115
2071
  return self.constant
2116
2072
 
2117
- def _deserialize(self, value, *args, **kwargs):
2073
+ def _deserialize(self, value, *args, **kwargs) -> _ContantT:
2118
2074
  return self.constant
2119
2075
 
2120
2076
 
2121
- class Inferred(Field):
2122
- """A field that infers how to serialize, based on the value type.
2123
-
2124
- .. warning::
2125
-
2126
- This class is treated as private API.
2127
- Users should not need to use this class directly.
2128
- """
2129
-
2130
- def __init__(self):
2131
- super().__init__()
2132
- # We memoize the fields to avoid creating and binding new fields
2133
- # every time on serialization.
2134
- self._field_cache = {}
2135
-
2136
- def _serialize(self, value, attr, obj, **kwargs):
2137
- field_cls = self.root.TYPE_MAPPING.get(type(value))
2138
- if field_cls is None:
2139
- field = super()
2140
- else:
2141
- field = self._field_cache.get(field_cls)
2142
- if field is None:
2143
- field = field_cls()
2144
- field._bind_to_schema(self.name, self.parent)
2145
- self._field_cache[field_cls] = field
2146
- return field._serialize(value, attr, obj, **kwargs)
2147
-
2148
-
2149
2077
  # Aliases
2150
2078
  URL = Url
2079
+
2151
2080
  Str = String
2152
2081
  Bool = Boolean
2153
2082
  Int = Integer