Field Types

Where possible pydantic uses standard library types to define fields, thus smoothing the learning curve. For many useful applications, however, no standard library type exists, so pydantic implements many commonly used types.

If no existing type suits your purpose you can also implement your own pydantic-compatible types with custom properties and validation.

Standard Library TypesπŸ”—

pydantic supports many common types from the python standard library. If you need stricter processing see Strict Types; if you need to constrain the values allowed (e.g. to require a positive int) see Constrained Types.

bool
see Booleans below for details on how bools are validated and what values are permitted
int
pydantic uses int(v) to coerce types to an int; see this warning on loss of information during data conversion
float
similarly, float(v) is used to coerce values to floats
str
strings are accepted as-is, int float and Decimal are coerced using str(v), bytes and bytearray are converted using v.decode(), enums inheriting from str are converted using v.value, and all other types cause an error
bytes
bytes are accepted as-is, bytearray is converted using bytes(v), str are converted using v.encode(), and int, float, and Decimal are coerced using str(v).encode()
list
allows list, tuple, set, frozenset, or generators and casts to a list; see typing.List below for sub-type constraints
tuple
allows list, tuple, set, frozenset, or generators and casts to a tuple; see typing.Tuple below for sub-type constraints
dict
dict(v) is used to attempt to convert a dictionary; see typing.Dict below for sub-type constraints
set
allows list, tuple, set, frozenset, or generators and casts to a set; see typing.Set below for sub-type constraints
frozonset
allows list, tuple, set, frozenset, or generators and casts to a frozen set; see typing.FrozenSet below for sub-type constraints
datetime.date
see Datetime Types below for more detail on parsing and validation
datetime.time
see Datetime Types below for more detail on parsing and validation
datetime.datetime
see Datetime Types below for more detail on parsing and validation
datetime.timedelta
see Datetime Types below for more detail on parsing and validation
typing.Any
allows any value include None, thus an Any field is optional
typing.TypeVar
constrains the values allowed based on constraints or bound, see TypeVar
typing.Union
see Unions below for more detail on parsing and validation
typing.Optional
Optional[x] is simply short hand for Union[x, None]; see Unions below for more detail on parsing and validation
typing.List
see Typing Iterables below for more detail on parsing and validation
typing.Tuple
see Typing Iterables below for more detail on parsing and validation
typing.Dict
see Typing Iterables below for more detail on parsing and validation
typing.Set
see Typing Iterables below for more detail on parsing and validation
typing.FrozenSet
see Typing Iterables below for more detail on parsing and validation
typing.Sequence
see Typing Iterables below for more detail on parsing and validation
typing.Type
see Type below for more detail on parsing and validation
typing.Callable
see Callable below for more detail on parsing and validation
typing.Pattern
will cause the input value to be passed to re.compile(v) to create a regex pattern
ipaddress.IPv4Address
simply uses the type itself for validation by passing the value to IPv4Address(v); see Pydantic Types for other custom IP address types
ipaddress.IPv4Interface
simply uses the type itself for validation by passing the value to IPv4Address(v); see Pydantic Types for other custom IP address types
ipaddress.IPv4Network
simply uses the type itself for validation by passing the value to IPv4Network(v); see Pydantic Types for other custom IP address types
ipaddress.IPv6Address
simply uses the type itself for validation by passing the value to IPv6Address(v); see Pydantic Types for other custom IP address types
ipaddress.IPv6Interface
simply uses the type itself for validation by passing the value to IPv6Interface(v); see Pydantic Types for other custom IP address types
ipaddress.IPv6Network
simply uses the type itself for validation by passing the value to IPv6Network(v); see Pydantic Types for other custom IP address types
enum.Enum
checks that the value is a valid member of the enum; see Enums and Choices for more details
enum.IntEnum
checks that the value is a valid member of the integer enum; see Enums and Choices for more details
decimal.Decimal
pydantic attempts to convert the value to a string, then passes the string to Decimal(v)
pathlib.Path
simply uses the type itself for validation by passing the value to Path(v); see Pydantic Types for other more strict path types
uuid.UUID
strings and bytes (converted to strings) are passed to UUID(v); see Pydantic Types for other stricter UUID types
ByteSize
converts a bytes string with units to bytes

Typing IterablesπŸ”—

pydantic uses standard library typing types as defined in PEP 484 to define complex objects.

from typing import Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Union

from pydantic import BaseModel

