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.
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.