Skip to content

Commit 47b4c42

Browse files
committed
add: synonym_set APIs
1 parent fba5eb3 commit 47b4c42

12 files changed

Lines changed: 577 additions & 2 deletions

src/typesense/analytics_v1.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
from typesense.analytics_rules_v1 import AnalyticsRulesV1
2121
from typesense.api_call import ApiCall
22+
from typesense.logger import logger
23+
24+
_analytics_v1_deprecation_warned = False
2225

2326

2427
class AnalyticsV1(object):
@@ -39,6 +42,17 @@ def __init__(self, api_call: ApiCall) -> None:
3942
Args:
4043
api_call (ApiCall): The API call object for making requests.
4144
"""
42-
self.rules = AnalyticsRulesV1(api_call)
45+
self._rules = AnalyticsRulesV1(api_call)
46+
47+
@property
48+
def rules(self) -> AnalyticsRulesV1:
49+
global _analytics_v1_deprecation_warned
50+
if not _analytics_v1_deprecation_warned:
51+
logger.warning(
52+
"AnalyticsV1 is deprecated and will be removed in a future release. "
53+
"Use client.analytics instead."
54+
)
55+
_analytics_v1_deprecation_warned = True
56+
return self._rules
4357

4458

src/typesense/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from typesense.operations import Operations
5252
from typesense.stemming import Stemming
5353
from typesense.stopwords import Stopwords
54+
from typesense.synonym_sets import SynonymSets
5455

5556
TDoc = typing.TypeVar("TDoc", bound=DocumentSchema)
5657

@@ -109,6 +110,7 @@ def __init__(self, config_dict: ConfigDict) -> None:
109110
self.operations = Operations(self.api_call)
110111
self.debug = Debug(self.api_call)
111112
self.stopwords = Stopwords(self.api_call)
113+
self.synonym_sets = SynonymSets(self.api_call)
112114
self.metrics = Metrics(self.api_call)
113115
self.conversations_models = ConversationsModels(self.api_call)
114116
self.nl_search_models = NLSearchModels(self.api_call)

src/typesense/synonym.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"""
2323

2424
from typesense.api_call import ApiCall
25+
from typesense.logger import logger
26+
27+
_synonym_deprecation_warned = False
2528
from typesense.types.synonym import SynonymDeleteSchema, SynonymSchema
2629

2730

@@ -63,6 +66,7 @@ def retrieve(self) -> SynonymSchema:
6366
Returns:
6467
SynonymSchema: The schema containing the synonym details.
6568
"""
69+
self._maybe_warn_deprecation()
6670
return self.api_call.get(self._endpoint_path(), entity_type=SynonymSchema)
6771

6872
def delete(self) -> SynonymDeleteSchema:
@@ -72,6 +76,7 @@ def delete(self) -> SynonymDeleteSchema:
7276
Returns:
7377
SynonymDeleteSchema: The schema containing the deletion response.
7478
"""
79+
self._maybe_warn_deprecation()
7580
return self.api_call.delete(
7681
self._endpoint_path(),
7782
entity_type=SynonymDeleteSchema,
@@ -95,3 +100,12 @@ def _endpoint_path(self) -> str:
95100
self.synonym_id,
96101
],
97102
)
103+
104+
def _maybe_warn_deprecation(self) -> None:
105+
global _synonym_deprecation_warned
106+
if not _synonym_deprecation_warned:
107+
logger.warning(
108+
"The synonyms API (collections/{collection}/synonyms) is deprecated and will be "
109+
"removed in a future release. Use synonym sets (synonym_sets) instead."
110+
)
111+
_synonym_deprecation_warned = True

