Skip to content

refactor(enums): make RedisVL's string enums StrEnum subclasses - #811

Draft
vishal-bala wants to merge 2 commits into
refactor/drop-python-3-10from
refactor/str-enum
Draft

vishal-bala wants to merge 2 commits into
refactor/drop-python-3-10from
refactor/str-enum

Conversation

@vishal-bala

@vishal-bala vishal-bala commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

On Python 3.11 and later, str() and f-strings on a (str, Enum) member return "Cls.MEMBER", not the member's value. Measured on 3.11.9, f"{VectorDataType.INT8}" gives 'VectorDataType.INT8'; on 3.10.19 it gave 'INT8', and str() has given the class-qualified form on every version. RedisVL builds error messages and query strings from these members, so they now print the class name: an SVS-VAMANA field with an int8 datatype fails with "Got: VectorDataType.INT8", and Tag("role") == [ChatRole.USER, ChatRole.TOOL] renders @role:{ChatRole\.USER|ChatRole\.TOOL}, which matches nothing. With Python 3.10 dropped, enum.StrEnum is available everywhere RedisVL runs, and its members stringify to their value.

Changes

StrEnum

Nine classes move from (str, Enum) to StrEnum, with their member values unchanged: FieldTypes, VectorDistanceMetric, VectorDataType, VectorIndexAlgorithm and CompressionType in redisvl/schema/fields.py, HybridPolicy in redisvl/query/query.py, Inclusive in redisvl/query/filter.py, ChatRole in redisvl/extensions/message_history/schema.py and MCPErrorCode in redisvl/mcp/errors.py. str(), format() and %s on a member now return its value. Nothing that already went through the value changes: equality and hashing against plain strings, repr(), json.dumps, pydantic model_dump() in both modes, IndexSchema.to_dict() and to_yaml(), and the FT.CREATE arguments redis-py encodes. The plain Enum classes, such as StorageType and FilterOperator, are untouched.

Stringification call sites

Where code reached for .value or getattr(member, "value", member) only to format a member, it now uses the member directly: the field-type list in the MCP list-indexes tool and the search tool's filter hint, _vector_dtype in the MCP upsert tool, the HYBRID_POLICY clause of VectorQuery, and the SVS-VAMANA compression error. Other .value calls stay, since they remain correct. Error messages that interpolate a member without .value now name the value, for example "is a text field" in the MCP lock-injection check.

Tests

tests/unit/test_str_enums.py pins the string form of every member, the encoded FT.CREATE arguments, the schema's pydantic and YAML output, and the error and query strings above. A new integration test in tests/integration/test_message_history.py stores messages with ChatRole members, reads the raw hash field back as b"user", b"tool" and b"assistant", and filters on a list of members.

Notes

redisvl/schema/validation.py checks JSON int8 and uint8 vectors against their ranges by comparing str(datatype).upper() with "INT8" and "UINT8". Under (str, Enum) that comparison never matched, so the range check never ran; with StrEnum it does. This PR leaves the file alone, because the separate fix on fix/int8-uint8-vector-range-check rewrites that comparison to use the members directly and adds the boundary tests. The two merge cleanly in either order, and the Release Notes below mention the range check so the behaviour change is not lost if this PR lands first.

The MCP error code does not reach MCP clients: FastMCP turns a raised RedisVLMCPError into a tool error whose text is the exception message alone, so the tool error payload is unchanged.

This PR is stacked on the Python 3.10 drop (#810) and should merge after it. It also overlaps #805, which fixes the INT8/UINT8 range check directly; whichever merges second should drop its duplicate test.

Release Notes

str() and f-strings on members of RedisVL's string enums (VectorDataType, VectorDistanceMetric, VectorIndexAlgorithm, CompressionType, FieldTypes, HybridPolicy, Inclusive, ChatRole and MCPErrorCode) now return the member's value, such as "INT8", rather than "VectorDataType.INT8"; code that matched the old form should compare with the member or its value. Schema validation now rejects JSON int8 vectors with elements outside -128..127 and uint8 vectors outside 0..255.

Reviewer Brief

Start here: redisvl/schema/fields.py, the five enum classes and BaseVectorFieldAttributes.field_data. Field attributes hold these members and pass them to redis-py unchanged, so this is where a change in how members turn into strings would reach Redis.

Where it could fail

  • redisvl/schema/validation.py:178: unchanged in this diff, but its str(datatype).upper() comparison now matches, so validate_object and load(..., validate_on_load=True) start rejecting out-of-range JSON int8/uint8 vectors that used to load.
  • User code that compared str(member) or an f-string with "VectorDataType.INT8"-style text now gets the bare value; nothing in redisvl/, tests/ or docs/ does this.

Not verified: cluster tests and live provider API tests, neither of which this change touches; Python 3.12, 3.14 and 3.15, which CI covers but this review did not run; the LeanVec cases in tests/integration/test_svs_integration.py, where 7 of 19 fail locally with zero documents indexed, identically on the parent commit.

Beyond CI: a before-and-after dump of every member's str, format, %s, repr, JSON and redis-py encoding, plus the schema's to_dict, to_yaml, both model_dump modes and the encoded FT.CREATE arguments, differs only in str, format and %s and in the three strings built from them (the SVS-VAMANA datatype error, a Tag filter given a list of members, and the INT8 range check), identically on 3.11.9 and 3.13.9; 42 of the 44 new unit tests fail against the parent commit, and the other 2 pin output that must not change.

Reviewed 239973b8 (rebased unchanged from e31bacbd), fixes in dfe48d35 · Claude Code (claude-opus-5-5) · lenses: correctness, security, design, tests, docs, Redis behaviour

str() and f-strings on a (str, Enum) member give "Cls.MEMBER" on Python
3.11+, so error messages and query strings built from members printed the
class name. StrEnum returns the value. Serialisation, YAML output and the
FT.CREATE arguments sent to Redis already used the value and are unchanged.

Drops the getattr(..., "value", ...) workarounds in the MCP tools and the
.value calls that existed only to format a member, and pins the visible
string form in tests/unit/test_str_enums.py.
The hybrid-policy, stored-role and MCP error-code checks passed under
(str, Enum) as well and duplicate existing coverage; the integration test
already pins the stored ChatRole bytes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant