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:
- Register agtype adapters on externally-managed connections.
- Use a pool's
configure callback to set up AGE on each new connection.
- Pass a pre-existing connection into the
Age wrapper.
- 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
Is your feature request related to a problem?
The
Ageclass andAge.connect()create and own a singlepsycopg.Connectioninternally. In production applications that need connection pooling (viapsycopg_pool.ConnectionPoolor similar), there is no supported way to:configurecallback to set up AGE on each new connection.Agewrapper.AgeLoaderandClientCursorwithout 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
This works, but relies on
age.age.AgeLoaderandage.age.ClientCursorwhich are not part of any documented public API and require# type: ignoreto 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':Additionally, export
AgeLoaderandClientCursoras documented public symbols (e.g., via__all__in__init__.pyor as top-level imports).Describe alternatives you've considered
Age.connect()to accept an existing connection — fragile.Ageto 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