Skip to content

fix: validate is_in/not_in receive a list in the filter builder#683

Open
NishchayMahor wants to merge 1 commit into
pinecone-io:mainfrom
NishchayMahor:fix/filter-builder-require-list
Open

fix: validate is_in/not_in receive a list in the filter builder#683
NishchayMahor wants to merge 1 commit into
pinecone-io:mainfrom
NishchayMahor:fix/filter-builder-require-list

Conversation

@NishchayMahor

@NishchayMahor NishchayMahor commented Jul 5, 2026

Copy link
Copy Markdown

What

In the metadata filter builder, Field.is_in() / Field.not_in() accept any value without checking it's a list. A common mistake — passing a bare value instead of a list — is silently turned into an invalid filter:

Field("genre").is_in("drama").to_dict()
# -> {"genre": {"$in": "drama"}}   ❌  (a bare string, not a list)

Because a string is iterable, nothing catches this client-side; the server rejects it later with an opaque 400 Invalid request, which is hard to trace back to the filter.

This is inconsistent with the numeric operators in the same class — gt / gte / lt / lte already validate their argument via _require_numeric and raise a clear TypeError.

Fix

Add a _require_list guard mirroring the existing _require_numeric, and call it from is_in / not_in (pinecone/utils/filter_builder.py). It accepts a list or tuple and raises a clear TypeError otherwise:

Field("genre").is_in("drama")
# TypeError: is_in requires a list of values, got str

Valid list/tuple usage is unchanged.

Testing

Added tests to TestFieldSetOperators in tests/unit/utils/test_filter_builder.py, mirroring the existing test_numeric_only_* cases (tuple accepted; is_in/not_in with a non-list raise TypeError matching "list"). Verified the behavior directly against the module — filter_builder.py is pure Python (list/tuple pass, str/int raise).


Note

Low Risk
Small, localized change to filter-builder validation with no auth, data, or API contract changes beyond catching invalid client usage earlier.

Overview
Field.is_in() and Field.not_in() now validate their argument client-side via a new _require_list helper (same pattern as _require_numeric on comparison operators).

Passing a bare scalar (e.g. Field("genre").is_in("drama")) no longer builds an invalid {"$in": "drama"} filter; it raises TypeError: is_in requires a list of values, got str. list and tuple inputs behave as before.

Unit tests cover tuple acceptance and TypeError for non-list values on both set operators.

Reviewed by Cursor Bugbot for commit 47a999d. Bugbot is set up for automated code reviews on this repo. Configure here.

Field.is_in() and Field.not_in() accepted any value, so Field("genre").is_in("drama")
silently produced {"genre": {"$in": "drama"}} — an invalid filter the server
rejects with an opaque 400. This is inconsistent with gt/gte/lt/lte, which already
validate via _require_numeric. Add a matching _require_list guard (accepts list or
tuple) and call it from is_in/not_in, with unit tests mirroring the numeric ones.
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