ErrLookup › Background articles › ValueError: the wrong-value guard at the library boundary
ValueError: the wrong-value guard at the library boundary
ValueError is Python's built-in signal that an argument had the right type but an unacceptable value, and across the 31 repositories in this family it shows up most often as an eager check at a library's input boundary. A negative precision, a geodetic geometry paired with a linear Distance, an unknown function name inside a query string, or a path that escapes its directory are all refused before any real work begins. Developers meet it the instant a call crosses into library code with a value the library's own contract forbids, and the message almost always names the offending argument.
Distilled from 1,102 documented records across 31 repositories.
Background
ValueError sits in the narrow middle of Python's exception hierarchy: a TypeError says the argument was the wrong kind of object entirely, a custom library exception says something domain-specific went wrong, and a ValueError says the object was the right kind but its value broke a contract. That is why it is the exception libraries reach for when validating arguments at a trust boundary. The records show it raised at construction time (Django's BloomIndex and BrinIndex, pandas' BinOp and FuncNode), at query-parse time (pandas eval/query, psycopg2 SQL templates), and on the first line of an operation (SQLAlchemy relationship assignment, numpy float-formatting helpers) — always before the library commits to the work.
The dominant shape is a single boolean check followed by a raise. numpy's _none_or_positive_arg accepts None or a non-negative number and rejects anything below zero; pika's HeartbeatChecker requires a timeout of at least one; alembic's batch mode demands a named constraint before it can copy a table; urllib3 forbids ssl_version alongside the newer minimum/maximum knobs. These guards exist because the alternative — proceeding and producing a wrong result, a corrupted file, or a less helpful error downstream — is worse. Several are explicitly defensive: pandas' BinOp message notes the standard parser only ever emits known operators, so reaching that raise usually means an internally constructed node or a version-drift bug.
The family diverges by concern. pandas uses ValueError as the enforcement arm of a whitelist: its eval/query engine resolves names against a fixed MATHOPS tuple and rejects anything outside it, and its transform machinery rejects results whose index does not match the input. Django GIS raises it when an operation is legal in one backend or field configuration but meaningless in another — a Distance object on geodetic geometry, a band index on a bbox-only operator, a PostGIS-style DE-9IM mask on Oracle. SQLAlchemy raises it for relationship-graph consistency: a bidirectional assignment that cascades into an unexpected attribute, or a check_old guard that no longer matches. pip raises it for security — a Zip-Slip path traversal, or an empty file-name component derived from a URL.
Not every use is clean. The MCP time server wraps its entire tool body in a try/except and re-raises every failure — including a structured McpError carrying an INVALID_PARAMS code — as a plain ValueError, destroying the error code and nesting the message twice. The Dify chat endpoint lets a field_validator's ValueError surface as a 422 through Pydantic, which is idiomatic but means the caller never sees the original uuid.UUID failure directly. The common thread from the caller's side is a single failed call with a message that names the offending value or argument; whether the caller can recover programmatically depends entirely on how faithfully the library preserved the underlying condition.
Common causes
- Argument value out of valid range.A numeric or structural argument falls outside the accepted range. numpy rejects a negative precision or pad_left, pika rejects a heartbeat timeout below one, and Django's BrinIndex rejects a non-positive pages_per_range. The value is the right type but too small, negative, or otherwise forbidden, and the guard fires before the library uses it.
- Operation unsupported for the current configuration or backend.The call is valid in principle but meaningless given the field type, SRID, or database backend. Django GIS refuses a Distance object on geodetic geometry (PostGIS and MySQL both), a band index on a bbox-only raster operator, and a PostGIS-style DE-9IM mask on Oracle. The library raises rather than produce a silently wrong result.
- Token or name not on the library's whitelist.An expression engine, SQL templater, or query parser resolves a name or operator against a fixed set and rejects anything outside it. pandas eval/query rejects functions outside MATHOPS and operators outside _binary_ops_dict; psycopg2 rejects any format spec inside a placeholder. This is deliberate surface-area restriction, not a parser bug.
- Data shape or index contract violation.The operation requires its input or output to share an index, advance monotonically, or be strictly boolean. pandas transform rejects a result whose index differs from the input, the numba rolling kernel rejects non-monotonic window boundaries, and boolean indexing rejects a mask containing NaN rather than silently dropping rows.
- Missing or mutually exclusive options.The caller supplied a combination the library cannot honor, or omitted a required piece. urllib3 forbids ssl_version together with ssl_minimum_version or ssl_maximum_version; pip forbids a root_dir alongside subprocess data sources; alembic's batch mode rejects an unnamed constraint because it cannot be re-created deterministically on the table copy.
- Identifier or path fails a safety or format check.The value would be unsafe or unparseable if used. pip rejects an archive member whose resolved path escapes the destination directory and a URL-derived name that collapses to empty, '.', or '..'; Dify rejects a non-UUID conversation_id or parent_message_id before the handler runs.
- Missing third-party dependency.A backend declared by configuration is not importable in the environment. Django's password hashers wrap the underlying ImportError as a ValueError naming the hasher and the missing module (argon2, bcrypt, scrypt), so the failure surfaces at startup or on the first password operation.
- Relationship or internal invariant violation.The library detects an inconsistency in its own object graph rather than bad user input. SQLAlchemy raises when a bidirectional assignment cascades into an unexpected attribute, or when a check_old guard no longer matches the loaded value; redis-py raises when maintenance notifications are considered enabled but no handler is supplied.
What usually fixes it
- Read the full message before changing anything. ValueErrors in this family are unusually specific: numpy interpolates the offending argument name, pandas lists the valid operator set, pydantic echoes the failing predicate, and the MCP time server appends the original error text after 'query: '. The cause is usually on the line you were shown.
- Validate at your own boundary before calling. Coerce types (int for pages_per_range), fill NA on masks, clamp widths with max(0, value), resolve identifiers client-side, and assert container types — so the library's guard is never the place you discover the problem.
- Match the operation to the backend or configuration. Check field type and SRID before GIS distance lookups, confirm an operator has a func before passing band indices, prefer the default Cython engine where the numba ordering constraint is too strict, and use geography=True or a projected SRID where linear units are required.
- Restrict dynamic input to a known whitelist before assembling query strings, SQL templates, or index definitions. Validate function names against MATHOPS, keep template placeholders to the restricted {}, {0}, {name} forms, and unit-test that every operator token you emit is registered.
- For security guards, never disable the check. Do not pass check=False to unarchive a Zip-Slip archive, and do not coerce a '.'/'..' file name into something usable — treat the rejected input as untrusted, audit its source, and extract into a sandbox.
- When you wrap errors, preserve structured information. Re-raise McpError unchanged and narrow the except so callers can still branch on the real error code; the catch-all-to-ValueError pattern in the MCP time server is the anti-pattern this family teaches against.
Go deeper
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Documented occurrences
- Only numeric values of degree units are allowed on geographic DWithin queries.(django/django)
- Invalid function call {node.func.id}(pandas-dev/pandas)
- "{name}" is not a supported function(pandas-dev/pandas)
- Band indices are not allowed for this operator, it works on bbox only.(django/django)
- must be a valid UUID(langgenius/dify)
- Code block (lines {start_line}-{end_line_no}) has different number of lines than the original block ({len(block_a['content'])} vs {len(block_b['content'])})(tiangolo/fastapi)
- Error processing mcp-server-time query: {str(e)}(modelcontextprotocol/servers)
- Constraint must have a name(sqlalchemy/alembic)
- Bidirectional attribute conflict detected: Passing object %s to attribute "%s" triggers a modify event on attribute "%s" via the backref "%s".(sqlalchemy/sqlalchemy)
- Either maint_notifications_pool_handler or oss_cluster_maint_notifications_handler must be set(redis/redis-py)
- Invalid SDO_RELATE mask: "%s"(django/django)
- BloomIndex.columns must be a list or tuple.(django/django)
- Function did not transform(pandas-dev/pandas)
- Start/End ordering requirement is violated at index {i}(pandas-dev/pandas)
- Only numeric values of degree units are allowed on geodetic distance queries.(django/django)
- Can't specify both 'ssl_version' and either 'ssl_minimum_version' or 'ssl_maximum_version'(urllib3/urllib3)
- Cannot mask with non-boolean array containing NA / NaN values(pandas-dev/pandas)
- {name} must be >= 0(numpy/numpy)
- Expected {predicate_err if isinstance(predicate_err, str) else predicate_err()}(pydantic/pydantic)
- no format specification supported by SQL(psycopg/psycopg2)
…and 1,082 more across the corpus — use search.
Honest provenance: generated on 2026-08-12 from AI-assisted analysis of the linked records. See how records are made.