About

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

  1. Overview

  2. Model Factories

  3. Validation Conditions

  4. Model Types

  5. Defining Models

  6. Decorators

  7. Universal Decorator

  8. Data Validation

  9. Optional Attributes

  10. Optional Decorator

  11. Mandatory Attributes

  12. Model Extension

  13. Filtering Models

  14. Model Attributes

  15. Model Operations

  16. 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 kinds of 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;

  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:

  1. model = ModelFactory(**kwargs) is a subtype of Json

  2. 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 factories

Validation Conditions

In {typed}, 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(x, 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, when viewed as a Json datas. 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 should satisfy such that isinstance(x, model) is True for model = ModelFactory(**kwargs) for some kwargs in Dict(TYPE)?

The answers for each question are in the following table:

model factory

validation condition

Model

the json data contains at least the defined attributes

Exact

the json data contains precisely the defined attributes

Ordered

the json data contains at least the defined attributes, but in the same ordering

Rigid

the json data contains precisely the defined attributes, and in the same ordering

table 2: validation conditions

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 3: model types

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. The typical way to define a model for it, is as follows:

1from typed import SomeType, OtherType, ...
2from typed.models import ModelFactory
3
4MyModel = ModelFactory(
5    some_attr: SomeType,
6    other_attr: OtherType,
7    ...
8)

So, for example, we define an instance of the model type MODEL as below (and Analogously for the other model factories and model types):

 1from typed import SomeType, OtherType, ...
 2from typed.models import Model
 3
 4MyModel = Model(
 5    some_attr: SomeType,
 6    other_attr: OtherType,
 7    ...
 8)
 9
10print(isinstance(MyModel, MODEL)) # returns True

Decorators

There is another way to define models, which is using the model decorators. Indeed, to each model factory there corresponds a namesake decorator, named in lowercase:

model factor

decorator

Model

@model

Exact

@exact

Ordered

@ordered

Rigid

@rigid

table 4: model decorators

The decorators can be applied to any class. This will generate a model of the corresponding model type, based on the attributes of the given class. This can be used to quickly create models, as follows (same for other model types by making use of other model decorators):

1from typed import SomeType, OtherType, ...
2from typed.models import model
3
4@model
5class MyModel:
6    some_attr: SomeType
7    other_attr: OtherType
8    ...

This approach can also be used to convert models from other sources into {typed} models. In particular, this can be used to generate {typed} models from pydantic models and from Python dataclasses.

The approach of creating {typed} models from model decorators is recommended. Indeed, while created directly from a model factory, the model a fully dynamically entity. This means that a LSP will not collect its attributes. On the other hand, while using the model decorators, they are applied to static classes, whose attributes are recognized by LSPs, providing a better developing experience.

Universal Decorator

Actually, one can use @model as an “universal decorator”, in the sense that one can reconstruct the behavior of the other model decorators from it. Indeed, @model contains boolean variables, as below, which, when set, introduce the corresponding decorator in the model construction.

variable

model decorator

exact

@exact

ordered

@ordered

rigid

@rigid

table 5: variables in @model

For example, one could create an exact model as follows:

 1from typed import SomeType, OtherType, ...
 2from typed.models import model
 3
 4@model(exact=True)
 5def MyModel:
 6    some_attr: SomeType
 7    other_attr: OtherType
 8    ...
 9
10print(isinstance(MyModel, EXACT)) # prints True

Data Validation

One time defined a model, one can use it to:

  1. validate existing Json data;

  2. create already validated Json data.

Indeed, suppose we have a given json_data. The validation can be realized by calling the model with the given Json data:

1
2json_data = {
3    "some_attr": some_value,
4    "other_attr": other_value
5    ...
6}
7
8validated_data = MyModel(**json_data)

Another way of validating a predefined json_data is through the Validate function:

1from typed.models import Validate
2
3validated_data = Validate(
4    model=MyModel,
5    data=json_data
6)

The simplest way to create validated data is to call the model with specified kwargs:

1validated_data = MyModel(
2    some_attr=some_value,
3    other_attr=other_value,
4    ...
5)

Independently of the case, if some condition used in the definition of the model MyModel is not satisfied, a TypeError will be raised.

For more on the {typed} error messages, see errors.

Optional Attributes

In the moment of the definition of a model, one can determine certain attributes as optional. If the model is defined from a model factory, this can be done my making use of the Optional: TYPE x Any -> TYPE directive, which receives an attribute and a default value:

1from typed import SomeType, OtherType, OptType
2from typed.models import ModelFactory, Optional
3
4MyModel = ModelFactory(
5    some_attr=SomeType,
6    other_attr=OtherValue,
7    opt_atrr=Optional(OptType, default_value),
8    ...
9)

Then, in the validation of some json_data through the model MyModel, if there is no entry named opt_attr, the validated data will be appended with such entry with value given by default_value.

The directive Optional also accept to do not pass a default value, i.e, it accepts a single argument Optional(OptType). In this case:

  1. First it will be checked if passed type OptType is nullable. In this case, Optional(OptType) will return Optional(OptType, null(OptType)), i.e, the null object of OptType will be set as the default argument.

  2. If OptType is not nullable, it will be checked if it can be initialized. In this case, Optional(OptType) will return Optional(OptType, OptType()).

  3. Finally, if not of the above conditions are satisfied, then None will be set as the default value. More precisely, Optional(OptType) will return Optional(Maybe(OptType), None).

In the definition of a model from a model decorator, an optional attribute can be defined by just setting a default value, or by making use of the Optional directive:

 1from typed import SomeType, OtherType, OptType
 2from tped.models import model, Optional
 3
 4@model
 5class MyModel:
 6    some_attr: SomeType
 7    other_attr: OtherType
 8    opt_attr: OptType=default_value
 9    ...
10
11@model
12class MyModel:
13    some_attr: SomeType
14    other_attr: OtherType
15    opt_attr: Optional(OptType, default_value)
16    ...

The difference, here, is that you can customize the behavior of passing a single argument to Optional through the variable nullable. Indeed, if nullable=False in the model decorator, then Optional(OptType) will return Optional(Maybe(OptType), None) directly, without checking for nullability conditions for OptType.

Optional Decorator

Some times you want to define a model such that every attribute is optional. These are the so-called optional models and define a type OPTIONAL. You can quickly create an optional model by making use of the @optional decorator:

 1from typed import SomeType, OtherType
 2from typed.models import optional
 3
 4@optional
 5class MyModel:
 6    some_attr: SomeType
 7    other_attr: OtherType=default_value
 8    ...
 9
10print(isinstance(MyModel, OPTIONAL)) # returns True

The above is equivalent to:

1from typed import SomeType, OtherType
2from typed.models import model, Optional
3
4@model
5class MyModel:
6    some_attr: Optional(SomeType)
7    other_attr: Optional(OtherType, default_value)
8    ...

While using @optional, you can control the behavior of the single argument case of Optional by making use of the variable nullable. In other words, @optional(nullable=False) implements the above, but with @model(nullable=False).

You can also use optional not to define an optional model, but to turn an already existing model into an optional model:

1from typed import optional
2from some.where import MyModel
3
4OptionalModel = optional(MyModel, nullable=False).
5
6print(isinstance(MyModel, OPTIONAL)) # returns True

The decorator @optional preserves the model type. Indeed, if isinstance(MyModel, EXACT) is True, then isinstance(optional(MyModel), EXACT) is True as well, and similarly for the other model types.

Mandatory Attributes

If an attribute in a model is not optional, it is mandatory. A model in which every attribute is mandatory is a mandatory model, which constitute a type MANDATORY.

Analogously to the optional case, there is a decorator @mandatory. If used in the creation of a model, it ignores any introduction of the Optional directive or any default value:

 1from typed import SomeType, OtherType, AnotherType
 2from typed.models import mandatory
 3
 4@mandatory
 5class MyModel:
 6    some_attr: SomeType
 7    other_attr: OtherType=default_value
 8    another_attr: Optional(AnotherType)
 9    ...
10
11print(isinstance(MyModel, MANDATORY)) # returns True

The above is equivalent to:

1from typed import SomeType, OtherType
2from typed.models import model
3
4@model
5class MyModel:
6    some_attr: SomeType
7    other_attr: OtherType
8    another_attr: AnotherType
9    ...

The function @mandatory can also be used to turn any already existing model into a mandatory model preserving its model type:

1from typed import mandatory
2from some.where import MyModel
3
4OptionalModel = mandatory(MyModel).
5
6print(isinstance(MyModel, MANDATORY)) # returns True

In the above, if isinstance(MyModel, EXACT) is True, then isinstance(optional(MyModel), EXACT) is True as well, and similarly for the other model types.

Model Extension

A model can be created extending other models. This ensures that the new model have at least the attributes that are defined in the model is extended. This is the inheritance behavior of models.

While defining a new model from a model factory, the models that are being extended can be listed in a __extends__ variable in the factory:

 1from typed import SomeType, OtherType, ...
 2from typed.models import ModelFactory
 3from some.where import SomeModel, OtherModel, ...
 4
 5MyModel = ModelFactory(
 6    __extends__=[SomeModel, OtherModel, ...],
 7    some_attr=SomeType,
 8    other_attr=OtherType,
 9    ...
10)

In the case of using model decorators, the extended models are passed in the variable extends (in the example below, the same works for other model decorators):

1from typed import SomeType, OtherType, ...
2from typed.models import model
3from some.where import SomeModel, OtherModel, ...
4
5@model(extends=[SomeModel, OtherModel, ...])
6class MyModel:
7    some_attr=SomeType,
8    other_attr=OtherType,
9    ...

The variables __extends__ and extends accept not only List(MODEL) type, but also other iterative types based on some model type, as Tuple(MODEL), Set(MODEL) or even just MODEL if a single model will be extended.

Filtering Models

Model Attributes

The model types MODEL, EXACT, and so on, have certain properties, which define corresponding attributes to their models: the so-called model attributes. They are described below:

attribute

meaning

.is_model

True for MODEL instances

.is_exact

True for EXACT instances

.is_ordered

True for ORDERED instances

.is_rigid

True for RIGID instances

.attrs

dict of all attributes

.optional_attrs

dict of optional attributes

.mandatory_attrs

dict of mandatory attributes

table 6: model attributes

Model Operations

… TBA …

Other Docs

  1. overview

  2. types

  3. errors

  4. examples

  5. api

  6. glossary

  7. changelog