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
Allow for passing keyword arguments to from_orm #3375
Conversation
You can already use Field(alias='whatever') on the model to allow for converting from ORM models that might be named differently, but this means that the model knows about the existence of a database (or at least it's columns), which seems like a leaky abstraction. With this modification though, the database will be the one to handle the naming conflict, which keeps the model more succinct.
please review |
This feature is VERY much appreciated. If my ORM model has A, B, C..., W and X fields but I want the resulting PydanticModel to have an extra Y and Z fields, I would've needed to:
And if you need a async function result the only headacheless solution is the second one. But just this one feature and all of these are solved. Have to point out though that the kwargs overrides the original ORM model fields, e.g.:
|
LGTM, please can you update the docs including an example. Please update. |
@samuelcolvin Docs have been updated |
ping @samuelcolvin |
@samuelcolvin is there anything else needed for this PR to be merged? |
pinging for relevancy |
Also pinging for relevancy |
I'll try to merge for v1.10 soon. |
There is an issue with this approach when an ORM object has descriptors (
|
@hauh is correct, if we're to support this, we would need to modify Please update. |
…o from_orm_kwargs
please update. |
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.
Looking good, just a few small things to fix. Please update.
Please remember to use the manage phrase described in the PR template to assign us once this is ready to review, otherwise it'll be missed.
pydantic/utils.py
Outdated
if self._kwargs: | ||
try: | ||
return self._kwargs[key] | ||
except KeyError: | ||
pass |
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.
Not sure this needs to be in the try:
block?
Also this code isn't covered at all which is why tests are failing.
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.
If there are kwargs
, but the key isn't contained, we have to fallback on the getattr()
call. I updated it to the following:
if self._kwargs and key in self._kwargs:
return self._kwargs[key]
return getattr(self._obj, key)
The try
block was one way, this is another way. Is there a better way you think?
please review. I don't know why the doc building is failing now, worked last commit, and works on my local machine. |
The docs build is failing because #4339 was merged. It'll be failing locally and the error message explains how to fix it, so should be easy to fix. Please update. |
```py | ||
{!.tmp_examples/models_orm_mode_kwargs.py!} | ||
``` | ||
_(This script is complete, it should run "as is")_ |
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.
@dosisod, this should fix docs build:
```py | |
{!.tmp_examples/models_orm_mode_kwargs.py!} | |
``` | |
_(This script is complete, it should run "as is")_ | |
{!.tmp_examples/models_orm_mode_kwargs.md!} |
You can test this locally as well, but you will need to pull changes from remote.
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.
@samuelcolvin, please beware that this PR introduces a breaking change
This was previously a valid usecase, but with these changes it will raise an error because second positional argument is now always passed to getter_dict
:
def test_custom_getter_dict():
class TestCls:
x = 1
y = 2
def custom_getter_dict(obj):
assert isinstance(obj, TestCls)
return {'x': 42, 'y': 24}
class Model(BaseModel):
x: int
y: int
class Config:
orm_mode = True
getter_dict = custom_getter_dict
model = Model.from_orm(TestCls())
assert model.dict() == {'x': 42, 'y': 24}
if not cls.__config__.orm_mode: | ||
raise ConfigError('You must have the config attribute orm_mode=True to use from_orm') | ||
obj = {ROOT_KEY: obj} if cls.__custom_root_type__ else cls._decompose_class(obj) | ||
obj = {ROOT_KEY: obj} if cls.__custom_root_type__ else cls._decompose_class(obj, kwargs) |
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.
Seems like, kwargs
will be silently ignored if cls.__custom_root_type__
is true. I don't think it should be the case.
if isinstance(obj, GetterDict): | ||
return obj | ||
return cls.__config__.getter_dict(obj) | ||
return cls.__config__.getter_dict(obj, kwargs) |
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.
This is a breaking change and should be reflected in docs and history.
if self._kwargs and key in self._kwargs: | ||
return self._kwargs[key] |
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.
IMO, this should be out of try/except AttrubuteError
block.
And perhaps we should ask for forgiveness, not permission (like you've done it in get
method)?
return getattr(self._obj, key) | ||
except AttributeError as e: | ||
raise KeyError(key) from e | ||
|
||
def get(self, key: Any, default: Any = None) -> Any: | ||
if self._kwargs: |
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.
IMO, The way of getting key out of kwargs should match one in __getitem__
Thanks so much @Bobronium for looking into this. I wonder if we should delay this feature to V2? @Bobronium @dosisod @PrettyWood @hramezani what do you think? |
It's one option. Another is checking if if "kwargs" not in inspect.signature(config.getter_dict).parameters:
orig_getter_dict = config.getter_dict
@functools.wraps(orig_getter_dict)
def updated_getter_dict(obj, kwargs):
return orig_getter_dict(obj)
config.getter_dict = updated_getter_dict Or perhaps, not changing the All this seems not quite right though, but I don't have a perfect solution in mind. |
The best option long term is pydantic/pydantic-core#223, I think that there's quite an elegant solution in rust and we don't have to worry about breaking changes. My worry is that any solution for this in V1.10 is either going to be hacky or a break changing in some edge case, or both. I'm leaning towards closing this and waiting for V2, although I feel pretty guilty for @dosisod who has worked on this a lot and responded to my feedback, they would understandably find it pretty annoying if this PR gets closed now. 🙏 |
computed fields PR will probably be closed so trust me I know the feeling 😆 but this is part of the deal when working on OSS. If there is a way to avoid any breaking change like @Bobronium suggested let's go for it. But if there is a risk of breaking change I would defer this PR. I would love 1.10 to be a smooth and stable release before the big bang |
@samuelcolvin I don't mind closing this PR, this feature seems to be introducing more friction then a feature of this size should introduce. And, if there are plans to handle this in v2, we might be better off waiting. Thank you all for your feedback and support! |
Thanks so much for understanding, we'll do our best to make this happen in V2. |
Change Summary
You can already use
Field(alias='whatever')
on the model to allow forconverting from ORM models that might be named differently, but this
means that the model knows about the existence of a database (or at
least it's columns), which seems like a leaky abstraction.
With this modification though, the database will be the one to handle
the naming conflict, which keeps the model more succinct.
Related issue number
None that I can find
Checklist
changes/<pull request or issue id>-<github username>.md
file added describing change(see changes/README.md for details)