Skip to content

Commit 0489c7c

Browse files
committed
feat: Add 'using' and 'database' arguments to management command
1 parent 3f8e75b commit 0489c7c

File tree

11 files changed

+207
-54
lines changed

11 files changed

+207
-54
lines changed

.github/workflows/tests_and_publish.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ jobs:
124124
runs-on: ubuntu-latest
125125
strategy:
126126
matrix:
127-
python-version: [ 3.9, '3.10', '3.11', '3.12', '3.13' ]
128-
django-version: [ 42, 50, 51, 52 ]
129-
opensearch-version: [ 10, 20, 30 ]
127+
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
128+
django-version: [ '42', '50', '51', '52' ]
129+
opensearch-version: [ '10', '20', '30' ]
130130
exclude:
131131
- python-version: 3.9
132132
django-version: 50
@@ -149,7 +149,7 @@ jobs:
149149

150150
- name: Run Opensearch in docker
151151
run: |
152-
docker compose up -d opensearch_test_${{ matrix.opensearch-version }}
152+
docker compose up -d opensearch_test_${{ matrix.opensearch-version }}_0 opensearch_test_${{ matrix.opensearch-version }}_1
153153
sleep 30
154154
155155
- name: Install Tox and any other packages

django_opensearch_dsl/documents.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ def search(cls, using: str = None, index: str = None) -> opensearchpy.Search:
6767
model=cls.django.model,
6868
)
6969

70-
def get_queryset(self, filter_: Optional[Q] = None, exclude: Optional[Q] = None, count: int = None) -> QuerySet:
70+
def get_queryset(
71+
self, filter_: Optional[Q] = None, exclude: Optional[Q] = None, count: int = None, alias: str = None
72+
) -> QuerySet:
7173
"""Return the queryset that should be indexed by this doc type."""
72-
qs = self.django.model.objects.all()
74+
qs = self.django.model.objects.using(alias).all()
7375

7476
if filter_:
7577
qs = qs.filter(filter_)
@@ -95,13 +97,14 @@ def get_indexing_queryset(
9597
verbose: bool = False,
9698
filter_: Optional[Q] = None,
9799
exclude: Optional[Q] = None,
100+
alias: str = None,
98101
count: int = None,
99102
action: CommandAction = CommandAction.INDEX,
100103
stdout: TextIO = sys.stdout,
101104
) -> Iterable:
102105
"""Divide the queryset into chunks."""
103106
chunk_size = self.django.queryset_pagination
104-
qs = self.get_queryset(filter_=filter_, exclude=exclude, count=count)
107+
qs = self.get_queryset(filter_=filter_, exclude=exclude, count=count, alias=alias)
105108
qs = qs.order_by("pk") if not qs.query.is_sliced else qs
106109
total = qs.count()
107110
model = self.django.model.__name__

django_opensearch_dsl/management/commands/opensearch.py

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,27 @@
88

99
import opensearchpy
1010
from django.core.exceptions import FieldError
11-
from django.core.management import BaseCommand
11+
from django.core.management import BaseCommand, CommandError
1212
from django.db.models import Q
13+
from opensearchpy import OpenSearch
14+
from opensearchpy.connection.connections import connections
1315

1416
from ...apps import DODConfig
1517
from ...enums import CommandAction
1618
from ...registries import registry
1719
from ..types import Values, parse
1820

1921

22+
def connection(using: str = "default") -> opensearchpy.OpenSearch:
23+
"""Return the OpenSearch connection for the given alias."""
24+
try:
25+
return connections.get_connection(using)
26+
except KeyError:
27+
raise CommandError(
28+
f"No OpenSearch connection found for alias '{using}', known connections are: {list(connections._kwargs.keys())}"
29+
)
30+
31+
2032
class Command(BaseCommand):
2133
"""Manage indices and documents."""
2234

@@ -46,20 +58,20 @@ def wrap(value: str) -> tuple[str, Values]:
4658
f"manage.py index: error: invalid filter: '{value}' (filter must be formatted as "
4759
f"'[Field Lookups]=[value]')\n",
4860
)
49-
exit(1)
61+
raise CommandError
5062
return lookup, v # noqa
5163

5264
return wrap
5365

