Skip to content

Commit

Permalink
support composable templates
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Nov 14, 2024
1 parent 5511abe commit f16b7c2
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 8 deletions.
2 changes: 1 addition & 1 deletion elasticsearch_dsl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
construct_field,
)
from .function import SF
from .index import AsyncIndex, AsyncIndexTemplate, Index, IndexTemplate
from .index import AsyncIndex, AsyncIndexTemplate, AsyncNewIndexTemplate, Index, IndexTemplate, NewIndexTemplate
from .mapping import AsyncMapping, Mapping
from .query import Q, Query
from .response import AggResponse, Response, UpdateByQueryResponse
Expand Down
54 changes: 54 additions & 0 deletions elasticsearch_dsl/_async/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,47 @@ async def save(
)


class AsyncNewIndexTemplate:
def __init__(
self,
name: str,
template: str,
index: Optional["AsyncIndex"] = None,
priority: Optional[int] = None,
**kwargs: Any,
):
if index is None:
self._index = AsyncIndex(template, **kwargs)
else:
if kwargs:
raise ValueError(
"You cannot specify options for Index when"
" passing an Index instance."
)
self._index = index.clone()
self._index._name = template
self._template_name = name
self.priority = priority

def __getattr__(self, attr_name: str) -> Any:
return getattr(self._index, attr_name)

def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {"template": self._index.to_dict()}
d["index_patterns"] = [self._index._name]
if self.priority is not None:
d["priority"] = self.priority
return d

async def save(
self, using: Optional[AsyncUsingType] = None
) -> "ObjectApiResponse[Any]":
es = get_connection(using or self._index._using)
return await es.indices.put_index_template(
name=self._template_name, **self.to_dict()
)


class AsyncIndex(IndexBase):
_using: AsyncUsingType

Expand Down Expand Up @@ -109,6 +150,19 @@ def as_template(
template_name, pattern or self._name, index=self, order=order
)

def as_new_template(
self,
template_name: str,
pattern: Optional[str] = None,
priority: Optional[int] = None,
) -> AsyncNewIndexTemplate:
# TODO: should we allow pattern to be a top-level arg?
# or maybe have an IndexPattern that allows for it and have
# Document._index be that?
return AsyncNewIndexTemplate(
template_name, pattern or self._name, index=self, priority=priority
)

async def load_mappings(self, using: Optional[AsyncUsingType] = None) -> None:
await self.get_or_create_mapping().update_from_es(
self._name, using=using or self._using
Expand Down
50 changes: 50 additions & 0 deletions elasticsearch_dsl/_sync/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,43 @@ def save(self, using: Optional[UsingType] = None) -> "ObjectApiResponse[Any]":
return es.indices.put_template(name=self._template_name, body=self.to_dict())


class NewIndexTemplate:
def __init__(
self,
name: str,
template: str,
index: Optional["Index"] = None,
priority: Optional[int] = None,
**kwargs: Any,
):
if index is None:
self._index = Index(template, **kwargs)
else:
if kwargs:
raise ValueError(
"You cannot specify options for Index when"
" passing an Index instance."
)
self._index = index.clone()
self._index._name = template
self._template_name = name
self.priority = priority

def __getattr__(self, attr_name: str) -> Any:
return getattr(self._index, attr_name)

def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {"template": self._index.to_dict()}
d["index_patterns"] = [self._index._name]
if self.priority is not None:
d["priority"] = self.priority
return d

def save(self, using: Optional[UsingType] = None) -> "ObjectApiResponse[Any]":
es = get_connection(using or self._index._using)
return es.indices.put_index_template(name=self._template_name, **self.to_dict())


class Index(IndexBase):
_using: UsingType

Expand Down Expand Up @@ -103,6 +140,19 @@ def as_template(
template_name, pattern or self._name, index=self, order=order
)

def as_new_template(
self,
template_name: str,
pattern: Optional[str] = None,
priority: Optional[int] = None,
) -> NewIndexTemplate:
# TODO: should we allow pattern to be a top-level arg?
# or maybe have an IndexPattern that allows for it and have
# Document._index be that?
return NewIndexTemplate(
template_name, pattern or self._name, index=self, priority=priority
)

def load_mappings(self, using: Optional[UsingType] = None) -> None:
self.get_or_create_mapping().update_from_es(
self._name, using=using or self._using
Expand Down
8 changes: 6 additions & 2 deletions elasticsearch_dsl/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@
# specific language governing permissions and limitations
# under the License.

from ._async.index import AsyncIndex, AsyncIndexTemplate # noqa: F401
from ._sync.index import Index, IndexTemplate # noqa: F401
from ._async.index import ( # noqa: F401
AsyncIndex,
AsyncIndexTemplate,
AsyncNewIndexTemplate,
)
from ._sync.index import Index, IndexTemplate, NewIndexTemplate # noqa: F401
3 changes: 2 additions & 1 deletion examples/alias_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

ALIAS = "test-blog"
PATTERN = ALIAS + "-*"
PRIORITY = 100


class BlogPost(Document):
Expand Down Expand Up @@ -81,7 +82,7 @@ def setup() -> None:
deploy.
"""
# create an index template
index_template = BlogPost._index.as_template(ALIAS, PATTERN)
index_template = BlogPost._index.as_new_template(ALIAS, PATTERN, priority=PRIORITY)
# upload the template into elasticsearch
# potentially overriding the one already there
index_template.save()
Expand Down
3 changes: 2 additions & 1 deletion examples/async/alias_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

ALIAS = "test-blog"
PATTERN = ALIAS + "-*"
PRIORITY = 100


class BlogPost(AsyncDocument):
Expand Down Expand Up @@ -82,7 +83,7 @@ async def setup() -> None:
deploy.
"""
# create an index template
index_template = BlogPost._index.as_template(ALIAS, PATTERN)
index_template = BlogPost._index.as_new_template(ALIAS, PATTERN, priority=PRIORITY)
# upload the template into elasticsearch
# potentially overriding the one already there
await index_template.save()
Expand Down
2 changes: 1 addition & 1 deletion examples/async/parent_child.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ async def save(self, **kwargs: Any) -> None: # type: ignore[override]

async def setup() -> None:
"""Create an IndexTemplate and save it into elasticsearch."""
index_template = Post._index.as_template("base")
index_template = Post._index.as_new_template("base", priority=100)
await index_template.save()


Expand Down
2 changes: 1 addition & 1 deletion examples/parent_child.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def save(self, **kwargs: Any) -> None: # type: ignore[override]

def setup() -> None:
"""Create an IndexTemplate and save it into elasticsearch."""
index_template = Post._index.as_template("base")
index_template = Post._index.as_new_template("base", priority=100)
index_template.save()


Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def teardown_method(self, _: Any) -> None:
)
self.client.indices.delete(index="*", expand_wildcards=expand_wildcards)
self.client.indices.delete_template(name="*")
self.client.indices.delete_index_template(name="*")

def es_version(self) -> Tuple[int, ...]:
if not hasattr(self, "_es_version"):
Expand Down Expand Up @@ -172,6 +173,9 @@ def write_client(client: Elasticsearch) -> Generator[Elasticsearch, None, None]:
for index_name in client.indices.get(index="test-*", expand_wildcards="all"):
client.indices.delete(index=index_name)
client.options(ignore_status=404).indices.delete_template(name="test-template")
client.options(ignore_status=404).indices.delete_index_template(
name="test-template"
)


@pytest_asyncio.fixture
Expand Down
23 changes: 23 additions & 0 deletions tests/test_integration/_async/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
AsyncDocument,
AsyncIndex,
AsyncIndexTemplate,
AsyncNewIndexTemplate,
Date,
Text,
analysis,
Expand Down Expand Up @@ -55,6 +56,28 @@ async def test_index_template_works(async_write_client: AsyncElasticsearch) -> N
} == await async_write_client.indices.get_mapping(index="test-blog")


