# Troubleshooting

Search for the exact error text below, then apply the correction for that failure mode.

## `Missing sqlalchemy_url in environ.`

The default fastsqla.lifespan reads the database URL from `SQLALCHEMY_URL`. The variable was absent when FastAPI started.

Set an async SQLAlchemy URL before starting the application:

```
export SQLALCHEMY_URL=sqlite+aiosqlite:///db.sqlite
uvicorn example:app

```

For configuration in code, create the lifespan explicitly:

```
from fastapi import FastAPI
from fastsqla import new_lifespan

app = FastAPI(lifespan=new_lifespan("sqlite+aiosqlite:///db.sqlite"))

```

## `The asyncio extension requires an async driver to be used`

The URL selects a synchronous database driver. For example, `sqlite:///db.sqlite` loads Python's synchronous `pysqlite` driver and produces:

```
The asyncio extension requires an async driver to be used.
The loaded 'pysqlite' is not async.

```

Install the asynchronous driver for the database and include it in the URL:

| Database   | Install                 | URL prefix              |
| ---------- | ----------------------- | ----------------------- |
| PostgreSQL | `pip install asyncpg`   | `postgresql+asyncpg://` |
| SQLite     | `pip install aiosqlite` | `sqlite+aiosqlite:///`  |
| MySQL      | `pip install aiomysql`  | `mysql+aiomysql://`     |

## `Could not locate a bind configured on SQL expression or this Session.`

A Session or open_session() operation ran outside the FastSQLA lifespan. The lifespan binds the shared session factory at startup and clears it at shutdown.

Attach the lifespan to FastAPI:

```
from fastapi import FastAPI
from fastsqla import lifespan

app = FastAPI(lifespan=lifespan)

```

Use `Session` only in endpoint parameters. Use `open_session()` for background work that runs after application startup and finishes before shutdown:

```
from fastsqla import open_session
from sqlalchemy import select

async def refresh_cache() -> None:
    async with open_session() as session:
        heroes = (await session.scalars(select(Hero))).all()

```

Do not import or configure `SessionFactory`; it is an internal lifecycle detail.

## `MissingGreenlet: greenlet_spawn has not been called`

SQLAlchemy attempted implicit database I/O while ordinary Python code accessed an unloaded ORM attribute. This often happens when response serialization touches a lazy-loaded relationship.

Load relationships explicitly inside the awaited query. `selectinload()` is a good default for collections:

```
from fastsqla import Session
from sqlalchemy import select
from sqlalchemy.orm import selectinload

async def get_team(team_id: int, session: Session) -> Team:
    stmt = (
        select(Team)
        .where(Team.id == team_id)
        .options(selectinload(Team.heroes))
    )
    return (await session.scalars(stmt)).one()

```

See SQLAlchemy's guidance on [preventing implicit I/O with `AsyncSession`](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html#preventing-implicit-io-when-using-asyncsession).

## `Input should be greater than or equal to 0`

The built-in Paginate dependency rejects a negative `offset`. Start at zero:

```
GET /heroes?offset=0&limit=10

```

## `Input should be less than or equal to 100`

The built-in `Paginate` dependency accepts `limit` values from 1 through 100. Use a smaller value or define an intentional maximum:

```
from typing import Annotated

from fastapi import Depends
from fastsqla import PaginateType, new_pagination

LargePage = Annotated[
    PaginateType[HeroModel],
    Depends(new_pagination(default_page_size=10, max_page_size=250)),
]

```

## `This Session's transaction has been rolled back due to a previous exception`

A database operation such as `flush()` raised an exception, but application code caught it and then tried to keep using the invalid transaction.

Translate the original database error into an exception and let FastSQLA roll back:

```
from fastapi import HTTPException
from fastsqla import Session
from sqlalchemy.exc import IntegrityError

async def create_hero(new_hero: HeroInput, session: Session) -> Hero:
    hero = Hero(**new_hero.model_dump())
    session.add(hero)
    try:
        await session.flush()
    except IntegrityError as exc:
        raise HTTPException(status_code=409, detail="Hero already exists") from exc
    return hero

```

Do not catch and ignore `IntegrityError`, and do not call `commit()` inside an endpoint.
