Skip to content

Bug: some circumstances allow raw driver exceptions to leak through #362

@ftsartek

Description

@ftsartek

Description

In certain use cases, driver exceptions can leak past the SQLSpec exception handling. This seems to only occur when the compiled package is used. When pure-python is used because the .so is missing, exceptions are caught as expected.

URL to code causing the issue

No response

MCVE

import asyncio
import importlib.util
import platform
import sys

import asyncpg
import sqlspec
import sqlspec.exceptions
from sqlspec.adapters.asyncpg import AsyncpgConfig

DSN = "postgresql://postgress:password@localhost:5432/test"


async def main() -> None:
    spec = importlib.util.find_spec("sqlspec.driver._async")
    origin = spec.origin if spec else "?"
    compiled = origin.endswith(".so") or origin.endswith(".pyd")
    print(f"Python {platform.python_version()}, sqlspec {sqlspec.__version__}, asyncpg {asyncpg.__version__}")
    print(f"driver._async: {'compiled' if compiled else 'pure Python'} ({origin})\n")

    config = AsyncpgConfig(connection_config={"dsn": DSN}, pool_config={"min_size": 1, "max_size": 2})
    await config.create_pool()
    try:
        async with config.provide_session() as s:
            await s.execute(
                "DROP TABLE IF EXISTS child; DROP TABLE IF EXISTS parent;"
                "CREATE TABLE parent (id serial PRIMARY KEY);"
                "CREATE TABLE child (id serial PRIMARY KEY, parent_id int NOT NULL REFERENCES parent(id));"
                "INSERT INTO parent VALUES (1); INSERT INTO child VALUES (1, 1);"
            )

        async with config.provide_session() as s:
            try:
                await s.execute("DELETE FROM parent WHERE id = $1", 1)
                print("ERROR: no exception raised")
                sys.exit(2)
            except sqlspec.exceptions.ForeignKeyViolationError:
                print("PASS: caught sqlspec.exceptions.ForeignKeyViolationError")
            except asyncpg.exceptions.ForeignKeyViolationError:
                print("FAIL: caught raw asyncpg.exceptions.ForeignKeyViolationError")
                print("      exception was not mapped by dispatch_statement_execution")
                sys.exit(1)
    finally:
        async with config.provide_session() as s:
            await s.execute("DROP TABLE IF EXISTS child; DROP TABLE IF EXISTS parent;")
        await config.close_pool()


if __name__ == "__main__":
    asyncio.run(main())

Steps to reproduce

1. Run MCVE with a fresh Python environment - should pass through the asyncpg error.
2. Rename the `_async.cpython-313-x86_64-linux-gnu.so` file to force pure python
3. Run MCVE again, SQLSpec exception should propagate

Screenshots

No response

Logs

When using SQLSpec as installed:

❯ uv run mcve_sqlspec_bug/reproduce.py
Python 3.13.9, sqlspec 0.38.4, asyncpg 0.31.0
driver._async: compiled (.../.venv/lib/python3.13/site-packages/sqlspec/driver/_async.cpython-313-x86_64-linux-gnu.so)

FAIL: caught raw asyncpg.exceptions.ForeignKeyViolationError
      exception was not mapped by dispatch_statement_execution


After renaming `_async.cpython-313-x86_64-linux-gnu.so`:


❯ uv run mcve_sqlspec_bug/reproduce.py
Python 3.13.9, sqlspec 0.38.4, asyncpg 0.31.0
driver._async: pure Python (/home/jordan/Projects/athenaeum_core/.venv/lib/python3.13/site-packages/sqlspec/driver/_async.py)

PASS: caught sqlspec.exceptions.ForeignKeyViolationError

Package Version

0.38.4

Platform

  • Linux
  • Mac
  • Windows
  • Other (Please specify in the description above)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions