marshmallow 3.26.0__py3-none-any.whl → 4.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
marshmallow/fields.py CHANGED
@@ -1,42 +1,43 @@
1
1
  # ruff: noqa: F841, 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,46 +500,31 @@ 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:
@@ -603,9 +537,8 @@ class Nested(Field):
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
561
  schema_class = class_registry.get_class(nested, all=False)
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
  )
@@ -675,10 +605,10 @@ class Nested(Field):
675
605
  self,
676
606
  value: typing.Any,
677
607
  attr: str | None,
678
- data: typing.Mapping[str, typing.Any] | None = None,
608
+ data: typing.Mapping[str, typing.Any] | None,
679
609
  partial: bool | types.StrSequenceOrSet | None = None,
680
610
  **kwargs,
681
- ) -> typing.Any:
611
+ ):
682
612
  """Same as :meth:`Field._deserialize` with additional ``partial`` argument.
683
613
 
684
614
  :param partial: For nested schemas, the ``partial``
@@ -712,9 +642,8 @@ class Pluck(Nested):
712
642
  loaded = AlbumSchema().load(in_data) # => {'artist': {'id': 42}}
713
643
  dumped = AlbumSchema().dump(loaded) # => {'artist': 42}
714
644
 
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.
645
+ :param nested: The Schema class or class name (string) to nest
646
+ :param str field_name: The key to pluck a value from.
718
647
  :param kwargs: The same keyword arguments that :class:`Nested` receives.
719
648
  """
720
649
 
@@ -724,8 +653,8 @@ class Pluck(Nested):
724
653
  field_name: str,
725
654
  *,
726
655
  many: bool = False,
727
- unknown: str | None = None,
728
- **kwargs,
656
+ unknown: types.UnknownOption | None = None,
657
+ **kwargs: Unpack[_BaseFieldKwargs],
729
658
  ):
730
659
  super().__init__(
731
660
  nested, only=(field_name,), many=many, unknown=unknown, **kwargs
@@ -754,7 +683,7 @@ class Pluck(Nested):
754
683
  return self._load(value, partial=partial)
755
684
 
756
685
 
757
- class List(Field):
686
+ class List(Field[list[typing.Optional[_InternalT]]]):
758
687
  """A list field, composed with another `Field` class or
759
688
  instance.
760
689
 
@@ -772,33 +701,37 @@ class List(Field):
772
701
  #: Default error messages.
773
702
  default_error_messages = {"invalid": "Not a valid list."}
774
703
 
775
- def __init__(self, cls_or_instance: Field | type[Field], **kwargs):
704
+ def __init__(
705
+ self,
706
+ cls_or_instance: Field[_InternalT] | type[Field[_InternalT]],
707
+ **kwargs: Unpack[_BaseFieldKwargs],
708
+ ):
776
709
  super().__init__(**kwargs)
777
710
  try:
778
- self.inner = resolve_field_instance(cls_or_instance)
779
- except FieldInstanceResolutionError as error:
711
+ self.inner: Field[_InternalT] = _resolve_field_instance(cls_or_instance)
712
+ except _FieldInstanceResolutionError as error:
780
713
  raise ValueError(
781
714
  "The list elements must be a subclass or instance of "
782
- "marshmallow.base.FieldABC."
715
+ "marshmallow.fields.Field."
783
716
  ) from error
784
717
  if isinstance(self.inner, Nested):
785
718
  self.only = self.inner.only
786
719
  self.exclude = self.inner.exclude
787
720
 
788
- def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
789
- super()._bind_to_schema(field_name, schema)
721
+ def _bind_to_schema(self, field_name: str, parent: Schema | Field) -> None:
722
+ super()._bind_to_schema(field_name, parent)
790
723
  self.inner = copy.deepcopy(self.inner)
791
724
  self.inner._bind_to_schema(field_name, self)
792
725
  if isinstance(self.inner, Nested):
793
726
  self.inner.only = self.only
794
727
  self.inner.exclude = self.exclude
795
728
 
796
- def _serialize(self, value, attr, obj, **kwargs) -> list[typing.Any] | None:
729
+ def _serialize(self, value, attr, obj, **kwargs) -> list[_InternalT] | None:
797
730
  if value is None:
798
731
  return None
799
732
  return [self.inner._serialize(each, attr, obj, **kwargs) for each in value]
800
733
 
801
- def _deserialize(self, value, attr, data, **kwargs) -> list[typing.Any]:
734
+ def _deserialize(self, value, attr, data, **kwargs) -> list[_InternalT | None]:
802
735
  if not utils.is_collection(value):
803
736
  raise self.make_error("invalid")
804
737
 
@@ -809,14 +742,14 @@ class List(Field):
809
742
  result.append(self.inner.deserialize(each, **kwargs))
810
743
  except ValidationError as error:
811
744
  if error.valid_data is not None:
812
- result.append(error.valid_data)
745
+ result.append(typing.cast("_InternalT", error.valid_data))
813
746
  errors.update({idx: error.messages})
814
747
  if errors:
815
748
  raise ValidationError(errors, valid_data=result)
816
749
  return result
817
750
 
818
751
 
819
- class Tuple(Field):
752
+ class Tuple(Field[tuple]):
820
753
  """A tuple field, composed of a fixed number of other `Field` classes or
821
754
  instances
822
755
 
@@ -842,7 +775,7 @@ class Tuple(Field):
842
775
  def __init__(
843
776
  self,
844
777
  tuple_fields: typing.Iterable[Field] | typing.Iterable[type[Field]],
845
- **kwargs,
778
+ **kwargs: Unpack[_BaseFieldKwargs],
846
779
  ):
847
780
  super().__init__(**kwargs)
848
781
  if not utils.is_collection(tuple_fields):
@@ -852,19 +785,19 @@ class Tuple(Field):
852
785
 
853
786
  try:
854
787
  self.tuple_fields = [
855
- resolve_field_instance(cls_or_instance)
788
+ _resolve_field_instance(cls_or_instance)
856
789
  for cls_or_instance in tuple_fields
857
790
  ]
858
- except FieldInstanceResolutionError as error:
791
+ except _FieldInstanceResolutionError as error:
859
792
  raise ValueError(
860
793
  'Elements of "tuple_fields" must be subclasses or '
861
- "instances of marshmallow.base.FieldABC."
794
+ "instances of marshmallow.fields.Field."
862
795
  ) from error
863
796
 
864
797
  self.validate_length = Length(equal=len(self.tuple_fields))
865
798
 
866
- def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
867
- super()._bind_to_schema(field_name, schema)
799
+ def _bind_to_schema(self, field_name: str, parent: Schema | Field) -> None:
800
+ super()._bind_to_schema(field_name, parent)
868
801
  new_tuple_fields = []
869
802
  for field in self.tuple_fields:
870
803
  new_field = copy.deepcopy(field)
@@ -873,7 +806,9 @@ class Tuple(Field):
873
806
 
874
807
  self.tuple_fields = new_tuple_fields
875
808
 
876
- def _serialize(self, value, attr, obj, **kwargs) -> tuple | None:
809
+ def _serialize(
810
+ self, value: tuple | None, attr: str | None, obj: typing.Any, **kwargs
811
+ ) -> tuple | None:
877
812
  if value is None:
878
813
  return None
879
814
 
@@ -882,8 +817,14 @@ class Tuple(Field):
882
817
  for field, each in zip(self.tuple_fields, value)
883
818
  )
