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
ORM mode: Add support for arbitrary class instances #562
Conversation
Codecov Report
@@ Coverage Diff @@
## master #562 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 15 15
Lines 2508 2531 +23
Branches 501 503 +2
=====================================
+ Hits 2508 2531 +23 |
pydantic/main.py
Outdated
|
||
@classmethod | ||
def _decompose_class(cls, obj: Any) -> 'DictStrAny': | ||
return obj.__dict__ |
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 you don't want to (or can't) use .__dict__
, having this as a separate function means you can override _decompose_class
and build a dict some other way.
pydantic/main.py
Outdated
exc = TypeError(f'{cls.__name__} expected dict not {type(obj).__name__}') | ||
raise ValidationError([ErrorWrapper(exc, loc='__obj__')]) from e | ||
else: | ||
obj = cls._decompose_class(obj) |
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.
need to catch attribute error at least here.
Thank you! Let me pull it and play with it. |
Thanks for your effort on this. I have a couple of observations. An ORM with
|
First of all, you don't need all that logic, because the dict returned by So the code above, just becomes @classmethod
def _decompose_class(model, obj: Any):
return model.__fields__ We could alternatively use something like: @classmethod
def _decompose_class(model, obj: Any):
return {name: getattr(obj, name) for name in dir(obj)} But that would have other problems. We could also look for a custom method on a class like I'll look throught the rest of what you've said when I get a chance. |
Hmm, I'm probably not understanding how to use I tried replacing the example from above with both suggestions and testing it (with the same test in the comment above) and it didn't work (I got testing errors). The single line version I can get working, not breaking @classmethod
def _decompose_class(model, obj: Any):
return {field.name: getattr(obj, field.alias, _missing) for field in model.__fields__.values() if getattr(obj, field.alias, _missing) is not _missing} But that's quite long. And it doesn't have into account Hmm, so
Thank you. |
I think you're confused here Here's an example of it working with sqlalchemy: from typing import List
from devtools import debug
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr
Base = declarative_base()
class Company(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
co = Company(id=123, public_key='foobar', name='Testing', domains=['example.com', 'foobar.com'])
debug(co, co.__dict__)
class CompanyModel(BaseModel):
id: int
public_key: constr(max_length=20)
name: constr(max_length=63)
domains: List[constr(max_length=255)]
debug(CompanyModel.parse_obj(co)) outputs: Which is surely what you want? |
|
Ah, yes, I think I understand. The first parameter is the same Pydantic class itself (being a Generating a The
In general, things that were fetched/generated by SQLAlchemy on attribute access, with But yes, models with normal columns worked OK, more or less. |
Ok, what about this? I'm now wrapping classes in Unfortunately there's no obvious way to check if something is an instance of some class to process or a simple variable like Does this work better? If so can you live with |
This looks great, thank you! I'm finishing a big uncommitted mess/work in progress in FastAPI, so I haven't been able to install it and test it locally with the changes in FastAPI I had created for it. But as soon as I finish this mess (commit/discard) I'll switch to that branch and test it. |
I was finally able to test this properly for the last 2 hours or so. With this, I can solve all the use cases I can imagine. Including SQLAlchemy hybrid properties and others. That's awesome. Thank you! 🎉 🚀 🍰 And yes, I think |
great news! I'm going to release I'll then work on completing this and we an add it to the next release. |
Cython! I'm pretty excited about that too. 🚀 That's awesome! Thank you for this. I know you're not into ORMs, but thanks for putting the effort into finding a way to solve it 🎉 🍰 I think there's quite a bunch of people that will be excited about it 😄 |
@tiangolo please review and let me know if you're happy with this. |
Great! I'm on it. |
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 awesome! 🌮 🎉
A couple of minor things that I'm also OK without them.
Apart from that, LG(reat)TM.
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.
Awesome! 🎉
LGTM
🎉 This is great! Thanks! I know several people that will celebrate it. 🎉 🍰 |
|
||
@classmethod | ||
def _decompose_class(cls: Type['Model'], obj: Any) -> GetterDict: | ||
return GetterDict(obj) |
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 this entire PR is extremely clever, really awesome job and solution even though you don't like ORMs!
My take on ORM support. @tiangolo please let me know if this would work for you?
This is just a WIP, will need more tests and docs but I want your opinion on it before I got any further.
Related issue number
replace #520
Checklist
HISTORY.rst
has been updated#<number>
@<whomever>