From f6f1dd07e2d392c63586b1621be78b1c9cf0f953 Mon Sep 17 00:00:00 2001 From: Li Wan Date: Tue, 17 Sep 2024 10:42:43 +1000 Subject: [PATCH 1/5] Add fix for settings dict --- src/marqo/index.py | 1 - src/marqo/models/create_index_settings.py | 19 ++- src/marqo/models/search_models.py | 6 +- tests/marqo_test.py | 2 +- tests/v2_tests/test_create_index.py | 144 +++++++++++++++++----- 5 files changed, 128 insertions(+), 44 deletions(-) diff --git a/src/marqo/index.py b/src/marqo/index.py index 7f28b7e8..61866ff7 100644 --- a/src/marqo/index.py +++ b/src/marqo/index.py @@ -167,7 +167,6 @@ def create(config: Config, textChunkPrefix=text_chunk_prefix, textQueryPrefix=text_query_prefix, ) - return req.post(f"indexes/{index_name}", body=local_create_index_settings.generate_request_body()) # py-marqo against Marqo Cloud diff --git a/src/marqo/models/create_index_settings.py b/src/marqo/models/create_index_settings.py index a9fac9bb..b7479fa7 100644 --- a/src/marqo/models/create_index_settings.py +++ b/src/marqo/models/create_index_settings.py @@ -1,7 +1,8 @@ from typing import Dict, Any, Optional, List +from pydantic import model_validator + from marqo.models import marqo_index -from pydantic import root_validator, Field from marqo.models.marqo_models import MarqoBaseModel @@ -56,11 +57,17 @@ def generate_request_body(self) -> dict: if self.settingsDict is not None: return self.settingsDict else: - return self.dict(exclude_none=True, exclude={"settingsDict"}) + return self.model_dump(exclude_none=True, exclude={"settingsDict"}) - @root_validator(pre=True) + @model_validator(mode='before') def check_settings_dict_compatibility(cls, values): - """ Ensures that settingsDict is not specified along with other parameters.""" - if values.get("settings_dict") is not None and any(arg_name for arg_name in values): - raise ValueError(f"settings_dict cannot be specified with other index creation parameters.") + """ Ensures that settingsDict is not specified along with other parameters. + + Raises: + ValueError: If settingsDict is specified along with other parameters. + """ + non_none_values = {k: v for k, v in values.items() if v is not None and k != "settingsDict"} + if values.get("settingsDict") is not None and len(non_none_values) > 0: + raise ValueError(f"'settings_dict' cannot be specified with other index creation parameters. You can " + f"move the parameters {list(non_none_values.keys())} to settingsDict.") return values \ No newline at end of file diff --git a/src/marqo/models/search_models.py b/src/marqo/models/search_models.py index e5e47bcc..d700fece 100644 --- a/src/marqo/models/search_models.py +++ b/src/marqo/models/search_models.py @@ -1,9 +1,7 @@ -from typing import Dict, List, Optional, Union -from marqo.models.marqo_models import StrictBaseModel -from abc import ABC from enum import Enum +from typing import Dict, List, Optional, Union -from pydantic import validator, BaseModel, root_validator +from marqo.models.marqo_models import StrictBaseModel class SearchBody(StrictBaseModel): diff --git a/tests/marqo_test.py b/tests/marqo_test.py index 76c9f52d..f7691048 100644 --- a/tests/marqo_test.py +++ b/tests/marqo_test.py @@ -111,7 +111,7 @@ class Config: arbitrary_types_allowed: bool = True def __str__(self): - return f"MockHTTPTraffic({json.dumps(self.dict(), indent=2)})" + return f"MockHTTPTraffic({json.dumps(self.model_dump(), indent=2)})" def raise_(ex): raise ex diff --git a/tests/v2_tests/test_create_index.py b/tests/v2_tests/test_create_index.py index 3fb593d4..ec14c9f5 100644 --- a/tests/v2_tests/test_create_index.py +++ b/tests/v2_tests/test_create_index.py @@ -1,11 +1,14 @@ +import random import uuid from pytest import mark import numpy as np +from marqo import Client from marqo.models.marqo_index import FieldType from marqo.errors import MarqoWebError from tests.marqo_test import MarqoTestCase +from marqo.models.marqo_cloud import CloudIndexSettings @mark.fixed @@ -57,18 +60,18 @@ def test_simple_index_creation(self): def test_create_simple_index_creation_with_prefix(self): # Create the indexes self.client.create_index( - index_name=self.override_index_name, - model="test_prefix", - text_query_prefix="test: ", - text_chunk_prefix="test: ", + index_name=self.override_index_name, + model="test_prefix", + text_query_prefix="test: ", + text_chunk_prefix="test: ", ) self.client.create_index( - index_name=self.default_index_name, - model="test_prefix", + index_name=self.default_index_name, + model="test_prefix", ) - + d1 = { - "_id": "doc1", + "_id": "doc1", "text_field_1": "hello document" } # Add documents to both @@ -78,27 +81,26 @@ def test_create_simple_index_creation_with_prefix(self): # Get override doc with tensor facets (for reference vector) retrieved_override_doc = self.client.index(self.override_index_name).get_document( document_id="doc1", expose_facets=True) - + # Get default doc with tensor facets (for reference vector) retrieved_default_doc = self.client.index(self.default_index_name).get_document( document_id="doc1", expose_facets=True) - + # Embed override - embed_res_override = self.client.index(self.override_index_name).embed("test: hello document", content_type=None) + embed_res_override = self.client.index(self.override_index_name).embed("test: hello document", + content_type=None) # Embed default - embed_res_default = self.client.index(self.default_index_name).embed("test passage: hello document", content_type=None) + embed_res_default = self.client.index(self.default_index_name).embed("test passage: hello document", + content_type=None) # Assert that the embeddings from override add docs and the embeddings from the embed call are the same - self.assertTrue(np.allclose(embed_res_override["embeddings"][0], retrieved_override_doc["_tensor_facets"][0]["_embedding"])) + self.assertTrue( + np.allclose(embed_res_override["embeddings"][0], retrieved_override_doc["_tensor_facets"][0]["_embedding"])) # Assert that the embeddings from override add docs and the embeddings from the embed call are the same - self.assertTrue(np.allclose(embed_res_default["embeddings"][0], retrieved_default_doc["_tensor_facets"][0]["_embedding"])) - - - - - + self.assertTrue( + np.allclose(embed_res_default["embeddings"][0], retrieved_default_doc["_tensor_facets"][0]["_embedding"])) def test_create_unstructured_image_index(self): self.client.create_index(index_name=self.index_name, type="unstructured", @@ -250,7 +252,7 @@ def test_create_structured_index_with_custom_model(self): "dimensions": 384, "tokens": 512, "type": "sbert"}, index_settings['modelProperties']) - + def test_create_structured_index_with_map_fields(self): self.client.create_index( index_name=self.index_name, @@ -260,7 +262,7 @@ def test_create_structured_index_with_map_fields(self): {"name": "map_score_mod_float_1", "type": FieldType.MapFloat, "features": ["score_modifier"]}, {"name": "map_score_mod_double_1", "type": FieldType.MapDouble, "features": ["score_modifier"]}, {"name": "map_score_mod_int_1", "type": FieldType.MapInt, "features": ["score_modifier"]}, - {"name": "map_score_mod_long_1", "type": FieldType.MapLong, "features": ["score_modifier"]},], + {"name": "map_score_mod_long_1", "type": FieldType.MapLong, "features": ["score_modifier"]}, ], tensor_fields=["test"] ) documents = [{"test": "test"}] @@ -341,7 +343,7 @@ def test_create_unstructured_index_with_languagebind(self): ) index_settings = self.client.index(self.index_name).get_settings() - + expected_settings = { "type": "unstructured", "model": "LanguageBind/Video_V1.5_FT_Audio_FT_Image", @@ -356,11 +358,13 @@ def test_create_unstructured_index_with_languagebind(self): # Test adding and searching documents ix = self.client.index(self.index_name) - + res = ix.add_documents( - documents = [ - {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/187680354/preview.mp3", "_id": "corporate"}, - {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/492763015/preview.mp3", "_id": "lofi"}, + documents=[ + {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/187680354/preview.mp3", + "_id": "corporate"}, + {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/492763015/preview.mp3", + "_id": "lofi"}, ], tensor_fields=["audio_field"] ) @@ -389,7 +393,7 @@ def test_create_structured_index_with_languagebind(self): ) index_settings = self.client.index(self.index_name).get_settings() - + expected_settings = { "type": "structured", "model": "LanguageBind/Video_V1.5_FT_Audio_FT_Image", @@ -409,11 +413,13 @@ def test_create_structured_index_with_languagebind(self): # Test adding and searching documents ix = self.client.index(self.index_name) - + res = ix.add_documents( - documents = [ - {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/187680354/preview.mp3", "_id": "corporate"}, - {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/492763015/preview.mp3", "_id": "lofi"}, + documents=[ + {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/187680354/preview.mp3", + "_id": "corporate"}, + {"audio_field": "https://audio-previews.elements.envatousercontent.com/files/492763015/preview.mp3", + "_id": "lofi"}, ], ) @@ -424,4 +430,78 @@ def test_create_structured_index_with_languagebind(self): self.assertEqual(2, len(doc['hits'])) self.assertEqual("corporate", doc['hits'][0]['_id']) - self.assertEqual("lofi", doc['hits'][1]['_id']) \ No newline at end of file + self.assertEqual("lofi", doc['hits'][1]['_id']) + + def test_create_index_SettingsDictCanNotBeSpecificWithOtherParametersLocal(self): + """Test that settings_dict cannot be specified with other index creation + parameters in local create_index call.""" + parameters_pool = { + "type": "structured", + "all_fields": [{"name": "test", "type": "text", "features": ["lexical_search"]}], + "tensor_fields": ["test"], + "treat_urls_and_pointers_as_images": True, + "treat_urls_and_pointers_as_media": True, + "filter_string_max_length": 50, + "model": "hf/e5-base-v2", + "model_properties": { + "name": "sentence-transformers/multi-qa-MiniLM-L6-cos-v1", + "dimensions": 384, + "tokens": 512, + "type": "sbert" + }, + "normalize_embeddings": True, + "text_preprocessing": "splitMethod", + "image_preprocessing": "patchMethod", + "vector_numeric_type": "float", + "ann_parameters": {"spaceType": "prenormalized-angular", "parameters": {"efConstruction": 512, "m": 16}}, + "text_query_prefix": "test", + "text_chunk_prefix": "test", + } + + for key in parameters_pool.keys(): + with self.subTest(f"key={key}"): + with self.assertRaises(ValueError) as e: + self.client.create_index( + index_name=self.index_name, + **{key: parameters_pool[key]}, + settings_dict={"type": "unstructured"} + ) + self.assertIn( + f"'settings_dict' cannot be specified with other index creation parameters.", + str(e.exception) + ) + + def test_create_index_SettingsDictCanNotBeSpecificWithOtherParametersCloud(self): + """Test that settings_dict cannot be specified with other index creation + parameters in cloud create_index call.""" + parameters_pool = { + "inference_type": "marqo.CPU.large", + "storage_class": "marqo.basic", + "number_of_replicas": 1, + "number_of_shards": 1, + "number_of_inferences": 1 + } + + test_cloud_client = Client("https://api.marqo.ai", api_key="test") + for key in parameters_pool.keys(): + with self.subTest(f"key={key}"): + with self.assertRaises(ValueError) as e: + test_cloud_client.create_index( + index_name=self.index_name, + **{key: parameters_pool[key]}, + settings_dict={"type": "unstructured"} + ) + self.assertIn( + f"'settings_dict' cannot be specified with other index creation parameters.", + str(e.exception) + ) + + def test_CloudIndexSettingsGenerateRequestBody(self): + """Test that CloudIndexSettings generate_request_body method returns the correct request body.""" + expected_request_body = { + "type": "unstructured", + } + cloud_index_settings = CloudIndexSettings( + type="unstructured" + ) + self.assertEqual(expected_request_body, cloud_index_settings.generate_request_body()) \ No newline at end of file From 16303b1b0c5bf4e9528002be6ec09ff414bd07e5 Mon Sep 17 00:00:00 2001 From: Li Wan Date: Tue, 17 Sep 2024 15:17:41 +1000 Subject: [PATCH 2/5] Add cloud only tests for attributes --- tests/cloud_test_logic/cloud_test_index.py | 1 - tests/cloud_test_logic/run_cloud_tests.py | 2 +- tests/conftest.py | 40 ++++++++++--- tests/v2_tests/test_create_index.py | 56 ++++++++++++++++--- tests/v2_tests/test_get_indexes.py | 2 +- tests/v2_tests/test_index.py | 4 +- .../test_index_manipulation_features.py | 8 +-- 7 files changed, 88 insertions(+), 25 deletions(-) diff --git a/tests/cloud_test_logic/cloud_test_index.py b/tests/cloud_test_logic/cloud_test_index.py index 1f66563f..4be10e73 100644 --- a/tests/cloud_test_logic/cloud_test_index.py +++ b/tests/cloud_test_logic/cloud_test_index.py @@ -135,5 +135,4 @@ class CloudTestIndex(str, Enum): "tensorFields": ["multimodal_field", "text_field_3", "video_field_3", "audio_field_2", "image_field_2"], "normalizeEmbeddings": True, }, - } diff --git a/tests/cloud_test_logic/run_cloud_tests.py b/tests/cloud_test_logic/run_cloud_tests.py index c662f299..08548bbf 100644 --- a/tests/cloud_test_logic/run_cloud_tests.py +++ b/tests/cloud_test_logic/run_cloud_tests.py @@ -59,7 +59,7 @@ def convert_string_to_boolean(string_value): sys.exit(1) print(f"All indices has been created, proceeding to run tests with pytest. Arguments: {sys.argv[1:]}") - pytest_args = ['tests/', '-m', 'not ignore_during_cloud_tests'] + sys.argv[1:] + pytest_args = ['tests/', '--cloud'] + sys.argv[1:] print("running integration tests with args:", pytest_args) pytest_exit_code = pytest.main(pytest_args) if pytest_exit_code != 0: diff --git a/tests/conftest.py b/tests/conftest.py index 9a51b6e6..a0eec124 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,20 +5,42 @@ def pytest_addoption(parser): # TODO Remove this after all the tests are fixed # This is only used as a temporary measure to run all tests # If not specified, only tests marked as 'fixed' will be run - parser.addoption("--all", action="store_true", help="run all tests") + parser.addoption("--all", action="store_true", default=False, help="run all tests") + parser.addoption("--cloud", action="store_true", default=False, help="run pytest in the cloud mode. Skip tests" + "marked as 'local_only_tests'.") + parser.addoption("--local", action="store_true", default=True, help="run pytest in the local mode. Skip tests" + "marked as 'cloud_only_tests'.") def pytest_collection_modifyitems(config, items): + # If --all option is specified, run all tests if config.getoption("--all"): - # If the --all option is used, run all tests return - else: - # Skip tests not marked as 'fixed' - skip_non_fixed = pytest.mark.skip(reason="not marked as fixed") - for item in items: - # TODO Remove this after all the tests are fixed - if "fixed" not in item.keywords: - item.add_marker(skip_non_fixed) + + # Skip tests not marked as 'fixed' + skip_non_fixed = pytest.mark.skip(reason="not marked as fixed") + # Skip tests marked as 'cloud_only_tests' when in local mode + skip_cloud_only = pytest.mark.skip(reason="skipped in local mode") + # Skip tests marked as 'local_only_tests' when in cloud mode + skip_local_only = pytest.mark.skip(reason="skipped in cloud mode") + + # If --cloud is provided, set --local to False + if config.getoption("--cloud"): + config.option.local = False + + for item in items: + if "fixed" not in item.keywords: + item.add_marker(skip_non_fixed) + + # If running in cloud mode, skip local-only tests + if config.getoption("--cloud"): + if "local_only_tests" in item.keywords: + item.add_marker(skip_local_only) + + # If running in local mode, skip cloud-only tests + if config.getoption("--local"): + if "cloud_only_tests" in item.keywords: + item.add_marker(skip_cloud_only) def pytest_configure(config): diff --git a/tests/v2_tests/test_create_index.py b/tests/v2_tests/test_create_index.py index ec14c9f5..90139828 100644 --- a/tests/v2_tests/test_create_index.py +++ b/tests/v2_tests/test_create_index.py @@ -1,18 +1,22 @@ -import random +import os import uuid +from typing import Dict, List -from pytest import mark import numpy as np -from marqo import Client +import requests +from ipywidgets import fixed +from pytest import mark -from marqo.models.marqo_index import FieldType +from marqo import Client from marqo.errors import MarqoWebError -from tests.marqo_test import MarqoTestCase from marqo.models.marqo_cloud import CloudIndexSettings +from marqo.models.marqo_index import FieldType +from tests.cloud_test_logic.cloud_test_index import index_name_to_settings_mappings +from tests.marqo_test import MarqoTestCase @mark.fixed -@mark.ignore_during_cloud_tests +@mark.local_only_tests class TestCreateIndex(MarqoTestCase): index_name = "test_create_index" + str(uuid.uuid4()).replace('-', '') override_index_name = "override_prefix" + str(uuid.uuid4()).replace('-', '') @@ -504,4 +508,42 @@ def test_CloudIndexSettingsGenerateRequestBody(self): cloud_index_settings = CloudIndexSettings( type="unstructured" ) - self.assertEqual(expected_request_body, cloud_index_settings.generate_request_body()) \ No newline at end of file + self.assertEqual(expected_request_body, cloud_index_settings.generate_request_body()) + +@fixed +@mark.cloud_only_tests +class TestCloudCreateIndex(MarqoTestCase): + + TEST_ATTRIBUTES_WITH_DEFAULTS = { + "inferenceType": "CPU.SMALL", + "numberOfShards": 1, + "numberOfReplicas": 0, + "numberOfInferences": 1, + "storageClass": "BASIC" + } + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.get_index_response: List[Dict] = requests.get( + f"{cls.authorized_url}/api/v2/indexes", + headers={"x-api-key": os.environ.get("MARQO_API_KEY", None)} + ).json()["results"] + + def test_cloud_index_attributes(self): + test_indexes = list(index_name_to_settings_mappings.keys()) + for test_index in test_indexes: + index_name = test_index + "_" + os.environ.get("MQ_TEST_RUN_IDENTIFIER", "") + with self.subTest(f"Index name: {index_name}"): + index_meta_data = [d for d in self.get_index_response if d.get("indexName") == index_name][0] + for test_attribute in list(self.TEST_ATTRIBUTES_WITH_DEFAULTS.keys()): + expected_value = index_name_to_settings_mappings[test_index].get( + test_attribute, + self.TEST_ATTRIBUTES_WITH_DEFAULTS[test_attribute] + ) + if isinstance(expected_value, int): + self.assertEqual(expected_value, int(index_meta_data[test_attribute])) + elif isinstance(expected_value, str): + self.assertIn(expected_value, index_meta_data[test_attribute].upper()) + else: + raise ValueError(f"Unexpected type for {test_attribute}: {type(expected_value)}") \ No newline at end of file diff --git a/tests/v2_tests/test_get_indexes.py b/tests/v2_tests/test_get_indexes.py index 1ea94dd4..ac367a98 100644 --- a/tests/v2_tests/test_get_indexes.py +++ b/tests/v2_tests/test_get_indexes.py @@ -20,7 +20,7 @@ def test_get_indexes_simple(self): ) assert self._is_index_name_in_get_indexes_response(test_index_name, self.client.get_indexes()) - @mark.ignore_during_cloud_tests + @mark.local_only_tests def test_get_indexes(self): """Asserts that the results grow after each create_index request If this test breaks, ensure another user isn't using the same Marqo diff --git a/tests/v2_tests/test_index.py b/tests/v2_tests/test_index.py index fbf137d1..76112d6e 100644 --- a/tests/v2_tests/test_index.py +++ b/tests/v2_tests/test_index.py @@ -200,7 +200,7 @@ def run(): assert run() - @mark.ignore_during_cloud_tests + @mark.local_only_tests def test_create_custom_number_of_replicas(self): intended_replicas = 1 self.client.create_index( @@ -544,7 +544,7 @@ def test_get_health_status_red(self): assert res['backend']['status'] == 'red' @mark.fixed - @mark.ignore_during_cloud_tests + @mark.local_only_tests def test_get_status_raises_error_on_local_index(self): index = self.client.index(self.generic_test_index_name) with self.assertRaises(UnsupportedOperationError): diff --git a/tests/v2_tests/test_index_manipulation_features.py b/tests/v2_tests/test_index_manipulation_features.py index f3b1dd9e..b595aee0 100644 --- a/tests/v2_tests/test_index_manipulation_features.py +++ b/tests/v2_tests/test_index_manipulation_features.py @@ -26,14 +26,14 @@ def setUp(self): # Create index tests @mark.fixed - @mark.ignore_during_cloud_tests + @mark.local_only_tests def test_create_index(self): self.client.create_index(self.generic_test_index_name) assert any(self.generic_test_index_name == ix["indexName"] for ix in self.client.get_indexes()['results']) @mark.fixed # TODO: unmark when cloud responses are fixed - @mark.ignore_during_cloud_tests + @mark.local_only_tests def test_create_index_double(self): index_name = self.get_test_index_name( cloud_test_index_to_use=CloudTestIndex.unstructured_image, @@ -48,7 +48,7 @@ def test_create_index_double(self): assert e.code == "index_already_exists" @mark.fixed - @mark.ignore_during_cloud_tests + @mark.local_only_tests def test_create_index_hnsw(self): self.client.create_index(index_name=self.generic_test_index_name, settings_dict={ @@ -72,7 +72,7 @@ def test_create_index_hnsw(self): # Delete index tests: - @mark.ignore_during_cloud_tests + @mark.local_only_tests @mark.fixed def test_create_delete_create_index(self): self.client.create_index(self.generic_test_index_name) From 9b630cb0f24df101ff26252d8c7c0664d9863514 Mon Sep 17 00:00:00 2001 From: Li Wan Date: Tue, 17 Sep 2024 15:28:47 +1000 Subject: [PATCH 3/5] Add cloud only tests for attributes --- tests/conftest.py | 4 ++++ tests/v2_tests/test_create_index.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index a0eec124..c09c8ba8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,4 +44,8 @@ def pytest_collection_modifyitems(config, items): def pytest_configure(config): + # Register custom markers programmatically + config.addinivalue_line("markers", "local_only_tests: mark a test to run only in local mode") + config.addinivalue_line("markers", "cloud_only_tests: mark a test to run only in cloud mode") config.addinivalue_line("markers", "fixed: mark test to run as part of fixed tests") + diff --git a/tests/v2_tests/test_create_index.py b/tests/v2_tests/test_create_index.py index 90139828..7937a905 100644 --- a/tests/v2_tests/test_create_index.py +++ b/tests/v2_tests/test_create_index.py @@ -4,7 +4,6 @@ import numpy as np import requests -from ipywidgets import fixed from pytest import mark from marqo import Client From c53e19e1140396c5d7d6237ac2fe076fa57c48ad Mon Sep 17 00:00:00 2001 From: Li Wan Date: Tue, 17 Sep 2024 15:37:25 +1000 Subject: [PATCH 4/5] Add cloud only tests for attributes --- tests/v2_tests/test_create_index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v2_tests/test_create_index.py b/tests/v2_tests/test_create_index.py index 7937a905..a26c4cab 100644 --- a/tests/v2_tests/test_create_index.py +++ b/tests/v2_tests/test_create_index.py @@ -509,7 +509,7 @@ def test_CloudIndexSettingsGenerateRequestBody(self): ) self.assertEqual(expected_request_body, cloud_index_settings.generate_request_body()) -@fixed +@mark.fixed @mark.cloud_only_tests class TestCloudCreateIndex(MarqoTestCase): From bd8d80ae7134874013796e1ef20ec174f9264f2d Mon Sep 17 00:00:00 2001 From: Li Wan Date: Tue, 17 Sep 2024 16:38:01 +1000 Subject: [PATCH 5/5] Update version to 3.8.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d70a558f..1bc90979 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ "tox" ], name="marqo", - version="3.8.0", + version="3.8.1", author="marqo org", author_email="org@marqo.io", description="Tensor search for humans",