Skip to content

Commit 3758724

Browse files
authored
fix(preview): accept deprecated field= kwarg on PreviewTextQuery (#665)
## Summary The rename `PreviewTextQuery.field` (str) → `PreviewTextQuery.fields` (list[str]) at `fefc4850` is a breaking call-site change for any user written against pre-rename `PreviewTextQuery`. The preview namespace's documented SemVer policy permits this, but the cost is small to make it non-breaking, so this PR does. - `field=` is reintroduced as a deprecated kwarg/attribute on the canonical `msgspec.Struct` (no parallel class, per the shim-purity convention). - When provided, it migrates to `fields=[field]` in `__post_init__` and emits a `DeprecationWarning`. - `omit_defaults=True` keeps the encoded payload free of a `"field": null` artifact; existing wire-shape tests are unchanged. - Decoding the legacy single-field backend response variant (`{"type": "text", "field": "...", "query": "..."}`) now works and routes through the same migration path. - Mutually exclusive: passing both `field=` and `fields=` raises `ValueError`; passing neither raises `ValueError`. ## Why Avoids breaking callers without forcing a re-design of the preview API. The backend already accepts both wire forms, so this only restores the Python-side call signature. ## Test plan - [x] `uv run pytest tests/unit/preview/models/test_score_by.py` — 21/21 pass, including 6 new tests covering the deprecation path - [x] `uv run pytest tests/unit/preview/` — 705 passed, 1 xfailed (no regressions) - [x] `uv run mypy --strict pinecone/` — clean across 186 source files - [x] `uv run ruff check` + `ruff format --check` — clean - [ ] Integration tests in `tests/integration/preview/` — out of scope for this PR (no behavior change on the wire) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: changes are confined to preview model initialization/serialization with added validation and tests; main risk is tightening constructor requirements for callers who passed neither `fields` nor `field`. > > **Overview** > Restores backward compatibility for `PreviewTextQuery` by reintroducing deprecated `field` support that auto-migrates to `fields=[...]` and emits a `DeprecationWarning`, while enforcing that callers provide *exactly one* of `field` or `fields`. > > Ensures JSON encoding stays canonical (no `"field": null`) via `omit_defaults=True`, and adds unit coverage for migration, validation errors, and decoding the legacy backend `{"field": ...}` variant. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c559ac0. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 926eb9d commit 3758724

2 files changed

Lines changed: 77 additions & 2 deletions

File tree

pinecone/preview/models/score_by.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
]
1616

1717

18-
class PreviewTextQuery(Struct, tag="text", tag_field="type", kw_only=True):
18+
class PreviewTextQuery(Struct, tag="text", tag_field="type", kw_only=True, omit_defaults=True):
1919
"""Full-text search query for scoring documents.
2020
2121
.. admonition:: Preview
@@ -29,10 +29,33 @@ class PreviewTextQuery(Struct, tag="text", tag_field="type", kw_only=True):
2929
Attributes:
3030
fields: One or more text field names to search across.
3131
query: Search query string.
32+
field: Deprecated alias for ``fields``. If provided, it is migrated
33+
to ``fields=[field]`` and a ``DeprecationWarning`` is emitted.
34+
Cleared to ``None`` after migration; read ``fields`` instead.
3235
"""
3336

34-
fields: list[str]
3537
query: str
38+
fields: list[str] | None = None
39+
field: str | None = None
40+
41+
def __post_init__(self) -> None:
42+
if self.field is not None:
43+
if self.fields is not None:
44+
raise ValueError(
45+
"PreviewTextQuery accepts `fields=[...]` or the deprecated "
46+
"`field=...`, but not both."
47+
)
48+
import warnings
49+
50+
warnings.warn(
51+
"PreviewTextQuery `field` is deprecated; use `fields=[...]`.",
52+
DeprecationWarning,
53+
stacklevel=2,
54+
)
55+
self.fields = [self.field]
56+
self.field = None
57+
elif self.fields is None:
58+
raise ValueError("PreviewTextQuery requires `fields=[...]`.")
3659

3760

3861
class PreviewQueryStringQuery(Struct, tag="query_string", tag_field="type", kw_only=True):

tests/unit/preview/models/test_score_by.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from __future__ import annotations
44

5+
import warnings
6+
57
import msgspec
8+
import pytest
69

710
from pinecone.preview.models.score_by import (
811
PreviewDenseVectorQuery,
@@ -52,6 +55,55 @@ def test_text_query_no_extra_fields() -> None:
5255
assert not hasattr(q, "sparse_values")
5356

5457

58+
# ---------------------------------------------------------------------------
59+
# PreviewTextQuery — deprecated `field=` alias
60+
# ---------------------------------------------------------------------------
61+
62+
63+
def test_text_query_deprecated_field_kwarg_migrates_to_fields() -> None:
64+
with pytest.warns(DeprecationWarning, match=r"`field` is deprecated"):
65+
q = PreviewTextQuery(field="title", query="hello")
66+
assert q.fields == ["title"]
67+
assert q.field is None
68+
69+
70+
def test_text_query_deprecated_field_kwarg_keeps_wire_shape_clean() -> None:
71+
with pytest.warns(DeprecationWarning, match=r"`field` is deprecated"):
72+
q = PreviewTextQuery(field="title", query="hello")
73+
decoded = msgspec.json.decode(msgspec.json.encode(q))
74+
assert decoded == {"type": "text", "fields": ["title"], "query": "hello"}
75+
assert "field" not in decoded
76+
77+
78+
def test_text_query_canonical_fields_emits_no_warning() -> None:
79+
with warnings.catch_warnings():
80+
warnings.simplefilter("error", DeprecationWarning)
81+
q = PreviewTextQuery(fields=["title"], query="hello")
82+
assert q.fields == ["title"]
83+
assert q.field is None
84+
85+
86+
def test_text_query_rejects_both_field_and_fields() -> None:
87+
with warnings.catch_warnings():
88+
warnings.simplefilter("ignore", DeprecationWarning)
89+
with pytest.raises(ValueError, match="not both"):
90+
PreviewTextQuery(field="title", fields=["body"], query="hello")
91+
92+
93+
def test_text_query_rejects_neither_field_nor_fields() -> None:
94+
with pytest.raises(ValueError, match="requires `fields"):
95+
PreviewTextQuery(query="hello")
96+
97+
98+
def test_text_query_decodes_backend_field_variant() -> None:
99+
"""Backend may return the legacy single-field form; decode should migrate."""
100+
raw = b'{"type": "text", "field": "body", "query": "hello"}'
101+
with pytest.warns(DeprecationWarning, match=r"`field` is deprecated"):
102+
result = msgspec.json.decode(raw, type=PreviewTextQuery)
103+
assert result.fields == ["body"]
104+
assert result.field is None
105+
106+
55107
# ---------------------------------------------------------------------------
56108
# PreviewQueryStringQuery
57109
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)