Added documentation for range types and adaptation

This commit is contained in:
Daniele Varrazzo 2012-09-24 00:49:44 +01:00
parent b1953640d2
commit c756d580f2
3 changed files with 128 additions and 35 deletions

1
NEWS
View File

@ -1,6 +1,7 @@
What's new in psycopg 2.4.6 What's new in psycopg 2.4.6
--------------------------- ---------------------------
- Added support for PostgreSQL 9.2 range types.
- Added support for backward scrollable cursors. Thanks to Jon Nelson - Added support for backward scrollable cursors. Thanks to Jon Nelson
for the initial patch (ticket #108). for the initial patch (ticket #108).
- connection.reset() implemented using DISCARD ALL on server versions - connection.reset() implemented using DISCARD ALL on server versions

View File

@ -211,6 +211,89 @@ requires no adapter registration.
.. index::
pair: range; Data types
Range data types
^^^^^^^^^^^^^^^^
.. versionadded:: 2.4.6
Psycopg offers a `Range` Python type and supports adaptation between them and
PostgreSQL |range|_ types. Builtin |range| types are supported out-of-the-box;
user-defined |range| types can be adapted using `register_range()`.
.. |range| replace:: :sql:`range`
.. _range: http://www.postgresql.org/docs/current/static/rangetypes.html
.. autoclass:: Range
This Python type is only used to pass and retrieve range values to and
from PostgreSQL and doesn't attempt to replicate the PostgreSQL range
features: it doesn't perform normalization and doesn't implement all the
operators__ supported by the database.
.. __: http://www.postgresql.org/docs/current/static/functions-range.html#RANGE-OPERATORS-TABLE
`!Range` objects are immutable, hashable, and support the ``in`` operator
(checking if an element is within the range). They can be tested for
equivalence but not for ordering. Empty ranges evaluate to `!False` in
boolean context, nonempty evaluate to `!True`.
Although it is possible to instantiate `!Range` objects, the class doesn't
have an adapter registered, so you cannot normally pass these instances as
query arguments. To use range objects as query arguments you can either
use one of the provided subclasses, such as `NumericRange` or create a
custom subclass using `register_range()`.
Object attributes:
.. autoattribute:: isempty
.. autoattribute:: lower
.. autoattribute:: upper
.. autoattribute:: lower_inc
.. autoattribute:: upper_inc
.. autoattribute:: lower_inf
.. autoattribute:: upper_inf
The following `Range` subclasses map builtin PostgreSQL |range| types to
Python objects: they have an adapter registered so their instances can be
passed as query arguments. |range| values read from database queries are
automatically casted into instances of these classes.
.. autoclass:: NumericRange
.. autoclass:: DateRange
.. autoclass:: DateTimeRange
.. autoclass:: DateTimeTZRange
Custom |range| types (created with |CREATE TYPE|_ :sql:`... AS RANGE`) can be
adapted to a custom `Range` subclass:
.. autofunction:: register_range
.. autoclass:: RangeCaster
Object attributes:
.. attribute:: range
The `!Range` subclass adapted.
.. attribute:: adapter
The `~psycopg2.extensions.ISQLQuote` responsible to adapt `!range`.
.. attribute:: typecaster
The object responsible for casting.
.. attribute:: array_typecaster
The object responsible for casting arrays, if available, else `!None`.
.. index:: .. index::
pair: UUID; Data types pair: UUID; Data types

View File

@ -31,26 +31,13 @@ from psycopg2.extensions import ISQLQuote, adapt, register_adapter
from psycopg2.extensions import new_type, new_array_type, register_type from psycopg2.extensions import new_type, new_array_type, register_type
class Range(object): class Range(object):
"""Python representation for a PostgreSQL range type. """Python representation for a PostgreSQL |range|_ type.
:param lower: lower bound for the range. None means unbound :param lower: lower bound for the range. `!None` means unbound
:param upper: upper bound for the range. None means unbound :param upper: upper bound for the range. `!None` means unbound
:param bounds: one of the literal strings ``()``, ``[)``, ``(]``, ``[]``, :param bounds: one of the literal strings ``()``, ``[)``, ``(]``, ``[]``,
representing whether the lower or upper bounds are included representing whether the lower or upper bounds are included
:param empty: if true, the range is empty :param empty: if `!True`, the range is empty
TODO: move this to the docs
This Python type is only used to pass and retrieve range values to and
from PostgreSQL and doesn't attempt to replicate the PostgreSQL range
features: it doesn't perform normalization and doesn't implement all the
operators supported.
Although it is possible to instantiate `!Range` objects, the class doesn't
have an adapter so you cannot normally pass these instances as query
arguments. To use range objects as query arguments you can either use one
of the provided subclasses, such as [TODO: the other] `IntRange` or create
a custom one using `register_range()`.
""" """
__slots__ = ('_lower', '_upper', '_bounds') __slots__ = ('_lower', '_upper', '_bounds')
@ -75,41 +62,41 @@ class Range(object):
@property @property
def lower(self): def lower(self):
"""The lower bound of the range. None if empty or unbound.""" """The lower bound of the range. `!None` if empty or unbound."""
return self._lower return self._lower
@property @property
def upper(self): def upper(self):
"""The upper bound of the range. None if empty or unbound.""" """The upper bound of the range. `!None` if empty or unbound."""
return self._upper return self._upper
@property @property
def isempty(self): def isempty(self):
"""True if the range is empty.""" """`!True` if the range is empty."""
return self._bounds is None return self._bounds is None
@property @property
def lower_inf(self): def lower_inf(self):
"""True if the range doesn't have a lower bound.""" """`!True` if the range doesn't have a lower bound."""
if self._bounds is None: return False if self._bounds is None: return False
return self._lower is None return self._lower is None
@property @property
def upper_inf(self): def upper_inf(self):
"""True if the range doesn't have an upper bound.""" """`!True` if the range doesn't have an upper bound."""
if self._bounds is None: return False if self._bounds is None: return False
return self._upper is None return self._upper is None
@property @property
def lower_inc(self): def lower_inc(self):
"""True if the lower bound is included in the range.""" """`!True` if the lower bound is included in the range."""
if self._bounds is None: return False if self._bounds is None: return False
if self._lower is None: return False if self._lower is None: return False
return self._bounds[0] == '[' return self._bounds[0] == '['
@property @property
def upper_inc(self): def upper_inc(self):
"""True if the upper bound is included in the range.""" """`!True` if the upper bound is included in the range."""
if self._bounds is None: return False if self._bounds is None: return False
if self._upper is None: return False if self._upper is None: return False
return self._bounds[1] == ']' return self._bounds[1] == ']'
@ -151,19 +138,30 @@ class Range(object):
def register_range(pgrange, pyrange, conn_or_curs, globally=False): def register_range(pgrange, pyrange, conn_or_curs, globally=False):
"""Register a typecaster and an adapter for range a range type. """Create and register an adapter and the typecasters to convert between
a PostgreSQL |range|_ type and a PostgreSQL `Range` subclass.
:param pgrange: the name of the PostgreSQL range type
:param pyrange: a Range (strict) subclass, or just the name to give it :param pgrange: the name of the PostgreSQL |range| type. Can be
(the class will be available as the `!range` attribute of the returned schema-qualified
`RangeCaster`. :param pyrange: a `Range` strict subclass, or just a name to give to a new
class
:param conn_or_curs: a connection or cursor used to find the oid of the :param conn_or_curs: a connection or cursor used to find the oid of the
range and its subtype; the typecaster is registered in a scope limited range and its subtype; the typecaster is registered in a scope limited
to this object, unless *globally* is set to `!True` to this object, unless *globally* is set to `!True`
:param globally: if `!False` (default) register the typecaster only on :param globally: if `!False` (default) register the typecaster only on
*conn_or_curs*, otherwise register it globally *conn_or_curs*, otherwise register it globally
:return: the registered `RangeCaster` instance responsible for the :return: `RangeCaster` instance responsible for the conversion
conversion
If a string is passed to *pyrange*, a new `Range` subclass is created
with such name and will be available as the `~RangeCaster.range` attribute
of the returned `RangeCaster` object.
The function queries the database on *conn_or_curs* to inspect the
*pgrange* type. Raise `~psycopg2.ProgrammingError` if the type is not
found. If querying the database is not advisable, use directly the
`RangeCaster` class and register the adapter and typecasters using the
provided functions.
""" """
caster = RangeCaster._from_db(pgrange, pyrange, conn_or_curs) caster = RangeCaster._from_db(pgrange, pyrange, conn_or_curs)
caster._register(not globally and conn_or_curs or None) caster._register(not globally and conn_or_curs or None)
@ -219,7 +217,12 @@ class RangeAdapter(object):
class RangeCaster(object): class RangeCaster(object):
"""Helper class to convert between `Range` and PostgreSQL range types.""" """Helper class to convert between `Range` and PostgreSQL range types.
Objects of this class are usually created by `register_range()`. Manual
creation could be useful if querying the database is not advisable: in
this case the oids must be provided.
"""
def __init__(self, pgrange, pyrange, oid, subtype_oid, array_oid=None): def __init__(self, pgrange, pyrange, oid, subtype_oid, array_oid=None):
self.subtype_oid = subtype_oid self.subtype_oid = subtype_oid
self._create_ranges(pgrange, pyrange) self._create_ranges(pgrange, pyrange)
@ -237,7 +240,9 @@ class RangeCaster(object):
def _create_ranges(self, pgrange, pyrange): def _create_ranges(self, pgrange, pyrange):
"""Create Range and RangeAdapter classes if needed.""" """Create Range and RangeAdapter classes if needed."""
# if got a string create a new RangeAdapter concrete type (with a name) # if got a string create a new RangeAdapter concrete type (with a name)
# else take it as an adapter. # else take it as an adapter. Passing an adapter should be considered
# an implementation detail and is not documented. It is currently used
# for the numeric ranges.
self.adapter = None self.adapter = None
if isinstance(pgrange, basestring): if isinstance(pgrange, basestring):
self.adapter = type(pgrange, (RangeAdapter,), {}) self.adapter = type(pgrange, (RangeAdapter,), {})
@ -378,7 +383,11 @@ where typname = %s and ns.nspname = %s;
class NumericRange(Range): class NumericRange(Range):
"""A `Range` suitable to pass Python numeric types to a PostgreSQL range.""" """A `Range` suitable to pass Python numeric types to a PostgreSQL range.
PostgreSQL types :sql:`int4range`, :sql:`int8range`, :sql:`numrange` are
casted into `!NumericRange` instances.
"""
pass pass
class DateRange(Range): class DateRange(Range):