psycopg/psycopg2 · error · TypeError
pgrange must be a string or a RangeAdapter strict subclass
Error message
pgrange must be a string or a RangeAdapter strict subclass
What it means
Raised by RangeCaster._create_ranges() (lib/_range.py:324-326) when the 'pgrange' argument is neither a string nor a strict subclass of RangeAdapter (i.e. it is RangeAdapter itself, a non-RangeAdapter class, or a non-type value). The constructor uses pgrange to build the adapter that serializes Python Range objects to PostgreSQL range literals, so it must resolve to a concrete adapter.
Source
Thrown at lib/_range.py:325
"""Create Range and RangeAdapter classes if needed."""
# if got a string create a new RangeAdapter concrete type (with a name)
# 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
if isinstance(pgrange, str):
self.adapter = type(pgrange, (RangeAdapter,), {})
self.adapter.name = pgrange
else:
try:
if issubclass(pgrange, RangeAdapter) \
and pgrange is not RangeAdapter:
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*.View on GitHub (pinned to 3a6d9d6ddc)
Solutions
- Pass a string (the PostgreSQL range type name, optionally schema-qualified) as the first argument to RangeCaster.
- Or pass a strict subclass of RangeAdapter that has a 'name' set.
- Prefer register_range(pgrange_name, pyrange, conn) which handles adapter creation correctly.
Example fix
// before
caster = RangeCaster(my_adapter_base, MyRange, oid=123, subtype_oid=23)
// after
caster = RangeCaster('myrange', MyRange, oid=123, subtype_oid=23) Defensive patterns
Strategy: type-guard
Validate before calling
if not (isinstance(pgrange, str)
or (isinstance(pgrange, type) and issubclass(pgrange, RangeAdapter)
and pgrange is not RangeAdapter)):
raise TypeError('pgrange must be str or a strict RangeAdapter subclass') Type guard
def is_valid_pgrange(p) -> bool:
return (isinstance(p, str)
or (isinstance(p, type) and issubclass(p, RangeAdapter)
and p is not RangeAdapter)) Try / catch
try:
caster = RangeCaster(pgrange, pyrange, oid=..., subtype_oid=...)
except TypeError:
# fall back to passing the type name as a string
caster = RangeCaster(str(pgrange), pyrange, oid=..., subtype_oid=...) Prevention
- Pass the PostgreSQL type name (string) unless you have a custom adapter class.
- Use register_range() which normalizes this argument for you.
When it happens
Trigger: Constructing RangeCaster directly with an invalid first argument: RangeCaster(123, MyRange, ...), RangeCaster(RangeAdapter, MyRange, ...) (the base class is explicitly excluded at line 319), or RangeCaster(SomeOtherClass, MyRange, ...). register_range() forwards its first arg to RangeCaster._from_db which always passes a string, so this is mainly a direct-construction issue.
Common situations: Developers bypass register_range() and instantiate RangeCaster manually (the docstring at line 288-291 allows this) but pass the wrong type for pgrange, confusing it with the Python range class.
Related errors
- pyrange must be a type or a Range 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/90f33bb2d82af08d.json.
Report an issue: GitHub.