pola-rs/polars · error · TypeError

Unrecognised connection type {qualified_type_name(conn)!r};

Error message

Unrecognised connection type {qualified_type_name(conn)!r}; no 'execute' or 'cursor' method

What it means

When read_database initializes a cursor from your object (ConnectionExecutor._cursor_init), it accepts anything with a cursor() method (DBAPI connection) or an execute() method (cursor, SQLAlchemy connection, ADBC handle). An object with neither is treated as an unrecognised connection type and raises TypeError including the qualified class name. This is the type-shape gate before any query runs - the repr in the message tells you exactly what leaked in.

Source

Thrown at py-polars/src/polars/io/database/_executor.py:472

                    return conn

        elif hasattr(conn, "cursor"):
            # connection has a dedicated cursor; prefer over direct execute
            cursor = (
                cast("Cursor", cursor()) if callable(cursor := conn.cursor) else cursor
            )
            self.can_close_cursor = True
            return cursor

        elif hasattr(conn, "execute"):
            # can execute directly (given cursor, sqlalchemy connection, etc)
            return conn

        msg = (
            f"Unrecognised connection type {qualified_type_name(conn)!r}; no "
            "'execute' or 'cursor' method"
        )
        raise TypeError(msg)

    async def _sqlalchemy_async_execute(self, query: TextClause, **options: Any) -> Any:
        """Execute a query using an async SQLAlchemy connection."""
        is_session = self._is_alchemy_session(self.cursor)
        cursor = self.cursor.begin() if is_session else self.cursor  # type: ignore[attr-defined]

        # check if connection is already started (eg: user awaited `engine.connect()`);
        # if so, use it directly without entering the context manager again
        if getattr(cursor, "sync_connection", None) is not None:
            return await cursor.execute(query, **options)

        async with cursor as conn:  # type: ignore[union-attr]
            if is_session and not hasattr(conn, "execute"):
                conn = conn.session
            result = await conn.execute(query, **options)
            return result

    def _sqlalchemy_setup(

View on GitHub (pinned to df599052da)

Solutions

  1. For connection strings use pl.read_database_uri(query, uri, engine='connectorx')
  2. For objects, pass a real connection/cursor: engine.connect(), raw DBAPI connect(), or conn.cursor()
  3. If wrapping drivers, expose an execute() method on your wrapper

Example fix

# before
pl.read_database('SELECT * FROM t', connection='postgresql://user:pw@host/db')

# after - URI goes to read_database_uri
pl.read_database_uri('SELECT * FROM t', 'postgresql://user:pw@host/db')

# after - or pass a live connection object
pl.read_database('SELECT * FROM t', connection=engine.connect())
Defensive patterns

Strategy: type-guard

Validate before calling

def is_connection_like(obj: object) -> bool:
    return hasattr(obj, 'cursor') or hasattr(obj, 'execute')

assert is_connection_like(conn), (
    'read_database needs a connection/cursor object; '
    'use read_database_uri for connection strings'
)
df = pl.read_database(query, connection=conn)

Type guard

from typing import TypeGuard
from typing import Any

def is_connection_or_cursor(obj: Any) -> TypeGuard[Any]:
    """Narrow to objects polars' read_database can drive."""
    return hasattr(obj, 'cursor') or hasattr(obj, 'execute')

Try / catch

try:
    df = pl.read_database(query, connection=conn)
except TypeError as err:
    if 'Unrecognised connection type' in str(err):
        df = pl.read_database_uri(query, conn) if isinstance(conn, str) else None
        if df is None:
            raise
    else:
        raise

Prevention

When it happens

Trigger: pl.read_database('SELECT 1', connection='postgresql://user:pw@host/db') (URI string instead of an object); passing a config dict, an Engine (has neither cursor nor execute), or an ORM Session-like wrapper without execute; passing None.

Common situations: Confusing read_database (wants a live connection/cursor object) with read_database_uri (wants a URI string); passing SQLAlchemy create_engine(...) result directly; factory functions returning a wrapper class that hides .execute().

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/92c784674dd94537. Report an issue: GitHub.