psycopg/psycopg2 · error · InterfaceError
failed to parse range: '{s}'
Error message
failed to parse range: '{s}' What it means
Raised by RangeCaster.parse() (lib/_range.py:441-443) when a string returned by PostgreSQL for a range column does not match the expected range literal grammar (regex at lib/_range.py:418-430). PostgreSQL range output looks like '[1,10)', '(,5]', 'empty', or '["a","z")'. If the server sends malformed data or the typecaster is misregistered, parsing fails.
Source
Thrown at lib/_range.py:443
(?: # upper bound:
" ( (?: [^"] | "")* ) " # - a quoted string
| ( [^"\)\]]+ ) # - or an unquoted string
)? # - or empty (not catched)
( \)|\] ) # upper bound flag
""", re.VERBOSE)
_re_undouble = re.compile(r'(["\\])\1')
def parse(self, s, cur=None):
if s is None:
return None
if s == 'empty':
return self.range(empty=True)
m = self._re_range.match(s)
if m is None:
raise InterfaceError(f"failed to parse range: '{s}'")
lower = m.group(3)
if lower is None:
lower = m.group(2)
if lower is not None:
lower = self._re_undouble.sub(r"\1", lower)
upper = m.group(5)
if upper is None:
upper = m.group(4)
if upper is not None:
upper = self._re_undouble.sub(r"\1", upper)
if cur is not None:
lower = cur.cast(self.subtype_oid, lower)
upper = cur.cast(self.subtype_oid, upper)
bounds = m.group(1) + m.group(6)View on GitHub (pinned to 3a6d9d6ddc)
Solutions
- Confirm the column is actually a range type and that the caster's oid matches it.
- Register casters with connection-scoped (not global) scope to avoid oid collisions across databases.
- Re-register the range type after a restore/migration that changes OIDs.
- Inspect the raw value with the default caster to see what the server actually sent.
Example fix
// before
# global caster with wrong oid intercepts a text column
caster = RangeCaster('myrange', MyRange, oid=25, subtype_oid=25)
caster._register() # oid 25 is text, not a range
// after
# register only on the specific connection with the correct oid
caster = register_range('myrange', MyRange, conn) Defensive patterns
Strategy: try-catch
Validate before calling
from psycopg2 import InterfaceError
# No pre-call validation; parse() runs at fetch time. Verify caster oid matches column:
with conn.cursor() as c:
c.execute("SELECT atttypid FROM pg_attribute WHERE attrelid=%s::regclass AND attname=%s", (tbl, col))
assert c.fetchone()[0] == expected_oid Try / catch
try:
rows = cur.fetchall()
except InterfaceError as e:
if 'failed to parse range' in str(e):
# re-register the caster with the correct oid, then re-query
pass
else: raise Prevention
- Register range casters at connection scope, not globally, to avoid OID collisions.
- Re-register casters after a database restore that changes OIDs.
- Confirm the column's atttypid equals the caster's oid before fetching.
When it happens
Trigger: Fetching from a range column whose value cannot be parsed by _re_range. This is almost always a misregistration (wrong subtype oid mapping a non-range column to a RangeCaster) rather than genuinely corrupt server data, because PostgreSQL itself validates range output.
Common situations: Manually creating a RangeCaster with the wrong oid that collides with another type, causing non-range strings to be routed to parse(). Also seen after a pg_dump/restore that changes OIDs while stale typecasters remain registered globally.
Related errors
- bound flags not valid: {bounds!r}
- RangeAdapter must be subclassed overriding its name or the g
- pgrange must be a string or a RangeAdapter strict subclass
- pyrange must be a type or a Range strict subclass
- range types not available in version %s
AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04).
Data as JSON: /data/errors/96efdb77d4ac3355.json.
Report an issue: GitHub.