python-attrs / cattrs

Composable custom class converters for attrs, dataclasses and friends.
https://catt.rs
MIT License
779 stars 108 forks source link

Question: Correct use of converters #511

Closed pohlt closed 4 months ago

pohlt commented 4 months ago

Description

I have to parse/structure incoming JSON strings containing timestamps which I wanted to convert to datetimes. I was struggling with this quite a bit, created some example code to illustrate an issue I was about to post, but finally found a working solution. But I'm still wondering if this is the correct use of the converters.

Here's the code with the three options I tested (including Option C which finally worked):

from datetime import datetime
import attrs
from cattrs.preconf.json import make_converter

@attrs.define
class K:
    # Option A
    timestamp: datetime = attrs.field(converter=datetime.fromisoformat)

    # Option B (VS Code correctly complains that timestamp is not a str, but a datetime object)
    # timestamp: str = attrs.field(converter=datetime.fromisoformat)

    # Option C
    # timestamp: datetime = attrs.field(
    #    converter=lambda s: s if isinstance(s, datetime) else datetime.fromisoformat(s)
    # )

conv = make_converter()

# Call below fails for Option A, because cattrs expects timestamp to be a datetime object.
k = conv.loads("""{"timestamp": "2020-01-01T00:00:00"}""", K)
print(k)
print(conv.unstructure(k))

# Call below fails for Option B, because cattrs thinks that timestamp is a string and does
# not apply the datetime converter hook. Then json complains about dateim not being JSON
# serializable.
print(conv.dumps(k))

print("You must have chosen Option C, because you made it to this line!")

Am I using the preconfigured converters correctly? Is Option C The Right Thing?

Tinche commented 4 months ago

Hi,

Are you very attached to having the converter in the field? I would do this and let cattrs handle the conversion completely:

from datetime import datetime

import attrs

from cattrs.preconf.json import make_converter

@attrs.define
class K:
    timestamp: datetime

conv = make_converter()

k = conv.loads("""{"timestamp": "2020-01-01T00:00:00"}""", K)
print(k)
print(conv.unstructure(k))
pohlt commented 4 months ago

See, I knew there had to be a better solution! 😜 Thanks a lot, @Tinche!

Tinche commented 4 months ago

No probs, let me know if you have other questions!