Skip to content

Commit 4595b9e

Browse files
committed
feat(overrides): add async support for override operations
- add AsyncOverride class for async individual override operations - add AsyncOverrides class for async overrides collection operations - add async tests for override and overrides functionality - add async fixtures for testing async override operations
1 parent 22e5727 commit 4595b9e

File tree

5 files changed

+482
-0
lines changed

5 files changed

+482
-0
lines changed

src/typesense/async_override.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
This module provides async functionality for managing individual overrides in Typesense.
3+
4+
Classes:
5+
- AsyncOverride: Handles async operations related to a specific override within a collection.
6+
7+
Methods:
8+
- __init__: Initializes the AsyncOverride object.
9+
- _endpoint_path: Constructs the API endpoint path for this specific override.
10+
- retrieve: Retrieves the details of this specific override.
11+
- delete: Deletes this specific override.
12+
13+
The AsyncOverride class interacts with the Typesense API to manage operations on a
14+
specific override within a collection. It provides methods to retrieve and delete
15+
individual overrides.
16+
17+
For more information regarding Overrides, refer to the Curation [documentation]
18+
(https://typesense.org/docs/27.0/api/curation.html#curation).
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 typing_extensions import deprecated
25+
26+
from typesense.async_api_call import AsyncApiCall
27+
from typesense.logger import warn_deprecation
28+
from typesense.types.override import OverrideDeleteSchema, OverrideSchema
29+
30+
31+
@deprecated("AsyncOverride is deprecated on v30+. Use client.curation_sets instead.")
32+
class AsyncOverride:
33+
"""
34+
Class for managing individual overrides in a Typesense collection (async).
35+
36+
This class provides methods to interact with a specific override,
37+
including retrieving and deleting it.
38+
39+
Attributes:
40+
api_call (AsyncApiCall): The API call object for making requests.
41+
collection_name (str): The name of the collection.
42+
override_id (str): The ID of the override.
43+
"""
44+
45+
@warn_deprecation( # type: ignore[untyped-decorator]
46+
"The override API (collections/{collection}/overrides/{override_id}) is deprecated is removed on v30+. "
47+
"Use curation sets (curation_sets) instead.",
48+
flag_name="overrides_deprecation",
49+
)
50+
def __init__(
51+
self,
52+
api_call: AsyncApiCall,
53+
collection_name: str,
54+
override_id: str,
55+
) -> None:
56+
"""
57+
Initialize the AsyncOverride object.
58+
59+
Args:
60+
api_call (AsyncApiCall): The API call object for making requests.
61+
collection_name (str): The name of the collection.
62+
override_id (str): The ID of the override.
63+
"""
64+
self.api_call = api_call
65+
self.collection_name = collection_name
66+
self.override_id = override_id
67+
68+
async def retrieve(self) -> OverrideSchema:
69+
"""
70+
Retrieve this specific override.
71+
72+
Returns:
73+
OverrideSchema: The schema containing the override details.
74+
"""
75+
response: OverrideSchema = await self.api_call.get(
76+
self._endpoint_path(),
77+
entity_type=OverrideSchema,
78+
as_json=True,
79+
)
80+
return response
81+
82+
async def delete(self) -> OverrideDeleteSchema:
83+
"""
84+
Delete this specific override.
85+
86+
Returns:
87+
OverrideDeleteSchema: The schema containing the deletion response.
88+
"""
89+
response: OverrideDeleteSchema = await self.api_call.delete(
90+
self._endpoint_path(),
91+
entity_type=OverrideDeleteSchema,
92+
)
93+
return response
94+
95+
def _endpoint_path(self) -> str:
96+
"""
97+
Construct the API endpoint path for this specific override.
98+
99+
Returns:
100+
str: The constructed endpoint path.
101+
"""
102+
from typesense.async_collections import AsyncCollections
103+
from typesense.async_overrides import AsyncOverrides
104+
105+
return "/".join(
106+
[
107+
AsyncCollections.resource_path,
108+
self.collection_name,
109+
AsyncOverrides.resource_path,
110+
self.override_id,
111+
],
112+
)

src/typesense/async_overrides.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""
2+
This module provides async functionality for managing overrides in Typesense.
3+
4+
Classes:
5+
- AsyncOverrides: Handles async operations related to overrides within a collection.
6+
7+
Methods:
8+
- __init__: Initializes the AsyncOverrides object.
9+
- __getitem__: Retrieves or creates an AsyncOverride object for a given override_id.
10+
- _endpoint_path: Constructs the API endpoint path for override operations.
11+
- upsert: Creates or updates an override.
12+
- retrieve: Retrieves all overrides for the collection.
13+
14+
Attributes:
15+
- RESOURCE_PATH: The API resource path for overrides.
16+
17+
The AsyncOverrides class interacts with the Typesense API to manage override operations
18+
within a specific collection. It provides methods to create, update, and retrieve
19+
overrides, as well as access individual AsyncOverride objects.
20+
21+
For more information regarding Overrides, refer to the Curation [documentation]
22+
(https://typesense.org/docs/27.0/api/curation.html#curation).
23+
24+
This module uses type hinting and is compatible with Python 3.11+ as well as earlier
25+
versions through the use of the typing_extensions library.
26+
"""
27+
28+
import sys
29+
30+
from typing_extensions import deprecated
31+
32+
from typesense.async_api_call import AsyncApiCall
33+
from typesense.async_override import AsyncOverride
34+
from typesense.logger import warn_deprecation
35+
from typesense.types.override import (
36+
OverrideCreateSchema,
37+
OverrideRetrieveSchema,
38+
OverrideSchema,
39+
)
40+
41+
if sys.version_info >= (3, 11):
42+
import typing
43+
else:
44+
import typing_extensions as typing
45+
46+
47+
@deprecated("AsyncOverrides is deprecated on v30+. Use client.curation_sets instead.")
48+
class AsyncOverrides:
49+
"""
50+
Class for managing overrides in a Typesense collection (async).
51+
52+
This class provides methods to interact with overrides, including
53+
retrieving, creating, and updating them.
54+
55+
Attributes:
56+
RESOURCE_PATH (str): The API resource path for overrides.
57+
api_call (AsyncApiCall): The API call object for making requests.
58+
collection_name (str): The name of the collection.
59+
overrides (Dict[str, AsyncOverride]): A dictionary of AsyncOverride objects.
60+
"""
61+
62+
resource_path: typing.Final[str] = "overrides"
63+
64+
@warn_deprecation( # type: ignore[untyped-decorator]
65+
"AsyncOverrides is deprecated on v30+. Use client.curation_sets instead.",
66+
flag_name="overrides_deprecation",
67+
)
68+
def __init__(
69+
self,
70+
api_call: AsyncApiCall,
71+
collection_name: str,
72+
) -> None:
73+
"""
74+
Initialize the AsyncOverrides object.
75+
76+
Args:
77+
api_call (AsyncApiCall): The API call object for making requests.
78+
collection_name (str): The name of the collection.
79+
"""
80+
self.api_call = api_call
81+
self.collection_name = collection_name
82+
self.overrides: typing.Dict[str, AsyncOverride] = {}
83+
84+
def __getitem__(self, override_id: str) -> AsyncOverride:
85+
"""
86+
Get or create an AsyncOverride object for a given override_id.
87+
88+
Args:
89+
override_id (str): The ID of the override.
90+
91+
Returns:
92+
AsyncOverride: The AsyncOverride object for the given ID.
93+
"""
94+
if not self.overrides.get(override_id):
95+
self.overrides[override_id] = AsyncOverride(
96+
self.api_call,
97+
self.collection_name,
98+
override_id,
99+
)
100+
return self.overrides[override_id]
101+
102+
async def upsert(
103+
self, override_id: str, schema: OverrideCreateSchema
104+
) -> OverrideSchema:
105+
"""
106+
Create or update an override.
107+
108+
Args:
109+
id (str): The ID of the override.
110+
schema (OverrideCreateSchema): The schema for creating or updating the override.
111+
112+
Returns:
113+
OverrideSchema: The created or updated override.
114+
"""
115+
response: OverrideSchema = await self.api_call.put(
116+
endpoint=self._endpoint_path(override_id),
117+
entity_type=OverrideSchema,
118+
body=schema,
119+
)
120+
return response
121+
122+
async def retrieve(self) -> OverrideRetrieveSchema:
123+
"""
124+
Retrieve all overrides for the collection.
125+
126+
Returns:
127+
OverrideRetrieveSchema: The schema containing all overrides.
128+
"""
129+
response: OverrideRetrieveSchema = await self.api_call.get(
130+
self._endpoint_path(),
131+
entity_type=OverrideRetrieveSchema,
132+
as_json=True,
133+
)
134+
return response
135+
136+
def _endpoint_path(self, override_id: typing.Union[str, None] = None) -> str:
137+
"""
138+
Construct the API endpoint path for override operations.
139+
140+
Args:
141+
override_id (Union[str, None], optional): The ID of the override. Defaults to None.
142+
143+
Returns:
144+
str: The constructed endpoint path.
145+
"""
146+
from typesense.async_collections import AsyncCollections
147+
148+
override_id = override_id or ""
149+
150+
return "/".join(
151+
[
152+
AsyncCollections.resource_path,
153+
self.collection_name,
154+
AsyncOverrides.resource_path,
155+
override_id,
156+
],
157+
)

tests/fixtures/override_fixtures.py

Lines changed: 25 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_override import AsyncOverride
9+
from typesense.async_overrides import AsyncOverrides
710
from typesense.override import Override
811
from typesense.overrides import Overrides
912

@@ -38,3 +41,25 @@ def fake_overrides_fixture(fake_api_call: ApiCall) -> Overrides:
3841
def fake_override_fixture(fake_api_call: ApiCall) -> Override:
3942
"""Return a Override object with test values."""
4043
return Override(fake_api_call, "companies", "company_override")
44+
45+
46+
@pytest.fixture(scope="function", name="actual_async_overrides")
47+
def actual_async_overrides_fixture(
48+
actual_async_api_call: AsyncApiCall,
49+
) -> AsyncOverrides:
50+
"""Return a AsyncOverrides object using a real API."""
51+
return AsyncOverrides(actual_async_api_call, "companies")
52+
53+
54+
@pytest.fixture(scope="function", name="fake_async_overrides")
55+
def fake_async_overrides_fixture(
56+
fake_async_api_call: AsyncApiCall,
57+
) -> AsyncOverrides:
58+
"""Return a AsyncOverrides object with test values."""
59+
return AsyncOverrides(fake_async_api_call, "companies")
60+
61+
62+
@pytest.fixture(scope="function", name="fake_async_override")
63+
def fake_async_override_fixture(fake_async_api_call: AsyncApiCall) -> AsyncOverride:
64+
"""Return a AsyncOverride object with test values."""
65+
return AsyncOverride(fake_async_api_call, "companies", "company_override")

tests/override_test.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
assert_to_contain_object,
1111
)
1212
from typesense.api_call import ApiCall
13+
from typesense.async_api_call import AsyncApiCall
14+
from typesense.async_collections import AsyncCollections
15+
from typesense.async_override import AsyncOverride
1316
from typesense.collections import Collections
1417
from typesense.override import Override, OverrideDeleteSchema
1518
from typesense.types.override import OverrideSchema
@@ -85,3 +88,60 @@ def test_actual_delete(
8588
response = actual_collections["companies"].overrides["company_override"].delete()
8689

8790
assert response == {"id": "company_override"}
91+
92+
93+
def test_init_async(fake_async_api_call: AsyncApiCall) -> None:
94+
"""Test that the AsyncOverride object is initialized correctly."""
95+
override = AsyncOverride(fake_async_api_call, "companies", "company_override")
96+
97+
assert override.collection_name == "companies"
98+
assert override.override_id == "company_override"
99+
assert_match_object(override.api_call, fake_async_api_call)
100+
assert_object_lists_match(
101+
override.api_call.node_manager.nodes,
102+
fake_async_api_call.node_manager.nodes,
103+
)
104+
assert_match_object(
105+
override.api_call.config.nearest_node,
106+
fake_async_api_call.config.nearest_node,
107+
)
108+
assert (
109+
override._endpoint_path() # noqa: WPS437
110+
== "/collections/companies/overrides/company_override"
111+
)
112+
113+
114+
async def test_actual_retrieve_async(
115+
actual_async_collections: AsyncCollections,
116+
delete_all: None,
117+
create_override: None,
118+
) -> None:
119+
"""Test that the AsyncOverride object can retrieve an override from Typesense Server."""
120+
response = await actual_async_collections["companies"].overrides["company_override"].retrieve()
121+
122+
assert response["rule"] == {
123+
"match": "exact",
124+
"query": "companies",
125+
}
126+
assert response["filter_by"] == "num_employees>10"
127+
assert_to_contain_object(
128+
response,
129+
{
130+
"rule": {
131+
"match": "exact",
132+
"query": "companies",
133+
},
134+
"filter_by": "num_employees>10",
135+
},
136+
)
137+
138+
139+
async def test_actual_delete_async(
140+
actual_async_collections: AsyncCollections,
141+
delete_all: None,
142+
create_override: None,
143+
) -> None:
144+
"""Test that the AsyncOverride object can delete an override from Typesense Server."""
145+
response = await actual_async_collections["companies"].overrides["company_override"].delete()
146+
147+
assert response == {"id": "company_override"}

0 commit comments

Comments
 (0)