About

This documentation covers the models system of typed, which is used specifically to data validation.

  1. About

  2. Overview

  3. Model Factories

  4. Model Validation

  5. Model Types

  6. Defining Models

  7. Model

  8. Validation

  9. Exact Models

  10. Other Docs

Overview

The models system is defined in the typed.models module, such that:

  1. it provides model factories, which are type factories constructing models;

  2. the difference between the model factories is how data is validated in instances of their models;

  3. each model factory has a corresponding model type, consisting of all its models;

  4. 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:

  1. it has only keyword arguments, i.e, its domain in Dict(TYPE);

  2. the value of each kwarg is a type;

  3. the key of each kwarg defines an attribute in the returning type;

  4. 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

table 1

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 object x such that isinstance(x, Json) is True, which conditions x need to satisfy such that isinstance(x, model) is True for model = ModelFactory(**kwargs) for some kwargs in Dict(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

table 2

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 the entity and provides specific errors, while isinstance 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)

Other Docs

  1. types

  2. errors

  3. examples

  4. lists of entities