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

  1. Pass a string (the PostgreSQL range type name, optionally schema-qualified) as the first argument to RangeCaster.
  2. Or pass a strict subclass of RangeAdapter that has a 'name' set.
  3. 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

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


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