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.status

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Pass a strict subclass of Range (e.g. NumericRange, DateRange, or your own subclass) as pyrange.
  2. Or pass a string to have RangeCaster auto-generate a new Range subclass with that name.
  3. 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

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


AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04). Data as JSON: /data/errors/284596fa980ca8cf.json. Report an issue: GitHub.