Skip to content

Commit b00b793

Browse files
committed
feat(collection): add async support for collection operations
- add AsyncCollection class for async individual collection operations - add AsyncCollections class for async collection collection operations - add async tests for collection and collections functionality - add async fixtures for testing async collection operations - remove future annotations imports from test files
1 parent 9413e2d commit b00b793

5 files changed

Lines changed: 508 additions & 3 deletions

File tree

src/typesense/async_collection.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""
2+
This module provides async functionality for managing individual collections in the Typesense API.
3+
4+
It contains the AsyncCollection class, which allows for retrieving, updating, and deleting
5+
collections asynchronously.
6+
7+
Classes:
8+
AsyncCollection: Manages async operations on a single collection in the Typesense API.
9+
10+
Dependencies:
11+
- typesense.async_api_call: Provides the AsyncApiCall class for making async API requests.
12+
- typesense.types.collection: Provides CollectionSchema and CollectionUpdateSchema types.
13+
- typesense.types.document: Provides DocumentSchema type.
14+
15+
Note: This module uses conditional imports to support both Python 3.11+ and earlier versions.
16+
"""
17+
18+
import sys
19+
20+
from typing_extensions import deprecated
21+
22+
from typesense.types.collection import CollectionSchema, CollectionUpdateSchema
23+
24+
if sys.version_info >= (3, 11):
25+
import typing
26+
else:
27+
import typing_extensions as typing
28+
29+
from typesense.async_api_call import AsyncApiCall
30+
from typesense.async_overrides import AsyncOverrides
31+
from typesense.async_synonyms import AsyncSynonyms
32+
from typesense.types.document import DocumentSchema
33+
34+
TDoc = typing.TypeVar("TDoc", bound=DocumentSchema, covariant=True)
35+
36+
37+
class AsyncCollection(typing.Generic[TDoc]):
38+
"""
39+
Manages async operations on a single collection in the Typesense API.
40+
41+
This class provides async methods to retrieve, update, and delete a collection.
42+
It is generic over the document type TDoc, which should be a subtype of DocumentSchema.
43+
44+
Attributes:
45+
name (str): The name of the collection.
46+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
47+
"""
48+
49+
def __init__(self, api_call: AsyncApiCall, name: str):
50+
"""
51+
Initialize the AsyncCollection instance.
52+
53+
Args:
54+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
55+
name (str): The name of the collection.
56+
"""
57+
self.name = name
58+
self.api_call = api_call
59+
# Import here to avoid circular imports
60+
from typesense.async_overrides import AsyncOverrides
61+
from typesense.async_synonyms import AsyncSynonyms
62+
63+
self._overrides = AsyncOverrides(api_call, name)
64+
self._synonyms = AsyncSynonyms(api_call, name)
65+
66+
async def retrieve(self) -> CollectionSchema:
67+
"""
68+
Retrieve the schema of this collection from Typesense.
69+
70+
Returns:
71+
CollectionSchema: The schema of the collection.
72+
"""
73+
response: CollectionSchema = await self.api_call.get(
74+
endpoint=self._endpoint_path,
75+
entity_type=CollectionSchema,
76+
as_json=True,
77+
)
78+
return response
79+
80+
async def update(
81+
self, schema_change: CollectionUpdateSchema
82+
) -> CollectionUpdateSchema:
83+
"""
84+
Update the schema of this collection in Typesense.
85+
86+
Args:
87+
schema_change (CollectionUpdateSchema):
88+
The changes to apply to the collection schema.
89+
90+
Returns:
91+
CollectionUpdateSchema: The updated schema of the collection.
92+
"""
93+
response: CollectionUpdateSchema = await self.api_call.patch(
94+
endpoint=self._endpoint_path,
95+
body=schema_change,
96+
entity_type=CollectionUpdateSchema,
97+
)
98+
return response
99+
100+
async def delete(
101+
self,
102+
delete_parameters: typing.Union[
103+
typing.Dict[str, typing.Union[str, bool]],
104+
None,
105+
] = None,
106+
) -> CollectionSchema:
107+
"""
108+
Delete this collection from Typesense.
109+
110+
Args:
111+
delete_parameters (Union[Dict[str, Union[str, bool]], None], optional):
112+
Additional parameters for the delete operation. Defaults to None.
113+
114+
Returns:
115+
CollectionSchema: The schema of the deleted collection.
116+
"""
117+
response: CollectionSchema = await self.api_call.delete(
118+
self._endpoint_path,
119+
entity_type=CollectionSchema,
120+
params=delete_parameters,
121+
)
122+
return response
123+
124+
@property
125+
@deprecated(
126+
"Overrides is deprecated on v30+. Use client.curation_sets instead.",
127+
category=None,
128+
)
129+
def overrides(self) -> AsyncOverrides:
130+
"""Return the AsyncOverrides instance for this collection.
131+
132+
Returns:
133+
AsyncOverrides: The AsyncOverrides instance for this collection.
134+
"""
135+
return self._overrides
136+
137+
@property
138+
@deprecated(
139+
"Synonyms is deprecated on v30+. Use client.synonym_sets instead.",
140+
category=None,
141+
)
142+
def synonyms(self) -> AsyncSynonyms:
143+
"""Return the AsyncSynonyms instance for this collection.
144+
145+
Returns:
146+
AsyncSynonyms: The AsyncSynonyms instance for this collection.
147+
"""
148+
"""Return the AsyncSynonyms instance for this collection."""
149+
return self._synonyms
150+
151+
@property
152+
def _endpoint_path(self) -> str:
153+
"""
154+
Get the API endpoint path for this collection.
155+
156+
Returns:
157+
str: The full endpoint path for the collection.
158+
"""
159+
from typesense.async_collections import AsyncCollections
160+
161+
return "/".join([AsyncCollections.resource_path, self.name])

src/typesense/async_collections.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""
2+
This module provides async functionality for managing collections in the Typesense API.
3+
4+
It contains the AsyncCollections class, which allows for creating, retrieving, and
5+
accessing individual collections asynchronously.
6+
7+
Classes:
8+
AsyncCollections: Manages collections in the Typesense API (async).
9+
10+
Dependencies:
11+
- typesense.async_api_call: Provides the AsyncApiCall class for making async API requests.
12+
- typesense.async_collection: Provides the AsyncCollection class for individual collection operations.
13+
- typesense.types.collection: Provides CollectionCreateSchema and CollectionSchema types.
14+
- typesense.types.document: Provides DocumentSchema type.
15+
16+
Note: This module uses conditional imports to support both Python 3.11+ and earlier versions.
17+
"""
18+
19+
import sys
20+
21+
if sys.version_info >= (3, 11):
22+
import typing
23+
else:
24+
import typing_extensions as typing
25+
26+
from typesense.async_api_call import AsyncApiCall
27+
from typesense.async_collection import AsyncCollection
28+
from typesense.types.collection import CollectionCreateSchema, CollectionSchema
29+
from typesense.types.document import DocumentSchema
30+
31+
TDoc = typing.TypeVar("TDoc", bound=DocumentSchema, covariant=True)
32+
33+
34+
class AsyncCollections(typing.Generic[TDoc]):
35+
"""
36+
Manages collections in the Typesense API (async).
37+
38+
This class provides async methods to create, retrieve, and access individual collections.
39+
It is generic over the document type TDoc, which should be a subtype of DocumentSchema.
40+
41+
Attributes:
42+
resource_path (str): The API endpoint path for collections operations.
43+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
44+
collections (Dict[str, AsyncCollection[TDoc]]):
45+
A dictionary of AsyncCollection instances, keyed by collection name.
46+
"""
47+
48+
resource_path: typing.Final[str] = "/collections"
49+
50+
def __init__(self, api_call: AsyncApiCall):
51+
"""
52+
Initialize the AsyncCollections instance.
53+
54+
Args:
55+
api_call (AsyncApiCall): The AsyncApiCall instance for making async API requests.
56+
"""
57+
self.api_call = api_call
58+
self.collections: typing.Dict[str, AsyncCollection[TDoc]] = {}
59+
60+
async def __contains__(self, collection_name: str) -> bool:
61+
"""
62+
Check if a collection exists in Typesense.
63+
64+
This method tries to retrieve the specified collection to check for its existence,
65+
utilizing the AsyncCollection.retrieve() method but without caching non-existent collections.
66+
67+
Args:
68+
collection_name (str): The name of the collection to check.
69+
70+
Returns:
71+
bool: True if the collection exists, False otherwise.
72+
"""
73+
if collection_name in self.collections:
74+
try:
75+
await self.collections[collection_name].retrieve()
76+
return True
77+
except Exception:
78+
self.collections.pop(collection_name, None)
79+
return False
80+
81+
try:
82+
await AsyncCollection(self.api_call, collection_name).retrieve()
83+
return True
84+
except Exception:
85+
return False
86+
87+
def __getitem__(self, collection_name: str) -> AsyncCollection[TDoc]:
88+
"""
89+
Get or create an AsyncCollection instance for a given collection name.
90+
91+
This method allows accessing collections using dictionary-like syntax.
92+
If the AsyncCollection instance doesn't exist, it creates a new one.
93+
94+
Args:
95+
collection_name (str): The name of the collection to access.
96+
97+
Returns:
98+
AsyncCollection[TDoc]: The AsyncCollection instance for the specified collection name.
99+
100+
Example:
101+
>>> collections = AsyncCollections(async_api_call)
102+
>>> fruits_collection = collections["fruits"]
103+
"""
104+
if not self.collections.get(collection_name):
105+
self.collections[collection_name] = AsyncCollection(
106+
self.api_call,
107+
collection_name,
108+
)
109+
return self.collections[collection_name]
110+
111+
async def create(self, schema: CollectionCreateSchema) -> CollectionSchema:
112+
"""
113+
Create a new collection in Typesense.
114+
115+
Args:
116+
schema (CollectionCreateSchema):
117+
The schema defining the structure of the new collection.
118+
119+
Returns:
120+
CollectionSchema:
121+
The schema of the created collection, as returned by the API.
122+
123+
Example:
124+
>>> collections = AsyncCollections(async_api_call)
125+
>>> schema = {
126+
... "name": "companies",
127+
... "fields": [
128+
... {"name": "company_name", "type": "string"},
129+
... {"name": "num_employees", "type": "int32"},
130+
... {"name": "country", "type": "string", "facet": True},
131+
... ],
132+
... "default_sorting_field": "num_employees",
133+
... }
134+
>>> created_schema = await collections.create(schema)
135+
"""
136+
call: CollectionSchema = await self.api_call.post(
137+
endpoint=AsyncCollections.resource_path,
138+
entity_type=CollectionSchema,
139+
as_json=True,
140+
body=schema,
141+
)
142+
return call
143+
144+
async def retrieve(self) -> typing.List[CollectionSchema]:
145+
"""
146+
Retrieve all collections from Typesense.
147+
148+
Returns:
149+
List[CollectionSchema]:
150+
A list of schemas for all collections in the Typesense instance.
151+
152+
Example:
153+
>>> collections = AsyncCollections(async_api_call)
154+
>>> all_collections = await collections.retrieve()
155+
>>> for collection in all_collections:
156+
... print(collection["name"])
157+
"""
158+
call: typing.List[CollectionSchema] = await self.api_call.get(
159+
endpoint=AsyncCollections.resource_path,
160+
as_json=True,
161+
entity_type=typing.List[CollectionSchema],
162+
)
163+
return call

tests/collection_test.py

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

3-
from __future__ import annotations
4-
53
from tests.utils.object_assertions import (
64
assert_match_object,
75
assert_object_lists_match,

0 commit comments

Comments
 (0)