psycopg/psycopg2 · error · NotImplementedError

RangeAdapter must be subclassed overriding its name or the g

Error message

RangeAdapter must be subclassed overriding its name or the getquoted() method

What it means

Raised by RangeAdapter.getquoted() when the adapter's 'name' class attribute is still None. RangeAdapter is an abstract base: concrete subclasses must either set a 'name' (the PostgreSQL range type name) or override getquoted() to produce the serialized representation. Instantiating the bare RangeAdapter and letting it adapt a Range will trigger this because name is None by default.

Source

Thrown at lib/_range.py:257

    This is an abstract class: concrete classes must set a `name` class
    attribute or override `getquoted()`.
    """
    name = None

    def __init__(self, adapted):
        self.adapted = adapted

    def __conform__(self, proto):
        if self._proto is ISQLQuote:
            return self

    def prepare(self, conn):
        self._conn = conn

    def getquoted(self):
        if self.name is None:
            raise NotImplementedError(
                'RangeAdapter must be subclassed overriding its name '
                'or the getquoted() method')

        r = self.adapted
        if r.isempty:
            return b"'empty'::" + self.name.encode('utf8')

        if r.lower is not None:
            a = adapt(r.lower)
            if hasattr(a, 'prepare'):
                a.prepare(self._conn)
            lower = a.getquoted()
        else:
            lower = b'NULL'

        if r.upper is not None:
            a = adapt(r.upper)
            if hasattr(a, 'prepare'):

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Subclass RangeAdapter and set a 'name' class attribute to the PostgreSQL range type, e.g. class MyAdapter(RangeAdapter): name = 'myrange'.
  2. Alternatively, override getquoted() in the subclass to return the proper bytes.
  3. Prefer using register_range() or the built-in casters (NumericRange, DateRange, etc.) which wire up adapters automatically.

Example fix

// before
class MyAdapter(RangeAdapter): pass
register_adapter(MyRange, MyAdapter)
// after
class MyAdapter(RangeAdapter):
    name = 'myrange'
register_adapter(MyRange, MyAdapter)
Defensive patterns

Strategy: type-guard

Validate before calling

if getattr(MyAdapter, 'name', None) is None and 'getquoted' not in MyAdapter.__dict__:
    raise TypeError('Adapter must set name or override getquoted')

Type guard

def is_concrete_range_adapter(cls) -> bool:
    return (isinstance(cls, type)
            and issubclass(cls, RangeAdapter)
            and cls is not RangeAdapter
            and (getattr(cls, 'name', None) is not None
                 or 'getquoted' in cls.__dict__))

Try / catch

try:
    adapter.getquoted()
except NotImplementedError:
    # adapter not concrete; use a built-in caster instead

Prevention

When it happens

Trigger: Directly creating RangeAdapter(some_range) without subclassing, or defining a subclass that forgets to set 'name' and does not override getquoted(). This happens during adaptation (when psycopg2 adapts a Range subclass for SQL) via the register_adapter mechanism at lib/_range.py:470.

Common situations: A developer creates a custom Range subclass and registers RangeAdapter (the base) instead of a concrete subclass. Also happens when experimenting with the adapter API without reading the docstring that marks RangeAdapter as abstract (lib/_range.py:237-242).

Related errors


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