About
This documentation describes the type system provided by {typed} and how to use it.
Overview
In {typed} type system we have three kinds of entities:
types: are the basic entities
factories: are functions used to build
types
typed functions: are functions with type hints checked at runtime.
Types
A {typed} is just a class named in CamelCase
. Types can be organized into primitive types and derived types. The first ones are those that come 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. Some of them are Python builtins.
type |
python builtin |
---|---|
Int |
int |
Float |
float |
Str |
str |
Bool |
bool |
Nill |
type(None) |
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 |
… |
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:
they are constructed as the concretization of a
metaclass
they are a subclass of some already defined {typed} type
they have a explicit
__instancecheck__
methodthey 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 thetype
of all types.
In more precise terms, a metatype
is any object t
such that:
issubclass(t, TYPE)
isTrue
;if
isinstance(x, t)
isTrue
for somex
, thenisinstance(x, TYPE)
isTrue
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 as 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 |
… |
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 |
… |
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 |
… |
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 the 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 associated 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
:
{typed} can be viewed as a library in which type hints really work.
Note that the factories of {typed} that corresponds to type annotations in typing
are always “type operations”. On other and, we commented that 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:
they are defined using the
@typed
decorator;each of its arguments is decorated with an annotation given by a {typed} type;
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, looking at the examples above, 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.