"""Tests for field serialization."""

import datetime as dt
import decimal
import ipaddress
import itertools
import math
import uuid
from collections import OrderedDict, namedtuple

import pytest

from marshmallow import Schema, fields
from marshmallow import missing as missing_
from tests.base import ALL_FIELDS, DateEnum, GenderEnum, HairColorEnum, User, central


class DateTimeList:
    def __init__(self, dtimes):
        self.dtimes = dtimes


class IntegerList:
    def __init__(self, ints):
        self.ints = ints


class DateTimeIntegerTuple:
    def __init__(self, dtime_int):
        self.dtime_int = dtime_int


class TestFieldSerialization:
    @pytest.fixture
    def user(self):
        return User("Foo", email="foo@bar.com", age=42)

    @pytest.mark.parametrize(
        ("value", "expected"), [(42, float(42)), (0, float(0)), (None, None)]
    )
    def test_number(self, value, expected, user):
        field = fields.Number()
        user.age = value
        assert field.serialize("age", user) == expected

    def test_number_as_string(self, user):
        user.age = 42
        field = fields.Number(as_string=True)
        assert field.serialize("age", user) == str(float(user.age))

    def test_number_as_string_passed_none(self, user):
        user.age = None
        field = fields.Number(as_string=True, allow_none=True)
        assert field.serialize("age", user) is None

    def test_function_field_passed_func(self, user):
        field = fields.Function(lambda obj: obj.name.upper())
        assert "FOO" == field.serialize("key", user)

    def test_function_field_passed_serialize_only_is_dump_only(self, user):
        field = fields.Function(serialize=lambda obj: obj.name.upper())
        assert field.dump_only is True

    def test_function_field_passed_deserialize_and_serialize_is_not_dump_only(self):
        field = fields.Function(
            serialize=lambda val: val.lower(), deserialize=lambda val: val.upper()
        )
        assert field.dump_only is False

    def test_function_field_passed_serialize(self, user):
        field = fields.Function(serialize=lambda obj: obj.name.upper())
        assert "FOO" == field.serialize("key", user)

    # https://github.com/marshmallow-code/marshmallow/issues/395
    def test_function_field_does_not_swallow_attribute_error(self, user):
        def raise_error(obj):
            raise AttributeError()

        field = fields.Function(serialize=raise_error)
        with pytest.raises(AttributeError):
            field.serialize("key", user)

    def test_serialize_with_load_only_param(self):
        class AliasingUserSerializer(Schema):
            name = fields.String()
            years = fields.Integer(load_only=True)
            size = fields.Integer(dump_only=True, load_only=True)
            nicknames = fields.List(fields.Str(), load_only=True)

        data = {
            "name": "Mick",
            "years": "42",
            "size": "12",
            "nicknames": ["Your Majesty", "Brenda"],
        }
        result = AliasingUserSerializer().dump(data)
        assert result["name"] == "Mick"
        assert "years" not in result
        assert "size" not in result
        assert "nicknames" not in result

    def test_function_field_load_only(self):
        field = fields.Function(deserialize=lambda obj: None)
        assert field.load_only

    def test_function_field_passed_serialize_with_context(self, user, monkeypatch):
        class Parent(Schema):
            pass

        field = fields.Function(
            serialize=lambda obj, context: obj.name.upper() + context["key"]
        )
        field.parent = Parent(context={"key": "BAR"})
        assert "FOOBAR" == field.serialize("key", user)

    def test_function_field_passed_uncallable_object(self):
        with pytest.raises(TypeError):
            fields.Function("uncallable")

    def test_integer_field(self, user):
        field = fields.Integer()
        assert field.serialize("age", user) == 42

    def test_integer_as_string_field(self, user):
        field = fields.Integer(as_string=True)
        assert field.serialize("age", user) == "42"

    def test_integer_field_default(self, user):
        user.age = None
        field = fields.Integer(dump_default=0)
        assert field.serialize("age", user) is None
        # missing
        assert field.serialize("age", {}) == 0

    def test_integer_field_default_set_to_none(self, user):
        user.age = None
        field = fields.Integer(dump_default=None)
        assert field.serialize("age", user) is None

    def test_uuid_field(self, user):
        user.uuid1 = uuid.UUID("12345678123456781234567812345678")
        user.uuid2 = None

        field = fields.UUID()
        assert isinstance(field.serialize("uuid1", user), str)
        assert field.serialize("uuid1", user) == "12345678-1234-5678-1234-567812345678"
        assert field.serialize("uuid2", user) is None

    def test_ip_address_field(self, user):
        ipv4_string = "192.168.0.1"
        ipv6_string = "ffff::ffff"
        ipv6_exploded_string = ipaddress.ip_address("ffff::ffff").exploded

        user.ipv4 = ipaddress.ip_address(ipv4_string)
        user.ipv6 = ipaddress.ip_address(ipv6_string)
        user.empty_ip = None

        field_compressed = fields.IP()
        assert isinstance(field_compressed.serialize("ipv4", user), str)
        assert field_compressed.serialize("ipv4", user) == ipv4_string
        assert isinstance(field_compressed.serialize("ipv6", user), str)
        assert field_compressed.serialize("ipv6", user) == ipv6_string
        assert field_compressed.serialize("empty_ip", user) is None

        field_exploded = fields.IP(exploded=True)
        assert isinstance(field_exploded.serialize("ipv6", user), str)
        assert field_exploded.serialize("ipv6", user) == ipv6_exploded_string

    def test_ipv4_address_field(self, user):
        ipv4_string = "192.168.0.1"

        user.ipv4 = ipaddress.ip_address(ipv4_string)
        user.empty_ip = None

        field = fields.IPv4()
        assert isinstance(field.serialize("ipv4", user), str)
        assert field.serialize("ipv4", user) == ipv4_string
        assert field.serialize("empty_ip", user) is None

    def test_ipv6_address_field(self, user):
        ipv6_string = "ffff::ffff"
        ipv6_exploded_string = ipaddress.ip_address("ffff::ffff").exploded

        user.ipv6 = ipaddress.ip_address(ipv6_string)
        user.empty_ip = None

        field_compressed = fields.IPv6()
        assert isinstance(field_compressed.serialize("ipv6", user), str)
        assert field_compressed.serialize("ipv6", user) == ipv6_string
        assert field_compressed.serialize("empty_ip", user) is None

        field_exploded = fields.IPv6(exploded=True)
        assert isinstance(field_exploded.serialize("ipv6", user), str)
        assert field_exploded.serialize("ipv6", user) == ipv6_exploded_string

    def test_ip_interface_field(self, user):
        ipv4interface_string = "192.168.0.1/24"
        ipv6interface_string = "ffff::ffff/128"
        ipv6interface_exploded_string = ipaddress.ip_interface(
            "ffff::ffff/128"
        ).exploded

        user.ipv4interface = ipaddress.ip_interface(ipv4interface_string)
        user.ipv6interface = ipaddress.ip_interface(ipv6interface_string)
        user.empty_ipinterface = None

        field_compressed = fields.IPInterface()
        assert isinstance(field_compressed.serialize("ipv4interface", user), str)
        assert field_compressed.serialize("ipv4interface", user) == ipv4interface_string
        assert isinstance(field_compressed.serialize("ipv6interface", user), str)
        assert field_compressed.serialize("ipv6interface", user) == ipv6interface_string
        assert field_compressed.serialize("empty_ipinterface", user) is None

        field_exploded = fields.IPInterface(exploded=True)
        assert isinstance(field_exploded.serialize("ipv6interface", user), str)
        assert (
            field_exploded.serialize("ipv6interface", user)
            == ipv6interface_exploded_string
        )

    def test_ipv4_interface_field(self, user):
        ipv4interface_string = "192.168.0.1/24"

        user.ipv4interface = ipaddress.ip_interface(ipv4interface_string)
        user.empty_ipinterface = None

        field = fields.IPv4Interface()
        assert isinstance(field.serialize("ipv4interface", user), str)
        assert field.serialize("ipv4interface", user) == ipv4interface_string
        assert field.serialize("empty_ipinterface", user) is None

    def test_ipv6_interface_field(self, user):
        ipv6interface_string = "ffff::ffff/128"
        ipv6interface_exploded_string = ipaddress.ip_interface(
            "ffff::ffff/128"
        ).exploded

        user.ipv6interface = ipaddress.ip_interface(ipv6interface_string)
        user.empty_ipinterface = None

        field_compressed = fields.IPv6Interface()
        assert isinstance(field_compressed.serialize("ipv6interface", user), str)
        assert field_compressed.serialize("ipv6interface", user) == ipv6interface_string
        assert field_compressed.serialize("empty_ipinterface", user) is None

        field_exploded = fields.IPv6Interface(exploded=True)
        assert isinstance(field_exploded.serialize("ipv6interface", user), str)
        assert (
            field_exploded.serialize("ipv6interface", user)
            == ipv6interface_exploded_string
        )

    def test_enum_field_by_symbol_serialization(self, user):
        user.sex = GenderEnum.male
        field = fields.Enum(GenderEnum)
        assert field.serialize("sex", user) == "male"

    def test_enum_field_by_value_true_serialization(self, user):
        user.hair_color = HairColorEnum.black
        field = fields.Enum(HairColorEnum, by_value=True)
        assert field.serialize("hair_color", user) == "black hair"
        user.sex = GenderEnum.male
        field = fields.Enum(GenderEnum, by_value=True)
        assert field.serialize("sex", user) == 1
        user.some_date = DateEnum.date_1

    def test_enum_field_by_value_field_serialization(self, user):
        user.hair_color = HairColorEnum.black
        field = fields.Enum(HairColorEnum, by_value=fields.String)
        assert field.serialize("hair_color", user) == "black hair"
        user.sex = GenderEnum.male
        field = fields.Enum(GenderEnum, by_value=fields.Integer)
        assert field.serialize("sex", user) == 1
        user.some_date = DateEnum.date_1
        field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y"))
        assert field.serialize("some_date", user) == "29/02/2004"

    def test_decimal_field(self, user):
        user.m1 = 12
        user.m2 = "12.355"
        user.m3 = decimal.Decimal(1)
        user.m4 = None

        field = fields.Decimal()
        assert isinstance(field.serialize("m1", user), decimal.Decimal)
        assert field.serialize("m1", user) == decimal.Decimal(12)
        assert isinstance(field.serialize("m2", user), decimal.Decimal)
        assert field.serialize("m2", user) == decimal.Decimal("12.355")
        assert isinstance(field.serialize("m3", user), decimal.Decimal)
        assert field.serialize("m3", user) == decimal.Decimal(1)
        assert field.serialize("m4", user) is None

        field = fields.Decimal(1)
        assert isinstance(field.serialize("m1", user), decimal.Decimal)
        assert field.serialize("m1", user) == decimal.Decimal(12)
        assert isinstance(field.serialize("m2", user), decimal.Decimal)
        assert field.serialize("m2", user) == decimal.Decimal("12.4")
        assert isinstance(field.serialize("m3", user), decimal.Decimal)
        assert field.serialize("m3", user) == decimal.Decimal(1)
        assert field.serialize("m4", user) is None

        field = fields.Decimal(1, decimal.ROUND_DOWN)
        assert isinstance(field.serialize("m1", user), decimal.Decimal)
        assert field.serialize("m1", user) == decimal.Decimal(12)
        assert isinstance(field.serialize("m2", user), decimal.Decimal)
        assert field.serialize("m2", user) == decimal.Decimal("12.3")
        assert isinstance(field.serialize("m3", user), decimal.Decimal)
        assert field.serialize("m3", user) == decimal.Decimal(1)
        assert field.serialize("m4", user) is None

    def test_decimal_field_string(self, user):
        user.m1 = 12
        user.m2 = "12.355"
        user.m3 = decimal.Decimal(1)
        user.m4 = None

        field = fields.Decimal(as_string=True)
        assert isinstance(field.serialize("m1", user), str)
        assert field.serialize("m1", user) == "12"
        assert isinstance(field.serialize("m2", user), str)
        assert field.serialize("m2", user) == "12.355"
        assert isinstance(field.serialize("m3", user), str)
        assert field.serialize("m3", user) == "1"
        assert field.serialize("m4", user) is None

        field = fields.Decimal(1, as_string=True)
        assert isinstance(field.serialize("m1", user), str)
        assert field.serialize("m1", user) == "12.0"
        assert isinstance(field.serialize("m2", user), str)
        assert field.serialize("m2", user) == "12.4"
        assert isinstance(field.serialize("m3", user), str)
        assert field.serialize("m3", user) == "1.0"
        assert field.serialize("m4", user) is None

        field = fields.Decimal(1, decimal.ROUND_DOWN, as_string=True)
        assert isinstance(field.serialize("m1", user), str)
        assert field.serialize("m1", user) == "12.0"
        assert isinstance(field.serialize("m2", user), str)
        assert field.serialize("m2", user) == "12.3"
        assert isinstance(field.serialize("m3", user), str)
        assert field.serialize("m3", user) == "1.0"
        assert field.serialize("m4", user) is None

    def test_decimal_field_special_values(self, user):
        user.m1 = "-NaN"
        user.m2 = "NaN"
        user.m3 = "-sNaN"
        user.m4 = "sNaN"
        user.m5 = "-Infinity"
        user.m6 = "Infinity"
        user.m7 = "-0"

        field = fields.Decimal(places=2, allow_nan=True)

        m1s = field.serialize("m1", user)
        assert isinstance(m1s, decimal.Decimal)
        assert m1s.is_qnan() and not m1s.is_signed()

        m2s = field.serialize("m2", user)
        assert isinstance(m2s, decimal.Decimal)
        assert m2s.is_qnan() and not m2s.is_signed()

        m3s = field.serialize("m3", user)
        assert isinstance(m3s, decimal.Decimal)
        assert m3s.is_qnan() and not m3s.is_signed()

        m4s = field.serialize("m4", user)
        assert isinstance(m4s, decimal.Decimal)
        assert m4s.is_qnan() and not m4s.is_signed()

        m5s = field.serialize("m5", user)
        assert isinstance(m5s, decimal.Decimal)
        assert m5s.is_infinite() and m5s.is_signed()

        m6s = field.serialize("m6", user)
        assert isinstance(m6s, decimal.Decimal)
        assert m6s.is_infinite() and not m6s.is_signed()

        m7s = field.serialize("m7", user)
        assert isinstance(m7s, decimal.Decimal)
        assert m7s.is_zero() and m7s.is_signed()

        field = fields.Decimal(as_string=True, allow_nan=True)

        m2s = field.serialize("m2", user)
        assert isinstance(m2s, str)
        assert m2s == user.m2

        m5s = field.serialize("m5", user)
        assert isinstance(m5s, str)
        assert m5s == user.m5

        m6s = field.serialize("m6", user)
        assert isinstance(m6s, str)
        assert m6s == user.m6

    def test_decimal_field_special_values_not_permitted(self, user):
        user.m7 = "-0"

        field = fields.Decimal(places=2)

        m7s = field.serialize("m7", user)
        assert isinstance(m7s, decimal.Decimal)
        assert m7s.is_zero() and m7s.is_signed()

    def test_decimal_field_fixed_point_representation(self, user):
        """
        Test we get fixed-point string representation for a Decimal number that would normally
        output in engineering notation.
        """
        user.m1 = "0.00000000100000000"

        field = fields.Decimal()
        s = field.serialize("m1", user)
        assert isinstance(s, decimal.Decimal)
        assert s == decimal.Decimal("1.00000000E-9")

        field = fields.Decimal(as_string=True)
        s = field.serialize("m1", user)
        assert isinstance(s, str)
        assert s == user.m1

        field = fields.Decimal(as_string=True, places=2)
        s = field.serialize("m1", user)
        assert isinstance(s, str)
        assert s == "0.00"

    def test_boolean_field_serialization(self, user):
        field = fields.Boolean()

        user.truthy = "non-falsy-ish"
        user.falsy = "false"
        user.none = None

        assert field.serialize("truthy", user) is True
        assert field.serialize("falsy", user) is False
        assert field.serialize("none", user) is None

    def test_email_field_serialize_none(self, user):
        user.email = None
        field = fields.Email()
        assert field.serialize("email", user) is None

    def test_dict_field_serialize_none(self, user):
        user.various_data = None
        field = fields.Dict()
        assert field.serialize("various_data", user) is None

    def test_dict_field_serialize(self, user):
        user.various_data = {"foo": "bar"}
        field = fields.Dict()
        dump = field.serialize("various_data", user)
        assert dump == {"foo": "bar"}
        # Check dump is a distinct object
        dump["foo"] = "baz"
        assert user.various_data["foo"] == "bar"

    def test_dict_field_serialize_ordereddict(self, user):
        user.various_data = OrderedDict([("foo", "bar"), ("bar", "baz")])
        field = fields.Dict()
        assert field.serialize("various_data", user) == OrderedDict(
            [("foo", "bar"), ("bar", "baz")]
        )

    def test_structured_dict_value_serialize(self, user):
        user.various_data = {"foo": decimal.Decimal("1")}
        field = fields.Dict(values=fields.Decimal)
        assert field.serialize("various_data", user) == {"foo": 1}

    def test_structured_dict_key_serialize(self, user):
        user.various_data = {1: "bar"}
        field = fields.Dict(keys=fields.Str)
        assert field.serialize("various_data", user) == {"1": "bar"}

    def test_structured_dict_key_value_serialize(self, user):
        user.various_data = {1: decimal.Decimal("1")}
        field = fields.Dict(keys=fields.Str, values=fields.Decimal)
        assert field.serialize("various_data", user) == {"1": 1}

    def test_url_field_serialize_none(self, user):
        user.homepage = None
        field = fields.Url()
        assert field.serialize("homepage", user) is None

    def test_method_field_with_method_missing(self):
        class BadSerializer(Schema):
            bad_field = fields.Method("invalid")

        with pytest.raises(AttributeError):
            BadSerializer()

    def test_method_field_passed_serialize_only_is_dump_only(self, user):
        field = fields.Method(serialize="method")
        assert field.dump_only is True
        assert field.load_only is False

    def test_method_field_passed_deserialize_only_is_load_only(self):
        field = fields.Method(deserialize="somemethod")
        assert field.load_only is True
        assert field.dump_only is False

    def test_method_field_with_uncallable_attribute(self):
        class BadSerializer(Schema):
            foo = "not callable"
            bad_field = fields.Method("foo")

        with pytest.raises(TypeError):
            BadSerializer()

    # https://github.com/marshmallow-code/marshmallow/issues/395
    def test_method_field_does_not_swallow_attribute_error(self):
        class MySchema(Schema):
            mfield = fields.Method("raise_error")

            def raise_error(self, obj):
                raise AttributeError()

        with pytest.raises(AttributeError):
            MySchema().dump({})

    def test_method_with_no_serialize_is_missing(self):
        m = fields.Method()
        m.parent = Schema()

        assert m.serialize("", "", "") is missing_

    def test_serialize_with_data_key_param(self):
        class DumpToSchema(Schema):
            name = fields.String(data_key="NamE")
            years = fields.Integer(data_key="YearS")

        data = {"name": "Richard", "years": 11}
        result = DumpToSchema().dump(data)
        assert result == {"NamE": "Richard", "YearS": 11}

    def test_serialize_with_data_key_as_empty_string(self):
        class MySchema(Schema):
            name = fields.Field(data_key="")

        schema = MySchema()
        assert schema.dump({"name": "Grace"}) == {"": "Grace"}

    def test_serialize_with_attribute_and_data_key_uses_data_key(self):
        class ConfusedDumpToAndAttributeSerializer(Schema):
            name = fields.String(data_key="FullName")
            username = fields.String(attribute="uname", data_key="UserName")
            years = fields.Integer(attribute="le_wild_age", data_key="Years")

        data = {"name": "Mick", "uname": "mick_the_awesome", "le_wild_age": 999}
        result = ConfusedDumpToAndAttributeSerializer().dump(data)

        assert result == {
            "FullName": "Mick",
            "UserName": "mick_the_awesome",
            "Years": 999,
        }

    @pytest.mark.parametrize("fmt", ["rfc", "rfc822"])
    @pytest.mark.parametrize(
        ("value", "expected"),
        [
            (dt.datetime(2013, 11, 10, 1, 23, 45), "Sun, 10 Nov 2013 01:23:45 -0000"),
            (
                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
                "Sun, 10 Nov 2013 01:23:45 +0000",
            ),
            (
                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central),
                "Sun, 10 Nov 2013 01:23:45 -0600",
            ),
        ],
    )
    def test_datetime_field_rfc822(self, fmt, value, expected):
        field = fields.DateTime(format=fmt)
        assert field.serialize("d", {"d": value}) == expected

    @pytest.mark.parametrize(
        ("fmt", "value", "expected"),
        [
            ("timestamp", dt.datetime(1970, 1, 1), 0),
            ("timestamp", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025),
            (
                "timestamp",
                dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc),
                1384043025,
            ),
            (
                "timestamp",
                dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=central),
                1384064625,
            ),
            ("timestamp_ms", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025000),
            (
                "timestamp_ms",
                dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc),
                1384043025000,
            ),
            (
                "timestamp_ms",
                dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=central),
                1384064625000,
            ),
        ],
    )
    def test_datetime_field_timestamp(self, fmt, value, expected):
        field = fields.DateTime(format=fmt)
        assert field.serialize("d", {"d": value}) == expected

    @pytest.mark.parametrize("fmt", ["iso", "iso8601", None])
    @pytest.mark.parametrize(
        ("value", "expected"),
        [
            (dt.datetime(2013, 11, 10, 1, 23, 45), "2013-11-10T01:23:45"),
            (
                dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc),
                "2013-11-10T01:23:45.123456+00:00",
            ),
            (
                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
                "2013-11-10T01:23:45+00:00",
            ),
            (
                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central),
                "2013-11-10T01:23:45-06:00",
            ),
        ],
    )
    def test_datetime_field_iso8601(self, fmt, value, expected):
        if fmt is None:
            # Test default is ISO
            field = fields.DateTime()
        else:
            field = fields.DateTime(format=fmt)
        assert field.serialize("d", {"d": value}) == expected

    def test_datetime_field_format(self, user):
        format = "%Y-%m-%d"
        field = fields.DateTime(format=format)
        assert field.serialize("created", user) == user.created.strftime(format)

    def test_string_field(self):
        field = fields.String()
        user = User(name=b"foo")
        assert field.serialize("name", user) == "foo"
        field = fields.String(allow_none=True)
        user.name = None
        assert field.serialize("name", user) is None

    def test_string_field_default_to_empty_string(self, user):
        field = fields.String(dump_default="")
        assert field.serialize("notfound", {}) == ""

    def test_time_field(self, user):
        field = fields.Time()
        expected = user.time_registered.isoformat()[:15]
        assert field.serialize("time_registered", user) == expected

        user.time_registered = None
        assert field.serialize("time_registered", user) is None

    @pytest.mark.parametrize("fmt", ["iso", "iso8601", None])
    @pytest.mark.parametrize(
        ("value", "expected"),
        [
            (dt.time(1, 23, 45), "01:23:45"),
            (dt.time(1, 23, 45, 123000), "01:23:45.123000"),
            (dt.time(1, 23, 45, 123456), "01:23:45.123456"),
        ],
    )
    def test_time_field_iso8601(self, fmt, value, expected):
        if fmt is None:
            # Test default is ISO
            field = fields.Time()
        else:
            field = fields.Time(format=fmt)
        assert field.serialize("d", {"d": value}) == expected

    def test_time_field_format(self, user):
        fmt = "%H:%M:%S"
        field = fields.Time(format=fmt)
        assert field.serialize("birthtime", user) == user.birthtime.strftime(fmt)

    def test_date_field(self, user):
        field = fields.Date()
        assert field.serialize("birthdate", user) == user.birthdate.isoformat()

        user.birthdate = None
        assert field.serialize("birthdate", user) is None

    def test_timedelta_field(self, user):
        user.d1 = dt.timedelta(days=1, seconds=1, microseconds=1)
        user.d2 = dt.timedelta(days=0, seconds=86401, microseconds=1)
        user.d3 = dt.timedelta(days=0, seconds=0, microseconds=86401000001)
        user.d4 = dt.timedelta(days=0, seconds=0, microseconds=0)
        user.d5 = dt.timedelta(days=-1, seconds=0, microseconds=0)
        user.d6 = dt.timedelta(
            days=1,
            seconds=1,
            microseconds=1,
            milliseconds=1,
            minutes=1,
            hours=1,
            weeks=1,
        )

        field = fields.TimeDelta(fields.TimeDelta.DAYS)
        assert field.serialize("d1", user) == 1
        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
        assert field.serialize("d1", user) == 86401
        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
        assert field.serialize("d1", user) == 86401000001
        field = fields.TimeDelta(fields.TimeDelta.HOURS)
        assert field.serialize("d1", user) == 24

        field = fields.TimeDelta(fields.TimeDelta.DAYS)
        assert field.serialize("d2", user) == 1
        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
        assert field.serialize("d2", user) == 86401
        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
        assert field.serialize("d2", user) == 86401000001

        field = fields.TimeDelta(fields.TimeDelta.DAYS)
        assert field.serialize("d3", user) == 1
        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
        assert field.serialize("d3", user) == 86401
        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
        assert field.serialize("d3", user) == 86401000001

        field = fields.TimeDelta(fields.TimeDelta.DAYS)
        assert field.serialize("d4", user) == 0
        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
        assert field.serialize("d4", user) == 0
        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
        assert field.serialize("d4", user) == 0

        field = fields.TimeDelta(fields.TimeDelta.DAYS)
        assert field.serialize("d5", user) == -1
        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
        assert field.serialize("d5", user) == -86400
        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
        assert field.serialize("d5", user) == -86400000000

        field = fields.TimeDelta(fields.TimeDelta.WEEKS)
        assert field.serialize("d6", user) == 1
        field = fields.TimeDelta(fields.TimeDelta.DAYS)
        assert field.serialize("d6", user) == 7 + 1
        field = fields.TimeDelta(fields.TimeDelta.HOURS)
        assert field.serialize("d6", user) == 7 * 24 + 24 + 1
        field = fields.TimeDelta(fields.TimeDelta.MINUTES)
        assert field.serialize("d6", user) == 7 * 24 * 60 + 24 * 60 + 60 + 1
        d6_seconds = (
            7 * 24 * 60 * 60
            + 24 * 60 * 60  # 1 week
            + 60 * 60  # 1 day
            + 60  # 1 hour
            + 1  # 1 minute
        )
        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
        assert field.serialize("d6", user) == d6_seconds
        field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS)
        assert field.serialize("d6", user) == d6_seconds * 1000 + 1
        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS)
        assert field.serialize("d6", user) == d6_seconds * 10**6 + 1000 + 1

        user.d7 = None
        assert field.serialize("d7", user) is None

        # https://github.com/marshmallow-code/marshmallow/issues/1856
        user.d8 = dt.timedelta(milliseconds=345)
        field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS)
        assert field.serialize("d8", user) == 345

        user.d9 = dt.timedelta(milliseconds=1999)
        field = fields.TimeDelta(fields.TimeDelta.SECONDS)
        assert field.serialize("d9", user) == 1

        user.d10 = dt.timedelta(
            weeks=1,
            days=6,
            hours=2,
            minutes=5,
            seconds=51,
            milliseconds=10,
            microseconds=742,
        )

        field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS, float)
        unit_value = dt.timedelta(microseconds=1).total_seconds()
        assert math.isclose(
            field.serialize("d10", user), user.d10.total_seconds() / unit_value
        )

        field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS, float)
        unit_value = dt.timedelta(milliseconds=1).total_seconds()
        assert math.isclose(
            field.serialize("d10", user), user.d10.total_seconds() / unit_value
        )

        field = fields.TimeDelta(fields.TimeDelta.SECONDS, float)
        assert math.isclose(field.serialize("d10", user), user.d10.total_seconds())

        field = fields.TimeDelta(fields.TimeDelta.MINUTES, float)
        unit_value = dt.timedelta(minutes=1).total_seconds()
        assert math.isclose(
            field.serialize("d10", user), user.d10.total_seconds() / unit_value
        )

        field = fields.TimeDelta(fields.TimeDelta.HOURS, float)
        unit_value = dt.timedelta(hours=1).total_seconds()
        assert math.isclose(
            field.serialize("d10", user), user.d10.total_seconds() / unit_value
        )

        field = fields.TimeDelta(fields.TimeDelta.DAYS, float)
        unit_value = dt.timedelta(days=1).total_seconds()
        assert math.isclose(
            field.serialize("d10", user), user.d10.total_seconds() / unit_value
        )

        field = fields.TimeDelta(fields.TimeDelta.WEEKS, float)
        unit_value = dt.timedelta(weeks=1).total_seconds()
        assert math.isclose(
            field.serialize("d10", user), user.d10.total_seconds() / unit_value
        )

        with pytest.raises(ValueError):
            fields.TimeDelta(fields.TimeDelta.SECONDS, str)

    def test_datetime_list_field(self):
        obj = DateTimeList([dt.datetime.now(dt.timezone.utc), dt.datetime.now()])
        field = fields.List(fields.DateTime)
        result = field.serialize("dtimes", obj)
        assert all(type(each) is str for each in result)

    def test_list_field_serialize_none_returns_none(self):
        obj = DateTimeList(None)
        field = fields.List(fields.DateTime)
        assert field.serialize("dtimes", obj) is None

    def test_list_field_work_with_generator_single_value(self):
        def custom_generator():
            yield dt.datetime.now(dt.timezone.utc)

        obj = DateTimeList(custom_generator())
        field = fields.List(fields.DateTime)
        result = field.serialize("dtimes", obj)
        assert len(result) == 1

    def test_list_field_work_with_generators_multiple_values(self):
        def custom_generator():
            yield from [dt.datetime.now(dt.timezone.utc), dt.datetime.now()]

        obj = DateTimeList(custom_generator())
        field = fields.List(fields.DateTime)
        result = field.serialize("dtimes", obj)
        assert len(result) == 2

    def test_list_field_work_with_generators_empty_generator_returns_none_for_every_non_returning_yield_statement(  # noqa: B950
        self,
    ):
        def custom_generator():
            yield
            yield

        obj = DateTimeList(custom_generator())
        field = fields.List(fields.DateTime, allow_none=True)
        result = field.serialize("dtimes", obj)
        assert len(result) == 2
        assert result[0] is None
        assert result[1] is None

    def test_list_field_work_with_set(self):
        custom_set = {1, 2, 3}
        obj = IntegerList(custom_set)
        field = fields.List(fields.Int)
        result = field.serialize("ints", obj)
        assert len(result) == 3
        assert 1 in result
        assert 2 in result
        assert 3 in result

    def test_list_field_work_with_custom_class_with_iterator_protocol(self):
        class IteratorSupportingClass:
            def __init__(self, iterable):
                self.iterable = iterable

            def __iter__(self):
                return iter(self.iterable)

        ints = IteratorSupportingClass([1, 2, 3])
        obj = IntegerList(ints)
        field = fields.List(fields.Int)
        result = field.serialize("ints", obj)
        assert len(result) == 3
        assert result[0] == 1
        assert result[1] == 2
        assert result[2] == 3

    def test_bad_list_field(self):
        class ASchema(Schema):
            id = fields.Int()

        with pytest.raises(ValueError):
            fields.List("string")
        expected_msg = (
            "The list elements must be a subclass or instance of "
            "marshmallow.base.FieldABC"
        )
        with pytest.raises(ValueError, match=expected_msg):
            fields.List(ASchema)

    def test_datetime_integer_tuple_field(self):
        obj = DateTimeIntegerTuple((dt.datetime.now(dt.timezone.utc), 42))
        field = fields.Tuple([fields.DateTime, fields.Integer])
        result = field.serialize("dtime_int", obj)
        assert type(result[0]) is str
        assert type(result[1]) is int

    def test_tuple_field_serialize_none_returns_none(self):
        obj = DateTimeIntegerTuple(None)
        field = fields.Tuple([fields.DateTime, fields.Integer])
        assert field.serialize("dtime_int", obj) is None

    def test_bad_tuple_field(self):
        class ASchema(Schema):
            id = fields.Int()

        with pytest.raises(ValueError):
            fields.Tuple(["string"])
        with pytest.raises(ValueError):
            fields.Tuple(fields.String)
        expected_msg = (
            'Elements of "tuple_fields" must be subclasses or '
            "instances of marshmallow.base.FieldABC."
        )
        with pytest.raises(ValueError, match=expected_msg):
            fields.Tuple([ASchema])

    def test_serialize_does_not_apply_validators(self, user):
        field = fields.Field(validate=lambda x: False)
        # No validation error raised
        assert field.serialize("age", user) == user.age

    def test_constant_field_serialization(self, user):
        field = fields.Constant("something")
        assert field.serialize("whatever", user) == "something"

    def test_constant_is_always_included_in_serialized_data(self):
        class MySchema(Schema):
            foo = fields.Constant(42)

        sch = MySchema()
        assert sch.dump({"bar": 24})["foo"] == 42
        assert sch.dump({"foo": 24})["foo"] == 42

    def test_constant_field_serialize_when_omitted(self):
        class MiniUserSchema(Schema):
            name = fields.Constant("bill")

        s = MiniUserSchema()
        assert s.dump({})["name"] == "bill"

    @pytest.mark.parametrize("FieldClass", ALL_FIELDS)
    def test_all_fields_serialize_none_to_none(self, FieldClass):
        field = FieldClass(allow_none=True)
        res = field.serialize("foo", {"foo": None})
        assert res is None


