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 jekyll\n")
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))
CodePudding user response:
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 jekyll\n")
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.