Skip to content

Commit fc0f205

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 cff9373 commit fc0f205

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+
- retrieve: Retrieves the details of this specific override.
10+
- delete: Deletes this specific override.
11+
12+
Attributes:
13+
- _endpoint_path: The API endpoint path for this specific override.
14+
15+
The AsyncOverride class interacts with the Typesense API to manage operations on a
16+
specific override within a collection. It provides methods to retrieve and delete
17+
individual overrides.
18+
19+
For more information regarding Overrides, refer to the Curation [documentation]
20+
(https://typesense.org/docs/27.0/api/curation.html#curation).
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.logger import warn_deprecation
28+
from typesense.types.override import OverrideDeleteSchema, OverrideSchema
29+
30+
31+
class AsyncOverride:
32+
"""
33+
Class for managing individual overrides in a Typesense collection (async).
34+
35+
This class provides methods to interact with a specific override,
36+
including retrieving and deleting it.
37+
38+
Attributes:
39+
api_call (AsyncApiCall): The API call object for making requests.
40+
collection_name (str): The name of the collection.
41+
override_id (str): The ID of the override.
42+
"""
43+
44+
def __init__(
45+
self,
46+
api_call: AsyncApiCall,
47+
collection_name: str,
48+
override_id: str,
49+
) -> None:
50+
"""
51+
Initialize the AsyncOverride object.
52+
53+
Args:
54+
api_call (AsyncApiCall): The API call object for making requests.
55+
collection_name (str): The name of the collection.
56+
override_id (str): The ID of the override.
57+
"""
58+
self.api_call = api_call
59+
self.collection_name = collection_name
60+
self.override_id = override_id
61+
62+
async def retrieve(self) -> OverrideSchema:
63+
"""
64+
Retrieve this specific override.
65+
66+
Returns:
67+
OverrideSchema: The schema containing the override details.
68+
"""
69+
response: OverrideSchema = await self.api_call.get(
70+
self._endpoint_path,
71+
entity_type=OverrideSchema,
72+
as_json=True,
73+
)
74+
return response
75+
76+
async def delete(self) -> OverrideDeleteSchema:
77+
"""
78+
Delete this specific override.
79+
80+
Returns:
81+
OverrideDeleteSchema: The schema containing the deletion response.
82+
"""
83+
response: OverrideDeleteSchema = await self.api_call.delete(
84+
self._endpoint_path,
85+
entity_type=OverrideDeleteSchema,
86+
)
87+
return response
88+
89+
@property
90+
@warn_deprecation( # type: ignore[untyped-decorator]
91+
"The override API (collections/{collection}/overrides/{override_id}) is deprecated is removed on v30+. "
92+
"Use curation sets (curation_sets) instead.",
93+
flag_name="overrides_deprecation",
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+
def __init__(
65+
self,
66+
api_call: AsyncApiCall,
67+
collection_name: str,
68+
) -> None:
69+
"""
70+
Initialize the AsyncOverrides object.
71+
72+
Args:
73+
api_call (AsyncApiCall): The API call object for making requests.
74+
collection_name (str): The name of the collection.
75+
"""
76+
self.api_call = api_call
77+
self.collection_name = collection_name
78+
self.overrides: typing.Dict[str, AsyncOverride] = {}
79+
80+
def __getitem__(self, override_id: str) -> AsyncOverride:
81+
"""
82+
Get or create an AsyncOverride object for a given override_id.
83+
84+
Args:
85+
override_id (str): The ID of the override.
86+
87+
Returns:
88+
AsyncOverride: The AsyncOverride object for the given ID.
89+
"""
90+
if not self.overrides.get(override_id):
91+
self.overrides[override_id] = AsyncOverride(
92+
self.api_call,
93+
self.collection_name,
94+
override_id,
95+
)
96+
return self.overrides[override_id]
97+
98+
async def upsert(
99+
self, override_id: str, schema: OverrideCreateSchema
100+
) -> OverrideSchema:
101+
"""
102+
Create or update an override.
103+
104+
Args:
105+
id (str): The ID of the override.
106+
schema (OverrideCreateSchema): The schema for creating or updating the override.
107+
108+
Returns:
109+
OverrideSchema: The created or updated override.
110+
"""
111+
response: OverrideSchema = await self.api_call.put(
112+
endpoint=self._endpoint_path(override_id),
113+
entity_type=OverrideSchema,
114+
body=schema,
115+
)
116+
return response
117+
118+
async def retrieve(self) -> OverrideRetrieveSchema:
119+
"""
120+
Retrieve all overrides for the collection.
121+
122+
Returns:
123+
OverrideRetrieveSchema: The schema containing all overrides.
124+
"""
125+
response: OverrideRetrieveSchema = await self.api_call.get(
126+
self._endpoint_path(),
127+
entity_type=OverrideRetrieveSchema,
128+
as_json=True,
129+
)
130+
return response
131+
132+
@warn_deprecation( # type: ignore[untyped-decorator]
133+
"AsyncOverrides is deprecated on v30+. Use client.curation_sets instead.",
134+
flag_name="overrides_deprecation",
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)