Constraints¶
When using Typed Decoding msgspec
will ensure decoded
messages match the specified types. For example, to decode a list of integers
from JSON:
>>> import msgspec
>>> msgspec.json.decode(b"[1, 2, 3]", type=list[int])
[1, 2, 3]
>>> msgspec.json.decode(b'[1, 2, "oops"]', type=list[int])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `int`, got `str` - at `$[2]`
Often this is sufficient, but sometimes you also need to impose constraints on the values (rather than the types) found in the message.
Constraints in msgspec
are specified by wrapping a type with
typing.Annotated
, and adding a msgspec.Meta
annotation.
For example, to constrain the list to positive integers (> 0
), you’d make
use of the gt
(greater-than) constraint:
>>> from typing import Annotated
>>> PositiveInt = Annotated[int, msgspec.Meta(gt=0)]
>>> msgspec.json.decode(b'[1, 2, 3]', type=list[PositiveInt])
[1, 2, 3]
>>> msgspec.json.decode(b'[1, 2, -1]', type=list[PositiveInt])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `int` >= 1 - at `$[2]`
Constraints can be combined to enforce complex requirements. Here’s a more
complete example enforcing the following constraints on a User
struct:
name
is astr
with1 <= length <= 32
matching the regular expression"^[a-z_][a-z0-9_-]*$"
.groups
is aset
of at most 16 strings, each with the same constraints asname
above, defaulting to the emptyset
.cpu_limit
is afloat
with a value>= 0.1
and<= 8
, defaulting to 1.mem_limit
is anint
with a value>= 256
and<= 8192
, defaulting to 1024.
from typing import Annotated
from msgspec import Struct, Meta
UnixName = Annotated[
str, Meta(min_length=1, max_length=32, pattern="^[a-z_][a-z0-9_-]*$")
]
class User(Struct):
name: UnixName
groups: Annotated[set[UnixName], Meta(max_length=16)] = set()
cpu_limit: Annotated[float, Meta(ge=0.1, le=8)] = 1
mem_limit: Annotated[int, Meta(ge=256, le=8192)] = 1024
As shown above, Annotated
types can applied inline, or used to create type
aliases and then reused elsewhere (as done with UnixName
).
The following constraints are supported:
Numeric Constraints¶
These constraints are valid on int
or float
types:
ge
: The value must be greater than or equal toge
.gt
: The value must be greater thangt
.le
: The value must be less than or equal tole
.lt
: The value must be less thanlt
.multiple_of
: The value must be a multiple ofmultiple_of
.
>>> import msgspec
>>> from typing import Annotated
>>> msgspec.json.decode(b'-1', type=Annotated[int, msgspec.Meta(ge=0)])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `int` >= 0
Warning
While multiple_of
works on float
types, we don’t recommend
specifying non-integral multiple_of
constraints, as they may be
erroneously marked as invalid due to floating point precision issues. For
example, annotating a float
type with multiple_of=10
is fine, but
multiple_of=0.1
may lead to issues. See this GitHub issue for more
details.
String Constraints¶
These constraints are valid on str
types:
min_length
: The minimum valid length, inclusive.max_length
: The maximum valid length, inclusive.pattern
: A regular expression pattern that the value must match. Note that patterns are treated as unanchored. This means that the pattern “es” matches not just “es” but also “expression”. If required, you must explicitly anchor the pattern by adding a “^” prefix and “$” suffix. For example, the pattern “^es$” only matches the string “es”
>>> import msgspec
>>> from typing import Annotated
>>> msgspec.json.decode(
... b'"invalid username"',
... type=Annotated[str, msgspec.Meta(pattern="^[a-z0-9_]*$")]
... )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `str` matching regex '^[a-z0-9_]*$'
Datetime Constraints¶
These constraints are valid on datetime.datetime
and datetime.time
types:
tz
: Whether the annotated type is required to be timezone-aware. Set toTrue
to require timezone-aware values, orFalse
to require timezone-naive values. The default isNone
, which accepts either timezone-aware or timezone-naive values.
>>> import msgspec
>>> from datetime import datetime
>>> from typing import Annotated
>>> msgspec.json.decode(
... b'"2022-04-02T18:18:10"',
... type=Annotated[datetime, msgspec.Meta(tz=True)] # require timezone aware
... )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `datetime` with a timezone component
>>> msgspec.json.decode(
... b'"2022-04-02T18:18:10-06:00"',
... type=Annotated[datetime, msgspec.Meta(tz=False)] # require timezone naive
... )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `datetime` with no timezone component
Bytes Constraints¶
These constraints are valid on bytes
and bytearray
types:
min_length
: The minimum valid length, inclusive.max_length
: The maximum valid length, inclusive.
>>> import msgspec
>>> from typing import Annotated
>>> msgspec.json.decode(
... b'"ZXhhbXBsZQ=="',
... type=Annotated[bytes, msgspec.Meta(min_length=10)]
... )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `bytes` of length >= 10
Sequence Constraints¶
These constraints are valid on list
, tuple
, set
, and frozenset
types:
min_length
: The minimum valid length, inclusive.max_length
: The maximum valid length, inclusive.
>>> import msgspec
>>> from typing import Annotated
>>> msgspec.json.decode(
... b'[1, 2, 3, 4]',
... type=Annotated[list[int], msgspec.Meta(max_length=3)]
... )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `array` of length <= 3
Mapping Constraints¶
These constraints are valid on dict
types:
min_length
: The minimum valid length, inclusive.max_length
: The maximum valid length, inclusive.
>>> import msgspec
>>> from typing import Annotated
>>> msgspec.json.decode(
... b'{"a": 1, "b": 2, "c": 3, "d": 4}',
... type=Annotated[dict[str, int], msgspec.Meta(max_length=3)]
... )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
msgspec.ValidationError: Expected `object` of length <= 3