{"id":"d572e85e483b6256","repo":"psycopg/psycopg2","slug":"rangeadapter-must-be-subclassed-overriding-its-nam","errorCode":null,"errorMessage":"RangeAdapter must be subclassed overriding its name or the getquoted() method","messagePattern":"RangeAdapter must be subclassed overriding its name or the getquoted\\(\\) method","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"lib/_range.py","lineNumber":257,"sourceCode":"\n    This is an abstract class: concrete classes must set a `name` class\n    attribute or override `getquoted()`.\n    \"\"\"\n    name = None\n\n    def __init__(self, adapted):\n        self.adapted = adapted\n\n    def __conform__(self, proto):\n        if self._proto is ISQLQuote:\n            return self\n\n    def prepare(self, conn):\n        self._conn = conn\n\n    def getquoted(self):\n        if self.name is None:\n            raise NotImplementedError(\n                'RangeAdapter must be subclassed overriding its name '\n                'or the getquoted() method')\n\n        r = self.adapted\n        if r.isempty:\n            return b\"'empty'::\" + self.name.encode('utf8')\n\n        if r.lower is not None:\n            a = adapt(r.lower)\n            if hasattr(a, 'prepare'):\n                a.prepare(self._conn)\n            lower = a.getquoted()\n        else:\n            lower = b'NULL'\n\n        if r.upper is not None:\n            a = adapt(r.upper)\n            if hasattr(a, 'prepare'):","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/psycopg/psycopg2/blob/3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db/lib/_range.py#L239-L275","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Subclass RangeAdapter and set a 'name' class attribute to the PostgreSQL range type, e.g. class MyAdapter(RangeAdapter): name = 'myrange'.","Alternatively, override getquoted() in the subclass to return the proper bytes.","Prefer using register_range() or the built-in casters (NumericRange, DateRange, etc.) which wire up adapters automatically."],"exampleFix":"// before\nclass MyAdapter(RangeAdapter): pass\nregister_adapter(MyRange, MyAdapter)\n// after\nclass MyAdapter(RangeAdapter):\n    name = 'myrange'\nregister_adapter(MyRange, MyAdapter)","handlingStrategy":"type-guard","validationCode":"if getattr(MyAdapter, 'name', None) is None and 'getquoted' not in MyAdapter.__dict__:\n    raise TypeError('Adapter must set name or override getquoted')","typeGuard":"def is_concrete_range_adapter(cls) -> bool:\n    return (isinstance(cls, type)\n            and issubclass(cls, RangeAdapter)\n            and cls is not RangeAdapter\n            and (getattr(cls, 'name', None) is not None\n                 or 'getquoted' in cls.__dict__))","tryCatchPattern":"try:\n    adapter.getquoted()\nexcept NotImplementedError:\n    # adapter not concrete; use a built-in caster instead","preventionTips":["Prefer register_range() over manually wiring adapters.","Treat RangeAdapter as abstract; always subclass and set 'name'."],"tags":["adapter","range","abstract-class","not-implemented"],"analyzedSha":"3a6d9d6ddc6b53eaa80b712f5fa6b23abbdc38db","analyzedAt":"2026-08-04T19:56:51.958Z","schemaVersion":2}