class Model(BaseModel):
    simple_list: list = None
    list_of_ints: List[int] = None

    simple_tuple: tuple = None
    tuple_of_different_types: Tuple[int, float, str, bool] = None

    simple_dict: dict = None
    dict_str_float: Dict[str, float] = None

    simple_set: set = None
    set_bytes: Set[bytes] = None
    frozen_set: FrozenSet[int] = None

    str_or_bytes: Union[str, bytes] = None
    none_or_str: Optional[str] = None

    sequence_of_ints: Sequence[int] = None

    compound: Dict[Union[str, bytes], List[Set[int]]] = None

print(Model(simple_list=['1', '2', '3']).simple_list)
#> ['1', '2', '3']
print(Model(list_of_ints=['1', '2', '3']).list_of_ints)
#> [1, 2, 3]

print(Model(simple_dict={'a': 1, b'b': 2}).simple_dict)
#> {'a': 1, b'b': 2}
print(Model(dict_str_float={'a': 1, b'b': 2}).dict_str_float)
#> {'a': 1.0, 'b': 2.0}

print(Model(simple_tuple=[1, 2, 3, 4]).simple_tuple)
#> (1, 2, 3, 4)
print(Model(tuple_of_different_types=[4, 3, 2, 1]).tuple_of_different_types)
#> (4, 3.0, '2', True)

print(Model(sequence_of_ints=[1, 2, 3, 4]).sequence_of_ints)
#> [1, 2, 3, 4]
print(Model(sequence_of_ints=(1, 2, 3, 4)).sequence_of_ints)
#> (1, 2, 3, 4)

(This script is complete, it should run "as is")

UnionsπŸ”—

The Union type allows a model attribute to accept different types, e.g.:

Warning

This script is complete, it should run "as is". However, it may not reflect the desired behavior; see below.

from uuid import UUID
from typing import Union
from pydantic import BaseModel

class User(BaseModel):
    id: Union[int, str, UUID]
    name: str

user_01 = User(id=123, name='John Doe')
print(user_01)
#> id=123 name='John Doe'
print(user_01.id)
#> 123
user_02 = User(id='1234', name='John Doe')
print(user_02)
#> id=1234 name='John Doe'
print(user_02.id)
#> 1234
user_03_uuid = UUID('cf57432e-809e-4353-adbd-9d5c0d733868')
user_03 = User(id=user_03_uuid, name='John Doe')
print(user_03)
#> id=275603287559914445491632874575877060712 name='John Doe'
print(user_03.id)
#> 275603287559914445491632874575877060712
print(user_03_uuid.int)
#> 275603287559914445491632874575877060712

However, as can be seen above, pydantic will attempt to 'match' any of the types defined under Union and will use the first one that matches. In the above example the id of user_03 was defined as a uuid.UUID class (which is defined under the attribute's Union annotation) but as the uuid.UUID can be marshalled into an int it chose to match against the int type and disregarded the other types.

As such, it is recommended that, when defining Union annotations, the most specific type is included first and followed by less specific types. In the above example, the UUID class should precede the int and str classes to preclude the unexpected representation as such:

from uuid import UUID
from typing import Union
from pydantic import BaseModel

class User(BaseModel):
    id: Union[UUID, int, str]
    name: str

user_03_uuid = UUID('cf57432e-809e-4353-adbd-9d5c0d733868')
user_03 = User(id=user_03_uuid, name='John Doe')
print(user_03)
#> id=UUID('cf57432e-809e-4353-adbd-9d5c0d733868') name='John Doe'
print(user_03.id)
#> cf57432e-809e-4353-adbd-9d5c0d733868
print(user_03_uuid.int)
#> 275603287559914445491632874575877060712

(This script is complete, it should run "as is")

Enums and ChoicesπŸ”—

pydantic uses python's standard enum classes to define choices.

from enum import Enum, IntEnum

from pydantic import BaseModel, ValidationError

class FruitEnum(str, Enum):
    pear = 'pear'
    banana = 'banana'

class ToolEnum(IntEnum):
    spanner = 1
    wrench = 2

class CookingModel(BaseModel):
    fruit: FruitEnum = FruitEnum.pear
    tool: ToolEnum = ToolEnum.spanner

print(CookingModel())
#> fruit=<FruitEnum.pear: 'pear'> tool=<ToolEnum.spanner: 1>
print(CookingModel(tool=2, fruit='banana'))
#> fruit=<FruitEnum.banana: 'banana'> tool=<ToolEnum.wrench: 2>
try:
    CookingModel(fruit='other')
except ValidationError as e:
    print(e)
"""
1 validation error for CookingModel
fruit
  value is not a valid enumeration member; permitted: 'pear', 'banana'
(type=type_error.enum; enum_values=[<FruitEnum.pear: 'pear'>,
<FruitEnum.banana: 'banana'>])
"""

(This script is complete, it should run "as is")

Datetime TypesπŸ”—

Pydantic supports the following datetime types:

  • datetime fields can be:

    • datetime, existing datetime object
    • int or float, assumed as Unix time, i.e. seconds (if <= 2e10) or milliseconds (if > 2e10) since 1 January 1970
    • str, following formats work:

      • YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z[Β±]HH[:]MM]]]
      • int or float as a string (assumed as Unix time)
  • date fields can be:

    • date, existing date object
    • int or float, see datetime
    • str, following formats work:

      • YYYY-MM-DD
      • int or float, see datetime
  • time fields can be:

    • time, existing time object
    • str, following formats work:

      • HH:MM[:SS[.ffffff]]
  • timedelta fields can be:

    • timedelta, existing timedelta object
    • int or float, assumed as seconds
    • str, following formats work:

      • [-][DD ][HH:MM]SS[.ffffff]
      • [Β±]P[DD]DT[HH]H[MM]M[SS]S (ISO 8601 format for timedelta)
