From ea6b1b3da15a20fd996d05bba12cfdcc11d4a3d7 Mon Sep 17 00:00:00 2001 From: addy790 <152245395+addy790@users.noreply.github.com> Date: Wed, 1 Jul 2026 16:59:10 +0530 Subject: [PATCH 1/2] Add support for foreignKeys settings --- .code-samples.meilisearch.yaml | 15 +++++ meilisearch/config.py | 1 + meilisearch/index.py | 58 +++++++++++++++++++ tests/conftest.py | 17 ++++++ .../test_settings_foreign_keys_meilisearch.py | 57 ++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 tests/settings/test_settings_foreign_keys_meilisearch.py diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 413dbfc5..5cc4dda9 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -208,6 +208,21 @@ update_filterable_attributes_1: |- ]) reset_filterable_attributes_1: |- client.index('movies').reset_filterable_attributes() +get_foreign_keys_setting_1: |- + client.index('books').get_foreign_keys() +update_foreign_keys_setting_1: |- + client.index('books').update_foreign_keys([ + { + 'foreignIndexUid': 'authors', + 'fieldName': 'author' + }, + { + 'foreignIndexUid': 'authors', + 'fieldName': 'related_authors' + } + ]) +reset_foreign_keys_setting_1: |- + client.index('books').reset_foreign_keys() get_searchable_attributes_1: |- client.index('movies').get_searchable_attributes() update_searchable_attributes_1: |- diff --git a/meilisearch/config.py b/meilisearch/config.py index 8cc59496..fd53a50e 100644 --- a/meilisearch/config.py +++ b/meilisearch/config.py @@ -28,6 +28,7 @@ class Paths: synonyms = "synonyms" accept_new_fields = "accept-new-fields" filterable_attributes = "filterable-attributes" + foreign_keys = "foreign-keys" sortable_attributes = "sortable-attributes" typo_tolerance = "typo-tolerance" dumps = "dumps" diff --git a/meilisearch/index.py b/meilisearch/index.py index db525a77..fedbd056 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -1715,6 +1715,64 @@ def reset_filterable_attributes(self) -> TaskInfo: return TaskInfo(**task) + def get_foreign_keys(self) -> list[dict[str, str]]: + """Get foreign keys of the index. + + Returns + ------- + settings: + List containing the foreign keys of the index + + Raises + ------ + MeilisearchApiError + An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors + """ + return self.http.get(self.__settings_url_for(self.config.paths.foreign_keys)) + + def update_foreign_keys(self, body: list[dict[str, str]] | None) -> TaskInfo: + """Update foreign keys of the index. + + Parameters + ---------- + body: + List containing the foreign keys, each a dict with 'foreignIndexUid' and 'fieldName'. + + Returns + ------- + task_info: + TaskInfo instance containing information about a task to track the progress of an asynchronous process. + https://www.meilisearch.com/docs/reference/api/tasks#get-one-task + + Raises + ------ + MeilisearchApiError + An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors + """ + task = self.http.put(self.__settings_url_for(self.config.paths.foreign_keys), body) + + return TaskInfo(**task) + + def reset_foreign_keys(self) -> TaskInfo: + """Reset foreign keys of the index to default values. + + Returns + ------- + task_info: + TaskInfo instance containing information about a task to track the progress of an asynchronous process. + https://www.meilisearch.com/docs/reference/api/tasks#get-one-task + + Raises + ------ + MeilisearchApiError + An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors + """ + task = self.http.delete( + self.__settings_url_for(self.config.paths.foreign_keys), + ) + + return TaskInfo(**task) + # SORTABLE ATTRIBUTES SUB-ROUTES def get_sortable_attributes(self) -> list[str]: diff --git a/tests/conftest.py b/tests/conftest.py index 9d630286..8379c319 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -369,3 +369,20 @@ def enable_render_route(): json={"renderRoute": False}, timeout=10, ) + + +@fixture +def enable_foreign_keys(): + requests.patch( + f"{common.BASE_URL}/experimental-features", + headers={"Authorization": f"Bearer {common.MASTER_KEY}"}, + json={"foreignKeys": True}, + timeout=10, + ) + yield + requests.patch( + f"{common.BASE_URL}/experimental-features", + headers={"Authorization": f"Bearer {common.MASTER_KEY}"}, + json={"foreignKeys": False}, + timeout=10, + ) diff --git a/tests/settings/test_settings_foreign_keys_meilisearch.py b/tests/settings/test_settings_foreign_keys_meilisearch.py new file mode 100644 index 00000000..98c40f1c --- /dev/null +++ b/tests/settings/test_settings_foreign_keys_meilisearch.py @@ -0,0 +1,57 @@ +FOREIGN_KEYS = [ + {"foreignIndexUid": "authors", "fieldName": "author"}, +] + + +def test_get_foreign_keys(empty_index, enable_foreign_keys): + """Tests getting the foreign keys.""" + response = empty_index().get_foreign_keys() + assert response == [] + + +def test_update_foreign_keys(empty_index, enable_foreign_keys): + """Tests updating the foreign keys.""" + index = empty_index() + response = index.update_foreign_keys(FOREIGN_KEYS) + index.wait_for_task(response.task_uid) + get_keys = index.get_foreign_keys() + assert len(get_keys) == len(FOREIGN_KEYS) + for key in FOREIGN_KEYS: + assert key in get_keys + + +def test_update_foreign_keys_to_none(empty_index, enable_foreign_keys): + """Tests updating the foreign keys at null.""" + index = empty_index() + # Update the settings first + response = index.update_foreign_keys(FOREIGN_KEYS) + update = index.wait_for_task(response.task_uid) + assert update.status == "succeeded" + # Check the settings have been correctly updated + get_keys = index.get_foreign_keys() + for key in FOREIGN_KEYS: + assert key in get_keys + # Launch test to update at null the setting + response = index.update_foreign_keys(None) + index.wait_for_task(response.task_uid) + response = index.get_foreign_keys() + assert response == [] + + +def test_reset_foreign_keys(empty_index, enable_foreign_keys): + """Tests resetting the foreign keys setting to its default value""" + index = empty_index() + # Update the settings first + response = index.update_foreign_keys(FOREIGN_KEYS) + update = index.wait_for_task(response.task_uid) + assert update.status == "succeeded" + # Check the settings have been correctly updated + get_keys = index.get_foreign_keys() + assert len(get_keys) == len(FOREIGN_KEYS) + for key in FOREIGN_KEYS: + assert key in get_keys + # Check the reset of the settings + response = index.reset_foreign_keys() + index.wait_for_task(response.task_uid) + response = index.get_foreign_keys() + assert response == [] From 72673e59355be66cce3b8f0ec6601def2f7f0b27 Mon Sep 17 00:00:00 2001 From: addy790 <152245395+addy790@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:15:52 +0530 Subject: [PATCH 2/2] Address CodeRabbit review comments --- meilisearch/index.py | 1 + tests/settings/test_settings_foreign_keys_meilisearch.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/meilisearch/index.py b/meilisearch/index.py index fedbd056..84cf5c85 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -1237,6 +1237,7 @@ def update_settings( - 'searchCutoffMs': Maximum search time in milliseconds - 'proximityPrecision': Precision for proximity ranking - 'localizedAttributes': Settings for localized attributes + - 'foreignKeys': List of foreign key relationships to other indexes More information: https://www.meilisearch.com/docs/reference/api/settings#update-settings diff --git a/tests/settings/test_settings_foreign_keys_meilisearch.py b/tests/settings/test_settings_foreign_keys_meilisearch.py index 98c40f1c..b893da09 100644 --- a/tests/settings/test_settings_foreign_keys_meilisearch.py +++ b/tests/settings/test_settings_foreign_keys_meilisearch.py @@ -13,7 +13,9 @@ def test_update_foreign_keys(empty_index, enable_foreign_keys): """Tests updating the foreign keys.""" index = empty_index() response = index.update_foreign_keys(FOREIGN_KEYS) - index.wait_for_task(response.task_uid) + update = index.wait_for_task(response.task_uid) + assert update.status == "succeeded" + get_keys = index.get_foreign_keys() assert len(get_keys) == len(FOREIGN_KEYS) for key in FOREIGN_KEYS: