diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/marshmallow/fields.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/marshmallow/fields.py | 2153 |
1 files changed, 2153 insertions, 0 deletions
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 |