884
819
 
885
- def _deserialize(self, value, attr, data, **kwargs) -> tuple:
886
- if not utils.is_collection(value):
820
+ def _deserialize(
821
+ self,
822
+ value: typing.Any,
823
+ attr: str | None,
824
+ data: typing.Mapping[str, typing.Any] | None,
825
+ **kwargs,
826
+ ) -> tuple:
827
+ if not utils.is_sequence_but_not_string(value):
887
828
  raise self.make_error("invalid")
888
829
 
889
830
  self.validate_length(value)
@@ -904,7 +845,7 @@ class Tuple(Field):
904
845
  return tuple(result)
905
846
 
906
847
 
907
- class String(Field):
848
+ class String(Field[str]):
908
849
  """A string field.
909
850
 
910
851
  :param kwargs: The same keyword arguments that :class:`Field` receives.
@@ -921,7 +862,7 @@ class String(Field):
921
862
  return None
922
863
  return utils.ensure_text_type(value)
923
864
 
924
- def _deserialize(self, value, attr, data, **kwargs) -> typing.Any:
865
+ def _deserialize(self, value, attr, data, **kwargs) -> str:
925
866
  if not isinstance(value, (str, bytes)):
926
867
  raise self.make_error("invalid")
927
868
  try:
@@ -930,16 +871,14 @@ class String(Field):
930
871
  raise self.make_error("invalid_utf8") from error
931
872
 
932
873
 
933
- class UUID(String):
874
+ class UUID(Field[uuid.UUID]):
934
875
  """A UUID field."""
935
876
 
936
877
  #: Default error messages.
937
878
  default_error_messages = {"invalid_uuid": "Not a valid UUID."}
938
879
 
939
- def _validated(self, value) -> uuid.UUID | None:
880
+ def _validated(self, value) -> uuid.UUID:
940
881
  """Format the value or raise a :exc:`ValidationError` if an error occurs."""
941
- if value is None:
942
- return None
943
882
  if isinstance(value, uuid.UUID):
944
883
  return value
945
884
  try:
@@ -949,15 +888,20 @@ class UUID(String):
949
888
  except (ValueError, AttributeError, TypeError) as error:
950
889
  raise self.make_error("invalid_uuid") from error
951
890
 
952
- def _deserialize(self, value, attr, data, **kwargs) -> uuid.UUID | None:
891
+ def _serialize(self, value, attr, obj, **kwargs) -> str | None:
892
+ if value is None:
893
+ return None
894
+ return str(value)
895
+
896
+ def _deserialize(self, value, attr, data, **kwargs) -> uuid.UUID:
953
897
  return self._validated(value)
954
898
 
955
899
 
956
- _NumType = typing.TypeVar("_NumType")
900
+ _NumT = typing.TypeVar("_NumT")
957
901
 
958
902
 
959
- class Number(Field, typing.Generic[_NumType]):
960
- """Base class for number fields.
903
+ class Number(Field[_NumT]):
904
+ """Base class for number fields. This class should not be used within schemas.
961
905
 
962
906
  :param as_string: If `True`, format the serialized value as a string.
963
907
  :param kwargs: The same keyword arguments that :class:`Field` receives.
@@ -967,7 +911,7 @@ class Number(Field, typing.Generic[_NumType]):
967
911
  Use `Integer <marshmallow.fields.Integer>`, `Float <marshmallow.fields.Float>`, or `Decimal <marshmallow.fields.Decimal>` instead.
968
912
  """
969
913
 
970
- num_type: type = float
914
+ num_type: type[_NumT]
971
915
 
972
916
  #: Default error messages.
973
917
  default_error_messages = {
@@ -975,21 +919,15 @@ class Number(Field, typing.Generic[_NumType]):
975
919
  "too_large": "Number too large.",
976
920
  }
977
921
 
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
- )
922
+ def __init__(self, *, as_string: bool = False, **kwargs: Unpack[_BaseFieldKwargs]):
985
923
  self.as_string = as_string
986
924
  super().__init__(**kwargs)
987
925
 
988
- def _format_num(self, value) -> _NumType:
926
+ def _format_num(self, value) -> _NumT:
989
927
  """Return the number value for value, given this field's `num_type`."""
990
- return self.num_type(value)
928
+ return self.num_type(value) # type: ignore[call-arg]
991
929
 
992
- def _validated(self, value: typing.Any) -> _NumType:
930
+ def _validated(self, value: typing.Any) -> _NumT:
993
931
  """Format the value or raise a :exc:`ValidationError` if an error occurs."""
994
932
  # (value is True or value is False) is ~5x faster than isinstance(value, bool)
995
933
  if value is True or value is False:
@@ -1001,17 +939,17 @@ class Number(Field, typing.Generic[_NumType]):
1001
939
  except OverflowError as error:
1002
940
  raise self.make_error("too_large", input=value) from error
1003
941
 
