-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add computed fields #2625
Conversation
b4ed70b
to
11d6ac9
Compare
11d6ac9
to
9d8bd6e
Compare
80363ea
to
54e23c5
Compare
7d8521c
to
83a3916
Compare
Writing only If TYPE_CHECKING:
computed_field = property won't work with kwargs like This is why I chose to keep the combo If we want to change the API and need to work on a better mypy support fine but I won't do it myself sorry: I have not the will nor the energy to work on the mypy plugin since it's a lot of work for IMHO not a big gain. |
9926afb
to
3f775d1
Compare
When reference a property with @comuted_field annotation, the type hint of the reference is gone. I'm not sure if the bug comes from this branch or the E.g.: @property
def A(self) -> int:
print(self.B) # actual is "(method) B: Any", expect is "(property) B: List[float]"
return 1
@computed_field
@property
def B(self) -> List[float]:
return [] Editor version info:
Pydantic version info:
|
any reason there hasnt been movement on this in a while? are we ever going to get it? |
@yicone, can reproduce. Also can fix by replacing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes issue described in #2625 (comment)
Tested on VSCode, please also confirm that there's no regression in PyCharm.
|
||
@overload | ||
def computed_field( | ||
func: Any, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func: Any, | |
func: T, |
exclude: Optional['ExcludeInclude'] = None, | ||
include: Optional['ExcludeInclude'] = None, | ||
repr: bool = True, | ||
) -> Any: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
) -> Any: | |
) -> T: |
|
||
|
||
def computed_field( | ||
func: Optional[Any] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func: Optional[Any] = None, | |
func: Optional[T] = None, |
exclude: Optional['ExcludeInclude'] = None, | ||
include: Optional['ExcludeInclude'] = None, | ||
repr: bool = True, | ||
) -> 'Union[Callable[[Any], ComputedField], ComputedField]': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
) -> 'Union[Callable[[Any], ComputedField], ComputedField]': | |
) -> 'Union[Callable[[T], ComputedField], ComputedField]': |
@pawelswiecki I'd suggest not to dig down into the rabbit hole when it comes to the features that can be implemented after the release. I think changes should be gradual and easy to make. It's possible to add support for Let's focus on things that can't be easily changed/fixed after release. As for your example, let me suggest this (click here):from enum import Enum
from typing_extensions import Self
from pydantic import BaseModel, Field
class EventType(Enum):
JOB_STARTED = "job_started"
JOB_FINISHED = "job_finished"
class BaseEvent(BaseModel):
id: str
event_type: EventType
def __init_subclass__(cls, **kwargs) -> None:
return super().__init_subclass__(**kwargs)
if (event_type_field := cls.__fields__.get('event_type')) is None or not event_type_field.field_info.const:
raise NotImplementedError('You must define event_type for concrete event')
@classmethod
def create(cls, **data) -> Self:
id_ = "generated id"
return cls(id=id_, **data)
class JobStartedEvent(BaseEvent):
event_type = Field(EventType.JOB_STARTED, const=True)
class JobFinishedEvent(BaseEvent):
event_type = Field(EventType.JOB_FINISHED, const=True)
print(JobFinishedEvent.create().event_type) # EventType.JOB_FINISHED
class InvalidEvent(BaseEvent): ... # NotImplementedError: You must define event_type for concrete event |
To summarize, things to do before merging:
Is there something else I'm missing? sorry for so many comments |
@Bobronium thanks for the recap. I'll work on it this weekend. Sorry for being inactive, just a lot on my plate with both professional and personal matters |
@prettywoo, can relate. Please take care! Also, feel free to ask for any help if needed. |
I am so excited for this feature to be implemented.
Yes @Bobronium, things to consider:
Thank you all so much for your hard work, it really does make my life much better at the very least. I can also help! |
Suggestion: I don't disagree with this, and think it makes most sense: ...
@computed_field
@cached_property
def hard_compute():
<very hard problem>
... However, I suggest that we also introduce shorthand notation for @cached_computed_field_property
... And: @computed_field_property
... This is nice when |
Feature suggestion: I realize that this is out of scope for this feature, but perhaps use the underlying backend for @computed(field_a, field_b, ...)
@jit
def some_func(field_a, field_b, ...):
... |
What about something like?
|
@bjmc, that is also an option. Where ...
@computed_field
@property
def hard_compute():
<very hard problem>
... Hence, my original suggestion. |
This won't be added for v1.10. I don't know if we can use some of this logic in V2 or whether we'll need a new implementation. |
@Bobronium Thank you for your suggestion, with the working code. Looking good, much appreciated! |
For those waiting for this feature, it will be 100% added in v2! EDIT: I'll make sure #2625 (comment) and #2625 (comment) are checked 😉 |
Wow. Looks like pylance (pyright) supports custom defined properties through simple descriptor typing. I've managed to create a from __future__ import annotations
from random import randint
from typing import Any, Callable, Generic, NoReturn, TypeVar, overload
_G1 = TypeVar("_G1")
_G2 = TypeVar("_G2")
_S1 = TypeVar("_S1")
_S2 = TypeVar("_S2")
class computed_field(Generic[_G1, _S1]):
fget: Callable[[Any], _G1] | None
fset: Callable[[Any, _S1], None] | None
fdel: Callable[[Any], None] | None
@overload
def __new__(
cls,
fget: Callable[[Any], _G2],
fset: None = ...,
fdel: Callable[[Any], None] | None = ...,
doc: str | None = ...,
) -> computed_field[_G2, NoReturn]:
...
@overload
def __new__(
cls,
fget: Callable[[Any], _G2],
fset: Callable[[Any, _S2], None],
fdel: Callable[[Any], None] | None = ...,
doc: str | None = ...,
) -> computed_field[_G2, _S2]:
...
@overload
def __new__(
cls,
fget: None = ...,
fset: None = ...,
fdel: Callable[[Any], None] | None = ...,
alias: str | None = ...,
title: str | None = ...,
description: str | None = ...,
exclude: set[str] | None = ...,
include: set[str] | None = ...,
repr: bool = True,
doc: str | None = ...,
) -> computed_field[NoReturn, NoReturn]:
...
def __call__(self: computed_field[NoReturn, NoReturn], fget: Callable[[Any], _G2]) -> computed_field[_G2, NoReturn]:
...
def getter(self, __fget: Callable[[Any], _G2]) -> computed_field[_G2, _S1]:
...
def setter(self, __fset: Callable[[Any, _S2], None]) -> computed_field[_G1, _S2]:
...
def deleter(self, __fdel: Callable[[Any], None]) -> computed_field[_G1, _S1]:
...
def __get__(self, __obj: Any, __type: type | None = ...) -> _G1:
...
def __set__(self, __obj: Any, __value: _S1) -> None:
...
def __delete__(self, __obj: Any) -> None:
...
class Square:
side: float
@computed_field
def area(self) -> float:
return self.side**2
@computed_field(alias="The random number")
def random_n(self) -> int:
return randint(0, 1_000)
reveal_type(Square().area)
reveal_type(Square().random_n) Type of "Square().area" is "float"
Type of "Square().random_n" is "int" Unfurtunately, both Actually, mypy also is able to infer properties types here! |
Another thing that I thought of is to include a keyword argument for computing all computed fields when running Example: model.dict(call_computed_fields=True) Will evaluate and provide |
@PrettyWood Any update on this feature, think it is a great addition and gets rid of accomplishing this in validators. Thank you! |
* implement computed fields 🎉 * dataclass properties * catch errors in properties * fix include and exclude * add example from pydantic/pydantic#2625, fix by_alias * fix on older python
Thank you all (especially @PrettyWood) for 2 years (! 🙈) of patience. I've migrated this feature to pydantic V2 and pydantic-core, see #5502. Thank you so much @PrettyWood for your work on this, I've used some of your code, and most of your tests and docs in the new PR 🙇 🙏. |
Change Summary
I'm waiting for feedback on the API and doc 🙏
property
-like syntaxComputedField
syntaxSee the tests for more examples
Related issue number
closes #935
closes #1241
closes #2313
Checklist
changes/<pull request or issue id>-<github username>.md
file added describing change(see changes/README.md for details)