diff --git a/CHANGELOG.md b/CHANGELOG.md index a6475d0fca..e7c9eaf28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ #### Changes +* Python code cleanup ([#2573](https://github.com/valkey-io/valkey-glide/pull/2573)) * Python: Python: FT.PROFILE command added ([#2543](https://github.com/valkey-io/valkey-glide/pull/2543)) * Python: Python: FT.AGGREGATE command added([#2530](https://github.com/valkey-io/valkey-glide/pull/2530)) * Python: Add JSON.OBJLEN command ([#2495](https://github.com/valkey-io/valkey-glide/pull/2495)) diff --git a/python/python/glide/__init__.py b/python/python/glide/__init__.py index 4db35d0e30..7d92c38b63 100644 --- a/python/python/glide/__init__.py +++ b/python/python/glide/__init__.py @@ -63,8 +63,8 @@ FtProfileOptions, ) from glide.async_commands.server_modules.ft_options.ft_search_options import ( - FtSeachOptions, FtSearchLimit, + FtSearchOptions, ReturnField, ) from glide.async_commands.server_modules.json import ( @@ -305,7 +305,7 @@ "VectorType", "FtSearchLimit", "ReturnField", - "FtSeachOptions", + "FtSearchOptions", "FtAggregateApply", "FtAggregateFilter", "FtAggregateClause", diff --git a/python/python/glide/async_commands/server_modules/ft.py b/python/python/glide/async_commands/server_modules/ft.py index c8a757a979..8b7dd4d71c 100644 --- a/python/python/glide/async_commands/server_modules/ft.py +++ b/python/python/glide/async_commands/server_modules/ft.py @@ -20,7 +20,7 @@ FtProfileOptions, ) from glide.async_commands.server_modules.ft_options.ft_search_options import ( - FtSeachOptions, + FtSearchOptions, ) from glide.constants import ( TOK, @@ -35,7 +35,7 @@ async def create( client: TGlideClient, - indexName: TEncodable, + index_name: TEncodable, schema: List[Field], options: Optional[FtCreateOptions] = None, ) -> TOK: @@ -44,9 +44,9 @@ async def create( Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name. + index_name (TEncodable): The index name. schema (List[Field]): Fields to populate into the index. Equivalent to `SCHEMA` block in the module API. - options (Optional[FtCreateOptions]): Optional arguments for the FT.CREATE command. See `FtCreateOptions`. + options (Optional[FtCreateOptions]): Optional arguments for the FT.CREATE command. Returns: TOK: A simple "OK" response. @@ -58,23 +58,23 @@ async def create( >>> result = await ft.create(glide_client, "my_idx1", schema, FtCreateOptions(DataType.HASH, prefixes)) 'OK' # Indicates successful creation of index named 'idx' """ - args: List[TEncodable] = [CommandNames.FT_CREATE, indexName] + args: List[TEncodable] = [CommandNames.FT_CREATE, index_name] if options: - args.extend(options.toArgs()) + args.extend(options.to_args()) if schema: args.append(FtCreateKeywords.SCHEMA) for field in schema: - args.extend(field.toArgs()) + args.extend(field.to_args()) return cast(TOK, await client.custom_command(args)) -async def dropindex(client: TGlideClient, indexName: TEncodable) -> TOK: +async def dropindex(client: TGlideClient, index_name: TEncodable) -> TOK: """ Drops an index. The index definition and associated content are deleted. Keys are unaffected. Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name for the index to be dropped. + index_name (TEncodable): The index name for the index to be dropped. Returns: TOK: A simple "OK" response. @@ -82,28 +82,28 @@ async def dropindex(client: TGlideClient, indexName: TEncodable) -> TOK: Examples: For the following example to work, an index named 'idx' must be already created. If not created, you will get an error. >>> from glide import ft - >>> indexName = "idx" - >>> result = await ft.dropindex(glide_client, indexName) + >>> index_name = "idx" + >>> result = await ft.dropindex(glide_client, index_name) 'OK' # Indicates successful deletion/dropping of index named 'idx' """ - args: List[TEncodable] = [CommandNames.FT_DROPINDEX, indexName] + args: List[TEncodable] = [CommandNames.FT_DROPINDEX, index_name] return cast(TOK, await client.custom_command(args)) async def search( client: TGlideClient, - indexName: TEncodable, + index_name: TEncodable, query: TEncodable, - options: Optional[FtSeachOptions], + options: Optional[FtSearchOptions], ) -> FtSearchResponse: """ Uses the provided query expression to locate keys within an index. Once located, the count and/or the content of indexed fields within those keys can be returned. Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name to search into. + index_name (TEncodable): The index name to search into. query (TEncodable): The text query to search. - options (Optional[FtSeachOptions]): The search options. See `FtSearchOptions`. + options (Optional[FtSearchOptions]): The search options. Returns: FtSearchResponse: A two element array, where first element is count of documents in result set, and the second element, which has the format Mapping[TEncodable, Mapping[TEncodable, TEncodable]] is a mapping between document names and map of their attributes. @@ -115,17 +115,17 @@ async def search( - A key named {json:}1 with value {"a":1, "b":2} >>> from glide import ft - >>> result = await ft.search(glide_client, "idx", "*", options=FtSeachOptions(return_fields=[ReturnField(field_identifier="first"), ReturnField(field_identifier="second")])) - [1, { b'json:1': { b'first': b'42', b'second': b'33' } }] # The first element, 1 is the number of keys returned in the search result. The second element is a map of data queried per key. + >>> result = await ft.search(glide_client, "idx", "*", options=FtSearchOptions(return_fields=[ReturnField(field_identifier="first"), ReturnField(field_identifier="second")])) + [1, { b'json:1': { b'first': b'42', b'second': b'33' } }] # The first element, 1 is the number of keys returned in the search result. The second element is a map of data queried per key. """ - args: List[TEncodable] = [CommandNames.FT_SEARCH, indexName, query] + args: List[TEncodable] = [CommandNames.FT_SEARCH, index_name, query] if options: - args.extend(options.toArgs()) + args.extend(options.to_args()) return cast(FtSearchResponse, await client.custom_command(args)) async def aliasadd( - client: TGlideClient, alias: TEncodable, indexName: TEncodable + client: TGlideClient, alias: TEncodable, index_name: TEncodable ) -> TOK: """ Adds an alias for an index. The new alias name can be used anywhere that an index name is required. @@ -133,7 +133,7 @@ async def aliasadd( Args: client (TGlideClient): The client to execute the command. alias (TEncodable): The alias to be added to an index. - indexName (TEncodable): The index name for which the alias has to be added. + index_name (TEncodable): The index name for which the alias has to be added. Returns: TOK: A simple "OK" response. @@ -143,7 +143,7 @@ async def aliasadd( >>> result = await ft.aliasadd(glide_client, "myalias", "myindex") 'OK' # Indicates the successful addition of the alias named "myalias" for the index. """ - args: List[TEncodable] = [CommandNames.FT_ALIASADD, alias, indexName] + args: List[TEncodable] = [CommandNames.FT_ALIASADD, alias, index_name] return cast(TOK, await client.custom_command(args)) @@ -168,7 +168,7 @@ async def aliasdel(client: TGlideClient, alias: TEncodable) -> TOK: async def aliasupdate( - client: TGlideClient, alias: TEncodable, indexName: TEncodable + client: TGlideClient, alias: TEncodable, index_name: TEncodable ) -> TOK: """ Updates an existing alias to point to a different physical index. This command only affects future references to the alias. @@ -176,7 +176,7 @@ async def aliasupdate( Args: client (TGlideClient): The client to execute the command. alias (TEncodable): The alias name. This alias will now be pointed to a different index. - indexName (TEncodable): The index name for which an existing alias has to updated. + index_name (TEncodable): The index name for which an existing alias has to updated. Returns: TOK: A simple "OK" response. @@ -186,20 +186,20 @@ async def aliasupdate( >>> result = await ft.aliasupdate(glide_client, "myalias", "myindex") 'OK' # Indicates the successful update of the alias to point to the index named "myindex" """ - args: List[TEncodable] = [CommandNames.FT_ALIASUPDATE, alias, indexName] + args: List[TEncodable] = [CommandNames.FT_ALIASUPDATE, alias, index_name] return cast(TOK, await client.custom_command(args)) -async def info(client: TGlideClient, indexName: TEncodable) -> FtInfoResponse: +async def info(client: TGlideClient, index_name: TEncodable) -> FtInfoResponse: """ Returns information about a given index. Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name for which the information has to be returned. + index_name (TEncodable): The index name for which the information has to be returned. Returns: - FtInfoResponse: Nested maps with info about the index. See example for more details. See `FtInfoResponse`. + FtInfoResponse: Nested maps with info about the index. See example for more details. Examples: An index with name 'myIndex', 1 text field and 1 vector field is already created for gettting the output of this example. @@ -238,19 +238,19 @@ async def info(client: TGlideClient, indexName: TEncodable) -> FtInfoResponse: b'index_degradation_percentage', 0 ] """ - args: List[TEncodable] = [CommandNames.FT_INFO, indexName] + args: List[TEncodable] = [CommandNames.FT_INFO, index_name] return cast(FtInfoResponse, await client.custom_command(args)) async def explain( - client: TGlideClient, indexName: TEncodable, query: TEncodable + client: TGlideClient, index_name: TEncodable, query: TEncodable ) -> TEncodable: """ Parse a query and return information about how that query was parsed. Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name for which the query is written. + index_name (TEncodable): The index name for which the query is written. query (TEncodable): The search query, same as the query passed as an argument to FT.SEARCH. Returns: @@ -258,22 +258,22 @@ async def explain( Examples: >>> from glide import ft - >>> result = await ft.explain(glide_client, indexName="myIndex", query="@price:[0 10]") + >>> result = await ft.explain(glide_client, index_name="myIndex", query="@price:[0 10]") b'Field {\n price\n 0\n 10\n}\n' # Parsed results. """ - args: List[TEncodable] = [CommandNames.FT_EXPLAIN, indexName, query] + args: List[TEncodable] = [CommandNames.FT_EXPLAIN, index_name, query] return cast(TEncodable, await client.custom_command(args)) async def explaincli( - client: TGlideClient, indexName: TEncodable, query: TEncodable + client: TGlideClient, index_name: TEncodable, query: TEncodable ) -> List[TEncodable]: """ Same as the FT.EXPLAIN command except that the results are displayed in a different format. More useful with cli. Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name for which the query is written. + index_name (TEncodable): The index name for which the query is written. query (TEncodable): The search query, same as the query passed as an argument to FT.SEARCH. Returns: @@ -281,16 +281,16 @@ async def explaincli( Examples: >>> from glide import ft - >>> result = await ft.explaincli(glide_client, indexName="myIndex", query="@price:[0 10]") + >>> result = await ft.explaincli(glide_client, index_name="myIndex", query="@price:[0 10]") [b'Field {', b' price', b' 0', b' 10', b'}', b''] # Parsed results. """ - args: List[TEncodable] = [CommandNames.FT_EXPLAINCLI, indexName, query] + args: List[TEncodable] = [CommandNames.FT_EXPLAINCLI, index_name, query] return cast(List[TEncodable], await client.custom_command(args)) async def aggregate( client: TGlideClient, - indexName: TEncodable, + index_name: TEncodable, query: TEncodable, options: Optional[FtAggregateOptions], ) -> FtAggregateResponse: @@ -299,7 +299,7 @@ async def aggregate( Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name for which the query is written. + index_name (TEncodable): The index name for which the query is written. query (TEncodable): The search query, same as the query passed as an argument to FT.SEARCH. options (Optional[FtAggregateOptions]): The optional arguments for the command. @@ -311,28 +311,28 @@ async def aggregate( >>> result = await ft.aggregate(glide_client, "myIndex", "*", FtAggregateOptions(loadFields=["__key"], clauses=[GroupBy(["@condition"], [Reducer("COUNT", [], "bicycles")])])) [{b'condition': b'refurbished', b'bicycles': b'1'}, {b'condition': b'new', b'bicycles': b'5'}, {b'condition': b'used', b'bicycles': b'4'}] """ - args: List[TEncodable] = [CommandNames.FT_AGGREGATE, indexName, query] + args: List[TEncodable] = [CommandNames.FT_AGGREGATE, index_name, query] if options: args.extend(options.to_args()) return cast(FtAggregateResponse, await client.custom_command(args)) async def profile( - client: TGlideClient, indexName: TEncodable, options: FtProfileOptions + client: TGlideClient, index_name: TEncodable, options: FtProfileOptions ) -> FtProfileResponse: """ Runs a search or aggregation query and collects performance profiling information. Args: client (TGlideClient): The client to execute the command. - indexName (TEncodable): The index name + index_name (TEncodable): The index name options (FtProfileOptions): Options for the command. Returns: FtProfileResponse: A two-element array. The first element contains results of query being profiled, the second element stores profiling information. Examples: - >>> ftSearchOptions = FtSeachOptions(return_fields=[ReturnField(field_identifier="a", alias="a_new"), ReturnField(field_identifier="b", alias="b_new")]) + >>> ftSearchOptions = FtSearchOptions(return_fields=[ReturnField(field_identifier="a", alias="a_new"), ReturnField(field_identifier="b", alias="b_new")]) >>> ftProfileResult = await ft.profile(glide_client, "myIndex", FtProfileOptions.from_query_options(query="*", queryOptions=ftSearchOptions)) [ [ @@ -357,5 +357,5 @@ async def profile( } ] """ - args: List[TEncodable] = [CommandNames.FT_PROFILE, indexName] + options.to_args() + args: List[TEncodable] = [CommandNames.FT_PROFILE, index_name] + options.to_args() return cast(FtProfileResponse, await client.custom_command(args)) diff --git a/python/python/glide/async_commands/server_modules/ft_options/ft_constants.py b/python/python/glide/async_commands/server_modules/ft_options/ft_constants.py index 15a978eac8..ec6c186728 100644 --- a/python/python/glide/async_commands/server_modules/ft_options/ft_constants.py +++ b/python/python/glide/async_commands/server_modules/ft_options/ft_constants.py @@ -42,7 +42,7 @@ class FtCreateKeywords: EF_RUNTIME = "EF_RUNTIME" -class FtSeachKeywords: +class FtSearchKeywords: """ Keywords used in the FT.SEARCH command. """ diff --git a/python/python/glide/async_commands/server_modules/ft_options/ft_create_options.py b/python/python/glide/async_commands/server_modules/ft_options/ft_create_options.py index 90aa2d9fdf..551c160641 100644 --- a/python/python/glide/async_commands/server_modules/ft_options/ft_create_options.py +++ b/python/python/glide/async_commands/server_modules/ft_options/ft_create_options.py @@ -95,7 +95,7 @@ def __init__( Args: name (TEncodable): The name of the field. - type (FieldType): The type of the field. See `FieldType`. + type (FieldType): The type of the field. alias (Optional[TEncodable]): An alias for the field. """ self.name = name @@ -103,7 +103,7 @@ def __init__( self.alias = alias @abstractmethod - def toArgs(self) -> List[TEncodable]: + def to_args(self) -> List[TEncodable]: """ Get the arguments representing the field. @@ -132,14 +132,8 @@ def __init__(self, name: TEncodable, alias: Optional[TEncodable] = None): """ super().__init__(name, FieldType.TEXT, alias) - def toArgs(self) -> List[TEncodable]: - """ - Get the arguments representing the text field. - - Returns: - List[TEncodable]: A list of text field arguments. - """ - args = super().toArgs() + def to_args(self) -> List[TEncodable]: + args = super().to_args() return args @@ -172,14 +166,8 @@ def __init__( self.separator = separator self.case_sensitive = case_sensitive - def toArgs(self) -> List[TEncodable]: - """ - Get the arguments representing the tag field. - - Returns: - List[TEncodable]: A list of tag field arguments. - """ - args = super().toArgs() + def to_args(self) -> List[TEncodable]: + args = super().to_args() if self.separator: args.extend([FtCreateKeywords.SEPARATOR, self.separator]) if self.case_sensitive: @@ -202,14 +190,8 @@ def __init__(self, name: TEncodable, alias: Optional[TEncodable] = None): """ super().__init__(name, FieldType.NUMERIC, alias) - def toArgs(self) -> List[TEncodable]: - """ - Get the arguments representing the numeric field. - - Returns: - List[TEncodable]: A list of numeric field arguments. - """ - args = super().toArgs() + def to_args(self) -> List[TEncodable]: + args = super().to_args() return args @@ -235,7 +217,7 @@ def __init__( self.type = type @abstractmethod - def toArgs(self) -> List[TEncodable]: + def to_args(self) -> List[TEncodable]: """ Get the arguments to be used for the algorithm of the vector field. @@ -276,14 +258,8 @@ def __init__( super().__init__(dimensions, distance_metric, type) self.initial_cap = initial_cap - def toArgs(self) -> List[TEncodable]: - """ - Get the arguments representing the vector field created with FLAT algorithm. - - Returns: - List[TEncodable]: A list of FLAT algorithm type vector arguments. - """ - args = super().toArgs() + def to_args(self) -> List[TEncodable]: + args = super().to_args() if self.initial_cap: args.extend([FtCreateKeywords.INITIAL_CAP, str(self.initial_cap)]) return args @@ -322,14 +298,8 @@ def __init__( self.vectors_examined_on_construction = vectors_examined_on_construction self.vectors_examined_on_runtime = vectors_examined_on_runtime - def toArgs(self) -> List[TEncodable]: - """ - Get the arguments representing the vector field created with HSNW algorithm. - - Returns: - List[TEncodable]: A list of HNSW algorithm type vector arguments. - """ - args = super().toArgs() + def to_args(self) -> List[TEncodable]: + args = super().to_args() if self.initial_cap: args.extend([FtCreateKeywords.INITIAL_CAP, str(self.initial_cap)]) if self.number_of_edges: @@ -365,25 +335,19 @@ def __init__( Args: name (TEncodable): The name of the vector field. - algorithm (VectorAlgorithm): The vector indexing algorithm. See `VectorAlgorithm`. + algorithm (VectorAlgorithm): The vector indexing algorithm. alias (Optional[TEncodable]): An alias for the field. - attributes (VectorFieldAttributes): Additional attributes to be passed with the vector field after the algorithm name. See `VectorFieldAttributes`. + attributes (VectorFieldAttributes): Additional attributes to be passed with the vector field after the algorithm name. """ super().__init__(name, FieldType.VECTOR, alias) self.algorithm = algorithm self.attributes = attributes - def toArgs(self) -> List[TEncodable]: - """ - Get the arguments representing the vector field. - - Returns: - List[TEncodable]: A list of vector field arguments. - """ - args = super().toArgs() + def to_args(self) -> List[TEncodable]: + args = super().to_args() args.append(self.algorithm.value) if self.attributes: - attribute_list = self.attributes.toArgs() + attribute_list = self.attributes.to_args() args.append(str(len(attribute_list))) args.extend(attribute_list) return args @@ -419,13 +383,13 @@ def __init__( Initialize the FT.CREATE optional fields. Args: - data_type (Optional[DataType]): The index data type. If not defined a `HASH` index is created. See `DataType`. + data_type (Optional[DataType]): The index data type. If not defined a `HASH` index is created. prefixes (Optional[List[TEncodable]]): A list of prefixes of index definitions. """ self.data_type = data_type self.prefixes = prefixes - def toArgs(self) -> List[TEncodable]: + def to_args(self) -> List[TEncodable]: """ Get the optional arguments for the FT.CREATE command. diff --git a/python/python/glide/async_commands/server_modules/ft_options/ft_profile_options.py b/python/python/glide/async_commands/server_modules/ft_options/ft_profile_options.py index d6ab8ceb7b..46bbab7b9f 100644 --- a/python/python/glide/async_commands/server_modules/ft_options/ft_profile_options.py +++ b/python/python/glide/async_commands/server_modules/ft_options/ft_profile_options.py @@ -9,7 +9,7 @@ FtProfileKeywords, ) from glide.async_commands.server_modules.ft_options.ft_search_options import ( - FtSeachOptions, + FtSearchOptions, ) from glide.constants import TEncodable @@ -37,8 +37,8 @@ class FtProfileOptions: def __init__( self, query: TEncodable, - queryType: QueryType, - queryOptions: Optional[Union[FtSeachOptions, FtAggregateOptions]] = None, + query_type: QueryType, + query_options: Optional[Union[FtSearchOptions, FtAggregateOptions]] = None, limited: Optional[bool] = False, ): """ @@ -46,20 +46,20 @@ def __init__( Args: query (TEncodable): The query that is being profiled. This is the query argument from the FT.AGGREGATE/FT.SEARCH command. - queryType (Optional[QueryType]): The type of query to be profiled. - queryOptions (Optional[Union[FtSeachOptions, FtAggregateOptions]]): The arguments/options for the FT.AGGREGATE/FT.SEARCH command being profiled. + query_type (Optional[QueryType]): The type of query to be profiled. + query_options (Optional[Union[FtSearchOptions, FtAggregateOptions]]): The arguments/options for the FT.AGGREGATE/FT.SEARCH command being profiled. limited (Optional[bool]): To provide some brief version of the output, otherwise a full verbose output is provided. """ self.query = query - self.queryType = queryType - self.queryOptions = queryOptions + self.query_type = query_type + self.query_options = query_options self.limited = limited @classmethod def from_query_options( cls, query: TEncodable, - queryOptions: Union[FtSeachOptions, FtAggregateOptions], + query_options: Union[FtSearchOptions, FtAggregateOptions], limited: Optional[bool] = False, ): """ @@ -67,27 +67,27 @@ def from_query_options( Args: query (TEncodable): The query that is being profiled. This is the query argument from the FT.AGGREGATE/FT.SEARCH command. - queryOptions (Optional[Union[FtSeachOptions, FtAggregateOptions]]): The arguments/options for the FT.AGGREGATE/FT.SEARCH command being profiled. + query_options (Optional[Union[FtSearchOptions, FtAggregateOptions]]): The arguments/options for the FT.AGGREGATE/FT.SEARCH command being profiled. limited (Optional[bool]): To provide some brief version of the output, otherwise a full verbose output is provided. """ - queryType: QueryType = QueryType.SEARCH - if type(queryOptions) == FtAggregateOptions: - queryType = QueryType.AGGREGATE - return cls(query, queryType, queryOptions, limited) + query_type: QueryType = QueryType.SEARCH + if type(query_options) == FtAggregateOptions: + query_type = QueryType.AGGREGATE + return cls(query, query_type, query_options, limited) @classmethod def from_query_type( - cls, query: TEncodable, queryType: QueryType, limited: Optional[bool] = False + cls, query: TEncodable, query_type: QueryType, limited: Optional[bool] = False ): """ A class method to create FtProfileOptions with QueryType. Args: query (TEncodable): The query that is being profiled. This is the query argument from the FT.AGGREGATE/FT.SEARCH command. - queryType (QueryType): The type of query to be profiled. + query_type (QueryType): The type of query to be profiled. limited (Optional[bool]): To provide some brief version of the output, otherwise a full verbose output is provided. """ - return cls(query, queryType, None, limited) + return cls(query, query_type, None, limited) def to_args(self) -> List[TEncodable]: """ @@ -96,13 +96,13 @@ def to_args(self) -> List[TEncodable]: Returns: List[TEncodable]: A list of remaining arguments for the FT.PROFILE command. """ - args: List[TEncodable] = [self.queryType.value] + args: List[TEncodable] = [self.query_type.value] if self.limited: args.append(FtProfileKeywords.LIMITED) args.extend([FtProfileKeywords.QUERY, self.query]) - if self.queryOptions: - if type(self.queryOptions) == FtAggregateOptions: - args.extend(cast(FtAggregateOptions, self.queryOptions).to_args()) + if self.query_options: + if type(self.query_options) == FtAggregateOptions: + args.extend(cast(FtAggregateOptions, self.query_options).to_args()) else: - args.extend(cast(FtSeachOptions, self.queryOptions).toArgs()) + args.extend(cast(FtSearchOptions, self.query_options).to_args()) return args diff --git a/python/python/glide/async_commands/server_modules/ft_options/ft_search_options.py b/python/python/glide/async_commands/server_modules/ft_options/ft_search_options.py index 79f5422edc..f76b309b0f 100644 --- a/python/python/glide/async_commands/server_modules/ft_options/ft_search_options.py +++ b/python/python/glide/async_commands/server_modules/ft_options/ft_search_options.py @@ -2,7 +2,7 @@ from typing import List, Mapping, Optional -from glide.async_commands.server_modules.ft_options.ft_constants import FtSeachKeywords +from glide.async_commands.server_modules.ft_options.ft_constants import FtSearchKeywords from glide.constants import TEncodable @@ -22,7 +22,7 @@ def __init__(self, offset: int, count: int): self.offset = offset self.count = count - def toArgs(self) -> List[TEncodable]: + def to_args(self) -> List[TEncodable]: """ Get the arguments for the LIMIT option of FT.SEARCH. @@ -30,7 +30,7 @@ def toArgs(self) -> List[TEncodable]: List[TEncodable]: A list of LIMIT option arguments. """ args: List[TEncodable] = [ - FtSeachKeywords.LIMIT, + FtSearchKeywords.LIMIT, str(self.offset), str(self.count), ] @@ -55,7 +55,7 @@ def __init__( self.field_identifier = field_identifier self.alias = alias - def toArgs(self) -> List[TEncodable]: + def to_args(self) -> List[TEncodable]: """ Get the arguments for the RETURN option of FT.SEARCH. @@ -64,12 +64,12 @@ def toArgs(self) -> List[TEncodable]: """ args: List[TEncodable] = [self.field_identifier] if self.alias: - args.append(FtSeachKeywords.AS) + args.append(FtSearchKeywords.AS) args.append(self.alias) return args -class FtSeachOptions: +class FtSearchOptions: """ This class represents the input options to be used in the FT.SEARCH command. All fields in this class are optional inputs for FT.SEARCH. @@ -99,7 +99,7 @@ def __init__( self.limit = limit self.count = count - def toArgs(self) -> List[TEncodable]: + def to_args(self) -> List[TEncodable]: """ Get the optional arguments for the FT.SEARCH command. @@ -109,23 +109,23 @@ def toArgs(self) -> List[TEncodable]: """ args: List[TEncodable] = [] if self.return_fields: - args.append(FtSeachKeywords.RETURN) + args.append(FtSearchKeywords.RETURN) return_field_args: List[TEncodable] = [] for return_field in self.return_fields: - return_field_args.extend(return_field.toArgs()) + return_field_args.extend(return_field.to_args()) args.append(str(len(return_field_args))) args.extend(return_field_args) if self.timeout: - args.append(FtSeachKeywords.TIMEOUT) + args.append(FtSearchKeywords.TIMEOUT) args.append(str(self.timeout)) if self.params: - args.append(FtSeachKeywords.PARAMS) + args.append(FtSearchKeywords.PARAMS) args.append(str(len(self.params))) for name, value in self.params.items(): args.append(name) args.append(value) if self.limit: - args.extend(self.limit.toArgs()) + args.extend(self.limit.to_args()) if self.count: - args.append(FtSeachKeywords.COUNT) + args.append(FtSearchKeywords.COUNT) return args diff --git a/python/python/tests/tests_server_modules/search/test_ft_create.py b/python/python/tests/tests_server_modules/search/test_ft_create.py deleted file mode 100644 index 6655fac0c0..0000000000 --- a/python/python/tests/tests_server_modules/search/test_ft_create.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 -import uuid -from typing import List - -import pytest -from glide.async_commands.server_modules import ft -from glide.async_commands.server_modules.ft_options.ft_create_options import ( - DataType, - DistanceMetricType, - Field, - FtCreateOptions, - NumericField, - TextField, - VectorAlgorithm, - VectorField, - VectorFieldAttributesHnsw, - VectorType, -) -from glide.config import ProtocolVersion -from glide.constants import OK, TEncodable -from glide.glide_client import GlideClusterClient - - -@pytest.mark.asyncio -class TestFtCreate: - @pytest.mark.parametrize("cluster_mode", [True]) - @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_ft_create(self, glide_client: GlideClusterClient): - fields: List[Field] = [] - textFieldTitle: TextField = TextField("$title") - numberField: NumericField = NumericField("$published_at") - textFieldCategory: TextField = TextField("$category") - fields.append(textFieldTitle) - fields.append(numberField) - fields.append(textFieldCategory) - - prefixes: List[TEncodable] = [] - prefixes.append("blog:post:") - - # Create an index with multiple fields with Hash data type. - index = str(uuid.uuid4()) - result = await ft.create( - glide_client, index, fields, FtCreateOptions(DataType.HASH, prefixes) - ) - assert result == OK - assert await ft.dropindex(glide_client, indexName=index) == OK - - # Create an index with multiple fields with JSON data type. - index2 = str(uuid.uuid4()) - result = await ft.create( - glide_client, index2, fields, FtCreateOptions(DataType.JSON, prefixes) - ) - assert result == OK - assert await ft.dropindex(glide_client, indexName=index2) == OK - - # Create an index for vectors of size 2 - # FT.CREATE hash_idx1 ON HASH PREFIX 1 hash: SCHEMA vec AS VEC VECTOR HNSW 6 DIM 2 TYPE FLOAT32 DISTANCE_METRIC L2 - index3 = str(uuid.uuid4()) - prefixes = [] - prefixes.append("hash:") - fields = [] - vectorFieldHash: VectorField = VectorField( - name="vec", - algorithm=VectorAlgorithm.HNSW, - attributes=VectorFieldAttributesHnsw( - dimensions=2, - distance_metric=DistanceMetricType.L2, - type=VectorType.FLOAT32, - ), - alias="VEC", - ) - fields.append(vectorFieldHash) - - result = await ft.create( - glide_client, index3, fields, FtCreateOptions(DataType.HASH, prefixes) - ) - assert result == OK - assert await ft.dropindex(glide_client, indexName=index3) == OK - - # Create a 6-dimensional JSON index using the HNSW algorithm - # FT.CREATE json_idx1 ON JSON PREFIX 1 json: SCHEMA $.vec AS VEC VECTOR HNSW 6 DIM 6 TYPE FLOAT32 DISTANCE_METRIC L2 - index4 = str(uuid.uuid4()) - prefixes = [] - prefixes.append("json:") - fields = [] - vectorFieldJson: VectorField = VectorField( - name="$.vec", - algorithm=VectorAlgorithm.HNSW, - attributes=VectorFieldAttributesHnsw( - dimensions=6, - distance_metric=DistanceMetricType.L2, - type=VectorType.FLOAT32, - ), - alias="VEC", - ) - fields.append(vectorFieldJson) - - result = await ft.create( - glide_client, index4, fields, FtCreateOptions(DataType.JSON, prefixes) - ) - assert result == OK - assert await ft.dropindex(glide_client, indexName=index4) == OK - - # Create an index without FtCreateOptions - - index5 = str(uuid.uuid4()) - result = await ft.create(glide_client, index5, fields, FtCreateOptions()) - assert result == OK - assert await ft.dropindex(glide_client, indexName=index5) == OK - - # TO-DO: - # Add additional tests from VSS documentation that require a combination of commands to run. diff --git a/python/python/tests/tests_server_modules/search/test_ft_dropindex.py b/python/python/tests/tests_server_modules/search/test_ft_dropindex.py deleted file mode 100644 index 717df38eb8..0000000000 --- a/python/python/tests/tests_server_modules/search/test_ft_dropindex.py +++ /dev/null @@ -1,44 +0,0 @@ -import uuid -from typing import List - -import pytest -from glide.async_commands.server_modules import ft -from glide.async_commands.server_modules.ft_options.ft_create_options import ( - DataType, - Field, - FtCreateOptions, - TextField, -) -from glide.config import ProtocolVersion -from glide.constants import OK, TEncodable -from glide.exceptions import RequestError -from glide.glide_client import GlideClusterClient - - -@pytest.mark.asyncio -class TestFtDropIndex: - @pytest.mark.parametrize("cluster_mode", [True]) - @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_ft_dropindex(self, glide_client: GlideClusterClient): - # Index name for the index to be dropped. - indexName = str(uuid.uuid4()) - - fields: List[Field] = [] - textFieldTitle: TextField = TextField("$title") - fields.append(textFieldTitle) - prefixes: List[TEncodable] = [] - prefixes.append("blog:post:") - - # Create an index with multiple fields with Hash data type. - result = await ft.create( - glide_client, indexName, fields, FtCreateOptions(DataType.HASH, prefixes) - ) - assert result == OK - - # Drop the index. Expects "OK" as a response. - result = await ft.dropindex(glide_client, indexName) - assert result == OK - - # Drop a non existent index. Expects a RequestError. - with pytest.raises(RequestError): - await ft.dropindex(glide_client, indexName) diff --git a/python/python/tests/tests_server_modules/search/test_ft_search.py b/python/python/tests/tests_server_modules/search/test_ft_search.py deleted file mode 100644 index bece8e1434..0000000000 --- a/python/python/tests/tests_server_modules/search/test_ft_search.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 - -import json -import time -import uuid -from typing import List, Mapping, Union, cast - -import pytest -from glide.async_commands.server_modules import ft -from glide.async_commands.server_modules import json as GlideJson -from glide.async_commands.server_modules.ft_options.ft_create_options import ( - DataType, - FtCreateOptions, - NumericField, -) -from glide.async_commands.server_modules.ft_options.ft_profile_options import ( - FtProfileOptions, -) -from glide.async_commands.server_modules.ft_options.ft_search_options import ( - FtSeachOptions, - ReturnField, -) -from glide.config import ProtocolVersion -from glide.constants import OK, FtSearchResponse, TEncodable -from glide.glide_client import GlideClusterClient - - -@pytest.mark.asyncio -class TestFtSearch: - sleep_wait_time = 0.5 # This value is in seconds - - @pytest.mark.parametrize("cluster_mode", [True]) - @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_ft_search(self, glide_client: GlideClusterClient): - prefix = "{json-search-" + str(uuid.uuid4()) + "}:" - json_key1 = prefix + str(uuid.uuid4()) - json_key2 = prefix + str(uuid.uuid4()) - json_value1 = {"a": 11111, "b": 2, "c": 3} - json_value2 = {"a": 22222, "b": 2, "c": 3} - prefixes: List[TEncodable] = [] - prefixes.append(prefix) - index = prefix + str(uuid.uuid4()) - - # Create an index. - assert ( - await ft.create( - glide_client, - index, - schema=[ - NumericField("$.a", "a"), - NumericField("$.b", "b"), - ], - options=FtCreateOptions(DataType.JSON), - ) - == OK - ) - - # Create a json key. - assert ( - await GlideJson.set(glide_client, json_key1, "$", json.dumps(json_value1)) - == OK - ) - assert ( - await GlideJson.set(glide_client, json_key2, "$", json.dumps(json_value2)) - == OK - ) - - # Wait for index to be updated to avoid this error - ResponseError: The index is under construction. - time.sleep(self.sleep_wait_time) - - ftSearchOptions = FtSeachOptions( - return_fields=[ - ReturnField(field_identifier="a", alias="a_new"), - ReturnField(field_identifier="b", alias="b_new"), - ] - ) - - # Search the index for string inputs. - result1 = await ft.search(glide_client, index, "*", options=ftSearchOptions) - # Check if we get the expected result from ft.search for string inputs. - TestFtSearch._ft_search_deep_compare_result( - self, - result=result1, - json_key1=json_key1, - json_key2=json_key2, - json_value1=json_value1, - json_value2=json_value2, - fieldName1="a", - fieldName2="b", - ) - - # Test FT.PROFILE for the above mentioned FT.SEARCH query and search options. - - ftProfileResult = await ft.profile( - glide_client, - index, - FtProfileOptions.from_query_options( - query="*", queryOptions=ftSearchOptions - ), - ) - print(ftProfileResult) - assert len(ftProfileResult) > 0 - - # Check if we get the expected result from FT.PROFILE for string inputs. - TestFtSearch._ft_search_deep_compare_result( - self, - result=cast(FtSearchResponse, ftProfileResult[0]), - json_key1=json_key1, - json_key2=json_key2, - json_value1=json_value1, - json_value2=json_value2, - fieldName1="a", - fieldName2="b", - ) - ftSearchOptionsByteInput = FtSeachOptions( - return_fields=[ - ReturnField(field_identifier=b"a", alias=b"a_new"), - ReturnField(field_identifier=b"b", alias=b"b_new"), - ] - ) - - # Search the index for byte type inputs. - result2 = await ft.search( - glide_client, bytes(index, "utf-8"), b"*", options=ftSearchOptionsByteInput - ) - - # Check if we get the expected result from ft.search for byte type inputs. - TestFtSearch._ft_search_deep_compare_result( - self, - result=result2, - json_key1=json_key1, - json_key2=json_key2, - json_value1=json_value1, - json_value2=json_value2, - fieldName1="a", - fieldName2="b", - ) - - # Test FT.PROFILE for the above mentioned FT.SEARCH query and search options for byte type inputs. - ftProfileResult = await ft.profile( - glide_client, - index, - FtProfileOptions.from_query_options( - query=b"*", queryOptions=ftSearchOptionsByteInput - ), - ) - assert len(ftProfileResult) > 0 - - # Check if we get the expected result from FT.PROFILE for byte type inputs. - TestFtSearch._ft_search_deep_compare_result( - self, - result=cast(FtSearchResponse, ftProfileResult[0]), - json_key1=json_key1, - json_key2=json_key2, - json_value1=json_value1, - json_value2=json_value2, - fieldName1="a", - fieldName2="b", - ) - - assert await ft.dropindex(glide_client, indexName=index) == OK - - def _ft_search_deep_compare_result( - self, - result: List[Union[int, Mapping[TEncodable, Mapping[TEncodable, TEncodable]]]], - json_key1: str, - json_key2: str, - json_value1: dict, - json_value2: dict, - fieldName1: str, - fieldName2: str, - ): - """ - Deep compare the keys and values in FT.SEARCH result array. - - Args: - result (List[Union[int, Mapping[TEncodable, Mapping[TEncodable, TEncodable]]]]): - json_key1 (str): The first key in search result. - json_key2 (str): The second key in the search result. - json_value1 (dict): The fields map for first key in the search result. - json_value2 (dict): The fields map for second key in the search result. - """ - assert len(result) == 2 - assert result[0] == 2 - searchResultMap: Mapping[TEncodable, Mapping[TEncodable, TEncodable]] = cast( - Mapping[TEncodable, Mapping[TEncodable, TEncodable]], result[1] - ) - expectedResultMap: Mapping[TEncodable, Mapping[TEncodable, TEncodable]] = { - json_key1.encode(): { - fieldName1.encode(): str(json_value1.get(fieldName1)).encode(), - fieldName2.encode(): str(json_value1.get(fieldName2)).encode(), - }, - json_key2.encode(): { - fieldName1.encode(): str(json_value2.get(fieldName1)).encode(), - fieldName2.encode(): str(json_value2.get(fieldName2)).encode(), - }, - } - assert searchResultMap == expectedResultMap diff --git a/python/python/tests/tests_server_modules/test_ft.py b/python/python/tests/tests_server_modules/test_ft.py index 5c49e5e7c2..7da25bb884 100644 --- a/python/python/tests/tests_server_modules/test_ft.py +++ b/python/python/tests/tests_server_modules/test_ft.py @@ -1,4 +1,6 @@ # Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +import json import time import uuid from typing import List, Mapping, Union, cast @@ -9,7 +11,6 @@ from glide.async_commands.server_modules import json as GlideJson from glide.async_commands.server_modules.ft_options.ft_aggregate_options import ( FtAggregateApply, - FtAggregateClause, FtAggregateGroupBy, FtAggregateOptions, FtAggregateReducer, @@ -32,8 +33,12 @@ from glide.async_commands.server_modules.ft_options.ft_profile_options import ( FtProfileOptions, ) +from glide.async_commands.server_modules.ft_options.ft_search_options import ( + FtSearchOptions, + ReturnField, +) from glide.config import ProtocolVersion -from glide.constants import OK, TEncodable +from glide.constants import OK, FtSearchResponse, TEncodable from glide.exceptions import RequestError from glide.glide_client import GlideClusterClient @@ -53,86 +58,406 @@ class TestFt: sleep_wait_time = 1 # This value is in seconds + @pytest.mark.parametrize("cluster_mode", [True]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_ft_create(self, glide_client: GlideClusterClient): + fields: List[Field] = [ + TextField("$title"), + NumericField("$published_at"), + TextField("$category"), + ] + prefixes: List[TEncodable] = ["blog:post:"] + + # Create an index with multiple fields with Hash data type. + index = str(uuid.uuid4()) + assert ( + await ft.create( + glide_client, index, fields, FtCreateOptions(DataType.HASH, prefixes) + ) + == OK + ) + assert await ft.dropindex(glide_client, index) == OK + + # Create an index with multiple fields with JSON data type. + index2 = str(uuid.uuid4()) + assert ( + await ft.create( + glide_client, index2, fields, FtCreateOptions(DataType.JSON, prefixes) + ) + == OK + ) + assert await ft.dropindex(glide_client, index2) == OK + + # Create an index for vectors of size 2 + # FT.CREATE hash_idx1 ON HASH PREFIX 1 hash: SCHEMA vec AS VEC VECTOR HNSW 6 DIM 2 TYPE FLOAT32 DISTANCE_METRIC L2 + index3 = str(uuid.uuid4()) + prefixes = ["hash:"] + fields = [ + VectorField( + name="vec", + algorithm=VectorAlgorithm.HNSW, + attributes=VectorFieldAttributesHnsw( + dimensions=2, + distance_metric=DistanceMetricType.L2, + type=VectorType.FLOAT32, + ), + alias="VEC", + ) + ] + + assert ( + await ft.create( + glide_client, index3, fields, FtCreateOptions(DataType.HASH, prefixes) + ) + == OK + ) + assert await ft.dropindex(glide_client, index3) == OK + + # Create a 6-dimensional JSON index using the HNSW algorithm + # FT.CREATE json_idx1 ON JSON PREFIX 1 json: SCHEMA $.vec AS VEC VECTOR HNSW 6 DIM 6 TYPE FLOAT32 DISTANCE_METRIC L2 + index4 = str(uuid.uuid4()) + prefixes = ["json:"] + fields = [ + VectorField( + name="$.vec", + algorithm=VectorAlgorithm.HNSW, + attributes=VectorFieldAttributesHnsw( + dimensions=6, + distance_metric=DistanceMetricType.L2, + type=VectorType.FLOAT32, + ), + alias="VEC", + ) + ] + + assert ( + await ft.create( + glide_client, index4, fields, FtCreateOptions(DataType.JSON, prefixes) + ) + == OK + ) + assert await ft.dropindex(glide_client, index4) == OK + + # Create an index without FtCreateOptions + + index5 = str(uuid.uuid4()) + assert await ft.create(glide_client, index5, fields, FtCreateOptions()) == OK + assert await ft.dropindex(glide_client, index5) == OK + + @pytest.mark.parametrize("cluster_mode", [True]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_ft_create_byte_type_input(self, glide_client: GlideClusterClient): + fields: List[Field] = [ + TextField(b"$title"), + NumericField(b"$published_at"), + TextField(b"$category"), + ] + prefixes: List[TEncodable] = [b"blog:post:"] + + # Create an index with multiple fields with Hash data type with byte type input. + index = str(uuid.uuid4()) + assert ( + await ft.create( + glide_client, + index.encode("utf-8"), + fields, + FtCreateOptions(DataType.HASH, prefixes), + ) + == OK + ) + assert await ft.dropindex(glide_client, index) == OK + + @pytest.mark.parametrize("cluster_mode", [True]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_ft_dropindex(self, glide_client: GlideClusterClient): + # Index name for the index to be dropped. + index_name = str(uuid.uuid4()) + fields: List[Field] = [TextField("$title")] + prefixes: List[TEncodable] = ["blog:post:"] + + # Create an index with multiple fields with Hash data type. + assert ( + await ft.create( + glide_client, + index_name, + fields, + FtCreateOptions(DataType.HASH, prefixes), + ) + == OK + ) + + # Drop the index. Expects "OK" as a response. + assert await ft.dropindex(glide_client, index_name) == OK + + # Create an index with multiple fields with Hash data type for byte type testing + index_name_for_bytes_type_input = str(uuid.uuid4()) + assert ( + await ft.create( + glide_client, + index_name_for_bytes_type_input, + fields, + FtCreateOptions(DataType.HASH, prefixes), + ) + == OK + ) + + # Drop the index. Expects "OK" as a response. + assert ( + await ft.dropindex( + glide_client, index_name_for_bytes_type_input.encode("utf-8") + ) + == OK + ) + + # Drop a non existent index. Expects a RequestError. + with pytest.raises(RequestError): + await ft.dropindex(glide_client, index_name) + + @pytest.mark.parametrize("cluster_mode", [True]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_ft_search(self, glide_client: GlideClusterClient): + prefix = "{json-search-" + str(uuid.uuid4()) + "}:" + json_key1 = prefix + str(uuid.uuid4()) + json_key2 = prefix + str(uuid.uuid4()) + json_value1 = {"a": 11111, "b": 2, "c": 3} + json_value2 = {"a": 22222, "b": 2, "c": 3} + index = prefix + str(uuid.uuid4()) + + # Create an index. + assert ( + await ft.create( + glide_client, + index, + schema=[ + NumericField("$.a", "a"), + NumericField("$.b", "b"), + ], + options=FtCreateOptions(DataType.JSON), + ) + == OK + ) + + # Create a json key. + assert ( + await GlideJson.set(glide_client, json_key1, "$", json.dumps(json_value1)) + == OK + ) + assert ( + await GlideJson.set(glide_client, json_key2, "$", json.dumps(json_value2)) + == OK + ) + + # Wait for index to be updated to avoid this error - ResponseError: The index is under construction. + time.sleep(self.sleep_wait_time) + + ft_search_options = FtSearchOptions( + return_fields=[ + ReturnField(field_identifier="a", alias="a_new"), + ReturnField(field_identifier="b", alias="b_new"), + ] + ) + + # Search the index for string inputs. + result1 = await ft.search(glide_client, index, "*", options=ft_search_options) + # Check if we get the expected result from ft.search for string inputs. + TestFt._ft_search_deep_compare_result( + self, + result=result1, + json_key1=json_key1, + json_key2=json_key2, + json_value1=json_value1, + json_value2=json_value2, + fieldName1="a", + fieldName2="b", + ) + + # Test FT.PROFILE for the above mentioned FT.SEARCH query and search options. + + ft_profile_result = await ft.profile( + glide_client, + index, + FtProfileOptions.from_query_options( + query="*", query_options=ft_search_options + ), + ) + assert len(ft_profile_result) > 0 + + # Check if we get the expected result from FT.PROFILE for string inputs. + TestFt._ft_search_deep_compare_result( + self, + result=cast(FtSearchResponse, ft_profile_result[0]), + json_key1=json_key1, + json_key2=json_key2, + json_value1=json_value1, + json_value2=json_value2, + fieldName1="a", + fieldName2="b", + ) + ft_search_options_bytes_input = FtSearchOptions( + return_fields=[ + ReturnField(field_identifier=b"a", alias=b"a_new"), + ReturnField(field_identifier=b"b", alias=b"b_new"), + ] + ) + + # Search the index for byte type inputs. + result2 = await ft.search( + glide_client, + index.encode("utf-8"), + b"*", + options=ft_search_options_bytes_input, + ) + + # Check if we get the expected result from ft.search for byte type inputs. + TestFt._ft_search_deep_compare_result( + self, + result=result2, + json_key1=json_key1, + json_key2=json_key2, + json_value1=json_value1, + json_value2=json_value2, + fieldName1="a", + fieldName2="b", + ) + + # Test FT.PROFILE for the above mentioned FT.SEARCH query and search options for byte type inputs. + ft_profile_result = await ft.profile( + glide_client, + index.encode("utf-8"), + FtProfileOptions.from_query_options( + query=b"*", query_options=ft_search_options_bytes_input + ), + ) + assert len(ft_profile_result) > 0 + + # Check if we get the expected result from FT.PROFILE for byte type inputs. + TestFt._ft_search_deep_compare_result( + self, + result=cast(FtSearchResponse, ft_profile_result[0]), + json_key1=json_key1, + json_key2=json_key2, + json_value1=json_value1, + json_value2=json_value2, + fieldName1="a", + fieldName2="b", + ) + + assert await ft.dropindex(glide_client, index) == OK + + def _ft_search_deep_compare_result( + self, + result: List[Union[int, Mapping[TEncodable, Mapping[TEncodable, TEncodable]]]], + json_key1: str, + json_key2: str, + json_value1: dict, + json_value2: dict, + fieldName1: str, + fieldName2: str, + ): + """ + Deep compare the keys and values in FT.SEARCH result array. + + Args: + result (List[Union[int, Mapping[TEncodable, Mapping[TEncodable, TEncodable]]]]): + json_key1 (str): The first key in search result. + json_key2 (str): The second key in the search result. + json_value1 (dict): The fields map for first key in the search result. + json_value2 (dict): The fields map for second key in the search result. + """ + assert len(result) == 2 + assert result[0] == 2 + search_result_map: Mapping[TEncodable, Mapping[TEncodable, TEncodable]] = cast( + Mapping[TEncodable, Mapping[TEncodable, TEncodable]], result[1] + ) + expected_result_map: Mapping[TEncodable, Mapping[TEncodable, TEncodable]] = { + json_key1.encode(): { + fieldName1.encode(): str(json_value1.get(fieldName1)).encode(), + fieldName2.encode(): str(json_value1.get(fieldName2)).encode(), + }, + json_key2.encode(): { + fieldName1.encode(): str(json_value2.get(fieldName1)).encode(), + fieldName2.encode(): str(json_value2.get(fieldName2)).encode(), + }, + } + assert search_result_map == expected_result_map + @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ft_aliasadd(self, glide_client: GlideClusterClient): - indexName: str = str(uuid.uuid4()) + index_name: str = str(uuid.uuid4()) alias: str = "alias" # Test ft.aliasadd throws an error if index does not exist. with pytest.raises(RequestError): - await ft.aliasadd(glide_client, alias, indexName) + await ft.aliasadd(glide_client, alias, index_name) # Test ft.aliasadd successfully adds an alias to an existing index. - await TestFt._create_test_index_hash_type(self, glide_client, indexName) - assert await ft.aliasadd(glide_client, alias, indexName) == OK - assert await ft.dropindex(glide_client, indexName=indexName) == OK + await TestFt._create_test_index_hash_type(self, glide_client, index_name) + assert await ft.aliasadd(glide_client, alias, index_name) == OK + assert await ft.dropindex(glide_client, index_name) == OK # Test ft.aliasadd for input of bytes type. - indexNameString = str(uuid.uuid4()) - indexNameBytes = bytes(indexNameString, "utf-8") - aliasNameBytes = b"alias-bytes" - await TestFt._create_test_index_hash_type(self, glide_client, indexNameString) - assert await ft.aliasadd(glide_client, aliasNameBytes, indexNameBytes) == OK - assert await ft.dropindex(glide_client, indexName=indexNameString) == OK + index_name_string = str(uuid.uuid4()) + index_names_bytes = index_name_string.encode("utf-8") + alias_name_bytes = b"alias-bytes" + await TestFt._create_test_index_hash_type(self, glide_client, index_name_string) + assert ( + await ft.aliasadd(glide_client, alias_name_bytes, index_names_bytes) == OK + ) + assert await ft.dropindex(glide_client, index_name_string) == OK @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ft_aliasdel(self, glide_client: GlideClusterClient): - indexName: TEncodable = str(uuid.uuid4()) + index_name: TEncodable = str(uuid.uuid4()) alias: str = "alias" - await TestFt._create_test_index_hash_type(self, glide_client, indexName) + await TestFt._create_test_index_hash_type(self, glide_client, index_name) # Test if deleting a non existent alias throws an error. with pytest.raises(RequestError): await ft.aliasdel(glide_client, alias) # Test if an existing alias is deleted successfully. - assert await ft.aliasadd(glide_client, alias, indexName) == OK + assert await ft.aliasadd(glide_client, alias, index_name) == OK assert await ft.aliasdel(glide_client, alias) == OK # Test if an existing alias is deleted successfully for bytes type input. - assert await ft.aliasadd(glide_client, alias, indexName) == OK - assert await ft.aliasdel(glide_client, bytes(alias, "utf-8")) == OK + assert await ft.aliasadd(glide_client, alias, index_name) == OK + assert await ft.aliasdel(glide_client, alias.encode("utf-8")) == OK - assert await ft.dropindex(glide_client, indexName=indexName) == OK + assert await ft.dropindex(glide_client, index_name) == OK @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ft_aliasupdate(self, glide_client: GlideClusterClient): - indexName: str = str(uuid.uuid4()) + index_name: str = str(uuid.uuid4()) alias: str = "alias" - await TestFt._create_test_index_hash_type(self, glide_client, indexName) - assert await ft.aliasadd(glide_client, alias, indexName) == OK - newAliasName: str = "newAlias" - newIndexName: str = str(uuid.uuid4()) + await TestFt._create_test_index_hash_type(self, glide_client, index_name) + assert await ft.aliasadd(glide_client, alias, index_name) == OK + new_alias_name: str = "newAlias" + new_index_name: str = str(uuid.uuid4()) - await TestFt._create_test_index_hash_type(self, glide_client, newIndexName) - assert await ft.aliasadd(glide_client, newAliasName, newIndexName) == OK + await TestFt._create_test_index_hash_type(self, glide_client, new_index_name) + assert await ft.aliasadd(glide_client, new_alias_name, new_index_name) == OK # Test if updating an already existing alias to point to an existing index returns "OK". - assert await ft.aliasupdate(glide_client, newAliasName, indexName) == OK + assert await ft.aliasupdate(glide_client, new_alias_name, index_name) == OK assert ( await ft.aliasupdate( - glide_client, bytes(alias, "utf-8"), bytes(newIndexName, "utf-8") + glide_client, alias.encode("utf-8"), new_index_name.encode("utf-8") ) == OK ) - assert await ft.dropindex(glide_client, indexName=indexName) == OK - assert await ft.dropindex(glide_client, indexName=newIndexName) == OK + assert await ft.dropindex(glide_client, index_name) == OK + assert await ft.dropindex(glide_client, new_index_name) == OK async def _create_test_index_hash_type( self, glide_client: GlideClusterClient, index_name: TEncodable ): # Helper function used for creating a basic index with hash data type with one text field. - fields: List[Field] = [] - text_field_title: TextField = TextField("title") - fields.append(text_field_title) - + fields: List[Field] = [TextField("title")] prefix = "{hash-search-" + str(uuid.uuid4()) + "}:" - prefixes: List[TEncodable] = [] - prefixes.append(prefix) - + prefixes: List[TEncodable] = [prefix] result = await ft.create( glide_client, index_name, fields, FtCreateOptions(DataType.HASH, prefixes) ) @@ -141,14 +466,29 @@ async def _create_test_index_hash_type( @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ft_info(self, glide_client: GlideClusterClient): - indexName = str(uuid.uuid4()) + index_name = str(uuid.uuid4()) + await TestFt._create_test_index_with_vector_field( + self, glide_client, index_name + ) + result = await ft.info(glide_client, index_name) + assert await ft.dropindex(glide_client, index_name) == OK + TestFt._ft_info_deep_compare_result(self, index_name, result) + + # Test for bytes type input. + index_name_for_bytes_input = str(uuid.uuid4()) await TestFt._create_test_index_with_vector_field( - self, glide_client=glide_client, index_name=indexName + self, glide_client, index_name_for_bytes_input ) - result = await ft.info(glide_client, indexName) - assert await ft.dropindex(glide_client, indexName=indexName) == OK + result = await ft.info(glide_client, index_name_for_bytes_input.encode("utf-8")) + assert await ft.dropindex(glide_client, index_name_for_bytes_input) == OK + TestFt._ft_info_deep_compare_result(self, index_name_for_bytes_input, result) + + # Querying a missing index throws an error. + with pytest.raises(RequestError): + await ft.info(glide_client, str(uuid.uuid4())) - assert indexName.encode() == result.get(b"index_name") + def _ft_info_deep_compare_result(self, index_name: str, result): + assert index_name.encode() == result.get(b"index_name") assert b"JSON" == result.get(b"key_type") assert [b"key-prefix"] == result.get(b"key_prefixes") @@ -157,65 +497,58 @@ async def test_ft_info(self, glide_client: GlideClusterClient): TestFt.SerchResultFieldsList, result.get(b"fields") ) assert len(fields) == 2 - textField: TestFt.SearchResultField = {} - vectorField: TestFt.SearchResultField = {} + text_field: TestFt.SearchResultField = {} + vector_field: TestFt.SearchResultField = {} if fields[0].get(b"type") == b"VECTOR": - vectorField = cast(TestFt.SearchResultField, fields[0]) - textField = cast(TestFt.SearchResultField, fields[1]) + vector_field = cast(TestFt.SearchResultField, fields[0]) + text_field = cast(TestFt.SearchResultField, fields[1]) else: - vectorField = cast(TestFt.SearchResultField, fields[1]) - textField = cast(TestFt.SearchResultField, fields[0]) + vector_field = cast(TestFt.SearchResultField, fields[1]) + text_field = cast(TestFt.SearchResultField, fields[0]) # Compare vector field arguments - assert b"$.vec" == vectorField.get(b"identifier") - assert b"VECTOR" == vectorField.get(b"type") - assert b"VEC" == vectorField.get(b"field_name") - vectorFieldParams: Mapping[TEncodable, Union[TEncodable, int]] = cast( + assert b"$.vec" == vector_field.get(b"identifier") + assert b"VECTOR" == vector_field.get(b"type") + assert b"VEC" == vector_field.get(b"field_name") + vector_field_params: Mapping[TEncodable, Union[TEncodable, int]] = cast( Mapping[TEncodable, Union[TEncodable, int]], - vectorField.get(b"vector_params"), + vector_field.get(b"vector_params"), ) - assert DistanceMetricType.L2.value.encode() == vectorFieldParams.get( + assert DistanceMetricType.L2.value.encode() == vector_field_params.get( b"distance_metric" ) - assert 2 == vectorFieldParams.get(b"dimension") - assert b"HNSW" == vectorFieldParams.get(b"algorithm") - assert b"FLOAT32" == vectorFieldParams.get(b"data_type") + assert 2 == vector_field_params.get(b"dimension") + assert b"HNSW" == vector_field_params.get(b"algorithm") + assert b"FLOAT32" == vector_field_params.get(b"data_type") # Compare text field arguments. - assert b"$.text-field" == textField.get(b"identifier") - assert b"TEXT" == textField.get(b"type") - assert b"text-field" == textField.get(b"field_name") - - # Querying a missing index throws an error. - with pytest.raises(RequestError): - await ft.info(glide_client, str(uuid.uuid4())) + assert b"$.text-field" == text_field.get(b"identifier") + assert b"TEXT" == text_field.get(b"type") + assert b"text-field" == text_field.get(b"field_name") async def _create_test_index_with_vector_field( self, glide_client: GlideClusterClient, index_name: TEncodable ): # Helper function used for creating an index with JSON data type with a text and vector field. - fields: List[Field] = [] - textField: Field = TextField("$.text-field", "text-field") - - vectorFieldHash: VectorField = VectorField( - name="$.vec", - algorithm=VectorAlgorithm.HNSW, - attributes=VectorFieldAttributesHnsw( - dimensions=2, - distance_metric=DistanceMetricType.L2, - type=VectorType.FLOAT32, + fields: List[Field] = [ + VectorField( + name="$.vec", + algorithm=VectorAlgorithm.HNSW, + attributes=VectorFieldAttributesHnsw( + dimensions=2, + distance_metric=DistanceMetricType.L2, + type=VectorType.FLOAT32, + ), + alias="VEC", ), - alias="VEC", - ) - fields.append(vectorFieldHash) - fields.append(textField) + TextField("$.text-field", "text-field"), + ] - prefixes: List[TEncodable] = [] - prefixes.append("key-prefix") + prefixes: List[TEncodable] = ["key-prefix"] await ft.create( glide_client, - indexName=index_name, + index_name, schema=fields, options=FtCreateOptions(DataType.JSON, prefixes=prefixes), ) @@ -223,76 +556,76 @@ async def _create_test_index_with_vector_field( @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ft_explain(self, glide_client: GlideClusterClient): - indexName = str(uuid.uuid4()) + index_name = str(uuid.uuid4()) await TestFt._create_test_index_for_ft_explain_commands( - self=self, glide_client=glide_client, index_name=indexName + self, glide_client, index_name ) # FT.EXPLAIN on a search query containing numeric field. query = "@price:[0 10]" - result = await ft.explain(glide_client, indexName=indexName, query=query) - resultString = cast(bytes, result).decode(encoding="utf-8") - assert "price" in resultString and "0" in resultString and "10" in resultString + result = await ft.explain(glide_client, index_name, query) + result_string = cast(bytes, result).decode(encoding="utf-8") + assert ( + "price" in result_string and "0" in result_string and "10" in result_string + ) # FT.EXPLAIN on a search query containing numeric field and having bytes type input to the command. - result = await ft.explain( - glide_client, indexName=indexName.encode(), query=query.encode() + result = await ft.explain(glide_client, index_name.encode(), query.encode()) + result_string = cast(bytes, result).decode(encoding="utf-8") + assert ( + "price" in result_string and "0" in result_string and "10" in result_string ) - resultString = cast(bytes, result).decode(encoding="utf-8") - assert "price" in resultString and "0" in resultString and "10" in resultString # FT.EXPLAIN on a search query that returns all data. - result = await ft.explain(glide_client, indexName=indexName, query="*") - resultString = cast(bytes, result).decode(encoding="utf-8") - assert "*" in resultString + result = await ft.explain(glide_client, index_name, query="*") + result_string = cast(bytes, result).decode(encoding="utf-8") + assert "*" in result_string - assert await ft.dropindex(glide_client, indexName=indexName) + assert await ft.dropindex(glide_client, index_name) # FT.EXPLAIN on a missing index throws an error. with pytest.raises(RequestError): - await ft.explain(glide_client, str(uuid.uuid4()), "*") + await ft.explain(glide_client, str(uuid.uuid4()), query="*") @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ft_explaincli(self, glide_client: GlideClusterClient): - indexName = str(uuid.uuid4()) + index_name = str(uuid.uuid4()) await TestFt._create_test_index_for_ft_explain_commands( - self=self, glide_client=glide_client, index_name=indexName + self, glide_client, index_name ) # FT.EXPLAINCLI on a search query containing numeric field. query = "@price:[0 10]" - result = await ft.explaincli(glide_client, indexName=indexName, query=query) - resultStringArr = [] + result = await ft.explaincli(glide_client, index_name, query) + result_string_arr = [] for i in result: - resultStringArr.append(cast(bytes, i).decode(encoding="utf-8").strip()) + result_string_arr.append(cast(bytes, i).decode(encoding="utf-8").strip()) assert ( - "price" in resultStringArr - and "0" in resultStringArr - and "10" in resultStringArr + "price" in result_string_arr + and "0" in result_string_arr + and "10" in result_string_arr ) # FT.EXPLAINCLI on a search query containing numeric field and having bytes type input to the command. - result = await ft.explaincli( - glide_client, indexName=indexName.encode(), query=query.encode() - ) - resultStringArr = [] + result = await ft.explaincli(glide_client, index_name.encode(), query.encode()) + result_string_arr = [] for i in result: - resultStringArr.append(cast(bytes, i).decode(encoding="utf-8").strip()) + result_string_arr.append(cast(bytes, i).decode(encoding="utf-8").strip()) assert ( - "price" in resultStringArr - and "0" in resultStringArr - and "10" in resultStringArr + "price" in result_string_arr + and "0" in result_string_arr + and "10" in result_string_arr ) # FT.EXPLAINCLI on a search query that returns all data. - result = await ft.explaincli(glide_client, indexName=indexName, query="*") - resultStringArr = [] + result = await ft.explaincli(glide_client, index_name, query="*") + result_string_arr = [] for i in result: - resultStringArr.append(cast(bytes, i).decode(encoding="utf-8").strip()) - assert "*" in resultStringArr + result_string_arr.append(cast(bytes, i).decode(encoding="utf-8").strip()) + assert "*" in result_string_arr - assert await ft.dropindex(glide_client, indexName=indexName) + assert await ft.dropindex(glide_client, index_name) # FT.EXPLAINCLI on a missing index throws an error. with pytest.raises(RequestError): @@ -302,15 +635,9 @@ async def _create_test_index_for_ft_explain_commands( self, glide_client: GlideClusterClient, index_name: TEncodable ): # Helper function used for creating an index having hash data type, one text field and one numeric field. - fields: List[Field] = [] - numeric_field: NumericField = NumericField("price") - text_field: TextField = TextField("title") - fields.append(text_field) - fields.append(numeric_field) - + fields: List[Field] = [TextField("title"), NumericField("price")] prefix = "{hash-search-" + str(uuid.uuid4()) + "}:" - prefixes: List[TEncodable] = [] - prefixes.append(prefix) + prefixes: List[TEncodable] = [prefix] assert ( await ft.create( @@ -327,20 +654,20 @@ async def _create_test_index_for_ft_explain_commands( async def test_ft_aggregate_with_bicycles_data( self, glide_client: GlideClusterClient, protocol ): - prefixBicycles = "{bicycles}:" - indexBicycles = prefixBicycles + str(uuid.uuid4()) + prefix_bicycles = "{bicycles}:" + index_bicycles = prefix_bicycles + str(uuid.uuid4()) await TestFt._create_index_for_ft_aggregate_with_bicycles_data( - self=self, - glide_client=glide_client, - index_name=indexBicycles, - prefix=prefixBicycles, + self, + glide_client, + index_bicycles, + prefix_bicycles, ) await TestFt._create_json_keys_for_ft_aggregate_with_bicycles_data( - self=self, glide_client=glide_client, prefix=prefixBicycles + self, glide_client, prefix_bicycles ) time.sleep(self.sleep_wait_time) - ftAggregateOptions: FtAggregateOptions = FtAggregateOptions( + ft_aggregate_options: FtAggregateOptions = FtAggregateOptions( loadFields=["__key"], clauses=[ FtAggregateGroupBy( @@ -352,12 +679,12 @@ async def test_ft_aggregate_with_bicycles_data( # Run FT.AGGREGATE command with the following arguments: ['FT.AGGREGATE', '{bicycles}:1e15faab-a870-488e-b6cd-f2b76c6916a3', '*', 'LOAD', '1', '__key', 'GROUPBY', '1', '@condition', 'REDUCE', 'COUNT', '0', 'AS', 'bicycles'] result = await ft.aggregate( glide_client, - indexName=indexBicycles, + index_bicycles, query="*", - options=ftAggregateOptions, + options=ft_aggregate_options, ) - sortedResult = sorted(result, key=lambda x: (x[b"condition"], x[b"bicycles"])) - expectedResult = sorted( + sorted_result = sorted(result, key=lambda x: (x[b"condition"], x[b"bicycles"])) + expected_result = sorted( [ { b"condition": b"refurbished", @@ -374,75 +701,77 @@ async def test_ft_aggregate_with_bicycles_data( ], key=lambda x: (x[b"condition"], x[b"bicycles"]), ) - assert sortedResult == expectedResult + assert sorted_result == expected_result # Test FT.PROFILE for the above mentioned FT.AGGREGATE query - ftProfileResult = await ft.profile( + ft_profile_result = await ft.profile( glide_client, - indexBicycles, + index_bicycles, FtProfileOptions.from_query_options( - query="*", queryOptions=ftAggregateOptions + query="*", query_options=ft_aggregate_options ), ) - assert len(ftProfileResult) > 0 + assert len(ft_profile_result) > 0 assert ( - sorted(ftProfileResult[0], key=lambda x: (x[b"condition"], x[b"bicycles"])) - == expectedResult + sorted( + ft_profile_result[0], key=lambda x: (x[b"condition"], x[b"bicycles"]) + ) + == expected_result ) - assert await ft.dropindex(glide_client, indexName=indexBicycles) == OK + assert await ft.dropindex(glide_client, index_bicycles) == OK @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ft_aggregate_with_movies_data( self, glide_client: GlideClusterClient, protocol ): - prefixMovies = "{movies}:" - indexMovies = prefixMovies + str(uuid.uuid4()) + prefix_movies = "{movies}:" + index_movies = prefix_movies + str(uuid.uuid4()) # Create index for movies data. await TestFt._create_index_for_ft_aggregate_with_movies_data( - self=self, - glide_client=glide_client, - index_name=indexMovies, - prefix=prefixMovies, + self, + glide_client, + index_movies, + prefix_movies, ) # Set JSON keys with movies data. await TestFt._create_hash_keys_for_ft_aggregate_with_movies_data( - self=self, glide_client=glide_client, prefix=prefixMovies + self, glide_client, prefix_movies ) # Wait for index to be updated. time.sleep(self.sleep_wait_time) # Run FT.AGGREGATE command with the following arguments: # ['FT.AGGREGATE', '{movies}:5a0e6257-3488-4514-96f2-f4c80f6cb0a9', '*', 'LOAD', '*', 'APPLY', 'ceil(@rating)', 'AS', 'r_rating', 'GROUPBY', '1', '@genre', 'REDUCE', 'COUNT', '0', 'AS', 'nb_of_movies', 'REDUCE', 'SUM', '1', 'votes', 'AS', 'nb_of_votes', 'REDUCE', 'AVG', '1', 'r_rating', 'AS', 'avg_rating', 'SORTBY', '4', '@avg_rating', 'DESC', '@nb_of_votes', 'DESC'] - - ftAggregateOptions: FtAggregateOptions = FtAggregateOptions( + # Testing for bytes type input. + ft_aggregate_options: FtAggregateOptions = FtAggregateOptions( loadAll=True, clauses=[ - FtAggregateApply(expression="ceil(@rating)", name="r_rating"), + FtAggregateApply(expression=b"ceil(@rating)", name=b"r_rating"), FtAggregateGroupBy( - ["@genre"], + [b"@genre"], [ - FtAggregateReducer("COUNT", [], "nb_of_movies"), - FtAggregateReducer("SUM", ["votes"], "nb_of_votes"), - FtAggregateReducer("AVG", ["r_rating"], "avg_rating"), + FtAggregateReducer(b"COUNT", [], b"nb_of_movies"), + FtAggregateReducer(b"SUM", [b"votes"], b"nb_of_votes"), + FtAggregateReducer(b"AVG", [b"r_rating"], b"avg_rating"), ], ), FtAggregateSortBy( properties=[ - FtAggregateSortProperty("@avg_rating", OrderBy.DESC), - FtAggregateSortProperty("@nb_of_votes", OrderBy.DESC), + FtAggregateSortProperty(b"@avg_rating", OrderBy.DESC), + FtAggregateSortProperty(b"@nb_of_votes", OrderBy.DESC), ] ), ], ) result = await ft.aggregate( glide_client, - indexName=indexMovies, - query="*", - options=ftAggregateOptions, + index_name=index_movies.encode("utf-8"), + query=b"*", + options=ft_aggregate_options, ) - sortedResult = sorted( + sorted_result = sorted( result, key=lambda x: ( x[b"genre"], @@ -451,7 +780,7 @@ async def test_ft_aggregate_with_movies_data( x[b"avg_rating"], ), ) - expectedResultSet = sorted( + expected_result = sorted( [ { b"genre": b"Drama", @@ -493,20 +822,20 @@ async def test_ft_aggregate_with_movies_data( x[b"avg_rating"], ), ) - assert expectedResultSet == sortedResult + assert expected_result == sorted_result # Test FT.PROFILE for the above mentioned FT.AGGREGATE query - ftProfileResult = await ft.profile( + ft_profile_result = await ft.profile( glide_client, - indexMovies, + index_movies, FtProfileOptions.from_query_options( - query="*", queryOptions=ftAggregateOptions + query="*", query_options=ft_aggregate_options ), ) - assert len(ftProfileResult) > 0 + assert len(ft_profile_result) > 0 assert ( sorted( - ftProfileResult[0], + ft_profile_result[0], key=lambda x: ( x[b"genre"], x[b"nb_of_movies"], @@ -514,10 +843,10 @@ async def test_ft_aggregate_with_movies_data( x[b"avg_rating"], ), ) - == expectedResultSet + == expected_result ) - assert await ft.dropindex(glide_client, indexName=indexMovies) == OK + assert await ft.dropindex(glide_client, index_movies) == OK async def _create_index_for_ft_aggregate_with_bicycles_data( self, glide_client: GlideClusterClient, index_name: TEncodable, prefix @@ -560,11 +889,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "1", ".", - '{"brand": "Bicyk", "model": "Hillcraft", "price": 1200, "description":' - + ' "Kids want to ride with as little weight as possible. Especially on an' - + ' incline! They may be at the age when a 27.5\\" wheel bike is just too clumsy' - + ' coming off a 24\\" bike. The Hillcraft 26 is just the solution they need!",' - + ' "condition": "used"}', + '{"brand": "Bicyk", "model": "Hillcraft", "price": 1200, "condition": "used"}', ) == OK ) @@ -574,12 +899,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "2", ".", - '{"brand": "Nord", "model": "Chook air 5", "price": 815, "description":' - + ' "The Chook Air 5 gives kids aged six years and older a durable and' - + " uberlight mountain bike for their first experience on tracks and easy" - + " cruising through forests and fields. The lower top tube makes it easy to" - + " mount and dismount in any situation, giving your kids greater safety on the" - + ' trails.", "condition": "used"}', + '{"brand": "Nord", "model": "Chook air 5", "price": 815, "condition": "used"}', ) == OK ) @@ -589,14 +909,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "3", ".", - '{"brand": "Eva", "model": "Eva 291", "price": 3400, "description": "The' - + " sister company to Nord, Eva launched in 2005 as the first and only" - + " women-dedicated bicycle brand. Designed by women for women, allEva bikes are" - + " optimized for the feminine physique using analytics from a body metrics" - + " database. If you like 29ers, try the Eva 291. It\\u2019s a brand new bike for" - + " 2022.. This full-suspension, cross-country ride has been designed for" - + " velocity. The 291 has 100mm of front and rear travel, a superlight aluminum" - + ' frame and fast-rolling 29-inch wheels. Yippee!", "condition": "used"}', + '{"brand": "Eva", "model": "Eva 291", "price": 3400, "condition": "used"}', ) == OK ) @@ -606,12 +919,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "4", ".", - '{"brand": "Noka Bikes", "model": "Kahuna", "price": 3200, "description":' - + ' "Whether you want to try your hand at XC racing or are looking for a lively' - + " trail bike that's just as inspiring on the climbs as it is over rougher" - + " ground, the Wilder is one heck of a bike built specifically for short women." - + " Both the frames and components have been tweaked to include a women\\u2019s" - + ' saddle, different bars and unique colourway.", "condition": "used"}', + '{"brand": "Noka Bikes", "model": "Kahuna", "price": 3200, "condition": "used"}', ) == OK ) @@ -621,12 +929,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "5", ".", - '{"brand": "Breakout", "model": "XBN 2.1 Alloy", "price": 810,' - + ' "description": "The XBN 2.1 Alloy is our entry-level road bike \\u2013 but' - + " that\\u2019s not to say that it\\u2019s a basic machine. With an internal" - + " weld aluminium frame, a full carbon fork, and the slick-shifting Claris gears" - + " from Shimano\\u2019s, this is a bike which doesn\\u2019t break the bank and" - + ' delivers craved performance.", "condition": "new"}', + '{"brand": "Breakout", "model": "XBN 2.1 Alloy", "price": 810, "condition": "new"}', ) == OK ) @@ -636,13 +939,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "6", ".", - '{"brand": "ScramBikes", "model": "WattBike", "price": 2300,' - + ' "description": "The WattBike is the best e-bike for people who still feel' - + " young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH" - + " Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on" - + " one charge. It\\u2019s great for tackling hilly terrain or if you just fancy" - + " a more leisurely ride. With three working modes, you can choose between" - + ' E-bike, assisted bicycle, and normal bike modes.", "condition": "new"}', + '{"brand": "ScramBikes", "model": "WattBike", "price": 2300, "condition": "new"}', ) == OK ) @@ -652,20 +949,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "7", ".", - '{"brand": "Peaknetic", "model": "Secto", "price": 430, "description":' - + ' "If you struggle with stiff fingers or a kinked neck or back after a few' - " minutes on the road, this lightweight, aluminum bike alleviates those issues" - " and allows you to enjoy the ride. From the ergonomic grips to the" - " lumbar-supporting seat position, the Roll Low-Entry offers incredible" - " comfort. The rear-inclined seat tube facilitates stability by allowing you to" - " put a foot on the ground to balance at a stop, and the low step-over frame" - " makes it accessible for all ability and mobility levels. The saddle is very" - " soft, with a wide back to support your hip joints and a cutout in the center" - " to redistribute that pressure. Rim brakes deliver satisfactory braking" - " control, and the wide tires provide a smooth, stable ride on paved roads and" - " gravel. Rack and fender mounts facilitate setting up the Roll Low-Entry as" - " your preferred commuter, and the BMX-like handlebar offers space for mounting" - ' a flashlight, bell, or phone holder.", "condition": "new"}', + '{"brand": "Peaknetic", "model": "Secto", "price": 430, "condition": "new"}', ) == OK ) @@ -675,16 +959,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "8", ".", - '{"brand": "nHill", "model": "Summit", "price": 1200, "description":' - + ' "This budget mountain bike from nHill performs well both on bike paths and' - + " on the trail. The fork with 100mm of travel absorbs rough terrain. Fat Kenda" - + " Booster tires give you grip in corners and on wet trails. The Shimano Tourney" - + " drivetrain offered enough gears for finding a comfortable pace to ride" - + " uphill, and the Tektro hydraulic disc brakes break smoothly. Whether you want" - + " an affordable bike that you can take to work, but also take trail in" - + " mountains on the weekends or you\\u2019re just after a stable, comfortable" - + ' ride for the bike path, the Summit gives a good value for money.",' - + ' "condition": "new"}', + '{"brand": "nHill", "model": "Summit", "price": 1200, "condition": "new"}', ) == OK ) @@ -694,14 +969,7 @@ async def _create_json_keys_for_ft_aggregate_with_bicycles_data( glide_client, prefix + "9", ".", - '{"model": "ThrillCycle", "brand": "BikeShind", "price": 815,' - + ' "description": "An artsy, retro-inspired bicycle that\\u2019s as' - + " functional as it is pretty: The ThrillCycle steel frame offers a smooth ride." - + " A 9-speed drivetrain has enough gears for coasting in the city, but we" - + " wouldn\\u2019t suggest taking it to the mountains. Fenders protect you from" - + " mud, and a rear basket lets you transport groceries, flowers and books. The" - + " ThrillCycle comes with a limited lifetime warranty, so this little guy will" - + ' last you long past graduation.", "condition": "refurbished"}', + '{"model": "ThrillCycle", "brand": "BikeShind", "price": 815, "condition": "refurbished"}', ) == OK ) @@ -733,10 +1001,6 @@ async def _create_hash_keys_for_ft_aggregate_with_movies_data( prefix + "11002", { "title": "Star Wars: Episode V - The Empire Strikes Back", - "plot": "After the Rebels are brutally overpowered by the Empire on the ice planet Hoth," - + " Luke Skywalker begins Jedi training with Yoda, while his friends are" - + " pursued by Darth Vader and a bounty hunter named Boba Fett all over the" - + " galaxy.", "release_year": "1980", "genre": "Action", "rating": "8.7", @@ -749,8 +1013,6 @@ async def _create_hash_keys_for_ft_aggregate_with_movies_data( prefix + "11003", { "title": "The Godfather", - "plot": "The aging patriarch of an organized crime dynasty transfers control of his" - + " clandestine empire to his reluctant son.", "release_year": "1972", "genre": "Drama", "rating": "9.2", @@ -763,8 +1025,6 @@ async def _create_hash_keys_for_ft_aggregate_with_movies_data( prefix + "11004", { "title": "Heat", - "plot": "A group of professional bank robbers start to feel the heat from police when they" - + " unknowingly leave a clue at their latest heist.", "release_year": "1995", "genre": "Thriller", "rating": "8.2", @@ -777,7 +1037,6 @@ async def _create_hash_keys_for_ft_aggregate_with_movies_data( prefix + "11005", { "title": "Star Wars: Episode VI - Return of the Jedi", - "plot": "The Rebels dispatch to Endor to destroy the second Empire's Death Star.", "release_year": "1983", "genre": "Action", "rating": "8.3",