Source code for pinecone.utils.filter_builder
"""Programmatic filter builder for metadata queries.
Produces plain dicts compatible with the ``filter`` parameter on
query, delete, update, and search methods.
Usage::
from pinecone import Field
f = (Field("genre") == "drama") & (Field("year").gte(2020))
results = index.query(vector=[...], filter=f.to_dict())
"""
from __future__ import annotations
from typing import Any
# Value types accepted by equality / set operators.
ScalarValue = str | int | float | bool
# Value types accepted by ordering (numeric-only) operators.
NumericValue = int | float
class Condition:
"""A composable metadata filter condition.
Wraps an internal filter dict and supports ``&`` (AND) and ``|`` (OR)
combination with automatic flattening of nested same-type logical
operators.
"""
__slots__ = ("_filter",)
def __init__(self, filter_dict: dict[str, Any]) -> None:
self._filter = filter_dict
# -- logical combinators --------------------------------------------------
def __and__(self, other: Condition) -> Condition:
left = list(self._filter["$and"]) if "$and" in self._filter else [self._filter]
right = list(other._filter["$and"]) if "$and" in other._filter else [other._filter]
return Condition({"$and": [*left, *right]})
def __or__(self, other: Condition) -> Condition:
left = list(self._filter["$or"]) if "$or" in self._filter else [self._filter]
right = list(other._filter["$or"]) if "$or" in other._filter else [other._filter]
return Condition({"$or": [*left, *right]})
# -- serialisation --------------------------------------------------------
def to_dict(self) -> dict[str, Any]:
"""Return the filter as a plain dict.
Raises:
ValueError: If the condition contains no operators.
"""
if not self._filter:
raise ValueError("Cannot convert an empty condition to a filter dict")
return self._filter
def __repr__(self) -> str:
return f"Condition({self._filter!r})"
[docs]
class Field:
"""Represents a metadata field name for building filter expressions.
Usage::
Field("genre") == "drama" # {"genre": {"$eq": "drama"}}
Field("score").gt(0.5) # {"score": {"$gt": 0.5}}
"""
__slots__ = ("_name",)
[docs]
def __init__(self, name: str) -> None:
self._name = name
# -- comparison operators (numeric only) ----------------------------------
def _require_numeric(self, op: str, value: Any) -> None:
if not isinstance(value, (int, float)) or isinstance(value, bool):
raise TypeError(
f"{op} requires a numeric value (int or float), got {type(value).__name__}"
)
[docs]
def gt(self, value: int | float) -> Condition:
"""``$gt`` — greater than (*numeric only*)."""
self._require_numeric("gt", value)
return Condition({self._name: {"$gt": value}})
[docs]
def gte(self, value: int | float) -> Condition:
"""``$gte`` — greater than or equal (*numeric only*)."""
self._require_numeric("gte", value)
return Condition({self._name: {"$gte": value}})
[docs]
def lt(self, value: int | float) -> Condition:
"""``$lt`` — less than (*numeric only*)."""
self._require_numeric("lt", value)
return Condition({self._name: {"$lt": value}})
[docs]
def lte(self, value: int | float) -> Condition:
"""``$lte`` — less than or equal (*numeric only*)."""
self._require_numeric("lte", value)
return Condition({self._name: {"$lte": value}})
# -- equality operators ---------------------------------------------------
def __eq__(self, value: object) -> Condition: # type: ignore[override]
"""``$eq`` — equal to."""
return Condition({self._name: {"$eq": value}})
def __ne__(self, value: object) -> Condition: # type: ignore[override]
"""``$ne`` — not equal to."""
return Condition({self._name: {"$ne": value}})
# -- set operators --------------------------------------------------------
[docs]
def is_in(self, values: list[str | int | float | bool]) -> Condition:
"""``$in`` — value is in the given list."""
return Condition({self._name: {"$in": values}})
[docs]
def not_in(self, values: list[str | int | float | bool]) -> Condition:
"""``$nin`` — value is not in the given list."""
return Condition({self._name: {"$nin": values}})
# -- exists operator ------------------------------------------------------
[docs]
def exists(self) -> Condition:
"""``$exists`` — field exists."""
return Condition({self._name: {"$exists": True}})
def __repr__(self) -> str:
return f"Field({self._name!r})"
# Backcompat alias, :meta private:
FilterBuilder = Field