1004
- def _to_string(self, value: _NumType) -> str:
942
+ def _to_string(self, value: _NumT) -> str:
1005
943
  return str(value)
1006
944
 
1007
- def _serialize(self, value, attr, obj, **kwargs) -> str | _NumType | None:
945
+ def _serialize(self, value, attr, obj, **kwargs) -> str | _NumT | None:
1008
946
  """Return a string if `self.as_string=True`, otherwise return this field's `num_type`."""
1009
947
  if value is None:
1010
948
  return None
1011
- ret: _NumType = self._format_num(value)
949
+ ret: _NumT = self._format_num(value)
1012
950
  return self._to_string(ret) if self.as_string else ret
1013
951
 
1014
- def _deserialize(self, value, attr, data, **kwargs) -> _NumType | None:
952
+ def _deserialize(self, value, attr, data, **kwargs) -> _NumT:
1015
953
  return self._validated(value)
1016
954
 
1017
955
 
@@ -1028,9 +966,15 @@ class Integer(Number[int]):
1028
966
  #: Default error messages.
1029
967
  default_error_messages = {"invalid": "Not a valid integer."}
1030
968
 
1031
- def __init__(self, *, strict: bool = False, **kwargs):
969
+ def __init__(
970
+ self,
971
+ *,
972
+ strict: bool = False,
973
+ as_string: bool = False,
974
+ **kwargs: Unpack[_BaseFieldKwargs],
975
+ ):
1032
976
  self.strict = strict
1033
- super().__init__(**kwargs)
977
+ super().__init__(as_string=as_string, **kwargs)
1034
978
 
1035
979
  # override Number
1036
980
  def _validated(self, value: typing.Any) -> int:
@@ -1055,7 +999,13 @@ class Float(Number[float]):
1055
999
  "special": "Special numeric values (nan or infinity) are not permitted."
1056
1000
  }
1057
1001
 
1058
- def __init__(self, *, allow_nan: bool = False, as_string: bool = False, **kwargs):
1002
+ def __init__(
1003
+ self,
1004
+ *,
1005
+ allow_nan: bool = False,
1006
+ as_string: bool = False,
1007
+ **kwargs: Unpack[_BaseFieldKwargs],
1008
+ ):
1059
1009
  self.allow_nan = allow_nan
1060
1010
  super().__init__(as_string=as_string, **kwargs)
1061
1011
 
@@ -1100,8 +1050,6 @@ class Decimal(Number[decimal.Decimal]):
1100
1050
  :param as_string: If `True`, serialize to a string instead of a Python
1101
1051
  `decimal.Decimal` type.
1102
1052
  :param kwargs: The same keyword arguments that :class:`Number` receives.
1103
-
1104
- .. versionadded:: 1.2.0
1105
1053
  """
1106
1054
 
1107
1055
  num_type = decimal.Decimal
@@ -1118,7 +1066,7 @@ class Decimal(Number[decimal.Decimal]):
1118
1066
  *,
1119
1067
  allow_nan: bool = False,
1120
1068
  as_string: bool = False,
1121
- **kwargs,
1069
+ **kwargs: Unpack[_BaseFieldKwargs],
1122
1070
  ):
1123
1071
  self.places = (
1124
1072
  decimal.Decimal((0, (1,), -places)) if places is not None else None
@@ -1152,7 +1100,7 @@ class Decimal(Number[decimal.Decimal]):
1152
1100
  return format(value, "f")
1153
1101
 
1154
1102
 
1155
- class Boolean(Field):
1103
+ class Boolean(Field[bool]):
1156
1104
  """A boolean field.
1157
1105
 
1158
1106
  :param truthy: Values that will (de)serialize to `True`. If an empty
@@ -1213,7 +1161,7 @@ class Boolean(Field):
1213
1161
  *,
1214
1162
  truthy: typing.Iterable | None = None,
1215
1163
  falsy: typing.Iterable | None = None,
1216
- **kwargs,
1164
+ **kwargs: Unpack[_BaseFieldKwargs],
1217
1165
  ):
1218
1166
  super().__init__(**kwargs)
1219
1167
 
@@ -1222,23 +1170,13 @@ class Boolean(Field):
1222
1170
  if falsy is not None:
1223
1171
  self.falsy = set(falsy)
1224
1172
 
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):
1173
+ def _deserialize(
1174
+ self,
1175
+ value: typing.Any,
1176
+ attr: str | None,
1177
+ data: typing.Mapping[str, typing.Any] | None,
1178
+ **kwargs,
1179
+ ) -> bool:
1242
1180
  if not self.truthy:
1243
1181
  return bool(value)
1244
1182
  try:
@@ -1251,69 +1189,45 @@ class Boolean(Field):
1251
1189
  raise self.make_error("invalid", input=value)
1252
1190
 
1253
1191
 