src/typesense/synonym_set.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Client for single Synonym Set operations."""
2+
3+
import sys
4+
5+
if sys.version_info >= (3, 11):
6+
import typing
7+
else:
8+
import typing_extensions as typing
9+
10+
from typesense.api_call import ApiCall
11+
from typesense.types.synonym_set import (
12+
SynonymSetDeleteSchema,
13+
SynonymSetRetrieveSchema,
14+
)
15+
16+
17+
class SynonymSet:
18+
def __init__(self, api_call: ApiCall, name: str) -> None:
19+
self.api_call = api_call
20+
self.name = name
21+
22+
@property
23+
def _endpoint_path(self) -> str:
24+
from typesense.synonym_sets import SynonymSets
25+
26+
return "/".join([SynonymSets.resource_path, self.name])
27+
28+
def retrieve(self) -> SynonymSetRetrieveSchema:
29+
response: SynonymSetRetrieveSchema = self.api_call.get(
30+
self._endpoint_path,
31+
as_json=True,
32+
entity_type=SynonymSetRetrieveSchema,
33+
)
34+
return response
35+
36+
def delete(self) -> SynonymSetDeleteSchema:
37+
response: SynonymSetDeleteSchema = self.api_call.delete(
38+
self._endpoint_path,
39+
entity_type=SynonymSetDeleteSchema,
40+
)
41+
return response
42+
43+

src/typesense/synonym_sets.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Client for Synonym Sets collection operations."""
2+
3+
import sys
4+
5+
if sys.version_info >= (3, 11):
6+
import typing
7+
else:
8+
import typing_extensions as typing
9+
10+
from typesense.api_call import ApiCall
11+
from typesense.types.synonym_set import (
12+
SynonymSetCreateSchema,
13+
SynonymSetDeleteSchema,
14+
SynonymSetRetrieveSchema,
15+
SynonymSetSchema,
16+
)
17+
18+
19+
class SynonymSets:
20+
resource_path: typing.Final[str] = "/synonym_sets"
21+
22+
def __init__(self, api_call: ApiCall) -> None:
23+
self.api_call = api_call
24+
25+
def retrieve(self) -> typing.List[SynonymSetSchema]:
26+
response: typing.List[SynonymSetSchema] = self.api_call.get(
27+
SynonymSets.resource_path,
28+
as_json=True,
29+
entity_type=typing.List[SynonymSetSchema],
30+
)
31+
return response
32+
33+
def __getitem__(self, synonym_set_name: str) -> "SynonymSet":
34+
from typesense.synonym_set import SynonymSet as PerSet
35+
36+
return PerSet(self.api_call, synonym_set_name)
37+
38+
def upsert(
39+
self,
40+
synonym_set_name: str,
41+
payload: SynonymSetCreateSchema,
42+
) -> SynonymSetSchema:
43+
response: SynonymSetSchema = self.api_call.put(
44+
"/".join([SynonymSets.resource_path, synonym_set_name]),
45+
body=payload,
46+
entity_type=SynonymSetSchema,
47+
)
48+
return response
49+
50+

src/typesense/synonyms.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
SynonymSchema,
3535
SynonymsRetrieveSchema,
3636
)
37+
from typesense.logger import logger
38+
39+
_synonyms_deprecation_warned = False
3740

3841
if sys.version_info >= (3, 11):
3942
import typing
@@ -98,6 +101,7 @@ def upsert(self, synonym_id: str, schema: SynonymCreateSchema) -> SynonymSchema:
98101
Returns:
99102
SynonymSchema: The created or updated synonym.
100103
"""
104+
self._maybe_warn_deprecation()
101105
response = self.api_call.put(
102106
self._endpoint_path(synonym_id),
103107
body=schema,
@@ -112,6 +116,7 @@ def retrieve(self) -> SynonymsRetrieveSchema:
112116
Returns:
113117
SynonymsRetrieveSchema: The schema containing all synonyms.
114118
"""
119+
self._maybe_warn_deprecation()
115120
response = self.api_call.get(
116121
self._endpoint_path(),
117122
entity_type=SynonymsRetrieveSchema,
@@ -139,3 +144,12 @@ def _endpoint_path(self, synonym_id: typing.Union[str, None] = None) -> str:
139144
synonym_id,
140145
],
141146
)
147+
148+
def _maybe_warn_deprecation(self) -> None:
149+
global _synonyms_deprecation_warned
150+
if not _synonyms_deprecation_warned:
151+
logger.warning(
152+
"The synonyms API (collections/{collection}/synonyms) is deprecated and will be "
153+
"removed in a future release. Use synonym sets (synonym_sets) instead."
154+
)
155+
_synonyms_deprecation_warned = True

