Skip to content

Python driver: No public API for connection pooling / external connection management #2369

@uesleilima

Description

@uesleilima

Is your feature request related to a problem?

The Age class and Age.connect() create and own a single psycopg.Connection internally. In production applications that need connection pooling (via psycopg_pool.ConnectionPool or similar), there is no supported way to:

  1. Register agtype adapters on externally-managed connections.
  2. Use a pool's configure callback to set up AGE on each new connection.
  3. Pass a pre-existing connection into the Age wrapper.
  4. Access AgeLoader and ClientCursor without reaching into private module attributes (age.age.AgeLoader, age.age.ClientCursor).

The only option is to replicate the driver's internal setup logic manually, which is fragile and tightly coupled to implementation details.

Current workaround

import age
import psycopg
from psycopg.types import TypeInfo
from psycopg_pool import ConnectionPool

def _configure_age_connection(conn):
    """Per-connection AGE setup — replicates driver internals."""
    with conn.cursor() as cur:
        cur.execute('SET search_path = ag_catalog, "$user", public;')

    ag_info = TypeInfo.fetch(conn, "agtype")
    if ag_info is None:
        raise RuntimeError("AGE agtype type not found")

    # These are private module attributes — no public API exists
    conn.adapters.register_loader(ag_info.oid, age.age.AgeLoader)       # type: ignore
    conn.adapters.register_loader(ag_info.array_oid, age.age.AgeLoader)  # type: ignore
    conn.commit()

pool = ConnectionPool(
    conninfo="host=localhost dbname=mydb",
    configure=_configure_age_connection,
    kwargs={"cursor_factory": age.age.ClientCursor},  # type: ignore
)
pool.open()

This works, but relies on age.age.AgeLoader and age.age.ClientCursor which are not part of any documented public API and require # type: ignore to satisfy type checkers.

Describe the solution you'd like

Expose a public function that configures an existing connection for AGE use, without creating a new connection or running LOAD 'age':

# Proposed API
def configure_connection(conn, graph_name=None, skip_load=False):
    """Register agtype adapters on an existing connection.

    Sets search_path, fetches agtype OIDs, and registers AgeLoader.
    Optionally checks/creates the graph.

    This enables use with external connection pools.
    """
    ...

Additionally, export AgeLoader and ClientCursor as documented public symbols (e.g., via __all__ in __init__.py or as top-level imports).

Describe alternatives you've considered

  • Monkeypatching Age.connect() to accept an existing connection — fragile.
  • Subclassing Age to override connection creation — the class was not designed for inheritance.

Related: #2353 (skip_load parameter) addresses part of this by making setUpAge() work on managed PostgreSQL, but even with that fix, there is no clean way to use the driver with a connection pool.

Environment

  • Python driver: master branch (psycopg3 version)
  • psycopg: 3.2.x
  • psycopg_pool: 3.2.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions