Skip to content

Commit 0f6503f

Browse files
committed
Switch from elasticsearch-dsl-py to elasticsearch-py
This commit switches the dependency away from elasticsearch-dsl-py as the upstream has been merged into elasticsearch-py. This commit also updates the test matrix to support ES9
1 parent e453aff commit 0f6503f

File tree

20 files changed

+734
-669
lines changed

20 files changed

+734
-669
lines changed

README.rst

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ Django Elasticsearch DSL
1212
:target: https://django-elasticsearch-dsl.readthedocs.io/en/latest/
1313

1414
Django Elasticsearch DSL is a package that allows indexing of django models in elasticsearch.
15-
It is built as a thin wrapper around elasticsearch-dsl-py_
16-
so you can use all the features developed by the elasticsearch-dsl-py team.
15+
It is built as a thin wrapper around elasticsearch-py_
16+
so you can use all the features developed by the elasticsearch-py team.
1717

1818
You can view the full documentation at https://django-elasticsearch-dsl.readthedocs.io
1919

20-
.. _elasticsearch-dsl-py: https://github.com/elastic/elasticsearch-dsl-py
20+
.. _elasticsearch-py: https://github.com/elastic/elasticsearch-py
2121

2222
Features
2323
--------
2424

25-
- Based on elasticsearch-dsl-py_ so you can make queries with the Search_ class.
25+
- Based on elasticsearch-py_ so you can make queries with the Search_ class.
2626
- Django signal receivers on save and delete for keeping Elasticsearch in sync.
2727
- Management commands for creating, deleting, rebuilding and populating indices.
2828
- Elasticsearch auto mapping from django models fields.
@@ -31,12 +31,14 @@ Features
3131
- Requirements
3232

3333
- Django >= 3.2
34-
- Python 3.8, 3.9, 3.10, 3.11
34+
- Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13
3535

3636
**Elasticsearch Compatibility:**
3737
The library is compatible with all Elasticsearch versions since 5.x
3838
**but you have to use a matching major version:**
3939

40+
- For Elasticsearch 9.0 and later, use the major version 9 (9.x.y) of the library.
41+
4042
- For Elasticsearch 8.0 and later, use the major version 8 (8.x.y) of the library.
4143

4244
- For Elasticsearch 7.0 and later, use the major version 7 (7.x.y) of the library.
@@ -45,6 +47,9 @@ The library is compatible with all Elasticsearch versions since 5.x
4547

4648
.. code-block:: python
4749
50+
# Elasticsearch 9.x
51+
elasticsearch>=9.0.0,<10.0.0
52+
4853
# Elasticsearch 8.x
4954
elasticsearch-dsl>=8.0.0,<9.0.0
5055

django_elasticsearch_dsl/apps.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
from django.conf import settings
33
from django.utils.module_loading import import_string
44

5-
from elasticsearch_dsl.connections import connections
5+
from elasticsearch.dsl.connections import connections
66

77

88
class DEDConfig(AppConfig):
9-
name = 'django_elasticsearch_dsl'
9+
name = "django_elasticsearch_dsl"
1010
verbose_name = "Django elasticsearch-dsl"
1111
signal_processor = None
1212

@@ -17,20 +17,20 @@ def ready(self):
1717
if not self.signal_processor:
1818
signal_processor_path = getattr(
1919
settings,
20-
'ELASTICSEARCH_DSL_SIGNAL_PROCESSOR',
21-
'django_elasticsearch_dsl.signals.RealTimeSignalProcessor'
20+
"ELASTICSEARCH_DSL_SIGNAL_PROCESSOR",
21+
"django_elasticsearch_dsl.signals.RealTimeSignalProcessor",
2222
)
2323
signal_processor_class = import_string(signal_processor_path)
2424
self.signal_processor = signal_processor_class(connections)
2525

2626
@classmethod
2727
def autosync_enabled(cls):
28-
return getattr(settings, 'ELASTICSEARCH_DSL_AUTOSYNC', True)
28+
return getattr(settings, "ELASTICSEARCH_DSL_AUTOSYNC", True)
2929

