Psycopg can adapt PostgreSQL composite types (either created with the |CREATE
TYPE|_ command or implicitly defined after a table row type) to and from
-Python tuples, `~collections.namedtuple`, or any other suitable object
-configured.
+Python tuples, `~collections.namedtuple`, or any suitably configured object.
.. |CREATE TYPE| replace:: :sql:`CREATE TYPE`
.. _CREATE TYPE: https://www.postgresql.org/docs/current/static/sql-createtype.html
.. autoclass:: psycopg.types.composite.CompositeInfo
`!CompositeInfo` is a `~psycopg.types.TypeInfo` subclass: check its
- documentation for the generic usage, especially the
+ documentation for the general usage, especially the
`~psycopg.types.TypeInfo.fetch()` method.
.. attribute:: python_type
- After `register_composite()` is called, it will contain the python type
- mapping to the registered composite.
+ After `register_composite()` is called, it will contain the Python type
+ adapting the registered composite.
.. autofunction:: psycopg.types.composite.register_composite
If the `!factory` is a type (and not a generic callable) then dumpers for
such type are created and registered too, so that passing objects of that
- type to a query will adapt them to the registered type. This assumes that
- the `!factory` is a sequence; if this is not the case you can specify the
- `!make_sequence` parameter. See :ref:`composite-generic`.
+ type to a query will adapt them to the registered composite type. This
+ assumes that `!factory` is a sequence; if this is not the case you can
+ specify the `!make_sequence` parameter: a function taking the object to
+ dump and the list of field names of the composite and returning a sequence
+ of values. See :ref:`composite-non-sequence`.
The `!factory` callable will be called with the sequence of value from the
composite. If passing the sequence of positional arguments is not suitable
- you can specify a `!make_instance` callable.
+ you can specify a `!make_object` callable, which takes the sequence of
+ composite values and field names and which should return a new instance of
+ the object to load. See :ref:`composite-non-sequence`.
.. versionadded:: 3.3
- the `!make_instance` and `!make_sequence` parameters.
+ the `!make_object` and `!make_sequence` parameters.
.. _composite-sequence:
card_back(face=card(value=8, suit='hearts'), back='blue')
-.. _composite-generic:
+.. _composite-non-sequence:
-Example: Generic Python object
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Example: non-sequence Python object
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 3.3
If your Python type takes keyword arguments, or if the sequence of value
coming from the PostgreSQL type is not suitable for it, it is possible to
-specify a :samp:`make_instance({values}, {names})` function to adapt the
+specify a :samp:`make_object({values}, {names})` function to adapt the
values from the composite to the right type requirements. For example::
>>> from dataclasses import dataclass
>>> def card_from_db(values: Sequence[Any], names: Sequence[str]) -> Card:
... return Card(**dict(zip(names, values)))
- >>> register_composite(info, conn, make_instance=card_from_db)
+ >>> register_composite(info, conn, make_object=card_from_db)
>>> conn.execute("select '(1,spades)'::card").fetchone()[0]
Card(suit='spades', value=1)
>>> register_composite(
... info, conn, factory=Card,
- ... make_instance=card_from_db, make_sequence=card_to_db)
+ ... make_object=card_from_db, make_sequence=card_to_db)
>>> conn.execute(
... "select %(card)s.value + 1, %(card)s.suit",
.. autoclass:: psycopg.types.multirange.MultirangeInfo
`!MultirangeInfo` is a `~psycopg.types.TypeInfo` subclass: check its
- documentation for generic details, especially the
+ documentation for general details, especially the
`~psycopg.types.TypeInfo.fetch()` method.
.. autofunction:: psycopg.types.multirange.register_multirange
)
T = TypeVar("T")
-InstanceMaker: TypeAlias = Callable[[Sequence[Any], Sequence[str]], T]
+ObjectMaker: TypeAlias = Callable[[Sequence[Any], Sequence[str]], T]
SequenceMaker: TypeAlias = Callable[[T, Sequence[str]], Sequence[Any]]
args = ()
else:
args = self._tx.load_sequence(tuple(_parse_text_record(data[1:-1])))
- return type(self).make_instance(args, self.fields_names)
+ return type(self).make_object(args, self.fields_names)
@staticmethod
@abstractmethod
- def make_instance(args: Sequence[Any], names: Sequence[str]) -> T: ...
+ def make_object(args: Sequence[Any], names: Sequence[str]) -> T: ...
class _CompositeBinaryLoader(Loader, Generic[T], ABC):
def load(self, data: abc.Buffer) -> T:
brecord, _ = _parse_binary_record(data) # assume oids == self.fields_types
record = self._tx.load_sequence(brecord)
- return type(self).make_instance(record, self.fields_names)
+ return type(self).make_object(record, self.fields_names)
@staticmethod
@abstractmethod
- def make_instance(args: Sequence[Any], names: Sequence[str]) -> T: ...
+ def make_object(args: Sequence[Any], names: Sequence[str]) -> T: ...
def register_composite(
info: CompositeInfo,
context: abc.AdaptContext | None = None,
factory: Callable[..., T] | None = None,
- make_instance: InstanceMaker[T] | None = None,
+ make_object: ObjectMaker[T] | None = None,
make_sequence: SequenceMaker[T] | None = None,
) -> None:
"""Register the adapters to load and dump a composite type.
:param factory: Callable to create a Python object from the sequence of
attributes read from the composite.
:type factory: `!Callable[..., T]` | `!None`
- :param make_instance: optional function taking values and names as input and
- returning the new type.
- :type make_instance: `!Callable[[Sequence[Any], Sequence[str]], T]` | `!None`
- :param make_sequence: optional function taking an instance and names as
- input and returning the fields to dump.
+ :param make_object: optional function to use on load, to adapt the
+ composite's sequence of values to a Python object
+ :type make_object: `!Callable[[Sequence[Any], Sequence[str]], T]` | `!None`
+ :param make_sequence: optional function to use on dump, to adapt an object
+ to the composite's sequence of values
:type make_sequence: `!Callable[[T, Sequence[str]], Sequence[Any]]` | `!None`
.. note::
if not factory:
factory = cast("Callable[..., T]", _nt_from_info(info))
- if not make_instance:
+ if not make_object:
- def make_instance(values: Sequence[Any], types: Sequence[str]) -> T:
+ def make_object(values: Sequence[Any], types: Sequence[str]) -> T:
return factory(*values)
adapters = context.adapters if context else postgres.adapters
# generate and register a customized text loader
loader: type[_CompositeLoader[T]] = _make_loader(
- info.name, field_names, field_types, make_instance
+ info.name, field_names, field_types, make_object
)
adapters.register_loader(info.oid, loader)
# generate and register a customized binary loader
binary_loader: type[_CompositeBinaryLoader[T]] = _make_binary_loader(
- info.name, field_names, field_types, make_instance
+ info.name, field_names, field_types, make_object
)
adapters.register_loader(info.oid, binary_loader)
name: str,
field_names: tuple[str, ...],
field_types: tuple[int, ...],
- make_instance: InstanceMaker[T],
+ make_object: ObjectMaker[T],
) -> type[_CompositeLoader[T]]:
doc = f"Text loader for the '{name}' composite."
d = {
"__doc__": doc,
"fields_types": field_types,
"fields_names": field_names,
- "make_instance": make_instance,
+ "make_object": make_object,
}
return type(f"{name.title()}Loader", (_CompositeLoader,), d)
name: str,
field_names: tuple[str, ...],
field_types: tuple[int, ...],
- make_instance: InstanceMaker[T],
+ make_object: ObjectMaker[T],
) -> type[_CompositeBinaryLoader[T]]:
doc = f"Binary loader for the '{name}' composite."
d = {
"__doc__": doc,
"fields_names": field_names,
"fields_types": field_types,
- "make_instance": make_instance,
+ "make_object": make_object,
}
return type(f"{name.title()}BinaryLoader", (_CompositeBinaryLoader,), d)