@pytest.mark.asyncio
async def test_new_index_template_works(async_write_client: AsyncElasticsearch) -> None:
it = AsyncNewIndexTemplate("test-template", "test-*")
it.document(Post)
it.settings(number_of_replicas=0, number_of_shards=1)
await it.save()

i = AsyncIndex("test-blog")
await i.create()

assert {
"test-blog": {
"mappings": {
"properties": {
"title": {"type": "text", "analyzer": "my_analyzer"},
"published_from": {"type": "date"},
}
}
}
} == await async_write_client.indices.get_mapping(index="test-blog")


@pytest.mark.asyncio
async def test_index_can_be_saved_even_with_settings(
async_write_client: AsyncElasticsearch,
Expand Down
32 changes: 31 additions & 1 deletion tests/test_integration/_sync/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@
import pytest
from elasticsearch import Elasticsearch

from elasticsearch_dsl import Date, Document, Index, IndexTemplate, Text, analysis
from elasticsearch_dsl import (
Date,
Document,
Index,
IndexTemplate,
NewIndexTemplate,
Text,
analysis,
)


class Post(Document):
Expand Down Expand Up @@ -48,6 +56,28 @@ def test_index_template_works(write_client: Elasticsearch) -> None:
} == write_client.indices.get_mapping(index="test-blog")


@pytest.mark.sync
def test_new_index_template_works(write_client: Elasticsearch) -> None:
it = NewIndexTemplate("test-template", "test-*")
it.document(Post)
it.settings(number_of_replicas=0, number_of_shards=1)
it.save()

i = Index("test-blog")
i.create()

assert {
"test-blog": {
"mappings": {
"properties": {
"title": {"type": "text", "analyzer": "my_analyzer"},
"published_from": {"type": "date"},
}
}
}
} == write_client.indices.get_mapping(index="test-blog")


@pytest.mark.sync
def test_index_can_be_saved_even_with_settings(
write_client: Elasticsearch,
Expand Down
1 change: 1 addition & 0 deletions utils/run-unasync.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def main(check=False):
"AsyncIndexMeta": "IndexMeta",
"AsyncIndexTemplate": "IndexTemplate",
"AsyncIndex": "Index",
"AsyncNewIndexTemplate": "NewIndexTemplate",
"AsyncUpdateByQuery": "UpdateByQuery",
"AsyncMapping": "Mapping",
"AsyncFacetedSearch": "FacetedSearch",
Expand Down

0 comments on commit f16b7c2

Please sign in to comment.