Replies: 36 comments 6 replies
-
|
Have you tried @tiangolo's Pydantic SQLAlchemy ? |
Beta Was this translation helpful? Give feedback.
-
|
Thank you for the contribution, but your proposal is for converting sqlalchemy model to pydantic, I need a way to convert a nested model from pydantic to sqlalchemy. |
Beta Was this translation helpful? Give feedback.
-
|
I am thinking about a recursive function to do what I want. I currently need an external "maping" dict that translates the pydantic sub class to the related sqlalchemy class by their name. Would be helpful if someone could provide an example of a recursive function for two dicts. Added difficulty that sub model could be lists or direct keys. |
Beta Was this translation helpful? Give feedback.
-
|
@j-gimbel I'll play with this more but this is what I came up with. # Too lazy to delete unused imports :(
from typing import Optional, Union, List, Dict, Any, Mapping, Type, TypeVar, Generic
def pydantic_to_sqlalchemy(schema: pydantic.main.ModelMetaclass) -> Any:
__fields_dict__ = {}
def recurse(obj: pydantic.main.ModelMetaclass, temp_key: str = "") -> None:
if isinstance(obj, pydantic.main.ModelMetaclass):
for key, value in obj.schema().items():
recurse(obj=value, temp_key=temp_key + key if temp_key else key)
if isinstance(obj, dict):
for key, value in obj.items():
recurse(obj=value, temp_key=temp_key + key if temp_key else key)
if isinstance(obj, list):
for item in range(len(obj)):
recurse(
obj=obj[item],
temp_key=temp_key + str(item) if temp_key else str(item),
)
else:
__fields_dict__[temp_key] = obj
recurse(schema)
return __fields_dict__Not sure how this would be helpful to you, maybe just for inspiration. But I'll keep playing with this. Out: {'': <class '__main__.SchemaRoot'>,
'definitions': {'SchemaSubBase': {'properties': {'someSubText': {'title': 'Somesubtext',
'type': 'string'}},
'required': ['someSubText'],
'title': 'SchemaSubBase',
'type': 'object'}},
'definitionsSchemaSubBase': {'properties': {'someSubText': {'title': 'Somesubtext',
'type': 'string'}},
'required': ['someSubText'],
'title': 'SchemaSubBase',
'type': 'object'},
'definitionsSchemaSubBaseproperties': {'someSubText': {'title': 'Somesubtext',
'type': 'string'}},
'definitionsSchemaSubBasepropertiessomeSubText': {'title': 'Somesubtext',
'type': 'string'},
'definitionsSchemaSubBasepropertiessomeSubTexttitle': 'Somesubtext',
'definitionsSchemaSubBasepropertiessomeSubTexttype': 'string',
'definitionsSchemaSubBaserequired0': 'someSubText',
'definitionsSchemaSubBasetitle': 'SchemaSubBase',
'definitionsSchemaSubBasetype': 'object',
'properties': {'id': {'title': 'Id', 'type': 'integer'},
'someRootText': {'title': 'Someroottext', 'type': 'string'},
'subData': {'default': [],
'items': {'$ref': '#/definitions/SchemaSubBase'},
'title': 'Subdata',
'type': 'array'}},
'propertiesid': {'title': 'Id', 'type': 'integer'},
'propertiesidtitle': 'Id',
'propertiesidtype': 'integer',
'propertiessomeRootText': {'title': 'Someroottext', 'type': 'string'},
'propertiessomeRootTexttitle': 'Someroottext',
'propertiessomeRootTexttype': 'string',
'propertiessubData': {'default': [],
'items': {'$ref': '#/definitions/SchemaSubBase'},
'title': 'Subdata',
'type': 'array'},
'propertiessubDataitems': {'$ref': '#/definitions/SchemaSubBase'},
'propertiessubDataitems$ref': '#/definitions/SchemaSubBase',
'propertiessubDatatitle': 'Subdata',
'propertiessubDatatype': 'array',
'required0': 'someRootText',
'required1': 'id',
'title': 'SchemaRoot',
'type': 'object'} |
Beta Was this translation helpful? Give feedback.
-
|
Tortoise has some stuff with pydantic conversion. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you both, I will check this out tomorrow. |
Beta Was this translation helpful? Give feedback.
-
|
Hey @j-gimbel ! Have you managed to find a solution? 👀 |
Beta Was this translation helpful? Give feedback.
-
|
Hey @bazakoskon, maybe you have managed to find a solution? 👀 |
Beta Was this translation helpful? Give feedback.
-
|
Hello, I reused the "_declarative_constructor" function code (which is the by-default init method for SQLAlchemy Base class) I guess the overrided "_declarative_constructor" function can be generalized (and used as "contructor" parameter when calling declarative_base()) to handle automatically iterables found in the list of kwargs, based on the class relationships. EDIT: |
Beta Was this translation helpful? Give feedback.
-
I did check this for nested object and it doesnt work |
Beta Was this translation helpful? Give feedback.
-
Would be thankful if u can add some full example,i tried to implement it and couldn't make it work ,thanks |
Beta Was this translation helpful? Give feedback.
-
Did u manage to find for this a solution? |
Beta Was this translation helpful? Give feedback.
-
|
@avico78 Please note that the proposed implementation requires that nested models are in the same module as parent models, as you can see here: But this can be easily modified if needed |
Beta Was this translation helpful? Give feedback.
-
|
@scd75 - great work man! I've tested with ~3-4 level of json level depth and it work perfectly, |
Beta Was this translation helpful? Give feedback.
-
|
@scd75 - found some issue , And by the pyadntic class we define the relation as one-->one meaning the json would look like: It failed with : Maybe im wrong , but seems it doesn't cover the case of nested dictionaries? |
Beta Was this translation helpful? Give feedback.
-
|
@scd75 , "if we actually made this a feature, then it would be a magnet for new user requests and issues that would be better solved if they just made their own constructor that does what they want. I'm still struggling making it work, following your suggestion I added debug print: from sqlalchemy.ext.declarative import declarative_base
import sys
def _declarative_constructor_auto_instantiate_nested(self, **kwargs):
cls_ = type(self)
relationships = self.__mapper__.relationships
for k in kwargs:
if not hasattr(cls_, k):
raise TypeError(
"%r is an invalid keyword argument for %s" % (k, cls_.__name__)
)
if k in relationships.keys():
if relationships[k].direction.name == 'ONETOMANY':
print("Current Key:",k,"relationships_direction:",relationships[k].direction.name,"argument:",relationships[k].argument)
#childclass = getattr(sys.modules[self.__module__], relationships[k].argument)
#setattr(self, k, [childclass(**elem) for elem in kwargs[k]])
else:
setattr(self, k, kwargs[k])
_declarative_constructor_auto_instantiate_nested.__name__ = "__init__"
Base = declarative_base(
constructor=_declarative_constructor_auto_instantiate_nested)
router (i commented some part to see the print of relationships[k].argument): def db_add_nested_data_pydantic_generic(db: Session, root: SchemaCustomerBase):
# this fails:
db_root = CustomerModel(**root.dict())
# db.add(db_root)
# db.commit()
# db.refresh(db_root)
return db_root
@app_sa_router.post("/addNestedModel_pydantic_generic")
def add_nested_data_pydantic_generic(root: SchemaCustomerBase, db: Session = Depends(get_db)):
print("im root",root)
data = db_add_nested_data_pydantic_generic(db=db, root=root)
return {"ok":1}And I get #coming from router
im root customer_no=0 first_name='string' last_name='string' subscriber=[SchemaSubscriberBase(subscriber_no=0)]
#coming from function
Current Key: subscriber relationships_direction: ONETOMANY argument: <bound method _class_resolver._resolve_name of <sqlalchemy.ext.declarative.clsregistry._class_resolver object at 0x7f060942dbe0>>Any idea? |
Beta Was this translation helpful? Give feedback.
-
|
@avico78 @scd75 Last few days I have created a library for this, mostly because I wanted to learn how to do that. It seems I went with a different approach. Please let me know what you think, feedback is greatly appriciated. Github: https://github.com/Wouterkoorn/sqlalchemy-pydantic-orm |
Beta Was this translation helpful? Give feedback.
-
|
@Wouterkoorn - tnx for your update i surely check it, |
Beta Was this translation helpful? Give feedback.
-
|
Hi 👋🏻 I ended up arriving to this issue when looking for ways to enable nested model auto-instantiation in my SQLAlchemy projects. My comment here is not strictly related to FastAPI, but could improve the solution provided in @scd75 's comment. My proposal includes two major changes:
In order to define our custom constructor as a common Proposed constructor: from sqlalchemy.orm import ONETOMANY
from sqlalchemy.orm import declarative_base
Base = declarative_base(constructor=None)
class BaseModel:
"""Base class for all the Python data models"""
def __init__(self, **kwargs):
"""
Custom initializer that allows nested children initialization.
Only keys that are present as instance's class attributes are allowed.
These could be, for example, any mapped columns or relationships.
Code inspired from GitHub.
Ref: https://github.com/tiangolo/fastapi/issues/2194
"""
cls = self.__class__
model_columns = self.__mapper__.columns
relationships = self.__mapper__.relationships
for key, val in kwargs.items():
if not hasattr(cls, key):
raise TypeError(f"Invalid keyword argument: {key}")
if key in model_columns:
setattr(self, key, val)
continue
if key in relationships:
relation_dir = relationships[key].direction.name
relation_cls = relationships[key].mapper.entity
if relation_dir == ONETOMANY.name:
instances = [relation_cls(**elem) for elem in val]
setattr(self, key, instances)
class ChildModel(Base, BaseModel):
...
class ParentModel(Base, BaseModel):
... |
Beta Was this translation helpful? Give feedback.
-
Hi @Sinclert , thanks for your comment. as in here: |
Beta Was this translation helpful? Give feedback.
-
|
Hey @scd75 , I believe the is no technical difference. However, defining the constructor as you would normally do with any other |
Beta Was this translation helpful? Give feedback.
-
Not sure If I am wrong. And I always get a Also, the official document said: Within the ORM, “one-to-one” is considered as a convention where the ORM expects that only one related row will exist for any parent row. So I guess ONETOONE is also regarded as ONETOMANY in Sqlachemy ? And I slightly change the code using |
Beta Was this translation helpful? Give feedback.
-
|
@yinzixie I think you are right. There is no In my initial testing, all the For instance, I noticed that the relationship constructor receives an argument called |
Beta Was this translation helpful? Give feedback.
-
|
@tiangolo Can we sponsor a fix to this issue or have you opine here? This issue is perhaps the messiest part of this whole FastApi framework. We have SqlAlchemy -> Pydantic models working very nicely, but going the other direction is involved -- especially when you throw in relationships and composite primary keys. We have a modification of https://github.com/Wouterkoorn/sqlalchemy-pydantic-orm in the works to see if that will solve our issue (adding non "id" pk support, and adding composite pks). |
Beta Was this translation helpful? Give feedback.
-
|
Agree with @don4of4. I tried @Wouterkoorn solution and it works great with nested models. @tiangolo please take a look on this if you can. |
Beta Was this translation helpful? Give feedback.
-
You're also welcome to make a PR. I'll be happy to spend some more time on it as well, it was indeed just a first POC version (although we do use it in production) |
Beta Was this translation helpful? Give feedback.
-
|
I used __ init__ method in the BaseModel class to convert the nested Dictonary into an ORM object. |
Beta Was this translation helpful? Give feedback.
-
|
From the moment I created my PoC, my idea was to integrate it into https://github.com/tiangolo/pydantic-sqlalchemy. I have no experience with SQLModel, is the topic of this discussion still an issue using that library? I could see this being solved by not needing to switch between models anymore |
Beta Was this translation helpful? Give feedback.
-
|
Thats why I love Django framework... |
Beta Was this translation helpful? Give feedback.
-
|
Hi 👋 everyone. Is there an update on this? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
First check
Example
Description
My Question is:
How to make nested sqlalchemy models from nested pydantic models (or python dicts) in a generic way and write them to the datase in "one shot".
My example model is called "root model" and has a list of submodels called "sub models" in "subData" key.
Please see above for pydantic and sql alchemy definitions.
Example:
The user provides a nested json string:
Open the browser and call the endpoint
/docs.You can play around with all endpoints and POST the json string from above.
/addNestedModel_pydantic_generic
When you call the endpoint /addNestedModel_pydantic_generic it will fail, because sqlalchemy cannot create the nested model from pydantic nested model directly:
AttributeError: 'dict' object has no attribute '_sa_instance_state'/addSimpleModel_pydantic
With a non-nested model it works.
The remaining endpoints are showing "hacks" to solve the problem of nested models.
/addNestedModel_pydantic
In this endpoint is generate the root model and andd the submodels with a loop in a non-generic way with pydantic models.
/addNestedModel_pydantic
In this endpoint is generate the root model and andd the submodels with a loop in a non-generic way with python dicts.
My solutions are only hacks, I want a generic way to create nested sqlalchemy models either from pydantic (preferred) or from a python dict.
Environment
Beta Was this translation helpful? Give feedback.
All reactions