diff --git a/docs/persistence.rst b/docs/persistence.rst index ab3ffbe4..da7f488c 100644 --- a/docs/persistence.rst +++ b/docs/persistence.rst @@ -271,6 +271,18 @@ field needs to be referenced, such as when specifying sort options in a When specifying sorting order, the ``+`` and ``-`` unary operators can be used on the class field attributes to indicate ascending and descending order. +Finally, the ``ClassVar`` annotation can be used to define a regular class +attribute that should not be mapped to the Elasticsearch index:: + +.. code:: python + + from typing import ClassVar + + class MyDoc(Document): + title: M[str] + created_at: M[datetime] = mapped_field(default_factory=datetime.now) + my_var: ClassVar[str] # regular class variable, ignored by Elasticsearch + Note on dates ~~~~~~~~~~~~~ diff --git a/elasticsearch_dsl/document_base.py b/elasticsearch_dsl/document_base.py index 83445aec..67eae0ab 100644 --- a/elasticsearch_dsl/document_base.py +++ b/elasticsearch_dsl/document_base.py @@ -21,6 +21,7 @@ TYPE_CHECKING, Any, Callable, + ClassVar, Dict, Generic, List, @@ -159,7 +160,10 @@ def __init__(self, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]): # field8: M[str] = mapped_field(MyCustomText(), default="foo") # # # legacy format without Python typing - # field8 = Text() + # field9 = Text() + # + # # ignore attributes + # field10: ClassVar[string] = "a regular class variable" annotations = attrs.get("__annotations__", {}) fields = set([n for n in attrs if isinstance(attrs[n], Field)]) fields.update(annotations.keys()) @@ -172,10 +176,14 @@ def __init__(self, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]): # the field has a type annotation, so next we try to figure out # what field type we can use type_ = annotations[name] + skip = False required = True multi = False while hasattr(type_, "__origin__"): - if type_.__origin__ == Mapped: + if type_.__origin__ == ClassVar: + skip = True + break + elif type_.__origin__ == Mapped: # M[type] -> extract the wrapped type type_ = type_.__args__[0] elif type_.__origin__ == Union: @@ -192,6 +200,9 @@ def __init__(self, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]): type_ = type_.__args__[0] else: break + if skip or type_ == ClassVar: + # skip ClassVar attributes + continue field = None field_args: List[Any] = [] field_kwargs: Dict[str, Any] = {} diff --git a/tests/_async/test_document.py b/tests/_async/test_document.py index 0233ddcc..00a570b2 100644 --- a/tests/_async/test_document.py +++ b/tests/_async/test_document.py @@ -26,7 +26,7 @@ import pickle from datetime import datetime from hashlib import md5 -from typing import Any, Dict, List, Optional +from typing import Any, ClassVar, Dict, List, Optional import pytest from pytest import raises @@ -675,6 +675,8 @@ class TypedDoc(AsyncDocument): s4: M[Optional[Secret]] = mapped_field( SecretField(), default_factory=lambda: "foo" ) + i1: ClassVar + i2: ClassVar[int] props = TypedDoc._doc_type.mapping.to_dict()["properties"] assert props == { @@ -708,6 +710,9 @@ class TypedDoc(AsyncDocument): "s4": {"type": "text"}, } + TypedDoc.i1 = "foo" + TypedDoc.i2 = 123 + doc = TypedDoc() assert doc.k3 == "foo" assert doc.s4 == "foo" @@ -723,6 +728,9 @@ class TypedDoc(AsyncDocument): "s3", } + assert TypedDoc.i1 == "foo" + assert TypedDoc.i2 == 123 + doc.st = "s" doc.li = [1, 2, 3] doc.k1 = "k1" diff --git a/tests/_sync/test_document.py b/tests/_sync/test_document.py index 4ba6992f..dddfc688 100644 --- a/tests/_sync/test_document.py +++ b/tests/_sync/test_document.py @@ -26,7 +26,7 @@ import pickle from datetime import datetime from hashlib import md5 -from typing import Any, Dict, List, Optional +from typing import Any, ClassVar, Dict, List, Optional import pytest from pytest import raises @@ -675,6 +675,8 @@ class TypedDoc(Document): s4: M[Optional[Secret]] = mapped_field( SecretField(), default_factory=lambda: "foo" ) + i1: ClassVar + i2: ClassVar[int] props = TypedDoc._doc_type.mapping.to_dict()["properties"] assert props == { @@ -708,6 +710,9 @@ class TypedDoc(Document): "s4": {"type": "text"}, } + TypedDoc.i1 = "foo" + TypedDoc.i2 = 123 + doc = TypedDoc() assert doc.k3 == "foo" assert doc.s4 == "foo" @@ -723,6 +728,9 @@ class TypedDoc(Document): "s3", } + assert TypedDoc.i1 == "foo" + assert TypedDoc.i2 == 123 + doc.st = "s" doc.li = [1, 2, 3] doc.k1 = "k1"