Summary
When using create_async_engine("sqlite+aiolibsql:///...") from SQLAlchemy 2.0, every awaited operation raises AwaitRequired even with poolclass=StaticPool or NullPool explicitly set. The dialect sqlite.aiolibsql (registered by sqlalchemy-libsql) advertises async support, but the underlying DBAPI (libsql-experimental) appears to be sync internally — so the dialect's async claim is either false or requires an async-specific DBAPI package that is not published.
This makes sqlalchemy-libsql unusable as a drop-in replacement for aiosqlite in SQLAlchemy 2.0 async stacks.
Versions
- Python 3.12
- SQLAlchemy 2.0.49
- alembic 1.18.4
- sqlalchemy-libsql 0.2.0 (entry-points:
sqlite.libsql, sqlite.aiolibsql)
- libsql-experimental 0.0.55 (transitive)
- Linux (WSL2 Ubuntu 22.04)
Minimal reproduction
import asyncio
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.pool import StaticPool
from sqlalchemy import text
async def main():
engine = create_async_engine(
"sqlite+aiolibsql:///:memory:",
poolclass=StaticPool,
)
async with engine.connect() as conn:
await conn.execute(text("SELECT 1"))
asyncio.run(main())
Result: AwaitRequired raised on await conn.execute(...).
Companion observations
- The sibling dialect
sqlite+libsql is rejected by create_async_engine with "The asyncio extension requires an async driver" — confirms it is sync, as expected.
- On Windows native (Python 3.12),
libsql-experimental==0.0.55 has no cp312-win-amd64 wheel and the sdist build fails on MAX_PATH=260 in the uv cache path during CMake/MSBuild — separate platform issue, not the focus here.
Expected behavior
Either:
sqlite+aiolibsql works as a true async dialect with a corresponding async DBAPI, or
- the dialect is removed / clearly documented as not yet functional, with a tracking issue for the async DBAPI work.
Context
Filed while evaluating libsql as a drop-in for aiosqlite in a SQLAlchemy 2.0 async project. Decision documented in https://github.com/swaggyd1/trampo/blob/master/docs/decisions/0003-spike-libsql-swap.md (ADR-0003 outcome). Stayed on aiosqlite until the async path is functional. Happy to test patches.
Summary
When using
create_async_engine("sqlite+aiolibsql:///...")from SQLAlchemy 2.0, every awaited operation raisesAwaitRequiredeven withpoolclass=StaticPoolorNullPoolexplicitly set. The dialectsqlite.aiolibsql(registered bysqlalchemy-libsql) advertises async support, but the underlying DBAPI (libsql-experimental) appears to be sync internally — so the dialect's async claim is either false or requires an async-specific DBAPI package that is not published.This makes
sqlalchemy-libsqlunusable as a drop-in replacement foraiosqlitein SQLAlchemy 2.0 async stacks.Versions
sqlite.libsql,sqlite.aiolibsql)Minimal reproduction
Result:
AwaitRequiredraised onawait conn.execute(...).Companion observations
sqlite+libsqlis rejected bycreate_async_enginewith "The asyncio extension requires an async driver" — confirms it is sync, as expected.libsql-experimental==0.0.55has nocp312-win-amd64wheel and the sdist build fails onMAX_PATH=260in the uv cache path during CMake/MSBuild — separate platform issue, not the focus here.Expected behavior
Either:
sqlite+aiolibsqlworks as a true async dialect with a corresponding async DBAPI, orContext
Filed while evaluating libsql as a drop-in for
aiosqlitein a SQLAlchemy 2.0 async project. Decision documented in https://github.com/swaggyd1/trampo/blob/master/docs/decisions/0003-spike-libsql-swap.md (ADR-0003 outcome). Stayed onaiosqliteuntil the async path is functional. Happy to test patches.