diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40579b9..74df5f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,8 @@ repos: rev: 23.1.0 hooks: - id: black + additional_dependencies: + - platformdirs - repo: https://github.com/asottile/pyupgrade rev: v3.3.1 @@ -22,7 +24,6 @@ repos: hooks: - id: flake8 additional_dependencies: - - flake8-bandit - flake8-blind-except - flake8-docstrings - flake8-logging-format @@ -30,7 +31,7 @@ repos: # python static type checking - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.1 + rev: v1.1.1 hooks: - id: mypy args: diff --git a/CHANGELOG.md b/CHANGELOG.md index 08e5b37..1cfc174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## v8.2 [unreleased] + +- Adds support for more complex client configuration [#68 - @ColeDCrawford] + ## v8.0 This is a non-functional release - updating the Python, Django and diff --git a/README.rst b/README.rst index aa1857e..5a54da8 100644 --- a/README.rst +++ b/README.rst @@ -55,6 +55,10 @@ The Django settings for search are contained in a dictionary called ``SEARCH_SET SEARCH_SETTINGS = { 'connections': { 'default': getenv('ELASTICSEARCH_URL'), + 'backup': { + # all Elasticsearch init kwargs can be used here + 'cloud_id': '{{ cloud_id }}' + } }, 'indexes': { 'blog': { @@ -79,6 +83,8 @@ The Django settings for search are contained in a dictionary called ``SEARCH_SET The ``connections`` node is (hopefully) self-explanatory - we support multiple connections, but in practice you should only need the one - 'default' connection. This is the URL used to connect to your ES instance. The ``settings`` node contains site-wide search settings. The ``indexes`` nodes is where we configure how Django and ES play together, and is where most of the work happens. +Note that prior to v8.2 the connection value had to be a connection string; since v8.2 this can still be a connection string, but can also be a dictionary that contains any kwarg that can be passed to the ``Elasticsearch`` init method. + **Index settings** Inside the index node we have a collection of named indexes - in this case just the single index called ``blog``. Inside each index we have a ``models`` key which contains a list of Django models that should appear in the index, denoted in ``app.ModelName`` format. You can have multiple models in an index, and a model can appear in multiple indexes. How models and indexes interact is described in the next section. diff --git a/elasticsearch_django/settings.py b/elasticsearch_django/settings.py index 8c0682a..8cbfa63 100644 --- a/elasticsearch_django/settings.py +++ b/elasticsearch_django/settings.py @@ -25,7 +25,10 @@ def get_client(connection: str = "default") -> Elasticsearch: """Return configured elasticsearch client.""" - return Elasticsearch(get_connection_string(connection)) + conn_settings = get_connection_settings(connection) + if isinstance(conn_settings, (str, list)): + return Elasticsearch(conn_settings) + return Elasticsearch(**conn_settings) def get_settings() -> SettingsType: @@ -46,7 +49,7 @@ def set_setting(key: str, value: SettingType) -> None: get_settings()[key] = value -def get_connection_string(connection: str = "default") -> str: +def get_connection_settings(connection: str = "default") -> str | list | dict: """Return index settings from Django conf.""" return settings.SEARCH_SETTINGS["connections"][connection] diff --git a/pyproject.toml b/pyproject.toml index d42a13d..dadc3d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "elasticsearch-django" -version = "8.1.2" +version = "8.2" description = "Elasticsearch Django app." license = "MIT" authors = ["YunoJuno "] diff --git a/tests/settings.py b/tests/settings.py index f9aa423..9ecb20d 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -125,7 +125,14 @@ ELASTICSEARCH_URL = getenv("ELASTICSEARCH_URL", "https://localhost:9200") SEARCH_SETTINGS = { - "connections": {"default": ELASTICSEARCH_URL}, + "connections": { + "default": ELASTICSEARCH_URL, + "custom": { + "hosts": "localhost", + "cloud_id": "foo", + "api_key": "bar", + }, + }, "indexes": { # name of the index "examples": { diff --git a/tests/test_models.py b/tests/test_models.py index 76f87a7..c3c3681 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -103,7 +103,7 @@ def test_as_search_document_update_partial( ) == {"simple_field_1": test_obj.simple_field_1} @mock.patch( - "elasticsearch_django.settings.get_connection_string", + "elasticsearch_django.settings.get_connection_settings", lambda: "http://testserver", ) @mock.patch("elasticsearch_django.models.get_client") @@ -122,7 +122,7 @@ def test_index_search_document(self, mock_client, test_obj: ExampleModel): ) @mock.patch( - "elasticsearch_django.settings.get_connection_string", + "elasticsearch_django.settings.get_connection_settings", lambda: "http://testserver", ) @mock.patch("elasticsearch_django.models.get_client") @@ -136,7 +136,7 @@ def test_index_search_document_cached(self, mock_client, test_obj: ExampleModel) assert mock_client.call_count == 0 @mock.patch( - "elasticsearch_django.settings.get_connection_string", + "elasticsearch_django.settings.get_connection_settings", lambda: "http://testserver", ) @mock.patch("elasticsearch_django.models.get_setting") @@ -158,7 +158,7 @@ def test_update_search_document( mock_setting.assert_called_once_with("retry_on_conflict", 0) @mock.patch( - "elasticsearch_django.settings.get_connection_string", + "elasticsearch_django.settings.get_connection_settings", lambda: "http://testserver", ) @mock.patch("elasticsearch_django.models.get_client") @@ -173,7 +173,7 @@ def test_update_search_document_empty(self, mock_client, test_obj: ExampleModel) mock_client.return_value.update.assert_not_called() @mock.patch( - "elasticsearch_django.settings.get_connection_string", + "elasticsearch_django.settings.get_connection_settings", lambda: "http://testserver", ) @mock.patch("elasticsearch_django.models.get_client") @@ -414,7 +414,6 @@ def test_from_search_results(self) -> None: @pytest.mark.django_db class ExecuteFunctionTests: - raw_hits = [ {"_id": "1", "_index": "foo", "_score": 1.1}, {"_id": "2", "_index": "foo", "_score": 1.2}, diff --git a/tests/test_settings.py b/tests/test_settings.py index b086993..dbb4c38 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -3,11 +3,12 @@ import pytest from django.apps import apps from django.test.utils import override_settings +from elasticsearch import Elasticsearch from elasticsearch_django.settings import ( auto_sync, get_client, - get_connection_string, + get_connection_settings, get_document_models, get_index_config, get_index_models, @@ -20,7 +21,10 @@ from .models import ExampleModel TEST_SETTINGS = { - "connections": {"default": "https://foo", "backup": "https://bar"}, + "connections": { + "default": "https://foo", + "backup": {"hosts": "https://bar.baz:123", "api_key": ("id", "secret")}, + }, "indexes": {"baz": {"models": ["tests.ExampleModel"]}}, "settings": {"foo": "bar", "auto_sync": True, "never_auto_sync": []}, } @@ -29,7 +33,7 @@ class SettingsFunctionTests: """Tests for the settings functions.""" - @mock.patch("elasticsearch_django.settings.get_connection_string") + @mock.patch("elasticsearch_django.settings.get_connection_settings") def test_get_client(self, mock_conn): """Test the get_client function.""" mock_conn.return_value = "http://foo:9200" @@ -37,6 +41,16 @@ def test_get_client(self, mock_conn): assert len(client.transport.node_pool.all()) == 1 assert client.transport.node_pool.all()[0].base_url == mock_conn() + @override_settings(SEARCH_SETTINGS=TEST_SETTINGS) + def test_get_client__init(self): + """Test the get_client function initialises with correct settings.""" + + def check_init(*args, **kwargs): + assert kwargs == TEST_SETTINGS["connections"]["backup"] + + with mock.patch.object(Elasticsearch, "__init__", check_init): + _ = get_client("backup") + @override_settings(SEARCH_SETTINGS=TEST_SETTINGS) def test_get_settings(self): """Test the get_settings method.""" @@ -55,10 +69,12 @@ def test_get_setting_with_default(self): assert get_setting("bar", "baz") == "baz" @override_settings(SEARCH_SETTINGS=TEST_SETTINGS) - def test_get_connection_string(self): - """Test the get_connection_string method.""" - assert get_connection_string() == TEST_SETTINGS["connections"]["default"] - assert get_connection_string("backup") == TEST_SETTINGS["connections"]["backup"] + def test_get_connection_settings(self): + """Test the get_connection_settings method.""" + assert get_connection_settings() == TEST_SETTINGS["connections"]["default"] + assert ( + get_connection_settings("backup") == TEST_SETTINGS["connections"]["backup"] + ) @override_settings(SEARCH_SETTINGS=TEST_SETTINGS) def test_get_index_config(self):