Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions RELEASE_1.2.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Release 1.2.1 — async driver fallback hotfix

Bugfix release for v1.2.0.

## What changed

### Fixed
- **Crash at adapter init when async DB driver is missing.** Since v1.2.0 the
default `SQLAlchemyAdapter` was always created with an explicit
`async_database_url`, which caused `create_async_engine()` to raise
`ModuleNotFoundError: No module named 'aiosqlite'` (or `asyncpg` /
`aiomysql`) at construction time, breaking purely-synchronous setups.

Now the adapter degrades gracefully: if the async DB-API driver is not
installed, `async_engine` and `AsyncSessionLocal` fall back to `None` and
`get_async_session()` raises a helpful `RuntimeError` explaining which
package to install. Sync usage continues to work without any extra
dependency.

### Internal
- Switched packaging in `pyproject.toml` from a hard-coded package list to
`[tool.setuptools.packages.find]`, so any future subpackages are picked up
automatically by the wheel/sdist build.
- Added regression tests covering both the explicit-URL and auto-converted-URL
paths when `aiosqlite` is missing.

## Upgrade

```bash
pip install -U fastapi-viewsets==1.2.1
```

No code changes are required. If you want full async support, install the
matching driver:

```bash
pip install aiosqlite # SQLite
pip install asyncpg # PostgreSQL
pip install aiomysql # MySQL
```
16 changes: 16 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Release Notes

## Version 1.2.1

### 🐛 Bugfix

- **`SQLAlchemyAdapter` no longer crashes at construction** when the
async DB-API driver (`aiosqlite` / `asyncpg` / `aiomysql`) is missing.
Sync-only setups keep working out of the box; `get_async_session()`
raises a helpful `RuntimeError` lazily if you try to use async without
the driver. Regression introduced in 1.2.0 by always passing an
explicit `async_database_url` from `ORMFactory.get_adapter_from_env`.

### 📦 Packaging

- Switched to `[tool.setuptools.packages.find]` so future subpackages are
picked up automatically by wheel/sdist builds.

## Version 1.2.0

### ✨ Highlights
Expand Down
18 changes: 12 additions & 6 deletions fastapi_viewsets/orm/sqlalchemy_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,25 @@ def __init__(

self.Base.query = self.db_session.query_property()

# Setup async engine and session
# Setup async engine and session.
#
# The async engine is best-effort: if the matching async DB-API
# driver (aiosqlite, asyncpg, aiomysql, ...) is not installed we
# fall back to ``async_engine = None`` so that purely synchronous
# usage still works. The error is re-raised lazily from
# :meth:`get_async_session` with installation hints.
if async_engine:
self.async_engine = async_engine
elif async_database_url:
self.async_engine = create_async_engine(async_database_url, echo=False)
else:
# Auto-convert sync URL to async
async_url = self._get_async_database_url()
async_url = async_database_url or self._get_async_database_url()
try:
self.async_engine = create_async_engine(async_url, echo=False)
except Exception:
# Missing async DB-API driver (aiosqlite/asyncpg/aiomysql),
# or no async dialect for this URL — degrade to sync-only mode.
# The error is re-raised lazily from ``get_async_session``.
self.async_engine = None

if self.async_engine:
self.AsyncSessionLocal = async_sessionmaker(
self.async_engine,
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "fastapi_viewsets"
version = "1.2.0"
version = "1.2.1"
description = "DRF-style viewsets for FastAPI with SQLAlchemy/Tortoise/Peewee adapters and Pydantic v2 support."
readme = "README.md"
license = { text = "MIT" }
Expand Down Expand Up @@ -51,8 +51,10 @@ Homepage = "https://github.com/svalench/fastapi_viewsets"
Issues = "https://github.com/svalench/fastapi_viewsets/issues"
Changelog = "https://github.com/svalench/fastapi_viewsets/blob/main/RELEASE_NOTES.md"

[tool.setuptools]
packages = ["fastapi_viewsets", "fastapi_viewsets.orm"]
[tool.setuptools.packages.find]
where = ["."]
include = ["fastapi_viewsets*"]
exclude = ["tests*"]

[tool.black]
line-length = 100
Expand Down
57 changes: 57 additions & 0 deletions tests/test_async_driver_missing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Regression test for v1.2.1.

When ``async_database_url`` is provided but the corresponding async DB-API
driver (e.g. ``aiosqlite``) is not installed, ``SQLAlchemyAdapter.__init__``
must degrade gracefully to sync-only mode instead of raising
``ModuleNotFoundError`` at construction time.

Regression introduced in v1.2.0 where ``ORMFactory.get_adapter_from_env``
started always passing an explicit ``async_database_url`` to the adapter,
bypassing the existing best-effort ``try/except`` on the URL-conversion path.
"""

from unittest.mock import patch

import pytest

from fastapi_viewsets.orm.sqlalchemy_adapter import SQLAlchemyAdapter


def _import_error(*_args, **_kwargs):
raise ModuleNotFoundError("No module named 'aiosqlite'")


def test_explicit_async_url_without_driver_falls_back_to_sync():
"""Adapter must not raise when async driver is missing."""
with patch(
"fastapi_viewsets.orm.sqlalchemy_adapter.create_async_engine",
side_effect=_import_error,
):
adapter = SQLAlchemyAdapter(
database_url="sqlite:///:memory:",
async_database_url="sqlite+aiosqlite:///:memory:",
)

assert adapter.async_engine is None
assert adapter.AsyncSessionLocal is None

# Sync session must keep working.
session = adapter.get_session()
assert session is not None
session.close()

# Async session must raise a helpful runtime error (not at __init__).
with pytest.raises(RuntimeError, match="async database driver"):
adapter.get_async_session()


def test_auto_converted_async_url_without_driver_falls_back_to_sync():
"""Same fallback when ``async_database_url`` is auto-derived."""
with patch(
"fastapi_viewsets.orm.sqlalchemy_adapter.create_async_engine",
side_effect=_import_error,
):
adapter = SQLAlchemyAdapter(database_url="sqlite:///:memory:")

assert adapter.async_engine is None
assert adapter.AsyncSessionLocal is None
Loading