About

This documentation describes the type system provided by typed and how to use it.

  1. About

  2. Overview

  3. Types

  4. Defining Types

  5. Metatypes

  6. Factories

  7. Defining Factories

  8. Annotations, Models and Dependent Types

  9. Typed Functions

  10. Typed Variables

  11. Other Docs

Overview

In typed type system we have three kinds of entities:

  1. types: are the basic entities

  2. factories: are functions used to build types

  3. typed functions: are functions with type hints checked at runtime.

Types

A type is just a class named in CamelCase. Types can be organized into primitive types and derived types. The first ones are those that comes already defined in typed. The other ones are those constructed by the user by making use of factories, as we will see in the sequence.

There are a lot of primitive types in typed. Some of them are Python builtins.

type

python builtin

Int

int

Float

float

Str

str

Bool

bool

Nill

type(None)

table 1

Other examples of primitive types in typed which are not Python builtins, include:

type

description

Any

the type of anything

Json

the type of a json entity

Path

the type of paths

table 2

For the full list of primitive and types, see here.

Defining Types

Although any CamelCase class is an acceptable type, in typed the types are typically subject to the following conditions:

  1. they are constructed as the concretization of a metaclass

  2. they are a subclass of some already defined typed type

  3. they have a explicit __instancecheck__ method

  4. they may contains a explicit __subclasscheck__ method

So, a typical definition of type is as follows:

 1# the metaclass
 2class _SomeType(type(existing_type)):
 3    ...
 4    def __instancecheck__(cls, instance):
 5        ...
 6    def __subclasscheck__(cls, subclass):
 7        ...
 8
 9# the new type
10SomeType = _SomeType('SomeType', (existing_type,) {})

Metatypes

A very special kind of types are the so-called metatypes. They are “types of types”, being named in UPPERCASE notation instead of in CamelCase notation.

  • The main example of metatype is TYPE which is the type of all types.

In more precise terms, a metatype is any object t such that issubclass(t, TYPE) is True. Equivalently, it is some t such that if isinstance(x, t) is True for some x, then isinstance(x, TYPE) is True as well.

The metatypes form themselves a metatype META.

Factories

In typed, a factory is a CamelCase named function that returns a type. It can receive types (i.e, instances of TYPE) as arguments or anything else. In the case where it receive only types as arguments, a factory is viewed as a “type operation”.

Another way of thinking about a factory is a dependent type, as will be discussed later.

There a lot of predefined factories in typed. Some examples of factories that can be viewed as “type operations” are the following:

factory

description

Union

returns the union type of types

List

returns the type of list elements of given types

Tuple

returns the type of tuple elements of given types

Set

returns the type of set elements of given types

Dict

returns the type of dict elements of given types

table 3

Other examples, which are not “type operations”, are:

factory

description

Regex

returns type of strings that matches a regex

Len

returns the subtype of objects of a given lenght

Not

returns any object which is an instance of given types

Enum

returns the type formed by fixed values of a given type

Single

returns the type with a single given object

table 4

Similarly, a metafactory is a factory which returns a metatype. As happens with metatypes, metafactories are denoted with UPPERCASE notation. Some examples are the following:

metafactory

description

SUB

returns the metatype of all subtypes of given types

NOT

returns the metatype of all types which are not the given types

table 5

For the list and definition of all predefined factories, see here.

Defining Factories

Besides the predefined factories, you can create your own. In this case, it is recommended to use the decorator @factory in order to ensure type safety in factory definition. The basic structure of a factory is as follows:

 1from typed import factory, Tuple, TYPE
 2
 3@factory
 4def SomeFactory(*args: Tuple(...)) -> TYPE:
 5    ...
 6    # 1. do something
 7    ...
 8    # 2. constructs a metaclass involving the '*args'
 9    class _SomeType(type(...)):
10        ...
11        def __instancecheck__(cls, instance):
12            ...
13        def __subclasscheck__(cls, subclass):
14            ...
15    # 3. returns a concrete class
16    ...
17    return _SomeType('SomeType', (...,), {...})

Annotations, Models and Dependent Types

As you can note from table above, there is a factory to each annotation in the library typing. In this sense, you can also think of a factory as a way to implement the type annotations from typing. In this way:

typed can be viewed as a library in which type hints really works.

Note that the factories of typed that corresponds to type annotations in typing are always “type operations”. On other and, we commented that in typed there are other kinds of factories. Therefore, actually

typed does much more than just implement type annotations.

A special flavor of factories are the so-called models. They receive a bunch of arguments and produces types that have such arguments as attributes. They work similar to dataclasses or to classes that extends BaseModel in pydantic, being specially used for data validation (this will be discussed in the sequence - see here). So:

typed provides an effective mechanism for data validation.

A generic factory is a function factory: X -> TYPE that assigns a type to a collection of arguments, hence can be viewed as an “indexed family of types”, hence as an implementation of dependent types in Python:

typed implements dependent types in Python.

Typed Functions

In typed, there is a distinguished class of functions: the typed functions. They have the following characteristics:

  1. they are defined using the @typed decorator;

  2. each of its arguments is decorated with an annotation given by a typed type;

  3. it also has a return annotation given by a typed type.

So, in essence, the structure of a typed function is as follows:

1from typed import typed, SomeType, OtherType, ...
2
3@typed
4def my_function(some_var: SomeType, ...) -> OtherType:
5    ...

When you define a function as a typed function, its type annotations are checked at runtime, ensuring type safety. This means that, if at runtime some_var is not an instance of SomeType, or if the return value of my_function is not an instance of OtherType, then a TypeError will be raised.

The type errors from typed are as intuitive as possible. For more about them, see errors.

Typed Variables

As a function, the @typed decorator can be applied not only to functions, but also to types, allowing us to define typed variables, as below.

1from typed import typed, SomeType
2
3some_var = typed(SomeType)
4
5some_var = typed(SomeType, some_value)

In both cases above, at runtime it will be checked if some_var has the defined type SomeType, returning a TypeError otherwise.

Other Docs

  1. models

  2. errors

  3. examples

  4. lists of entities