FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Different response model behaviour on Mac vs Ubuntu when joining tables · Issue #379 · fastapi/sqlmodel · GitHub

Repository navigation

Different response model behaviour on Mac vs Ubuntu when joining tables #379

Closed

Description

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

# Example interleaved with the description

Description

I have these models:

class SourceBase(SQLModel):
    name: str = Field(nullable=False)
    url: str = Field(nullable=False, sa_column_kwargs={"unique": True})


class Source(SourceBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    users: List["User"] = Relationship(back_populates="sources", link_model=UserSourceLink)
    articles: List["Article"] = Relationship(back_populates="source")


class SourceGet(SourceBase):
    id: int


class ArticleBase(SQLModel):
    title: str = Field(nullable=False)
    url: str = Field(nullable=False, sa_column_kwargs={"unique": True})
    published_date: str = Field(nullable=False)
    read: bool = Field(default=False, nullable=False)
    source_id: Optional[int] = Field(default=None, foreign_key="source.id")


class Article(ArticleBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    source: Source = Relationship(back_populates="articles")


class ArticleRead(ArticleBase):
    id: int

class ArticleReadWithSource(ArticleGet):
    source: Optional[SourceGet] = None

I have a function that selects all rows from the article table:

def select_all_articles(offset, limit, session: Session) -> List:
    statement = select(Article).order_by(Article.published_date.desc()).offset(offset).limit(limit)
    results = session.exec(statement).all()
    return results

I have another function which returns these rows along with the source to which they get joined:

@router.get("/", response_model=List[ArticleReadWithSource])
def get_all_articles(
    offset: int = 0,
    limit: int = Query(default=100, lte=100),
    session: Session = Depends(db.get_session),
    current_user: User = Depends(get_current_user),
) -> List:
    articles = db.select_all_articles(offset, limit, session)
    return articles

This works perfectly on my local machine (macOS Montery):

However, when running this same code on a remote Ubuntu 20.04 machine, the join seems to silently fail and the source object is returned as a null

Has anyone ever seen any behaviour like this? The code is identical on each machine so I'm struggling to think of ways to debug this

Operating System

Linux

Operating System Details

No response

SQLModel Version

0.06

Python Version

3.10.5

Additional Context

No response

Activity

  1. antont commented on Jul 18, 2022

    I think it can only be a version difference, maybe you have different versions of the libraries on the different machines. Maybe different SQLAlchemy and you hit this, #315

  2. HenryDashwood commented on Aug 25, 2022

    Author

    Spent this evening putting together a minimal viable reproduction of the error and just saw your comment. Downgrading SQLAlchemy to 1.4.35 does indeed resolve the issue.

    In case it's useful, running pytest on the code below will pass on MacOS (M1) but fail on Ubuntu. There is a much smaller and cleaner code example in the issue you linked to though

    from typing import List, Optional
    
    import pytest
    from fastapi import Depends, FastAPI
    from fastapi.testclient import TestClient
    from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
    from sqlmodel.pool import StaticPool
    
    
    app = FastAPI()
    
    engine = create_engine("sqlite://", echo=False, connect_args={"check_same_thread": False})
    
    
    def get_session():
        with Session(engine) as session:
            yield session
    
    
    class SourceBase(SQLModel):
        name: Optional[str] = Field(nullable=True)
    
    
    class Source(SourceBase, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        articles: List["Article"] = Relationship(back_populates="source")
    
    
    class SourceRead(SourceBase):
        id: int
    
    
    class ArticleBase(SQLModel):
        title: str = Field(nullable=False)
        source_id: Optional[int] = Field(default=None, foreign_key="source.id")
    
    
    class ArticleCreate(SQLModel):
        title: str = Field(nullable=False)
        source_name: str = Field(nullable=False)
    
    
    class Article(ArticleBase, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        source: Source = Relationship(back_populates="articles")
    
    
    class ArticleGet(ArticleBase):
        id: int
    
    
    class ArticleGetWithSource(ArticleGet):
        source: Optional[SourceRead] = None
    
    
    @pytest.fixture(name="session")
    def session_fixture():
        engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
        SQLModel.metadata.create_all(engine)
        with Session(engine) as session:
            yield session
    
    
    @pytest.fixture(name="client")
    def client_fixture(session: Session):
        def get_session_override():
            return session
    
        app.dependency_overrides[get_session] = get_session_override
        client = TestClient(app)
    
        yield client
        app.dependency_overrides.clear()
    
    
    @app.post("/add", response_model=ArticleGetWithSource)
    def add_article(*, session: Session = Depends(get_session), payload: ArticleCreate) -> Article:
        source = session.exec(select(Source).where(Source.name == payload.source_name)).first()
        source_id = source.id if source else None
        article = Article(title=payload.title, source_id=source_id)
        session.add(article)
        session.commit()
        return session.exec(select(Article).where(Article.id == article.id)).first()
    
    
    def test_create_article(session: Session, client: TestClient):
        source = Source(name="The Way Of The Dodo")
        session.add(source)
        session.commit()
    
        response = client.post("/add", json={"title": "All This Useless Beauty", "source_name": "The Way Of The Dodo"})
        data = response.json()
    
        assert response.status_code == 200
        assert data["title"] == "All This Useless Beauty"
        assert data["source_id"] is not None
        assert data["id"] is not None
        assert data["source"] is not None
        assert data["source"]["id"] is not None
        assert data["source"]["name"] == "The Way Of The Dodo"
        assert data["id"] == data["source"]["id"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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


      Back | FazBrowse Home | New Git URL