unmarshals
¶
Support for unmarshalling unstructured data into Python data structures.
Notes
"Unmarshalling" refers to the process of taking a "primitive" form of data, such as a basic dictionary or JSON string, and coercing it into a higher-order structured data type.
Tip
You may use this package directly, but we encourage you to work with the
higher-level API provided by the typelib
module.
Typical Usage
>>> import dataclasses
>>> import decimal
>>> from typelib import unmarshals
>>>
>>> @dataclasses.dataclass(slots=True, weakref_slot=True, kw_only=True)
... class Struct:
... key: str
... number: decimal.Decimal
...
>>>
>>> data = {"key": "some-key", "number": "3.14"}
>>> unmarshals.unmarshal(Struct, data)
Struct(key='some-key', number=decimal.Decimal('3.14'))
>>> unmarshaller = unmarshals.unmarshaller(Struct)
>>> unmarshaller(data)
Struct(key='some-key', number=decimal.Decimal('3.14'))
See Also
AbstractUnmarshaller
¶
Abstract base class defining the common interface for unmarshallers.
Unmarshallers are custom callables which maintain type-specific information. They use this information to provide robust, performant logic for decoding and converting primtive Python objects or JSON-endcoded data into their target type.
Unmarshallers support contextual deserialization, which enables the unmarshalling of nested types.
Attributes:
-
t
(type[T]
) –The root type of this unmarshaller.
-
origin
(type[T]
) –If
t
is a generic, this will be an actionable runtime type related tot
, otherwise it is the same ast
. -
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
) –If this unmarshaller is used in a nested context, this will reference the field/parameter/index at which this unmarshaller should be used.
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
BytesUnmarshaller
¶
Bases: AbstractUnmarshaller[BytesT]
, Generic[BytesT]
Unmarshaller that encodes an input to bytes.
Note
We will format a member of the datetime
module into ISO format before converting to bytes.
See Also
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
CastUnmarshaller
¶
Bases: AbstractUnmarshaller[T]
Unmarshaller that converts an input to an instance of T
with a direct cast.
Note
Before casting to the bound type, we will attempt to decode the value into a real Python object.
See Also
Parameters:
-
t
(type[T]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context (unused).
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> T
Unmarshal a value into the bound T
type.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
DateTimeUnmarshaller
¶
Bases: AbstractUnmarshaller[datetime]
, Generic[DateTimeT]
Unmarshaller that converts an input to a datetime.datetime
(or subclasses).
Notes
This class tries to handle the 90% case:
- If we are already a
datetime.datetime
instance, return it. - If we are a
float
orint
instance, treat it as a unix timestamp, at UTC. - Attempt to decode any bytes/string input into a real Python value.
- If we have a string value, parse it into either a
datetime.date
instance, adatetime.time
instance or adatetime.datetime
. - If the parsed result is a
datetime.time
instance, then merge the parsed time with today, at the timezone specified in the time instance. - If the parsed result is a
datetime.date
instance, create adatetime.datetime
instance at midnight of the indicated date, UTC.
TL;DR
There are many ways to represent a datetime object over-the-wire. Your most fool-proof method is to rely upon ISO 8601 or RFC 3339.
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
Unmarshal a value into the bound DateTimeT
type.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
DateUnmarshaller
¶
Bases: AbstractUnmarshaller[DateT]
, Generic[DateT]
Unmarshaller that converts an input to a datetime.date
(or subclasses).
Notes
This class tries to handle the 90% case:
- If we are already a
datetime.date
instance, return it. - If we are a
float
orint
instance, treat it as a unix timestamp, at UTC. - Attempt to decode any bytes/string input into a real Python value.
- If we have a string value, parse it into either a
datetime.date
- If the parsed result is a
datetime.time
instance, then return the result ofdatetime.datetime.now
, at UTC, as adatetime.date
.
TL;DR
There are many ways to represent a date object over-the-wire. Your most fool-proof method is to rely upon ISO 8601 or RFC 3339.
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> DateT
Unmarshal a value into the bound DateT
type.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
DelayedUnmarshaller
¶
Bases: AbstractUnmarshaller[T]
Delayed proxy for a given type's unmarshaller, used when we encounter a typing.ForwardRef
.
Notes
This allows us to delay the resolution of the given type reference until call-time, enabling support for cyclic and recursive types.
Source code in src/typelib/unmarshals/api.py
FixedTupleUnmarshaller
¶
Bases: AbstractUnmarshaller[TupleT]
Unmarshaller for a "fixed" tuple (e.g., tuple[int, str, float]
).
Note
Python supports two distinct uses for tuples, unlike in other languages:
- Tuples with a fixed number of members.
- Tuples of variable length (an immutable sequence).
"Fixed" tuples may have a distinct type for each member, while variable-length tuples may only have a single type (or union of types) for all members.
Variable-length tuples are handled by our generic iterable unmarshaller.
For "fixed" tuples, the algorithm is:
- Attempt to decode the input into a real Python object.
- zip the stack of member unmarshallers and the values in the decoded object.
- Unmarshal each value using the associated unmarshaller for that position.
- Pass the unmarshalling iterator in to the type's constructor.
Tip
If the input has more members than the type definition allows, those members will be dropped by nature of our unmarshalling algorithm.
Parameters:
-
t
(type[TupleT]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context. Used to resolve the value unmarshaller stack.
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
LiteralUnmarshaller
¶
Bases: AbstractUnmarshaller[LiteralT]
, Generic[LiteralT]
Unmarshaller that will enforce an input conform to a defined typing.Literal
.
Note
We will attempt to decode the value into a real Python object if the input fails initial membership evaluation.
See Also
Parameters:
-
t
(type[LiteralT]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context (unused).
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
NoOpUnmarshaller
¶
Bases: AbstractUnmarshaller[T]
Unmarshaller that does nothing.
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
NoneTypeUnmarshaller
¶
Bases: AbstractUnmarshaller[None]
Unmarshaller for null values.
Note
We will attempt to decode any string/bytes input before evaluating for None
.
See Also
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> None
Unmarshal the given input into a None
value.
Parameters:
-
val
(Any
) –The value to unmarshal.
Raises:
-
ValueError
–If
val
is notNone
after decoding.
Source code in src/typelib/unmarshals/routines.py
NumberUnmarshaller
¶
Bases: AbstractUnmarshaller[NumberT]
, Generic[NumberT]
Unmarshaller that converts an input to a number.
Note
Number unmarshalling follows a best-effort strategy. We may extend type resolution to support more advanced type unmarshalling strategies in the future.
As of now:
1. Attempt to decode any bytes/string input into a real Python value.
2. If the input is a member of the datetime
module, convert it to a number.
3. If the input is a mapping, unpack it into the number constructor.
4. If the input is an iterable, unpack it into the number constructor.
5. Otherwise, call the number constructor with the input.
See Also
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> NumberT
Unmarshall a value into the bound Number type.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
PatternUnmarshaller
¶
Bases: AbstractUnmarshaller[PatternT]
, Generic[PatternT]
Unmarshaller that converts an input to a re.Pattern
.
Note
You can't instantiate a re.Pattern
directly, so we don't have a good
method for handling patterns from a different library out-of-the-box. We simply call
re.compile()
on the decoded input.
See Also
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
StringUnmarshaller
¶
Bases: AbstractUnmarshaller[StringT]
, Generic[StringT]
Unmarshaller that converts an input to a string.
Note
We will format a member of the datetime
module into ISO format.
See Also
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
StructuredTypeUnmarshaller
¶
Bases: AbstractUnmarshaller[_ST]
Unmarshaller for a "structured" (user-defined) type.
Note
This unmarshaller supports the unmarshalling of any mapping or structured type into the targeted structured type. There are limitations.
The algorithm is:
- Attempt to decode the input into a real Python object.
- Using a mapping of the structured types "field" to the field-type's unmarshaller, iterate over the field->value pairs of the input, skipping fields in the input which are not present in the field mapping.
- Store each unmarshalled value in a keyword-argument mapping.
- Unpack the keyword argument mapping into the bound type's constructor.
Tip
While we don't currently support arbitrary collections, we may add this functionality at a later date. Doing so requires more advanced introspection and parameter-binding that would lead to a significant loss in performance if not done carefully.
Parameters:
-
t
(type[_ST]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context. Used to resolve the value field-to-unmarshaller mapping.
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
SubscriptedIterableUnmarshaller
¶
Bases: AbstractUnmarshaller[IterableT]
, Generic[IterableT]
Unmarshaller for a subscripted iterable type.
Note
This unmarshaller handles standard simple iterable types. We leverage our own
generic itervalues
to allow for translating other collections or structured
objects into the target iterable.
The algorithm is as follows:
- We attempt to decode the input into a real Python object.
- We iterate over values in the decoded input.
- We call the value-type's unmarshaller on the
value
members. - We pass the unmarshalling iterator in to the type's constructor.
Parameters:
-
t
(type[IterableT]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context. Used to resolve the member unmarshaller.
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> IterableT
Unmarshal a value into the bound IterableT
.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
SubscriptedIteratorUnmarshaller
¶
Bases: AbstractUnmarshaller[IteratorT]
, Generic[IteratorT]
Unmarshaller for a subscripted iterator type.
Note
This unmarshaller handles standard simple iterable types. We leverage our own
generic itervalues
to allow for translating other collections or structured
objects into the target iterator.
The algorithm is as follows:
- We attempt to decode the input into a real Python object.
- We iterate over values in the decoded input.
- We call the value-type's unmarshaller on the
value
members. - We return a new, unmarshalling iterator.
Parameters:
-
t
(type[IteratorT]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context. Used to resolve the member unmarshaller.
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> IteratorT
Unmarshal a value into the bound IteratorT
.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
SubscriptedMappingUnmarshaller
¶
Bases: AbstractUnmarshaller[MappingT]
, Generic[MappingT]
Unmarshaller for a subscripted mapping type.
Note
This unmarshaller handles standard key->value mappings. We leverage our own
generic iteritems
to allow for translating other collections or structured
objects into the target mapping.
The algorithm is as follows:
- We attempt to decode the input into a real Python object.
- We iterate over key->value pairs.
- We call the key-type's unmarshaller on the
key
members. - We call the value-type's unmarshaller on the
value
members. - We pass the unmarshalling iterator in to the type's constructor.
Parameters:
-
t
(type[MappingT]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context. Used to resolve the member unmarshallers.
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> MappingT
Unmarshal a value into the bound MappingT
.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
TimeDeltaUnmarshaller
¶
Bases: AbstractUnmarshaller[TimeDeltaT]
, Generic[TimeDeltaT]
Unmarshaller that converts an input to a datetime.timedelta
(or subclasses).
Notes
This class tries to handle the 90% case:
- If we are already a
datetime.timedelta
instance, return it. - If we are a
float
orint
instance, treat it as total seconds for a delta. - Attempt to decode any bytes/string input into a real Python value.
- If we have a string value, parse it into a
datetime.timedelta
instance. - If the parsed result is not exactly the bound
TimeDeltaT
type, convert it.
TL;DR
There are many ways to represent a time object over-the-wire. Your most fool-proof method is to rely upon ISO 8601 or RFC 3339.
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> TimeDeltaT
Unmarshal a value into the bound TimeDeltaT
type.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
TimeUnmarshaller
¶
Bases: AbstractUnmarshaller[TimeT]
, Generic[TimeT]
Unmarshaller that converts an input to adatetime.time
(or subclasses).
Notes
This class tries to handle the 90% case:
- If we are already a
datetime.time
instance, return it. - If we are a
float
orint
instance, treat it as a unix timestamp, at UTC. - Attempt to decode any bytes/string input into a real Python value.
- If we have a string value, parse it into either a
datetime.date
instance, adatetime.time
instance or adatetime.datetime
. - If the parsed result is a
datetime.datetime
instance, then extract the time portion, preserving the associated timezone. - If the parsed result is a
datetime.date
instance, create a time instance at midnight, UTC.
TL;DR
There are many ways to represent a time object over-the-wire. Your most fool-proof method is to rely upon ISO 8601 or RFC 3339.
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> TimeT
Unmarshal a value into the bound TimeT
type.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Source code in src/typelib/unmarshals/routines.py
UUIDUnmarshaller
¶
Bases: AbstractUnmarshaller[UUIDT]
, Generic[UUIDT]
Unmarshaller that converts an input to a uuid.UUID
(or subclasses).
Note
The resolution algorithm is intentionally simple:
- Attempt to decode any bytes/string input into a real Python object.
- If the value is an integer, pass it into the constructor via the
int=
param. - Otherwise, pass into the constructor directly.
Tip
While the uuid.UUID
constructor supports many different keyword
inputs for different types of UUID formats/encodings, we don't have a great
method for detecting the correct input. We have moved with the assumption that
the two most common formats are a standard string encoding, or an integer encoding.
Parameters:
-
t
(type[T]
) –The root type of this unmarshaller.
-
context
(ContextT
) –The complete type context for this unmarshaller.
-
var
(str | None
, default:None
) –The associated field or parameter name for this unmarshaller (optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> UUIDT
Unmarshal a value into the bound UUIDT
type.
Parameters:
-
val
(Any
) –The input value to unmarshal.
See Also
Source code in src/typelib/unmarshals/routines.py
UnionUnmarshaller
¶
Bases: AbstractUnmarshaller[UnionT]
, Generic[UnionT]
Unmarshaller that will convert an input to one of the types defined in a typing.Union
.
Note
Union deserialization is messy and violates a static type-checking mechanism -
for static type-checkers, str | int
is equivalent to int | str
. This breaks
down during unmarshalling for the simple fact that casting something to str
will always succeed, so we would never actually unmarshal the input it an int
,
even if that is the "correct" result.
Our algorithm is intentionally simple:
- We iterate through each union member from top to bottom and call the resolved unmarshaller, returning the result.
- If any of
(ValueError, TypeError, SyntaxError)
, try again with the next unmarshaller. - If all unmarshallers fail, then we have an invalid input, raise an error.
TL;DR
In order to ensure correctness, you should treat your union members as a stack, sorted from most-strict initialization to least-strict.
Parameters:
-
t
(type[UnionT]
) –The type to unmarshal into.
-
context
(ContextT
) –Any nested type context. Used to resolve the member unmarshallers.
-
var
(str | None
, default:None
) –A variable name for the indicated type annotation (unused, optional).
Source code in src/typelib/unmarshals/routines.py
__call__
¶
__call__(val: Any) -> UnionT
Unmarshal a value into the bound UnionT
.
Parameters:
-
val
(Any
) –The input value to unmarshal.
Raises:
-
ValueError
–If
val
cannot be unmarshalled into any member type.
Source code in src/typelib/unmarshals/routines.py
unmarshal
¶
unmarshaller
¶
unmarshaller(t: type[T] | ForwardRef | TypeAliasType | str) -> AbstractUnmarshaller[T]
Get an un-marshaller routine for a given type.
Parameters:
-
t
(type[T] | ForwardRef | TypeAliasType | str
) –The type annotation to generate an unmarshaller for. May be a type, type alias,
typing.ForwardRef
, or string reference.