Skip to content

Commit 7a40527

Browse files
documentation
1 parent 15e213f commit 7a40527

File tree

3 files changed

+150
-8
lines changed

3 files changed

+150
-8
lines changed

docs/persistence.rst

+148-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ layer for your application.
99
For more comprehensive examples have a look at the examples_ directory in the
1010
repository.
1111

12-
.. _examples: https://github.com/elastic/elasticsearch-dsl-py/tree/master/examples
12+
.. _examples: https://github.com/elastic/elasticsearch-dsl-py/tree/main/examples
1313

1414
.. _doc_type:
1515

@@ -66,14 +66,14 @@ settings in elasticsearch (see :ref:`life-cycle` for details).
6666
Data types
6767
~~~~~~~~~~
6868

69-
The ``Document`` instances should be using native python types like
69+
The ``Document`` instances use native python types like ``str`` and
7070
``datetime``. In case of ``Object`` or ``Nested`` fields an instance of the
71-
``InnerDoc`` subclass should be used just like in the ``add_comment`` method in
72-
the above example where we are creating an instance of the ``Comment`` class.
71+
``InnerDoc`` subclass is used, as in the ``add_comment`` method in the above
72+
example where we are creating an instance of the ``Comment`` class.
7373

7474
There are some specific types that were created as part of this library to make
75-
working with specific field types easier, for example the ``Range`` object used
76-
in any of the `range fields
75+
working with some field types easier, for example the ``Range`` object used in
76+
any of the `range fields
7777
<https://www.elastic.co/guide/en/elasticsearch/reference/current/range.html>`_:
7878

7979
.. code:: python
@@ -103,6 +103,148 @@ in any of the `range fields
103103
# empty range is unbounded
104104
Range().lower # None, False
105105
106+
Python Type Hints
107+
~~~~~~~~~~~~~~~~~
108+
109+
Document fields can be defined using standard Python type hints if desired.
110+
Here are some simple examples:
111+
112+
.. code:: python
113+
114+
from typing import Optional
115+
116+
class Post(Document):
117+
title: str # same as Text(required=True)
118+
created_at: Optional[datetime] # same as Date(required=False)
119+
published: bool # same as Boolean(required=True)
120+
121+
Python types are mapped to their corresponding field type according to the
122+
following table:
123+
124+
.. list-table:: Python type to DSL field mappings
125+
:header-rows: 1
126+
127+
* - Python type
128+
- DSL field
129+
* - ``str``
130+
- ``Text(required=True)``
131+
* - ``bool``
132+
- ``Boolean(required=True)``
133+
* - ``int``
134+
- ``Integer(required=True)``
135+
* - ``float``
136+
- ``Float(required=True)``
137+
* - ``bytes``
138+
- ``Binary(required=True)``
139+
* - ``datetime``
140+
- ``Date(required=True)``
141+
* - ``date``
142+
- ``Date(format="yyyy-MM-dd", required=True)``
143+
144+
In addition to the above native types, a field can also be given a type hint
145+
of an ``InnerDoc`` subclass, in which case it becomes an ``Object`` field of
146+
that class. When the ``InnerDoc`` subclass is wrapped with ``List``, a
147+
``Nested`` field is created instead.
148+
149+
.. code:: python
150+
151+
from typing import List
152+
153+
class Address(InnerDoc):
154+
...
155+
156+
class Comment(InnerDoc):
157+
...
158+
159+
class Post(Document):
160+
address: Address # same as Object(Address)
161+
comments: List[Comment] # same as Nested(Comment)
162+
163+
Unfortunately it is impossible to have Python type hints that uniquely
164+
identify every possible Elasticsearch field type. To choose a field type that
165+
is different thant the ones in the table above, the field instance can be added
166+
explicitly as a right-side assignment in the field declaration. The next
167+
example creates a field that is typed as ``str``, but is mapped to ``Keyword``
168+
instead of ``Text``:
169+
170+
.. code:: python
171+
172+
class MyDocument(Document):
173+
category: str = Keyword()
174+
175+
This form can also be used when additional options need to be given to
176+
initialize the field, such as when using custom analyzer settings:
177+
178+
.. code:: python
179+
180+
class Comment(InnerDoc):
181+
content: str = Text(analyzer='snowball')
182+
183+
The standard ``Optional`` modifier from the Python ``typing`` package can be
184+
used to change a typed field from required to optional. The ``List`` modifier
185+
can be added to a field to convert it to an array, similar to using the
186+
``multi=True`` argument on the field object.
187+
188+
When using type hints as above, subclasses of ``Document`` and ``InnerDoc``
189+
inherit some of the behaviors associated with Python dataclasses. If
190+
necessary, the ``mapped_field()`` wrapper can be used on the right side of a
191+
typed field declaration, enabling dataclass options such as ``default`` or
192+
``default_factory`` to be included:
193+
194+
.. code:: python
195+
196+
class MyDocument(Document):
197+
title: str = mapped_field(default="no title")
198+
created_at: datetime = mapped_field(default_factory=datetime.now)
199+
published: bool = mapped_field(default=False)
200+
category: str = mapped_field(Keyword(), default="general")
201+
202+
Static type checkers such as `mypy <https://mypy-lang.org/>`_ and
203+
`pyright <https://github.com/microsoft/pyright>`_ can use the type hints and
204+
the dataclass-specific options added to the ``mapped_field()`` function to
205+
improve type inference and provide better real-time suggestions in IDEs.
206+
207+
One situation in which type checkers can't infer the correct type is when
208+
using fields as class attributes. Consider the following example:
209+
210+
.. code:: python
211+
212+
class MyDocument(Document):
213+
title: str = mapped_field(default="no title")
214+
215+
doc = MyDocument()
216+
# doc.title is typed as "str" (correct)
217+
# MyDocument.title is also typed as "str" (incorrect)
218+
219+
To help type checkers correctly identify class attributes as such, the ``M``
220+
generic must be used as a wrapper to the type hint, as shown in the next
221+
example:
222+
223+
.. code:: python
224+
225+
from elasticsearch_dsl import M
226+
227+
class MyDocument(Document):
228+
title: M[str]
229+
created_at: M[datetime] = mapped_field(default_factory=datetime.now)
230+
231+
doc = MyDocument()
232+
# doc.title is typed as "str"
233+
# MyDocument.title is typed as "InstrumentedField"
234+
235+
Note that the ``M`` type hint does not provide any runtime behavior, it just
236+
provides additional typing declarations for type checkers.
237+
238+
The ``InstrumentedField`` objects returned when fields are accessed as class
239+
attributes are proxies for the field instances that can be used anywhere a
240+
field needs to be referenced, such as when specifying sort options in a
241+
``Search`` object:
242+
243+
.. code:: python
244+
245+
# sort by creation date descending, and title ascending
246+
s = MyDocument.search().sort(-MyDocument.created_at, MyDocument.title)
247+
106248
Note on dates
107249
~~~~~~~~~~~~~
108250

examples/async/vectors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ async def create():
133133

134134
async def search(query):
135135
return WorkplaceDoc.search().knn(
136-
field="passages.embedding",
136+
field=WorkplaceDoc.passages.embedding,
137137
k=5,
138138
num_candidates=50,
139139
query_vector=list(WorkplaceDoc.get_embedding(query)),

examples/vectors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def create():
132132

133133
def search(query):
134134
return WorkplaceDoc.search().knn(
135-
field="passages.embedding",
135+
field=WorkplaceDoc.passages.embedding,
136136
k=5,
137137
num_candidates=50,
138138
query_vector=list(WorkplaceDoc.get_embedding(query)),

0 commit comments

Comments
 (0)