from datetime import date, datetime, time, timedelta
from pydantic import BaseModel

class Model(BaseModel):
    d: date = None
    dt: datetime = None
    t: time = None
    td: timedelta = None

m = Model(
    d=1966280412345.6789,
    dt='2032-04-23T10:20:30.400+02:30',
    t=time(4, 8, 16),
    td='P3DT12H30M5S'
)

print(m.dict())
"""
{
    'd': datetime.date(2032, 4, 22),
    'dt': datetime.datetime(2032, 4, 23, 10, 20, 30, 400000,
tzinfo=datetime.timezone(datetime.timedelta(seconds=9000))),
    't': datetime.time(4, 8, 16),
    'td': datetime.timedelta(days=3, seconds=45005),
}
"""

BooleansπŸ”—

Warning

The logic for parsing bool fields has changed as of version v1.0.

Prior to v1.0, bool parsing never failed, leading to some unexpected results. The new logic is described below.

A standard bool field will raise a ValidationError if the value is not one of the following:

  • A valid boolean (i.e. True or False),
  • The integers 0 or 1,
  • a str which when converted to lower case is one of '0', 'off', 'f', 'false', 'n', 'no', '1', 'on', 't', 'true', 'y', 'yes'
  • a bytes which is valid (per the previous rule) when decoded to str

Note

If you want stricter boolean logic (e.g. a field which only permits True and False) you can use StrictBool.

Here is a script demonstrating some of these behaviors:

from pydantic import BaseModel, ValidationError

class BooleanModel(BaseModel):
    bool_value: bool

print(BooleanModel(bool_value=False))
#> bool_value=False
print(BooleanModel(bool_value='False'))
#> bool_value=False
try:
    BooleanModel(bool_value=[])
except ValidationError as e:
    print(str(e))
"""
1 validation error for BooleanModel
bool_value
  value could not be parsed to a boolean (type=type_error.bool)
"""

(This script is complete, it should run "as is")

CallableπŸ”—

Fields can also be of type Callable:

from typing import Callable
from pydantic import BaseModel

class Foo(BaseModel):
    callback: Callable[[int], int]

m = Foo(callback=lambda x: x)
print(m)
#> callback=<function <lambda> at 0x7f16de7b2a60>

(This script is complete, it should run "as is")

Warning

Callable fields only perform a simple check that the argument is callable; no validation of arguments, their types, or the return type is performed.

TypeπŸ”—

pydantic supports the use of Type[T] to specify that a field may only accept classes (not instances) that are subclasses of T.

from typing import Type

from pydantic import BaseModel
from pydantic import ValidationError

class Foo:
    pass

class Bar(Foo):
    pass

class Other:
    pass

class SimpleModel(BaseModel):
    just_subclasses: Type[Foo]

SimpleModel(just_subclasses=Foo)
SimpleModel(just_subclasses=Bar)
try:
    SimpleModel(just_subclasses=Other)
except ValidationError as e:
    print(e)
"""
1 validation error for SimpleModel
just_subclasses
  subclass of Foo expected (type=type_error.subclass; expected_class=Foo)
"""

(This script is complete, it should run "as is")

You may also use Type to specify that any class is allowed.

from typing import Type

from pydantic import BaseModel, ValidationError

class Foo:
    pass

class LenientSimpleModel(BaseModel):
    any_class_goes: Type

LenientSimpleModel(any_class_goes=int)
LenientSimpleModel(any_class_goes=Foo)
try:
    LenientSimpleModel(any_class_goes=Foo())
