From 23a180bb67019188318f6e3a28fe2d61fa9ccfe0 Mon Sep 17 00:00:00 2001 From: prateek-kumar-improving Date: Tue, 15 Oct 2024 11:33:00 -0700 Subject: [PATCH] Python FT.DROPINDEX command (#2437) * Python [FT.DROPINDEX] Added command --------- Signed-off-by: Prateek Kumar --- CHANGELOG.md | 1 + .../glide/async_commands/server_modules/ft.py | 30 ++++++- .../{ => ft_options}/ft_constants.py | 3 +- .../ft_options/ft_create_options.py | 78 +++++++++---------- .../{test_ft.py => search/test_ft_create.py} | 8 +- .../search/test_ft_dropindex.py | 44 +++++++++++ 6 files changed, 116 insertions(+), 48 deletions(-) rename python/python/glide/async_commands/server_modules/{ => ft_options}/ft_constants.py (87%) rename python/python/tests/tests_server_modules/{test_ft.py => search/test_ft_create.py} (95%) create mode 100644 python/python/tests/tests_server_modules/search/test_ft_dropindex.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bf7bb69d44..ae54d5022e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ #### Changes +* Python: Python FT.DROPINDEX command ([#2437](https://github.com/valkey-io/valkey-glide/pull/2437)) * Python: Python: Added FT.CREATE command([#2413](https://github.com/valkey-io/valkey-glide/pull/2413)) * Python: Add JSON.ARRLEN command ([#2403](https://github.com/valkey-io/valkey-glide/pull/2403)) * Python: Add JSON.CLEAR command ([#2418](https://github.com/valkey-io/valkey-glide/pull/2418)) diff --git a/python/python/glide/async_commands/server_modules/ft.py b/python/python/glide/async_commands/server_modules/ft.py index b7c764cd0f..74d75e8953 100644 --- a/python/python/glide/async_commands/server_modules/ft.py +++ b/python/python/glide/async_commands/server_modules/ft.py @@ -5,7 +5,7 @@ from typing import List, Optional, cast -from glide.async_commands.server_modules.ft_constants import ( +from glide.async_commands.server_modules.ft_options.ft_constants import ( CommandNames, FtCreateKeywords, ) @@ -30,10 +30,10 @@ async def create( client (TGlideClient): The client to execute the command. indexName (TEncodable): The index name for the index to be created schema (List[Field]): The fields of the index schema, specifying the fields and their types. - options (Optional[FtCreateOptions]): Optional arguments for the [FT.CREATE] command. + options (Optional[FtCreateOptions]): Optional arguments for the FT.CREATE command. See `FtCreateOptions`. Returns: - If the index is successfully created, returns "OK". + TOK: A simple "OK" response. Examples: >>> from glide.async_commands.server_modules import ft @@ -44,7 +44,7 @@ async def create( >>> prefixes.append("blog:post:") >>> index = "idx" >>> result = await ft.create(glide_client, index, schema, FtCreateOptions(DataType.HASH, prefixes)) - b'OK' # Indicates successful creation of index named 'idx' + 'OK' # Indicates successful creation of index named 'idx' """ args: List[TEncodable] = [CommandNames.FT_CREATE, indexName] if options: @@ -54,3 +54,25 @@ async def create( for field in schema: args.extend(field.toArgs()) return cast(TOK, await client.custom_command(args)) + + +async def dropindex(client: TGlideClient, indexName: 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. + + Returns: + TOK: A simple "OK" response. + + 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.async_commands.server_modules import ft + >>> indexName = "idx" + >>> result = await ft.dropindex(glide_client, indexName) + 'OK' # Indicates successful deletion/dropping of index named 'idx' + """ + args: List[TEncodable] = [CommandNames.FT_DROPINDEX, indexName] + return cast(TOK, await client.custom_command(args)) diff --git a/python/python/glide/async_commands/server_modules/ft_constants.py b/python/python/glide/async_commands/server_modules/ft_options/ft_constants.py similarity index 87% rename from python/python/glide/async_commands/server_modules/ft_constants.py rename to python/python/glide/async_commands/server_modules/ft_options/ft_constants.py index 3c48f5b67c..d1e8e524eb 100644 --- a/python/python/glide/async_commands/server_modules/ft_constants.py +++ b/python/python/glide/async_commands/server_modules/ft_options/ft_constants.py @@ -7,11 +7,12 @@ class CommandNames: """ FT_CREATE = "FT.CREATE" + FT_DROPINDEX = "FT.DROPINDEX" class FtCreateKeywords: """ - Keywords used in the [FT.CREATE] command statment. + Keywords used in the FT.CREATE command statment. """ SCHEMA = "SCHEMA" 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 d3db3dbe75..89ac1d760d 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 @@ -3,7 +3,7 @@ from enum import Enum from typing import List, Optional -from glide.async_commands.server_modules.ft_constants import FtCreateKeywords +from glide.async_commands.server_modules.ft_options.ft_constants import FtCreateKeywords from glide.constants import TEncodable @@ -85,15 +85,15 @@ def __init__( self, name: TEncodable, type: FieldType, - alias: Optional[str] = None, + alias: Optional[TEncodable] = None, ): """ Initialize a new field instance. Args: name (TEncodable): The name of the field. - type (FieldType): The type of the field. - alias (Optional[str]): An alias for the field. + type (FieldType): The type of the field. See `FieldType`. + alias (Optional[TEncodable]): An alias for the field. """ self.name = name self.type = type @@ -119,13 +119,13 @@ class TextField(Field): Class for defining text fields in a schema. """ - def __init__(self, name: TEncodable, alias: Optional[str] = None): + def __init__(self, name: TEncodable, alias: Optional[TEncodable] = None): """ Initialize a new TextField instance. Args: name (TEncodable): The name of the text field. - alias (Optional[str]): An alias for the field. + alias (Optional[TEncodable]): An alias for the field. """ super().__init__(name, FieldType.TEXT, alias) @@ -148,8 +148,8 @@ class TagField(Field): def __init__( self, name: TEncodable, - alias: Optional[str] = None, - separator: Optional[str] = None, + alias: Optional[TEncodable] = None, + separator: Optional[TEncodable] = None, case_sensitive: bool = False, ): """ @@ -157,8 +157,8 @@ def __init__( Args: name (TEncodable): The name of the tag field. - alias (Optional[str]): An alias for the field. - separator (Optional[str]): Specify how text in the attribute is split into individual tags. Must be a single character. + alias (Optional[TEncodable]): An alias for the field. + separator (Optional[TEncodable]): Specify how text in the attribute is split into individual tags. Must be a single character. case_sensitive (bool): Preserve the original letter cases of tags. If set to False, characters are converted to lowercase by default. """ super().__init__(name, FieldType.TAG, alias) @@ -185,13 +185,13 @@ class NumericField(Field): Class for defining the numeric fields in a schema. """ - def __init__(self, name: TEncodable, alias: Optional[str] = None): + def __init__(self, name: TEncodable, alias: Optional[TEncodable] = None): """ Initialize a new NumericField instance. Args: name (TEncodable): The name of the numeric field. - alias (Optional[str]): An alias for the field. + alias (Optional[TEncodable]): An alias for the field. """ super().__init__(name, FieldType.NUMERIC, alias) @@ -219,21 +219,21 @@ def __init__(self, dim: int, distance_metric: DistanceMetricType, type: VectorTy Args: dim (int): Number of dimensions in the vector. distance_metric (DistanceMetricType): The distance metric used in vector type field. Can be one of [L2 | IP | COSINE]. - type (VectorType): Vector type. The only supported type is FLOAT32. + type (VectorType): Vector type. The only supported type is FLOAT32. See `VectorType`. """ self.dim = dim self.distance_metric = distance_metric self.type = type @abstractmethod - def toArgs(self) -> List[str]: + def toArgs(self) -> List[TEncodable]: """ Get the arguments to be used for the algorithm of the vector field. Returns: - List[str]: A list of arguments. + List[TEncodable]: A list of arguments. """ - args = [] + args: List[TEncodable] = [] if self.dim: args.extend([FtCreateKeywords.DIM, str(self.dim)]) if self.distance_metric: @@ -260,19 +260,19 @@ def __init__( Args: dim (int): Number of dimensions in the vector. - distance_metric (DistanceMetricType): The distance metric used in vector type field. Can be one of [L2 | IP | COSINE]. - type (VectorType): Vector type. The only supported type is FLOAT32. + distance_metric (DistanceMetricType): The distance metric used in vector type field. Can be one of [L2 | IP | COSINE]. See `DistanceMetricType`. + type (VectorType): Vector type. The only supported type is FLOAT32. See `VectorType`. initial_cap (Optional[int]): Initial vector capacity in the index affecting memory allocation size of the index. Defaults to 1024. """ super().__init__(dim, distance_metric, type) self.initial_cap = initial_cap - def toArgs(self) -> List[str]: + def toArgs(self) -> List[TEncodable]: """ Get the arguments representing the vector field created with FLAT algorithm. Returns: - List[str]: A list of FLAT algorithm type vector arguments. + List[TEncodable]: A list of FLAT algorithm type vector arguments. """ args = super().toArgs() if self.initial_cap: @@ -300,8 +300,8 @@ def __init__( Args: dim (int): Number of dimensions in the vector. - distance_metric (DistanceMetricType): The distance metric used in vector type field. Can be one of [L2 | IP | COSINE]. - type (VectorType): Vector type. The only supported type is FLOAT32. + distance_metric (DistanceMetricType): The distance metric used in vector type field. Can be one of [L2 | IP | COSINE]. See `DistanceMetricType`. + type (VectorType): Vector type. The only supported type is FLOAT32. See `VectorType`. initial_cap (Optional[int]): Initial vector capacity in the index affecting memory allocation size of the index. Defaults to 1024. m (Optional[int]): Number of maximum allowed outgoing edges for each node in the graph in each layer. Default is 16, maximum is 512. ef_contruction (Optional[int]): Controls the number of vectors examined during index construction. Default value is 200, Maximum value is 4096. @@ -313,12 +313,12 @@ def __init__( self.ef_contruction = ef_contruction self.ef_runtime = ef_runtime - def toArgs(self) -> List[str]: + def toArgs(self) -> List[TEncodable]: """ Get the arguments representing the vector field created with HSNW algorithm. Returns: - List[str]: A list of HNSW algorithm type vector arguments. + List[TEncodable]: A list of HNSW algorithm type vector arguments. """ args = super().toArgs() if self.initial_cap: @@ -342,16 +342,16 @@ def __init__( name: TEncodable, algorithm: VectorAlgorithm, attributes: VectorFieldAttributes, - alias: Optional[str] = None, + alias: Optional[TEncodable] = None, ): """ Initialize a new VectorField instance. Args: name (TEncodable): The name of the vector field. - algorithm (VectorAlgorithm): The vector indexing algorithm. - alias (Optional[str]): An alias for the field. - attributes (VectorFieldAttributes): Additional attributes to be passed with the vector field after the algorithm name. + algorithm (VectorAlgorithm): The vector indexing algorithm. See `VectorAlgorithm`. + 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`. """ super().__init__(name, FieldType.VECTOR, alias) self.algorithm = algorithm @@ -390,34 +390,34 @@ class DataType(Enum): class FtCreateOptions: """ - This class represents the input options to be used in the [FT.CREATE] command. - All fields in this class are optional inputs for [FT.CREATE]. + This class represents the input options to be used in the FT.CREATE command. + All fields in this class are optional inputs for FT.CREATE. """ def __init__( self, data_type: Optional[DataType] = None, - prefixes: Optional[List[str]] = None, + prefixes: Optional[List[TEncodable]] = None, ): """ - Initialize the [FT.CREATE] optional fields. + Initialize the FT.CREATE optional fields. Args: - data_type (Optional[DataType]): The type of data to be indexed using [FT.CREATE]. - prefixes (Optional[List[str]]): The prefix of the key to be indexed. + data_type (Optional[DataType]): The type of data to be indexed using FT.CREATE. See `DataType`. + prefixes (Optional[List[TEncodable]]): The prefix of the key to be indexed. """ self.data_type = data_type self.prefixes = prefixes - def toArgs(self) -> List[str]: + def toArgs(self) -> List[TEncodable]: """ - Get the optional arguments for the [FT.CREATE] command. + Get the optional arguments for the FT.CREATE command. Returns: - List[str]: - List of [FT.CREATE] optional agruments. + List[TEncodable]: + List of FT.CREATE optional agruments. """ - args = [] + args: List[TEncodable] = [] if self.data_type: args.append(FtCreateKeywords.ON) args.append(self.data_type.value) diff --git a/python/python/tests/tests_server_modules/test_ft.py b/python/python/tests/tests_server_modules/search/test_ft_create.py similarity index 95% rename from python/python/tests/tests_server_modules/test_ft.py rename to python/python/tests/tests_server_modules/search/test_ft_create.py index 93f9efc9c1..c08346563b 100644 --- a/python/python/tests/tests_server_modules/test_ft.py +++ b/python/python/tests/tests_server_modules/search/test_ft_create.py @@ -17,15 +17,15 @@ VectorType, ) from glide.config import ProtocolVersion -from glide.constants import OK +from glide.constants import OK, TEncodable from glide.glide_client import GlideClusterClient @pytest.mark.asyncio -class TestVss: +class TestFtCreate: @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) - async def test_vss_create(self, glide_client: GlideClusterClient): + async def test_ft_create(self, glide_client: GlideClusterClient): fields: List[Field] = [] textFieldTitle: TextField = TextField("$title") numberField: NumericField = NumericField("$published_at") @@ -34,7 +34,7 @@ async def test_vss_create(self, glide_client: GlideClusterClient): fields.append(numberField) fields.append(textFieldCategory) - prefixes: List[str] = [] + prefixes: List[TEncodable] = [] prefixes.append("blog:post:") # Create an index with multiple fields with Hash data type. 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 new file mode 100644 index 0000000000..717df38eb8 --- /dev/null +++ b/python/python/tests/tests_server_modules/search/test_ft_dropindex.py @@ -0,0 +1,44 @@ +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)