Fitting data into types

The base feature of typefit is to fit data into Python data structures. By example:

from typing import NamedTuple, Text
from typefit import typefit


class Item(NamedTuple):
    id: int
    title: Text


item = typefit(Item, {"id": 42, "title": "Item title yay"})
assert item.title == "Item title yay"

It will use typing annotations in order to understand what kind of data structure you expect and then it will create an instance of the specified type based on the provided data.

Fitting Enum

Typefit can also be used to fit data to an Enum.

from typefit import typefit

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

item = typefit(Color, 2)
assert item == Color.GREEN

typefit will return the attribute of the enum class if the value is valid or else it will raise a ValueError exception.

Fitting mappings

The concept is as simple as that but it can become pretty powerful. Firstly, typing definitions can be recursive, so this would work:

from dataclasses import dataclass, field
from typing import List, Text
from typefit import typefit


@dataclass
class Comment:
    text: Text
    children: List["Comment"] = field(default_factory=list)


data = {
    "text": "Hello",
    "children": [
        {
            "text": "Howdy",
        },
        {
            "text": "Hello to you too",
        },
    ]
}

comment = typefit(Comment, data)

You’ll notice that we switched from typing.NamedTuple to a dataclass. Both approaches work, that’s up to you to decide which fits your needs most.

Note

This example uses a forward reference as type because the Comment is not defined at the time we need it. This means that Python will have to be able to resolve this reference later, meaning that this class has to be importable. If hidden inside a 2-nd order function it won’t work.

Custom field names

If you don’t want your fields to bear the exact name as they have in the JSON that you are deserializing, you can specify customized names:

from dataclasses import dataclass, field
from typing import Text
from typefit import typefit, meta, other_field


@dataclass
class Info:
    some_thing: Text = field(metadata=meta(source=other_field('someThing')))


x: Info = typefit(Info, {"someThing": "foo"})
assert x.some_thing == "foo"

Parsing narrow types

This system also allows you to parse input and coerce it into a more pythonic object. A typical example for that would be to parse a date. Typefit uses pendulum in order to parse dates and will return a Pendulum date time object.

from typefit import narrows, typefit

data = "2019-01-01T00:00:00Z"
date = typefit(narrows.DateTime, data)

assert date.month == 1

Note

Date narrow types depend on the pendulum package, however Typefit doesn’t list it as a dependency. If you want to be able to use those, you need to install pendulum for your project.

Narrows are types that will help narrow-down the data you are parsing to a Python function. Of course, you might not want to limit yourself to builtin narrow types.

Custom type

You can provide a custom narrow type simply by creating a type whose constructor will have exactly a single argument which is properly annotated (with a simple type like int or Text but not a generic like Union or List).

from typing import Text
from typefit import typefit

class Name:
    def __init__(self, full_name: Text):
        split = full_name.split(' ')

        if len(split) != 2:
            raise ValueError('Too many names')

        self.first_name, self.last_name = split

name = typefit(Name, "Rémy Sanchez")
print(name.first_name)
print(name.last_name)

Wrapper type

However sometimes you just want to wrap a type that already exists but doesn’t have the right arguments in its constructor. That’s the case of the date-related narrows described above. Let’s dissect one.

import pendulum

class TimeStamp(pendulum.DateTime):
    def __new__(cls, date: int):
        return pendulum.from_timestamp(date)

You’ll probably ask why is there some funny business with __new__ instead of just created a function that will parse and return the desired value. The answer is that you’re doing type annotations so you must provide valid types otherwise you’ll confuse your static type checker, which loses the interest of annotating types in a first place.

Reference

You’ll find here the reference of the public API. Private function are documented in the source code but might change or disappear without notice. The current API is not stable but will try to change as little as possible before version 1.

Typefit

The core typefit module only exposes one function.

typefit.typefit(t: Type[T], value: Any) → T

Fits a JSON-decoded value into native Python type-annotated objects.

Parameters
  • t

    Type to fit the value into. Currently supported types are:

    • Simple builtins like int, float, typing.Text, typing.bool

    • Enumerations which are subclass of enum.Enum.

    • Custom types. The constructor needs to accept exactly one parameter and that parameter should have a typing annotation.

    • typing.Union to define several possible types

    • typing.List to declare a list and the type of list values

  • value – Value to be fit into the type

Returns

If the value fits, a value of the right type is returned.

Return type

T

Raises

ValueError – When the fitting cannot be done, a ValueError is raised.

Narrows

Narrows are data types that integrate some form of parsing to generate Python objects from more generic types like strings. All those classes accept exactly one argument to their constructor which is the data structure to convert.

class typefit.narrows.Date

Parses a date and returns a standard pendulum Date.

class typefit.narrows.DateTime

Parses a date/time and returns a standard pendulum DateTime.

class typefit.narrows.TimeStamp

Parses a Unix timestamp and returns a standard pendulum DateTime.

Meta

The meta module allows to specify meta-information on fields and types in order to affect the way that typefit will deal with them.

class typefit.meta.Source

Provides a way back and forth to convert the data from and to a JSON structure. Since the conversion from JSON is able to dig into any number of fields from the original mapping, the conversion to JSON will have to produce a dictionary as output, even if it has only one key.

property value_from_json

Alias for field number 0

property value_to_json

Alias for field number 1

typefit.meta.meta(source: Optional[typefit.meta.Source] = None)

Generates the field metadata for a field based on what arguments are provided. By example, to source a field into a field with another name in the JSON data, you can do something like:

>>> @dataclass
>>> class Foo:
>>>     x: int = field(metadata=meta(source=other_field('x')))

See also

other_field()

Parameters

source – Source function, that given the mapping as input will provide the value as output. If the value isn’t found in the mapping, a KeyError should arise.

typefit.meta.other_field(name: str) → typefit.meta.Source

Looks for the value in a field named name.

Parameters

name – Name of the field to look for the value into.