1254
- class DateTime(Field):
1255
- """A formatted datetime string.
1192
+ _D = typing.TypeVar("_D", dt.datetime, dt.date, dt.time)
1256
1193
 
1257
- Example: ``'2014-12-22T03:12:58.019077+00:00'``
1258
1194
 
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.
1195
+ class _TemporalField(Field[_D], metaclass=abc.ABCMeta):
1196
+ """Base field for date and time related fields including common (de)serialization logic."""
1263
1197
 
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"
1198
+ # Subclasses should define each of these class constants
1199
+ SERIALIZATION_FUNCS: dict[str, typing.Callable[[_D], str | float]]
1200
+ DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], _D]]
1201
+ DEFAULT_FORMAT: str
1202
+ OBJ_TYPE: str
1203
+ SCHEMA_OPTS_VAR_NAME: str
1293
1204
 
1294
- #: Default error messages.
1295
1205
  default_error_messages = {
1296
1206
  "invalid": "Not a valid {obj_type}.",
1297
1207
  "invalid_awareness": "Not a valid {awareness} {obj_type}.",
1298
1208
  "format": '"{input}" cannot be formatted as a {obj_type}.',
1299
1209
  }
1300
1210
 
1301
- def __init__(self, format: str | None = None, **kwargs) -> None: # noqa: A002
1211
+ def __init__(
1212
+ self,
1213
+ format: str | None = None, # noqa: A002
1214
+ **kwargs: Unpack[_BaseFieldKwargs],
1215
+ ) -> None:
1302
1216
  super().__init__(**kwargs)
1303
1217
  # Allow this to be None. It may be set later in the ``_serialize``
1304
1218
  # or ``_deserialize`` methods. This allows a Schema to dynamically set the
1305
1219
  # format, e.g. from a Meta option
1306
1220
  self.format = format
1307
1221
 
1308
- def _bind_to_schema(self, field_name, schema):
1309
- super()._bind_to_schema(field_name, schema)
1222
+ def _bind_to_schema(self, field_name, parent):
1223
+ super()._bind_to_schema(field_name, parent)
1310
1224
  self.format = (
1311
1225
  self.format
1312
1226
  or getattr(self.root.opts, self.SCHEMA_OPTS_VAR_NAME)
1313
1227
  or self.DEFAULT_FORMAT
1314
1228
  )
1315
1229
 
1316
- def _serialize(self, value, attr, obj, **kwargs) -> str | float | None:
1230
+ def _serialize(self, value: _D | None, attr, obj, **kwargs) -> str | float | None:
1317
1231
  if value is None:
1318
1232
  return None
1319
1233
  data_format = self.format or self.DEFAULT_FORMAT
@@ -1322,7 +1236,10 @@ class DateTime(Field):
1322
1236
  return format_func(value)
1323
1237
  return value.strftime(data_format)
1324
1238
 
1325
- def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
1239
+ def _deserialize(self, value, attr, data, **kwargs) -> _D:
1240
+ internal_type: type[_D] = getattr(dt, self.OBJ_TYPE)
1241
+ if isinstance(value, internal_type):
1242
+ return value
1326
1243
  data_format = self.format or self.DEFAULT_FORMAT
1327
1244
  func = self.DESERIALIZATION_FUNCS.get(data_format)
1328
1245
  try:
@@ -1334,6 +1251,51 @@ class DateTime(Field):
1334
1251
  "invalid", input=value, obj_type=self.OBJ_TYPE
1335
1252
  ) from error
1336
1253
 
1254
+ @staticmethod
1255
+ @abc.abstractmethod
1256
+ def _make_object_from_format(value: typing.Any, data_format: str) -> _D: ...
1257
+
1258
+
1259
+ class DateTime(_TemporalField[dt.datetime]):
1260
+ """A formatted datetime string.
1261
+
1262
+ Example: ``'2014-12-22T03:12:58.019077+00:00'``
1263
+
1264
+ :param format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601),
1265
+ ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp) or a date format string.
1266
+ If `None`, defaults to "iso".
1267
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
1268
+
1269
+ .. versionchanged:: 3.0.0rc9
1270
+ Does not modify timezone information on (de)serialization.
1271
+ .. versionchanged:: 3.19
1272
+ Add timestamp as a format.
1273
+ """
1274
+
1275
+ SERIALIZATION_FUNCS: dict[str, typing.Callable[[dt.datetime], str | float]] = {
1276
+ "iso": dt.datetime.isoformat,
1277
+ "iso8601": dt.datetime.isoformat,
1278
+ "rfc": email.utils.format_datetime,
1279
+ "rfc822": email.utils.format_datetime,
1280
+ "timestamp": utils.timestamp,
1281
+ "timestamp_ms": utils.timestamp_ms,
1282
+ }
1283
+
1284
+ DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], dt.datetime]] = {
1285
+ "iso": dt.datetime.fromisoformat,
1286
+ "iso8601": dt.datetime.fromisoformat,
1287
+ "rfc": email.utils.parsedate_to_datetime,
1288
+ "rfc822": email.utils.parsedate_to_datetime,
1289
+ "timestamp": utils.from_timestamp,
1290
+ "timestamp_ms": utils.from_timestamp_ms,
1291
+ }
1292
+
1293
+ DEFAULT_FORMAT = "iso"
1294
+
1295
+ OBJ_TYPE = "datetime"
1296
+
1297
+ SCHEMA_OPTS_VAR_NAME = "datetimeformat"
1298
+
1337
1299
  @staticmethod
1338
1300
  def _make_object_from_format(value, data_format) -> dt.datetime:
1339
1301
  return dt.datetime.strptime(value, data_format)
@@ -1359,14 +1321,14 @@ class NaiveDateTime(DateTime):
1359
1321
  format: str | None = None, # noqa: A002
1360
1322
  *,
1361
1323
  timezone: dt.timezone | None = None,
1362
- **kwargs,
1324
+ **kwargs: Unpack[_BaseFieldKwargs],
1363
1325
  ) -> None:
1364
1326
  super().__init__(format=format, **kwargs)
1365
1327
  self.timezone = timezone
1366
1328
 
1367
1329
  def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
1368
1330
  ret = super()._deserialize(value, attr, data, **kwargs)
1369
- if is_aware(ret):
1331
+ if utils.is_aware(ret):
1370
1332
  if self.timezone is None:
1371
1333
  raise self.make_error(
1372
1334
  "invalid_awareness",
@@ -1396,14 +1358,14 @@ class AwareDateTime(DateTime):
1396
1358
  format: str | None = None, # noqa: A002
1397
1359
  *,
1398
1360
  default_timezone: dt.tzinfo | None = None,
1399
- **kwargs,
1361
+ **kwargs: Unpack[_BaseFieldKwargs],
1400
1362
  ) -> None:
1401
1363
  super().__init__(format=format, **kwargs)
1402
1364
  self.default_timezone = default_timezone
1403
1365
 
1404
1366
  def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
1405
1367
  ret = super()._deserialize(value, attr, data, **kwargs)
1406
- if not is_aware(ret):
1368
+ if not utils.is_aware(ret):
1407
1369
  if self.default_timezone is None:
1408
1370
  raise self.make_error(
1409
1371
  "invalid_awareness",
@@ -1414,7 +1376,7 @@ class AwareDateTime(DateTime):
1414
1376
  return ret
1415
1377
 
1416
1378
 
1417
- class Time(DateTime):
1379
+ class Time(_TemporalField[dt.time]):
1418
1380
  """A formatted time string.
