Skip to content
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

Insert object with Many-to-many relatioship via sqlalchemy #159

Open
micoloth opened this issue Jun 15, 2022 · 2 comments
Open

Insert object with Many-to-many relatioship via sqlalchemy #159

micoloth opened this issue Jun 15, 2022 · 2 comments

Comments

@micoloth
Copy link

micoloth commented Jun 15, 2022

Hi! I'm trying to understand if fastapi_crudrouter supports inserting object with nested fields that represent many-to-many relatioships, via the Sqlalchemy ORM.

This is the code I have:

from sqlalchemy import create_engine, Column, Integer, String, Table, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, joinedload

# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=True,
                       connect_args={"check_same_thread": False})

# Make the DeclarativeMeta
Base = declarative_base()

# ------------------------------- SqlAlchemy classes with many to many relationship----------------------------


# Declare Classes / Tables
book_authors = Table('book_authors', Base.metadata,
    Column('book_id', ForeignKey('books.id'), primary_key=True),
    Column('author_id', ForeignKey('authors.id'), primary_key=True)
)



class Book(Base):
    __tablename__ = 'books'
    id = Column(Integer, primary_key=True)
    title = Column(String, nullable=False)
    authors = relationship("Author", secondary="book_authors", back_populates='books')

class Author(Base):
    __tablename__ = 'authors'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    books = relationship("Book", secondary="book_authors", back_populates='authors')

# Create the tables in the database
Base.metadata.create_all(engine)

# ------------------------------- Pydantic classes ----------------------------

from typing import List
from pydantic import BaseModel

class AuthorBase(BaseModel):
    id: int
    name: str

    class Config:
        orm_mode = True

class BookBase(BaseModel):
    id: int
    title: str

    class Config:
        orm_mode = True

class BookSchema(BookBase):
    authors: List[AuthorBase]

class AuthorSchema(AuthorBase):
    books: List[BookBase]


from fastapi import FastAPI, Depends


# ------------------------------- Connect to DB ----------------------------

app = FastAPI(title="Bookipedia")

def get_db():
    db = Session(bind=engine)
    try:
        yield db
    finally:
        db.close()


# ------------------------------- Routers ----------------------------


from fastapi_crudrouter import SQLAlchemyCRUDRouter


routers = [SQLAlchemyCRUDRouter(schema=AuthorSchema, create_schema=AuthorSchema, db_model=Author, db=get_db),
          SQLAlchemyCRUDRouter(schema=BookSchema, create_schema=BookSchema, db_model=Book, db=get_db)]


app.include_router(routers[0])
app.include_router(routers[1])



import uvicorn
if __name__ == "__main__":
  uvicorn.run('app:app', port=8080, reload=True, debug=True)


# Go to: http://127.0.0.1:8000/docs

But if one goes to http://127.0.0.1:8000/docs and then to POST/ Books, in the "Try it out" feature, and adds this set of data:

{
  "id": 0,
  "title": "moby_dick",
  "authors": [
    {
      "id": 0,
      "name": "MELLEVILLE"
    }
  ]
}

It doesn't work, it returns this error:

File "C:\Python310\lib\site-packages\sqlalchemy\orm\attributes.py", line 1765, in emit_backref_from_collection_append_event
    child_state, child_dict = instance_state(child), instance_dict(child)
AttributeError: 'dict' object has no attribute '_sa_instance_state'

I wanted to understand if I'm doing something wrong, or this use case is not supported by fastapi_crudrouter.

Thanks!

@nuno-andre
Copy link
Contributor

Adding another example with SQLModel (which subclasses Pydantic's BaseModel and SQLAlchemy's DeclarativeMeta avoiding the need to declare model and schema separately).

The above example would look something like this. The tables are set up properly, but the API doesn't show neither authors nor books fields.

from sqlmodel import SQLModel, Field, Relationship

# link table
class BookAuthor(SQLModel, table=True):
    book:   int = Field(primary_key=True, foreign_key='book.id')
    author: int = Field(primary_key=True, foreign_key='author.id')

class Book(SQLModel, table=True):
    id:      int = Field(primary_key=True)
    title:   str
    authors: list['Author'] = Relationship(
        back_populates='books',
        link_model=BookAuthor,
    )
 
class Author(SQLModel, table=True):
    id:    int = Field(primary_key=True)
    name:  str
    books: list[Book] = Relationship(
        back_populates='authors',
        link_model=BookAuthor,
    )

@miguelebf
Copy link

Hi, i have the same issue, did you find any solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants