Skip to content

Commit 723b6dc

Browse files
committed
feat(synonym-set): add async support for synonym set operations
- add AsyncSynonymSet class for async individual synonym set operations - add AsyncSynonymSets class for async synonym sets collection operations - add async tests for synonym set and sets functionality - add async fixtures for testing async synonym set operations - remove future annotations imports from test files
1 parent 0487a9a commit 723b6dc

File tree

6 files changed

+348
-2
lines changed

6 files changed

+348
-2
lines changed

src/typesense/async_synonym_set.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Client for single Synonym Set operations (async)."""
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.async_api_call import AsyncApiCall
11+
from typesense.types.synonym_set import (
12+
SynonymItemDeleteSchema,
13+
SynonymItemSchema,
14+
SynonymSetCreateSchema,
15+
SynonymSetDeleteSchema,
16+
SynonymSetRetrieveSchema,
17+
)
18+
19+
20+
class AsyncSynonymSet:
21+
def __init__(self, api_call: AsyncApiCall, name: str) -> None:
22+
self.api_call = api_call
23+
self.name = name
24+
25+
@property
26+
def _endpoint_path(self) -> str:
27+
from typesense.async_synonym_sets import AsyncSynonymSets
28+
29+
return "/".join([AsyncSynonymSets.resource_path, self.name])
30+
31+
async def retrieve(self) -> SynonymSetRetrieveSchema:
32+
response: SynonymSetRetrieveSchema = await self.api_call.get(
33+
self._endpoint_path,
34+
as_json=True,
35+
entity_type=SynonymSetRetrieveSchema,
36+
)
37+
return response
38+
39+
async def upsert(self, set: SynonymSetCreateSchema) -> SynonymSetCreateSchema:
40+
response: SynonymSetCreateSchema = await self.api_call.put(
41+
self._endpoint_path,
42+
entity_type=SynonymSetCreateSchema,
43+
body=set,
44+
)
45+
return response
46+
47+
async def delete(self) -> SynonymSetDeleteSchema:
48+
response: SynonymSetDeleteSchema = await self.api_call.delete(
49+
self._endpoint_path,
50+
entity_type=SynonymSetDeleteSchema,
51+
)
52+
return response
53+
54+
@property
55+
def _items_path(self) -> str:
56+
return "/".join([self._endpoint_path, "items"]) # /synonym_sets/{name}/items
57+
58+
async def list_items(
59+
self,
60+
*,
61+
limit: typing.Union[int, None] = None,
62+
offset: typing.Union[int, None] = None,
63+
) -> typing.List[SynonymItemSchema]:
64+
params: typing.Dict[str, typing.Union[int, None]] = {
65+
"limit": limit,
66+
"offset": offset,
67+
}
68+
clean_params: typing.Dict[str, int] = {
69+
k: v for k, v in params.items() if v is not None
70+
}
71+
response: typing.List[SynonymItemSchema] = await self.api_call.get(
72+
self._items_path,
73+
as_json=True,
74+
entity_type=typing.List[SynonymItemSchema],
75+
params=clean_params or None,
76+
)
77+
return response
78+
79+
async def get_item(self, item_id: str) -> SynonymItemSchema:
80+
response: SynonymItemSchema = await self.api_call.get(
81+
"/".join([self._items_path, item_id]),
82+
as_json=True,
83+
entity_type=SynonymItemSchema,
84+
)
85+
return response
86+
87+
async def upsert_item(
88+
self, item_id: str, item: SynonymItemSchema
89+
) -> SynonymItemSchema:
90+
response: SynonymItemSchema = await self.api_call.put(
91+
"/".join([self._items_path, item_id]),
92+
body=item,
93+
entity_type=SynonymItemSchema,
94+
)
95+
return response
96+
97+
async def delete_item(self, item_id: str) -> SynonymItemDeleteSchema:
98+
# API returns {"id": "..."} for delete; openapi defines SynonymItemDeleteResponse with name but for items it's id
99+
response: SynonymItemDeleteSchema = await self.api_call.delete(
100+
"/".join([self._items_path, item_id]), entity_type=SynonymItemDeleteSchema
101+
)
102+
return response
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Client for Synonym Sets collection operations (async)."""
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.async_api_call import AsyncApiCall
11+
from typesense.async_synonym_set import AsyncSynonymSet
12+
from typesense.types.synonym_set import (
13+
SynonymSetSchema,
14+
)
15+
16+
17+
class AsyncSynonymSets:
18+
resource_path: typing.Final[str] = "/synonym_sets"
19+
20+
def __init__(self, api_call: AsyncApiCall) -> None:
21+
self.api_call = api_call
22+
23+
async def retrieve(self) -> typing.List[SynonymSetSchema]:
24+
response: typing.List[SynonymSetSchema] = await self.api_call.get(
25+
AsyncSynonymSets.resource_path,
26+
as_json=True,
27+
entity_type=typing.List[SynonymSetSchema],
28+
)
29+
return response
30+
31+
def __getitem__(self, synonym_set_name: str) -> AsyncSynonymSet:
32+
from typesense.async_synonym_set import AsyncSynonymSet as PerSet
33+
34+
return PerSet(self.api_call, synonym_set_name)

tests/fixtures/synonym_set_fixtures.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import requests
55

66
from typesense.api_call import ApiCall
7+
from typesense.async_api_call import AsyncApiCall
8+
from typesense.async_synonym_set import AsyncSynonymSet
9+
from typesense.async_synonym_sets import AsyncSynonymSets
710
from typesense.synonym_set import SynonymSet
811
from typesense.synonym_sets import SynonymSets
912

@@ -69,3 +72,31 @@ def fake_synonym_sets_fixture(fake_api_call: ApiCall) -> SynonymSets:
6972
def fake_synonym_set_fixture(fake_api_call: ApiCall) -> SynonymSet:
7073
"""Return a SynonymSet object with test values."""
7174
return SynonymSet(fake_api_call, "test-set")
75+
76+
77+
@pytest.fixture(scope="function", name="actual_async_synonym_sets")
78+
def actual_async_synonym_sets_fixture(
79+
actual_async_api_call: AsyncApiCall,
80+
) -> AsyncSynonymSets:
81+
"""Return a AsyncSynonymSets object using a real API."""
82+
return AsyncSynonymSets(actual_async_api_call)
83+
84+
85+
@pytest.fixture(scope="function", name="actual_async_synonym_set")
86+
def actual_async_synonym_set_fixture(actual_async_api_call: AsyncApiCall) -> AsyncSynonymSet:
87+
"""Return a AsyncSynonymSet object using a real API."""
88+
return AsyncSynonymSet(actual_async_api_call, "test-set")
89+
90+
91+
@pytest.fixture(scope="function", name="fake_async_synonym_sets")
92+
def fake_async_synonym_sets_fixture(
93+
fake_async_api_call: AsyncApiCall,
94+
) -> AsyncSynonymSets:
95+
"""Return a AsyncSynonymSets object with test values."""
96+
return AsyncSynonymSets(fake_async_api_call)
97+
98+
99+
@pytest.fixture(scope="function", name="fake_async_synonym_set")
100+
def fake_async_synonym_set_fixture(fake_async_api_call: AsyncApiCall) -> AsyncSynonymSet:
101+
"""Return a AsyncSynonymSet object with test values."""
102+
return AsyncSynonymSet(fake_async_api_call, "test-set")

tests/synonym_set_items_test.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66