src/typesense/types/synonym_set.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Synonym Set types for Typesense Python Client."""
2+
3+
import sys
4+
5+
from typesense.types.collection import Locales
6+
7+
if sys.version_info >= (3, 11):
8+
import typing
9+
else:
10+
import typing_extensions as typing
11+
12+
13+
class SynonymItemSchema(typing.TypedDict):
14+
"""
15+
Schema representing an individual synonym item inside a synonym set.
16+
17+
Attributes:
18+
id (str): Unique identifier for the synonym item.
19+
synonyms (list[str]): The synonyms array.
20+
root (str, optional): For 1-way synonyms, indicates the root word that words in
21+
the synonyms parameter map to.
22+
locale (Locales, optional): Locale for the synonym.
23+
symbols_to_index (list[str], optional): Symbols to index as-is in synonyms.
24+
"""
25+
26+
id: str
27+
synonyms: typing.List[str]
28+
root: typing.NotRequired[str]
29+
locale: typing.NotRequired[Locales]
30+
symbols_to_index: typing.NotRequired[typing.List[str]]
31+
32+
33+
class SynonymSetCreateSchema(typing.TypedDict):
34+
"""
35+
Schema for creating or updating a synonym set.
36+
37+
Attributes:
38+
items (list[SynonymItemSchema]): Array of synonym items.
39+
"""
40+
41+
items: typing.List[SynonymItemSchema]
42+
43+
44+
class SynonymSetSchema(SynonymSetCreateSchema):
45+
"""
46+
Schema representing a synonym set.
47+
48+
Attributes:
49+
name (str): Name of the synonym set.
50+
"""
51+
52+
name: str
53+
54+
55+
class SynonymSetsRetrieveSchema(typing.List[SynonymSetSchema]):
56+
"""Deprecated alias for list of synonym sets; use List[SynonymSetSchema] directly."""
57+
58+
59+
class SynonymSetRetrieveSchema(SynonymSetCreateSchema):
60+
"""Response schema for retrieving a single synonym set by name."""
61+
62+
63+
class SynonymSetDeleteSchema(typing.TypedDict):
64+
"""Response schema for deleting a synonym set.
65+
66+
Attributes:
67+
name (str): Name of the deleted synonym set.
68+
"""
69+
70+
name: str
71+
72+

tests/analytics_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typesense.api_call import ApiCall
88

99

10-
@pytest.mark.skipif(is_v30_or_above(Client({"api_key": "xyz", "nodes": [{"host": "localhost", "port": 8108, "protocol": "http"}]})), reason="Skip AnalyticsV1 tests on v30+")
10+
@pytest.mark.skipif(not is_v30_or_above(Client({"api_key": "xyz", "nodes": [{"host": "localhost", "port": 8108, "protocol": "http"}]})), reason="Skip AnalyticsV1 tests on v30+")
1111
def test_init(fake_api_call: ApiCall) -> None:
1212
"""Test that the AnalyticsV1 object is initialized correctly."""
1313
analytics = Analytics(fake_api_call)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Fixtures for the synonym set tests."""
2+
3+
import pytest
4+
import requests
5+
6+
from typesense.api_call import ApiCall
7+
from typesense.synonym_set import SynonymSet
8+
from typesense.synonym_sets import SynonymSets
9+
10+
11+
@pytest.fixture(scope="function", name="create_synonym_set")
12+
def create_synonym_set_fixture() -> None:
13+
"""Create a synonym set in the Typesense server."""
14+
url = "http://localhost:8108/synonym_sets/test-set"
15+
headers = {"X-TYPESENSE-API-KEY": "xyz"}
16+
data = {
17+
"items": [
18+
{
19+
"id": "company_synonym",
20+
"synonyms": ["companies", "corporations", "firms"],
21+
}
22+
]
23+
}
24+
25+
resp = requests.put(url, headers=headers, json=data, timeout=3)
26+
resp.raise_for_status()
27+
28+
29+
@pytest.fixture(scope="function", name="delete_all_synonym_sets")
30+
def clear_typesense_synonym_sets() -> None:
31+
"""Remove all synonym sets from the Typesense server."""
32+
url = "http://localhost:8108/synonym_sets"
33+
headers = {"X-TYPESENSE-API-KEY": "xyz"}
34+
35+
# Get the list of synonym sets
36+
response = requests.get(url, headers=headers, timeout=3)
37+
response.raise_for_status()
38+
data = response.json()
39+
40+
# Delete each synonym set
41+
for synset in data:
42+
name = synset.get("name")
43+
if not name:
44+
continue
45+
delete_url = f"{url}/{name}"
46+
delete_response = requests.delete(delete_url, headers=headers, timeout=3)
47+
delete_response.raise_for_status()
48+
49+
50+
@pytest.fixture(scope="function", name="actual_synonym_sets")
51+
def actual_synonym_sets_fixture(actual_api_call: ApiCall) -> SynonymSets:
52+
"""Return a SynonymSets object using a real API."""
53+
return SynonymSets(actual_api_call)
54+
55+
56+
@pytest.fixture(scope="function", name="actual_synonym_set")
57+
def actual_synonym_set_fixture(actual_api_call: ApiCall) -> SynonymSet:
58+
"""Return a SynonymSet object using a real API."""
59+
return SynonymSet(actual_api_call, "test-set")
60+
61+
62+
@pytest.fixture(scope="function", name="fake_synonym_sets")
63+
def fake_synonym_sets_fixture(fake_api_call: ApiCall) -> SynonymSets:
64+
"""Return a SynonymSets object with test values."""
65+
return SynonymSets(fake_api_call)
66+
67+
68+
@pytest.fixture(scope="function", name="fake_synonym_set")
69+
def fake_synonym_set_fixture(fake_api_call: ApiCall) -> SynonymSet:
70+
"""Return a SynonymSet object with test values."""
71+
return SynonymSet(fake_api_call, "test-set")
72+
73+

tests/import_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"operations",
2121
"override",
2222
"stopword",
23+
"synonym_set",
2324
"synonym",
2425
]
2526

@@ -41,6 +42,8 @@
4142
"overrides",
4243
"operations",
4344
"synonyms",
45+
"synonym_set",
46+
"synonym_sets",
4447
"preprocess",
4548
"stopwords",
4649
]

0 commit comments

Comments
 (0)