Skip to content

Commit

Permalink
Add support for rich Elasticsearch config options
Browse files Browse the repository at this point in the history
  • Loading branch information
hugorodgerbrown committed Mar 8, 2023
1 parent 6c446ae commit 6434c63
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 19 deletions.
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,15 +24,14 @@ repos:
hooks:
- id: flake8
additional_dependencies:
- flake8-bandit
- flake8-blind-except
- flake8-docstrings
- flake8-logging-format
- flake8-print

# python static type checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.0.1
rev: v1.1.1
hooks:
- id: mypy
args:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand All @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions elasticsearch_django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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]

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "elasticsearch-django"
version = "8.1.2"
version = "8.2"
description = "Elasticsearch Django app."
license = "MIT"
authors = ["YunoJuno <[email protected]>"]
Expand Down
9 changes: 8 additions & 1 deletion tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
11 changes: 5 additions & 6 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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},
Expand Down
30 changes: 23 additions & 7 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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": []},
}
Expand All @@ -29,14 +33,24 @@
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"
client = get_client()
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."""
Expand All @@ -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):
Expand Down

0 comments on commit 6434c63

Please sign in to comment.