77
from tests.utils.version import is_v30_or_above
8+
from typesense.async_synonym_sets import AsyncSynonymSets
89
from typesense.client import Client
910
from typesense.synonym_sets import SynonymSets
1011
from typesense.types.synonym_set import (
@@ -83,3 +84,68 @@ def test_actual_delete_item(
8384
response = actual_synonym_sets["test-set"].delete_item("company_synonym")
8485

8586
assert response == {"id": "company_synonym"}
87+
88+
89+
async def test_actual_list_items_async(
90+
actual_async_synonym_sets: AsyncSynonymSets,
91+
delete_all_synonym_sets: None,
92+
create_synonym_set: None,
93+
) -> None:
94+
"""Test that the AsyncSynonymSet object can list items from Typesense Server."""
95+
response = await actual_async_synonym_sets["test-set"].list_items()
96+
97+
assert response == [
98+
{
99+
"id": "company_synonym",
100+
"root": "",
101+
"synonyms": ["companies", "corporations", "firms"],
102+
},
103+
]
104+
105+
106+
async def test_actual_get_item_async(
107+
actual_async_synonym_sets: AsyncSynonymSets,
108+
delete_all_synonym_sets: None,
109+
create_synonym_set: None,
110+
) -> None:
111+
"""Test that the AsyncSynonymSet object can get a specific item from Typesense Server."""
112+
response = await actual_async_synonym_sets["test-set"].get_item("company_synonym")
113+
114+
assert response == {
115+
"id": "company_synonym",
116+
"root": "",
117+
"synonyms": ["companies", "corporations", "firms"],
118+
}
119+
120+
121+
async def test_actual_upsert_item_async(
122+
actual_async_synonym_sets: AsyncSynonymSets,
123+
delete_all_synonym_sets: None,
124+
create_synonym_set: None,
125+
) -> None:
126+
"""Test that the AsyncSynonymSet object can upsert an item in Typesense Server."""
127+
payload: SynonymItemSchema = {
128+
"id": "brand_synonym",
129+
"synonyms": ["brand", "brands", "label"],
130+
}
131+
response = await actual_async_synonym_sets["test-set"].upsert_item(
132+
"brand_synonym", payload
133+
)
134+
135+
assert response == {
136+
"id": "brand_synonym",
137+
"synonyms": ["brand", "brands", "label"],
138+
}
139+
140+
141+
async def test_actual_delete_item_async(
142+
actual_async_synonym_sets: AsyncSynonymSets,
143+
delete_all_synonym_sets: None,
144+
create_synonym_set: None,
145+
) -> None:
146+
"""Test that the AsyncSynonymSet object can delete an item from Typesense Server."""
147+
response = await actual_async_synonym_sets["test-set"].delete_item(
148+
"company_synonym"
149+
)
150+
151+
assert response == {"id": "company_synonym"}

tests/synonym_set_test.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from tests.utils.object_assertions import assert_match_object, assert_object_lists_match
88
from tests.utils.version import is_v30_or_above
99
from typesense.api_call import ApiCall
10+
from typesense.async_api_call import AsyncApiCall
11+
from typesense.async_synonym_sets import AsyncSynonymSets
1012
from typesense.client import Client
1113
from typesense.synonym_set import SynonymSet
1214
from typesense.synonym_sets import SynonymSets
@@ -69,3 +71,52 @@ def test_actual_delete(
6971
response = actual_synonym_sets["test-set"].delete()
7072

7173
assert response == {"name": "test-set"}
74+
75+
76+
def test_init_async(fake_async_api_call: AsyncApiCall) -> None:
77+
"""Test that the AsyncSynonymSet object is initialized correctly."""
78+
from typesense.async_synonym_set import AsyncSynonymSet
79+
80+
synset = AsyncSynonymSet(fake_async_api_call, "test-set")
81+
82+
assert synset.name == "test-set"
83+
assert_match_object(synset.api_call, fake_async_api_call)
84+
assert_object_lists_match(
85+
synset.api_call.node_manager.nodes,
86+
fake_async_api_call.node_manager.nodes,
87+
)
88+
assert_match_object(
89+
synset.api_call.config.nearest_node,
90+
fake_async_api_call.config.nearest_node,
91+
)
92+
assert synset._endpoint_path == "/synonym_sets/test-set" # noqa: WPS437
93+
94+
95+
async def test_actual_retrieve_async(
96+
actual_async_synonym_sets: AsyncSynonymSets,
97+
delete_all_synonym_sets: None,
98+
create_synonym_set: None,
99+
) -> None:
100+
"""Test that the AsyncSynonymSet object can retrieve a synonym set from Typesense Server."""
101+
response = await actual_async_synonym_sets["test-set"].retrieve()
102+
103+
assert response == {
104+
"name": "test-set",
105+
"items": [
106+
{
107+
"id": "company_synonym",
108+
"root": "",
109+
"synonyms": ["companies", "corporations", "firms"],
110+
}
111+
],
112+
}
113+
114+
115+
async def test_actual_delete_async(
116+
actual_async_synonym_sets: AsyncSynonymSets,
117+
create_synonym_set: None,
118+
) -> None:
119+
"""Test that the AsyncSynonymSet object can delete a synonym set from Typesense Server."""
120+
response = await actual_async_synonym_sets["test-set"].delete()
121+
122+
assert response == {"name": "test-set"}

0 commit comments

Comments
 (0)