Skip to main content

Convert dictionaries to dataclasses and back

Project description

howard

Python datatype marshalling

This library marshalls dictionaries [read json] into instances of defined dataclasses and back.

i.e.

from dataclasses import dataclass

import howard

@dataclass
class Person:
    name: str
    age: int


my_dict = {'name': 'Bob', 'age': 24}
person = howard.from_dict(my_dict, Person)
assert person.name == 'Bob'
assert person.age == 24

The main purpose is to use python dataclasses as a schema definition rather than having to hand-write a schema. Howard does not currently include logic itself for generating json-schema (swagger) object documentation, but that is a long term goal of it.

Howard differs from a standard dataclass because it can recursively marshall and unmarshall. It also supports more types out of the box than dataclasses.asdict does. Some supported types:

  • Enums
  • TypedDict
  • Collections (lists/dictionaries)
  • Datetime
  • all primitives (int/string/boolean/float)

All of the logic for howard is in your dataclass definition, not in howard. Howard just has a to_dict and from_dict method, and it bases all decisions off of your dataclass. There is no inheritance on custom types, everything is standard, built-in python. (3.7+)

Installing

pip install howard

More examples

For more examples, you can go look at the tests at tests/test_howard.py

Here is a basic example of recursive types and how it can work with howard:

from enum import Enum
from dataclasses import dataclass, field
from typing import List

import howard

@dataclass
class Suit(Enum):
    heart = 'h'
    spade = 's'
    diamond = 'd'
    club = 'c'


def validate_rank(i: int) -> int:
    lb, ub = 1, 13
    if lb <= i <= ub:
        return i
    raise ValueError(f'{i} is not between {lb} and {ub}')


@dataclass
class Card:
    rank: int = field(metadata={'howard': {'decoder': validate_rank}})
    suit: Suit


@dataclass
class Hand:
    hand_id: int = 0
    cards: List[Card] = field(default_factory=list)


d = {'hand_id': 2, 'cards': [{'rank': 2, 'suit': 'c'}, {'rank': 10, 'suit': 'h'}]}

# d is a dictionary, now we turn it into the dataclass
obj = howard.from_dict(d, Hand)

assert isinstance(obj, Hand)
assert obj.hand_id == 2
assert len(obj.cards) == 2
assert isinstance(obj.cards[0], Card)
assert obj.cards[0].suit == Suit.club

# and back to a dictionary
json_dict = howard.to_dict(obj)

In the above example, you can see a couple things.

  1. A Hand contains a list of Card.
  2. The sub-object Card also gets unmarshalled correctly.
  3. The Suit object is an enum, which is like a string in json form, but only has 4 possible values.
  4. The Card has a field called rank which has its own custom decoder. In this case, the decoder acts as a validator, but can also be used for custom decode logic.

FAQ

  • Why not just use dataclasses.asdict and MyDataclass(**my_dict)? dataclasses.asdict doesn't work on all types, for example, Enums or datetimes. MyDataclass(**my_dict) will not recursively turn the subobjects into their respective datatype.

  • What about custom types? You can specify custom decoders and encoders in a dataclass field.metadata section. See example above.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

howard-1.8.0.tar.gz (4.6 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page