Typical is built around a powerful, high-level functional API whose purpose is to make working with annotations at runtime a breeze for any developer.
Installation is as simple as
pip install -U typical.
For an easy speedup at runtime, you can add
ujson to the
typical[json]. This results in a 30-50%
improvement when serializing your data with
The simplest way to get going with Typical is the
decorator. This is the core entrypoint for all the magic
import enum import typic class Decision(enum.IntEnum): YES = 1 NO = 0 MAYBE = -1 class Explanation(str, enum.Enum): YES = "Of course!" NO = "That's just the way it is." MAYBE = "¯\_(ツ)_/¯" DECISION_MAP = dict(zip(Decision, Explanation)) @typic.al def explain(decision: Decision) -> str: return DECISION_MAP[decision] print(repr(explain(1.0))) #> <Explanation.YES: 'Of course!'>
In the above example, Typical has taken care of your runtime
type-validation automatically. It also follows the classical Python
logic of duck-typing:
float -> int -> Decision. But it will handle
more cases than that:
print(repr(explain(b"-1"))) #> <Explanation.MAYBE: '¯\\_(ツ)_/¯'>
Typical knows to look for common cases such as json and string/byte literals and handles them gracefully.
That means you don’t have to remember to handle every single edge case yourself. Just write your pure function and let Typical handle the rest. This is incredibly useful for code which lives on the edges of your application - such as a handler for an external caller or an ingestor from a data-source.
But what about errors? Typical is built to provide transparency in
the event that a value cannot be transmuted into the expected
annotation. Taking the above example, if we pass a value not defined
explain(2) #> ValueError: 2 is not a valid Decision
So instead of a random, non-descriptive
KeyError, you get a clear,
ValueError which can be easily passed on to the external
caller for handling.
Typical works with classes too -
import typic @typic.al class Foo: bar: str def __init__(self, bar: str): self.bar = bar print(repr(Foo(b"bar").bar)) #> 'bar'
But if you’re wrapping your class, you may as well go all the way with that sexy dataclass-style…
import typic @typic.klass class Foo: bar: str
This is a dataclass under the hood, so anything you do with
dataclasses you can do with your
@typic.klass, and much, much more.