3030
@classmethod
3131
def default_index_settings(cls):
32-
return getattr(settings, 'ELASTICSEARCH_DSL_INDEX_SETTINGS', {})
32+
return getattr(settings, "ELASTICSEARCH_DSL_INDEX_SETTINGS", {})
3333

3434
@classmethod
3535
def auto_refresh_enabled(cls):
36-
return getattr(settings, 'ELASTICSEARCH_DSL_AUTO_REFRESH', True)
36+
return getattr(settings, "ELASTICSEARCH_DSL_AUTO_REFRESH", True)

django_elasticsearch_dsl/documents.py

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django import VERSION as DJANGO_VERSION
88
from django.db import models
99
from elasticsearch.helpers import bulk, parallel_bulk
10-
from elasticsearch_dsl import Document as DSLDocument
10+
from elasticsearch.dsl import Document as DSLDocument
1111
from six import iteritems
1212

1313
from .exceptions import ModelFieldNotMappedError
@@ -21,7 +21,8 @@
2121
KeywordField,
2222
LongField,
2323
ShortField,
24-
TextField, TimeField,
24+
TextField,
25+
TimeField,
2526
)
2627
from .search import Search
2728
from .signals import post_index
@@ -88,7 +89,7 @@ def search(cls, using=None, index=None):
8889
using=cls._get_using(using),
8990
index=cls._default_index(index),
9091
doc_type=[cls],
91-
model=cls.django.model
92+
model=cls.django.model,
9293
)
9394

9495
def get_queryset(self):
@@ -104,7 +105,7 @@ def get_indexing_queryset(self):
104105
qs = self.get_queryset()
105106
kwargs = {}
106107
if DJANGO_VERSION >= (2,) and self.django.queryset_pagination:
107-
kwargs = {'chunk_size': self.django.queryset_pagination}
108+
kwargs = {"chunk_size": self.django.queryset_pagination}
108109
return qs.iterator(**kwargs)
109110

