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
Does pydantic.dataclasses.dataclass
support classic mapping in SQLAlchemy?
#1089
Comments
Best to use ORM mode. I have no personal interest in ORMs or supporting them - long term they're a mistake, you'd do much better to just write SQL. However if there's some easy way to support |
Thank you for the quick response! |
Django's ORM is kind of a special case, since:
Good luck with |
From my personal experience, the major difference between SQLAlchemy and Django ORM is really the difference between Active Record and Data Mapper. (Or maybe I am just not experienced enough to spot/appreciate the others lol. 🤦♂️) I really disliked SQLAlchemy when I first started Python web dev in Flask & Flask-SQLAlchemy and came to like Django ORM quite a lot when my new job uses Django. But using Django ORM means a tight data coupling. With some background in .NET Core and EF Core and recently learning more about software architecture, I slowly come to appreciate many design choices SQLAlchemy makes. The syntax is not the best for sure, but many things just make more sense now. Edit: SQLAlchemy Core also has some very useful tools for building queries, including helpers for using textual SQL. |
i have closed my other issue (#1403) and would like to add my request here. |
a good compromise would be if pydantic could take a python standard dataclass and convert it into a new dataclass made of pydantic dataclasses.
|
Hi! This is really interesting feature, and I would like to give it some thought. I was playing around with sqlalchemy mapper and pydantic, so the main problem was:
This will cause some problems with Yes, sqlalchemy users would have to rewrite |
That's an interesting idea.
Could you illustrate with a complete example?
…On Tue, 21 Apr, 2020, 11:38 Denis Surkov, ***@***.***> wrote:
Hi!
This is really interesting feature, and I would like to give it some
thought. I was playing around with sqlalchemy mapper and pydantic, so the
main problem was:
@no_type_check
def __setattr__(self, name, value):
...
if not hasattr(self, '__fields_set__'):
object.__setattr__(self, '__fields_set__', set())
This will cause some problems with *eq*, because *eq* checks all field,
but sqlalchemy loads some private attributes. @samuelcolvin
<https://github.com/samuelcolvin> , what do you think about this
approach? As I see no tests failed.
Yes, sqlalchemy users would have to rewrite *eq* of their pydantic
BaseModels, but I think it's much easier for developers.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#1089 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAASYU7YJP5V2FRZ7JZ7YOTRNUZ5LANCNFSM4JYCWC2A>
.
|
Some dirty hacks, but I hope you understand the idea.
|
It is indeed quite an interesting use-case to use Model definitions: from pydantic.dataclasses import dataclass as pydantic_dataclass
from dataclasses import dataclass
@pydantic_dataclass
class PydanticRequest:
test: confloat(ge=10, le=100)
@dataclass # This then takes the annotations and builds a new dataclass ;)
@duplicates_pydantic_dataclass(PydanticRequest) # This prepares __annotations__ dynamically
class Request:
pass And the decorator which does the job def duplicates_pydantic_dataclass(pydantic_cls):
def wrapper(cls):
cls.__annotations__ = {}
for field_key, field_value in pydantic_cls.__dataclass_fields__.items():
cls.__annotations__[field_key] = field_value.type
return cls
return wrapper This way I transparently use pydantic for validations pr = PydanticRequest(test=11.0) sqlalchemy mappers are defined for For CRUD operation, I have the following: from dataclasses import asdict
def instantiate_with_data(cls, instance):
return cls(**asdict(instance))
>>> pr = PydanticRequest(test=11.0)
>>> r = instantiate_with_data(Request, pr)
>>> session.add(r) Although, it suits alright for simple objects and mappings |
Just to add since it isn't mentioned here: Since v1.7 pydantic has supported automatically creating pydantic dataclasses for stdlib dataclasses, see #1817 |
I think I've followed most of the discussion so far, but I don't think that supporting pydantic dataclass creation from a standard dataclass would solve my problem, as, I like pydantic dataclasses and am using them as a part of my validation elsewhere, I'd rather keep pydantic dataclasses with extra validation, and hopefully stick as close as possible to PEP 557 in terms of keeping dataclasses as close as possible to "normal" python classes and not interfering with the usage of the class otherwise. A minimal (ish) test case for my issue is here. As far as I can tell, there is something going on with the implementation of a pydantic dataclass which alters the way that SQLAlchemy sets up either the new or init methods on new instances of a model which means that a new instance of a model has no This feels fixable, even if just by some kind of monkeypatch via a class decorator, for now, in advance of a proper PR to fix the issue in the future. I thought it was to do with the @samuelcolvin I understand your opinions/frustrations on ORMs, and in the past have shared them (and indeed, now do share some), it's an interesting academic argument between the relative merits of mappers/active record, and about whether or not ORMs are worth the complexity that they add. My hot take is that they are, I like to reason about my database structures as python objects to automate most things, and have my team focus on spending time writing more complex edge cases (i.e., writing SQL that matters). This is actually something that - given our geographies - I'd love to chat about over a beer in London sometime. That said, SQLAlchemy is one of two leading ORMs used by Pythonistas around the world, follows a sensible architecture pattern, is well supported and is basically essential to the python ecosystem. Pydantic is also a popular library, and on the up, so easier integration between the two seems like a good idea. It's also blocking a couple of my projects at the moment so is something I want to fix, at least for my code, ASAP! I would be more than happy (read: keen) to put together a draft PR in either SQLAlchemy or pydantic, or at least post some gist about how to fix and get around this issue, but could do with a pointer to exactly where the issue is currently. If anyone can add some detail to this issue before tomorrow afternoon, I would be enormously grateful, else, I'll update once I've had chance to find out what the exact issue is. |
i dont have much resources to look into this but when I did, the main thing I was observing was that pydantic seems to want to write into |
Thank-you @zzzeek this is very helpful and interesting and I really appreciate your time! If it's easy do you have a source code/docs link to the SQLA use of the descriptor protocol? I've been a long-time introspector of SQLA for how it defines my models/tables but I'm a bit hazy on exactly how it works in relation to instances and it would give me a headstart to have some docs on the ideas behind how this works. The problem you described makes it sound like it could be trickier than planned, but I think the idea behind pydantic dataclasses is that they're very similar to standard dataclasses, but just add a few dunder properties such as Honestly, I'm looking forward to getting stuck into this tomorrow. Any notes / headstarts very much welcome. |
SQLAlchemy's descriptor logic is based on the descriptor class InstrumentedAttribute: https://github.com/sqlalchemy/sqlalchemy/blob/master/lib/sqlalchemy/orm/attributes.py#L447 that's where all userspace attribute operations happen. SQLAlchemy's use of descriptors can be modified highly using some little-used extension points, but still needs some level where the "set" can be intercepted. The attempt I made to make some of this work can be seen at: https://gist.github.com/zzzeek/bcccc70393f14a190e849878ed63e419 |
Thanks Mike - this has been really helpful. Unfortunately I've only been able to look at this late today so will be a day or so until I find a solution to my issue. Based on your code and notes I think the issue is the line below in object.__setattr__(self, '__dict__', d) My implementation of building a dataclass from a pydantic schema is slightly different from your gist, in that I've defined my models as (pydantic) dataclasses, thinking that, as in the spirit of PEP 557 there would be minimal interference between the two libraries on what should be essentially just a plain old python class. However, it seems that since (of course) both libraries have a need to alter behaviour around Thinking aloud, and without chance to properly plan or sketch out a solution, I think a potential longer-term candidate for a fix could be to make pydantic's implementation with respect to dataclasses customizable such that operations which directly alter (and/or access?) I'll take a closer look again tomorrow and sketch out some thoughts as I try to find a solution to my issue. As always, any further input/comments much appreciated. Thanks again for your help @zzzeek, and to both Mike and @samuelcolvin for writing and maintaining such amazing open source libraries. |
The fix (in my example) turns out to be remarkably simple using a monkey patch. I'm not sure whether reassigning |
…ydantic#1089) Co-authored-by: Samuel Colvin <s@muelcolvin.com>
Question
First of all, thank you so much for your awesome job! Pydantic is a very good library and I really like its combination with FastAPI. 🚀
So my question is does
pydantic.dataclasses.dataclass
support classic mapping in SQLAlchemy? I am working on a project and hopefully can build it with clean architecture and therefore, would like to use pydantic's dataclass for my domain model and have SQLAlchemy depend on it.I have a proof of concept code snippet as below, which would work if I use the dataclass from Python's std lib.
Error Output:
Thanks in advance!
The text was updated successfully, but these errors were encountered: