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

Logfire causes sqlalchemy.exc.NoInspectionAvailable to raise when testing simple FastAPI+SQLModel app #728

Closed
igorantonow314 opened this issue Dec 27, 2024 · 3 comments · Fixed by #733
Assignees

Comments

@igorantonow314
Copy link

Description

Summary

I copied* this example from FastAPI's docs, added testing using TestClient and logfire with logfire.instrument_pydantic(), and encountered an error. The app without the line logfire.instrument_pydantic() works fine.
(*I removed irrelevant routes).

Details

Here is the index.py code:

from typing import Annotated

import logfire
from fastapi import Depends, FastAPI, HTTPException, Query
from fastapi.testclient import TestClient
from sqlmodel import Field, Session, SQLModel, create_engine, select



logfire.configure()
logfire.instrument_pydantic()


class HeroBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)


class Hero(HeroBase, table=True):
    id: int | None = Field(default=None, primary_key=True)
    secret_name: str


class HeroPublic(HeroBase):
    id: int


class HeroCreate(HeroBase):
    secret_name: str


class HeroUpdate(HeroBase):
    name: str | None = None
    age: int | None = None
    secret_name: str | None = None


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

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, connect_args=connect_args)


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


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


SessionDep = Annotated[Session, Depends(get_session)]
app = FastAPI()


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


@app.post("/heroes/", response_model=HeroPublic)
def create_hero(hero: HeroCreate, session: SessionDep):
    db_hero = Hero.model_validate(hero)
    session.add(db_hero)
    session.commit()
    session.refresh(db_hero)
    return db_hero




def test_create():
    with TestClient(app) as client:
        response = client.post('/heroes', json={"name": "some name", "age":123, "secret_name":"younewerguess"})
        assert response.status_code == 200

I ran pytest index.py and got the error:

sqlalchemy.exc.NoInspectionAvailable: No inspection system is available for object of type <class 'index.Hero'>

part of trace:

<...>
/home/igor/.local/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:807: in run
    result = context.run(func, *args)
index.py:65: in create_hero
    db_hero = Hero.model_validate(hero)
/home/igor/.local/lib/python3.10/site-packages/sqlmodel/main.py:848: in model_validate
    return sqlmodel_validate(
/home/igor/.local/lib/python3.10/site-packages/sqlmodel/_compat.py:320: in sqlmodel_validate
    cls.__pydantic_validator__.validate_python(
/home/igor/.local/lib/python3.10/site-packages/logfire/integrations/pydantic.py:139: in wrapped_validator
    self._on_success(span, result)
/home/igor/.local/lib/python3.10/site-packages/logfire/integrations/pydantic.py:195: in _on_success
    self._set_span_attributes(
<...>
/home/igor/.local/lib/python3.10/site-packages/logfire/_internal/main.py:2090: in set_attribute
    self._json_schema_properties[key] = create_json_schema(value, set())
/home/igor/.local/lib/python3.10/site-packages/logfire/_internal/json_schema.py:149: in create_json_schema
    log_internal_error()
/home/igor/.local/lib/python3.10/site-packages/logfire/_internal/json_schema.py:127: in create_json_schema
    return _sqlalchemy_schema(obj, seen)
/home/igor/.local/lib/python3.10/site-packages/logfire/_internal/json_schema.py:344: in _sqlalchemy_schema
    state = sa_inspect(obj)

Full trace: pytest_output.txt

Possible reason

I may be wrong, this is just my opinion, but it may be useful.
Logfire is trying to log the instance of <class 'index.Hero'> (which is successfully created) and attempts to create json from it:
link to code line

def create_json_schema(obj: Any, seen: set[int]) -> JsonDict:
   <...>
  elif is_sqlalchemy(obj):
    return _sqlalchemy_schema(obj, seen)

is_sqlalchemy(obj) is True because <class 'index.Hero'> is instance of DeclarativeMeta.
But _sqlalchemy_schema(obj, seen) does something called

for cls in type_.__mro__:
  <...>

and mro is <class 'index.Hero'>, <class 'index.HeroBase'>, <class 'sqlmodel.main.SQLModel'>, <class 'pydantic.main.BaseModel'>, <class 'object'>. Maybe the SQLModel class is somehow "not connected" with sqlalchemy.inspect() and refuses to work properly, but I don't have enough knowledge about sqlalchemy, so I'm sorry for my (probably clueless) guesses.

Python, Logfire & OS Versions, related packages (not required)

logfire="2.11.0"
platform="Linux-6.8.0-49-generic-x86_64-with-glibc2.35"
python="3.10.12 (main, Nov  6 2024, 20:22:13) [GCC 11.4.0]"
[related_packages]
requests="2.32.3"
pydantic="2.10.3"
fastapi="0.115.6"
protobuf="5.29.2"
rich="13.9.4"
tomli="2.2.1"
executing="2.0.1"
opentelemetry-api="1.29.0"
opentelemetry-exporter-otlp-proto-common="1.29.0"
opentelemetry-exporter-otlp-proto-http="1.29.0"
opentelemetry-instrumentation="0.50b0"
opentelemetry-instrumentation-asgi="0.50b0"
opentelemetry-instrumentation-fastapi="0.50b0"
opentelemetry-instrumentation-sqlalchemy="0.50b0"
opentelemetry-proto="1.29.0"
opentelemetry-sdk="1.29.0"
opentelemetry-semantic-conventions="0.50b0"
opentelemetry-util-http="0.50b0"
@Kludex Kludex self-assigned this Dec 27, 2024
@Kludex
Copy link
Member

Kludex commented Dec 27, 2024

I've found this issue on SQLModel itself: fastapi/sqlmodel#71.

That said... This issue only happens with instrument_pydantic.

@Kludex
Copy link
Member

Kludex commented Dec 27, 2024

We can modify the is_sqlalchemy function to check if we can use sqlalchemy.inspect, but it seems our Pydantic plugin is doing something here.

@alexmojaki
Copy link
Contributor

Fix released in 2.11.1

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

Successfully merging a pull request may close this issue.

3 participants