import datetime import json from decimal import Decimal from enum import Enum from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network from uuid import UUID import pytest from pydantic import BaseModel, create_model from pydantic.json import pydantic_encoder, timedelta_isoformat from pydantic.types import SecretBytes, SecretStr class MyEnum(Enum): foo = 'bar' snap = 'crackle' @pytest.mark.parametrize( 'input,output', [ (UUID('ebcdab58-6eb8-46fb-a190-d07a33e9eac8'), '"ebcdab58-6eb8-46fb-a190-d07a33e9eac8"'), (IPv4Address('192.168.0.1'), '"192.168.0.1"'), (SecretStr('abcd'), '"**********"'), (SecretStr(''), '""'), (SecretBytes(b'xyz'), '"**********"'), (SecretBytes(b''), '""'), (IPv6Address('::1:0:1'), '"::1:0:1"'), (IPv4Interface('192.168.0.0/24'), '"192.168.0.0/24"'), (IPv6Interface('2001:db00::/120'), '"2001:db00::/120"'), (IPv4Network('192.168.0.0/24'), '"192.168.0.0/24"'), (IPv6Network('2001:db00::/120'), '"2001:db00::/120"'), (datetime.datetime(2032, 1, 1, 1, 1), '"2032-01-01T01:01:00"'), (datetime.datetime(2032, 1, 1, 1, 1, tzinfo=datetime.timezone.utc), '"2032-01-01T01:01:00+00:00"'), (datetime.datetime(2032, 1, 1), '"2032-01-01T00:00:00"'), (datetime.time(12, 34, 56), '"12:34:56"'), (datetime.timedelta(days=12, seconds=34, microseconds=56), '1036834.000056'), ({1, 2, 3}, '[1, 2, 3]'), (frozenset([1, 2, 3]), '[1, 2, 3]'), ((v for v in range(4)), '[0, 1, 2, 3]'), (b'this is bytes', '"this is bytes"'), (Decimal('12.34'), '12.34'), (create_model('BarModel', a='b', c='d')(), '{"a": "b", "c": "d"}'), (MyEnum.foo, '"bar"'), ], ) def test_encoding(input, output): assert output == json.dumps(input, default=pydantic_encoder) def test_model_encoding(): class ModelA(BaseModel): x: int y: str class Model(BaseModel): a: float b: bytes c: Decimal d: ModelA m = Model(a=10.2, b='foobar', c=10.2, d={'x': 123, 'y': '123'}) assert m.dict() == {'a': 10.2, 'b': b'foobar', 'c': Decimal('10.2'), 'd': {'x': 123, 'y': '123'}} assert m.json() == '{"a": 10.2, "b": "foobar", "c": 10.2, "d": {"x": 123, "y": "123"}}' assert m.json(exclude={'b'}) == '{"a": 10.2, "c": 10.2, "d": {"x": 123, "y": "123"}}' def test_invalid_model(): class Foo: pass with pytest.raises(TypeError): json.dumps(Foo, default=pydantic_encoder) @pytest.mark.parametrize( 'input,output', [ (datetime.timedelta(days=12, seconds=34, microseconds=56), 'P12DT0H0M34.000056S'), (datetime.timedelta(days=1001, hours=1, minutes=2, seconds=3, microseconds=654_321), 'P1001DT1H2M3.654321S'), ], ) def test_iso_timedelta(input, output): assert output == timedelta_isoformat(input) def test_custom_encoder(): class Model(BaseModel): x: datetime.timedelta y: Decimal z: datetime.date class Config: json_encoders = {datetime.timedelta: lambda v: f'{v.total_seconds():0.3f}s', Decimal: lambda v: 'a decimal'} assert Model(x=123, y=5, z='2032-06-01').json() == '{"x": "123.000s", "y": "a decimal", "z": "2032-06-01"}' def test_custom_iso_timedelta(): class Model(BaseModel): x: datetime.timedelta class Config: json_encoders = {datetime.timedelta: timedelta_isoformat} m = Model(x=123) assert m.json() == '{"x": "P0DT0H2M3.000000S"}' def test_custom_encoder_arg(): class Model(BaseModel): x: datetime.timedelta m = Model(x=123) assert m.json() == '{"x": 123.0}' assert m.json(encoder=lambda v: '__default__') == '{"x": "__default__"}'