1419
1381
 
1420
1382
  Example: ``'03:12:58.019077'``
@@ -1424,9 +1386,15 @@ class Time(DateTime):
1424
1386
  :param kwargs: The same keyword arguments that :class:`Field` receives.
1425
1387
  """
1426
1388
 
1427
- SERIALIZATION_FUNCS = {"iso": utils.to_iso_time, "iso8601": utils.to_iso_time}
1389
+ SERIALIZATION_FUNCS = {
1390
+ "iso": dt.time.isoformat,
1391
+ "iso8601": dt.time.isoformat,
1392
+ }
1428
1393
 
1429
- DESERIALIZATION_FUNCS = {"iso": utils.from_iso_time, "iso8601": utils.from_iso_time}
1394
+ DESERIALIZATION_FUNCS = {
1395
+ "iso": dt.time.fromisoformat,
1396
+ "iso8601": dt.time.fromisoformat,
1397
+ }
1430
1398
 
1431
1399
  DEFAULT_FORMAT = "iso"
1432
1400
 
@@ -1439,7 +1407,7 @@ class Time(DateTime):
1439
1407
  return dt.datetime.strptime(value, data_format).time()
1440
1408
 
1441
1409
 
1442
- class Date(DateTime):
1410
+ class Date(_TemporalField[dt.date]):
1443
1411
  """ISO8601-formatted date string.
1444
1412
 
1445
1413
  :param format: Either ``"iso"`` (for ISO8601) or a date format string.
@@ -1453,9 +1421,15 @@ class Date(DateTime):
1453
1421
  "format": '"{input}" cannot be formatted as a date.',
1454
1422
  }
1455
1423
 
1456
- SERIALIZATION_FUNCS = {"iso": utils.to_iso_date, "iso8601": utils.to_iso_date}
1424
+ SERIALIZATION_FUNCS = {
1425
+ "iso": dt.date.isoformat,
1426
+ "iso8601": dt.date.isoformat,
1427
+ }
1457
1428
 
1458
- DESERIALIZATION_FUNCS = {"iso": utils.from_iso_date, "iso8601": utils.from_iso_date}
1429
+ DESERIALIZATION_FUNCS = {
1430
+ "iso": dt.date.fromisoformat,
1431
+ "iso8601": dt.date.fromisoformat,
1432
+ }
1459
1433
 
1460
1434
  DEFAULT_FORMAT = "iso"
1461
1435
 
@@ -1468,43 +1442,51 @@ class Date(DateTime):
1468
1442
  return dt.datetime.strptime(value, data_format).date()
1469
1443
 
1470
1444
 
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.
1445
+ class TimeDelta(Field[dt.timedelta]):
1446
+ """A field that (de)serializes a :class:`datetime.timedelta` object to a `float`.
1447
+ The `float` can represent any time unit that the :class:`datetime.timedelta` constructor
1448
+ supports.
1475
1449
 
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`.
1450
+ :param precision: The time unit used for (de)serialization. Must be one of 'weeks',
1451
+ 'days', 'hours', 'minutes', 'seconds', 'milliseconds' or 'microseconds'.
1480
1452
  :param kwargs: The same keyword arguments that :class:`Field` receives.
1481
1453
 
1482
- Integer Caveats
1483
- ---------------
1484
- Any fractional parts (which depends on the precision used) will be truncated
1485
- when serializing using `int`.
1486
-
1487
1454
  Float Caveats
1488
1455
  -------------
1489
- Use of `float` when (de)serializing may result in data precision loss due
1490
- to the way machines handle floating point values.
1456
+ Precision loss may occur when serializing a highly precise :class:`datetime.timedelta`
1457
+ object using a big ``precision`` unit due to floating point arithmetics.
1491
1458
 
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)`.
1459
+ When necessary, the :class:`datetime.timedelta` constructor rounds `float` inputs
1460
+ to whole microseconds during initialization of the object. As a result, deserializing
1461
+ a `float` might be subject to rounding, regardless of `precision`. For example,
1462
+ ``TimeDelta().deserialize("1.1234567") == timedelta(seconds=1, microseconds=123457)``.
1495
1463
 
1496
1464
  .. 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.
1465
+ Allow serialization to `float` through use of a new `serialization_type` parameter.
1466
+ Defaults to `int` for backwards compatibility. Also affects deserialization.
1467
+ .. versionchanged:: 4.0.0
1468
+ Remove `serialization_type` parameter and always serialize to float.
1469
+ Value is cast to a `float` upon deserialization.
1499
1470
  """
1500
1471
 
1472
+ WEEKS = "weeks"
1501
1473
  DAYS = "days"
1474
+ HOURS = "hours"
1475
+ MINUTES = "minutes"
1502
1476
  SECONDS = "seconds"
1503
- MICROSECONDS = "microseconds"
1504
1477
  MILLISECONDS = "milliseconds"
1505
- MINUTES = "minutes"
1506
- HOURS = "hours"
1507
- WEEKS = "weeks"
1478
+ MICROSECONDS = "microseconds"
1479
+
1480
+ # cache this mapping on class level for performance
1481
+ _unit_to_microseconds_mapping = {
1482
+ WEEKS: 1000000 * 60 * 60 * 24 * 7,
1483
+ DAYS: 1000000 * 60 * 60 * 24,
1484
+ HOURS: 1000000 * 60 * 60,
1485
+ MINUTES: 1000000 * 60,
1486
+ SECONDS: 1000000,
1487
+ MILLISECONDS: 1000,
1488
+ MICROSECONDS: 1,
1489
+ }
1508
1490
 
1509
1491
  #: Default error messages.
1510
1492
  default_error_messages = {
@@ -1515,49 +1497,32 @@ class TimeDelta(Field):
1515
1497
  def __init__(
1516
1498
  self,
1517
1499
  precision: str = SECONDS,
1518
- serialization_type: type[int | float] = int,
1519
- **kwargs,
1520
- ):
1500
+ **kwargs: Unpack[_BaseFieldKwargs],
1501
+ ) -> None:
1521
1502
  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
