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 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) |
Other examples of primitive types in typed
which are not Python builtin
s, 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
typethey 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)
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 |
… |
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 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:
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, 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.