class TestSchemaSerialization:
    def test_serialize_with_missing_param_value(self):
        class AliasingUserSerializer(Schema):
            name = fields.String()
            birthdate = fields.DateTime(dump_default=dt.datetime(2017, 9, 29))

        data = {"name": "Mick"}
        result = AliasingUserSerializer().dump(data)
        assert result["name"] == "Mick"
        assert result["birthdate"] == "2017-09-29T00:00:00"

    def test_serialize_with_missing_param_callable(self):
        class AliasingUserSerializer(Schema):
            name = fields.String()
            birthdate = fields.DateTime(dump_default=lambda: dt.datetime(2017, 9, 29))

        data = {"name": "Mick"}
        result = AliasingUserSerializer().dump(data)
        assert result["name"] == "Mick"
        assert result["birthdate"] == "2017-09-29T00:00:00"


def test_serializing_named_tuple():
    Point = namedtuple("Point", ["x", "y"])

    field = fields.Field()

    p = Point(x=4, y=2)

    assert field.serialize("x", p) == 4


def test_serializing_named_tuple_with_meta():
    Point = namedtuple("Point", ["x", "y"])
    p = Point(x=4, y=2)

    class PointSerializer(Schema):
        class Meta:
            fields = ("x", "y")

    serialized = PointSerializer().dump(p)
    assert serialized["x"] == 4
    assert serialized["y"] == 2


def test_serializing_slice():
    values = [{"value": value} for value in range(5)]
    slice = itertools.islice(values, None)

    class ValueSchema(Schema):
        value = fields.Int()

    serialized = ValueSchema(many=True).dump(slice)
    assert serialized == values


# https://github.com/marshmallow-code/marshmallow/issues/1163
def test_nested_field_many_serializing_generator():
    class MySchema(Schema):
        name = fields.Str()

    class OtherSchema(Schema):
        objects = fields.Nested(MySchema, many=True)

    def gen():
        yield {"name": "foo"}
        yield {"name": "bar"}

    obj = {"objects": gen()}
    data = OtherSchema().dump(obj)

    assert data.get("objects") == [{"name": "foo"}, {"name": "bar"}]