except ValidationError as e:
    print(e)
"""
1 validation error for LenientSimpleModel
any_class_goes
  a class is expected (type=type_error.class)
"""

(This script is complete, it should run "as is")

TypeVarπŸ”—

TypeVar is supported either unconstrained, constrained or with a bound.

from typing import TypeVar
from pydantic import BaseModel

Foobar = TypeVar('Foobar')
BoundFloat = TypeVar('BoundFloat', bound=float)
IntStr = TypeVar('IntStr', int, str)

class Model(BaseModel):
    a: Foobar  # equivalent of ": Any"
    b: BoundFloat  # equivalent of ": float"
    c: IntStr  # equivalent of ": Union[int, str]"

print(Model(a=[1], b=4.2, c='x'))
#> a=[1] b=4.2 c='x'

# a may be None and is therefore optional
print(Model(b=1, c=1))
#> a=None b=1.0 c=1

(This script is complete, it should run "as is")

Literal TypeπŸ”—

Note

This is not strictly part of the python standard library; it requires the typing-extensions package.

pydantic supports the use of typing_extensions.Literal as a lightweight way to specify that a field may accept only specific literal values:

from typing_extensions import Literal

from pydantic import BaseModel, ValidationError

class Pie(BaseModel):
    flavor: Literal['apple', 'pumpkin']

Pie(flavor='apple')
Pie(flavor='pumpkin')
try:
    Pie(flavor='cherry')
except ValidationError as e:
    print(str(e))
"""
1 validation error for Pie
flavor
  unexpected value; permitted: 'apple', 'pumpkin' (type=value_error.const;
given=cherry; permitted=('apple', 'pumpkin'))
"""

(This script is complete, it should run "as is")

One benefit of this field type is that it can be used to check for equality with one or more specific values without needing to declare custom validators:

from typing import ClassVar, List, Union

from typing_extensions import Literal

from pydantic import BaseModel, ValidationError

class Cake(BaseModel):
    kind: Literal['cake']
    required_utensils: ClassVar[List[str]] = ['fork', 'knife']

class IceCream(BaseModel):
    kind: Literal['icecream']
    required_utensils: ClassVar[List[str]] = ['spoon']

class Meal(BaseModel):
    dessert: Union[Cake, IceCream]

print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Cake
print(type(Meal(dessert={'kind': 'icecream'}).dessert).__name__)
#> IceCream
try:
    Meal(dessert={'kind': 'pie'})
except ValidationError as e:
    print(str(e))
"""
2 validation errors for Meal
dessert -> kind
  unexpected value; permitted: 'cake' (type=value_error.const; given=pie;
permitted=('cake',))
dessert -> kind
  unexpected value; permitted: 'icecream' (type=value_error.const; given=pie;
permitted=('icecream',))
"""

(This script is complete, it should run "as is")

With proper ordering in an annotated Union, you can use this to parse types of decreasing specificity:

from typing import Optional, Union

from typing_extensions import Literal

from pydantic import BaseModel

class Dessert(BaseModel):
    kind: str

class Pie(Dessert):
    kind: Literal['pie']
    flavor: Optional[str]

class ApplePie(Pie):
    flavor: Literal['apple']

class PumpkinPie(Pie):
    flavor: Literal['pumpkin']

class Meal(BaseModel):
    dessert: Union[ApplePie, PumpkinPie, Pie, Dessert]

print(type(Meal(dessert={'kind': 'pie', 'flavor': 'apple'}).dessert).__name__)
#> ApplePie
print(type(Meal(dessert={'kind': 'pie', 'flavor': 'pumpkin'}).dessert).__name__)
#> PumpkinPie
print(type(Meal(dessert={'kind': 'pie'}).dessert).__name__)
#> Pie
print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Dessert

(This script is complete, it should run "as is")

Pydantic TypesπŸ”—

pydantic also provides a variety of other useful types:

