LaunchedMotionShot- Make informative screen guides for your products, tools, and SOPs!
Published on

pydictable

Origin

As part of my work at MyShubhLife, we needed a solution to serialize and deserialize the python objects on multiple instances. Tricky part is, even the serialized content had to be readable either by human or other sytems. What else could be it other than JSON. When we deserialize it back, we wanted it to be a python object with native type hints. Following is the example

json = """{"name": "Pramod"}"""
obj = convert(json)
obj.name # Should be accessable, with pycharm or any IDE type hints

Eventually, we understood that we can use the above convert(...) function to validate the json data. We had different implementations of this, in-place, in multiple services. It quickly came back biting us and we had to solve it across the services we had.

These problems led me to start building a library to solve exactly two issues

  1. Schema validation
  2. Serialization with type hints

I cannot deny talking about Pydantic. We actually tried adopting it. There were few problems which stopped us from integrating it

  1. It was pretty heavy in size. We were hitting the max size of the packaged service
  2. We were not so happy in using a library which has multiple dependencies
  3. It was too early to use a beast like Pydantic. Needed a simple solution

I started this with just a file couple of years back but multiple teams quickly started adopting it. I decided to make it a independent pip package. Currently, the code is hosted at https://github.com/pskd73/pydictable as completely open source project.

Usage

Install it like any other python package

pip install pydictable

Start using it by defining the schemas. You can simply use native python type hints and it just works! You can nest schemas also.

class LatLng(DictAble):
    lat: int
    lng: int

class Address(DictAble):
    pin_code: int
    lat_lng: LatLng

class Person(DictAble):
    name: str
    address: Address

Just pass the python dict to the DictAbles.

input_dict = {
    'name': 'Pramod',
    'address': {
        'pin_code': 560032,
        'lat_lng': {
            'lat': 12345,
            'lng': 67890
        }
    }
}

p = Person(dict=input_dict)

It will throw DataValidationError error if any issue with the input dict passed.

Extendability

By design, this package is built for extendability. You can created your own fields and validators.

class PositiveIntField(IntField):
    def validate_json(self, field_name: str, v):
        assert v > 0, 'Should be positive integer'

class Person(DictAble):
    age: int = PositiveIntField()

Person(dict={'age': -1}) # Raises DataValidationError

Polymorphism

It can even support polymorphism out of the box.

class Homo(DictAble):
    name: str

class Neanderthal(Homo):
    animals_killed: int

class Sapien(Homo):
    words_spoken: int

class Human(DictAble):
    species: Homo = MultiTypeField([Neanderthal, Sapien])

human = Human(dict={
    'species': {
        'name': 'Mufasa',
        'words_spoken': 1024,
        '__type': 'Sapien'
    }
})
assert human.species.name == 'Mufasa'
assert isinstance(human.species, Sapien)
assert human.species.words_spoken == 1024

As a team, we are regularly maintaining it. Our vision is to keep it as simple as possible, pure python, and zero dependency solution. Feel free to try it out, report issues, and send pull requests. Cheers :)