psycopg/psycopg2 · error · ValueError
bound flags not valid: {bounds!r}
Error message
bound flags not valid: {bounds!r} What it means
Raised by Range.__init__ when the 'bounds' argument is not one of the four valid PostgreSQL range bound flags: '[)', '(]', '()', or '[]'. These flags denote whether each endpoint is inclusive (bracket) or exclusive (parenthesis). Any other string value (including lowercase variants or reversed pairs) is rejected because it cannot be mapped to a PostgreSQL range literal.
Source
Thrown at lib/_range.py:50
from psycopg2.extensions import new_type, new_array_type, register_type
class Range:
"""Python representation for a PostgreSQL |range|_ type.
:param lower: lower bound for the range. `!None` means unbound
:param upper: upper bound for the range. `!None` means unbound
:param bounds: one of the literal strings ``()``, ``[)``, ``(]``, ``[]``,
representing whether the lower or upper bounds are included
:param empty: if `!True`, the range is empty
"""
__slots__ = ('_lower', '_upper', '_bounds')
def __init__(self, lower=None, upper=None, bounds='[)', empty=False):
if not empty:
if bounds not in ('[)', '(]', '()', '[]'):
raise ValueError(f"bound flags not valid: {bounds!r}")
self._lower = lower
self._upper = upper
self._bounds = bounds
else:
self._lower = self._upper = self._bounds = None
def __repr__(self):
if self._bounds is None:
return f"{self.__class__.__name__}(empty=True)"
else:
return "{}({!r}, {!r}, {!r})".format(self.__class__.__name__,
self._lower, self._upper, self._bounds)
def __str__(self):
if self._bounds is None:
return 'empty'
View on GitHub (pinned to 3a6d9d6ddc)
Solutions
- Use exactly one of '[)', '(]', '()', '[]' as the bounds argument (default is '[)').
- If the bounds string comes from external input, validate it against ('[)', '(]', '()', '[]') before constructing the Range.
- If you actually want an empty range, pass empty=True instead of trying to express emptiness via bounds.
Example fix
// before r = Range(1, 10, bounds=']( ') // after r = Range(1, 10, bounds='[)')
Defensive patterns
Strategy: validation
Validate before calling
VALID_BOUNDS = ('[)', '(]', '()', '[]')
if bounds not in VALID_BOUNDS:
raise ValueError(f'invalid bounds {bounds!r}; expected one of {VALID_BOUNDS}')
r = Range(lower, upper, bounds=bounds) Type guard
def is_valid_bounds(b) -> bool:
return isinstance(b, str) and b in ('[)', '(]', '()', '[]') Try / catch
try:
r = Range(lo, hi, bounds=b)
except ValueError as e:
# log and fall back to default bounds
r = Range(lo, hi) Prevention
- Never construct bounds strings dynamically without validating against the 4 allowed flags.
- Treat bounds as an enum, not free text; centralize the allowed set in a constant.
When it happens
Trigger: Constructing a Range (or any subclass like NumericRange, DateRange) with an explicit bounds keyword that is misspelled or wrong, e.g. Range(1, 10, bounds='[]'), Range(1, 10, bounds='[ ]'), Range(1, 10, bounds=')('), or passing a non-string object. The check at lib/_range.py:49 runs only when empty=False (the default).
Common situations: Developers confuse the bracket/parenthesis order (writing '][' or ')('), add a space inside the flag, or use uppercase. Also occurs when bounds are read from user input or a config file without validation. The empty=True path bypasses this entirely, so some users forget that non-empty ranges always require a valid flag.
Related errors
- pgrange must be a string or a RangeAdapter strict subclass
- pyrange must be a type or a Range strict subclass
- RangeAdapter must be subclassed overriding its name or the g
- range types not available in version %s
- PostgreSQL range '{name}' not found
AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04).
Data as JSON: /data/errors/17633e51e4e82e97.json.
Report an issue: GitHub.