1503
 
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
- )
1504
+ if precision not in self._unit_to_microseconds_mapping:
1505
+ units = ", ".join(self._unit_to_microseconds_mapping)
1506
+ msg = f"The precision must be one of: {units}."
1536
1507
  raise ValueError(msg)
1537
1508
 
1538
- if serialization_type not in (int, float):
1539
- raise ValueError("The serialization type must be one of int or float")
1540
-
1541
1509
  self.precision = precision
1542
- self.serialization_type = serialization_type
1543
1510
  super().__init__(**kwargs)
1544
1511
 
1545
- def _serialize(self, value, attr, obj, **kwargs):
1512
+ def _serialize(self, value, attr, obj, **kwargs) -> float | None:
1546
1513
  if value is None:
1547
1514
  return None
1548
1515
 
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()
1516
+ # limit float arithmetics to a single division to minimize precision loss
1517
+ microseconds: int = utils.timedelta_to_microseconds(value)
1518
+ microseconds_per_unit: int = self._unit_to_microseconds_mapping[self.precision]
1519
+ return microseconds / microseconds_per_unit
1557
1520
 
1558
- def _deserialize(self, value, attr, data, **kwargs):
1521
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.timedelta:
1522
+ if isinstance(value, dt.timedelta):
1523
+ return value
1559
1524
  try:
1560
- value = self.serialization_type(value)
1525
+ value = float(value)
1561
1526
  except (TypeError, ValueError) as error:
1562
1527
  raise self.make_error("invalid") from error
1563
1528
 
@@ -1569,7 +1534,10 @@ class TimeDelta(Field):
1569
1534
  raise self.make_error("invalid") from error
1570
1535
 
1571
1536
 
1572
- class Mapping(Field):
1537
+ _MappingT = typing.TypeVar("_MappingT", bound=_Mapping)
1538
+
1539
+
1540
+ class Mapping(Field[_MappingT]):
1573
1541
  """An abstract class for objects with key-value pairs. This class should not be used within schemas.
1574
1542
 
1575
1543
  :param keys: A field class or instance for dict keys.
@@ -1586,7 +1554,7 @@ class Mapping(Field):
1586
1554
  Use `Dict <marshmallow.fields.Dict>` instead.
1587
1555
  """
1588
1556
 
1589
- mapping_type = dict
1557
+ mapping_type: type[_MappingT]
1590
1558
 
1591
1559
  #: Default error messages.
1592
1560
  default_error_messages = {"invalid": "Not a valid mapping type."}
@@ -1595,42 +1563,35 @@ class Mapping(Field):
1595
1563
  self,
1596
1564
  keys: Field | type[Field] | None = None,
1597
1565
  values: Field | type[Field] | None = None,
1598
- **kwargs,
1566
+ **kwargs: Unpack[_BaseFieldKwargs],
1599
1567
  ):
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
1568
  super().__init__(**kwargs)
1607
1569
  if keys is None:
1608
1570
  self.key_field = None
1609
1571
  else:
1610
1572
  try:
1611
- self.key_field = resolve_field_instance(keys)
1612
- except FieldInstanceResolutionError as error:
1573
+ self.key_field = _resolve_field_instance(keys)
1574
+ except _FieldInstanceResolutionError as error:
1613
1575
  raise ValueError(
1614
- '"keys" must be a subclass or instance of '
1615
- "marshmallow.base.FieldABC."
1576
+ '"keys" must be a subclass or instance of marshmallow.fields.Field.'
1616
1577
  ) from error
1617
1578
 
1618
1579
  if values is None:
1619
1580
  self.value_field = None
1620
1581
  else:
1621
1582
  try:
1622
- self.value_field = resolve_field_instance(values)
1623
- except FieldInstanceResolutionError as error:
1583
+ self.value_field = _resolve_field_instance(values)
1584
+ except _FieldInstanceResolutionError as error:
1624
1585
  raise ValueError(
1625
1586
  '"values" must be a subclass or instance of '
1626
- "marshmallow.base.FieldABC."
1587
+ "marshmallow.fields.Field."
1627
1588
  ) from error
1628
1589
  if isinstance(self.value_field, Nested):
1629
1590
  self.only = self.value_field.only
1630
1591
  self.exclude = self.value_field.exclude
1631
1592
 
1632
- def _bind_to_schema(self, field_name, schema):
1633
- super()._bind_to_schema(field_name, schema)
1593
+ def _bind_to_schema(self, field_name, parent):
1594
+ super()._bind_to_schema(field_name, parent)
1634
1595
  if self.value_field:
1635
1596
  self.value_field = copy.deepcopy(self.value_field)
1636
1597
  self.value_field._bind_to_schema(field_name, self)
@@ -1710,9 +1671,8 @@ class Mapping(Field):
1710
1671
  return result
1711
1672
 
1712
1673
 
1713
- class Dict(Mapping):
1714
- """A dict field. Supports dicts and dict-like objects. Extends
1715
- Mapping with dict as the mapping_type.
1674
+ class Dict(Mapping[dict]):
1675
+ """A dict field. Supports dicts and dict-like objects
1716
1676
 
1717
1677
  Example: ::
1718
1678
 
@@ -1748,7 +1708,7 @@ class Url(String):
1748
1708
  absolute: bool = True,
1749
1709
  schemes: types.StrSequenceOrSet | None = None,
1750
1710
  require_tld: bool = True,
1751
- **kwargs,
1711
+ **kwargs: Unpack[_BaseFieldKwargs],
1752
1712
  ):
1753
1713
  super().__init__(**kwargs)
1754
1714
 
@@ -1776,14 +1736,14 @@ class Email(String):
1776
1736
  #: Default error messages.
1777
1737
  default_error_messages = {"invalid": "Not a valid email address."}
1778
1738
 
1779
- def __init__(self, *args, **kwargs) -> None:
1780
- super().__init__(*args, **kwargs)
1739
+ def __init__(self, **kwargs: Unpack[_BaseFieldKwargs]) -> None:
1740
+ super().__init__(**kwargs)
1781
1741
  # Insert validation into self.validators so that multiple errors can be stored.
1782
1742
  validator = validate.Email(error=self.error_messages["invalid"])
1783
1743
  self.validators.insert(0, validator)
