Row factories#
Cursor’s fetch*
methods, by default, return the records received from the
database as tuples. This can be changed to better suit the needs of the
programmer by using custom row factories.
The module psycopg.rows
exposes several row factories ready to be used. For
instance, if you want to return your records as dictionaries, you can use
dict_row
:
>>> from psycopg.rows import dict_row
>>> conn = psycopg.connect(DSN, row_factory=dict_row)
>>> conn.execute("select 'John Doe' as name, 33 as age").fetchone()
{'name': 'John Doe', 'age': 33}
The row_factory
parameter is supported by the connect()
method and the cursor()
method. Later usage of row_factory
overrides a previous one. It is also possible to change the
Connection.row_factory
or Cursor.row_factory
attributes to change what
they return:
>>> cur = conn.cursor(row_factory=dict_row)
>>> cur.execute("select 'John Doe' as name, 33 as age").fetchone()
{'name': 'John Doe', 'age': 33}
>>> from psycopg.rows import namedtuple_row
>>> cur.row_factory = namedtuple_row
>>> cur.execute("select 'John Doe' as name, 33 as age").fetchone()
Row(name='John Doe', age=33)
If you want to return objects of your choice you can use a row factory
generator, for instance class_row
or
args_row
, or you can write your own row factory:
>>> from dataclasses import dataclass
>>> @dataclass
... class Person:
... name: str
... age: int
... weight: Optional[int] = None
>>> from psycopg.rows import class_row
>>> cur = conn.cursor(row_factory=class_row(Person))
>>> cur.execute("select 'John Doe' as name, 33 as age").fetchone()
Person(name='John Doe', age=33, weight=None)
Creating new row factories#
A row factory is a callable that accepts a Cursor
object and returns
another callable, a row maker, which takes raw data (as a sequence of
values) and returns the desired object.
The role of the row factory is to inspect a query result (it is called after a
query is executed and properties such as description
and
pgresult
are available on the cursor) and to prepare a callable
which is efficient to call repeatedly (because, for instance, the names of the
columns are extracted, sanitised, and stored in local variables).
Formally, these objects are represented by the RowFactory
and
RowMaker
protocols.
RowFactory
objects can be implemented as a class, for instance:
from typing import Any, Sequence
from psycopg import Cursor
class DictRowFactory:
def __init__(self, cursor: Cursor[Any]):
self.fields = [c.name for c in cursor.description]
def __call__(self, values: Sequence[Any]) -> dict[str, Any]:
return dict(zip(self.fields, values))
or as a plain function:
def dict_row_factory(cursor: Cursor[Any]) -> RowMaker[dict[str, Any]]:
fields = [c.name for c in cursor.description]
def make_row(values: Sequence[Any]) -> dict[str, Any]:
return dict(zip(fields, values))
return make_row
These can then be used by specifying a row_factory
argument in
Connection.connect()
, Connection.cursor()
, or by setting the
Connection.row_factory
attribute.
conn = psycopg.connect(row_factory=DictRowFactory)
cur = conn.execute("SELECT first_name, last_name, age FROM persons")
person = cur.fetchone()
print(f"{person['first_name']} {person['last_name']}")