110111
def init_prepare(self):
@@ -113,7 +114,7 @@ def init_prepare(self):
113114
from the model and generate a list of callables to avoid doing that
114115
work on every object instance over.
115116
"""
116-
index_fields = getattr(self, '_fields', {})
117+
index_fields = getattr(self, "_fields", {})
117118
fields = []
118119
for name, field in iteritems(index_fields):
119120
if not isinstance(field, DEDField):
@@ -122,15 +123,20 @@ def init_prepare(self):
122123
if not field._path:
123124
field._path = [name]
124125

125-
prep_func = getattr(self, 'prepare_%s_with_related' % name, None)
126+
prep_func = getattr(self, "prepare_%s_with_related" % name, None)
126127
if prep_func:
127-
fn = partial(prep_func, related_to_ignore=self._related_instance_to_ignore)
128+
fn = partial(
129+
prep_func, related_to_ignore=self._related_instance_to_ignore
130+
)
128131
else:
129-
prep_func = getattr(self, 'prepare_%s' % name, None)
132+
prep_func = getattr(self, "prepare_%s" % name, None)
130133
if prep_func:
131134
fn = prep_func
132135
else:
133-
fn = partial(field.get_value_from_instance, field_value_to_ignore=self._related_instance_to_ignore)
136+
fn = partial(
137+
field.get_value_from_instance,
138+
field_value_to_ignore=self._related_instance_to_ignore,
139+
)
134140

135141
fields.append((name, field, fn))
136142

@@ -166,8 +172,9 @@ def to_field(cls, field_name, model_field):
166172
model field to ES field logic
167173
"""
168174
try:
169-
return cls.get_model_field_class_to_field_class()[
170-
model_field.__class__](attr=field_name)
175+
return cls.get_model_field_class_to_field_class()[model_field.__class__](
176+
attr=field_name
177+
)
171178
except KeyError:
172179
raise ModelFieldNotMappedError(
173180
"Cannot convert model field {} "
@@ -178,17 +185,16 @@ def bulk(self, actions, **kwargs):
178185
response = bulk(client=self._get_connection(), actions=actions, **kwargs)
179186
# send post index signal
180187
post_index.send(
181-
sender=self.__class__,
182-
instance=self,
183-
actions=actions,
184-
response=response
188+
sender=self.__class__, instance=self, actions=actions, response=response
185189
)
186190
return response
187191

188192
def parallel_bulk(self, actions, **kwargs):
189-
if self.django.queryset_pagination and 'chunk_size' not in kwargs:
190-
kwargs['chunk_size'] = self.django.queryset_pagination
191-
bulk_actions = parallel_bulk(client=self._get_connection(), actions=actions, **kwargs)
193+
if self.django.queryset_pagination and "chunk_size" not in kwargs:
194+
kwargs["chunk_size"] = self.django.queryset_pagination
195+
bulk_actions = parallel_bulk(
196+
client=self._get_connection(), actions=actions, **kwargs
197+
)
192198
# As the `parallel_bulk` is lazy, we need to get it into `deque` to run it instantly
193199
# See https://discuss.elastic.co/t/helpers-parallel-bulk-in-python-not-working/39498/2
194200
deque(bulk_actions, maxlen=0)
@@ -207,29 +213,26 @@ def generate_id(cls, object_instance):
207213

208214
def _prepare_action(self, object_instance, action):
209215
return {
210-
'_op_type': action,
211-
'_index': self._index._name,
212-
'_id': self.generate_id(object_instance),
213-
'_source': (
214-
self.prepare(object_instance) if action != 'delete' else None
215-
),
216+
"_op_type": action,
217+
"_index": self._index._name,
218+
"_id": self.generate_id(object_instance),
219+
"_source": (self.prepare(object_instance) if action != "delete" else None),
216220
}
217221

218222
def _get_actions(self, object_list, action):
219223
for object_instance in object_list:
220-
if action == 'delete' or self.should_index_object(object_instance):
224+
if action == "delete" or self.should_index_object(object_instance):
221225
yield self._prepare_action(object_instance, action)
222-
226+
223227
def get_actions(self, object_list, action):
224228
"""
225229
Generate the elasticsearch payload.
226230
"""
227231
return self._get_actions(object_list, action)
228232

229-
230233
def _bulk(self, *args, **kwargs):
231234
"""Helper for switching between normal and parallel bulk operation"""
232-
parallel = kwargs.pop('parallel', False)
235+
parallel = kwargs.pop("parallel", False)
233236
if parallel:
234237
return self.parallel_bulk(*args, **kwargs)
235238
else:
@@ -242,24 +245,22 @@ def should_index_object(self, obj):
242245
"""
243246
return True
244247

245-
def update(self, thing, refresh=None, action='index', parallel=False, **kwargs):
248+
def update(self, thing, refresh=None, action="index", parallel=False, **kwargs):
246249
"""
247250
Update each document in ES for a model, iterable of models or queryset
248251
"""
249252
if refresh is not None:
250-
kwargs['refresh'] = refresh
253+
kwargs["refresh"] = refresh
251254
elif self.django.auto_refresh:
252-
kwargs['refresh'] = self.django.auto_refresh
255+
kwargs["refresh"] = self.django.auto_refresh
253256

254257
if isinstance(thing, models.Model):
255258
object_list = [thing]
256259
else:
257260
object_list = thing
258261

259262
return self._bulk(
260-
self._get_actions(object_list, action),
261-
parallel=parallel,
262-
**kwargs
263+
self._get_actions(object_list, action), parallel=parallel, **kwargs
263264
)
264265

265266

django_elasticsearch_dsl/fields.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
else:
1111
from django.utils.encoding import force_str
1212
from django.utils.functional import Promise
13-
from elasticsearch_dsl.field import (
13+
from elasticsearch.dsl.field import (
1414
Boolean,
1515
Byte,
1616
Completion,
@@ -38,10 +38,10 @@
3838
class DEDField(Field):
3939
def __init__(self, attr=None, **kwargs):
4040
super(DEDField, self).__init__(**kwargs)
41-
self._path = attr.split('.') if attr else []
41+
self._path = attr.split(".") if attr else []
4242

4343
def __setattr__(self, key, value):
44-
if key == 'get_value_from_instance':
44+
if key == "get_value_from_instance":
4545
self.__dict__[key] = value
4646
else:
4747
super(DEDField, self).__setattr__(key, value)
@@ -57,21 +57,15 @@ def get_value_from_instance(self, instance, field_value_to_ignore=None):
5757
for attr in self._path:
5858
try:
5959
instance = instance[attr]
60-
except (
61-
TypeError, AttributeError,
62-
KeyError, ValueError, IndexError
63-
):
60+
except (TypeError, AttributeError, KeyError, ValueError, IndexError):
6461
try:
6562
instance = getattr(instance, attr)
6663
except ObjectDoesNotExist:
6764
return None
6865
except (TypeError, AttributeError):
6966
try:
7067
instance = instance[int(attr)]
71-
except (
72-
IndexError, ValueError,
73-
KeyError, TypeError
74-
):
68+
except (IndexError, ValueError, KeyError, TypeError):
7569
if self._required:
7670
raise VariableLookupError(
7771
"Failed lookup for key [{}] in "
@@ -100,29 +94,28 @@ class ObjectField(DEDField, Object):
10094
def _get_inner_field_data(self, obj, field_value_to_ignore=None):
10195
data = {}
10296

103-
if hasattr(self, 'properties'):
97+
if hasattr(self, "properties"):
10498
for name, field in self.properties.to_dict().items():
10599
if not isinstance(field, DEDField):
106100
continue
107101

108102
if field._path == []:
109103
field._path = [name]
110104

111-
data[name] = field.get_value_from_instance(
112-
obj, field_value_to_ignore
113-
)
105+
data[name] = field.get_value_from_instance(obj, field_value_to_ignore)
114106
else:
115107
doc_instance = self._doc_class()
116108
for name, field in self._doc_class._doc_type.mapping.properties._params.get(
117-
'properties', {}).items(): # noqa
109+
"properties", {}
110+
).items(): # noqa
118111
if not isinstance(field, DEDField):
119112
continue
120113

121114
if field._path == []:
122115
field._path = [name]
123116

124117
# This allows for retrieving data from an InnerDoc with prepare_field_name functions.
125-
prep_func = getattr(doc_instance, 'prepare_%s' % name, None)
118+
prep_func = getattr(doc_instance, "prepare_%s" % name, None)
126119

127120
if prep_func:
128121
data[name] = prep_func(obj)
@@ -155,7 +148,8 @@ def get_value_from_instance(self, instance, field_value_to_ignore=None):
155148
if is_iterable and not isinstance(objs, dict):
156149
return [
157150
self._get_inner_field_data(obj, field_value_to_ignore)
158-
for obj in objs if obj != field_value_to_ignore
151+
for obj in objs
152+
if obj != field_value_to_ignore
159153
]
160154

161155
return self._get_inner_field_data(objs, field_value_to_ignore)
@@ -248,11 +242,12 @@ class SearchAsYouTypeField(DEDField, SearchAsYouType):
248242
class FileFieldMixin(object):
249243
def get_value_from_instance(self, instance, field_value_to_ignore=None):
250244
_file = super(FileFieldMixin, self).get_value_from_instance(
251-
instance, field_value_to_ignore)
245+
instance, field_value_to_ignore
246+
)
252247

253248
if isinstance(_file, FieldFile):
254-
return _file.url if _file else ''
255-
return _file if _file else ''
249+
return _file.url if _file else ""
250+
return _file if _file else ""
256251

257252

258253
class FileField(FileFieldMixin, DEDField, Text):
@@ -261,8 +256,9 @@ class FileField(FileFieldMixin, DEDField, Text):
261256

262257
class TimeField(KeywordField):
263258
def get_value_from_instance(self, instance, field_value_to_ignore=None):
264-
time = super(TimeField, self).get_value_from_instance(instance,
265-
field_value_to_ignore)
259+
time = super(TimeField, self).get_value_from_instance(
260+
instance, field_value_to_ignore
261+
)
266262

267263
if time:
268264
return time.isoformat()

django_elasticsearch_dsl/indices.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from copy import deepcopy
22

3-
from elasticsearch_dsl import Index as DSLIndex
3+
from elasticsearch.dsl import Index as DSLIndex
44
from six import python_2_unicode_compatible
55

66
from .apps import DEDConfig

0 commit comments

Comments
 (0)