1784
1744
 
1785
1745
 
1786
- class IP(Field):
1746
+ class IP(Field[typing.Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]):
1787
1747
  """A IP address field.
1788
1748
 
1789
1749
  :param exploded: If `True`, serialize ipv6 address in long form, ie. with groups
@@ -1796,8 +1756,8 @@ class IP(Field):
1796
1756
 
1797
1757
  DESERIALIZATION_CLASS: type | None = None
1798
1758
 
1799
- def __init__(self, *args, exploded=False, **kwargs):
1800
- super().__init__(*args, **kwargs)
1759
+ def __init__(self, *, exploded: bool = False, **kwargs: Unpack[_BaseFieldKwargs]):
1760
+ super().__init__(**kwargs)
1801
1761
  self.exploded = exploded
1802
1762
 
1803
1763
  def _serialize(self, value, attr, obj, **kwargs) -> str | None:
@@ -1809,9 +1769,7 @@ class IP(Field):
1809
1769
 
1810
1770
  def _deserialize(
1811
1771
  self, value, attr, data, **kwargs
1812
- ) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
1813
- if value is None:
1814
- return None
1772
+ ) -> ipaddress.IPv4Address | ipaddress.IPv6Address:
1815
1773
  try:
1816
1774
  return (self.DESERIALIZATION_CLASS or ipaddress.ip_address)(
1817
1775
  utils.ensure_text_type(value)
@@ -1842,7 +1800,9 @@ class IPv6(IP):
1842
1800
  DESERIALIZATION_CLASS = ipaddress.IPv6Address
1843
1801
 
1844
1802
 
1845
- class IPInterface(Field):
1803
+ class IPInterface(
1804
+ Field[typing.Union[ipaddress.IPv4Interface, ipaddress.IPv6Interface]]
1805
+ ):
1846
1806
  """A IPInterface field.
1847
1807
 
1848
1808
  IP interface is the non-strict form of the IPNetwork type where arbitrary host
@@ -1860,8 +1820,8 @@ class IPInterface(Field):
1860
1820
 
1861
1821
  DESERIALIZATION_CLASS: type | None = None
1862
1822
 
1863
- def __init__(self, *args, exploded: bool = False, **kwargs):
1864
- super().__init__(*args, **kwargs)
1823
+ def __init__(self, *, exploded: bool = False, **kwargs: Unpack[_BaseFieldKwargs]):
1824
+ super().__init__(**kwargs)
1865
1825
  self.exploded = exploded
1866
1826
 
1867
1827
  def _serialize(self, value, attr, obj, **kwargs) -> str | None:
@@ -1871,11 +1831,9 @@ class IPInterface(Field):
1871
1831
  return value.exploded
1872
1832
  return value.compressed
1873
1833
 
1874
- def _deserialize(self, value, attr, data, **kwargs) -> None | (
1875
- ipaddress.IPv4Interface | ipaddress.IPv6Interface
1876
- ):
1877
- if value is None:
1878
- return None
1834
+ def _deserialize(
1835
+ self, value, attr, data, **kwargs
1836
+ ) -> ipaddress.IPv4Interface | ipaddress.IPv6Interface:
1879
1837
  try:
1880
1838
  return (self.DESERIALIZATION_CLASS or ipaddress.ip_interface)(
1881
1839
  utils.ensure_text_type(value)
@@ -1900,7 +1858,10 @@ class IPv6Interface(IPInterface):
1900
1858
  DESERIALIZATION_CLASS = ipaddress.IPv6Interface
1901
1859
 
1902
1860
 
1903
- class Enum(Field):
1861
+ _EnumT = typing.TypeVar("_EnumT", bound=EnumType)
1862
+
1863
+
1864
+ class Enum(Field[_EnumT]):
1904
1865
  """An Enum field (de)serializing enum members by symbol (name) or by value.
1905
1866
 
1906
1867
  :param enum: Enum class
@@ -1908,7 +1869,7 @@ class Enum(Field):
1908
1869
  or Field class or instance to use to (de)serialize by value. Defaults to False.
1909
1870
 
1910
1871
  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`.
1872
+ If it is `True`, they are (de)serialized by value using `marshmallow.fields.Raw`.
1912
1873
  If it is a field instance or class, they are (de)serialized by value using this field.
1913
1874
 
1914
1875
  .. versionadded:: 3.18.0
@@ -1920,10 +1881,10 @@ class Enum(Field):
1920
1881
 
1921
1882
  def __init__(
1922
1883
  self,
1923
- enum: type[EnumType],
1884
+ enum: type[_EnumT],
1924
1885
  *,
1925
1886
  by_value: bool | Field | type[Field] = False,
1926
- **kwargs,
1887
+ **kwargs: Unpack[_BaseFieldKwargs],
1927
1888
  ):
1928
1889
  super().__init__(**kwargs)
1929
1890
  self.enum = enum
@@ -1941,17 +1902,19 @@ class Enum(Field):
1941
1902
  self.field = Raw()
1942
1903
  else:
1943
1904
  try:
1944
- self.field = resolve_field_instance(by_value)
1945
- except FieldInstanceResolutionError as error:
1905
+ self.field = _resolve_field_instance(by_value)
1906
+ except _FieldInstanceResolutionError as error:
1946
1907
  raise ValueError(
1947
1908
  '"by_value" must be either a bool or a subclass or instance of '
1948
- "marshmallow.base.FieldABC."
1909
+ "marshmallow.fields.Field."
1949
1910
  ) from error
1950
1911
  self.choices_text = ", ".join(
1951
1912
  str(self.field._serialize(m.value, None, None)) for m in enum
1952
1913
  )
1953
1914
 
1954
- def _serialize(self, value, attr, obj, **kwargs):
1915
+ def _serialize(
1916
+ self, value: _EnumT | None, attr: str | None, obj: typing.Any, **kwargs
1917
+ ) -> typing.Any | None:
1955
1918
  if value is None:
1956
1919
  return None
1957
1920
  if self.by_value:
@@ -1960,7 +1923,9 @@ class Enum(Field):
1960
1923
  val = value.name
1961
1924
  return self.field._serialize(val, attr, obj, **kwargs)
1962
1925
 
1963
- def _deserialize(self, value, attr, data, **kwargs):
1926
+ def _deserialize(self, value, attr, data, **kwargs) -> _EnumT:
1927
+ if isinstance(value, self.enum):
1928
+ return value
1964
1929
  val = self.field._deserialize(value, attr, data, **kwargs)
1965
1930
  if self.by_value:
1966
1931
  try:
@@ -1983,10 +1948,6 @@ class Method(Field):
1983
1948
  a value The method must take a single argument ``value``, which is the
1984
1949
  value to deserialize.
1985
1950
 
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
1951
  .. versionchanged:: 3.0.0
1991
1952
  Removed ``method_name`` parameter.
1992
1953
  """
@@ -1997,7 +1958,7 @@ class Method(Field):
1997
1958
  self,
1998
1959
  serialize: str | None = None,
1999
1960
  deserialize: str | None = None,
2000
- **kwargs,
1961
+ **kwargs: Unpack[_BaseFieldKwargs], # FIXME: Omit dump_only and load_only
2001
1962
  ):