FilePath
like Path, but the path must exist and be a file
DirectoryPath
like Path, but the path must exist and be a directory
EmailStr
requires email-validator to be installed; the input string must be a valid email address, and the output is a simple string
NameEmail
requires email-validator to be installed; the input string must be either a valid email address or in the format Fred Bloggs <fred.bloggs@example.com>, and the output is a NameEmail object which has two properties: name and email. For Fred Bloggs <fred.bloggs@example.com> the name would be "Fred Bloggs"; for fred.bloggs@example.com it would be "fred.bloggs".
PyObject
expects a string and loads the python object importable at that dotted path; e.g. if 'math.cos' was provided, the resulting field value would be the function cos
Color
for parsing HTML and CSS colors; see Color Type
Json
a special type wrapper which loads JSON before parsing; see JSON Type
PaymentCardNumber
for parsing and validating payment cards; see payment cards
AnyUrl
any URL; see URLs
AnyHttpUrl
an HTTP URL; see URLs
HttpUrl
a stricter HTTP URL; see URLs
PostgresDsn
a postgres DSN style URL; see URLs
RedisDsn
a redis DSN style URL; see URLs
stricturl
a type method for arbitrary URL constraints; see URLs
UUID1
requires a valid UUID of type 1; see UUID above
UUID3
requires a valid UUID of type 3; see UUID above
UUID4
requires a valid UUID of type 4; see UUID above
UUID5
requires a valid UUID of type 5; see UUID above
SecretBytes
bytes where the value is kept partially secret; see Secrets
SecretStr
string where the value is kept partially secret; see Secrets
IPvAnyAddress
allows either an IPv4Address or an IPv6Address
IPvAnyInterface
allows either an IPv4Interface or an IPv6Interface
IPvAnyNetwork
allows either an IPv4Network or an IPv6Network
NegativeFloat
allows a float which is negative; uses standard float parsing then checks the value is less than 0; see Constrained Types
NegativeInt
allows an int which is negative; uses standard int parsing then checks the value is less than 0; see Constrained Types
PositiveFloat
allows a float which is positive; uses standard float parsing then checks the value is greater than 0; see Constrained Types
PositiveInt
allows an int which is positive; uses standard int parsing then checks the value is greater than 0; see Constrained Types
conbytes
type method for constraining bytes; see Constrained Types
condecimal
type method for constraining Decimals; see Constrained Types
confloat
type method for constraining floats; see Constrained Types
conint
type method for constraining ints; see Constrained Types
conlist
type method for constraining lists; see Constrained Types
constr
type method for constraining strs; see Constrained Types

URLsπŸ”—

For URI/URL validation the following types are available:

  • AnyUrl: any scheme allowed, TLD not required
  • AnyHttpUrl: schema http or https, TLD not required
  • HttpUrl: schema http or https, TLD required, max length 2083
  • PostgresDsn: schema postgres or postgresql, userinfo required, TLD not required
  • RedisDsn: schema redis, userinfo required, tld not required
  • stricturl, method with the following keyword arguments:
    • strip_whitespace: bool = True
    • min_length: int = 1
    • max_length: int = 2 ** 16
    • tld_required: bool = True
    • allowed_schemes: Optional[Set[str]] = None

The above types (which all inherit from AnyUrl) will attempt to give descriptive errors when invalid URLs are provided:

from pydantic import BaseModel, HttpUrl, ValidationError

class MyModel(BaseModel):
    url: HttpUrl

m = MyModel(url='http://www.example.com')
print(m.url)
#> http://www.example.com
try:
    MyModel(url='ftp://invalid.url')
except ValidationError as e:
    print(e)
"""
1 validation error for MyModel
url
  URL scheme not permitted (type=value_error.url.scheme;
allowed_schemes={'http', 'https'})
"""

try:
    MyModel(url='not a url')
except ValidationError as e:
    print(e)
"""
1 validation error for MyModel
url
  invalid or missing URL scheme (type=value_error.url.scheme)
"""

(This script is complete, it should run "as is")

If you require a custom URI/URL type, it can be created in a similar way to the types defined above.

URL PropertiesπŸ”—

Assuming an input URL of http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit, the above types export the following properties:

  • scheme: always set - the url schema (http above)
  • host: always set - the url host (example.com above)
  • host_type: always set - describes the type of host, either:

  • domain: e.g. example.com,

  • int_domain: international domain, see below, e.g. examplΒ£e.org,
  • ipv4: an IP V4 address, e.g. 127.0.0.1, or
  • ipv6: an IP V6 address, e.g. 2001:db8:ff00:42

  • user: optional - the username if included (samuel above)

  • password: optional - the password if included (pass above)
  • tld: optional - the top level domain (com above), Note: this will be wrong for any two-level domain, e.g. "co.uk". You'll need to implement your own list of TLDs if you require full TLD validation
  • port: optional - the port (8000 above)
  • path: optional - the path (/the/path/ above)
  • query: optional - the URL query (aka GET arguments or "search string") (query=here above)
  • fragment: optional - the fragment (fragment=is;this=bit above)

If further validation is required, these properties can be used by validators to enforce specific behaviour:

from pydantic import BaseModel, HttpUrl, PostgresDsn, ValidationError, validator

class MyModel(BaseModel):
    url: HttpUrl

