Skip to content

Are Many-to-Many link supported with fastapi? #121

@Trophime

Description

@Trophime

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the SQLModel documentation, with the integrated search.
  • I already searched in Google "How to X in SQLModel" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to SQLModel but to Pydantic.
  • I already checked if it is not related to SQLModel but to SQLAlchemy.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

from typing import List, Optional

from fastapi import Depends, FastAPI, HTTPException, Query
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine

sqlite_file_name = "test.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

# version sans fastapi: engine = create_engine(sqlite_url, echo=True)
connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)

def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

class MPartMagnetLink(SQLModel, table=True):
    """
    MPart/Magnet many to many link table
    """
    magnet_id: Optional[int] = Field(
        default=None, foreign_key="magnet.id", primary_key=True
    )
    mpart_id: Optional[int] = Field(
        default=None, foreign_key="mpart.id", primary_key=True
    )


class MagnetBase(SQLModel):
    """
    Magnet
    """
    name: str

class Magnet(MagnetBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    mparts: List["MPart"] = Relationship(back_populates="magnets", link_model=MPartMagnetLink)

class MagnetRead(MagnetBase):
    id: int

class MagnetCreate(MagnetBase):
    pass

class MagnetUpdate(SQLModel):
    """
    Magnet
    """
    name: str
    mparts: List["MPart"] = [] #Relationship(back_populates="magnets", link_model=MPartMagnetLink)

class MPartBase(SQLModel):
    """
    Magnet Part
    """
    name: str

class MPart(MPartBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    magnets: List[Magnet] = Relationship(back_populates="mparts", link_model=MPartMagnetLink)

class MPartRead(MPartBase):
    id: int

class MPartCreate(MPartBase):
    pass

class MPartUpdate(SQLModel):
    """
    Magnet Part
    """
    name: str
    magnets: List[Magnet] = []

class MPartReadWithMagnet(MPartRead):
    magnets: List[MagnetRead] = []

class MagnetReadWithMParts(MagnetRead):
    mparts: List[MPartRead] = []


def get_session():
    with Session(engine) as session:
        yield session

app = FastAPI()

@app.patch("/magnets/{magnet_id}", response_model=MagnetRead)
def update_magnet(
    *, session: Session = Depends(get_session), magnet_id: int, magnet: MagnetUpdate):
    db_magnet = session.get(Magnet, magnet_id)
    if not db_magnet:
        raise HTTPException(status_code=404, detail="Magnet not found")
    magnet_data = magnet.dict(exclude_unset=True)
    for key, value in magnet_data.items():
        setattr(db_magnet, key, value)
    session.add(db_magnet)
    session.commit()
    session.refresh(db_magnet)
    return db_magnet

@app.on_event("startup")
def on_startup():
    create_db_and_tables()

Description

  • Start uvicorn
  • Open browser: it crashes:
2021-10-04 15:53:27,962 INFO sqlalchemy.engine.Engine COMMIT
INFO:     Application startup complete.
INFO:     127.0.0.1:54084 - "GET /docs HTTP/1.1" 200 OK
INFO:     127.0.0.1:54084 - "GET /openapi.json HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/vscode/.local/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 375, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/vscode/.local/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/home/vscode/.local/lib/python3.8/site-packages/fastapi/applications.py", line 208, in __call__
    await super().__call__(scope, receive, send)
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/routing.py", line 580, in __call__
    await route.handle(scope, receive, send)
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/routing.py", line 241, in handle
    await self.app(scope, receive, send)
  File "/home/vscode/.local/lib/python3.8/site-packages/starlette/routing.py", line 52, in app
    response = await func(request)
  File "/home/vscode/.local/lib/python3.8/site-packages/fastapi/applications.py", line 161, in openapi
    return JSONResponse(self.openapi())
  File "/home/vscode/.local/lib/python3.8/site-packages/fastapi/applications.py", line 136, in openapi
    self.openapi_schema = get_openapi(
  File "/home/vscode/.local/lib/python3.8/site-packages/fastapi/openapi/utils.py", line 387, in get_openapi
    definitions = get_model_definitions(
  File "/home/vscode/.local/lib/python3.8/site-packages/fastapi/utils.py", line 24, in get_model_definitions
    m_schema, m_definitions, m_nested_models = model_process_schema(
  File "pydantic/schema.py", line 548, in pydantic.schema.model_process_schema
  File "pydantic/schema.py", line 589, in pydantic.schema.model_type_schema
  File "pydantic/schema.py", line 241, in pydantic.schema.field_schema
  File "pydantic/schema.py", line 440, in pydantic.schema.field_type_schema
  File "pydantic/schema.py", line 773, in pydantic.schema.field_singleton_schema
  File "pydantic/schema.py", line 667, in pydantic.schema.field_singleton_sub_fields_schema
  File "pydantic/schema.py", line 495, in pydantic.schema.field_type_schema
  File "pydantic/schema.py", line 839, in pydantic.schema.field_singleton_schema
  File "/usr/lib/python3.8/abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class

Operating System

Linux

Operating System Details

Ubuntu 20.04 LTS

SQLModel Version

0.0.4

Python Version

Python 3.8.10

Additional Context

I've tried to adapt the tutorial example with the many-to-many associative tables
for using it with fastapi without success. If this feature is supported it would be great
to have an example in the tutorial.

Best

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions