psycopg/psycopg2 · error · TypeError
pyrange must be a type or a Range strict subclass
Error message
pyrange must be a type or a Range strict subclass
What it means
Raised by RangeCaster._create_ranges() (lib/_range.py:337-339) when the 'pyrange' argument is neither a string, nor a type, nor a strict subclass of Range (Range itself is excluded). The pyrange defines the Python class that PostgreSQL range values are cast into, so it must be a concrete Range subclass or a name from which one is generated.
Source
Thrown at lib/_range.py:338
self.adapter = pgrange
except TypeError:
pass
if self.adapter is None:
raise TypeError(
'pgrange must be a string or a RangeAdapter strict subclass')
self.range = None
try:
if isinstance(pyrange, str):
self.range = type(pyrange, (Range,), {})
if issubclass(pyrange, Range) and pyrange is not Range:
self.range = pyrange
except TypeError:
pass
if self.range is None:
raise TypeError(
'pyrange must be a type or a Range strict subclass')
@classmethod
def _from_db(self, name, pyrange, conn_or_curs):
"""Return a `RangeCaster` instance for the type *pgrange*.
Raise `ProgrammingError` if the type is not found.
"""
from psycopg2.extensions import STATUS_IN_TRANSACTION
from psycopg2.extras import _solve_conn_curs
conn, curs = _solve_conn_curs(conn_or_curs)
if conn.info.server_version < 90200:
raise ProgrammingError("range types not available in version %s"
% conn.info.server_version)
# Store the transaction status of the connection to revert it after use
conn_status = conn.statusView on GitHub (pinned to 3a6d9d6ddc)
Solutions
- Pass a strict subclass of Range (e.g. NumericRange, DateRange, or your own subclass) as pyrange.
- Or pass a string to have RangeCaster auto-generate a new Range subclass with that name.
- Double-check you are not passing Python's builtin range type.
Example fix
// before
caster = RangeCaster('myrange', range, oid=123, subtype_oid=23)
// after
class MyRange(Range): pass
caster = RangeCaster('myrange', MyRange, oid=123, subtype_oid=23) Defensive patterns
Strategy: type-guard
Validate before calling
import builtins
if pyrange is Range or pyrange is builtins.range:
raise TypeError('pyrange must be a strict Range subclass, not base Range or builtin range')
if not (isinstance(pyrange, (str, type)) and (isinstance(pyrange, str) or issubclass(pyrange, Range))):
raise TypeError('pyrange must be a string or Range subclass') Type guard
def is_valid_pyrange(p) -> bool:
import builtins
return (isinstance(p, str)
or (isinstance(p, type) and issubclass(p, Range) and p is not Range
and p is not builtins.range)) Try / catch
try:
caster = RangeCaster('myrange', pyrange, ...)
except TypeError:
caster = RangeCaster('myrange', 'AutoRange', ...) Prevention
- Do not confuse psycopg2.Range with Python's builtin range.
- Define an explicit subclass rather than reusing the base Range.
When it happens
Trigger: Constructing RangeCaster with a pyrange that is a non-type value (e.g. an instance, a number), the bare Range base class, or a class unrelated to Range. Examples: RangeCaster('myrange', range, ...), RangeCaster('myrange', 42, ...), RangeCaster('myrange', some_instance, ...).
Common situations: Developers confuse Python's builtin 'range' type with psycopg2's Range, passing the builtin. Or they pass an instance instead of a class. The exclusion of the base Range (line 332) also trips people who pass 'Range' expecting generic behavior.
Related errors
- pgrange must be a string or a RangeAdapter strict subclass
- bound flags not valid: {bounds!r}
- RangeAdapter must be subclassed overriding its name or the g
- range types not available in version %s
- PostgreSQL range '{name}' not found
AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04).
Data as JSON: /data/errors/284596fa980ca8cf.json.
Report an issue: GitHub.