2002
1963
  # Set dump_only and load_only based on arguments
2003
1964
  kwargs["dump_only"] = bool(serialize) and not bool(deserialize)
@@ -2008,18 +1969,18 @@ class Method(Field):
2008
1969
  self._serialize_method = None
2009
1970
  self._deserialize_method = None
2010
1971
 
2011
- def _bind_to_schema(self, field_name, schema):
1972
+ def _bind_to_schema(self, field_name, parent):
2012
1973
  if self.serialize_method_name:
2013
1974
  self._serialize_method = utils.callable_or_raise(
2014
- getattr(schema, self.serialize_method_name)
1975
+ getattr(parent, self.serialize_method_name)
2015
1976
  )
2016
1977
 
2017
1978
  if self.deserialize_method_name:
2018
1979
  self._deserialize_method = utils.callable_or_raise(
2019
- getattr(schema, self.deserialize_method_name)
1980
+ getattr(parent, self.deserialize_method_name)
2020
1981
  )
2021
1982
 
2022
- super()._bind_to_schema(field_name, schema)
1983
+ super()._bind_to_schema(field_name, parent)
2023
1984
 
2024
1985
  def _serialize(self, value, attr, obj, **kwargs):
2025
1986
  if self._serialize_method is not None:
@@ -2037,22 +1998,20 @@ class Function(Field):
2037
1998
 
2038
1999
  :param serialize: A callable from which to retrieve the value.
2039
2000
  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.
2001
+ to be serialized.
2042
2002
  If no callable is provided then the ```load_only``` flag will be set
2043
2003
  to True.
2044
2004
  :param deserialize: A callable from which to retrieve the value.
2045
2005
  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.
2006
+ to be deserialized.
2048
2007
  If no callable is provided then ```value``` will be passed through
2049
2008
  unchanged.
2050
2009
 
2051
- .. versionchanged:: 2.3.0
2052
- Deprecated ``func`` parameter in favor of ``serialize``.
2053
-
2054
2010
  .. versionchanged:: 3.0.0a1
2055
2011
  Removed ``func`` parameter.
2012
+
2013
+ .. versionchanged:: 4.0.0
2014
+ Don't pass context to serialization and deserialization functions.
2056
2015
  """
2057
2016
 
2058
2017
  _CHECK_ATTRIBUTE = False
@@ -2069,7 +2028,7 @@ class Function(Field):
2069
2028
  | typing.Callable[[typing.Any, dict], typing.Any]
2070
2029
  | None
2071
2030
  ) = None,
2072
- **kwargs,
2031
+ **kwargs: Unpack[_BaseFieldKwargs], # FIXME: Omit dump_only and load_only
2073
2032
  ):
2074
2033
  # Set dump_only and load_only based on arguments
2075
2034
  kwargs["dump_only"] = bool(serialize) and not bool(deserialize)
@@ -2079,23 +2038,18 @@ class Function(Field):
2079
2038
  self.deserialize_func = deserialize and utils.callable_or_raise(deserialize)
2080
2039
 
2081
2040
  def _serialize(self, value, attr, obj, **kwargs):
2082
- return self._call_or_raise(self.serialize_func, obj, attr)
2041
+ return self.serialize_func(obj)
2083
2042
 
2084
2043
  def _deserialize(self, value, attr, data, **kwargs):
2085
2044
  if self.deserialize_func:
2086
- return self._call_or_raise(self.deserialize_func, value, attr)
2045
+ return self.deserialize_func(value)
2087
2046
  return value
2088
2047
 
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)
2048
+
2049
+ _ContantT = typing.TypeVar("_ContantT")
2096
2050
 
2097
2051
 
2098
- class Constant(Field):
2052
+ class Constant(Field[_ContantT]):
2099
2053
  """A field that (de)serializes to a preset constant. If you only want the
2100
2054
  constant added for serialization or deserialization, you should use
2101
2055
  ``dump_only=True`` or ``load_only=True`` respectively.
@@ -2105,49 +2059,22 @@ class Constant(Field):
2105
2059
 
2106
2060
  _CHECK_ATTRIBUTE = False
2107
2061
 
2108
- def __init__(self, constant: typing.Any, **kwargs):
2062
+ def __init__(self, constant: _ContantT, **kwargs: Unpack[_BaseFieldKwargs]):
2109
2063
  super().__init__(**kwargs)
2110
2064
  self.constant = constant
2111
2065
  self.load_default = constant
2112
2066
  self.dump_default = constant
2113
2067
 
2114
- def _serialize(self, value, *args, **kwargs):
2068
+ def _serialize(self, value, *args, **kwargs) -> _ContantT:
2115
2069
  return self.constant
2116
2070
 
2117
- def _deserialize(self, value, *args, **kwargs):
2071
+ def _deserialize(self, value, *args, **kwargs) -> _ContantT:
2118
2072
  return self.constant
2119
2073
 
2120
2074
 
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
2075
  # Aliases
2150
2076
  URL = Url
2077
+
2151
2078
  Str = String
2152
2079
  Bool = Boolean
2153
2080
  Int = Integer