In Python, with the libraries attrs
and cattrs
, I have a nested structure defined as follows:
@attrs.define
class Score:
added: datetime
value: float
@attrs.define
class Entry:
score: Score
tags: List[str]
added: Optional[datetime] = None
@attrs.define
class Entries:
entries: Dict[date, Entry] = attrs.Factory(dict)
With this data:
data = {
'entries': {
'2024-01-01': {
'score': {'added': '2023-02-03 04:03:00', 'value': 80.3},
'tags': ['meatball', 'salami', 'jerky'],
},
# … more days
}
}
And some un/structure
hooks to handle the date
and datetime
types:
cattrs.register_unstructure_hook(datetime, lambda dt: datetime.strptime(dt, '%Y-%m-%d %H:%M:%S'))
cattrs.register_structure_hook(datetime, lambda dt, _: str(dt))
cattrs.register_unstructure_hook(date, lambda dt: datetime.strptime(dt, '%Y-%m-%d').date())
cattrs.register_structure_hook(date, lambda dt, _: str(dt))
Now when I destructure an instance of Entries
, the resulting dict
does not have the date
and datetime
objects in string form:
structured = cattrs.structure(data, Entries)
cattrs.unstructure(structured) == {
'entries': {
datetime.date(2024, 1, 1): {
'score': {
'added': datetime.datetime(2023, 2, 3, 4, 3),
'value': 80.3
},
'tags': ['meatball', 'salami', 'jerky'],
'added': None
}
}
}
How can I get cattrs to stringify date
and datetime
objects recursively?
Your hooks don't seem correct. Your unstructure hooks should make the types simpler (so, from datetimes to strings), and your structure hooks should add structure to the data (so, from strings to datetimes).
So, for the unstructure hooks:
# Takes a datetime, returns a string
cattrs.register_unstructure_hook(datetime, lambda dt: dt.strftime("%Y-%m-%d %H:%M:%S"))
# Takes a date, returns a string
cattrs.register_unstructure_hook(date, lambda dt: dt.strftime("%Y-%m-%d"))
and for the structure hooks:
# Takes a string, returns a datetime
cattrs.register_structure_hook(
datetime, lambda dt, _: datetime.strptime(dt, "%Y-%m-%d %H:%M:%S")
)
# Takes a string, returns a date
cattrs.register_structure_hook(
date, lambda dt, _: datetime.strptime(dt, "%Y-%m-%d").date()
)
After applying these modifications your example seems to work. Good luck!