Skip to content
Advertisement

Converting arguments to custom objects using dataclasses package

I recently discovered the dataclasses Python package. I’m running into an issue when using custom classes in my type annotation. I’ve got a simple example below.

When the Entry class gets passed the location argument, the value of that argument should be used to construct a Location object. Similarly, when the Entry class gets passed a string for the creationDate argument, it should be parsed (using dateutil.parser.parse) to create a datetime.datetime object. In my code, the location and creationDate arguments are not converted to the Location and datetime.datetime objects. I’m not sure how to make this work. Please advise.

Granted, I could do this without using the dataclasses package. It would add more boilerplate code. I’m also using this as an excuse to learn the dataclasses package so I can use it more efficiently the next time.

Try it

import datetime
import dateutil.parser
import dataclasses
import inspect

@dataclasses.dataclass
class Location():
    latitude: float
    longitude: float

@dataclasses.dataclass
class Entry():
    """
    A single DayOne entry
    """
    creationDate: datetime.datetime = 
        dataclasses.field(default_factory=dateutil.parser.parse)
    location: Location = None

    @classmethod
    def factory(cls, **kwargs):
        class_fields = {k:v for k,v in kwargs.items()
                        if k in inspect.signature(cls).parameters}

        return cls(**class_fields)

if __name__ == "__main__":
    print("Converting from dayone to jekylln")


    args = {
        "creationDate": '2022-05-30T04:44:33Z',
        "location": {
            'latitude': -37.8721,
            'longitude': 175.6829,
            'named': 'Hobbiton'
        },
        "text": "In a hole in the ground there lived a hobbit. Not a nasty, dirty, wet hole, filled with the ends of worms and an oozy smell, nor yet a dry, bare, sandy hole with nothing in it to sit down on or to eat: it was a hobbit-hole, and that means comfort."
    }

    entry = Entry.factory(**args)
    print(type(entry.location))
    print(type(entry.creationDate))

Advertisement

Answer

One option could be to use dataclass-wizard, which is a bit more lightweight than pydantic. It uses typing-extensions module for earlier python versions, but in 3.10+ it only relies on core python stdlib.

Usage:

from __future__ import annotations  # can be removed in 3.10+

import datetime
# import dateutil.parser
import dataclasses
from pprint import pprint

from dataclass_wizard import JSONWizard


@dataclasses.dataclass
class Location:
    latitude: float
    longitude: float
    named: str | None = None


@dataclasses.dataclass
class Entry(JSONWizard):
    """
    A single DayOne entry
    """
    creation_date: datetime.datetime
    location: Location | None = None


if __name__ == "__main__":
    print("Converting from dayone to jekylln")

    args = {
        "creationDate": '2022-05-30T04:44:33Z',
        "location": {
            'latitude': -37.8721,
            'longitude': 175.6829,
            'named': 'Hobbiton'
        },
        "text": "In a hole in the ground there lived a hobbit. Not a nasty, dirty, wet hole, filled with the ends of worms and an oozy smell, nor yet a dry, bare, sandy hole with nothing in it to sit down on or to eat: it was a hobbit-hole, and that means comfort."
    }

    entry = Entry.from_dict(args)
    print(type(entry.location))
    print(type(entry.creation_date))
    print()

    print('Object:')
    pprint(entry)

Result:

Converting from dayone to jekyll

<class '__main__.Location'>
<class 'datetime.datetime'>

Object:
Entry(creation_date=datetime.datetime(2022, 5, 30, 4, 44, 33, tzinfo=datetime.timezone.utc),
      location=Location(latitude=-37.8721, longitude=175.6829, named='Hobbiton'))

Side note: I haven’t actually thought of using dateutil.parser.parse to parse date strings, though that might be a good idea coinidentally. The current implementation uses datetime.fromisoformat which does work well enough in the general use case.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement