From dc013f4c814774bfa5ecf9454aa3564798570c3d Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 16:14:07 +0200 Subject: [PATCH 01/14] feat(client): add stemming dictionary support - add new `stemming` module with dictionary management functionality - add `StemmingDictionaries` class for crud operations on dictionaries - add corresponding types and tests for stemming operations - update `Client` class to expose stemming functionality --- setup.cfg | 5 +- src/typesense/client.py | 5 +- src/typesense/stemming.py | 50 +++++++ src/typesense/stemming_dictionaries.py | 187 +++++++++++++++++++++++++ src/typesense/stemming_dictionary.py | 75 ++++++++++ src/typesense/types/stemming.py | 45 ++++++ tests/fixtures/stemming_fixtures.py | 14 ++ tests/stemming_test.py | 40 ++++++ 8 files changed, 418 insertions(+), 3 deletions(-) create mode 100644 src/typesense/stemming.py create mode 100644 src/typesense/stemming_dictionaries.py create mode 100644 src/typesense/stemming_dictionary.py create mode 100644 src/typesense/types/stemming.py create mode 100644 tests/fixtures/stemming_fixtures.py create mode 100644 tests/stemming_test.py diff --git a/setup.cfg b/setup.cfg index ccbbe25..c440de4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,11 +20,12 @@ max-complexity = 6 # # Excluding some directories: exclude = .git,__pycache__,venv,.eggs,*.egg -ignore = Q000, WPS602, WPS432, WPS305, WPS221, WPS230, WPS234, WPS433, WPS440, W503, WPS331, WPS306, WPS237, WPS202, RST301, RST306, WPS214, WPS235, WPS226, WPS337, WPS320, F821, WPS201 +ignore = Q000, WPS602, WPS432, WPS305, WPS221, WPS230, WPS234, WPS433, WPS440, W503, WPS331, WPS306, WPS237, WPS202, RST301, RST306, WPS214, WPS235, WPS226, WPS337, WPS320, F821, WPS201, E704, D102 per-file-ignores = tests/*.py: S101, WPS226, WPS118, WPS202, WPS204, WPS218, WPS211, WPS604, WPS431, WPS210, WPS201, WPS437 src/typesense/types/*.py: B950, WPS215, WPS111, WPS462, WPS322, WPS428, WPS114, WPS110, WPS202 - src/typesense/documents.py: WPS320, E704, D102, WPS428, WPS220 + src/typesense/documents.py: WPS320, WPS428, WPS220 + src/typesense/stemming_dictionaries.py: WPS320, WPS428, WPS220 src/typesense/api_call.py: WPS110, WPS211 src/typesense/request_handler.py: WPS110, WPS211 diff --git a/src/typesense/client.py b/src/typesense/client.py index 557b45f..dc7d09b 100644 --- a/src/typesense/client.py +++ b/src/typesense/client.py @@ -45,6 +45,7 @@ from typesense.keys import Keys from typesense.multi_search import MultiSearch from typesense.operations import Operations +from typesense.stemming import Stemming from typesense.stopwords import Stopwords TDoc = typing.TypeVar("TDoc", bound=DocumentSchema) @@ -56,7 +57,7 @@ class Client: This class serves as the entry point for all Typesense operations. It initializes and provides access to various components of the Typesense SDK, such as collections, - multi-search, keys, aliases, analytics, operations, debug, stopwords, + multi-search, keys, aliases, analytics, stemming, operations, debug, stopwords, and conversation models. Attributes: @@ -67,6 +68,7 @@ class Client: keys (Keys): Instance for managing API keys. aliases (Aliases): Instance for managing collection aliases. analytics (Analytics): Instance for analytics operations. + stemming (Stemming): Instance for stemming dictionary operations. operations (Operations): Instance for various Typesense operations. debug (Debug): Instance for debug operations. stopwords (Stopwords): Instance for managing stopwords. @@ -96,6 +98,7 @@ def __init__(self, config_dict: ConfigDict) -> None: self.keys = Keys(self.api_call) self.aliases = Aliases(self.api_call) self.analytics = Analytics(self.api_call) + self.stemming = Stemming(self.api_call) self.operations = Operations(self.api_call) self.debug = Debug(self.api_call) self.stopwords = Stopwords(self.api_call) diff --git a/src/typesense/stemming.py b/src/typesense/stemming.py new file mode 100644 index 0000000..b6845da --- /dev/null +++ b/src/typesense/stemming.py @@ -0,0 +1,50 @@ +""" +Module for managing stemming dictionaries in Typesense. + +This module provides a class for managing stemming dictionaries in Typesense, +including creating, updating, and retrieving them. + +Classes: + - Stemming: Handles operations related to stemming dictionaries. + +Attributes: + - StemmingDictionaries: The StemmingDictionaries object for managing stemming dictionaries. + +Methods: + - __init__: Initializes the Stemming object. + +The Stemming class interacts with the Typesense API to manage stemming dictionary operations. +It provides access to the StemmingDictionaries object for managing stemming dictionaries. + +For more information on stemming dictionaries, refer to the Stemming +[documentation](https://typesense.org/docs/28.0/api/stemming.html) + +This module uses type hinting and is compatible with Python 3.11+ as well as earlier +versions through the use of the typing_extensions library. +""" + +from typesense.api_call import ApiCall +from typesense.stemming_dictionaries import StemmingDictionaries + + +class Stemming(object): + """ + Class for managing stemming dictionaries in Typesense. + + This class provides methods to interact with stemming dictionaries, including + creating, updating, and retrieving them. + + Attributes: + dictionaries (StemmingDictionaries): The StemmingDictionaries object for managing + stemming dictionaries. + """ + + def __init__(self, api_call: ApiCall): + """ + Initialize the Stemming object. + + Args: + api_call (ApiCall): The API call object for making requests. + """ + self.api_call = api_call + self.dictionaries = StemmingDictionaries(api_call) diff --git a/src/typesense/stemming_dictionaries.py b/src/typesense/stemming_dictionaries.py new file mode 100644 index 0000000..01471f1 --- /dev/null +++ b/src/typesense/stemming_dictionaries.py @@ -0,0 +1,187 @@ +""" +Module for interacting with the stemming dictionaries endpoint of the Typesense API. + +This module provides a class for managing stemming dictionaries in Typesense, including creating +and updating them. + +Classes: + - StemmingDictionaries: Handles operations related to stemming dictionaries. + +Methods: + - __init__: Initializes the StemmingDictionaries object. + - __getitem__: Retrieves or creates a StemmingDictionary object for a given dictionary_id. + - upsert: Creates or updates a stemming dictionary. + - _upsert_list: Creates or updates a list of stemming dictionaries. + - _dump_to_jsonl: Dumps a list of StemmingDictionaryCreateSchema objects to a JSONL string. + - _parse_response: Parses the response from the upsert operation. + - _upsert_raw: Performs the raw upsert operation. + - _endpoint_path: Constructs the API endpoint path for this specific stemming dictionary. + +The StemmingDictionaries class interacts with the Typesense API to manage stemming dictionary +operations. +It provides methods to create, update, and retrieve stemming dictionaries, as well as +access individual StemmingDictionary objects. + +For more information on stemming dictionaries, +refer to the Stemming [documentation](https://typesense.org/docs/28.0/api/stemming.html) +""" + +import sys + +if sys.version_info >= (3, 11): + import typing +else: + import typing_extensions as typing + +import json + +from typesense.api_call import ApiCall +from typesense.stemming_dictionary import StemmingDictionary +from typesense.types.stemming import ( + StemmingDictionariesRetrieveSchema, + StemmingDictionaryCreateSchema, +) + + +class StemmingDictionaries: + """ + Class for managing stemming dictionaries in Typesense. + + This class provides methods to interact with stemming dictionaries, including + creating, updating, and retrieving them. + + Attributes: + api_call (ApiCall): The API call object for making requests. + stemming_dictionaries (Dict[str, StemmingDictionary]): A dictionary of + StemmingDictionary objects. + """ + + resource_path: typing.Final[str] = "/stemming/dictionaries" + + def __init__(self, api_call: ApiCall): + """ + Initialize the StemmingDictionaries object. + + Args: + api_call (ApiCall): The API call object for making requests. + """ + self.api_call = api_call + self.stemming_dictionaries: typing.Dict[str, StemmingDictionary] = {} + + def __getitem__(self, dictionary_id: str) -> StemmingDictionary: + """ + Get or create an StemmingDictionary object for a given rule_id. + + Args: + rule_id (str): The ID of the analytics rule. + + Returns: + StemmingDictionary: The StemmingDictionary object for the given ID. + """ + if not self.stemming_dictionaries.get(dictionary_id): + self.stemming_dictionaries[dictionary_id] = StemmingDictionary( + self.api_call, + dictionary_id, + ) + return self.stemming_dictionaries[dictionary_id] + + def retrieve(self) -> StemmingDictionariesRetrieveSchema: + """ + Retrieve the list of stemming dictionaries. + + Returns: + StemmingDictionariesRetrieveSchema: The list of stemming dictionaries. + """ + response: StemmingDictionariesRetrieveSchema = self.api_call.get( + self._endpoint_path(), + entity_type=StemmingDictionariesRetrieveSchema, + ) + return response + + @typing.overload + def upsert( + self, + dictionary_id: str, + word_root_combinations: typing.Union[str, bytes], + ) -> str: ... + + @typing.overload + def upsert( + self, + dictionary_id: str, + word_root_combinations: typing.List[StemmingDictionaryCreateSchema], + ) -> typing.List[StemmingDictionaryCreateSchema]: ... + + def upsert( + self, + dictionary_id: str, + word_root_combinations: typing.Union[ + typing.List[StemmingDictionaryCreateSchema], + str, + bytes, + ], + ) -> typing.Union[str, typing.List[StemmingDictionaryCreateSchema]]: + if isinstance(word_root_combinations, (str, bytes)): + return self._upsert_raw(dictionary_id, word_root_combinations) + + return self._upsert_list(dictionary_id, word_root_combinations) + + def _upsert_list( + self, + dictionary_id: str, + word_root_combinations: typing.List[StemmingDictionaryCreateSchema], + ) -> typing.List[StemmingDictionaryCreateSchema]: + word_combos_in_jsonl = self._dump_to_jsonl(word_root_combinations) + response = self._upsert_raw(dictionary_id, word_combos_in_jsonl) + return self._parse_response(response) + + def _dump_to_jsonl( + self, + word_root_combinations: typing.List[StemmingDictionaryCreateSchema], + ) -> str: + word_root_strs = [json.dumps(combo) for combo in word_root_combinations] + + return "\n".join(word_root_strs) + + def _parse_response( + self, + response: str, + ) -> typing.List[StemmingDictionaryCreateSchema]: + object_list: typing.List[StemmingDictionaryCreateSchema] = [] + + for line in response.split("\n"): + try: + decoded = json.loads(line) + except json.JSONDecodeError: + raise ValueError(f"Failed to parse JSON from response: {line}") + object_list.append(decoded) + return object_list + + def _upsert_raw( + self, + dictionary_id: str, + word_root_combinations: typing.Union[bytes, str], + ) -> str: + response: str = self.api_call.post( + self._endpoint_path("import"), + body=word_root_combinations, + as_json=False, + entity_type=str, + params={"id": dictionary_id}, + ) + return response + + def _endpoint_path(self, action: typing.Union[str, None] = None) -> str: + """ + Construct the API endpoint path for this specific stemming dictionary. + + Args: + action (str, optional): The action to perform on the stemming dictionary. + Defaults to None. + + Returns: + str: The constructed endpoint path. + """ + if action: + return f"{StemmingDictionaries.resource_path}/{action}" + return StemmingDictionaries.resource_path diff --git a/src/typesense/stemming_dictionary.py b/src/typesense/stemming_dictionary.py new file mode 100644 index 0000000..f528fc1 --- /dev/null +++ b/src/typesense/stemming_dictionary.py @@ -0,0 +1,75 @@ +""" +Module for managing individual stemming dictionaries in Typesense. + +This module provides a class for managing individual stemming dictionaries in Typesense, +including retrieving them. + +Classes: + - StemmingDictionary: Handles operations related to individual stemming dictionaries. + +Methods: + - __init__: Initializes the StemmingDictionary object. + - retrieve: Retrieves this specific stemming dictionary. + +The StemmingDictionary class interacts with the Typesense API to manage operations on a +specific stemming dictionary. It provides methods to retrieve the dictionary details. + +For more information on stemming dictionaries, refer to the Stemming +[documentation](https://typesense.org/docs/28.0/api/stemming.html) + +This module uses type hinting and is compatible with Python 3.11+ as well as earlier +versions through the use of the typing_extensions library. +""" + +from typesense.api_call import ApiCall +from typesense.types.stemming import StemmingDictionarySchema + + +class StemmingDictionary: + """ + Class for managing individual stemming dictionaries in Typesense. + + This class provides methods to interact with a specific stemming dictionary, + including retrieving it. + + Attributes: + api_call (ApiCall): The API call object for making requests. + dict_id (str): The ID of the stemming dictionary. + """ + + def __init__(self, api_call: ApiCall, dict_id: str): + """ + Initialize the StemmingDictionary object. + + Args: + api_call (ApiCall): The API call object for making requests. + dict_id (str): The ID of the stemming dictionary. + """ + self.api_call = api_call + self.dict_id = dict_id + + def retrieve(self) -> StemmingDictionarySchema: + """ + Retrieve this specific stemming dictionary. + + Returns: + StemmingDictionarySchema: The schema containing the stemming dictionary details. + """ + response: StemmingDictionarySchema = self.api_call.get( + self._endpoint_path, + entity_type=StemmingDictionarySchema, + as_json=True, + ) + return response + + @property + def _endpoint_path(self) -> str: + """ + Construct the API endpoint path for this specific analytics rule. + + Returns: + str: The constructed endpoint path. + """ + from typesense.stemming_dictionaries import StemmingDictionaries + + return "/".join([StemmingDictionaries.resource_path, self.dict_id]) diff --git a/src/typesense/types/stemming.py b/src/typesense/types/stemming.py new file mode 100644 index 0000000..2cc3dd9 --- /dev/null +++ b/src/typesense/types/stemming.py @@ -0,0 +1,45 @@ +"""Stemming types for Typesense Python Client.""" + +import sys + +if sys.version_info >= (3, 11): + import typing +else: + import typing_extensions as typing + + +class StemmingDictionaryCreateSchema(typing.TypedDict): + """ + Schema for creating a [stemming dictionary](https://typesense.org/docs/28/api/stemming.html#creating-a-stemming-dictionary). + + Attributes: + name (str): The name of the stemming dictionary. + words (list[str]): The list of words in the stemming dictionary. + """ + + word: str + root: str + + +class StemmingDictionarySchema(typing.TypedDict): + """ + Schema for a stemming dictionary. + + Attributes: + id (str): The ID of the stemming dictionary. + words (list[StemmingDictionarySchema]): The list of words and their roots in the stemming dictionary. + """ + + id: str + words: typing.List[StemmingDictionaryCreateSchema] + + +class StemmingDictionariesRetrieveSchema(typing.TypedDict): + """ + Schema for retrieving stemming dictionaries. + + Attributes: + data (list[str]): The list of stemming dictionary names. + """ + + dictionaries: typing.List[str] diff --git a/tests/fixtures/stemming_fixtures.py b/tests/fixtures/stemming_fixtures.py new file mode 100644 index 0000000..be571ed --- /dev/null +++ b/tests/fixtures/stemming_fixtures.py @@ -0,0 +1,14 @@ +"""Fixtures for the Analytics Rules tests.""" + +import pytest + +from typesense.api_call import ApiCall +from typesense.stemming import Stemming + + +@pytest.fixture(scope="function", name="actual_stemming") +def actual_stemming_fixture( + actual_api_call: ApiCall, +) -> Stemming: + """Return a Stemming object using a real API.""" + return Stemming(actual_api_call) diff --git a/tests/stemming_test.py b/tests/stemming_test.py new file mode 100644 index 0000000..9c0a812 --- /dev/null +++ b/tests/stemming_test.py @@ -0,0 +1,40 @@ +"""Tests for stemming.""" + +from typesense.stemming import Stemming + + +def test_actual_upsert( + actual_stemming: Stemming, +) -> None: + """Test that it can upsert a stemming dictionary to Typesense Server.""" + response = actual_stemming.dictionaries.upsert( + "set_1", + [{"word": "running", "root": "run"}, {"word": "fishing", "root": "fish"}], + ) + + assert response == [ + {"word": "running", "root": "run"}, + {"word": "fishing", "root": "fish"}, + ] + + +def test_actual_retrieve_many( + actual_stemming: Stemming, +) -> None: + """Test that it can retrieve all stemming dictionaries from Typesense Server.""" + response = actual_stemming.dictionaries.retrieve() + assert response == {"dictionaries": ["set_1"]} + + +def test_actual_retrieve( + actual_stemming: Stemming, +) -> None: + """Test that it can retrieve a single stemming dictionary from Typesense Server.""" + response = actual_stemming.dictionaries["set_1"].retrieve() + assert response == { + "id": "set_1", + "words": [ + {"word": "running", "root": "run"}, + {"word": "fishing", "root": "fish"}, + ], + } From 89c19be3e2c1a71451ffe5b4ecd23db55c0b50d9 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 17:26:45 +0200 Subject: [PATCH 02/14] feat(types): add geopolygon type to collection field types --- src/typesense/types/collection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/typesense/types/collection.py b/src/typesense/types/collection.py index b2366dc..39ea2c1 100644 --- a/src/typesense/types/collection.py +++ b/src/typesense/types/collection.py @@ -17,6 +17,7 @@ "float", "bool", "geopoint", + "geopolygon", "geopoint[]", "string[]", "int32[]", From 738f5a4e6e4c40e26f23d00e7932e0789656a3bb Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 17:27:24 +0200 Subject: [PATCH 03/14] feat(collections): add collection truncation option --- src/typesense/types/document.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/typesense/types/document.py b/src/typesense/types/document.py index 2bebc85..701e75f 100644 --- a/src/typesense/types/document.py +++ b/src/typesense/types/document.py @@ -830,11 +830,13 @@ class DeleteQueryParameters(typing.TypedDict): Parameters for deleting documents. Attributes: + truncate (str): Truncate the collection, keeping just the schema. filter_by (str): Filter to apply to documents. batch_size (int): Batch size for deleting documents. ignore_not_found (bool): Ignore not found documents. """ + truncate: typing.NotRequired[bool] filter_by: str batch_size: typing.NotRequired[int] ignore_not_found: typing.NotRequired[bool] From edd41e796073eed82ec42fc3758551103701f08f Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 17:27:44 +0200 Subject: [PATCH 04/14] feat(multi-search): add union parameter to multisearch --- src/typesense/types/multi_search.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/typesense/types/multi_search.py b/src/typesense/types/multi_search.py index 392f129..3619c0b 100644 --- a/src/typesense/types/multi_search.py +++ b/src/typesense/types/multi_search.py @@ -29,4 +29,5 @@ class MultiSearchRequestSchema(typing.TypedDict): searches (list[MultiSearchParameters]): The search parameters. """ + union: typing.NotRequired[typing.Literal[True]] searches: typing.List[MultiSearchParameters] From 19bc04dd49f2a72db0ae2c2a036104f846276047 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 17:28:17 +0200 Subject: [PATCH 05/14] feat(operations): add the `schema_changes` operation option --- src/typesense/operations.py | 31 ++++++++++++++++++++++++++++--- src/typesense/types/operations.py | 15 +++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/typesense/operations.py b/src/typesense/operations.py index a2e7782..4332089 100644 --- a/src/typesense/operations.py +++ b/src/typesense/operations.py @@ -22,6 +22,7 @@ HealthCheckResponse, LogSlowRequestsTimeParams, OperationResponse, + SchemaChangesResponse, SnapshotParameters, ) @@ -48,8 +49,9 @@ class Operations: """ resource_path: typing.Final[str] = "/operations" - healht_path: typing.Final[str] = "/health" + health_path: typing.Final[str] = "/health" config_path: typing.Final[str] = "/config" + schema_changes: typing.Final[str] = "/schema_changes" def __init__(self, api_call: ApiCall): """ @@ -60,6 +62,23 @@ def __init__(self, api_call: ApiCall): """ self.api_call = api_call + @typing.overload + def perform( + self, + operation_name: typing.Literal["schema_changes"], + query_params: None = None, + ) -> typing.List[SchemaChangesResponse]: + """ + Perform a vote operation. + + Args: + operation_name (Literal["schema_changes"]): The name of the operation. + query_params (None, optional): Query parameters (not used for vote operation). + + Returns: + OperationResponse: The response from the vote operation. + """ + @typing.overload def perform( self, @@ -150,7 +169,13 @@ def perform( def perform( self, operation_name: typing.Union[ - typing.Literal["snapshot, vote, db/compact, cache/clear"], + typing.Literal[ + "snapshot", + "vote", + "db/compact", + "cache/clear", + "schema_changes", + ], str, ], query_params: typing.Union[ @@ -189,7 +214,7 @@ def is_healthy(self) -> bool: bool: True if the server is healthy, False otherwise. """ call_resp = self.api_call.get( - Operations.healht_path, + Operations.health_path, as_json=True, entity_type=HealthCheckResponse, ) diff --git a/src/typesense/types/operations.py b/src/typesense/types/operations.py index 566f517..e2a03a3 100644 --- a/src/typesense/types/operations.py +++ b/src/typesense/types/operations.py @@ -41,6 +41,21 @@ class HealthCheckResponse(typing.TypedDict): ok: bool +class SchemaChangesResponse(typing.TypedDict): + """ + Response schema for schema changes. + + Attributes: + collection (str): The name of the collection. + validated_docs (int): The number of validated documents. + altered_docs (int): The number of altered documents + """ + + collection: str + validated_docs: int + altered_docs: int + + class OperationResponse(typing.TypedDict): """ Response schema for operations. From 0325b7d86de06505659569b750efd874d524e37f Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 17:30:34 +0200 Subject: [PATCH 06/14] feat(field): add `tokens_separators` and `symbols_to_index` to field-level --- src/typesense/types/collection.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/typesense/types/collection.py b/src/typesense/types/collection.py index 39ea2c1..e929fa6 100644 --- a/src/typesense/types/collection.py +++ b/src/typesense/types/collection.py @@ -47,6 +47,8 @@ class CollectionFieldSchema(typing.Generic[_TType], typing.TypedDict, total=Fals optional (bool): Whether the field is optional. infix (bool): Whether the field is an infix. stem (bool): Whether the field is a stem. + symbols_to_index (list[str]): The symbols to index + token_separators (list[str]): The token separators. locale (Locales): The locale of the field. sort (bool): Whether the field is sortable. store (bool): Whether the field is stored. @@ -65,6 +67,8 @@ class CollectionFieldSchema(typing.Generic[_TType], typing.TypedDict, total=Fals locale: typing.NotRequired[Locales] sort: typing.NotRequired[bool] store: typing.NotRequired[bool] + symbols_to_index: typing.NotRequired[typing.List[str]] + token_separators: typing.NotRequired[typing.List[str]] num_dim: typing.NotRequired[float] range_index: typing.NotRequired[bool] index: typing.NotRequired[bool] @@ -84,6 +88,8 @@ class RegularCollectionFieldSchema(CollectionFieldSchema[_FieldType]): stem (bool): Whether the field is a stem. locale (Locales): The locale of the field. sort (bool): Whether the field is sortable. + symbols_to_index (list[str]): The symbols to index + token_separators (list[str]): The token separators. store (bool): Whether the field is stored. num_dim (float): The number of dimensions. range_index (bool): Whether the field is a range index. @@ -102,6 +108,8 @@ class ReferenceCollectionFieldSchema(CollectionFieldSchema[_ReferenceFieldType]) facet (bool): Whether the field is a facet. optional (bool): Whether the field is optional. infix (bool): Whether the field is an infix. + symbols_to_index (list[str]): The symbols to index + token_separators (list[str]): The token separators. stem (bool): Whether the field is a stem. locale (Locales): The locale of the field. sort (bool): Whether the field is sortable. From 1bbc7e8f4a02cebd6c3fd85f25fa5a4c359b4eaa Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 17:33:17 +0200 Subject: [PATCH 07/14] feat(multi-search): add `rerank_hybrid_searches` parameter to multisearch --- src/typesense/types/document.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/typesense/types/document.py b/src/typesense/types/document.py index 701e75f..529e08d 100644 --- a/src/typesense/types/document.py +++ b/src/typesense/types/document.py @@ -569,6 +569,7 @@ class MultiSearchParameters(SearchParameters): """ collection: str + rerank_hybrid_matches: typing.NotRequired[bool] class MultiSearchCommonParameters( From bf3fbcf09627e86eec29eb725d8e760070204736 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 4 Feb 2025 17:34:08 +0200 Subject: [PATCH 08/14] feat(search): add `max_filter_by_candidates` search param --- src/typesense/types/document.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/typesense/types/document.py b/src/typesense/types/document.py index 529e08d..2d2c5cf 100644 --- a/src/typesense/types/document.py +++ b/src/typesense/types/document.py @@ -368,6 +368,7 @@ class FilterParameters(typing.TypedDict): """ filter_by: typing.NotRequired[str] + max_filter_by_candidates: typing.NotRequired[int] enable_lazy_filter: typing.NotRequired[bool] From 26d41a32caf7bf4fe114794ee3b06730f5ab414b Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 19 Feb 2025 02:01:05 +0200 Subject: [PATCH 09/14] fix(api_call): use SessionFunctionKwargs directly instead of unpacking --- src/typesense/api_call.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/typesense/api_call.py b/src/typesense/api_call.py index 47d0eec..90e1929 100644 --- a/src/typesense/api_call.py +++ b/src/typesense/api_call.py @@ -334,7 +334,7 @@ def _execute_request( as_json: typing.Literal[True], last_exception: typing.Union[None, Exception] = None, num_retries: int = 0, - **kwargs: typing.Unpack[SessionFunctionKwargs[TParams, TBody]], + **kwargs: SessionFunctionKwargs[TParams, TBody], ) -> TEntityDict: """ Execute a request to the Typesense API with retry logic. @@ -373,7 +373,7 @@ def _execute_request( as_json: typing.Literal[False], last_exception: typing.Union[None, Exception] = None, num_retries: int = 0, - **kwargs: typing.Unpack[SessionFunctionKwargs[TParams, TBody]], + **kwargs: SessionFunctionKwargs[TParams, TBody], ) -> str: """ Execute a request to the Typesense API with retry logic. @@ -411,7 +411,7 @@ def _execute_request( as_json: typing.Union[typing.Literal[True], typing.Literal[False]] = True, last_exception: typing.Union[None, Exception] = None, num_retries: int = 0, - **kwargs: typing.Unpack[SessionFunctionKwargs[TParams, TBody]], + **kwargs: SessionFunctionKwargs[TParams, TBody], ) -> typing.Union[TEntityDict, str]: """ Execute a request to the Typesense API with retry logic. @@ -473,7 +473,7 @@ def _make_request_and_process_response( url: str, entity_type: typing.Type[TEntityDict], as_json: bool, - **kwargs: typing.Any, + **kwargs: SessionFunctionKwargs[TParams, TBody], ) -> typing.Union[TEntityDict, str]: """Make the API request and process the response.""" request_response = self.request_handler.make_request( @@ -493,7 +493,7 @@ def _make_request_and_process_response( def _prepare_request_params( self, endpoint: str, - **kwargs: typing.Unpack[SessionFunctionKwargs[TParams, TBody]], + **kwargs: SessionFunctionKwargs[TParams, TBody], ) -> typing.Tuple[Node, str, SessionFunctionKwargs[TParams, TBody]]: node = self.node_manager.get_node() url = node.url() + endpoint From 9531a721d9ddea494a4a245b2e738230c90a0a60 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 19 Feb 2025 02:08:53 +0200 Subject: [PATCH 10/14] chore: ignore rules for docstrings on overloads --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d623aa3..bb8169c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ ignore = Q000, WPS602, WPS432, WPS305, WPS221, WPS230, WPS234, WPS433, WPS440, W per-file-ignores = tests/*.py: S101, WPS226, WPS118, WPS202, WPS204, WPS218, WPS211, WPS604, WPS431, WPS210, WPS201, WPS437 src/typesense/types/*.py: B950, WPS215, WPS111, WPS462, WPS322, WPS428, WPS114, WPS110, WPS202 - src/typesense/documents.py: WPS320, WPS428, WPS220 + src/typesense/documents.py: WPS320, E704, D102, WPS428, WPS220 src/typesense/stemming_dictionaries.py: WPS320, WPS428, WPS220 src/typesense/api_call.py: WPS110, WPS211 src/typesense/request_handler.py: WPS110, WPS211 From 8e398d9ec53f5367ae857e4970c666bbcdb5039e Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 19 Feb 2025 02:10:28 +0200 Subject: [PATCH 11/14] chore: ignore rules for docstrings on overloads on stemming --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bb8169c..ecafea2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,7 +25,7 @@ per-file-ignores = tests/*.py: S101, WPS226, WPS118, WPS202, WPS204, WPS218, WPS211, WPS604, WPS431, WPS210, WPS201, WPS437 src/typesense/types/*.py: B950, WPS215, WPS111, WPS462, WPS322, WPS428, WPS114, WPS110, WPS202 src/typesense/documents.py: WPS320, E704, D102, WPS428, WPS220 - src/typesense/stemming_dictionaries.py: WPS320, WPS428, WPS220 + src/typesense/stemming_dictionaries.py: WPS320, E704, D102, WPS428, WPS220 src/typesense/api_call.py: WPS110, WPS211 src/typesense/request_handler.py: WPS110, WPS211 From a113035bd91bfa86bf66cb56571943ece3909ee5 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Sun, 16 Feb 2025 12:30:23 +0200 Subject: [PATCH 12/14] test: change integration test for debug to avoid future failures - Check for existence of keys and assert their types, instead of checking for their values --- tests/debug_test.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/debug_test.py b/tests/debug_test.py index efe8e92..37c593a 100644 --- a/tests/debug_test.py +++ b/tests/debug_test.py @@ -47,9 +47,11 @@ def test_retrieve(fake_debug: Debug) -> None: def test_actual_retrieve(actual_debug: Debug) -> None: - """Test that the Debug object can retrieve a debug on Typesense server.""" - json_response: DebugResponseSchema = {"state": 1, "version": "27.1"} - + """Test that the Debug object can retrieve a debug on Typesense server and verify response structure.""" response = actual_debug.retrieve() - assert response == json_response + assert "state" in response + assert "version" in response + + assert isinstance(response["state"], int) + assert isinstance(response["version"], str) From 8058450f08867f581b19f5da10a9bb66cd1b0c36 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 19 Feb 2025 15:02:03 +0200 Subject: [PATCH 13/14] ci: update typesense version to 28.0 --- .github/workflows/test-and-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-and-lint.yml b/.github/workflows/test-and-lint.yml index 45b96d7..67ede67 100644 --- a/.github/workflows/test-and-lint.yml +++ b/.github/workflows/test-and-lint.yml @@ -14,7 +14,7 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] services: typesense: - image: typesense/typesense:27.1 + image: typesense/typesense:28.0 ports: - 8108:8108 volumes: From f3a1f528cc39d7b88baa86b04d84810ea943d02f Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 19 Feb 2025 15:16:42 +0200 Subject: [PATCH 14/14] fix(tests): update tests for v28 --- tests/collection_test.py | 2 ++ tests/collections_test.py | 4 ++++ tests/documents_test.py | 1 - 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/collection_test.py b/tests/collection_test.py index d292be3..33c7837 100644 --- a/tests/collection_test.py +++ b/tests/collection_test.py @@ -197,6 +197,7 @@ def test_actual_retrieve( "sort": False, "infix": False, "stem": False, + "stem_dictionary": "", "store": True, }, { @@ -209,6 +210,7 @@ def test_actual_retrieve( "sort": True, "infix": False, "stem": False, + "stem_dictionary": "", "store": True, }, ], diff --git a/tests/collections_test.py b/tests/collections_test.py index 7df3a60..82f19ef 100644 --- a/tests/collections_test.py +++ b/tests/collections_test.py @@ -199,6 +199,7 @@ def test_actual_create(actual_collections: Collections, delete_all: None) -> Non "sort": False, "infix": False, "stem": False, + "stem_dictionary": "", "store": True, }, { @@ -211,6 +212,7 @@ def test_actual_create(actual_collections: Collections, delete_all: None) -> Non "sort": False, "infix": False, "stem": False, + "stem_dictionary": "", "store": True, }, ], @@ -265,6 +267,7 @@ def test_actual_retrieve( "sort": False, "infix": False, "stem": False, + "stem_dictionary": "", "store": True, }, { @@ -277,6 +280,7 @@ def test_actual_retrieve( "sort": True, "infix": False, "stem": False, + "stem_dictionary": "", "store": True, }, ], diff --git a/tests/documents_test.py b/tests/documents_test.py index f0572e6..9926798 100644 --- a/tests/documents_test.py +++ b/tests/documents_test.py @@ -237,7 +237,6 @@ def test_import_fail( expected.append( { "code": 409, - "document": '{"company_name": "Wrong", "id": "0", "num_employees": 0}', "error": "A document with id 0 already exists.", "success": False, },