aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/marshmallow
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/marshmallow')
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/__init__.py81
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/base.py61
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/class_registry.py103
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/decorators.py238
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/error_store.py60
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/exceptions.py71
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/fields.py2153
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/orderedset.py89
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/schema.py1309
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/types.py40
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/utils.py380
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/validate.py700
-rw-r--r--.venv/lib/python3.12/site-packages/marshmallow/warnings.py10
14 files changed, 5295 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/__init__.py b/.venv/lib/python3.12/site-packages/marshmallow/__init__.py
new file mode 100644
index 00000000..85b0f3c0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/__init__.py
@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+import importlib.metadata
+import typing
+
+from packaging.version import Version
+
+from marshmallow.decorators import (
+ post_dump,
+ post_load,
+ pre_dump,
+ pre_load,
+ validates,
+ validates_schema,
+)
+from marshmallow.exceptions import ValidationError
+from marshmallow.schema import Schema, SchemaOpts
+from marshmallow.utils import EXCLUDE, INCLUDE, RAISE, missing, pprint
+
+from . import fields
+
+
+def __getattr__(name: str) -> typing.Any:
+ import warnings
+
+ if name == "__version__":
+ warnings.warn(
+ "The '__version__' attribute is deprecated and will be removed in"
+ " in a future version. Use feature detection or"
+ " 'importlib.metadata.version(\"marshmallow\")' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return importlib.metadata.version("marshmallow")
+
+ if name == "__parsed_version__":
+ warnings.warn(
+ "The '__parsed_version__' attribute is deprecated and will be removed in"
+ " in a future version. Use feature detection or"
+ " 'packaging.Version(importlib.metadata.version(\"marshmallow\"))' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return Version(importlib.metadata.version("marshmallow"))
+
+ if name == "__version_info__":
+ warnings.warn(
+ "The '__version_info__' attribute is deprecated and will be removed in"
+ " in a future version. Use feature detection or"
+ " 'packaging.Version(importlib.metadata.version(\"marshmallow\")).release' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ __parsed_version__ = Version(importlib.metadata.version("marshmallow"))
+ __version_info__: tuple[int, int, int] | tuple[int, int, int, str, int] = (
+ __parsed_version__.release # type: ignore[assignment]
+ )
+ if __parsed_version__.pre:
+ __version_info__ += __parsed_version__.pre # type: ignore[assignment]
+ return __version_info__
+
+ raise AttributeError(name)
+
+
+__all__ = [
+ "EXCLUDE",
+ "INCLUDE",
+ "RAISE",
+ "Schema",
+ "SchemaOpts",
+ "ValidationError",
+ "fields",
+ "missing",
+ "post_dump",
+ "post_load",
+ "pprint",
+ "pre_dump",
+ "pre_load",
+ "validates",
+ "validates_schema",
+]
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/base.py b/.venv/lib/python3.12/site-packages/marshmallow/base.py
new file mode 100644
index 00000000..14b248aa
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/base.py
@@ -0,0 +1,61 @@
+"""Abstract base classes.
+
+These are necessary to avoid circular imports between schema.py and fields.py.
+
+.. warning::
+
+ This module is deprecated. Users should not import from this module.
+ Use `marshmallow.fields.Field` and `marshmallow.schema.Schema` as base classes instead.
+"""
+
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+
+
+class FieldABC(ABC):
+ """Abstract base class from which all Field classes inherit."""
+
+ @abstractmethod
+ def serialize(self, attr, obj, accessor=None):
+ pass
+
+ @abstractmethod
+ def deserialize(self, value):
+ pass
+
+ @abstractmethod
+ def _serialize(self, value, attr, obj, **kwargs):
+ pass
+
+ @abstractmethod
+ def _deserialize(self, value, attr, data, **kwargs):
+ pass
+
+
+class SchemaABC(ABC):
+ """Abstract base class from which all Schemas inherit."""
+
+ @abstractmethod
+ def dump(self, obj, *, many: bool | None = None):
+ pass
+
+ @abstractmethod
+ def dumps(self, obj, *, many: bool | None = None):
+ pass
+
+ @abstractmethod
+ def load(self, data, *, many: bool | None = None, partial=None, unknown=None):
+ pass
+
+ @abstractmethod
+ def loads(
+ self,
+ json_data,
+ *,
+ many: bool | None = None,
+ partial=None,
+ unknown=None,
+ **kwargs,
+ ):
+ pass
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/class_registry.py b/.venv/lib/python3.12/site-packages/marshmallow/class_registry.py
new file mode 100644
index 00000000..6c02f9cd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/class_registry.py
@@ -0,0 +1,103 @@
+"""A registry of :class:`Schema <marshmallow.Schema>` classes. This allows for string
+lookup of schemas, which may be used with
+class:`fields.Nested <marshmallow.fields.Nested>`.
+
+.. warning::
+
+ This module is treated as private API.
+ Users should not need to use this module directly.
+"""
+# ruff: noqa: ERA001
+
+from __future__ import annotations
+
+import typing
+
+from marshmallow.exceptions import RegistryError
+
+if typing.TYPE_CHECKING:
+ from marshmallow import Schema
+
+ SchemaType = type[Schema]
+
+# {
+# <class_name>: <list of class objects>
+# <module_path_to_class>: <list of class objects>
+# }
+_registry = {} # type: dict[str, list[SchemaType]]
+
+
+def register(classname: str, cls: SchemaType) -> None:
+ """Add a class to the registry of serializer classes. When a class is
+ registered, an entry for both its classname and its full, module-qualified
+ path are added to the registry.
+
+ Example: ::
+
+ class MyClass:
+ pass
+
+
+ register("MyClass", MyClass)
+ # Registry:
+ # {
+ # 'MyClass': [path.to.MyClass],
+ # 'path.to.MyClass': [path.to.MyClass],
+ # }
+
+ """
+ # Module where the class is located
+ module = cls.__module__
+ # Full module path to the class
+ # e.g. user.schemas.UserSchema
+ fullpath = f"{module}.{classname}"
+ # If the class is already registered; need to check if the entries are
+ # in the same module as cls to avoid having multiple instances of the same
+ # class in the registry
+ if classname in _registry and not any(
+ each.__module__ == module for each in _registry[classname]
+ ):
+ _registry[classname].append(cls)
+ elif classname not in _registry:
+ _registry[classname] = [cls]
+
+ # Also register the full path
+ if fullpath not in _registry:
+ _registry.setdefault(fullpath, []).append(cls)
+ else:
+ # If fullpath does exist, replace existing entry
+ _registry[fullpath] = [cls]
+
+
+@typing.overload
+def get_class(classname: str, *, all: typing.Literal[False] = ...) -> SchemaType: ...
+
+
+@typing.overload
+def get_class(
+ classname: str, *, all: typing.Literal[True] = ...
+) -> list[SchemaType]: ...
+
+
+def get_class(classname: str, *, all: bool = False) -> list[SchemaType] | SchemaType: # noqa: A002
+ """Retrieve a class from the registry.
+
+ :raises: `marshmallow.exceptions.RegistryError` if the class cannot be found
+ or if there are multiple entries for the given class name.
+ """
+ try:
+ classes = _registry[classname]
+ except KeyError as error:
+ raise RegistryError(
+ f"Class with name {classname!r} was not found. You may need "
+ "to import the class."
+ ) from error
+ if len(classes) > 1:
+ if all:
+ return _registry[classname]
+ raise RegistryError(
+ f"Multiple classes with name {classname!r} "
+ "were found. Please use the full, "
+ "module-qualified path."
+ )
+ return _registry[classname][0]
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/decorators.py b/.venv/lib/python3.12/site-packages/marshmallow/decorators.py
new file mode 100644
index 00000000..eefba6c1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/decorators.py
@@ -0,0 +1,238 @@
+"""Decorators for registering schema pre-processing and post-processing methods.
+These should be imported from the top-level `marshmallow` module.
+
+Methods decorated with
+`pre_load <marshmallow.decorators.pre_load>`, `post_load <marshmallow.decorators.post_load>`,
+`pre_dump <marshmallow.decorators.pre_dump>`, `post_dump <marshmallow.decorators.post_dump>`,
+and `validates_schema <marshmallow.decorators.validates_schema>` receive
+``many`` as a keyword argument. In addition, `pre_load <marshmallow.decorators.pre_load>`,
+`post_load <marshmallow.decorators.post_load>`,
+and `validates_schema <marshmallow.decorators.validates_schema>` receive
+``partial``. If you don't need these arguments, add ``**kwargs`` to your method
+signature.
+
+
+Example: ::
+
+ from marshmallow import (
+ Schema,
+ pre_load,
+ pre_dump,
+ post_load,
+ validates_schema,
+ validates,
+ fields,
+ ValidationError,
+ )
+
+
+ class UserSchema(Schema):
+ email = fields.Str(required=True)
+ age = fields.Integer(required=True)
+
+ @post_load
+ def lowerstrip_email(self, item, many, **kwargs):
+ item["email"] = item["email"].lower().strip()
+ return item
+
+ @pre_load(pass_many=True)
+ def remove_envelope(self, data, many, **kwargs):
+ namespace = "results" if many else "result"
+ return data[namespace]
+
+ @post_dump(pass_many=True)
+ def add_envelope(self, data, many, **kwargs):
+ namespace = "results" if many else "result"
+ return {namespace: data}
+
+ @validates_schema
+ def validate_email(self, data, **kwargs):
+ if len(data["email"]) < 3:
+ raise ValidationError("Email must be more than 3 characters", "email")
+
+ @validates("age")
+ def validate_age(self, data, **kwargs):
+ if data < 14:
+ raise ValidationError("Too young!")
+
+.. note::
+ These decorators only work with instance methods. Class and static
+ methods are not supported.
+
+.. warning::
+ The invocation order of decorated methods of the same type is not guaranteed.
+ If you need to guarantee order of different processing steps, you should put
+ them in the same processing method.
+"""
+
+from __future__ import annotations
+
+import functools
+from collections import defaultdict
+from typing import Any, Callable, cast
+
+PRE_DUMP = "pre_dump"
+POST_DUMP = "post_dump"
+PRE_LOAD = "pre_load"
+POST_LOAD = "post_load"
+VALIDATES = "validates"
+VALIDATES_SCHEMA = "validates_schema"
+
+
+class MarshmallowHook:
+ __marshmallow_hook__: dict[str, list[tuple[bool, Any]]] | None = None
+
+
+def validates(field_name: str) -> Callable[..., Any]:
+ """Register a field validator.
+
+ :param field_name: Name of the field that the method validates.
+ """
+ return set_hook(None, VALIDATES, field_name=field_name)
+
+
+def validates_schema(
+ fn: Callable[..., Any] | None = None,
+ pass_many: bool = False, # noqa: FBT001, FBT002
+ pass_original: bool = False, # noqa: FBT001, FBT002
+ skip_on_field_errors: bool = True, # noqa: FBT001, FBT002
+) -> Callable[..., Any]:
+ """Register a schema-level validator.
+
+ By default it receives a single object at a time, transparently handling the ``many``
+ argument passed to the `Schema <marshmallow.Schema>`'s :func:`~marshmallow.Schema.validate` call.
+ If ``pass_many=True``, the raw data (which may be a collection) is passed.
+
+ If ``pass_original=True``, the original data (before unmarshalling) will be passed as
+ an additional argument to the method.
+
+ If ``skip_on_field_errors=True``, this validation method will be skipped whenever
+ validation errors have been detected when validating fields.
+
+ .. versionchanged:: 3.0.0b1
+ ``skip_on_field_errors`` defaults to `True`.
+
+ .. versionchanged:: 3.0.0
+ ``partial`` and ``many`` are always passed as keyword arguments to
+ the decorated method.
+ """
+ return set_hook(
+ fn,
+ VALIDATES_SCHEMA,
+ many=pass_many,
+ pass_original=pass_original,
+ skip_on_field_errors=skip_on_field_errors,
+ )
+
+
+def pre_dump(
+ fn: Callable[..., Any] | None = None,
+ pass_many: bool = False, # noqa: FBT001, FBT002
+) -> Callable[..., Any]:
+ """Register a method to invoke before serializing an object. The method
+ receives the object to be serialized and returns the processed object.
+
+ By default it receives a single object at a time, transparently handling the ``many``
+ argument passed to the `Schema <marshmallow.Schema>`'s :func:`~marshmallow.Schema.dump` call.
+ If ``pass_many=True``, the raw data (which may be a collection) is passed.
+
+ .. versionchanged:: 3.0.0
+ ``many`` is always passed as a keyword arguments to the decorated method.
+ """
+ return set_hook(fn, PRE_DUMP, many=pass_many)
+
+
+def post_dump(
+ fn: Callable[..., Any] | None = None,
+ pass_many: bool = False, # noqa: FBT001, FBT002
+ pass_original: bool = False, # noqa: FBT001, FBT002
+) -> Callable[..., Any]:
+ """Register a method to invoke after serializing an object. The method
+ receives the serialized object and returns the processed object.
+
+ By default it receives a single object at a time, transparently handling the ``many``
+ argument passed to the `Schema <marshmallow.Schema>`'s :func:`~marshmallow.Schema.dump` call.
+ If ``pass_many=True``, the raw data (which may be a collection) is passed.
+
+ If ``pass_original=True``, the original data (before serializing) will be passed as
+ an additional argument to the method.
+
+ .. versionchanged:: 3.0.0
+ ``many`` is always passed as a keyword arguments to the decorated method.
+ """
+ return set_hook(fn, POST_DUMP, many=pass_many, pass_original=pass_original)
+
+
+def pre_load(
+ fn: Callable[..., Any] | None = None,
+ pass_many: bool = False, # noqa: FBT001, FBT002
+) -> Callable[..., Any]:
+ """Register a method to invoke before deserializing an object. The method
+ receives the data to be deserialized and returns the processed data.
+
+ By default it receives a single object at a time, transparently handling the ``many``
+ argument passed to the `Schema <marshmallow.Schema>`'s :func:`~marshmallow.Schema.load` call.
+ If ``pass_many=True``, the raw data (which may be a collection) is passed.
+
+ .. versionchanged:: 3.0.0
+ ``partial`` and ``many`` are always passed as keyword arguments to
+ the decorated method.
+ """
+ return set_hook(fn, PRE_LOAD, many=pass_many)
+
+
+def post_load(
+ fn: Callable[..., Any] | None = None,
+ pass_many: bool = False, # noqa: FBT001, FBT002
+ pass_original: bool = False, # noqa: FBT001, FBT002
+) -> Callable[..., Any]:
+ """Register a method to invoke after deserializing an object. The method
+ receives the deserialized data and returns the processed data.
+
+ By default it receives a single object at a time, transparently handling the ``many``
+ argument passed to the `Schema <marshmallow.Schema>`'s :func:`~marshmallow.Schema.load` call.
+ If ``pass_many=True``, the raw data (which may be a collection) is passed.
+
+ If ``pass_original=True``, the original data (before deserializing) will be passed as
+ an additional argument to the method.
+
+ .. versionchanged:: 3.0.0
+ ``partial`` and ``many`` are always passed as keyword arguments to
+ the decorated method.
+ """
+ return set_hook(fn, POST_LOAD, many=pass_many, pass_original=pass_original)
+
+
+def set_hook(
+ fn: Callable[..., Any] | None,
+ tag: str,
+ many: bool = False, # noqa: FBT001, FBT002
+ **kwargs: Any,
+) -> Callable[..., Any]:
+ """Mark decorated function as a hook to be picked up later.
+ You should not need to use this method directly.
+
+ .. note::
+ Currently only works with functions and instance methods. Class and
+ static methods are not supported.
+
+ :return: Decorated function if supplied, else this decorator with its args
+ bound.
+ """
+ # Allow using this as either a decorator or a decorator factory.
+ if fn is None:
+ return functools.partial(set_hook, tag=tag, many=many, **kwargs)
+
+ # Set a __marshmallow_hook__ attribute instead of wrapping in some class,
+ # because I still want this to end up as a normal (unbound) method.
+ function = cast(MarshmallowHook, fn)
+ try:
+ hook_config = function.__marshmallow_hook__
+ except AttributeError:
+ function.__marshmallow_hook__ = hook_config = defaultdict(list)
+ # Also save the kwargs for the tagged function on
+ # __marshmallow_hook__, keyed by <tag>
+ if hook_config is not None:
+ hook_config[tag].append((many, kwargs))
+
+ return fn
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/error_store.py b/.venv/lib/python3.12/site-packages/marshmallow/error_store.py
new file mode 100644
index 00000000..61320ab2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/error_store.py
@@ -0,0 +1,60 @@
+"""Utilities for storing collections of error messages.
+
+.. warning::
+
+ This module is treated as private API.
+ Users should not need to use this module directly.
+"""
+
+from marshmallow.exceptions import SCHEMA
+
+
+class ErrorStore:
+ def __init__(self):
+ #: Dictionary of errors stored during serialization
+ self.errors = {}
+
+ def store_error(self, messages, field_name=SCHEMA, index=None):
+ # field error -> store/merge error messages under field name key
+ # schema error -> if string or list, store/merge under _schema key
+ # -> if dict, store/merge with other top-level keys
+ if field_name != SCHEMA or not isinstance(messages, dict):
+ messages = {field_name: messages}
+ if index is not None:
+ messages = {index: messages}
+ self.errors = merge_errors(self.errors, messages)
+
+
+def merge_errors(errors1, errors2): # noqa: PLR0911
+ """Deeply merge two error messages.
+
+ The format of ``errors1`` and ``errors2`` matches the ``message``
+ parameter of :exc:`marshmallow.exceptions.ValidationError`.
+ """
+ if not errors1:
+ return errors2
+ if not errors2:
+ return errors1
+ if isinstance(errors1, list):
+ if isinstance(errors2, list):
+ return errors1 + errors2
+ if isinstance(errors2, dict):
+ return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
+ return [*errors1, errors2]
+ if isinstance(errors1, dict):
+ if isinstance(errors2, list):
+ return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
+ if isinstance(errors2, dict):
+ errors = dict(errors1)
+ for key, val in errors2.items():
+ if key in errors:
+ errors[key] = merge_errors(errors[key], val)
+ else:
+ errors[key] = val
+ return errors
+ return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
+ if isinstance(errors2, list):
+ return [errors1, *errors2]
+ if isinstance(errors2, dict):
+ return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
+ return [errors1, errors2]
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/exceptions.py b/.venv/lib/python3.12/site-packages/marshmallow/exceptions.py
new file mode 100644
index 00000000..096b6bd0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/exceptions.py
@@ -0,0 +1,71 @@
+"""Exception classes for marshmallow-related errors."""
+
+from __future__ import annotations
+
+import typing
+
+# Key used for schema-level validation errors
+SCHEMA = "_schema"
+
+
+class MarshmallowError(Exception):
+ """Base class for all marshmallow-related errors."""
+
+
+class ValidationError(MarshmallowError):
+ """Raised when validation fails on a field or schema.
+
+ Validators and custom fields should raise this exception.
+
+ :param message: An error message, list of error messages, or dict of
+ error messages. If a dict, the keys are subitems and the values are error messages.
+ :param field_name: Field name to store the error on.
+ If `None`, the error is stored as schema-level error.
+ :param data: Raw input data.
+ :param valid_data: Valid (de)serialized data.
+ """
+
+ def __init__(
+ self,
+ message: str | list | dict,
+ field_name: str = SCHEMA,
+ data: typing.Mapping[str, typing.Any]
+ | typing.Iterable[typing.Mapping[str, typing.Any]]
+ | None = None,
+ valid_data: list[dict[str, typing.Any]] | dict[str, typing.Any] | None = None,
+ **kwargs,
+ ):
+ self.messages = [message] if isinstance(message, (str, bytes)) else message
+ self.field_name = field_name
+ self.data = data
+ self.valid_data = valid_data
+ self.kwargs = kwargs
+ super().__init__(message)
+
+ def normalized_messages(self):
+ if self.field_name == SCHEMA and isinstance(self.messages, dict):
+ return self.messages
+ return {self.field_name: self.messages}
+
+ @property
+ def messages_dict(self) -> dict[str, typing.Any]:
+ if not isinstance(self.messages, dict):
+ raise TypeError(
+ "cannot access 'messages_dict' when 'messages' is of type "
+ + type(self.messages).__name__
+ )
+ return self.messages
+
+
+class RegistryError(NameError):
+ """Raised when an invalid operation is performed on the serializer
+ class registry.
+ """
+
+
+class StringNotCollectionError(MarshmallowError, TypeError):
+ """Raised when a string is passed when a list of strings is expected."""
+
+
+class FieldInstanceResolutionError(MarshmallowError, TypeError):
+ """Raised when schema to instantiate is neither a Schema class nor an instance."""
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/fields.py b/.venv/lib/python3.12/site-packages/marshmallow/fields.py
new file mode 100644
index 00000000..dc3d8120
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/fields.py
@@ -0,0 +1,2153 @@
+# ruff: noqa: F841, SLF001
+from __future__ import annotations
+
+import collections
+import copy
+import datetime as dt
+import decimal
+import ipaddress
+import math
+import numbers
+import typing
+import uuid
+import warnings
+from collections.abc import Mapping as _Mapping
+
+from marshmallow import class_registry, types, utils, validate
+from marshmallow.base import FieldABC
+from marshmallow.exceptions import (
+ FieldInstanceResolutionError,
+ StringNotCollectionError,
+ ValidationError,
+)
+from marshmallow.utils import (
+ is_aware,
+ is_collection,
+ resolve_field_instance,
+)
+from marshmallow.utils import (
+ missing as missing_,
+)
+from marshmallow.validate import And, Length
+from marshmallow.warnings import (
+ ChangedInMarshmallow4Warning,
+ RemovedInMarshmallow4Warning,
+)
+
+if typing.TYPE_CHECKING:
+ from enum import Enum as EnumType
+
+ from marshmallow.schema import Schema, SchemaMeta
+
+
+__all__ = [
+ "IP",
+ "URL",
+ "UUID",
+ "AwareDateTime",
+ "Bool",
+ "Boolean",
+ "Constant",
+ "Date",
+ "DateTime",
+ "Decimal",
+ "Dict",
+ "Email",
+ "Enum",
+ "Field",
+ "Float",
+ "Function",
+ "IPInterface",
+ "IPv4",
+ "IPv4Interface",
+ "IPv6",
+ "IPv6Interface",
+ "Int",
+ "Integer",
+ "List",
+ "Mapping",
+ "Method",
+ "NaiveDateTime",
+ "Nested",
+ "Number",
+ "Pluck",
+ "Raw",
+ "Str",
+ "String",
+ "Time",
+ "TimeDelta",
+ "Tuple",
+ "Url",
+]
+
+
+class Field(FieldABC):
+ """Base field from which other fields inherit.
+
+ :param dump_default: If set, this value will be used during serialization if the
+ input value is missing. If not set, the field will be excluded from the
+ serialized output if the input value is missing. May be a value or a callable.
+ :param load_default: Default deserialization value for the field if the field is not
+ found in the input data. May be a value or a callable.
+ :param data_key: The name of the dict key in the external representation, i.e.
+ the input of `load` and the output of `dump`.
+ If `None`, the key will match the name of the field.
+ :param attribute: The name of the key/attribute in the internal representation, i.e.
+ the output of `load` and the input of `dump`.
+ If `None`, the key/attribute will match the name of the field.
+ Note: This should only be used for very specific use cases such as
+ outputting multiple fields for a single attribute, or using keys/attributes
+ that are invalid variable names, unsuitable for field names. In most cases,
+ you should use ``data_key`` instead.
+ :param validate: Validator or collection of validators that are called
+ during deserialization. Validator takes a field's input value as
+ its only parameter and returns a boolean.
+ If it returns `False`, an :exc:`ValidationError` is raised.
+ :param required: Raise a :exc:`ValidationError` if the field value
+ is not supplied during deserialization.
+ :param allow_none: Set this to `True` if `None` should be considered a valid value during
+ validation/deserialization. If set to `False` (the default), `None` is considered invalid input.
+ If ``load_default`` is explicitly set to `None` and ``allow_none`` is unset,
+ `allow_none` is implicitly set to ``True``.
+ :param load_only: If `True` skip this field during serialization, otherwise
+ its value will be present in the serialized data.
+ :param dump_only: If `True` skip this field during deserialization, otherwise
+ its value will be present in the deserialized object. In the context of an
+ HTTP API, this effectively marks the field as "read-only".
+ :param error_messages: Overrides for `Field.default_error_messages`.
+ :param metadata: Extra information to be stored as field metadata.
+
+ .. versionchanged:: 3.0.0b8
+ Add ``data_key`` parameter for the specifying the key in the input and
+ output data. This parameter replaced both ``load_from`` and ``dump_to``.
+
+ .. versionchanged:: 3.13.0
+ Replace ``missing`` and ``default`` parameters with ``load_default`` and ``dump_default``.
+
+ .. versionchanged:: 3.24.0
+ `Field <marshmallow.fields.Field>` should no longer be used as a field within a `Schema <marshmallow.Schema>`.
+ Use `Raw <marshmallow.fields.Raw>` or another `Field <marshmallow.fields.Field>` subclass instead.
+ """
+
+ # Some fields, such as Method fields and Function fields, are not expected
+ # to exist as attributes on the objects to serialize. Set this to False
+ # for those fields
+ _CHECK_ATTRIBUTE = True
+
+ #: Default error messages for various kinds of errors. The keys in this dictionary
+ #: are passed to `Field.make_error`. The values are error messages passed to
+ #: :exc:`marshmallow.exceptions.ValidationError`.
+ default_error_messages: dict[str, str] = {
+ "required": "Missing data for required field.",
+ "null": "Field may not be null.",
+ "validator_failed": "Invalid value.",
+ }
+
+ def __init__(
+ self,
+ *,
+ load_default: typing.Any = missing_,
+ missing: typing.Any = missing_,
+ dump_default: typing.Any = missing_,
+ default: typing.Any = missing_,
+ data_key: str | None = None,
+ attribute: str | None = None,
+ validate: types.Validator | typing.Iterable[types.Validator] | None = None,
+ required: bool = False,
+ allow_none: bool | None = None,
+ load_only: bool = False,
+ dump_only: bool = False,
+ error_messages: dict[str, str] | None = None,
+ metadata: typing.Mapping[str, typing.Any] | None = None,
+ **additional_metadata,
+ ) -> None:
+ if self.__class__ is Field:
+ warnings.warn(
+ "`Field` should not be instantiated. Use `fields.Raw` or "
+ "another field subclass instead.",
+ ChangedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ # handle deprecated `default` and `missing` parameters
+ if default is not missing_:
+ warnings.warn(
+ "The 'default' argument to fields is deprecated. "
+ "Use 'dump_default' instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ if dump_default is missing_:
+ dump_default = default
+ if missing is not missing_:
+ warnings.warn(
+ "The 'missing' argument to fields is deprecated. "
+ "Use 'load_default' instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ if load_default is missing_:
+ load_default = missing
+ self.dump_default = dump_default
+ self.load_default = load_default
+
+ self.attribute = attribute
+ self.data_key = data_key
+ self.validate = validate
+ if validate is None:
+ self.validators = []
+ elif callable(validate):
+ self.validators = [validate]
+ elif utils.is_iterable_but_not_string(validate):
+ self.validators = list(validate)
+ else:
+ raise ValueError(
+ "The 'validate' parameter must be a callable "
+ "or a collection of callables."
+ )
+
+ # If allow_none is None and load_default is None
+ # None should be considered valid by default
+ self.allow_none = load_default is None if allow_none is None else allow_none
+ self.load_only = load_only
+ self.dump_only = dump_only
+ if required is True and load_default is not missing_:
+ raise ValueError("'load_default' must not be set for required fields.")
+ self.required = required
+
+ metadata = metadata or {}
+ self.metadata = {**metadata, **additional_metadata}
+ if additional_metadata:
+ warnings.warn(
+ "Passing field metadata as keyword arguments is deprecated. Use the "
+ "explicit `metadata=...` argument instead. "
+ f"Additional metadata: {additional_metadata}",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+
+ # Collect default error message from self and parent classes
+ messages: dict[str, str] = {}
+ for cls in reversed(self.__class__.__mro__):
+ messages.update(getattr(cls, "default_error_messages", {}))
+ messages.update(error_messages or {})
+ self.error_messages = messages
+
+ self.parent: Field | Schema | None = None
+ self.name: str | None = None
+ self.root: Schema | None = None
+
+ def __repr__(self) -> str:
+ return (
+ f"<fields.{self.__class__.__name__}(dump_default={self.dump_default!r}, "
+ f"attribute={self.attribute!r}, "
+ f"validate={self.validate}, required={self.required}, "
+ f"load_only={self.load_only}, dump_only={self.dump_only}, "
+ f"load_default={self.load_default}, allow_none={self.allow_none}, "
+ f"error_messages={self.error_messages})>"
+ )
+
+ def __deepcopy__(self, memo):
+ return copy.copy(self)
+
+ def get_value(
+ self,
+ obj: typing.Any,
+ attr: str,
+ accessor: (
+ typing.Callable[[typing.Any, str, typing.Any], typing.Any] | None
+ ) = None,
+ default: typing.Any = missing_,
+ ):
+ """Return the value for a given key from an object.
+
+ :param obj: The object to get the value from.
+ :param attr: The attribute/key in `obj` to get the value from.
+ :param accessor: A callable used to retrieve the value of `attr` from
+ the object `obj`. Defaults to `marshmallow.utils.get_value`.
+ """
+ accessor_func = accessor or utils.get_value
+ check_key = attr if self.attribute is None else self.attribute
+ return accessor_func(obj, check_key, default)
+
+ def _validate(self, value: typing.Any):
+ """Perform validation on ``value``. Raise a :exc:`ValidationError` if validation
+ does not succeed.
+ """
+ self._validate_all(value)
+
+ @property
+ def _validate_all(self) -> typing.Callable[[typing.Any], None]:
+ return And(*self.validators, error=self.error_messages["validator_failed"])
+
+ def make_error(self, key: str, **kwargs) -> ValidationError:
+ """Helper method to make a `ValidationError` with an error message
+ from ``self.error_messages``.
+ """
+ try:
+ msg = self.error_messages[key]
+ except KeyError as error:
+ class_name = self.__class__.__name__
+ message = (
+ f"ValidationError raised by `{class_name}`, but error key `{key}` does "
+ "not exist in the `error_messages` dictionary."
+ )
+ raise AssertionError(message) from error
+ if isinstance(msg, (str, bytes)):
+ msg = msg.format(**kwargs)
+ return ValidationError(msg)
+
+ def fail(self, key: str, **kwargs):
+ """Helper method that raises a `ValidationError` with an error message
+ from ``self.error_messages``.
+
+ .. deprecated:: 3.0.0
+ Use `make_error <marshmallow.fields.Field.make_error>` instead.
+ """
+ warnings.warn(
+ f'`Field.fail` is deprecated. Use `raise self.make_error("{key}", ...)` instead.',
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ raise self.make_error(key=key, **kwargs)
+
+ def _validate_missing(self, value: typing.Any) -> None:
+ """Validate missing values. Raise a :exc:`ValidationError` if
+ `value` should be considered missing.
+ """
+ if value is missing_ and self.required:
+ raise self.make_error("required")
+ if value is None and not self.allow_none:
+ raise self.make_error("null")
+
+ def serialize(
+ self,
+ attr: str,
+ obj: typing.Any,
+ accessor: (
+ typing.Callable[[typing.Any, str, typing.Any], typing.Any] | None
+ ) = None,
+ **kwargs,
+ ):
+ """Pulls the value for the given key from the object, applies the
+ field's formatting and returns the result.
+
+ :param attr: The attribute/key to get from the object.
+ :param obj: The object to access the attribute/key from.
+ :param accessor: Function used to access values from ``obj``.
+ :param kwargs: Field-specific keyword arguments.
+ """
+ if self._CHECK_ATTRIBUTE:
+ value = self.get_value(obj, attr, accessor=accessor)
+ if value is missing_:
+ default = self.dump_default
+ value = default() if callable(default) else default
+ if value is missing_:
+ return value
+ else:
+ value = None
+ return self._serialize(value, attr, obj, **kwargs)
+
+ def deserialize(
+ self,
+ value: typing.Any,
+ attr: str | None = None,
+ data: typing.Mapping[str, typing.Any] | None = None,
+ **kwargs,
+ ):
+ """Deserialize ``value``.
+
+ :param value: The value to deserialize.
+ :param attr: The attribute/key in `data` to deserialize.
+ :param data: The raw input data passed to `Schema.load <marshmallow.Schema.load>`.
+ :param kwargs: Field-specific keyword arguments.
+ :raise ValidationError: If an invalid value is passed or if a required value
+ is missing.
+ """
+ # Validate required fields, deserialize, then validate
+ # deserialized value
+ self._validate_missing(value)
+ if value is missing_:
+ _miss = self.load_default
+ return _miss() if callable(_miss) else _miss
+ if self.allow_none and value is None:
+ return None
+ output = self._deserialize(value, attr, data, **kwargs)
+ self._validate(output)
+ return output
+
+ # Methods for concrete classes to override.
+
+ def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
+ """Update field with values from its parent schema. Called by
+ `Schema._bind_field <marshmallow.Schema._bind_field>`.
+
+ :param field_name: Field name set in schema.
+ :param schema: Parent object.
+ """
+ self.parent = self.parent or schema
+ self.name = self.name or field_name
+ self.root = self.root or (
+ self.parent.root if isinstance(self.parent, FieldABC) else self.parent
+ )
+
+ def _serialize(
+ self, value: typing.Any, attr: str | None, obj: typing.Any, **kwargs
+ ) -> typing.Any:
+ """Serializes ``value`` to a basic Python datatype. Noop by default.
+ Concrete :class:`Field` classes should implement this method.
+
+ Example: ::
+
+ class TitleCase(Field):
+ def _serialize(self, value, attr, obj, **kwargs):
+ if not value:
+ return ""
+ return str(value).title()
+
+ :param value: The value to be serialized.
+ :param attr: The attribute or key on the object to be serialized.
+ :param obj: The object the value was pulled from.
+ :param kwargs: Field-specific keyword arguments.
+ :return: The serialized value
+ """
+ return value
+
+ def _deserialize(
+ self,
+ value: typing.Any,
+ attr: str | None,
+ data: typing.Mapping[str, typing.Any] | None,
+ **kwargs,
+ ) -> typing.Any:
+ """Deserialize value. Concrete :class:`Field` classes should implement this method.
+
+ :param value: The value to be deserialized.
+ :param attr: The attribute/key in `data` to be deserialized.
+ :param data: The raw input data passed to the `Schema.load <marshmallow.Schema.load>`.
+ :param kwargs: Field-specific keyword arguments.
+ :raise ValidationError: In case of formatting or validation failure.
+ :return: The deserialized value.
+
+ .. versionchanged:: 3.0.0
+ Added ``**kwargs`` to signature.
+ """
+ return value
+
+ # Properties
+
+ @property
+ def context(self) -> dict | None:
+ """The context dictionary for the parent `Schema <marshmallow.Schema>`."""
+ if self.parent:
+ return self.parent.context
+ return None
+
+ # the default and missing properties are provided for compatibility and
+ # emit warnings when they are accessed and set
+ @property
+ def default(self):
+ warnings.warn(
+ "The 'default' attribute of fields is deprecated. "
+ "Use 'dump_default' instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ return self.dump_default
+
+ @default.setter
+ def default(self, value):
+ warnings.warn(
+ "The 'default' attribute of fields is deprecated. "
+ "Use 'dump_default' instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ self.dump_default = value
+
+ @property
+ def missing(self):
+ warnings.warn(
+ "The 'missing' attribute of fields is deprecated. "
+ "Use 'load_default' instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ return self.load_default
+
+ @missing.setter
+ def missing(self, value):
+ warnings.warn(
+ "The 'missing' attribute of fields is deprecated. "
+ "Use 'load_default' instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ self.load_default = value
+
+
+class Raw(Field):
+ """Field that applies no formatting."""
+
+
+class Nested(Field):
+ """Allows you to nest a :class:`Schema <marshmallow.Schema>`
+ inside a field.
+
+ Examples: ::
+
+ class ChildSchema(Schema):
+ id = fields.Str()
+ name = fields.Str()
+ # Use lambda functions when you need two-way nesting or self-nesting
+ parent = fields.Nested(lambda: ParentSchema(only=("id",)), dump_only=True)
+ siblings = fields.List(
+ fields.Nested(lambda: ChildSchema(only=("id", "name")))
+ )
+
+
+ class ParentSchema(Schema):
+ id = fields.Str()
+ children = fields.List(
+ fields.Nested(ChildSchema(only=("id", "parent", "siblings")))
+ )
+ spouse = fields.Nested(lambda: ParentSchema(only=("id",)))
+
+ When passing a `Schema <marshmallow.Schema>` instance as the first argument,
+ the instance's ``exclude``, ``only``, and ``many`` attributes will be respected.
+
+ Therefore, when passing the ``exclude``, ``only``, or ``many`` arguments to `fields.Nested`,
+ you should pass a `Schema <marshmallow.Schema>` class (not an instance) as the first argument.
+
+ ::
+
+ # Yes
+ author = fields.Nested(UserSchema, only=("id", "name"))
+
+ # No
+ author = fields.Nested(UserSchema(), only=("id", "name"))
+
+ :param nested: `Schema <marshmallow.Schema>` instance, class, class name (string), dictionary, or callable that
+ returns a `Schema <marshmallow.Schema>` or dictionary.
+ Dictionaries are converted with `Schema.from_dict <marshmallow.Schema.from_dict>`.
+ :param exclude: A list or tuple of fields to exclude.
+ :param only: A list or tuple of fields to marshal. If `None`, all fields are marshalled.
+ This parameter takes precedence over ``exclude``.
+ :param many: Whether the field is a collection of objects.
+ :param unknown: Whether to exclude, include, or raise an error for unknown
+ fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+ """
+
+ #: Default error messages.
+ default_error_messages = {"type": "Invalid type."}
+
+ def __init__(
+ self,
+ nested: (
+ Schema
+ | SchemaMeta
+ | str
+ | dict[str, Field]
+ | typing.Callable[[], Schema | SchemaMeta | dict[str, Field]]
+ ),
+ *,
+ dump_default: typing.Any = missing_,
+ default: typing.Any = missing_,
+ only: types.StrSequenceOrSet | None = None,
+ exclude: types.StrSequenceOrSet = (),
+ many: bool = False,
+ unknown: str | None = None,
+ **kwargs,
+ ):
+ # Raise error if only or exclude is passed as string, not list of strings
+ if only is not None and not is_collection(only):
+ raise StringNotCollectionError('"only" should be a collection of strings.')
+ if not is_collection(exclude):
+ raise StringNotCollectionError(
+ '"exclude" should be a collection of strings.'
+ )
+ if nested == "self":
+ warnings.warn(
+ "Passing 'self' to `Nested` is deprecated. "
+ "Use `Nested(lambda: MySchema(...))` instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ self.nested = nested
+ self.only = only
+ self.exclude = exclude
+ self.many = many
+ self.unknown = unknown
+ self._schema: Schema | None = None # Cached Schema instance
+ super().__init__(default=default, dump_default=dump_default, **kwargs)
+
+ @property
+ def schema(self) -> Schema:
+ """The nested `Schema <marshmallow.Schema>` object.
+
+ .. versionchanged:: 1.0.0
+ Renamed from ``serializer`` to ``schema``.
+ """
+ if not self._schema:
+ # Inherit context from parent.
+ context = getattr(self.parent, "context", {})
+ if callable(self.nested) and not isinstance(self.nested, type):
+ nested = self.nested()
+ else:
+ nested = typing.cast("Schema", self.nested)
+ # defer the import of `marshmallow.schema` to avoid circular imports
+ from marshmallow.schema import Schema
+
+ if isinstance(nested, dict):
+ nested = Schema.from_dict(nested)
+
+ if isinstance(nested, Schema):
+ self._schema = copy.copy(nested)
+ self._schema.context.update(context)
+ # Respect only and exclude passed from parent and re-initialize fields
+ set_class = typing.cast(type[set], self._schema.set_class)
+ if self.only is not None:
+ if self._schema.only is not None:
+ original = self._schema.only
+ else: # only=None -> all fields
+ original = self._schema.fields.keys()
+ self._schema.only = set_class(self.only) & set_class(original)
+ if self.exclude:
+ original = self._schema.exclude
+ self._schema.exclude = set_class(self.exclude) | set_class(original)
+ self._schema._init_fields()
+ else:
+ if isinstance(nested, type) and issubclass(nested, Schema):
+ schema_class: type[Schema] = nested
+ elif not isinstance(nested, (str, bytes)):
+ raise ValueError(
+ "`Nested` fields must be passed a "
+ f"`Schema`, not {nested.__class__}."
+ )
+ elif nested == "self":
+ schema_class = typing.cast(Schema, self.root).__class__
+ else:
+ schema_class = class_registry.get_class(nested, all=False)
+ self._schema = schema_class(
+ many=self.many,
+ only=self.only,
+ exclude=self.exclude,
+ context=context,
+ load_only=self._nested_normalized_option("load_only"),
+ dump_only=self._nested_normalized_option("dump_only"),
+ )
+ return self._schema
+
+ def _nested_normalized_option(self, option_name: str) -> list[str]:
+ nested_field = f"{self.name}."
+ return [
+ field.split(nested_field, 1)[1]
+ for field in getattr(self.root, option_name, set())
+ if field.startswith(nested_field)
+ ]
+
+ def _serialize(self, nested_obj, attr, obj, **kwargs):
+ # Load up the schema first. This allows a RegistryError to be raised
+ # if an invalid schema name was passed
+ schema = self.schema
+ if nested_obj is None:
+ return None
+ many = schema.many or self.many
+ return schema.dump(nested_obj, many=many)
+
+ def _test_collection(self, value: typing.Any) -> None:
+ many = self.schema.many or self.many
+ if many and not utils.is_collection(value):
+ raise self.make_error("type", input=value, type=value.__class__.__name__)
+
+ def _load(
+ self, value: typing.Any, partial: bool | types.StrSequenceOrSet | None = None
+ ):
+ try:
+ valid_data = self.schema.load(value, unknown=self.unknown, partial=partial)
+ except ValidationError as error:
+ raise ValidationError(
+ error.messages, valid_data=error.valid_data
+ ) from error
+ return valid_data
+
+ def _deserialize(
+ self,
+ value: typing.Any,
+ attr: str | None,
+ data: typing.Mapping[str, typing.Any] | None,
+ partial: bool | types.StrSequenceOrSet | None = None,
+ **kwargs,
+ ) -> typing.Any:
+ """Same as :meth:`Field._deserialize` with additional ``partial`` argument.
+
+ :param partial: For nested schemas, the ``partial``
+ parameter passed to `marshmallow.Schema.load`.
+
+ .. versionchanged:: 3.0.0
+ Add ``partial`` parameter.
+ """
+ self._test_collection(value)
+ return self._load(value, partial=partial)
+
+
+class Pluck(Nested):
+ """Allows you to replace nested data with one of the data's fields.
+
+ Example: ::
+
+ from marshmallow import Schema, fields
+
+
+ class ArtistSchema(Schema):
+ id = fields.Int()
+ name = fields.Str()
+
+
+ class AlbumSchema(Schema):
+ artist = fields.Pluck(ArtistSchema, "id")
+
+
+ in_data = {"artist": 42}
+ loaded = AlbumSchema().load(in_data) # => {'artist': {'id': 42}}
+ dumped = AlbumSchema().dump(loaded) # => {'artist': 42}
+
+ :param nested: The Schema class or class name (string)
+ to nest, or ``"self"`` to nest the `Schema <marshmallow.Schema>` within itself.
+ :param field_name: The key to pluck a value from.
+ :param kwargs: The same keyword arguments that :class:`Nested` receives.
+ """
+
+ def __init__(
+ self,
+ nested: Schema | SchemaMeta | str | typing.Callable[[], Schema],
+ field_name: str,
+ *,
+ many: bool = False,
+ unknown: str | None = None,
+ **kwargs,
+ ):
+ super().__init__(
+ nested, only=(field_name,), many=many, unknown=unknown, **kwargs
+ )
+ self.field_name = field_name
+
+ @property
+ def _field_data_key(self) -> str:
+ only_field = self.schema.fields[self.field_name]
+ return only_field.data_key or self.field_name
+
+ def _serialize(self, nested_obj, attr, obj, **kwargs):
+ ret = super()._serialize(nested_obj, attr, obj, **kwargs)
+ if ret is None:
+ return None
+ if self.many:
+ return utils.pluck(ret, key=self._field_data_key)
+ return ret[self._field_data_key]
+
+ def _deserialize(self, value, attr, data, partial=None, **kwargs):
+ self._test_collection(value)
+ if self.many:
+ value = [{self._field_data_key: v} for v in value]
+ else:
+ value = {self._field_data_key: value}
+ return self._load(value, partial=partial)
+
+
+class List(Field):
+ """A list field, composed with another `Field` class or
+ instance.
+
+ Example: ::
+
+ numbers = fields.List(fields.Float())
+
+ :param cls_or_instance: A field class or instance.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ .. versionchanged:: 3.0.0rc9
+ Does not serialize scalar values to single-item lists.
+ """
+
+ #: Default error messages.
+ default_error_messages = {"invalid": "Not a valid list."}
+
+ def __init__(self, cls_or_instance: Field | type[Field], **kwargs):
+ super().__init__(**kwargs)
+ try:
+ self.inner = resolve_field_instance(cls_or_instance)
+ except FieldInstanceResolutionError as error:
+ raise ValueError(
+ "The list elements must be a subclass or instance of "
+ "marshmallow.base.FieldABC."
+ ) from error
+ if isinstance(self.inner, Nested):
+ self.only = self.inner.only
+ self.exclude = self.inner.exclude
+
+ def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
+ super()._bind_to_schema(field_name, schema)
+ self.inner = copy.deepcopy(self.inner)
+ self.inner._bind_to_schema(field_name, self)
+ if isinstance(self.inner, Nested):
+ self.inner.only = self.only
+ self.inner.exclude = self.exclude
+
+ def _serialize(self, value, attr, obj, **kwargs) -> list[typing.Any] | None:
+ if value is None:
+ return None
+ return [self.inner._serialize(each, attr, obj, **kwargs) for each in value]
+
+ def _deserialize(self, value, attr, data, **kwargs) -> list[typing.Any]:
+ if not utils.is_collection(value):
+ raise self.make_error("invalid")
+
+ result = []
+ errors = {}
+ for idx, each in enumerate(value):
+ try:
+ result.append(self.inner.deserialize(each, **kwargs))
+ except ValidationError as error:
+ if error.valid_data is not None:
+ result.append(error.valid_data)
+ errors.update({idx: error.messages})
+ if errors:
+ raise ValidationError(errors, valid_data=result)
+ return result
+
+
+class Tuple(Field):
+ """A tuple field, composed of a fixed number of other `Field` classes or
+ instances
+
+ Example: ::
+
+ row = Tuple((fields.String(), fields.Integer(), fields.Float()))
+
+ .. note::
+ Because of the structured nature of `collections.namedtuple` and
+ `typing.NamedTuple`, using a Schema within a Nested field for them is
+ more appropriate than using a `Tuple` field.
+
+ :param tuple_fields: An iterable of field classes or
+ instances.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ .. versionadded:: 3.0.0rc4
+ """
+
+ #: Default error messages.
+ default_error_messages = {"invalid": "Not a valid tuple."}
+
+ def __init__(
+ self,
+ tuple_fields: typing.Iterable[Field] | typing.Iterable[type[Field]],
+ **kwargs,
+ ):
+ super().__init__(**kwargs)
+ if not utils.is_collection(tuple_fields):
+ raise ValueError(
+ "tuple_fields must be an iterable of Field classes or instances."
+ )
+
+ try:
+ self.tuple_fields = [
+ resolve_field_instance(cls_or_instance)
+ for cls_or_instance in tuple_fields
+ ]
+ except FieldInstanceResolutionError as error:
+ raise ValueError(
+ 'Elements of "tuple_fields" must be subclasses or '
+ "instances of marshmallow.base.FieldABC."
+ ) from error
+
+ self.validate_length = Length(equal=len(self.tuple_fields))
+
+ def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None:
+ super()._bind_to_schema(field_name, schema)
+ new_tuple_fields = []
+ for field in self.tuple_fields:
+ new_field = copy.deepcopy(field)
+ new_field._bind_to_schema(field_name, self)
+ new_tuple_fields.append(new_field)
+
+ self.tuple_fields = new_tuple_fields
+
+ def _serialize(self, value, attr, obj, **kwargs) -> tuple | None:
+ if value is None:
+ return None
+
+ return tuple(
+ field._serialize(each, attr, obj, **kwargs)
+ for field, each in zip(self.tuple_fields, value)
+ )
+
+ def _deserialize(self, value, attr, data, **kwargs) -> tuple:
+ if not utils.is_collection(value):
+ raise self.make_error("invalid")
+
+ self.validate_length(value)
+
+ result = []
+ errors = {}
+
+ for idx, (field, each) in enumerate(zip(self.tuple_fields, value)):
+ try:
+ result.append(field.deserialize(each, **kwargs))
+ except ValidationError as error:
+ if error.valid_data is not None:
+ result.append(error.valid_data)
+ errors.update({idx: error.messages})
+ if errors:
+ raise ValidationError(errors, valid_data=result)
+
+ return tuple(result)
+
+
+class String(Field):
+ """A string field.
+
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+ """
+
+ #: Default error messages.
+ default_error_messages = {
+ "invalid": "Not a valid string.",
+ "invalid_utf8": "Not a valid utf-8 string.",
+ }
+
+ def _serialize(self, value, attr, obj, **kwargs) -> str | None:
+ if value is None:
+ return None
+ return utils.ensure_text_type(value)
+
+ def _deserialize(self, value, attr, data, **kwargs) -> typing.Any:
+ if not isinstance(value, (str, bytes)):
+ raise self.make_error("invalid")
+ try:
+ return utils.ensure_text_type(value)
+ except UnicodeDecodeError as error:
+ raise self.make_error("invalid_utf8") from error
+
+
+class UUID(String):
+ """A UUID field."""
+
+ #: Default error messages.
+ default_error_messages = {"invalid_uuid": "Not a valid UUID."}
+
+ def _validated(self, value) -> uuid.UUID | None:
+ """Format the value or raise a :exc:`ValidationError` if an error occurs."""
+ if value is None:
+ return None
+ if isinstance(value, uuid.UUID):
+ return value
+ try:
+ if isinstance(value, bytes) and len(value) == 16:
+ return uuid.UUID(bytes=value)
+ return uuid.UUID(value)
+ except (ValueError, AttributeError, TypeError) as error:
+ raise self.make_error("invalid_uuid") from error
+
+ def _deserialize(self, value, attr, data, **kwargs) -> uuid.UUID | None:
+ return self._validated(value)
+
+
+_NumType = typing.TypeVar("_NumType")
+
+
+class Number(Field, typing.Generic[_NumType]):
+ """Base class for number fields.
+
+ :param as_string: If `True`, format the serialized value as a string.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ .. versionchanged:: 3.24.0
+ `Number <marshmallow.fields.Number>` should no longer be used as a field within a `Schema <marshmallow.Schema>`.
+ Use `Integer <marshmallow.fields.Integer>`, `Float <marshmallow.fields.Float>`, or `Decimal <marshmallow.fields.Decimal>` instead.
+ """
+
+ num_type: type = float
+
+ #: Default error messages.
+ default_error_messages = {
+ "invalid": "Not a valid number.",
+ "too_large": "Number too large.",
+ }
+
+ def __init__(self, *, as_string: bool = False, **kwargs):
+ if self.__class__ is Number:
+ warnings.warn(
+ "`Number` field should not be instantiated. Use `Integer`, `Float`, or `Decimal` instead.",
+ ChangedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ self.as_string = as_string
+ super().__init__(**kwargs)
+
+ def _format_num(self, value) -> _NumType:
+ """Return the number value for value, given this field's `num_type`."""
+ return self.num_type(value)
+
+ def _validated(self, value: typing.Any) -> _NumType:
+ """Format the value or raise a :exc:`ValidationError` if an error occurs."""
+ # (value is True or value is False) is ~5x faster than isinstance(value, bool)
+ if value is True or value is False:
+ raise self.make_error("invalid", input=value)
+ try:
+ return self._format_num(value)
+ except (TypeError, ValueError) as error:
+ raise self.make_error("invalid", input=value) from error
+ except OverflowError as error:
+ raise self.make_error("too_large", input=value) from error
+
+ def _to_string(self, value: _NumType) -> str:
+ return str(value)
+
+ def _serialize(self, value, attr, obj, **kwargs) -> str | _NumType | None:
+ """Return a string if `self.as_string=True`, otherwise return this field's `num_type`."""
+ if value is None:
+ return None
+ ret: _NumType = self._format_num(value)
+ return self._to_string(ret) if self.as_string else ret
+
+ def _deserialize(self, value, attr, data, **kwargs) -> _NumType | None:
+ return self._validated(value)
+
+
+class Integer(Number[int]):
+ """An integer field.
+
+ :param strict: If `True`, only integer types are valid.
+ Otherwise, any value castable to `int` is valid.
+ :param kwargs: The same keyword arguments that :class:`Number` receives.
+ """
+
+ num_type = int
+
+ #: Default error messages.
+ default_error_messages = {"invalid": "Not a valid integer."}
+
+ def __init__(self, *, strict: bool = False, **kwargs):
+ self.strict = strict
+ super().__init__(**kwargs)
+
+ # override Number
+ def _validated(self, value: typing.Any) -> int:
+ if self.strict and not isinstance(value, numbers.Integral):
+ raise self.make_error("invalid", input=value)
+ return super()._validated(value)
+
+
+class Float(Number[float]):
+ """A double as an IEEE-754 double precision string.
+
+ :param allow_nan: If `True`, `NaN`, `Infinity` and `-Infinity` are allowed,
+ even though they are illegal according to the JSON specification.
+ :param as_string: If `True`, format the value as a string.
+ :param kwargs: The same keyword arguments that :class:`Number` receives.
+ """
+
+ num_type = float
+
+ #: Default error messages.
+ default_error_messages = {
+ "special": "Special numeric values (nan or infinity) are not permitted."
+ }
+
+ def __init__(self, *, allow_nan: bool = False, as_string: bool = False, **kwargs):
+ self.allow_nan = allow_nan
+ super().__init__(as_string=as_string, **kwargs)
+
+ def _validated(self, value: typing.Any) -> float:
+ num = super()._validated(value)
+ if self.allow_nan is False:
+ if math.isnan(num) or num == float("inf") or num == float("-inf"):
+ raise self.make_error("special")
+ return num
+
+
+class Decimal(Number[decimal.Decimal]):
+ """A field that (de)serializes to the Python ``decimal.Decimal`` type.
+ It's safe to use when dealing with money values, percentages, ratios
+ or other numbers where precision is critical.
+
+ .. warning::
+
+ This field serializes to a `decimal.Decimal` object by default. If you need
+ to render your data as JSON, keep in mind that the `json` module from the
+ standard library does not encode `decimal.Decimal`. Therefore, you must use
+ a JSON library that can handle decimals, such as `simplejson`, or serialize
+ to a string by passing ``as_string=True``.
+
+ .. warning::
+
+ If a JSON `float` value is passed to this field for deserialization it will
+ first be cast to its corresponding `string` value before being deserialized
+ to a `decimal.Decimal` object. The default `__str__` implementation of the
+ built-in Python `float` type may apply a destructive transformation upon
+ its input data and therefore cannot be relied upon to preserve precision.
+ To avoid this, you can instead pass a JSON `string` to be deserialized
+ directly.
+
+ :param places: How many decimal places to quantize the value. If `None`, does
+ not quantize the value.
+ :param rounding: How to round the value during quantize, for example
+ `decimal.ROUND_UP`. If `None`, uses the rounding value from
+ the current thread's context.
+ :param allow_nan: If `True`, `NaN`, `Infinity` and `-Infinity` are allowed,
+ even though they are illegal according to the JSON specification.
+ :param as_string: If `True`, serialize to a string instead of a Python
+ `decimal.Decimal` type.
+ :param kwargs: The same keyword arguments that :class:`Number` receives.
+
+ .. versionadded:: 1.2.0
+ """
+
+ num_type = decimal.Decimal
+
+ #: Default error messages.
+ default_error_messages = {
+ "special": "Special numeric values (nan or infinity) are not permitted."
+ }
+
+ def __init__(
+ self,
+ places: int | None = None,
+ rounding: str | None = None,
+ *,
+ allow_nan: bool = False,
+ as_string: bool = False,
+ **kwargs,
+ ):
+ self.places = (
+ decimal.Decimal((0, (1,), -places)) if places is not None else None
+ )
+ self.rounding = rounding
+ self.allow_nan = allow_nan
+ super().__init__(as_string=as_string, **kwargs)
+
+ # override Number
+ def _format_num(self, value):
+ num = decimal.Decimal(str(value))
+ if self.allow_nan:
+ if num.is_nan():
+ return decimal.Decimal("NaN") # avoid sNaN, -sNaN and -NaN
+ if self.places is not None and num.is_finite():
+ num = num.quantize(self.places, rounding=self.rounding)
+ return num
+
+ # override Number
+ def _validated(self, value: typing.Any) -> decimal.Decimal:
+ try:
+ num = super()._validated(value)
+ except decimal.InvalidOperation as error:
+ raise self.make_error("invalid") from error
+ if not self.allow_nan and (num.is_nan() or num.is_infinite()):
+ raise self.make_error("special")
+ return num
+
+ # override Number
+ def _to_string(self, value: decimal.Decimal) -> str:
+ return format(value, "f")
+
+
+class Boolean(Field):
+ """A boolean field.
+
+ :param truthy: Values that will (de)serialize to `True`. If an empty
+ set, any non-falsy value will deserialize to `True`. If `None`,
+ `marshmallow.fields.Boolean.truthy` will be used.
+ :param falsy: Values that will (de)serialize to `False`. If `None`,
+ `marshmallow.fields.Boolean.falsy` will be used.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+ """
+
+ #: Default truthy values.
+ truthy = {
+ "t",
+ "T",
+ "true",
+ "True",
+ "TRUE",
+ "on",
+ "On",
+ "ON",
+ "y",
+ "Y",
+ "yes",
+ "Yes",
+ "YES",
+ "1",
+ 1,
+ # Equal to 1
+ # True,
+ }
+ #: Default falsy values.
+ falsy = {
+ "f",
+ "F",
+ "false",
+ "False",
+ "FALSE",
+ "off",
+ "Off",
+ "OFF",
+ "n",
+ "N",
+ "no",
+ "No",
+ "NO",
+ "0",
+ 0,
+ # Equal to 0
+ # 0.0,
+ # False,
+ }
+
+ #: Default error messages.
+ default_error_messages = {"invalid": "Not a valid boolean."}
+
+ def __init__(
+ self,
+ *,
+ truthy: typing.Iterable | None = None,
+ falsy: typing.Iterable | None = None,
+ **kwargs,
+ ):
+ super().__init__(**kwargs)
+
+ if truthy is not None:
+ self.truthy = set(truthy)
+ if falsy is not None:
+ self.falsy = set(falsy)
+
+ def _serialize(
+ self, value: typing.Any, attr: str | None, obj: typing.Any, **kwargs
+ ):
+ if value is None:
+ return None
+
+ try:
+ if value in self.truthy:
+ return True
+ if value in self.falsy:
+ return False
+ except TypeError:
+ pass
+
+ return bool(value)
+
+ def _deserialize(self, value, attr, data, **kwargs):
+ if not self.truthy:
+ return bool(value)
+ try:
+ if value in self.truthy:
+ return True
+ if value in self.falsy:
+ return False
+ except TypeError as error:
+ raise self.make_error("invalid", input=value) from error
+ raise self.make_error("invalid", input=value)
+
+
+class DateTime(Field):
+ """A formatted datetime string.
+
+ Example: ``'2014-12-22T03:12:58.019077+00:00'``
+
+ :param format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601),
+ ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp) or a date format string.
+ If `None`, defaults to "iso".
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ .. versionchanged:: 3.0.0rc9
+ Does not modify timezone information on (de)serialization.
+ .. versionchanged:: 3.19
+ Add timestamp as a format.
+ """
+
+ SERIALIZATION_FUNCS: dict[str, typing.Callable[[typing.Any], str | float]] = {
+ "iso": utils.isoformat,
+ "iso8601": utils.isoformat,
+ "rfc": utils.rfcformat,
+ "rfc822": utils.rfcformat,
+ "timestamp": utils.timestamp,
+ "timestamp_ms": utils.timestamp_ms,
+ }
+
+ DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], typing.Any]] = {
+ "iso": utils.from_iso_datetime,
+ "iso8601": utils.from_iso_datetime,
+ "rfc": utils.from_rfc,
+ "rfc822": utils.from_rfc,
+ "timestamp": utils.from_timestamp,
+ "timestamp_ms": utils.from_timestamp_ms,
+ }
+
+ DEFAULT_FORMAT = "iso"
+
+ OBJ_TYPE = "datetime"
+
+ SCHEMA_OPTS_VAR_NAME = "datetimeformat"
+
+ #: Default error messages.
+ default_error_messages = {
+ "invalid": "Not a valid {obj_type}.",
+ "invalid_awareness": "Not a valid {awareness} {obj_type}.",
+ "format": '"{input}" cannot be formatted as a {obj_type}.',
+ }
+
+ def __init__(self, format: str | None = None, **kwargs) -> None: # noqa: A002
+ super().__init__(**kwargs)
+ # Allow this to be None. It may be set later in the ``_serialize``
+ # or ``_deserialize`` methods. This allows a Schema to dynamically set the
+ # format, e.g. from a Meta option
+ self.format = format
+
+ def _bind_to_schema(self, field_name, schema):
+ super()._bind_to_schema(field_name, schema)
+ self.format = (
+ self.format
+ or getattr(self.root.opts, self.SCHEMA_OPTS_VAR_NAME)
+ or self.DEFAULT_FORMAT
+ )
+
+ def _serialize(self, value, attr, obj, **kwargs) -> str | float | None:
+ if value is None:
+ return None
+ data_format = self.format or self.DEFAULT_FORMAT
+ format_func = self.SERIALIZATION_FUNCS.get(data_format)
+ if format_func:
+ return format_func(value)
+ return value.strftime(data_format)
+
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
+ data_format = self.format or self.DEFAULT_FORMAT
+ func = self.DESERIALIZATION_FUNCS.get(data_format)
+ try:
+ if func:
+ return func(value)
+ return self._make_object_from_format(value, data_format)
+ except (TypeError, AttributeError, ValueError) as error:
+ raise self.make_error(
+ "invalid", input=value, obj_type=self.OBJ_TYPE
+ ) from error
+
+ @staticmethod
+ def _make_object_from_format(value, data_format) -> dt.datetime:
+ return dt.datetime.strptime(value, data_format)
+
+
+class NaiveDateTime(DateTime):
+ """A formatted naive datetime string.
+
+ :param format: See :class:`DateTime`.
+ :param timezone: Used on deserialization. If `None`,
+ aware datetimes are rejected. If not `None`, aware datetimes are
+ converted to this timezone before their timezone information is
+ removed.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ .. versionadded:: 3.0.0rc9
+ """
+
+ AWARENESS = "naive"
+
+ def __init__(
+ self,
+ format: str | None = None, # noqa: A002
+ *,
+ timezone: dt.timezone | None = None,
+ **kwargs,
+ ) -> None:
+ super().__init__(format=format, **kwargs)
+ self.timezone = timezone
+
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
+ ret = super()._deserialize(value, attr, data, **kwargs)
+ if is_aware(ret):
+ if self.timezone is None:
+ raise self.make_error(
+ "invalid_awareness",
+ awareness=self.AWARENESS,
+ obj_type=self.OBJ_TYPE,
+ )
+ ret = ret.astimezone(self.timezone).replace(tzinfo=None)
+ return ret
+
+
+class AwareDateTime(DateTime):
+ """A formatted aware datetime string.
+
+ :param format: See :class:`DateTime`.
+ :param default_timezone: Used on deserialization. If `None`, naive
+ datetimes are rejected. If not `None`, naive datetimes are set this
+ timezone.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ .. versionadded:: 3.0.0rc9
+ """
+
+ AWARENESS = "aware"
+
+ def __init__(
+ self,
+ format: str | None = None, # noqa: A002
+ *,
+ default_timezone: dt.tzinfo | None = None,
+ **kwargs,
+ ) -> None:
+ super().__init__(format=format, **kwargs)
+ self.default_timezone = default_timezone
+
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
+ ret = super()._deserialize(value, attr, data, **kwargs)
+ if not is_aware(ret):
+ if self.default_timezone is None:
+ raise self.make_error(
+ "invalid_awareness",
+ awareness=self.AWARENESS,
+ obj_type=self.OBJ_TYPE,
+ )
+ ret = ret.replace(tzinfo=self.default_timezone)
+ return ret
+
+
+class Time(DateTime):
+ """A formatted time string.
+
+ Example: ``'03:12:58.019077'``
+
+ :param format: Either ``"iso"`` (for ISO8601) or a date format string.
+ If `None`, defaults to "iso".
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+ """
+
+ SERIALIZATION_FUNCS = {"iso": utils.to_iso_time, "iso8601": utils.to_iso_time}
+
+ DESERIALIZATION_FUNCS = {"iso": utils.from_iso_time, "iso8601": utils.from_iso_time}
+
+ DEFAULT_FORMAT = "iso"
+
+ OBJ_TYPE = "time"
+
+ SCHEMA_OPTS_VAR_NAME = "timeformat"
+
+ @staticmethod
+ def _make_object_from_format(value, data_format):
+ return dt.datetime.strptime(value, data_format).time()
+
+
+class Date(DateTime):
+ """ISO8601-formatted date string.
+
+ :param format: Either ``"iso"`` (for ISO8601) or a date format string.
+ If `None`, defaults to "iso".
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+ """
+
+ #: Default error messages.
+ default_error_messages = {
+ "invalid": "Not a valid date.",
+ "format": '"{input}" cannot be formatted as a date.',
+ }
+
+ SERIALIZATION_FUNCS = {"iso": utils.to_iso_date, "iso8601": utils.to_iso_date}
+
+ DESERIALIZATION_FUNCS = {"iso": utils.from_iso_date, "iso8601": utils.from_iso_date}
+
+ DEFAULT_FORMAT = "iso"
+
+ OBJ_TYPE = "date"
+
+ SCHEMA_OPTS_VAR_NAME = "dateformat"
+
+ @staticmethod
+ def _make_object_from_format(value, data_format):
+ return dt.datetime.strptime(value, data_format).date()
+
+
+class TimeDelta(Field):
+ """A field that (de)serializes a :class:`datetime.timedelta` object to an
+ integer or float and vice versa. The integer or float can represent the
+ number of days, seconds or microseconds.
+
+ :param precision: Influences how the integer or float is interpreted during
+ (de)serialization. Must be 'days', 'seconds', 'microseconds',
+ 'milliseconds', 'minutes', 'hours' or 'weeks'.
+ :param serialization_type: Whether to (de)serialize to a `int` or `float`.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ Integer Caveats
+ ---------------
+ Any fractional parts (which depends on the precision used) will be truncated
+ when serializing using `int`.
+
+ Float Caveats
+ -------------
+ Use of `float` when (de)serializing may result in data precision loss due
+ to the way machines handle floating point values.
+
+ Regardless of the precision chosen, the fractional part when using `float`
+ will always be truncated to microseconds.
+ For example, `1.12345` interpreted as microseconds will result in `timedelta(microseconds=1)`.
+
+ .. versionchanged:: 3.17.0
+ Allow (de)serialization to `float` through use of a new `serialization_type` parameter.
+ `int` is the default to retain previous behaviour.
+ """
+
+ DAYS = "days"
+ SECONDS = "seconds"
+ MICROSECONDS = "microseconds"
+ MILLISECONDS = "milliseconds"
+ MINUTES = "minutes"
+ HOURS = "hours"
+ WEEKS = "weeks"
+
+ #: Default error messages.
+ default_error_messages = {
+ "invalid": "Not a valid period of time.",
+ "format": "{input!r} cannot be formatted as a timedelta.",
+ }
+
+ def __init__(
+ self,
+ precision: str = SECONDS,
+ serialization_type: type[int | float] = int,
+ **kwargs,
+ ):
+ precision = precision.lower()
+ units = (
+ self.DAYS,
+ self.SECONDS,
+ self.MICROSECONDS,
+ self.MILLISECONDS,
+ self.MINUTES,
+ self.HOURS,
+ self.WEEKS,
+ )
+
+ if precision not in units:
+ msg = 'The precision must be {} or "{}".'.format(
+ ", ".join([f'"{each}"' for each in units[:-1]]), units[-1]
+ )
+ raise ValueError(msg)
+
+ if serialization_type not in (int, float):
+ raise ValueError("The serialization type must be one of int or float")
+
+ self.precision = precision
+ self.serialization_type = serialization_type
+ super().__init__(**kwargs)
+
+ def _serialize(self, value, attr, obj, **kwargs):
+ if value is None:
+ return None
+
+ base_unit = dt.timedelta(**{self.precision: 1})
+
+ if self.serialization_type is int:
+ delta = utils.timedelta_to_microseconds(value)
+ unit = utils.timedelta_to_microseconds(base_unit)
+ return delta // unit
+ assert self.serialization_type is float # noqa: S101
+ return value.total_seconds() / base_unit.total_seconds()
+
+ def _deserialize(self, value, attr, data, **kwargs):
+ try:
+ value = self.serialization_type(value)
+ except (TypeError, ValueError) as error:
+ raise self.make_error("invalid") from error
+
+ kwargs = {self.precision: value}
+
+ try:
+ return dt.timedelta(**kwargs)
+ except OverflowError as error:
+ raise self.make_error("invalid") from error
+
+
+class Mapping(Field):
+ """An abstract class for objects with key-value pairs. This class should not be used within schemas.
+
+ :param keys: A field class or instance for dict keys.
+ :param values: A field class or instance for dict values.
+ :param kwargs: The same keyword arguments that :class:`Field` receives.
+
+ .. note::
+ When the structure of nested data is not known, you may omit the
+ `keys` and `values` arguments to prevent content validation.
+
+ .. versionadded:: 3.0.0rc4
+ .. versionchanged:: 3.24.0
+ `Mapping <marshmallow.fields.Mapping>` should no longer be used as a field within a `Schema <marshmallow.Schema>`.
+ Use `Dict <marshmallow.fields.Dict>` instead.
+ """
+
+ mapping_type = dict
+
+ #: Default error messages.
+ default_error_messages = {"invalid": "Not a valid mapping type."}
+
+ def __init__(
+ self,
+ keys: Field | type[Field] | None = None,
+ values: Field | type[Field] | None = None,
+ **kwargs,
+ ):
+ if self.__class__ is Mapping:
+ warnings.warn(
+ "`Mapping` field should not be instantiated. Use `Dict` instead.",
+ ChangedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ super().__init__(**kwargs)
+ if keys is None:
+ self.key_field = None
+ else:
+ try:
+ self.key_field = resolve_field_instance(keys)
+ except FieldInstanceResolutionError as error:
+ raise ValueError(
+ '"keys" must be a subclass or instance of '
+ "marshmallow.base.FieldABC."
+ ) from error
+
+ if values is None:
+ self.value_field = None
+ else:
+ try:
+ self.value_field = resolve_field_instance(values)
+ except FieldInstanceResolutionError as error:
+ raise ValueError(
+ '"values" must be a subclass or instance of '
+ "marshmallow.base.FieldABC."
+ ) from error
+ if isinstance(self.value_field, Nested):
+ self.only = self.value_field.only
+ self.exclude = self.value_field.exclude
+
+ def _bind_to_schema(self, field_name, schema):
+ super()._bind_to_schema(field_name, schema)
+ if self.value_field:
+ self.value_field = copy.deepcopy(self.value_field)
+ self.value_field._bind_to_schema(field_name, self)
+ if isinstance(self.value_field, Nested):
+ self.value_field.only = self.only
+ self.value_field.exclude = self.exclude
+ if self.key_field:
+ self.key_field = copy.deepcopy(self.key_field)
+ self.key_field._bind_to_schema(field_name, self)
+
+ def _serialize(self, value, attr, obj, **kwargs):
+ if value is None:
+ return None
+ if not self.value_field and not self.key_field:
+ return self.mapping_type(value)
+
+ # Serialize keys
+ if self.key_field is None:
+ keys = {k: k for k in value}
+ else:
+ keys = {
+ k: self.key_field._serialize(k, None, None, **kwargs) for k in value
+ }
+
+ # Serialize values
+ result = self.mapping_type()
+ if self.value_field is None:
+ for k, v in value.items():
+ if k in keys:
+ result[keys[k]] = v
+ else:
+ for k, v in value.items():
+ result[keys[k]] = self.value_field._serialize(v, None, None, **kwargs)
+
+ return result
+
+ def _deserialize(self, value, attr, data, **kwargs):
+ if not isinstance(value, _Mapping):
+ raise self.make_error("invalid")
+ if not self.value_field and not self.key_field:
+ return self.mapping_type(value)
+
+ errors = collections.defaultdict(dict)
+
+ # Deserialize keys
+ if self.key_field is None:
+ keys = {k: k for k in value}
+ else:
+ keys = {}
+ for key in value:
+ try:
+ keys[key] = self.key_field.deserialize(key, **kwargs)
+ except ValidationError as error:
+ errors[key]["key"] = error.messages
+
+ # Deserialize values
+ result = self.mapping_type()
+ if self.value_field is None:
+ for k, v in value.items():
+ if k in keys:
+ result[keys[k]] = v
+ else:
+ for key, val in value.items():
+ try:
+ deser_val = self.value_field.deserialize(val, **kwargs)
+ except ValidationError as error:
+ errors[key]["value"] = error.messages
+ if error.valid_data is not None and key in keys:
+ result[keys[key]] = error.valid_data
+ else:
+ if key in keys:
+ result[keys[key]] = deser_val
+
+ if errors:
+ raise ValidationError(errors, valid_data=result)
+
+ return result
+
+
+class Dict(Mapping):
+ """A dict field. Supports dicts and dict-like objects. Extends
+ Mapping with dict as the mapping_type.
+
+ Example: ::
+
+ numbers = fields.Dict(keys=fields.Str(), values=fields.Float())
+
+ :param kwargs: The same keyword arguments that :class:`Mapping` receives.
+
+ .. versionadded:: 2.1.0
+ """
+
+ mapping_type = dict
+
+
+class Url(String):
+ """An URL field.
+
+ :param default: Default value for the field if the attribute is not set.
+ :param relative: Whether to allow relative URLs.
+ :param absolute: Whether to allow absolute URLs.
+ :param require_tld: Whether to reject non-FQDN hostnames.
+ :param schemes: Valid schemes. By default, ``http``, ``https``,
+ ``ftp``, and ``ftps`` are allowed.
+ :param kwargs: The same keyword arguments that :class:`String` receives.
+ """
+
+ #: Default error messages.
+ default_error_messages = {"invalid": "Not a valid URL."}
+
+ def __init__(
+ self,
+ *,
+ relative: bool = False,
+ absolute: bool = True,
+ schemes: types.StrSequenceOrSet | None = None,
+ require_tld: bool = True,
+ **kwargs,
+ ):
+ super().__init__(**kwargs)
+
+ self.relative = relative
+ self.absolute = absolute
+ self.require_tld = require_tld
+ # Insert validation into self.validators so that multiple errors can be stored.
+ validator = validate.URL(
+ relative=self.relative,
+ absolute=self.absolute,
+ schemes=schemes,
+ require_tld=self.require_tld,
+ error=self.error_messages["invalid"],
+ )
+ self.validators.insert(0, validator)
+
+
+class Email(String):
+ """An email field.
+
+ :param args: The same positional arguments that :class:`String` receives.
+ :param kwargs: The same keyword arguments that :class:`String` receives.
+ """
+
+ #: Default error messages.
+ default_error_messages = {"invalid": "Not a valid email address."}
+
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+ # Insert validation into self.validators so that multiple errors can be stored.
+ validator = validate.Email(error=self.error_messages["invalid"])
+ self.validators.insert(0, validator)
+
+
+class IP(Field):
+ """A IP address field.
+
+ :param exploded: If `True`, serialize ipv6 address in long form, ie. with groups
+ consisting entirely of zeros included.
+
+ .. versionadded:: 3.8.0
+ """
+
+ default_error_messages = {"invalid_ip": "Not a valid IP address."}
+
+ DESERIALIZATION_CLASS: type | None = None
+
+ def __init__(self, *args, exploded=False, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.exploded = exploded
+
+ def _serialize(self, value, attr, obj, **kwargs) -> str | None:
+ if value is None:
+ return None
+ if self.exploded:
+ return value.exploded
+ return value.compressed
+
+ def _deserialize(
+ self, value, attr, data, **kwargs
+ ) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
+ if value is None:
+ return None
+ try:
+ return (self.DESERIALIZATION_CLASS or ipaddress.ip_address)(
+ utils.ensure_text_type(value)
+ )
+ except (ValueError, TypeError) as error:
+ raise self.make_error("invalid_ip") from error
+
+
+class IPv4(IP):
+ """A IPv4 address field.
+
+ .. versionadded:: 3.8.0
+ """
+
+ default_error_messages = {"invalid_ip": "Not a valid IPv4 address."}
+
+ DESERIALIZATION_CLASS = ipaddress.IPv4Address
+
+
+class IPv6(IP):
+ """A IPv6 address field.
+
+ .. versionadded:: 3.8.0
+ """
+
+ default_error_messages = {"invalid_ip": "Not a valid IPv6 address."}
+
+ DESERIALIZATION_CLASS = ipaddress.IPv6Address
+
+
+class IPInterface(Field):
+ """A IPInterface field.
+
+ IP interface is the non-strict form of the IPNetwork type where arbitrary host
+ addresses are always accepted.
+
+ IPAddress and mask e.g. '192.168.0.2/24' or '192.168.0.2/255.255.255.0'
+
+ see https://python.readthedocs.io/en/latest/library/ipaddress.html#interface-objects
+
+ :param exploded: If `True`, serialize ipv6 interface in long form, ie. with groups
+ consisting entirely of zeros included.
+ """
+
+ default_error_messages = {"invalid_ip_interface": "Not a valid IP interface."}
+
+ DESERIALIZATION_CLASS: type | None = None
+
+ def __init__(self, *args, exploded: bool = False, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.exploded = exploded
+
+ def _serialize(self, value, attr, obj, **kwargs) -> str | None:
+ if value is None:
+ return None
+ if self.exploded:
+ return value.exploded
+ return value.compressed
+
+ def _deserialize(self, value, attr, data, **kwargs) -> None | (
+ ipaddress.IPv4Interface | ipaddress.IPv6Interface
+ ):
+ if value is None:
+ return None
+ try:
+ return (self.DESERIALIZATION_CLASS or ipaddress.ip_interface)(
+ utils.ensure_text_type(value)
+ )
+ except (ValueError, TypeError) as error:
+ raise self.make_error("invalid_ip_interface") from error
+
+
+class IPv4Interface(IPInterface):
+ """A IPv4 Network Interface field."""
+
+ default_error_messages = {"invalid_ip_interface": "Not a valid IPv4 interface."}
+
+ DESERIALIZATION_CLASS = ipaddress.IPv4Interface
+
+
+class IPv6Interface(IPInterface):
+ """A IPv6 Network Interface field."""
+
+ default_error_messages = {"invalid_ip_interface": "Not a valid IPv6 interface."}
+
+ DESERIALIZATION_CLASS = ipaddress.IPv6Interface
+
+
+class Enum(Field):
+ """An Enum field (de)serializing enum members by symbol (name) or by value.
+
+ :param enum: Enum class
+ :param by_value: Whether to (de)serialize by value or by name,
+ or Field class or instance to use to (de)serialize by value. Defaults to False.
+
+ If `by_value` is `False` (default), enum members are (de)serialized by symbol (name).
+ If it is `True`, they are (de)serialized by value using :class:`Raw`.
+ If it is a field instance or class, they are (de)serialized by value using this field.
+
+ .. versionadded:: 3.18.0
+ """
+
+ default_error_messages = {
+ "unknown": "Must be one of: {choices}.",
+ }
+
+ def __init__(
+ self,
+ enum: type[EnumType],
+ *,
+ by_value: bool | Field | type[Field] = False,
+ **kwargs,
+ ):
+ super().__init__(**kwargs)
+ self.enum = enum
+ self.by_value = by_value
+
+ # Serialization by name
+ if by_value is False:
+ self.field: Field = String()
+ self.choices_text = ", ".join(
+ str(self.field._serialize(m, None, None)) for m in enum.__members__
+ )
+ # Serialization by value
+ else:
+ if by_value is True:
+ self.field = Raw()
+ else:
+ try:
+ self.field = resolve_field_instance(by_value)
+ except FieldInstanceResolutionError as error:
+ raise ValueError(
+ '"by_value" must be either a bool or a subclass or instance of '
+ "marshmallow.base.FieldABC."
+ ) from error
+ self.choices_text = ", ".join(
+ str(self.field._serialize(m.value, None, None)) for m in enum
+ )
+
+ def _serialize(self, value, attr, obj, **kwargs):
+ if value is None:
+ return None
+ if self.by_value:
+ val = value.value
+ else:
+ val = value.name
+ return self.field._serialize(val, attr, obj, **kwargs)
+
+ def _deserialize(self, value, attr, data, **kwargs):
+ val = self.field._deserialize(value, attr, data, **kwargs)
+ if self.by_value:
+ try:
+ return self.enum(val)
+ except ValueError as error:
+ raise self.make_error("unknown", choices=self.choices_text) from error
+ try:
+ return getattr(self.enum, val)
+ except AttributeError as error:
+ raise self.make_error("unknown", choices=self.choices_text) from error
+
+
+class Method(Field):
+ """A field that takes the value returned by a `Schema <marshmallow.Schema>` method.
+
+ :param serialize: The name of the Schema method from which
+ to retrieve the value. The method must take an argument ``obj``
+ (in addition to self) that is the object to be serialized.
+ :param deserialize: Optional name of the Schema method for deserializing
+ a value The method must take a single argument ``value``, which is the
+ value to deserialize.
+
+ .. versionchanged:: 2.3.0
+ Deprecated ``method_name`` parameter in favor of ``serialize`` and allow
+ ``serialize`` to not be passed at all.
+
+ .. versionchanged:: 3.0.0
+ Removed ``method_name`` parameter.
+ """
+
+ _CHECK_ATTRIBUTE = False
+
+ def __init__(
+ self,
+ serialize: str | None = None,
+ deserialize: str | None = None,
+ **kwargs,
+ ):
+ # Set dump_only and load_only based on arguments
+ kwargs["dump_only"] = bool(serialize) and not bool(deserialize)
+ kwargs["load_only"] = bool(deserialize) and not bool(serialize)
+ super().__init__(**kwargs)
+ self.serialize_method_name = serialize
+ self.deserialize_method_name = deserialize
+ self._serialize_method = None
+ self._deserialize_method = None
+
+ def _bind_to_schema(self, field_name, schema):
+ if self.serialize_method_name:
+ self._serialize_method = utils.callable_or_raise(
+ getattr(schema, self.serialize_method_name)
+ )
+
+ if self.deserialize_method_name:
+ self._deserialize_method = utils.callable_or_raise(
+ getattr(schema, self.deserialize_method_name)
+ )
+
+ super()._bind_to_schema(field_name, schema)
+
+ def _serialize(self, value, attr, obj, **kwargs):
+ if self._serialize_method is not None:
+ return self._serialize_method(obj)
+ return missing_
+
+ def _deserialize(self, value, attr, data, **kwargs):
+ if self._deserialize_method is not None:
+ return self._deserialize_method(value)
+ return value
+
+
+class Function(Field):
+ """A field that takes the value returned by a function.
+
+ :param serialize: A callable from which to retrieve the value.
+ The function must take a single argument ``obj`` which is the object
+ to be serialized. It can also optionally take a ``context`` argument,
+ which is a dictionary of context variables passed to the serializer.
+ If no callable is provided then the ```load_only``` flag will be set
+ to True.
+ :param deserialize: A callable from which to retrieve the value.
+ The function must take a single argument ``value`` which is the value
+ to be deserialized. It can also optionally take a ``context`` argument,
+ which is a dictionary of context variables passed to the deserializer.
+ If no callable is provided then ```value``` will be passed through
+ unchanged.
+
+ .. versionchanged:: 2.3.0
+ Deprecated ``func`` parameter in favor of ``serialize``.
+
+ .. versionchanged:: 3.0.0a1
+ Removed ``func`` parameter.
+ """
+
+ _CHECK_ATTRIBUTE = False
+
+ def __init__(
+ self,
+ serialize: (
+ typing.Callable[[typing.Any], typing.Any]
+ | typing.Callable[[typing.Any, dict], typing.Any]
+ | None
+ ) = None,
+ deserialize: (
+ typing.Callable[[typing.Any], typing.Any]
+ | typing.Callable[[typing.Any, dict], typing.Any]
+ | None
+ ) = None,
+ **kwargs,
+ ):
+ # Set dump_only and load_only based on arguments
+ kwargs["dump_only"] = bool(serialize) and not bool(deserialize)
+ kwargs["load_only"] = bool(deserialize) and not bool(serialize)
+ super().__init__(**kwargs)
+ self.serialize_func = serialize and utils.callable_or_raise(serialize)
+ self.deserialize_func = deserialize and utils.callable_or_raise(deserialize)
+
+ def _serialize(self, value, attr, obj, **kwargs):
+ return self._call_or_raise(self.serialize_func, obj, attr)
+
+ def _deserialize(self, value, attr, data, **kwargs):
+ if self.deserialize_func:
+ return self._call_or_raise(self.deserialize_func, value, attr)
+ return value
+
+ def _call_or_raise(self, func, value, attr):
+ if len(utils.get_func_args(func)) > 1:
+ if self.parent.context is None:
+ msg = f"No context available for Function field {attr!r}"
+ raise ValidationError(msg)
+ return func(value, self.parent.context)
+ return func(value)
+
+
+class Constant(Field):
+ """A field that (de)serializes to a preset constant. If you only want the
+ constant added for serialization or deserialization, you should use
+ ``dump_only=True`` or ``load_only=True`` respectively.
+
+ :param constant: The constant to return for the field attribute.
+ """
+
+ _CHECK_ATTRIBUTE = False
+
+ def __init__(self, constant: typing.Any, **kwargs):
+ super().__init__(**kwargs)
+ self.constant = constant
+ self.load_default = constant
+ self.dump_default = constant
+
+ def _serialize(self, value, *args, **kwargs):
+ return self.constant
+
+ def _deserialize(self, value, *args, **kwargs):
+ return self.constant
+
+
+class Inferred(Field):
+ """A field that infers how to serialize, based on the value type.
+
+ .. warning::
+
+ This class is treated as private API.
+ Users should not need to use this class directly.
+ """
+
+ def __init__(self):
+ super().__init__()
+ # We memoize the fields to avoid creating and binding new fields
+ # every time on serialization.
+ self._field_cache = {}
+
+ def _serialize(self, value, attr, obj, **kwargs):
+ field_cls = self.root.TYPE_MAPPING.get(type(value))
+ if field_cls is None:
+ field = super()
+ else:
+ field = self._field_cache.get(field_cls)
+ if field is None:
+ field = field_cls()
+ field._bind_to_schema(self.name, self.parent)
+ self._field_cache[field_cls] = field
+ return field._serialize(value, attr, obj, **kwargs)
+
+
+# Aliases
+URL = Url
+Str = String
+Bool = Boolean
+Int = Integer
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/orderedset.py b/.venv/lib/python3.12/site-packages/marshmallow/orderedset.py
new file mode 100644
index 00000000..95fbc60f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/orderedset.py
@@ -0,0 +1,89 @@
+# OrderedSet
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+from collections.abc import MutableSet
+
+
+class OrderedSet(MutableSet):
+ def __init__(self, iterable=None):
+ self.end = end = []
+ end += [None, end, end] # sentinel node for doubly linked list
+ self.map = {} # key --> [key, prev, next]
+ if iterable is not None:
+ self |= iterable
+
+ def __len__(self):
+ return len(self.map)
+
+ def __contains__(self, key):
+ return key in self.map
+
+ def add(self, key):
+ if key not in self.map:
+ end = self.end
+ curr = end[1]
+ curr[2] = end[1] = self.map[key] = [key, curr, end]
+
+ def discard(self, key):
+ if key in self.map:
+ key, prev, next = self.map.pop(key) # noqa: A001
+ prev[2] = next
+ next[1] = prev
+
+ def __iter__(self):
+ end = self.end
+ curr = end[2]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[2]
+
+ def __reversed__(self):
+ end = self.end
+ curr = end[1]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[1]
+
+ def pop(self, last=True):
+ if not self:
+ raise KeyError("set is empty")
+ key = self.end[1][0] if last else self.end[2][0]
+ self.discard(key)
+ return key
+
+ def __repr__(self):
+ if not self:
+ return f"{self.__class__.__name__}()"
+ return f"{self.__class__.__name__}({list(self)!r})"
+
+ def __eq__(self, other):
+ if isinstance(other, OrderedSet):
+ return len(self) == len(other) and list(self) == list(other)
+ return set(self) == set(other)
+
+
+if __name__ == "__main__":
+ s = OrderedSet("abracadaba")
+ t = OrderedSet("simsalabim")
+ print(s | t)
+ print(s & t)
+ print(s - t)
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/py.typed b/.venv/lib/python3.12/site-packages/marshmallow/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/py.typed
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/schema.py b/.venv/lib/python3.12/site-packages/marshmallow/schema.py
new file mode 100644
index 00000000..50fb5c49
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/schema.py
@@ -0,0 +1,1309 @@
+"""The `Schema <marshmallow.Schema>` class, including its metaclass and options (`class Meta <marshmallow.Schema.Meta>`)."""
+
+from __future__ import annotations
+
+import copy
+import datetime as dt
+import decimal
+import functools
+import inspect
+import json
+import operator
+import typing
+import uuid
+import warnings
+from abc import ABCMeta
+from collections import OrderedDict, defaultdict
+from collections.abc import Mapping
+from itertools import zip_longest
+
+from marshmallow import base, class_registry, types
+from marshmallow import fields as ma_fields
+from marshmallow.decorators import (
+ POST_DUMP,
+ POST_LOAD,
+ PRE_DUMP,
+ PRE_LOAD,
+ VALIDATES,
+ VALIDATES_SCHEMA,
+)
+from marshmallow.error_store import ErrorStore
+from marshmallow.exceptions import SCHEMA, StringNotCollectionError, ValidationError
+from marshmallow.orderedset import OrderedSet
+from marshmallow.utils import (
+ EXCLUDE,
+ INCLUDE,
+ RAISE,
+ get_value,
+ is_collection,
+ is_instance_or_subclass,
+ missing,
+ set_value,
+ validate_unknown_parameter_value,
+)
+from marshmallow.warnings import RemovedInMarshmallow4Warning
+
+if typing.TYPE_CHECKING:
+ from marshmallow.fields import Field
+
+
+def _get_fields(attrs) -> list[tuple[str, Field]]:
+ """Get fields from a class
+
+ :param attrs: Mapping of class attributes
+ """
+ return [
+ (field_name, field_value)
+ for field_name, field_value in attrs.items()
+ if is_instance_or_subclass(field_value, base.FieldABC)
+ ]
+
+
+# This function allows Schemas to inherit from non-Schema classes and ensures
+# inheritance according to the MRO
+def _get_fields_by_mro(klass: SchemaMeta):
+ """Collect fields from a class, following its method resolution order. The
+ class itself is excluded from the search; only its parents are checked. Get
+ fields from ``_declared_fields`` if available, else use ``__dict__``.
+
+ :param klass: Class whose fields to retrieve
+ """
+ mro = inspect.getmro(klass)
+ # Combine fields from all parents
+ # functools.reduce(operator.iadd, list_of_lists) is faster than sum(list_of_lists, [])
+ # Loop over mro in reverse to maintain correct order of fields
+ return functools.reduce(
+ operator.iadd,
+ (
+ _get_fields(
+ getattr(base, "_declared_fields", base.__dict__),
+ )
+ for base in mro[:0:-1]
+ ),
+ [],
+ )
+
+
+class SchemaMeta(ABCMeta):
+ """Metaclass for the Schema class. Binds the declared fields to
+ a ``_declared_fields`` attribute, which is a dictionary mapping attribute
+ names to field objects. Also sets the ``opts`` class attribute, which is
+ the Schema class's `class Meta <marshmallow.Schema.Meta>` options.
+ """
+
+ Meta: type
+ opts: typing.Any
+ OPTIONS_CLASS: type
+ _declared_fields: dict[str, Field]
+
+ def __new__(
+ mcs, # noqa: N804
+ name: str,
+ bases: tuple[type, ...],
+ attrs: dict[str, typing.Any],
+ ) -> SchemaMeta:
+ meta = attrs.get("Meta")
+ ordered = getattr(meta, "ordered", False)
+ if not ordered:
+ # Inherit 'ordered' option
+ # Warning: We loop through bases instead of MRO because we don't
+ # yet have access to the class object
+ # (i.e. can't call super before we have fields)
+ for base_ in bases:
+ if hasattr(base_, "Meta") and hasattr(base_.Meta, "ordered"):
+ ordered = base_.Meta.ordered
+ break
+ else:
+ ordered = False
+ cls_fields = _get_fields(attrs)
+ # Remove fields from list of class attributes to avoid shadowing
+ # Schema attributes/methods in case of name conflict
+ for field_name, _ in cls_fields:
+ del attrs[field_name]
+ klass = super().__new__(mcs, name, bases, attrs)
+ inherited_fields = _get_fields_by_mro(klass)
+
+ meta = klass.Meta
+ # Set klass.opts in __new__ rather than __init__ so that it is accessible in
+ # get_declared_fields
+ klass.opts = klass.OPTIONS_CLASS(meta, ordered=ordered)
+ # Add fields specified in the `include` class Meta option
+ cls_fields += list(klass.opts.include.items())
+
+ # Assign _declared_fields on class
+ klass._declared_fields = mcs.get_declared_fields( # noqa: SLF001
+ klass=klass,
+ cls_fields=cls_fields,
+ inherited_fields=inherited_fields,
+ dict_cls=dict,
+ )
+ return klass
+
+ @classmethod
+ def get_declared_fields(
+ mcs, # noqa: N804
+ klass: SchemaMeta,
+ cls_fields: list[tuple[str, Field]],
+ inherited_fields: list[tuple[str, Field]],
+ dict_cls: type[dict] = dict,
+ ) -> dict[str, Field]:
+ """Returns a dictionary of field_name => `Field` pairs declared on the class.
+ This is exposed mainly so that plugins can add additional fields, e.g. fields
+ computed from `class Meta <marshmallow.Schema.Meta>` options.
+
+ :param klass: The class object.
+ :param cls_fields: The fields declared on the class, including those added
+ by the ``include`` `class Meta <marshmallow.Schema.Meta>` option.
+ :param inherited_fields: Inherited fields.
+ :param dict_cls: dict-like class to use for dict output Default to ``dict``.
+ """
+ return dict_cls(inherited_fields + cls_fields)
+
+ def __init__(cls, name, bases, attrs):
+ super().__init__(name, bases, attrs)
+ if name and cls.opts.register:
+ class_registry.register(name, cls)
+ cls._hooks = cls.resolve_hooks()
+
+ def resolve_hooks(cls) -> dict[str, list[tuple[str, bool, dict]]]:
+ """Add in the decorated processors
+
+ By doing this after constructing the class, we let standard inheritance
+ do all the hard work.
+ """
+ mro = inspect.getmro(cls)
+
+ hooks: dict[str, list[tuple[str, bool, dict]]] = defaultdict(list)
+
+ for attr_name in dir(cls):
+ # Need to look up the actual descriptor, not whatever might be
+ # bound to the class. This needs to come from the __dict__ of the
+ # declaring class.
+ for parent in mro:
+ try:
+ attr = parent.__dict__[attr_name]
+ except KeyError:
+ continue
+ else:
+ break
+ else:
+ # In case we didn't find the attribute and didn't break above.
+ # We should never hit this - it's just here for completeness
+ # to exclude the possibility of attr being undefined.
+ continue
+
+ try:
+ hook_config: dict[str, list[tuple[bool, dict]]] = (
+ attr.__marshmallow_hook__
+ )
+ except AttributeError:
+ pass
+ else:
+ for tag, config in hook_config.items():
+ # Use name here so we can get the bound method later, in
+ # case the processor was a descriptor or something.
+ hooks[tag].extend(
+ (attr_name, many, kwargs) for many, kwargs in config
+ )
+
+ return hooks
+
+
+class SchemaOpts:
+ """Defines defaults for `marshmallow.Schema.Meta`."""
+
+ def __init__(self, meta: type, ordered: bool = False): # noqa: FBT001, FBT002
+ self.fields = getattr(meta, "fields", ())
+ if not isinstance(self.fields, (list, tuple)):
+ raise ValueError("`fields` option must be a list or tuple.")
+ self.additional = getattr(meta, "additional", ())
+ if not isinstance(self.additional, (list, tuple)):
+ raise ValueError("`additional` option must be a list or tuple.")
+ if self.fields and self.additional:
+ raise ValueError(
+ "Cannot set both `fields` and `additional` options for the same Schema."
+ )
+ self.exclude = getattr(meta, "exclude", ())
+ if not isinstance(self.exclude, (list, tuple)):
+ raise ValueError("`exclude` must be a list or tuple.")
+ self.dateformat = getattr(meta, "dateformat", None)
+ self.datetimeformat = getattr(meta, "datetimeformat", None)
+ self.timeformat = getattr(meta, "timeformat", None)
+ if hasattr(meta, "json_module"):
+ warnings.warn(
+ "The json_module class Meta option is deprecated. Use render_module instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ render_module = getattr(meta, "json_module", json)
+ else:
+ render_module = json
+ self.render_module = getattr(meta, "render_module", render_module)
+ if hasattr(meta, "ordered"):
+ warnings.warn(
+ "The `ordered` `class Meta` option is deprecated. "
+ "Field order is already preserved by default. "
+ "Set `Schema.dict_class` to OrderedDict to maintain the previous behavior.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ self.ordered = getattr(meta, "ordered", ordered)
+ self.index_errors = getattr(meta, "index_errors", True)
+ self.include = getattr(meta, "include", {})
+ self.load_only = getattr(meta, "load_only", ())
+ self.dump_only = getattr(meta, "dump_only", ())
+ self.unknown = validate_unknown_parameter_value(getattr(meta, "unknown", RAISE))
+ self.register = getattr(meta, "register", True)
+ self.many = getattr(meta, "many", False)
+
+
+class Schema(base.SchemaABC, metaclass=SchemaMeta):
+ """Base schema class with which to define schemas.
+
+ Example usage:
+
+ .. code-block:: python
+
+ import datetime as dt
+ from dataclasses import dataclass
+
+ from marshmallow import Schema, fields
+
+
+ @dataclass
+ class Album:
+ title: str
+ release_date: dt.date
+
+
+ class AlbumSchema(Schema):
+ title = fields.Str()
+ release_date = fields.Date()
+
+
+ album = Album("Beggars Banquet", dt.date(1968, 12, 6))
+ schema = AlbumSchema()
+ data = schema.dump(album)
+ data # {'release_date': '1968-12-06', 'title': 'Beggars Banquet'}
+
+ :param only: Whitelist of the declared fields to select when
+ instantiating the Schema. If None, all fields are used. Nested fields
+ can be represented with dot delimiters.
+ :param exclude: Blacklist of the declared fields to exclude
+ when instantiating the Schema. If a field appears in both `only` and
+ `exclude`, it is not used. Nested fields can be represented with dot
+ delimiters.
+ :param many: Should be set to `True` if ``obj`` is a collection
+ so that the object will be serialized to a list.
+ :param context: Optional context passed to :class:`fields.Method` and
+ :class:`fields.Function` fields.
+ :param load_only: Fields to skip during serialization (write-only fields)
+ :param dump_only: Fields to skip during deserialization (read-only fields)
+ :param partial: Whether to ignore missing fields and not require
+ any fields declared. Propagates down to ``Nested`` fields as well. If
+ its value is an iterable, only missing fields listed in that iterable
+ will be ignored. Use dot delimiters to specify nested fields.
+ :param unknown: Whether to exclude, include, or raise an error for unknown
+ fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
+
+ .. versionchanged:: 3.0.0
+ `prefix` parameter removed.
+ """
+
+ TYPE_MAPPING: dict[type, type[Field]] = {
+ str: ma_fields.String,
+ bytes: ma_fields.String,
+ dt.datetime: ma_fields.DateTime,
+ float: ma_fields.Float,
+ bool: ma_fields.Boolean,
+ tuple: ma_fields.Raw,
+ list: ma_fields.Raw,
+ set: ma_fields.Raw,
+ int: ma_fields.Integer,
+ uuid.UUID: ma_fields.UUID,
+ dt.time: ma_fields.Time,
+ dt.date: ma_fields.Date,
+ dt.timedelta: ma_fields.TimeDelta,
+ decimal.Decimal: ma_fields.Decimal,
+ }
+ #: Overrides for default schema-level error messages
+ error_messages: dict[str, str] = {}
+
+ _default_error_messages: dict[str, str] = {
+ "type": "Invalid input type.",
+ "unknown": "Unknown field.",
+ }
+
+ OPTIONS_CLASS: type = SchemaOpts
+
+ set_class = OrderedSet
+
+ # These get set by SchemaMeta
+ opts: typing.Any
+ _declared_fields: dict[str, Field] = {}
+ _hooks: dict[str, list[tuple[str, bool, dict]]] = {}
+
+ class Meta:
+ """Options object for a Schema.
+
+ Example usage: ::
+
+ from marshmallow import Schema
+
+
+ class MySchema(Schema):
+ class Meta:
+ fields = ("id", "email", "date_created")
+ exclude = ("password", "secret_attribute")
+
+ .. admonition:: A note on type checking
+
+ Type checkers will only check the attributes of the `Meta <marshmallow.Schema.Meta>`
+ class if you explicitly subclass `marshmallow.Schema.Meta`.
+
+ .. code-block:: python
+
+ from marshmallow import Schema
+
+
+ class MySchema(Schema):
+ # Not checked by type checkers
+ class Meta:
+ additional = True
+
+
+ class MySchema2(Schema):
+ # Type checkers will check attributes
+ class Meta(Schema.Opts):
+ additional = True # Incompatible types in assignment
+
+ .. versionremoved:: 3.0.0b7 Remove ``strict``.
+ .. versionadded:: 3.0.0b12 Add `unknown`.
+ .. versionchanged:: 3.0.0b17 Rename ``dateformat`` to `datetimeformat`.
+ .. versionadded:: 3.9.0 Add `timeformat`.
+ .. versionchanged:: 3.26.0 Deprecate `ordered`. Field order is preserved by default.
+ """
+
+ fields: typing.ClassVar[tuple[str, ...] | list[str]]
+ """Fields to include in the (de)serialized result"""
+ additional: typing.ClassVar[tuple[str, ...] | list[str]]
+ """Fields to include in addition to the explicitly declared fields.
+ `additional <marshmallow.Schema.Meta.additional>` and `fields <marshmallow.Schema.Meta.fields>`
+ are mutually-exclusive options.
+ """
+ include: typing.ClassVar[dict[str, Field]]
+ """Dictionary of additional fields to include in the schema. It is
+ usually better to define fields as class variables, but you may need to
+ use this option, e.g., if your fields are Python keywords.
+ """
+ exclude: typing.ClassVar[tuple[str, ...] | list[str]]
+ """Fields to exclude in the serialized result.
+ Nested fields can be represented with dot delimiters.
+ """
+ many: typing.ClassVar[bool]
+ """Whether data should be (de)serialized as a collection by default."""
+ dateformat: typing.ClassVar[str]
+ """Default format for `Date <marshmallow.fields.Date>` fields."""
+ datetimeformat: typing.ClassVar[str]
+ """Default format for `DateTime <marshmallow.fields.DateTime>` fields."""
+ timeformat: typing.ClassVar[str]
+ """Default format for `Time <marshmallow.fields.Time>` fields."""
+
+ # FIXME: Use a more constrained type here.
+ # ClassVar[RenderModule] doesn't work.
+ render_module: typing.Any
+ """ Module to use for `loads <marshmallow.Schema.loads>` and `dumps <marshmallow.Schema.dumps>`.
+ Defaults to `json` from the standard library.
+ """
+ ordered: typing.ClassVar[bool]
+ """If `True`, `Schema.dump <marshmallow.Schema.dump>` is a `collections.OrderedDict`."""
+ index_errors: typing.ClassVar[bool]
+ """If `True`, errors dictionaries will include the index of invalid items in a collection."""
+ load_only: typing.ClassVar[tuple[str, ...] | list[str]]
+ """Fields to exclude from serialized results"""
+ dump_only: typing.ClassVar[tuple[str, ...] | list[str]]
+ """Fields to exclude from serialized results"""
+ unknown: typing.ClassVar[str]
+ """Whether to exclude, include, or raise an error for unknown fields in the data.
+ Use `EXCLUDE`, `INCLUDE` or `RAISE`.
+ """
+ register: typing.ClassVar[bool]
+ """Whether to register the `Schema <marshmallow.Schema>` with marshmallow's internal
+ class registry. Must be `True` if you intend to refer to this `Schema <marshmallow.Schema>`
+ by class name in `Nested` fields. Only set this to `False` when memory
+ usage is critical. Defaults to `True`.
+ """
+
+ def __init__(
+ self,
+ *,
+ only: types.StrSequenceOrSet | None = None,
+ exclude: types.StrSequenceOrSet = (),
+ many: bool | None = None,
+ context: dict | None = None,
+ load_only: types.StrSequenceOrSet = (),
+ dump_only: types.StrSequenceOrSet = (),
+ partial: bool | types.StrSequenceOrSet | None = None,
+ unknown: str | None = None,
+ ):
+ # Raise error if only or exclude is passed as string, not list of strings
+ if only is not None and not is_collection(only):
+ raise StringNotCollectionError('"only" should be a list of strings')
+ if not is_collection(exclude):
+ raise StringNotCollectionError('"exclude" should be a list of strings')
+ # copy declared fields from metaclass
+ self.declared_fields = copy.deepcopy(self._declared_fields)
+ self.many = self.opts.many if many is None else many
+ self.only = only
+ self.exclude: set[typing.Any] | typing.MutableSet[typing.Any] = set(
+ self.opts.exclude
+ ) | set(exclude)
+ self.ordered = self.opts.ordered
+ self.load_only = set(load_only) or set(self.opts.load_only)
+ self.dump_only = set(dump_only) or set(self.opts.dump_only)
+ self.partial = partial
+ self.unknown = (
+ self.opts.unknown
+ if unknown is None
+ else validate_unknown_parameter_value(unknown)
+ )
+ if context:
+ warnings.warn(
+ "The `context` parameter is deprecated and will be removed in marshmallow 4.0. "
+ "Use `contextvars.ContextVar` to pass context instead.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ self.context = context or {}
+ self._normalize_nested_options()
+ #: Dictionary mapping field_names -> :class:`Field` objects
+ self.fields: dict[str, Field] = {}
+ self.load_fields: dict[str, Field] = {}
+ self.dump_fields: dict[str, Field] = {}
+ self._init_fields()
+ messages = {}
+ messages.update(self._default_error_messages)
+ for cls in reversed(self.__class__.__mro__):
+ messages.update(getattr(cls, "error_messages", {}))
+ messages.update(self.error_messages or {})
+ self.error_messages = messages
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__}(many={self.many})>"
+
+ @property
+ def dict_class(self) -> type[dict]:
+ """`dict` type to return when serializing."""
+ if self.ordered:
+ return OrderedDict
+ return dict
+
+ @classmethod
+ def from_dict(
+ cls,
+ fields: dict[str, Field],
+ *,
+ name: str = "GeneratedSchema",
+ ) -> type[Schema]:
+ """Generate a `Schema <marshmallow.Schema>` class given a dictionary of fields.
+
+ .. code-block:: python
+
+ from marshmallow import Schema, fields
+
+ PersonSchema = Schema.from_dict({"name": fields.Str()})
+ print(PersonSchema().load({"name": "David"})) # => {'name': 'David'}
+
+ Generated schemas are not added to the class registry and therefore cannot
+ be referred to by name in `Nested` fields.
+
+
+ :param fields: Dictionary mapping field names to field instances.
+ :param name: Optional name for the class, which will appear in
+ the ``repr`` for the class.
+
+ .. versionadded:: 3.0.0
+ """
+ Meta = type(
+ "GeneratedMeta", (getattr(cls, "Meta", object),), {"register": False}
+ )
+ return type(name, (cls,), {**fields.copy(), "Meta": Meta})
+
+ ##### Override-able methods #####
+
+ def handle_error(
+ self, error: ValidationError, data: typing.Any, *, many: bool, **kwargs
+ ):
+ """Custom error handler function for the schema.
+
+ :param error: The `ValidationError` raised during (de)serialization.
+ :param data: The original input data.
+ :param many: Value of ``many`` on dump or load.
+ :param partial: Value of ``partial`` on load.
+
+ .. versionchanged:: 3.0.0rc9
+ Receives `many` and `partial` (on deserialization) as keyword arguments.
+ """
+
+ def get_attribute(self, obj: typing.Any, attr: str, default: typing.Any):
+ """Defines how to pull values from an object to serialize.
+
+ .. versionchanged:: 3.0.0a1
+ Changed position of ``obj`` and ``attr``.
+ """
+ return get_value(obj, attr, default)
+
+ ##### Serialization/Deserialization API #####
+
+ @staticmethod
+ def _call_and_store(getter_func, data, *, field_name, error_store, index=None):
+ """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`.
+
+ :param getter_func: Function for getting the serialized/deserialized
+ value from ``data``.
+ :param data: The data passed to ``getter_func``.
+ :param field_name: Field name.
+ :param index: Index of the item being validated, if validating a collection,
+ otherwise `None`.
+ """
+ try:
+ value = getter_func(data)
+ except ValidationError as error:
+ error_store.store_error(error.messages, field_name, index=index)
+ # When a Nested field fails validation, the marshalled data is stored
+ # on the ValidationError's valid_data attribute
+ return error.valid_data or missing
+ return value
+
+ def _serialize(self, obj: typing.Any, *, many: bool = False):
+ """Serialize ``obj``.
+
+ :param obj: The object(s) to serialize.
+ :param many: `True` if ``data`` should be serialized as a collection.
+ :return: A dictionary of the serialized data
+ """
+ if many and obj is not None:
+ return [self._serialize(d, many=False) for d in obj]
+ ret = self.dict_class()
+ for attr_name, field_obj in self.dump_fields.items():
+ value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
+ if value is missing:
+ continue
+ key = field_obj.data_key if field_obj.data_key is not None else attr_name
+ ret[key] = value
+ return ret
+
+ def dump(self, obj: typing.Any, *, many: bool | None = None):
+ """Serialize an object to native Python data types according to this
+ Schema's fields.
+
+ :param obj: The object to serialize.
+ :param many: Whether to serialize `obj` as a collection. If `None`, the value
+ for `self.many` is used.
+ :return: Serialized data
+
+ .. versionadded:: 1.0.0
+ .. versionchanged:: 3.0.0b7
+ This method returns the serialized data rather than a ``(data, errors)`` duple.
+ A :exc:`ValidationError <marshmallow.exceptions.ValidationError>` is raised
+ if ``obj`` is invalid.
+ .. versionchanged:: 3.0.0rc9
+ Validation no longer occurs upon serialization.
+ """
+ many = self.many if many is None else bool(many)
+ if self._hooks[PRE_DUMP]:
+ processed_obj = self._invoke_dump_processors(
+ PRE_DUMP, obj, many=many, original_data=obj
+ )
+ else:
+ processed_obj = obj
+
+ result = self._serialize(processed_obj, many=many)
+
+ if self._hooks[POST_DUMP]:
+ result = self._invoke_dump_processors(
+ POST_DUMP, result, many=many, original_data=obj
+ )
+
+ return result
+
+ def dumps(self, obj: typing.Any, *args, many: bool | None = None, **kwargs):
+ """Same as :meth:`dump`, except return a JSON-encoded string.
+
+ :param obj: The object to serialize.
+ :param many: Whether to serialize `obj` as a collection. If `None`, the value
+ for `self.many` is used.
+ :return: A ``json`` string
+
+ .. versionadded:: 1.0.0
+ .. versionchanged:: 3.0.0b7
+ This method returns the serialized data rather than a ``(data, errors)`` duple.
+ A :exc:`ValidationError <marshmallow.exceptions.ValidationError>` is raised
+ if ``obj`` is invalid.
+ """
+ serialized = self.dump(obj, many=many)
+ return self.opts.render_module.dumps(serialized, *args, **kwargs)
+
+ def _deserialize(
+ self,
+ data: (
+ typing.Mapping[str, typing.Any]
+ | typing.Iterable[typing.Mapping[str, typing.Any]]
+ ),
+ *,
+ error_store: ErrorStore,
+ many: bool = False,
+ partial=None,
+ unknown=RAISE,
+ index=None,
+ ) -> typing.Any | list[typing.Any]:
+ """Deserialize ``data``.
+
+ :param data: The data to deserialize.
+ :param error_store: Structure to store errors.
+ :param many: `True` if ``data`` should be deserialized as a collection.
+ :param partial: Whether to ignore missing fields and not require
+ any fields declared. Propagates down to ``Nested`` fields as well. If
+ its value is an iterable, only missing fields listed in that iterable
+ will be ignored. Use dot delimiters to specify nested fields.
+ :param unknown: Whether to exclude, include, or raise an error for unknown
+ fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
+ :param index: Index of the item being serialized (for storing errors) if
+ serializing a collection, otherwise `None`.
+ :return: The deserialized data as `dict_class` instance or list of `dict_class`
+ instances if `many` is `True`.
+ """
+ index_errors = self.opts.index_errors
+ index = index if index_errors else None
+ if many:
+ if not is_collection(data):
+ error_store.store_error([self.error_messages["type"]], index=index)
+ ret_l = []
+ else:
+ ret_l = [
+ self._deserialize(
+ typing.cast(dict, d),
+ error_store=error_store,
+ many=False,
+ partial=partial,
+ unknown=unknown,
+ index=idx,
+ )
+ for idx, d in enumerate(data)
+ ]
+ return ret_l
+ ret_d = self.dict_class()
+ # Check data is a dict
+ if not isinstance(data, Mapping):
+ error_store.store_error([self.error_messages["type"]], index=index)
+ else:
+ partial_is_collection = is_collection(partial)
+ for attr_name, field_obj in self.load_fields.items():
+ field_name = (
+ field_obj.data_key if field_obj.data_key is not None else attr_name
+ )
+ raw_value = data.get(field_name, missing)
+ if raw_value is missing:
+ # Ignore missing field if we're allowed to.
+ if partial is True or (
+ partial_is_collection and attr_name in partial
+ ):
+ continue
+ d_kwargs = {}
+ # Allow partial loading of nested schemas.
+ if partial_is_collection:
+ prefix = field_name + "."
+ len_prefix = len(prefix)
+ sub_partial = [
+ f[len_prefix:] for f in partial if f.startswith(prefix)
+ ]
+ d_kwargs["partial"] = sub_partial
+ elif partial is not None:
+ d_kwargs["partial"] = partial
+
+ def getter(
+ val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs
+ ):
+ return field_obj.deserialize(
+ val,
+ field_name,
+ data,
+ **d_kwargs,
+ )
+
+ value = self._call_and_store(
+ getter_func=getter,
+ data=raw_value,
+ field_name=field_name,
+ error_store=error_store,
+ index=index,
+ )
+ if value is not missing:
+ key = field_obj.attribute or attr_name
+ set_value(ret_d, key, value)
+ if unknown != EXCLUDE:
+ fields = {
+ field_obj.data_key if field_obj.data_key is not None else field_name
+ for field_name, field_obj in self.load_fields.items()
+ }
+ for key in set(data) - fields:
+ value = data[key]
+ if unknown == INCLUDE:
+ ret_d[key] = value
+ elif unknown == RAISE:
+ error_store.store_error(
+ [self.error_messages["unknown"]],
+ key,
+ (index if index_errors else None),
+ )
+ return ret_d
+
+ def load(
+ self,
+ data: (
+ typing.Mapping[str, typing.Any]
+ | typing.Iterable[typing.Mapping[str, typing.Any]]
+ ),
+ *,
+ many: bool | None = None,
+ partial: bool | types.StrSequenceOrSet | None = None,
+ unknown: str | None = None,
+ ):
+ """Deserialize a data structure to an object defined by this Schema's fields.
+
+ :param data: The data to deserialize.
+ :param many: Whether to deserialize `data` as a collection. If `None`, the
+ value for `self.many` is used.
+ :param partial: Whether to ignore missing fields and not require
+ any fields declared. Propagates down to ``Nested`` fields as well. If
+ its value is an iterable, only missing fields listed in that iterable
+ will be ignored. Use dot delimiters to specify nested fields.
+ :param unknown: Whether to exclude, include, or raise an error for unknown
+ fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
+ If `None`, the value for `self.unknown` is used.
+ :return: Deserialized data
+
+ .. versionadded:: 1.0.0
+ .. versionchanged:: 3.0.0b7
+ This method returns the deserialized data rather than a ``(data, errors)`` duple.
+ A :exc:`ValidationError <marshmallow.exceptions.ValidationError>` is raised
+ if invalid data are passed.
+ """
+ return self._do_load(
+ data, many=many, partial=partial, unknown=unknown, postprocess=True
+ )
+
+ def loads(
+ self,
+ json_data: str | bytes | bytearray,
+ *,
+ many: bool | None = None,
+ partial: bool | types.StrSequenceOrSet | None = None,
+ unknown: str | None = None,
+ **kwargs,
+ ):
+ """Same as :meth:`load`, except it uses `marshmallow.Schema.Meta.render_module` to deserialize
+ the passed string before passing data to :meth:`load`.
+
+ :param json_data: A string of the data to deserialize.
+ :param many: Whether to deserialize `obj` as a collection. If `None`, the
+ value for `self.many` is used.
+ :param partial: Whether to ignore missing fields and not require
+ any fields declared. Propagates down to ``Nested`` fields as well. If
+ its value is an iterable, only missing fields listed in that iterable
+ will be ignored. Use dot delimiters to specify nested fields.
+ :param unknown: Whether to exclude, include, or raise an error for unknown
+ fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
+ If `None`, the value for `self.unknown` is used.
+ :return: Deserialized data
+
+ .. versionadded:: 1.0.0
+ .. versionchanged:: 3.0.0b7
+ This method returns the deserialized data rather than a ``(data, errors)`` duple.
+ A :exc:`ValidationError <marshmallow.exceptions.ValidationError>` is raised
+ if invalid data are passed.
+ """
+ data = self.opts.render_module.loads(json_data, **kwargs)
+ return self.load(data, many=many, partial=partial, unknown=unknown)
+
+ def _run_validator(
+ self,
+ validator_func: types.SchemaValidator,
+ output,
+ *,
+ original_data,
+ error_store: ErrorStore,
+ many: bool,
+ partial: bool | types.StrSequenceOrSet | None,
+ pass_original: bool,
+ index: int | None = None,
+ ):
+ try:
+ if pass_original: # Pass original, raw data (before unmarshalling)
+ validator_func(output, original_data, partial=partial, many=many)
+ else:
+ validator_func(output, partial=partial, many=many)
+ except ValidationError as err:
+ field_name = err.field_name
+ data_key: str
+ if field_name == SCHEMA:
+ data_key = SCHEMA
+ else:
+ field_obj: Field | None = None
+ try:
+ field_obj = self.fields[field_name]
+ except KeyError:
+ if field_name in self.declared_fields:
+ field_obj = self.declared_fields[field_name]
+ if field_obj:
+ data_key = (
+ field_obj.data_key
+ if field_obj.data_key is not None
+ else field_name
+ )
+ else:
+ data_key = field_name
+ error_store.store_error(err.messages, data_key, index=index)
+
+ def validate(
+ self,
+ data: (
+ typing.Mapping[str, typing.Any]
+ | typing.Iterable[typing.Mapping[str, typing.Any]]
+ ),
+ *,
+ many: bool | None = None,
+ partial: bool | types.StrSequenceOrSet | None = None,
+ ) -> dict[str, list[str]]:
+ """Validate `data` against the schema, returning a dictionary of
+ validation errors.
+
+ :param data: The data to validate.
+ :param many: Whether to validate `data` as a collection. If `None`, the
+ value for `self.many` is used.
+ :param partial: Whether to ignore missing fields and not require
+ any fields declared. Propagates down to ``Nested`` fields as well. If
+ its value is an iterable, only missing fields listed in that iterable
+ will be ignored. Use dot delimiters to specify nested fields.
+ :return: A dictionary of validation errors.
+
+ .. versionadded:: 1.1.0
+ """
+ try:
+ self._do_load(data, many=many, partial=partial, postprocess=False)
+ except ValidationError as exc:
+ return typing.cast(dict[str, list[str]], exc.messages)
+ return {}
+
+ ##### Private Helpers #####
+
+ def _do_load(
+ self,
+ data: (
+ typing.Mapping[str, typing.Any]
+ | typing.Iterable[typing.Mapping[str, typing.Any]]
+ ),
+ *,
+ many: bool | None = None,
+ partial: bool | types.StrSequenceOrSet | None = None,
+ unknown: str | None = None,
+ postprocess: bool = True,
+ ):
+ """Deserialize `data`, returning the deserialized result.
+ This method is private API.
+
+ :param data: The data to deserialize.
+ :param many: Whether to deserialize `data` as a collection. If `None`, the
+ value for `self.many` is used.
+ :param partial: Whether to validate required fields. If its
+ value is an iterable, only fields listed in that iterable will be
+ ignored will be allowed missing. If `True`, all fields will be allowed missing.
+ If `None`, the value for `self.partial` is used.
+ :param unknown: Whether to exclude, include, or raise an error for unknown
+ fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
+ If `None`, the value for `self.unknown` is used.
+ :param postprocess: Whether to run post_load methods..
+ :return: Deserialized data
+ """
+ error_store = ErrorStore()
+ errors: dict[str, list[str]] = {}
+ many = self.many if many is None else bool(many)
+ unknown = (
+ self.unknown
+ if unknown is None
+ else validate_unknown_parameter_value(unknown)
+ )
+ if partial is None:
+ partial = self.partial
+ # Run preprocessors
+ if self._hooks[PRE_LOAD]:
+ try:
+ processed_data = self._invoke_load_processors(
+ PRE_LOAD, data, many=many, original_data=data, partial=partial
+ )
+ except ValidationError as err:
+ errors = err.normalized_messages()
+ result: list | dict | None = None
+ else:
+ processed_data = data
+ if not errors:
+ # Deserialize data
+ result = self._deserialize(
+ processed_data,
+ error_store=error_store,
+ many=many,
+ partial=partial,
+ unknown=unknown,
+ )
+ # Run field-level validation
+ self._invoke_field_validators(
+ error_store=error_store, data=result, many=many
+ )
+ # Run schema-level validation
+ if self._hooks[VALIDATES_SCHEMA]:
+ field_errors = bool(error_store.errors)
+ self._invoke_schema_validators(
+ error_store=error_store,
+ pass_many=True,
+ data=result,
+ original_data=data,
+ many=many,
+ partial=partial,
+ field_errors=field_errors,
+ )
+ self._invoke_schema_validators(
+ error_store=error_store,
+ pass_many=False,
+ data=result,
+ original_data=data,
+ many=many,
+ partial=partial,
+ field_errors=field_errors,
+ )
+ errors = error_store.errors
+ # Run post processors
+ if not errors and postprocess and self._hooks[POST_LOAD]:
+ try:
+ result = self._invoke_load_processors(
+ POST_LOAD,
+ result,
+ many=many,
+ original_data=data,
+ partial=partial,
+ )
+ except ValidationError as err:
+ errors = err.normalized_messages()
+ if errors:
+ exc = ValidationError(errors, data=data, valid_data=result)
+ self.handle_error(exc, data, many=many, partial=partial)
+ raise exc
+
+ return result
+
+ def _normalize_nested_options(self) -> None:
+ """Apply then flatten nested schema options.
+ This method is private API.
+ """
+ if self.only is not None:
+ # Apply the only option to nested fields.
+ self.__apply_nested_option("only", self.only, "intersection")
+ # Remove the child field names from the only option.
+ self.only = self.set_class([field.split(".", 1)[0] for field in self.only])
+ if self.exclude:
+ # Apply the exclude option to nested fields.
+ self.__apply_nested_option("exclude", self.exclude, "union")
+ # Remove the parent field names from the exclude option.
+ self.exclude = self.set_class(
+ [field for field in self.exclude if "." not in field]
+ )
+
+ def __apply_nested_option(self, option_name, field_names, set_operation) -> None:
+ """Apply nested options to nested fields"""
+ # Split nested field names on the first dot.
+ nested_fields = [name.split(".", 1) for name in field_names if "." in name]
+ # Partition the nested field names by parent field.
+ nested_options = defaultdict(list) # type: defaultdict
+ for parent, nested_names in nested_fields:
+ nested_options[parent].append(nested_names)
+ # Apply the nested field options.
+ for key, options in iter(nested_options.items()):
+ new_options = self.set_class(options)
+ original_options = getattr(self.declared_fields[key], option_name, ())
+ if original_options:
+ if set_operation == "union":
+ new_options |= self.set_class(original_options)
+ if set_operation == "intersection":
+ new_options &= self.set_class(original_options)
+ setattr(self.declared_fields[key], option_name, new_options)
+
+ def _init_fields(self) -> None:
+ """Update self.fields, self.load_fields, and self.dump_fields based on schema options.
+ This method is private API.
+ """
+ if self.opts.fields:
+ available_field_names = self.set_class(self.opts.fields)
+ else:
+ available_field_names = self.set_class(self.declared_fields.keys())
+ if self.opts.additional:
+ available_field_names |= self.set_class(self.opts.additional)
+
+ invalid_fields = self.set_class()
+
+ if self.only is not None:
+ # Return only fields specified in only option
+ field_names: typing.AbstractSet[typing.Any] = self.set_class(self.only)
+
+ invalid_fields |= field_names - available_field_names
+ else:
+ field_names = available_field_names
+
+ # If "exclude" option or param is specified, remove those fields.
+ if self.exclude:
+ # Note that this isn't available_field_names, since we want to
+ # apply "only" for the actual calculation.
+ field_names = field_names - self.exclude
+ invalid_fields |= self.exclude - available_field_names
+
+ if invalid_fields:
+ message = f"Invalid fields for {self}: {invalid_fields}."
+ raise ValueError(message)
+
+ fields_dict = self.dict_class()
+ for field_name in field_names:
+ field_obj = self.declared_fields.get(field_name, ma_fields.Inferred())
+ self._bind_field(field_name, field_obj)
+ fields_dict[field_name] = field_obj
+
+ load_fields, dump_fields = self.dict_class(), self.dict_class()
+ for field_name, field_obj in fields_dict.items():
+ if not field_obj.dump_only:
+ load_fields[field_name] = field_obj
+ if not field_obj.load_only:
+ dump_fields[field_name] = field_obj
+
+ dump_data_keys = [
+ field_obj.data_key if field_obj.data_key is not None else name
+ for name, field_obj in dump_fields.items()
+ ]
+ if len(dump_data_keys) != len(set(dump_data_keys)):
+ data_keys_duplicates = {
+ x for x in dump_data_keys if dump_data_keys.count(x) > 1
+ }
+ raise ValueError(
+ "The data_key argument for one or more fields collides "
+ "with another field's name or data_key argument. "
+ "Check the following field names and "
+ f"data_key arguments: {list(data_keys_duplicates)}"
+ )
+ load_attributes = [obj.attribute or name for name, obj in load_fields.items()]
+ if len(load_attributes) != len(set(load_attributes)):
+ attributes_duplicates = {
+ x for x in load_attributes if load_attributes.count(x) > 1
+ }
+ raise ValueError(
+ "The attribute argument for one or more fields collides "
+ "with another field's name or attribute argument. "
+ "Check the following field names and "
+ f"attribute arguments: {list(attributes_duplicates)}"
+ )
+
+ self.fields = fields_dict
+ self.dump_fields = dump_fields
+ self.load_fields = load_fields
+
+ def on_bind_field(self, field_name: str, field_obj: Field) -> None:
+ """Hook to modify a field when it is bound to the `Schema <marshmallow.Schema>`.
+
+ No-op by default.
+ """
+ return
+
+ def _bind_field(self, field_name: str, field_obj: Field) -> None:
+ """Bind field to the schema, setting any necessary attributes on the
+ field (e.g. parent and name).
+
+ Also set field load_only and dump_only values if field_name was
+ specified in `class Meta <marshmallow.Schema.Meta>`.
+ """
+ if field_name in self.load_only:
+ field_obj.load_only = True
+ if field_name in self.dump_only:
+ field_obj.dump_only = True
+ try:
+ field_obj._bind_to_schema(field_name, self) # noqa: SLF001
+ except TypeError as error:
+ # Field declared as a class, not an instance. Ignore type checking because
+ # we handle unsupported arg types, i.e. this is dead code from
+ # the type checker's perspective.
+ if isinstance(field_obj, type) and issubclass(field_obj, base.FieldABC):
+ msg = (
+ f'Field for "{field_name}" must be declared as a '
+ "Field instance, not a class. "
+ f'Did you mean "fields.{field_obj.__name__}()"?' # type: ignore[attr-defined]
+ )
+ raise TypeError(msg) from error
+ raise
+ self.on_bind_field(field_name, field_obj)
+
+ def _invoke_dump_processors(
+ self, tag: str, data, *, many: bool, original_data=None
+ ):
+ # The pass_many post-dump processors may do things like add an envelope, so
+ # invoke those after invoking the non-pass_many processors which will expect
+ # to get a list of items.
+ data = self._invoke_processors(
+ tag, pass_many=False, data=data, many=many, original_data=original_data
+ )
+ return self._invoke_processors(
+ tag, pass_many=True, data=data, many=many, original_data=original_data
+ )
+
+ def _invoke_load_processors(
+ self,
+ tag: str,
+ data,
+ *,
+ many: bool,
+ original_data,
+ partial: bool | types.StrSequenceOrSet | None,
+ ):
+ # This has to invert the order of the dump processors, so run the pass_many
+ # processors first.
+ data = self._invoke_processors(
+ tag,
+ pass_many=True,
+ data=data,
+ many=many,
+ original_data=original_data,
+ partial=partial,
+ )
+ return self._invoke_processors(
+ tag,
+ pass_many=False,
+ data=data,
+ many=many,
+ original_data=original_data,
+ partial=partial,
+ )
+
+ def _invoke_field_validators(self, *, error_store: ErrorStore, data, many: bool):
+ for attr_name, _, validator_kwargs in self._hooks[VALIDATES]:
+ validator = getattr(self, attr_name)
+ field_name = validator_kwargs["field_name"]
+
+ try:
+ field_obj = self.fields[field_name]
+ except KeyError as error:
+ if field_name in self.declared_fields:
+ continue
+ raise ValueError(f'"{field_name}" field does not exist.') from error
+
+ data_key = (
+ field_obj.data_key if field_obj.data_key is not None else field_name
+ )
+ if many:
+ for idx, item in enumerate(data):
+ try:
+ value = item[field_obj.attribute or field_name]
+ except KeyError:
+ pass
+ else:
+ validated_value = self._call_and_store(
+ getter_func=validator,
+ data=value,
+ field_name=data_key,
+ error_store=error_store,
+ index=(idx if self.opts.index_errors else None),
+ )
+ if validated_value is missing:
+ item.pop(field_name, None)
+ else:
+ try:
+ value = data[field_obj.attribute or field_name]
+ except KeyError:
+ pass
+ else:
+ validated_value = self._call_and_store(
+ getter_func=validator,
+ data=value,
+ field_name=data_key,
+ error_store=error_store,
+ )
+ if validated_value is missing:
+ data.pop(field_name, None)
+
+ def _invoke_schema_validators(
+ self,
+ *,
+ error_store: ErrorStore,
+ pass_many: bool,
+ data,
+ original_data,
+ many: bool,
+ partial: bool | types.StrSequenceOrSet | None,
+ field_errors: bool = False,
+ ):
+ for attr_name, hook_many, validator_kwargs in self._hooks[VALIDATES_SCHEMA]:
+ if hook_many != pass_many:
+ continue
+ validator = getattr(self, attr_name)
+ if field_errors and validator_kwargs["skip_on_field_errors"]:
+ continue
+ pass_original = validator_kwargs.get("pass_original", False)
+
+ if many and not pass_many:
+ for idx, (item, orig) in enumerate(zip(data, original_data)):
+ self._run_validator(
+ validator,
+ item,
+ original_data=orig,
+ error_store=error_store,
+ many=many,
+ partial=partial,
+ index=idx,
+ pass_original=pass_original,
+ )
+ else:
+ self._run_validator(
+ validator,
+ data,
+ original_data=original_data,
+ error_store=error_store,
+ many=many,
+ pass_original=pass_original,
+ partial=partial,
+ )
+
+ def _invoke_processors(
+ self,
+ tag: str,
+ *,
+ pass_many: bool,
+ data,
+ many: bool,
+ original_data=None,
+ **kwargs,
+ ):
+ for attr_name, hook_many, processor_kwargs in self._hooks[tag]:
+ if hook_many != pass_many:
+ continue
+ # This will be a bound method.
+ processor = getattr(self, attr_name)
+ pass_original = processor_kwargs.get("pass_original", False)
+
+ if many and not pass_many:
+ if pass_original:
+ data = [
+ processor(item, original, many=many, **kwargs)
+ for item, original in zip_longest(data, original_data)
+ ]
+ else:
+ data = [processor(item, many=many, **kwargs) for item in data]
+ elif pass_original:
+ data = processor(data, original_data, many=many, **kwargs)
+ else:
+ data = processor(data, many=many, **kwargs)
+ return data
+
+
+BaseSchema = Schema # for backwards compatibility
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/types.py b/.venv/lib/python3.12/site-packages/marshmallow/types.py
new file mode 100644
index 00000000..599f6b49
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/types.py
@@ -0,0 +1,40 @@
+"""Type aliases.
+
+.. warning::
+
+ This module is provisional. Types may be modified, added, and removed between minor releases.
+"""
+
+from __future__ import annotations
+
+import typing
+
+#: A type that can be either a sequence of strings or a set of strings
+StrSequenceOrSet = typing.Union[typing.Sequence[str], typing.AbstractSet[str]]
+
+#: Type for validator functions
+Validator = typing.Callable[[typing.Any], typing.Any]
+
+
+class SchemaValidator(typing.Protocol):
+ def __call__(
+ self,
+ output: typing.Any,
+ original_data: typing.Any = ...,
+ *,
+ partial: bool | StrSequenceOrSet | None = None,
+ many: bool = False,
+ ) -> None: ...
+
+
+class RenderModule(typing.Protocol):
+ def dumps(
+ self, obj: typing.Any, *args: typing.Any, **kwargs: typing.Any
+ ) -> str: ...
+
+ def loads(
+ self,
+ json_data: str | bytes | bytearray,
+ *args: typing.Any,
+ **kwargs: typing.Any,
+ ) -> typing.Any: ...
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/utils.py b/.venv/lib/python3.12/site-packages/marshmallow/utils.py
new file mode 100644
index 00000000..df3cd294
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/utils.py
@@ -0,0 +1,380 @@
+"""Utility methods for marshmallow."""
+
+# ruff: noqa: T201, T203
+from __future__ import annotations
+
+import collections
+import datetime as dt
+import functools
+import inspect
+import json
+import re
+import typing
+import warnings
+from collections.abc import Mapping
+from email.utils import format_datetime, parsedate_to_datetime
+from pprint import pprint as py_pprint
+
+from marshmallow.base import FieldABC
+from marshmallow.exceptions import FieldInstanceResolutionError
+from marshmallow.warnings import RemovedInMarshmallow4Warning
+
+if typing.TYPE_CHECKING:
+ from marshmallow.fields import Field
+
+
+EXCLUDE = "exclude"
+INCLUDE = "include"
+RAISE = "raise"
+_UNKNOWN_VALUES = {EXCLUDE, INCLUDE, RAISE}
+
+
+class _Missing:
+ def __bool__(self):
+ return False
+
+ def __copy__(self):
+ return self
+
+ def __deepcopy__(self, _):
+ return self
+
+ def __repr__(self):
+ return "<marshmallow.missing>"
+
+
+# Singleton value that indicates that a field's value is missing from input
+# dict passed to `Schema.load <marshmallow.Schema.load>`. If the field's value is not required,
+# it's ``default`` value is used.
+missing = _Missing()
+
+
+def is_generator(obj) -> bool:
+ """Return True if ``obj`` is a generator"""
+ return inspect.isgeneratorfunction(obj) or inspect.isgenerator(obj)
+
+
+def is_iterable_but_not_string(obj) -> bool:
+ """Return True if ``obj`` is an iterable object that isn't a string."""
+ return (hasattr(obj, "__iter__") and not hasattr(obj, "strip")) or is_generator(obj)
+
+
+def is_collection(obj) -> bool:
+ """Return True if ``obj`` is a collection type, e.g list, tuple, queryset."""
+ return is_iterable_but_not_string(obj) and not isinstance(obj, Mapping)
+
+
+def is_instance_or_subclass(val, class_) -> bool:
+ """Return True if ``val`` is either a subclass or instance of ``class_``."""
+ try:
+ return issubclass(val, class_)
+ except TypeError:
+ return isinstance(val, class_)
+
+
+def is_keyed_tuple(obj) -> bool:
+ """Return True if ``obj`` has keyed tuple behavior, such as
+ namedtuples or SQLAlchemy's KeyedTuples.
+ """
+ return isinstance(obj, tuple) and hasattr(obj, "_fields")
+
+
+def pprint(obj, *args, **kwargs) -> None:
+ """Pretty-printing function that can pretty-print OrderedDicts
+ like regular dictionaries. Useful for printing the output of
+ :meth:`marshmallow.Schema.dump`.
+
+ .. deprecated:: 3.7.0
+ marshmallow.pprint will be removed in marshmallow 4.
+ """
+ warnings.warn(
+ "marshmallow's pprint function is deprecated and will be removed in marshmallow 4.",
+ RemovedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ if isinstance(obj, collections.OrderedDict):
+ print(json.dumps(obj, *args, **kwargs))
+ else:
+ py_pprint(obj, *args, **kwargs)
+
+
+# https://stackoverflow.com/a/27596917
+def is_aware(datetime: dt.datetime) -> bool:
+ return (
+ datetime.tzinfo is not None and datetime.tzinfo.utcoffset(datetime) is not None
+ )
+
+
+def from_rfc(datestring: str) -> dt.datetime:
+ """Parse a RFC822-formatted datetime string and return a datetime object.
+
+ https://stackoverflow.com/questions/885015/how-to-parse-a-rfc-2822-date-time-into-a-python-datetime # noqa: B950
+ """
+ return parsedate_to_datetime(datestring)
+
+
+def rfcformat(datetime: dt.datetime) -> str:
+ """Return the RFC822-formatted representation of a datetime object.
+
+ :param datetime: The datetime.
+ """
+ return format_datetime(datetime)
+
+
+# Hat tip to Django for ISO8601 deserialization functions
+
+_iso8601_datetime_re = re.compile(
+ r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
+ r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
+ r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
+ r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
+)
+
+_iso8601_date_re = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
+
+_iso8601_time_re = re.compile(
+ r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
+ r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
+)
+
+
+def get_fixed_timezone(offset: float | dt.timedelta) -> dt.timezone:
+ """Return a tzinfo instance with a fixed offset from UTC."""
+ if isinstance(offset, dt.timedelta):
+ offset = offset.total_seconds() // 60
+ sign = "-" if offset < 0 else "+"
+ hhmm = "{:02d}{:02d}".format(*divmod(abs(offset), 60))
+ name = sign + hhmm
+ return dt.timezone(dt.timedelta(minutes=offset), name)
+
+
+def from_iso_datetime(value):
+ """Parse a string and return a datetime.datetime.
+
+ This function supports time zone offsets. When the input contains one,
+ the output uses a timezone with a fixed offset from UTC.
+ """
+ match = _iso8601_datetime_re.match(value)
+ if not match:
+ raise ValueError("Not a valid ISO8601-formatted datetime string")
+ kw = match.groupdict()
+ kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
+ tzinfo = kw.pop("tzinfo")
+ if tzinfo == "Z":
+ tzinfo = dt.timezone.utc
+ elif tzinfo is not None:
+ offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0
+ offset = 60 * int(tzinfo[1:3]) + offset_mins
+ if tzinfo[0] == "-":
+ offset = -offset
+ tzinfo = get_fixed_timezone(offset)
+ kw = {k: int(v) for k, v in kw.items() if v is not None}
+ kw["tzinfo"] = tzinfo
+ return dt.datetime(**kw) # noqa: DTZ001
+
+
+def from_iso_time(value):
+ """Parse a string and return a datetime.time.
+
+ This function doesn't support time zone offsets.
+ """
+ match = _iso8601_time_re.match(value)
+ if not match:
+ raise ValueError("Not a valid ISO8601-formatted time string")
+ kw = match.groupdict()
+ kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
+ kw = {k: int(v) for k, v in kw.items() if v is not None}
+ return dt.time(**kw)
+
+
+def from_iso_date(value):
+ """Parse a string and return a datetime.date."""
+ match = _iso8601_date_re.match(value)
+ if not match:
+ raise ValueError("Not a valid ISO8601-formatted date string")
+ kw = {k: int(v) for k, v in match.groupdict().items()}
+ return dt.date(**kw)
+
+
+def from_timestamp(value: typing.Any) -> dt.datetime:
+ if value is True or value is False:
+ raise ValueError("Not a valid POSIX timestamp")
+ value = float(value)
+ if value < 0:
+ raise ValueError("Not a valid POSIX timestamp")
+
+ # Load a timestamp with utc as timezone to prevent using system timezone.
+ # Then set timezone to None, to let the Field handle adding timezone info.
+ try:
+ return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).replace(tzinfo=None)
+ except OverflowError as exc:
+ raise ValueError("Timestamp is too large") from exc
+ except OSError as exc:
+ raise ValueError("Error converting value to datetime") from exc
+
+
+def from_timestamp_ms(value: typing.Any) -> dt.datetime:
+ value = float(value)
+ return from_timestamp(value / 1000)
+
+
+def timestamp(
+ value: dt.datetime,
+) -> float:
+ if not is_aware(value):
+ # When a date is naive, use UTC as zone info to prevent using system timezone.
+ value = value.replace(tzinfo=dt.timezone.utc)
+ return value.timestamp()
+
+
+def timestamp_ms(value: dt.datetime) -> float:
+ return timestamp(value) * 1000
+
+
+def isoformat(datetime: dt.datetime) -> str:
+ """Return the ISO8601-formatted representation of a datetime object.
+
+ :param datetime: The datetime.
+ """
+ return datetime.isoformat()
+
+
+def to_iso_time(time: dt.time) -> str:
+ return dt.time.isoformat(time)
+
+
+def to_iso_date(date: dt.date) -> str:
+ return dt.date.isoformat(date)
+
+
+def ensure_text_type(val: str | bytes) -> str:
+ if isinstance(val, bytes):
+ val = val.decode("utf-8")
+ return str(val)
+
+
+def pluck(dictlist: list[dict[str, typing.Any]], key: str):
+ """Extracts a list of dictionary values from a list of dictionaries.
+ ::
+
+ >>> dlist = [{'id': 1, 'name': 'foo'}, {'id': 2, 'name': 'bar'}]
+ >>> pluck(dlist, 'id')
+ [1, 2]
+ """
+ return [d[key] for d in dictlist]
+
+
+# Various utilities for pulling keyed values from objects
+
+
+def get_value(obj, key: int | str, default=missing):
+ """Helper for pulling a keyed value off various types of objects. Fields use
+ this method by default to access attributes of the source object. For object `x`
+ and attribute `i`, this method first tries to access `x[i]`, and then falls back to
+ `x.i` if an exception is raised.
+
+ .. warning::
+ If an object `x` does not raise an exception when `x[i]` does not exist,
+ `get_value` will never check the value `x.i`. Consider overriding
+ `marshmallow.fields.Field.get_value` in this case.
+ """
+ if not isinstance(key, int) and "." in key:
+ return _get_value_for_keys(obj, key.split("."), default)
+ return _get_value_for_key(obj, key, default)
+
+
+def _get_value_for_keys(obj, keys, default):
+ if len(keys) == 1:
+ return _get_value_for_key(obj, keys[0], default)
+ return _get_value_for_keys(
+ _get_value_for_key(obj, keys[0], default), keys[1:], default
+ )
+
+
+def _get_value_for_key(obj, key, default):
+ if not hasattr(obj, "__getitem__"):
+ return getattr(obj, key, default)
+
+ try:
+ return obj[key]
+ except (KeyError, IndexError, TypeError, AttributeError):
+ return getattr(obj, key, default)
+
+
+def set_value(dct: dict[str, typing.Any], key: str, value: typing.Any):
+ """Set a value in a dict. If `key` contains a '.', it is assumed
+ be a path (i.e. dot-delimited string) to the value's location.
+
+ ::
+
+ >>> d = {}
+ >>> set_value(d, 'foo.bar', 42)
+ >>> d
+ {'foo': {'bar': 42}}
+ """
+ if "." in key:
+ head, rest = key.split(".", 1)
+ target = dct.setdefault(head, {})
+ if not isinstance(target, dict):
+ raise ValueError(
+ f"Cannot set {key} in {head} due to existing value: {target}"
+ )
+ set_value(target, rest, value)
+ else:
+ dct[key] = value
+
+
+def callable_or_raise(obj):
+ """Check that an object is callable, else raise a :exc:`TypeError`."""
+ if not callable(obj):
+ raise TypeError(f"Object {obj!r} is not callable.")
+ return obj
+
+
+def _signature(func: typing.Callable) -> list[str]:
+ return list(inspect.signature(func).parameters.keys())
+
+
+def get_func_args(func: typing.Callable) -> list[str]:
+ """Given a callable, return a list of argument names. Handles
+ `functools.partial` objects and class-based callables.
+
+ .. versionchanged:: 3.0.0a1
+ Do not return bound arguments, eg. ``self``.
+ """
+ if inspect.isfunction(func) or inspect.ismethod(func):
+ return _signature(func)
+ if isinstance(func, functools.partial):
+ return _signature(func.func)
+ # Callable class
+ return _signature(func)
+
+
+def resolve_field_instance(cls_or_instance: type[Field] | Field) -> Field:
+ """Return a field instance from a field class or instance.
+
+ :param cls_or_instance: Field class or instance.
+ """
+ if isinstance(cls_or_instance, type):
+ if not issubclass(cls_or_instance, FieldABC):
+ raise FieldInstanceResolutionError
+ return cls_or_instance()
+ if not isinstance(cls_or_instance, FieldABC):
+ raise FieldInstanceResolutionError
+ return cls_or_instance
+
+
+def timedelta_to_microseconds(value: dt.timedelta) -> int:
+ """Compute the total microseconds of a timedelta
+
+ https://github.com/python/cpython/blob/bb3e0c240bc60fe08d332ff5955d54197f79751c/Lib/datetime.py#L665-L667 # noqa: B950
+ """
+ return (value.days * (24 * 3600) + value.seconds) * 1000000 + value.microseconds
+
+
+def validate_unknown_parameter_value(obj: typing.Any) -> str:
+ if obj not in _UNKNOWN_VALUES:
+ raise ValueError(
+ f"Object {obj!r} is not a valid value for the 'unknown' parameter"
+ )
+ return obj
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/validate.py b/.venv/lib/python3.12/site-packages/marshmallow/validate.py
new file mode 100644
index 00000000..d56912a3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/validate.py
@@ -0,0 +1,700 @@
+"""Validation classes for various types of data."""
+
+from __future__ import annotations
+
+import re
+import typing
+import warnings
+from abc import ABC, abstractmethod
+from itertools import zip_longest
+from operator import attrgetter
+
+from marshmallow.exceptions import ValidationError
+from marshmallow.warnings import ChangedInMarshmallow4Warning
+
+if typing.TYPE_CHECKING:
+ from marshmallow import types
+
+_T = typing.TypeVar("_T")
+
+
+class Validator(ABC):
+ """Abstract base class for validators.
+
+ .. note::
+ This class does not provide any validation behavior. It is only used to
+ add a useful `__repr__` implementation for validators.
+ """
+
+ error: str | None = None
+
+ def __repr__(self) -> str:
+ args = self._repr_args()
+ args = f"{args}, " if args else ""
+
+ return f"<{self.__class__.__name__}({args}error={self.error!r})>"
+
+ def _repr_args(self) -> str:
+ """A string representation of the args passed to this validator. Used by
+ `__repr__`.
+ """
+ return ""
+
+ @abstractmethod
+ def __call__(self, value: typing.Any) -> typing.Any: ...
+
+
+class And(Validator):
+ """Compose multiple validators and combine their error messages.
+
+ Example: ::
+
+ from marshmallow import validate, ValidationError
+
+
+ def is_even(value):
+ if value % 2 != 0:
+ raise ValidationError("Not an even value.")
+
+
+ validator = validate.And(validate.Range(min=0), is_even)
+ validator(-1)
+ # ValidationError: ['Must be greater than or equal to 0.', 'Not an even value.']
+
+ :param validators: Validators to combine.
+ :param error: Error message to use when a validator returns ``False``.
+ """
+
+ default_error_message = "Invalid value."
+
+ def __init__(self, *validators: types.Validator, error: str | None = None):
+ self.validators = tuple(validators)
+ self.error: str = error or self.default_error_message
+
+ def _repr_args(self) -> str:
+ return f"validators={self.validators!r}"
+
+ def __call__(self, value: typing.Any) -> typing.Any:
+ errors: list[str | dict] = []
+ kwargs: dict[str, typing.Any] = {}
+ for validator in self.validators:
+ try:
+ r = validator(value)
+ if not isinstance(validator, Validator) and r is False:
+ warnings.warn(
+ "Returning `False` from a validator is deprecated. "
+ "Raise a `ValidationError` instead.",
+ ChangedInMarshmallow4Warning,
+ stacklevel=2,
+ )
+ raise ValidationError(self.error) # noqa: TRY301
+ except ValidationError as err:
+ kwargs.update(err.kwargs)
+ if isinstance(err.messages, dict):
+ errors.append(err.messages)
+ else:
+ errors.extend(err.messages)
+ if errors:
+ raise ValidationError(errors, **kwargs)
+ return value
+
+
+class URL(Validator):
+ """Validate a URL.
+
+ :param relative: Whether to allow relative URLs.
+ :param absolute: Whether to allow absolute URLs.
+ :param error: Error message to raise in case of a validation error.
+ Can be interpolated with `{input}`.
+ :param schemes: Valid schemes. By default, ``http``, ``https``,
+ ``ftp``, and ``ftps`` are allowed.
+ :param require_tld: Whether to reject non-FQDN hostnames.
+ """
+
+ class RegexMemoizer:
+ def __init__(self):
+ self._memoized = {}
+
+ def _regex_generator(
+ self, *, relative: bool, absolute: bool, require_tld: bool
+ ) -> typing.Pattern:
+ hostname_variants = [
+ # a normal domain name, expressed in [A-Z0-9] chars with hyphens allowed only in the middle
+ # note that the regex will be compiled with IGNORECASE, so these are upper and lowercase chars
+ (
+ r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
+ r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)"
+ ),
+ # or the special string 'localhost'
+ r"localhost",
+ # or IPv4
+ r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}",
+ # or IPv6
+ r"\[[A-F0-9]*:[A-F0-9:]+\]",
+ ]
+ if not require_tld:
+ # allow dotless hostnames
+ hostname_variants.append(r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)")
+
+ absolute_part = "".join(
+ (
+ # scheme (e.g. 'https://', 'ftp://', etc)
+ # this is validated separately against allowed schemes, so in the regex
+ # we simply want to capture its existence
+ r"(?:[a-z0-9\.\-\+]*)://",
+ # userinfo, for URLs encoding authentication
+ # e.g. 'ftp://foo:bar@ftp.example.org/'
+ r"(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?",
+ # netloc, the hostname/domain part of the URL plus the optional port
+ r"(?:",
+ "|".join(hostname_variants),
+ r")",
+ r"(?::\d+)?",
+ )
+ )
+ relative_part = r"(?:/?|[/?]\S+)\Z"
+
+ if relative:
+ if absolute:
+ parts: tuple[str, ...] = (
+ r"^(",
+ absolute_part,
+ r")?",
+ relative_part,
+ )
+ else:
+ parts = (r"^", relative_part)
+ else:
+ parts = (r"^", absolute_part, relative_part)
+
+ return re.compile("".join(parts), re.IGNORECASE)
+
+ def __call__(
+ self, *, relative: bool, absolute: bool, require_tld: bool
+ ) -> typing.Pattern:
+ key = (relative, absolute, require_tld)
+ if key not in self._memoized:
+ self._memoized[key] = self._regex_generator(
+ relative=relative, absolute=absolute, require_tld=require_tld
+ )
+
+ return self._memoized[key]
+
+ _regex = RegexMemoizer()
+
+ default_message = "Not a valid URL."
+ default_schemes = {"http", "https", "ftp", "ftps"}
+
+ def __init__(
+ self,
+ *,
+ relative: bool = False,
+ absolute: bool = True,
+ schemes: types.StrSequenceOrSet | None = None,
+ require_tld: bool = True,
+ error: str | None = None,
+ ):
+ if not relative and not absolute:
+ raise ValueError(
+ "URL validation cannot set both relative and absolute to False."
+ )
+ self.relative = relative
+ self.absolute = absolute
+ self.error: str = error or self.default_message
+ self.schemes = schemes or self.default_schemes
+ self.require_tld = require_tld
+
+ def _repr_args(self) -> str:
+ return f"relative={self.relative!r}, absolute={self.absolute!r}"
+
+ def _format_error(self, value) -> str:
+ return self.error.format(input=value)
+
+ def __call__(self, value: str) -> str:
+ message = self._format_error(value)
+ if not value:
+ raise ValidationError(message)
+
+ # Check first if the scheme is valid
+ scheme = None
+ if "://" in value:
+ scheme = value.split("://")[0].lower()
+ if scheme not in self.schemes:
+ raise ValidationError(message)
+
+ regex = self._regex(
+ relative=self.relative, absolute=self.absolute, require_tld=self.require_tld
+ )
+
+ # Hostname is optional for file URLS. If absent it means `localhost`.
+ # Fill it in for the validation if needed
+ if scheme == "file" and value.startswith("file:///"):
+ matched = regex.search(value.replace("file:///", "file://localhost/", 1))
+ else:
+ matched = regex.search(value)
+
+ if not matched:
+ raise ValidationError(message)
+
+ return value
+
+
+class Email(Validator):
+ """Validate an email address.
+
+ :param error: Error message to raise in case of a validation error. Can be
+ interpolated with `{input}`.
+ """
+
+ USER_REGEX = re.compile(
+ r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*\Z" # dot-atom
+ # quoted-string
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]'
+ r'|\\[\001-\011\013\014\016-\177])*"\Z)',
+ re.IGNORECASE | re.UNICODE,
+ )
+
+ DOMAIN_REGEX = re.compile(
+ # domain
+ r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
+ r"(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\Z"
+ # literal form, ipv4 address (SMTP 4.1.3)
+ r"|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)"
+ r"(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z",
+ re.IGNORECASE | re.UNICODE,
+ )
+
+ DOMAIN_WHITELIST = ("localhost",)
+
+ default_message = "Not a valid email address."
+
+ def __init__(self, *, error: str | None = None):
+ self.error: str = error or self.default_message
+
+ def _format_error(self, value: str) -> str:
+ return self.error.format(input=value)
+
+ def __call__(self, value: str) -> str:
+ message = self._format_error(value)
+
+ if not value or "@" not in value:
+ raise ValidationError(message)
+
+ user_part, domain_part = value.rsplit("@", 1)
+
+ if not self.USER_REGEX.match(user_part):
+ raise ValidationError(message)
+
+ if domain_part not in self.DOMAIN_WHITELIST:
+ if not self.DOMAIN_REGEX.match(domain_part):
+ try:
+ domain_part = domain_part.encode("idna").decode("ascii")
+ except UnicodeError:
+ pass
+ else:
+ if self.DOMAIN_REGEX.match(domain_part):
+ return value
+ raise ValidationError(message)
+
+ return value
+
+
+class Range(Validator):
+ """Validator which succeeds if the value passed to it is within the specified
+ range. If ``min`` is not specified, or is specified as `None`,
+ no lower bound exists. If ``max`` is not specified, or is specified as `None`,
+ no upper bound exists. The inclusivity of the bounds (if they exist) is configurable.
+ If ``min_inclusive`` is not specified, or is specified as `True`, then
+ the ``min`` bound is included in the range. If ``max_inclusive`` is not specified,
+ or is specified as `True`, then the ``max`` bound is included in the range.
+
+ :param min: The minimum value (lower bound). If not provided, minimum
+ value will not be checked.
+ :param max: The maximum value (upper bound). If not provided, maximum
+ value will not be checked.
+ :param min_inclusive: Whether the `min` bound is included in the range.
+ :param max_inclusive: Whether the `max` bound is included in the range.
+ :param error: Error message to raise in case of a validation error.
+ Can be interpolated with `{input}`, `{min}` and `{max}`.
+ """
+
+ message_min = "Must be {min_op} {{min}}."
+ message_max = "Must be {max_op} {{max}}."
+ message_all = "Must be {min_op} {{min}} and {max_op} {{max}}."
+
+ message_gte = "greater than or equal to"
+ message_gt = "greater than"
+ message_lte = "less than or equal to"
+ message_lt = "less than"
+
+ def __init__(
+ self,
+ min=None, # noqa: A002
+ max=None, # noqa: A002
+ *,
+ min_inclusive: bool = True,
+ max_inclusive: bool = True,
+ error: str | None = None,
+ ):
+ self.min = min
+ self.max = max
+ self.error = error
+ self.min_inclusive = min_inclusive
+ self.max_inclusive = max_inclusive
+
+ # interpolate messages based on bound inclusivity
+ self.message_min = self.message_min.format(
+ min_op=self.message_gte if self.min_inclusive else self.message_gt
+ )
+ self.message_max = self.message_max.format(
+ max_op=self.message_lte if self.max_inclusive else self.message_lt
+ )
+ self.message_all = self.message_all.format(
+ min_op=self.message_gte if self.min_inclusive else self.message_gt,
+ max_op=self.message_lte if self.max_inclusive else self.message_lt,
+ )
+
+ def _repr_args(self) -> str:
+ return f"min={self.min!r}, max={self.max!r}, min_inclusive={self.min_inclusive!r}, max_inclusive={self.max_inclusive!r}"
+
+ def _format_error(self, value: _T, message: str) -> str:
+ return (self.error or message).format(input=value, min=self.min, max=self.max)
+
+ def __call__(self, value: _T) -> _T:
+ if self.min is not None and (
+ value < self.min if self.min_inclusive else value <= self.min
+ ):
+ message = self.message_min if self.max is None else self.message_all
+ raise ValidationError(self._format_error(value, message))
+
+ if self.max is not None and (
+ value > self.max if self.max_inclusive else value >= self.max
+ ):
+ message = self.message_max if self.min is None else self.message_all
+ raise ValidationError(self._format_error(value, message))
+
+ return value
+
+
+_SizedT = typing.TypeVar("_SizedT", bound=typing.Sized)
+
+
+class Length(Validator):
+ """Validator which succeeds if the value passed to it has a
+ length between a minimum and maximum. Uses len(), so it
+ can work for strings, lists, or anything with length.
+
+ :param min: The minimum length. If not provided, minimum length
+ will not be checked.
+ :param max: The maximum length. If not provided, maximum length
+ will not be checked.
+ :param equal: The exact length. If provided, maximum and minimum
+ length will not be checked.
+ :param error: Error message to raise in case of a validation error.
+ Can be interpolated with `{input}`, `{min}` and `{max}`.
+ """
+
+ message_min = "Shorter than minimum length {min}."
+ message_max = "Longer than maximum length {max}."
+ message_all = "Length must be between {min} and {max}."
+ message_equal = "Length must be {equal}."
+
+ def __init__(
+ self,
+ min: int | None = None, # noqa: A002
+ max: int | None = None, # noqa: A002
+ *,
+ equal: int | None = None,
+ error: str | None = None,
+ ):
+ if equal is not None and any([min, max]):
+ raise ValueError(
+ "The `equal` parameter was provided, maximum or "
+ "minimum parameter must not be provided."
+ )
+
+ self.min = min
+ self.max = max
+ self.error = error
+ self.equal = equal
+
+ def _repr_args(self) -> str:
+ return f"min={self.min!r}, max={self.max!r}, equal={self.equal!r}"
+
+ def _format_error(self, value: _SizedT, message: str) -> str:
+ return (self.error or message).format(
+ input=value, min=self.min, max=self.max, equal=self.equal
+ )
+
+ def __call__(self, value: _SizedT) -> _SizedT:
+ length = len(value)
+
+ if self.equal is not None:
+ if length != self.equal:
+ raise ValidationError(self._format_error(value, self.message_equal))
+ return value
+
+ if self.min is not None and length < self.min:
+ message = self.message_min if self.max is None else self.message_all
+ raise ValidationError(self._format_error(value, message))
+
+ if self.max is not None and length > self.max:
+ message = self.message_max if self.min is None else self.message_all
+ raise ValidationError(self._format_error(value, message))
+
+ return value
+
+
+class Equal(Validator):
+ """Validator which succeeds if the ``value`` passed to it is
+ equal to ``comparable``.
+
+ :param comparable: The object to compare to.
+ :param error: Error message to raise in case of a validation error.
+ Can be interpolated with `{input}` and `{other}`.
+ """
+
+ default_message = "Must be equal to {other}."
+
+ def __init__(self, comparable, *, error: str | None = None):
+ self.comparable = comparable
+ self.error: str = error or self.default_message
+
+ def _repr_args(self) -> str:
+ return f"comparable={self.comparable!r}"
+
+ def _format_error(self, value: _T) -> str:
+ return self.error.format(input=value, other=self.comparable)
+
+ def __call__(self, value: _T) -> _T:
+ if value != self.comparable:
+ raise ValidationError(self._format_error(value))
+ return value
+
+
+class Regexp(Validator):
+ """Validator which succeeds if the ``value`` matches ``regex``.
+
+ .. note::
+
+ Uses `re.match`, which searches for a match at the beginning of a string.
+
+ :param regex: The regular expression string to use. Can also be a compiled
+ regular expression pattern.
+ :param flags: The regexp flags to use, for example re.IGNORECASE. Ignored
+ if ``regex`` is not a string.
+ :param error: Error message to raise in case of a validation error.
+ Can be interpolated with `{input}` and `{regex}`.
+ """
+
+ default_message = "String does not match expected pattern."
+
+ def __init__(
+ self,
+ regex: str | bytes | typing.Pattern,
+ flags: int = 0,
+ *,
+ error: str | None = None,
+ ):
+ self.regex = (
+ re.compile(regex, flags) if isinstance(regex, (str, bytes)) else regex
+ )
+ self.error: str = error or self.default_message
+
+ def _repr_args(self) -> str:
+ return f"regex={self.regex!r}"
+
+ def _format_error(self, value: str | bytes) -> str:
+ return self.error.format(input=value, regex=self.regex.pattern)
+
+ @typing.overload
+ def __call__(self, value: str) -> str: ...
+
+ @typing.overload
+ def __call__(self, value: bytes) -> bytes: ...
+
+ def __call__(self, value):
+ if self.regex.match(value) is None:
+ raise ValidationError(self._format_error(value))
+
+ return value
+
+
+class Predicate(Validator):
+ """Call the specified ``method`` of the ``value`` object. The
+ validator succeeds if the invoked method returns an object that
+ evaluates to True in a Boolean context. Any additional keyword
+ argument will be passed to the method.
+
+ :param method: The name of the method to invoke.
+ :param error: Error message to raise in case of a validation error.
+ Can be interpolated with `{input}` and `{method}`.
+ :param kwargs: Additional keyword arguments to pass to the method.
+ """
+
+ default_message = "Invalid input."
+
+ def __init__(self, method: str, *, error: str | None = None, **kwargs):
+ self.method = method
+ self.error: str = error or self.default_message
+ self.kwargs = kwargs
+
+ def _repr_args(self) -> str:
+ return f"method={self.method!r}, kwargs={self.kwargs!r}"
+
+ def _format_error(self, value: typing.Any) -> str:
+ return self.error.format(input=value, method=self.method)
+
+ def __call__(self, value: _T) -> _T:
+ method = getattr(value, self.method)
+
+ if not method(**self.kwargs):
+ raise ValidationError(self._format_error(value))
+
+ return value
+
+
+class NoneOf(Validator):
+ """Validator which fails if ``value`` is a member of ``iterable``.
+
+ :param iterable: A sequence of invalid values.
+ :param error: Error message to raise in case of a validation error. Can be
+ interpolated using `{input}` and `{values}`.
+ """
+
+ default_message = "Invalid input."
+
+ def __init__(self, iterable: typing.Iterable, *, error: str | None = None):
+ self.iterable = iterable
+ self.values_text = ", ".join(str(each) for each in self.iterable)
+ self.error: str = error or self.default_message
+
+ def _repr_args(self) -> str:
+ return f"iterable={self.iterable!r}"
+
+ def _format_error(self, value) -> str:
+ return self.error.format(input=value, values=self.values_text)
+
+ def __call__(self, value: typing.Any) -> typing.Any:
+ try:
+ if value in self.iterable:
+ raise ValidationError(self._format_error(value))
+ except TypeError:
+ pass
+
+ return value
+
+
+class OneOf(Validator):
+ """Validator which succeeds if ``value`` is a member of ``choices``.
+
+ :param choices: A sequence of valid values.
+ :param labels: Optional sequence of labels to pair with the choices.
+ :param error: Error message to raise in case of a validation error. Can be
+ interpolated with `{input}`, `{choices}` and `{labels}`.
+ """
+
+ default_message = "Must be one of: {choices}."
+
+ def __init__(
+ self,
+ choices: typing.Iterable,
+ labels: typing.Iterable[str] | None = None,
+ *,
+ error: str | None = None,
+ ):
+ self.choices = choices
+ self.choices_text = ", ".join(str(choice) for choice in self.choices)
+ self.labels = labels if labels is not None else []
+ self.labels_text = ", ".join(str(label) for label in self.labels)
+ self.error: str = error or self.default_message
+
+ def _repr_args(self) -> str:
+ return f"choices={self.choices!r}, labels={self.labels!r}"
+
+ def _format_error(self, value) -> str:
+ return self.error.format(
+ input=value, choices=self.choices_text, labels=self.labels_text
+ )
+
+ def __call__(self, value: typing.Any) -> typing.Any:
+ try:
+ if value not in self.choices:
+ raise ValidationError(self._format_error(value))
+ except TypeError as error:
+ raise ValidationError(self._format_error(value)) from error
+
+ return value
+
+ def options(
+ self,
+ valuegetter: str | typing.Callable[[typing.Any], typing.Any] = str,
+ ) -> typing.Iterable[tuple[typing.Any, str]]:
+ """Return a generator over the (value, label) pairs, where value
+ is a string associated with each choice. This convenience method
+ is useful to populate, for instance, a form select field.
+
+ :param valuegetter: Can be a callable or a string. In the former case, it must
+ be a one-argument callable which returns the value of a
+ choice. In the latter case, the string specifies the name
+ of an attribute of the choice objects. Defaults to `str()`
+ or `str()`.
+ """
+ valuegetter = valuegetter if callable(valuegetter) else attrgetter(valuegetter)
+ pairs = zip_longest(self.choices, self.labels, fillvalue="")
+
+ return ((valuegetter(choice), label) for choice, label in pairs)
+
+
+class ContainsOnly(OneOf):
+ """Validator which succeeds if ``value`` is a sequence and each element
+ in the sequence is also in the sequence passed as ``choices``. Empty input
+ is considered valid.
+
+ :param choices: Same as :class:`OneOf`.
+ :param labels: Same as :class:`OneOf`.
+ :param error: Same as :class:`OneOf`.
+
+ .. versionchanged:: 3.0.0b2
+ Duplicate values are considered valid.
+ .. versionchanged:: 3.0.0b2
+ Empty input is considered valid. Use `validate.Length(min=1) <marshmallow.validate.Length>`
+ to validate against empty inputs.
+ """
+
+ default_message = "One or more of the choices you made was not in: {choices}."
+
+ def _format_error(self, value) -> str:
+ value_text = ", ".join(str(val) for val in value)
+ return super()._format_error(value_text)
+
+ def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
+ # We can't use set.issubset because does not handle unhashable types
+ for val in value:
+ if val not in self.choices:
+ raise ValidationError(self._format_error(value))
+ return value
+
+
+class ContainsNoneOf(NoneOf):
+ """Validator which fails if ``value`` is a sequence and any element
+ in the sequence is a member of the sequence passed as ``iterable``. Empty input
+ is considered valid.
+
+ :param iterable: Same as :class:`NoneOf`.
+ :param error: Same as :class:`NoneOf`.
+
+ .. versionadded:: 3.6.0
+ """
+
+ default_message = "One or more of the choices you made was in: {values}."
+
+ def _format_error(self, value) -> str:
+ value_text = ", ".join(str(val) for val in value)
+ return super()._format_error(value_text)
+
+ def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
+ for val in value:
+ if val in self.iterable:
+ raise ValidationError(self._format_error(value))
+ return value
diff --git a/.venv/lib/python3.12/site-packages/marshmallow/warnings.py b/.venv/lib/python3.12/site-packages/marshmallow/warnings.py
new file mode 100644
index 00000000..ea561cf6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/marshmallow/warnings.py
@@ -0,0 +1,10 @@
+class Marshmallow4Warning(DeprecationWarning):
+ pass
+
+
+class ChangedInMarshmallow4Warning(Marshmallow4Warning):
+ pass
+
+
+class RemovedInMarshmallow4Warning(Marshmallow4Warning):
+ pass