Skip to content

Commit 0743752

Browse files
committed
feat(stemming): add async support for stemming operations
- add AsyncStemming class for async stemming dictionary operations - add AsyncStemmingDictionaries class for async dictionaries collection operations - add AsyncStemmingDictionary class for async individual dictionary operations - add async tests for stemming functionality - add async fixtures for testing async stemming operations
1 parent f7cd884 commit 0743752

File tree

5 files changed

+367
-0
lines changed

5 files changed

+367
-0
lines changed

src/typesense/async_stemming.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
Module for managing stemming dictionaries in Typesense (async).
3+
4+
This module provides a class for managing stemming dictionaries in Typesense,
5+
including creating, updating, and retrieving them asynchronously.
6+
7+
Classes:
8+
- AsyncStemming: Handles async operations related to stemming dictionaries.
9+
10+
Attributes:
11+
- AsyncStemmingDictionaries: The AsyncStemmingDictionaries object for managing stemming dictionaries.
12+
13+
Methods:
14+
- __init__: Initializes the AsyncStemming object.
15+
16+
The AsyncStemming class interacts with the Typesense API to manage stemming dictionary operations.
17+
It provides access to the AsyncStemmingDictionaries object for managing stemming dictionaries.
18+
19+
For more information on stemming dictionaries, refer to the Stemming
20+
[documentation](https://typesense.org/docs/28.0/api/stemming.html)
21+
22+
This module uses type hinting and is compatible with Python 3.11+ as well as earlier
23+
versions through the use of the typing_extensions library.
24+
"""
25+
26+
from typesense.async_api_call import AsyncApiCall
27+
from typesense.async_stemming_dictionaries import AsyncStemmingDictionaries
28+
29+
30+
class AsyncStemming(object):
31+
"""
32+
Class for managing stemming dictionaries in Typesense (async).
33+
34+
This class provides methods to interact with stemming dictionaries, including
35+
creating, updating, and retrieving them.
36+
37+
Attributes:
38+
dictionaries (AsyncStemmingDictionaries): The AsyncStemmingDictionaries object for managing
39+
stemming dictionaries.
40+
"""
41+
42+
def __init__(self, api_call: AsyncApiCall):
43+
"""
44+
Initialize the AsyncStemming object.
45+
46+
Args:
47+
api_call (AsyncApiCall): The API call object for making requests.
48+
"""
49+
self.api_call = api_call
50+
self.dictionaries = AsyncStemmingDictionaries(api_call)
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""
2+
Module for interacting with the stemming dictionaries endpoint of the Typesense API (async).
3+
4+
This module provides a class for managing stemming dictionaries in Typesense, including creating
5+
and updating them asynchronously.
6+
7+
Classes:
8+
- AsyncStemmingDictionaries: Handles async operations related to stemming dictionaries.
9+
10+
Methods:
11+
- __init__: Initializes the AsyncStemmingDictionaries object.
12+
- __getitem__: Retrieves or creates an AsyncStemmingDictionary object for a given dictionary_id.
13+
- upsert: Creates or updates a stemming dictionary.
14+
- _upsert_list: Creates or updates a list of stemming dictionaries.
15+
- _dump_to_jsonl: Dumps a list of StemmingDictionaryCreateSchema objects to a JSONL string.
16+
- _parse_response: Parses the response from the upsert operation.
17+
- _upsert_raw: Performs the raw upsert operation.
18+
- _endpoint_path: Constructs the API endpoint path for this specific stemming dictionary.
19+
20+
The AsyncStemmingDictionaries class interacts with the Typesense API to manage stemming dictionary
21+
operations. It provides methods to create, update, and retrieve stemming dictionaries, as well as
22+
access individual AsyncStemmingDictionary objects.
23+
24+
For more information on stemming dictionaries,
25+
refer to the Stemming [documentation](https://typesense.org/docs/28.0/api/stemming.html)
26+
"""
27+
28+
import json
29+
import sys
30+
31+
if sys.version_info >= (3, 11):
32+
import typing
33+
else:
34+
import typing_extensions as typing
35+
36+
from typesense.async_api_call import AsyncApiCall
37+
from typesense.async_stemming_dictionary import AsyncStemmingDictionary
38+
from typesense.types.stemming import (
39+
StemmingDictionariesRetrieveSchema,
40+
StemmingDictionaryCreateSchema,
41+
)
42+
43+
44+
class AsyncStemmingDictionaries:
45+
"""
46+
Class for managing stemming dictionaries in Typesense (async).
47+
48+
This class provides methods to interact with stemming dictionaries, including
49+
creating, updating, and retrieving them.
50+
51+
Attributes:
52+
api_call (AsyncApiCall): The API call object for making requests.
53+
stemming_dictionaries (Dict[str, AsyncStemmingDictionary]): A dictionary of
54+
AsyncStemmingDictionary objects.
55+
"""
56+
57+
resource_path: typing.Final[str] = "/stemming/dictionaries"
58+
59+
def __init__(self, api_call: AsyncApiCall):
60+
"""
61+
Initialize the AsyncStemmingDictionaries object.
62+
63+
Args:
64+
api_call (AsyncApiCall): The API call object for making requests.
65+
"""
66+
self.api_call = api_call
67+
self.stemming_dictionaries: typing.Dict[str, AsyncStemmingDictionary] = {}
68+
69+
def __getitem__(self, dictionary_id: str) -> AsyncStemmingDictionary:
70+
"""
71+
Get or create an AsyncStemmingDictionary object for a given dictionary_id.
72+
73+
Args:
74+
dictionary_id (str): The ID of the stemming dictionary.
75+
76+
Returns:
77+
AsyncStemmingDictionary: The AsyncStemmingDictionary object for the given ID.
78+
"""
79+
if not self.stemming_dictionaries.get(dictionary_id):
80+
self.stemming_dictionaries[dictionary_id] = AsyncStemmingDictionary(
81+
self.api_call,
82+
dictionary_id,
83+
)
84+
return self.stemming_dictionaries[dictionary_id]
85+
86+
async def retrieve(self) -> StemmingDictionariesRetrieveSchema:
87+
"""
88+
Retrieve the list of stemming dictionaries.
89+
90+
Returns:
91+
StemmingDictionariesRetrieveSchema: The list of stemming dictionaries.
92+
"""
93+
response: StemmingDictionariesRetrieveSchema = await self.api_call.get(
94+
self._endpoint_path(),
95+
entity_type=StemmingDictionariesRetrieveSchema,
96+
)
97+
return response
98+
99+
@typing.overload
100+
async def upsert(
101+
self,
102+
dictionary_id: str,
103+
word_root_combinations: typing.Union[str, bytes],
104+
) -> str: ...
105+
106+
@typing.overload
107+
async def upsert(
108+
self,
109+
dictionary_id: str,
110+
word_root_combinations: typing.List[StemmingDictionaryCreateSchema],
111+
) -> typing.List[StemmingDictionaryCreateSchema]: ...
112+
113+
async def upsert(
114+
self,
115+
dictionary_id: str,
116+
word_root_combinations: typing.Union[
117+
typing.List[StemmingDictionaryCreateSchema],
118+
str,
119+
bytes,
120+
],
121+
) -> typing.Union[str, typing.List[StemmingDictionaryCreateSchema]]:
122+
if isinstance(word_root_combinations, (str, bytes)):
123+
return await self._upsert_raw(dictionary_id, word_root_combinations)
124+
125+
return await self._upsert_list(dictionary_id, word_root_combinations)
126+
127+
async def _upsert_list(
128+
self,
129+
dictionary_id: str,
130+
word_root_combinations: typing.List[StemmingDictionaryCreateSchema],
131+
) -> typing.List[StemmingDictionaryCreateSchema]:
132+
word_combos_in_jsonl = self._dump_to_jsonl(word_root_combinations)
133+
response = await self._upsert_raw(dictionary_id, word_combos_in_jsonl)
134+
return self._parse_response(response)
135+
136+
def _dump_to_jsonl(
137+
self,
138+
word_root_combinations: typing.List[StemmingDictionaryCreateSchema],
139+
) -> str:
140+
word_root_strs = [json.dumps(combo) for combo in word_root_combinations]
141+
142+
return "\n".join(word_root_strs)
143+
144+
def _parse_response(
145+
self,
146+
response: str,
147+
) -> typing.List[StemmingDictionaryCreateSchema]:
148+
object_list: typing.List[StemmingDictionaryCreateSchema] = []
149+
150+
for line in response.split("\n"):
151+
try:
152+
decoded = json.loads(line)
153+
except json.JSONDecodeError as err:
154+
raise ValueError(f"Failed to parse JSON from response: {line}") from err
155+
object_list.append(decoded)
156+
return object_list
157+
158+
async def _upsert_raw(
159+
self,
160+
dictionary_id: str,
161+
word_root_combinations: typing.Union[bytes, str],
162+
) -> str:
163+
response: str = await self.api_call.post(
164+
self._endpoint_path("import"),
165+
body=word_root_combinations,
166+
as_json=False,
167+
entity_type=str,
168+
params={"id": dictionary_id},
169+
)
170+
return response
171+
172+
def _endpoint_path(self, action: typing.Union[str, None] = None) -> str:
173+
"""
174+
Construct the API endpoint path for this specific stemming dictionary.
175+
176+
Args:
177+
action (str, optional): The action to perform on the stemming dictionary.
178+
Defaults to None.
179+
180+
Returns:
181+
str: The constructed endpoint path.
182+
"""
183+
if action:
184+
return f"{AsyncStemmingDictionaries.resource_path}/{action}"
185+
return AsyncStemmingDictionaries.resource_path
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
Module for managing individual stemming dictionaries in Typesense (async).
3+
4+
This module provides a class for managing individual stemming dictionaries in Typesense,
5+
including retrieving them asynchronously.
6+
7+
Classes:
8+
- AsyncStemmingDictionary: Handles async operations related to individual stemming dictionaries.
9+
10+
Methods:
11+
- __init__: Initializes the AsyncStemmingDictionary object.
12+
- retrieve: Retrieves this specific stemming dictionary.
13+
14+
The AsyncStemmingDictionary class interacts with the Typesense API to manage operations on a
15+
specific stemming dictionary. It provides methods to retrieve the dictionary details.
16+
17+
For more information on stemming dictionaries, refer to the Stemming
18+
[documentation](https://typesense.org/docs/28.0/api/stemming.html)
19+
20+
This module uses type hinting and is compatible with Python 3.11+ as well as earlier
21+
versions through the use of the typing_extensions library.
22+
"""
23+
24+
from typesense.async_api_call import AsyncApiCall
25+
from typesense.types.stemming import StemmingDictionarySchema
26+
27+
28+
class AsyncStemmingDictionary:
29+
"""
30+
Class for managing individual stemming dictionaries in Typesense (async).
31+
32+
This class provides methods to interact with a specific stemming dictionary,
33+
including retrieving it.
34+
35+
Attributes:
36+
api_call (AsyncApiCall): The API call object for making requests.
37+
dict_id (str): The ID of the stemming dictionary.
38+
"""
39+
40+
def __init__(self, api_call: AsyncApiCall, dict_id: str):
41+
"""
42+
Initialize the AsyncStemmingDictionary object.
43+
44+
Args:
45+
api_call (AsyncApiCall): The API call object for making requests.
46+
dict_id (str): The ID of the stemming dictionary.
47+
"""
48+
self.api_call = api_call
49+
self.dict_id = dict_id
50+
51+
async def retrieve(self) -> StemmingDictionarySchema:
52+
"""
53+
Retrieve this specific stemming dictionary.
54+
55+
Returns:
56+
StemmingDictionarySchema: The schema containing the stemming dictionary details.
57+
"""
58+
response: StemmingDictionarySchema = await self.api_call.get(
59+
self._endpoint_path,
60+
entity_type=StemmingDictionarySchema,
61+
as_json=True,
62+
)
63+
return response
64+
65+
@property
66+
def _endpoint_path(self) -> str:
67+
"""
68+
Construct the API endpoint path for this specific stemming dictionary.
69+
70+
Returns:
71+
str: The constructed endpoint path.
72+
"""
73+
from typesense.async_stemming_dictionaries import AsyncStemmingDictionaries
74+
75+
return "/".join([AsyncStemmingDictionaries.resource_path, self.dict_id])

tests/fixtures/stemming_fixtures.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import pytest
44

55
from typesense.api_call import ApiCall
6+
from typesense.async_api_call import AsyncApiCall
7+
from typesense.async_stemming import AsyncStemming
68
from typesense.stemming import Stemming
79

810

@@ -12,3 +14,19 @@ def actual_stemming_fixture(
1214
) -> Stemming:
1315
"""Return a Stemming object using a real API."""
1416
return Stemming(actual_api_call)
17+
18+
19+
@pytest.fixture(scope="function", name="actual_async_stemming")
20+
def actual_async_stemming_fixture(
21+
actual_async_api_call: AsyncApiCall,
22+
) -> AsyncStemming:
23+
"""Return a AsyncStemming object using a real API."""
24+
return AsyncStemming(actual_async_api_call)
25+
26+
27+
@pytest.fixture(scope="function", name="fake_async_stemming")
28+
def fake_async_stemming_fixture(
29+
fake_async_api_call: AsyncApiCall,
30+
) -> AsyncStemming:
31+
"""Return a AsyncStemming object with test values."""
32+
return AsyncStemming(fake_async_api_call)

tests/stemming_test.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Tests for stemming."""
22

3+
from typesense.async_api_call import AsyncApiCall
4+
from typesense.async_stemming import AsyncStemming
35
from typesense.stemming import Stemming
46

57

@@ -38,3 +40,40 @@ def test_actual_retrieve(
3840
{"word": "fishing", "root": "fish"},
3941
],
4042
}
43+
44+
45+
async def test_actual_upsert_async(
46+
actual_async_stemming: AsyncStemming,
47+
) -> None:
48+
"""Test that it can upsert a stemming dictionary to Typesense Server."""
49+
response = await actual_async_stemming.dictionaries.upsert(
50+
"set_1",
51+
[{"word": "running", "root": "run"}, {"word": "fishing", "root": "fish"}],
52+
)
53+
54+
assert response == [
55+
{"word": "running", "root": "run"},
56+
{"word": "fishing", "root": "fish"},
57+
]
58+
59+
60+
async def test_actual_retrieve_many_async(
61+
actual_async_stemming: AsyncStemming,
62+
) -> None:
63+
"""Test that it can retrieve all stemming dictionaries from Typesense Server."""
64+
response = await actual_async_stemming.dictionaries.retrieve()
65+
assert response == {"dictionaries": ["set_1"]}
66+
67+
68+
async def test_actual_retrieve_async(
69+
actual_async_stemming: AsyncStemming,
70+
) -> None:
71+
"""Test that it can retrieve a single stemming dictionary from Typesense Server."""
72+
response = await actual_async_stemming.dictionaries["set_1"].retrieve()
73+
assert response == {
74+
"id": "set_1",
75+
"words": [
76+
{"word": "running", "root": "run"},
77+
{"word": "fishing", "root": "fish"},
78+
],
79+
}

0 commit comments

Comments
 (0)