ariebovenberg / whenever

⏰ Modern datetime library for Python
https://whenever.rtfd.io
MIT License
860 stars 13 forks source link

pydantic compatible types #175

Open xneanq opened 1 day ago

xneanq commented 1 day ago

Hi I was fiddling around trying to implement pydantic compatible subclasses of whenever classes potentially to add to pydantic-extra-types but they're marked @final

What's the reason for them to be @final?

Any chance you would implement pydantic methods or lift the @final restriction?

ariebovenberg commented 1 day ago

Hi @xneanq, thanks for opening this issue.

Regarding @final: they are marked this way for two reasons:

  1. In my experience, designing a class for subclassing requires special attention (i.e. documenting what you can override, what parts are stable, method call order, etc.). Since all classes in whenever are designed as concrete classes, it was most straightforward to simply mark them as "final". It prevents subclassing since the classes aren't designed for it.
  2. The rust extension is able to optimize performance in some cases because there are no unexpected subclasses.

That said, Pydantic compatibility seems like a good idea, considering its popularity 👍 . What would needed to be implement in order to be pydantic-compatible? Perhaps some kind of patching can be done 🤔

illphil commented 1 day ago

looking at the pendulum_dt.py in pydantic-extra-types it looks like a few methods would need to be implemented which look fairly simple

I'm not sure the neatest was to proceed, my first thoughts revolve around a mixin class implementing the required methods conditional on pydantic being available perhaps via extras

ariebovenberg commented 1 day ago

At the moment, my preferred way would be to add the required methods directly to the whenever library.

This may take some time to implement though, as I want to do make sure it integrates well with pydantics customization options.

If you need pydantic support today, the best solution would be to use Annotated, as described here

ariebovenberg commented 1 day ago

Looking at pendulum_dt more closely, you should be able to build a similarly functioning whenever plugin without subclassing:

# pydantic_extra_types/whenever.py
from whenever import LocalDateTime as _LocalDateTime
from typing import Annotated

class _LocalDateTimePydanticAnnotation:
    @classmethod
    def __get_pydantic_core_schema__(...): ...
    @classmethod
    def __get_pydantic_json_schema__(...): ...

LocalDateTime = Annotated[_LocalDateTime, _LocalDateTimePydanticAnnotation]

the result:

from pydantic_extra_types.whenever import LocalDateTime

class MyModel(Model):
    x: LocalDateTime

# still usable as a class
LocalDateTime.MIN  # still works
LocalDateTime(2020, 1, 1)  # also works

note that in the long term, it'd always be nicer to do:

from whenever import LocalDateTime

class MyPydanticModel(Model):
    x: LocalDateTime  # usable directly
illphil commented 1 day ago

sounds like you've got the next release in the bag ;)

ariebovenberg commented 1 day ago

@illphil well, I suppose it's a two-phase thing:

  1. add the Annotated thing to pydantic_extra_types. @illphil @xneanq feel free to take this up. No changes to whenever needed.
  2. in the long term, add support to whenever directly. However, seeing as option (1) is so reasonable, it's unsure if this would even be needed.