About
This documentation covers the models system of typed, which is used specifically to data validation.
Overview
The models system is defined in the typed.models
module, such that:
it provides model factories, which are type factories constructing models;
the difference between the model factories is how data is validated in instances of their models;
each model factory has a corresponding model type, consisting of all its models;
the process of creating models is compatible with other sources of data validation structures, as Python dataclasses and pydantic
BaseModel
.
Model Factories
Recall that in typed
we have type factories, which are functions that return a type, hence that take values into the metatype TYPE
of all types. Recall, yet, that we have a primitive type Json
.
In a very general perspective, a model factory is a type factory such that:
it has only keyword arguments, i.e, its domain in
Dict(TYPE)
;the value of each
kwarg
is a type;the key of each
kwarg
defines an attribute in the returning type;its returning type is a subtype of
Json
.
In other words, a type factory is a function ModelFactory: Dict(TYPE) -> TYPE
such that model = ModelFactory(**kwargs)
is a sutype of Json
, i.e, such that issubclass(model, Json)
is True
, and such that model.key
is defined for each key
in kwargs
.
We have the following model factories:
model factory |
description |
---|---|
Model |
the factory of _basic_ modes |
Exact |
the factory of _strict_ models |
Ordered |
the factory of _ordered_ models |
Rigid |
the factory of _rigid_ models |
Model Validation
A model is precisely a type which is constructed from a model factory. In other words, it is an instance model = ModelFactory(**kwargs)
for some ModelFactory
listed above, and some kwargs
in Dict(TYPE)
.
The difference between the model factories is precisely how their models are validated.
More precisely, since the model factories produces subtypes of Json
, it follows that if isinstance(, model)
is True
, then x
is a Json
data. Each key
in kwargs
corresponds to an attribute of model
, which, in turn, corresponds to an entry in each instance x
of model
. The different flavors of model factories deals, precisely, with the reciprocal question:
Take a model factory
ModelFactory
. Then, given an objectx
such thatisinstance(x, Json)
isTrue
, which conditionsx
need to satisfy such thatisinstance(x, model)
isTrue
formodel = ModelFactory(**kwargs)
for somekwargs
inDict(TYPE)
?
Model Types
Each model factory defines a type whose instances are its models:
model factory |
models type |
---|---|
Model |
MODEL |
Exact |
EXACT |
Ordered |
ORDERED |
Rigid |
RIGID |
So, isinstance(x, MODEL)
is True
iff isinstance(x, TYPE)
is True
, and x = Model(**kwargs)
for some dictionary kwargs
of type Dict(TYPE)
. Analogously for the other model factories and model types.
Defining Models
Let ModelFactory
be a generic model factory, i.e, some of that in table 1.
Model
You can create type models
which are subclasses of the base Json
class and can be used to quickly validate data, much as you can do with BaseModel
in pydantic, but following the typed
philosophy:
1from typed import Int, Str, List
2from typed.models import Model
3
4SomeModel = Model(
5 x=Str,
6 y=Int,
7 z=List(Int, Str)
8)
9
10json = {
11 'x': 'foo',
12 'y': 'bar',
13 'z': [1, 'foobar']
14}
With the above, isinstance(json, SomeModel)
returns False
, since y
is expected to be an integer. As a consequence, if you use that in a typed function
, this will raise a TypeError
, as follows:
1from typed import typed, SomeType
2from some.where import SomeModel
3
4@typed
5def some_function(some_json: SomeModel) -> SomeType:
6 ...
7
8json = {
9 'x': 'foo',
10 'y': 'bar',
11 'z': [1, 'foobar']
12}
13
14some_function(json) # a raise descriptive TypeError
Validation
You can validade a model entity before calling it in a typed function using the Instance
checker:
1from typed import typed, Int, Str, List
2from typed.models import Model, Instance
3
4Model1 = Model(
5 arg1=Str,
6 arg2=Int,
7 arg3=List(Int, Str)
8)
9
10json1 = {
11 'arg1': 'foo',
12 'arg2': 'bar',
13 'arg3': [1, 'foobar']
14}
15
16model1_instance = Instance(
17 model=Model1,
18 entity=json1
19)
Such a validation is better than using just
isinstance(entity, model)
because it parses theentity
and provides specific errors, whileisinstance
just return a boolean value.
Another way to validate an instance is to call it directly as an argument for some model
:
1model1_instance = Model1({
2 'arg1': 'foo',
3 'arg2': 'bar',
4 'arg3': [1, 'foobar']
5})
You can also use a kwargs
approach:
1model1_instance = Model1({
2 arg1='foo',
3 arg2='bar',
4 arg3=[1, 'foobar']
5})
Notice that you can also use the above approaches to create valid instances and not only to validate already existing instances.
Exact Models
In pydantic, a model created from BaseModel
do a strict validation: a json data is considered an instance of the model iff it exactly matches the non-optional entries. In typed
, the Model
factory creates subtypes of Json
, so that a typical type checking will only evaluate if a json data contains the data defined in the model obtained from Model
. So, for example, the following will not raise a TypeError
:
1from typed import typed, Int, Str, List
2from typed.models import Model, Instance
3
4Model1 = Model(
5 arg1=Str,
6 arg2=Int,
7 arg3=List(Int, Str)
8)
9
10json2 = {
11 'arg1': 'foo',
12 'arg2': 2,
13 'arg3': [1, 'foobar']
14 'arg4': 'bar'
15}
16
17model1_instance = Instance(
18 model=Model1,
19 entity=json2
20)
For an exact evaluation, as occurs while using BaseModel
, you could use the Exact
factory from typed.models
. It also provides a Optional
directive, as in pydantic:
1from typed import typed, Int, Str, List
2from typed.models import ExactModel, Instance
3
4Model1 = ExactModel(
5 arg1=Str,
6 arg2=Optional(Int, 0), # optional entries always expect a default value
7 arg3=List(Int, Str)
8)
9
10Model2 = ExactModel(
11 arg1=Str,
12 arg2=Int,
13 arg3=List(Int, Str)
14)
15
16json1 = {
17 'arg1': 'foo',
18 'arg3': [1, 'foobar']
19}
20
21json2 = {
22 'arg1': 'foo',
23 'arg2': 2,
24 'arg3': [1, 'foobar']
25}
26
27# will NOT raise a TypeError
28model1_instance = Instance(
29 model=Model1,
30 entity=json1
31)
32
33# will NOT raise a TypeError
34model2_instance = Instance(
35 model=Model2,
36 entity=json2
37)
38
39# WILL raise a TypeError
40model2_instance = Instance(
41 model=Model2,
42 entity=json1
43)