54-
def __list_index(self, **options: Any) -> None: # noqa pragma: no cover
66+
def __list_index(self, using: OpenSearch, **options: Any) -> None: # noqa pragma: no cover
5567
"""List all known index and indicate whether they are created or not."""
5668
indices = registry.get_indices()
5769
result = defaultdict(list)
5870
for index in indices:
5971
module = index._doc_types[0].__module__.split(".")[-2] # noqa
60-
exists = index.exists()
72+
exists = index.exists(using=using)
6173
checkbox = f"[{'X' if exists else ' '}]"
62-
count = f" ({index.search().count()} documents)" if exists else ""
74+
count = f" ({index.search(using=using).count()} documents)" if exists else ""
6375
result[module].append(f"{checkbox} {index._name}{count}")
6476
for app, indice_names in result.items():
6577
self.stdout.write(self.style.MIGRATE_LABEL(app))
@@ -72,6 +84,7 @@ def _manage_index(
7284
force: bool,
7385
verbosity: int,
7486
ignore_error: bool,
87+
using: OpenSearch,
7588
**options: Any,
7689
) -> None:
7790
"""Manage the creation and deletion of indices."""
@@ -85,7 +98,7 @@ def _manage_index(
8598
unknown = set(indices) - set(known_name)
8699
if unknown:
87100
self.stderr.write(f"Unknown indices '{list(unknown)}', choices are: '{known_name}'")
88-
exit(1)
101+
raise CommandError
89102

90103
# Only keep given indices
91104
given_indices = [i for i in known if i._name in indices]
@@ -107,7 +120,7 @@ def _manage_index(
107120
self.stdout.write("")
108121
break
109122
elif p.lower() in ["no", "n"]:
110-
exit(1)
123+
raise CommandError
111124

112125
pp = action.present_participle.title()
113126
for index in given_indices:
@@ -119,24 +132,24 @@ def _manage_index(
119132
self.stdout.flush()
120133
try:
121134
if action == CommandAction.CREATE:
122-
index.create()
135+
index.create(using=using)
123136
elif action == CommandAction.DELETE:
124-
index.delete()
137+
index.delete(using=using)
125138
elif action == CommandAction.UPDATE:
126-
index.put_mapping(body=index.to_dict()["mappings"])
139+
index.put_mapping(using=using, body=index.to_dict()["mappings"])
127140
else:
128141
try:
129-
index.delete()
142+
index.delete(using=using)
130143
except opensearchpy.exceptions.NotFoundError:
131144
pass
132-
index.create()
145+
index.create(using=using)
133146
except opensearchpy.exceptions.TransportError as e:
134147
if verbosity or not ignore_error:
135148
error = self.style.ERROR(f"Error: {e.error} - {e.info}")
136149
self.stderr.write(f"{pp} index '{index._name}'...\n{error}") # noqa
137150
if not ignore_error:
138151
self.stderr.write("exiting...")
139-
exit(1)
152+
raise CommandError
140153
else:
141154
if verbosity:
142155
self.stdout.write(f"{pp} index '{index._name}'... {self.style.SUCCESS('OK')}") # noqa
@@ -153,6 +166,8 @@ def _manage_document(
153166
count: bool,
154167
refresh: bool,
155168
missing: bool,
169+
using: OpenSearch,
170+
database: str,
156171
**options: Any,
157172
) -> None:
158173
"""Manage the creation and deletion of indices."""
@@ -168,19 +183,19 @@ def _manage_document(
168183
unknown = set(indices) - set(known_name)
169184
if unknown:
170185
self.stderr.write(f"Unknown indices '{list(unknown)}', choices are: '{known_name}'")
171-
exit(1)
186+
raise CommandError
172187

173188
# Only keep given indices
174189
given_indices = list(filter(lambda i: i._name in indices, known)) # type: ignore[arg-type]
175190
else:
176191
given_indices = list(known)
177192

178193
# Ensure every indices needed are created
179-
not_created = [i._name for i in given_indices if not i.exists()] # noqa
194+
not_created = [i._name for i in given_indices if not i.exists(using=using)] # noqa
180195
if not_created:
181196
self.stderr.write(f"The following indices are not created : {not_created}")
182197
self.stderr.write("Use 'python3 manage.py opensearch list' to list indices' state.")
183-
exit(1)
198+
raise CommandError
184199

185200
# Check field, preparing to display expected actions
186201
s = f"The following documents will be {action.past}:"
@@ -189,17 +204,17 @@ def _manage_document(
189204
# Handle --missing
190205
exclude_ = exclude
191206
if missing and action == CommandAction.INDEX:
192-
q = Q(pk__in=[h.meta.id for h in index.search().extra(stored_fields=[]).scan()])
207+
q = Q(pk__in=[h.meta.id for h in index.search(using=using).extra(stored_fields=[]).scan()])
193208
exclude_ = exclude_ & q if exclude_ is not None else q
194209

195210
document = index._doc_types[0]() # noqa
196211
try:
197212
kwargs_list.append({"filter_": filter_, "exclude": exclude_, "count": count})
198-
qs = document.get_queryset(filter_=filter_, exclude=exclude_, count=count).count()
213+
qs = document.get_queryset(filter_=filter_, exclude=exclude_, count=count, alias=database).count()
199214
except FieldError as e:
200215
model = index._doc_types[0].django.model.__name__ # noqa
201216
self.stderr.write(f"Error while filtering on '{model}' (from index '{index._name}'):\n{e}'") # noqa
202-
exit(1)
217+
raise CommandError
203218
else:
204219
s += f"\n\t- {qs} {document.django.model.__name__}."
205220

@@ -215,14 +230,16 @@ def _manage_document(
215230
self.stdout.write("\n")
216231
break
217232
elif p.lower() in ["no", "n"]:
218-
exit(1)
233+
raise CommandError
219234

220235
result = "\n"
221236
for index, kwargs in zip(given_indices, kwargs_list):
222237
document = index._doc_types[0]() # noqa
223-
qs = document.get_indexing_queryset(stdout=self.stdout._out, verbose=verbosity, action=action, **kwargs)
238+
qs = document.get_indexing_queryset(
239+
stdout=self.stdout._out, verbose=verbosity, action=action, alias=database, **kwargs
240+
)
224241
success, errors = document.update(
225-
qs, parallel=parallel, refresh=refresh, action=action, raise_on_error=False
242+
qs, parallel=parallel, refresh=refresh, action=action, raise_on_error=False, using=using
226243
)
227244

228245
success_str = self.style.SUCCESS(success) if success else success
@@ -256,6 +273,12 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
256273
description="Show all available indices (and their state) for the current project.",
257274
)
258275
subparser.set_defaults(func=self.__list_index)
276+
subparser.add_argument(
277+
"--using",
278+
type=connection,
279+
default=connection("default"),
280+
help="Alias of the OpenSearch connection to use. Default to 'default'.",
281+
)
259282

260283
# 'manage' subcommand
261284
subparser = subparsers.add_parser(
@@ -264,6 +287,12 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
264287
description="Manage the creation an deletion of indices.",
265288
)
266289
subparser.set_defaults(func=self._manage_index)
290+
subparser.add_argument(
291+
"--using",
292+
type=connection,
293+
default=connection("default"),
294+
help="Alias of the OpenSearch connection to use. Default to 'default'.",
295+
)
267296
subparser.add_argument(
268297
"action",
269298
type=str,
@@ -308,6 +337,13 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
308337
CommandAction.UPDATE.value,
309338
],
310339
)
340+
subparser.add_argument(
341+
"--using",
342+
type=connection,
343+
default=connection("default"),
344+
help="Alias of the OpenSearch connection to use. Default to 'default'.",
345+
)
346+
subparser.add_argument("-d", "--database", default=None, dest="database", help="Nominates a database.")
311347
subparser.add_argument(
312348
"-f",
313349
"--filters",
@@ -383,6 +419,6 @@ def handle(self, *args: Any, **options: Any) -> None:
383419
if "func" not in options: # pragma: no cover
384420
self.stderr.write(self.usage)
385421
self.stderr.write(f"manage.py opensearch: error: no subcommand provided.")
386-
exit(1)
422+
raise CommandError
387423

388424
options["func"](**options)

docker-compose.yml

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
services:
22

3-
opensearch_test_10:
4-
container_name: opensearch_test_10
3+
opensearch_test_10_0:
4+
container_name: opensearch_test_10_0
55
image: opensearchproject/opensearch:1
66
ports:
77
- "9210:9200"
@@ -11,8 +11,19 @@ services:
1111
- discovery.type=single-node
1212
- "ES_JAVA_OPTS=-Xms128m -Xmx128m"
1313

14-
opensearch_test_20:
15-
container_name: opensearch_test_20
14+
opensearch_test_10_1:
15+
container_name: opensearch_test_10_1
16+
image: opensearchproject/opensearch:1
17+
ports:
18+
- "9211:9200"
19+
- "9611:9600"
20+
environment:
21+
- plugins.security.disabled=true
22+
- discovery.type=single-node
23+
- "ES_JAVA_OPTS=-Xms128m -Xmx128m"
24+
25+
opensearch_test_20_0:
26+
container_name: opensearch_test_20_0
1627
image: opensearchproject/opensearch:2
1728
ports:
1829
- "9220:9200"
@@ -23,8 +34,20 @@ services:
2334
- "ES_JAVA_OPTS=-Xms128m -Xmx128m"
2435
- OPENSEARCH_INITIAL_ADMIN_PASSWORD="XJ67NCmLj4yMPPz0wthVUvVGV0cQiq"
2536

26-
opensearch_test_30:
27-
container_name: opensearch_test_30
37+
opensearch_test_20_1:
38+
container_name: opensearch_test_20_1
39+
image: opensearchproject/opensearch:2
40+
ports:
41+
- "9221:9200"
42+
- "9621:9600"
43+
environment:
44+
- plugins.security.disabled=true
45+
- discovery.type=single-node
46+
- "ES_JAVA_OPTS=-Xms128m -Xmx128m"
47+
- OPENSEARCH_INITIAL_ADMIN_PASSWORD="XJ67NCmLj4yMPPz0wthVUvVGV0cQiq"
48+
49+
opensearch_test_30_0:
50+
container_name: opensearch_test_30_0
2851
image: opensearchproject/opensearch:3
2952
ports:
3053
- "9230:9200"
@@ -34,3 +57,15 @@ services:
3457
- discovery.type=single-node
3558
- "ES_JAVA_OPTS=-Xms128m -Xmx128m"
3659
- OPENSEARCH_INITIAL_ADMIN_PASSWORD="XJ67NCmLj4yMPPz0wthVUvVGV0cQiq"
60+
61+
opensearch_test_30_1:
62+
container_name: opensearch_test_30_1
63+
image: opensearchproject/opensearch:3
64+
ports:
65+
- "9231:9200"
66+
- "9631:9600"
67+
environment:
68+
- plugins.security.disabled=true
69+
- discovery.type=single-node
70+
- "ES_JAVA_OPTS=-Xms128m -Xmx128m"
71+
- OPENSEARCH_INITIAL_ADMIN_PASSWORD="XJ67NCmLj4yMPPz0wthVUvVGV0cQiq"

docs/document.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,12 @@ queryset used to fetch the data.
106106

107107
---
108108

109-
* `def get_queryset(self, filter_=None, exclude=None, count=None)`
109+
* `def get_queryset(self, filter_=None, exclude=None, count=None, alias=None)`
110110

111111
* `filter_` (`Optional[Q]`) - `Q` object given to the queryset's `filter()` method.
112112
* `exclude` (`Optional[Q]`) - `Q` object given to the queryset's `exclude()` method.
113113
* `count` (`Optional[int]`) - Limit the queryset with the given number.
114+
* `alias` (`Optional[str]`) - Alias of the database to use.
114115

115116
By default, this method retrieves all objects from the model associated with the `Document`, optionally filtering and
116117
excluding elements according to the given arguments. You can also limit the number of results using the `count` argument.
@@ -131,7 +132,7 @@ class EventDocument(Document):
131132

132133
country = fields.ObjectField(doc_class=CountryDocument)
133134

134-
def get_queryset(self, filter_: Optional[Q] = None, exclude: Optional[Q] = None, count: int = 0) -> QuerySet:
135+
def get_queryset(self, filter_: Optional[Q]=None, exclude: Optional[Q]=None, count: int=0, alias: str=None) -> QuerySet:
135136
"""Select country to improve indexing performance."""
136137
return super().get_queryset(filter_=filter_, exclude=exclude, count=count).select_related('country')
137138
```

docs/fields.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ directly. Change the `CarDocument` to look like this:
6868
```python
6969
# documents.py
7070

71-
@registry.register_document
7271
class CarDocument(Document):
7372
# add a string field to the Opensearch mapping called type, the
7473
# value of which is derived from the model's type_to_string attribute
@@ -192,7 +191,7 @@ class CarDocument(Document):
192191
'color',
193192
]
194193

195-
def get_queryset(self, filters: Optional[Dict[str, Any]] = None, count: int = 0) -> 'QuerySet':
194+
def get_queryset(self, filters: Optional[Dict[str, Any]]=None, count: int=0, alias: str=None) -> 'QuerySet':
196195
"""Not mandatory but to improve performance we can select related in one sql request"""
197196
return super().get_queryset(count=count).select_related(
198197
'manufacturer'

0 commit comments

Comments
 (0)