m = MyModel(url='http://www.example.com')

# the repr() method for a url will display all properties of the url
print(repr(m.url))
#> HttpUrl('http://www.example.com', scheme='http', host='www.example.com',
#> tld='com', host_type='domain')
print(m.url.scheme)
#> http
print(m.url.host)
#> www.example.com
print(m.url.host_type)
#> domain
print(m.url.port)
#> None
class MyDatabaseModel(BaseModel):
    db: PostgresDsn

    @validator('db')
    def check_db_name(cls, v):
        assert v.path and len(v.path) > 1, 'database must be provided'
        return v

m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar')
print(m.db)
#> postgres://user:pass@localhost:5432/foobar

try:
    MyDatabaseModel(db='postgres://user:pass@localhost:5432')
except ValidationError as e:
    print(e)
"""
1 validation error for MyDatabaseModel
db
  database must be provided (type=assertion_error)
"""

(This script is complete, it should run "as is")

International DomainsπŸ”—

"International domains" (e.g. a URL where the host includes non-ascii characters) will be encoded via punycode (see this article for a good description of why this is important):

from pydantic import BaseModel, HttpUrl

class MyModel(BaseModel):
    url: HttpUrl

m1 = MyModel(url='http://punyΒ£code.com')
print(m1.url)
#> http://xn--punycode-eja.com
print(m1.url.host_type)
#> int_domain
m2 = MyModel(url='https://www.аррӏС.com/')
print(m2.url)
#> https://www.xn--80ak6aa92e.com/
print(m2.url.host_type)
#> int_domain

(This script is complete, it should run "as is")

Warning

Underscores in HostnamesπŸ”—

In pydantic underscores are allowed in all parts of a domain except the tld. Technically this might be wrong - in theory the hostname cannot have underscores, but subdomains can.

To explain this; consider the following two cases:

  • exam_ple.co.uk: the hostname is exam_ple, which should not be allowed since it contains an underscore
  • foo_bar.example.com the hostname is example, which should be allowed since the underscore is in the subdomain

Without having an exhaustive list of TLDs, it would be impossible to differentiate between these two. Therefore underscores are allowed, but you can always do further validation in a validator if desired.

Also, Chrome, Firefox, and Safari all currently accept http://exam_ple.com as a URL, so we're in good (or at least big) company.

Color TypeπŸ”—

You can use the Color data type for storing colors as per CSS3 specification. Colors can be defined via:

  • name (e.g. "Black", "azure")
  • hexadecimal value (e.g. "0x000", "#FFFFFF", "7fffd4")
  • RGB/RGBA tuples (e.g. (255, 255, 255), (255, 255, 255, 0.5))
  • RGB/RGBA strings (e.g. "rgb(255, 255, 255)", "rgba(255, 255, 255, 0.5)")
  • HSL strings (e.g. "hsl(270, 60%, 70%)", "hsl(270, 60%, 70%, .5)")
from pydantic import BaseModel, ValidationError
from pydantic.color import Color

c = Color('ff00ff')
print(c.as_named())
#> magenta
print(c.as_hex())
#> #f0f
c2 = Color('green')
print(c2.as_rgb_tuple())
#> (0, 128, 0)
print(c2.original())
#> green
print(repr(Color('hsl(180, 100%, 50%)')))
#> Color('cyan', rgb=(0, 255, 255))
class Model(BaseModel):
    color: Color

print(Model(color='purple'))
#> color=Color('purple', rgb=(128, 0, 128))
try:
    Model(color='hello')
except ValidationError as e:
    print(e)
"""
1 validation error for Model
color
  value is not a valid color: string not recognised as a valid color
(type=value_error.color; reason=string not recognised as a valid color)
"""

(This script is complete, it should run "as is")

Color has the following methods:

original
the original string or tuple passed to Color
as_named
returns a named CSS3 color; fails if the alpha channel is set or no such color exists unless fallback=True is supplied, in which case it falls back to as_hex
as_hex
returns a string in the format #fff or #ffffff; will contain 4 (or 8) hex values if the alpha channel is set, e.g. #7f33cc26
as_rgb
returns a string in the format rgb(<red>, <green>, <blue>), or rgba(<red>, <green>, <blue>, <alpha>) if the alpha channel is set
as_rgb_tuple
returns a 3- or 4-tuple in RGB(a) format. The alpha keyword argument can be used to define whether the alpha channel should be included; options: True - always include, False - never include, None (default) - include if set
as_hsl
string in the format hsl(<hue deg>, <saturation %>, <lightness %>) or hsl(<hue deg>, <saturation %>, <lightness %>, <alpha>) if the alpha channel is set
as_hsl_tuple
returns a 3- or 4-tuple in HSL(a) format. The alpha keyword argument can be used to define whether the alpha channel should be included; options: True - always include, False - never include, None (the default) - include if set

The __str__ method for Color returns self.as_named(fallback=True).

Note

the as_hsl* refer to hue, saturation, lightness "HSL" as used in html and most of the world, not "HLS" as used in python's colorsys.

Secret TypesπŸ”—

You can use the SecretStr and the SecretBytes data types for storing sensitive information that you do not want to be visible in logging or tracebacks. The SecretStr and SecretBytes will be formatted as either '**********' or '' on conversion to json.

from pydantic import BaseModel, SecretStr, SecretBytes, ValidationError

class SimpleModel(BaseModel):
    password: SecretStr
    password_bytes: SecretBytes

sm = SimpleModel(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes')

# Standard access methods will not display the secret
print(sm)
#> password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(sm.password)
#> **********
print(sm.json())
#> {"password": "**********", "password_bytes": "**********"}

# Use get_secret_value method to see the secret's content.
print(sm.password.get_secret_value())
#> IAmSensitive
print(sm.password_bytes.get_secret_value())
#> b'IAmSensitiveBytes'

try:
    SimpleModel(password=[1, 2, 3], password_bytes=[1, 2, 3])
except ValidationError as e:
    print(e)
"""
2 validation errors for SimpleModel
password
  str type expected (type=type_error.str)
password_bytes
  byte type expected (type=type_error.bytes)
"""

(This script is complete, it should run "as is")

Json TypeπŸ”—

You can use Json data type to make pydantic first load a raw JSON string. It can also optionally be used to parse the loaded object into another type base on the type Json is parameterised with:

from typing import List

from pydantic import BaseModel, Json, ValidationError

class SimpleJsonModel(BaseModel):
    json_obj: Json

class ComplexJsonModel(BaseModel):
    json_obj: Json[List[int]]

print(SimpleJsonModel(json_obj='{"b": 1}'))
#> json_obj={'b': 1}
print(ComplexJsonModel(json_obj='[1, 2, 3]'))
#> json_obj=[1, 2, 3]
try:
    ComplexJsonModel(json_obj=12)
except ValidationError as e:
    print(e)
"""
1 validation error for ComplexJsonModel
json_obj
  JSON object must be str, bytes or bytearray (type=type_error.json)
"""

try:
    ComplexJsonModel(json_obj='[a, b]')
except ValidationError as e:
    print(e)
"""
1 validation error for ComplexJsonModel
json_obj
  Invalid JSON (type=value_error.json)
"""

try:
    ComplexJsonModel(json_obj='["a", "b"]')
except ValidationError as e:
    print(e)
"""
2 validation errors for ComplexJsonModel
json_obj -> 0
  value is not a valid integer (type=type_error.integer)
json_obj -> 1
  value is not a valid integer (type=type_error.integer)
"""

(This script is complete, it should run "as is")

Payment Card NumbersπŸ”—

The PaymentCardNumber type validates payment cards (such as a debit or credit card).

from datetime import date

from pydantic import BaseModel
from pydantic.types import PaymentCardBrand, PaymentCardNumber, constr

class Card(BaseModel):
    name: constr(strip_whitespace=True, min_length=1)
    number: PaymentCardNumber
    exp: date

    @property
    def brand(self) -> PaymentCardBrand:
        return self.number.brand

    @property
    def expired(self) -> bool:
        return self.exp < date.today()

card = Card(
    name='Georg Wilhelm Friedrich Hegel',
    number='4000000000000002',
    exp=date(2023, 9, 30)
)

assert card.number.brand == PaymentCardBrand.visa
assert card.number.bin == '400000'
assert card.number.last4 == '0002'
assert card.number.masked == '400000******0002'

(This script is complete, it should run "as is")

PaymentCardBrand can be one of the following based on the BIN:

  • PaymentCardBrand.amex
  • PaymentCardBrand.mastercard
  • PaymentCardBrand.visa
  • PaymentCardBrand.other

The actual validation verifies the card number is:

  • a str of only digits
  • luhn valid
  • the correct length based on the BIN, if Amex, Mastercard or Visa, and between 12 and 19 digits for all other brands

Constrained TypesπŸ”—

The value of numerous common types can be restricted using con* type functions:

from decimal import Decimal

from pydantic import (
    BaseModel,
    NegativeFloat,
    NegativeInt,
    PositiveFloat,
    PositiveInt,
    conbytes,
    condecimal,
    confloat,
    conint,
    conlist,
    constr,
    Field,
)

class Model(BaseModel):
    short_bytes: conbytes(min_length=2, max_length=10)
    strip_bytes: conbytes(strip_whitespace=True)

    short_str: constr(min_length=2, max_length=10)
    regex_str: constr(regex='apple (pie|tart|sandwich)')
    strip_str: constr(strip_whitespace=True)

    big_int: conint(gt=1000, lt=1024)
    mod_int: conint(multiple_of=5)
    pos_int: PositiveInt
    neg_int: NegativeInt

    big_float: confloat(gt=1000, lt=1024)
    unit_interval: confloat(ge=0, le=1)
    mod_float: confloat(multiple_of=0.5)
    pos_float: PositiveFloat
    neg_float: NegativeFloat

    short_list: conlist(int, min_items=1, max_items=4)

    decimal_positive: condecimal(gt=0)
    decimal_negative: condecimal(lt=0)
    decimal_max_digits_and_places: condecimal(max_digits=2, decimal_places=2)
    mod_decimal: condecimal(multiple_of=Decimal('0.25'))

    bigger_int: int = Field(..., gt=10000)

(This script is complete, it should run "as is")

Where Field refers to the field function.

Strict TypesπŸ”—

You can use the StrictStr, StrictInt, StrictFloat, and StrictBool types to prevent coercion from compatible types. These types will only pass validation when the validated value is of the respective type or is a subtype of that type. This behavior is also exposed via the strict field of the ConstrainedStr, ConstrainedFloat and ConstrainedInt classes and can be combined with a multitude of complex validation rules.

The following caveats apply:

  • StrictInt (and the strict option of ConstrainedInt) will not accept bool types, even though bool is a subclass of int in Python. Other subclasses will work.
  • StrictFloat (and the strict option of ConstrainedFloat) will not accept int.
from pydantic import (
    BaseModel, confloat,  StrictBool, StrictInt, ValidationError
)

class StrictIntModel(BaseModel):
    strict_int: StrictInt

try:
    StrictIntModel(strict_int=3.14159)
except ValidationError as e:
    print(e)
"""
1 validation error for StrictIntModel
strict_int
  value is not a valid integer (type=type_error.integer)
"""

class ConstrainedFloatModel(BaseModel):
    constrained_float: confloat(strict=True, ge=0.0)

try:
    ConstrainedFloatModel(constrained_float=3)
except ValidationError as e:
    print(e)
"""
1 validation error for ConstrainedFloatModel
constrained_float
  value is not a valid float (type=type_error.float)
"""

try:
    ConstrainedFloatModel(constrained_float=-1.23)
except ValidationError as e:
    print(e)
"""
1 validation error for ConstrainedFloatModel
constrained_float
  ensure this value is greater than or equal to 0.0
(type=value_error.number.not_ge; limit_value=0.0)
"""

class StrictBoolModel(BaseModel):
    strict_bool: StrictBool

try:
    StrictBoolModel(strict_bool='False')
except ValidationError as e:
    print(str(e))
"""
1 validation error for StrictBoolModel
strict_bool
  value is not a valid boolean (type=value_error.strictbool)
"""

(This script is complete, it should run "as is")

ByteSizeπŸ”—

You can use the ByteSize data type to convert byte string representation to raw bytes and print out human readable versions of the bytes as well.

Info

Note that 1b will be parsed as "1 byte" and not "1 bit".

from pydantic import BaseModel, ByteSize

class MyModel(BaseModel):
    size: ByteSize

print(MyModel(size=52000).size)
#> 52000
print(MyModel(size='3000 KiB').size)
#> 3072000

m = MyModel(size='50 PB')
print(m.size.human_readable())
#> 44.4PiB
print(m.size.human_readable(decimal=True))
#> 50.0PB

(This script is complete, it should run "as is")

Custom Data TypesπŸ”—

You can also define your own custom data types. The classmethod __get_validators__ will be called to get validators to parse and validate the input data.

from pydantic import BaseModel, ValidationError

class StrictStr(str):
    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        if not isinstance(v, str):
            raise ValueError(f'strict string: str expected not {type(v)}')
        return v

class Model(BaseModel):
    s: StrictStr

print(Model(s='hello'))
#> s='hello'
try:
    print(Model(s=123))
except ValidationError as e:
    print(e.json())
"""
[
  {
    "loc": [
      "s"
    ],
    "msg": "strict string: str expected not <class 'int'>",
    "type": "value_error"
  }
]
"""

(This script is complete, it should run "as is")