Поля конкретной модели PostgreSQL¶
Все эти поля доступны из модуля django.contrib.postgres.fields.
Индексирование этих полей¶
Index и Field.db_index оба создают индекс B-дерева, что не особенно полезно при запросе сложных типов данных. Такие индексы, как GinIndex и GistIndex, подходят лучше, хотя выбор индекса зависит от используемых вами запросов. Как правило, GiST может быть хорошим выбором для полей range и HStoreField, а GIN может быть полезен для ArrayField.
МассивФилд¶
- class ArrayField(base_field, size=None, **options)¶
Поле для хранения списков данных. Можно использовать большинство типов полей, и вы передаете другой экземпляр поля как
base_field. Вы также можете указатьsize. ArrayField может быть вложенным для хранения многомерных массивов.Если вы задаете поле
default, убедитесь, что это вызываемый объект, такой какlist(для пустого значения по умолчанию) или вызываемый объект, который возвращает список (например, функция). Неправильное использованиеdefault=[]создает изменяемое значение по умолчанию, которое используется всеми экземплярамиArrayField.- base_field¶
Это обязательный аргумент.
Specifies the underlying data type and behavior for the array. It should be an instance of a subclass of
Field. For example, it could be anIntegerFieldor aCharField. Most field types are permitted, with the exception of those handling relational data (ForeignKey,OneToOneFieldandManyToManyField).Поля массива можно вкладывать друг в друга — вы можете указать экземпляр ArrayField в качестве base_field. Например:
from django.contrib.postgres.fields import ArrayField from django.db import models class ChessBoard(models.Model): board = ArrayField( ArrayField( models.CharField(max_length=10, blank=True), size=8, ), size=8, )
Преобразование значений между базой данных и моделью, проверка данных и конфигурации, а также сериализация делегируются базовому полю.
- size¶
Это необязательный аргумент.
Если оно передано, массив будет иметь указанный максимальный размер. Это будет передано в базу данных, хотя PostgreSQL в настоящее время не применяет это ограничение.
Примечание
При вложении ArrayField, независимо от того, используете ли вы параметр size или нет, PostgreSQL требует, чтобы массивы были прямоугольными:
from django.contrib.postgres.fields import ArrayField
from django.db import models
class Board(models.Model):
pieces = ArrayField(ArrayField(models.IntegerField()))
# Valid
Board(pieces=[
[2, 3],
[2, 1],
])
# Not valid
Board(pieces=[
[2, 3],
[2],
])
Если требуются нестандартные формы, то базовое поле должно быть обнуляемым, а значения дополняться «Нет».
Запрос ArrayField¶
Существует ряд пользовательских поисков и преобразований для ArrayField. Мы будем использовать следующий пример модели:
from django.contrib.postgres.fields import ArrayField
from django.db import models
class Post(models.Model):
name = models.CharField(max_length=200)
tags = ArrayField(models.CharField(max_length=200), blank=True)
def __str__(self):
return self.name
contains¶
The contains lookup is overridden on ArrayField. The
returned objects will be those where the values passed are a subset of the
data. It uses the SQL operator @>. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])
>>> Post.objects.filter(tags__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contains=['django'])
<QuerySet [<Post: First post>, <Post: Third post>]>
>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
<QuerySet [<Post: First post>]>
contained_by¶
This is the inverse of the contains lookup -
the objects returned will be those where the data is a subset of the values
passed. It uses the SQL operator <@. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])
>>> Post.objects.filter(tags__contained_by=['thoughts', 'django'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contained_by=['thoughts', 'django', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
перекрытие¶
Returns objects where the data shares any results with the values passed. Uses
the SQL operator &&. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])
>>> Post.objects.filter(tags__overlap=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__overlap=['thoughts', 'tutorial'])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
лен¶
Returns the length of the array. The lookups available afterwards are those
available for IntegerField. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.filter(tags__len=1)
<QuerySet [<Post: Second post>]>
Индексные преобразования¶
Index transforms index into the array. Any non-negative integer can be used.
There are no errors if it exceeds the size of the
array. The lookups available after the transform are those from the
base_field. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.filter(tags__0='thoughts')
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__1__iexact='Django')
<QuerySet [<Post: First post>]>
>>> Post.objects.filter(tags__276='javascript')
<QuerySet []>
Примечание
PostgreSQL использует индексацию с отсчетом от 1 для полей массива при написании необработанного SQL. Однако эти индексы и индексы, используемые в slices, используют индексацию, отсчитываемую от 0, чтобы быть совместимыми с Python.
Срезные преобразования¶
Slice transforms take a slice of the array. Any two non-negative integers can be used, separated by a single underscore. The lookups available after the transform do not change. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['django', 'python', 'thoughts'])
>>> Post.objects.filter(tags__0_1=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__0_2__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>
Примечание
PostgreSQL использует индексацию с отсчетом от 1 для полей массива при написании необработанного SQL. Однако эти фрагменты и те, которые используются в indexes, используют индексацию, отсчитываемую от 0, чтобы быть совместимыми с Python.
Многомерные массивы с индексами и срезами
PostgreSQL имеет довольно эзотерическое поведение при использовании индексов и срезов в многомерных массивах. Всегда можно использовать индексы для доступа к окончательным базовым данным, но большинство других фрагментов ведут себя странно на уровне базы данных и не могут поддерживаться Django логически и последовательно.
CIText fields¶
- class CIText(**options)¶
A mixin to create case-insensitive text fields backed by the citext type. Read about the performance considerations prior to using it.
To use
citext, use theCITextExtensionoperation to setup the citext extension in PostgreSQL before the firstCreateModelmigration operation.If you’re using an
ArrayFieldofCITextfields, you must add'django.contrib.postgres'in yourINSTALLED_APPS, otherwise field values will appear as strings like'{thoughts,django}'.Several fields that use the mixin are provided:
- class CICharField(**options)¶
- class CIEmailField(**options)¶
- class CITextField(**options)¶
These fields subclass
CharField,EmailField, andTextField, respectively.max_lengthwon’t be enforced in the database sincecitextbehaves similar to PostgreSQL’stexttype.
HStoreField¶
- class HStoreField(**options)¶
Поле для хранения пар ключ-значение. Используемый тип данных Python — «dict». Ключи должны быть строками, а значения могут быть либо строками, либо значениями NULL («None» в Python).
Чтобы использовать это поле, вам необходимо:
Добавьте
'django.contrib.postgres'в настройки:INSTALLED_APPS.Setup the hstore extension in PostgreSQL.
Вы увидите ошибку типа «невозможно адаптировать тип «dict», если пропустите первый шаг, или «тип «hstore» не существует», если пропустите второй.
Примечание
В некоторых случаях может быть полезно потребовать или ограничить ключи, действительные для данного поля. Это можно сделать с помощью KeysValidator.
Запрос HStoreField¶
Помимо возможности запроса по ключу, для HStoreField доступен ряд пользовательских поисков.
Мы будем использовать следующий пример модели:
from django.contrib.postgres.fields import HStoreField
from django.db import models
class Dog(models.Model):
name = models.CharField(max_length=200)
data = HStoreField()
def __str__(self):
return self.name
Ключевые запросы¶
To query based on a given key, you can use that key as the lookup name:
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})
>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>
You can chain other lookups after key lookups:
>>> Dog.objects.filter(data__breed__contains='l')
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
Если ключ, который вы хотите запросить, конфликтует с именем другого поиска, вам нужно вместо этого использовать поиск hstorefield.contains.
Предупреждение
Поскольку любая строка может быть ключом в значении hstore, любой поиск, кроме перечисленных ниже, будет интерпретироваться как поиск ключа. Никаких ошибок не возникает. Будьте особенно осторожны с ошибками при вводе и всегда проверяйте, работают ли ваши запросы так, как вы задумали.
contains¶
The contains lookup is overridden on
HStoreField. The returned objects are
those where the given dict of key-value pairs are all contained in the
field. It uses the SQL operator @>. For example:
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})
>>> Dog.objects.filter(data__contains={'owner': 'Bob'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>
contained_by¶
This is the inverse of the contains lookup -
the objects returned will be those where the key-value pairs on the object are
a subset of those in the value passed. It uses the SQL operator <@. For
example:
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})
>>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>
has_key¶
Returns objects where the given key is in the data. Uses the SQL operator
?. For example:
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__has_key='owner')
<QuerySet [<Dog: Meg>]>
has_any_keys¶
Returns objects where any of the given keys are in the data. Uses the SQL
operator ?|. For example:
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
>>> Dog.objects.create(name='Fred', data={})
>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
has_keys¶
Returns objects where all of the given keys are in the data. Uses the SQL operator
?&. For example:
>>> Dog.objects.create(name='Rufus', data={})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
<QuerySet [<Dog: Meg>]>
ключи¶
Returns objects where the array of keys is the given value. Note that the order
is not guaranteed to be reliable, so this transform is mainly useful for using
in conjunction with lookups on
ArrayField. Uses the SQL function
akeys(). For example:
>>> Dog.objects.create(name='Rufus', data={'toy': 'bone'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__keys__overlap=['breed', 'toy'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
ценности¶
Returns objects where the array of values is the given value. Note that the
order is not guaranteed to be reliable, so this transform is mainly useful for
using in conjunction with lookups on
ArrayField. Uses the SQL function
avals(). For example:
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
>>> Dog.objects.filter(data__values__contains=['collie'])
<QuerySet [<Dog: Meg>]>
JSONField¶
- class JSONField(encoder=None, **options)¶
Поле для хранения данных в формате JSON. В Python данные представлены в собственном формате Python: словари, списки, строки, числа, логические значения и «Нет».
- encoder¶
An optional JSON-encoding class to serialize data types not supported by the standard JSON serializer (
datetime,uuid, etc.). For example, you can use theDjangoJSONEncoderclass or any otherjson.JSONEncodersubclass.When the value is retrieved from the database, it will be in the format chosen by the custom encoder (most often a string), so you’ll need to take extra steps to convert the value back to the initial data type (
Model.from_db()andField.from_db_value()are two possible hooks for that purpose). Your deserialization may need to account for the fact that you can’t be certain of the input type. For example, you run the risk of returning adatetimethat was actually a string that just happened to be in the same format chosen fordatetimes.
If you give the field a
default, ensure it’s a callable such asdict(for an empty default) or a callable that returns a dict (such as a function). Incorrectly usingdefault={}creates a mutable default that is shared between all instances ofJSONField.
Примечание
PostgreSQL имеет два собственных типа данных на основе JSON: json и jsonb. Основное различие между ними заключается в том, как они хранятся и как их можно запрашивать. Поле json в PostgreSQL хранится как исходное строковое представление JSON и должно декодироваться на лету при запросе на основе ключей. Поле jsonb хранится на основе фактической структуры JSON, что позволяет индексировать. Компромиссом являются небольшие дополнительные затраты на запись в поле jsonb. JSONField использует jsonb.
Не рекомендуется, начиная с версии 3.1: Use django.db.models.JSONField instead.
Querying JSONField¶
See Запрос JSONField for details.
Поля диапазона¶
Существует пять типов полей диапазона, соответствующих встроенным типам диапазонов в PostgreSQL. Эти поля используются для хранения диапазона значений; например, временные метки начала и окончания события или диапазон возрастов, для которого подходит занятие.
All of the range fields translate to psycopg2 Range objects in Python, but also accept tuples as input if no bounds
information is necessary. The default is lower bound included, upper bound
excluded, that is [) (see the PostgreSQL documentation for details about
different bounds).
IntegerRangeField¶
- class IntegerRangeField(**options)¶
Stores a range of integers. Based on an
IntegerField. Represented by anint4rangein the database and aNumericRangein Python.Независимо от границ, указанных при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, который включает нижнюю границу и исключает верхнюю границу, то есть
[).
BigIntegerRangeField¶
- class BigIntegerRangeField(**options)¶
Stores a range of large integers. Based on a
BigIntegerField. Represented by anint8rangein the database and aNumericRangein Python.Независимо от границ, указанных при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, который включает нижнюю границу и исключает верхнюю границу, то есть
[).
Десятичноеполедиапазона¶
- class DecimalRangeField(**options)¶
Stores a range of floating point values. Based on a
DecimalField. Represented by anumrangein the database and aNumericRangein Python.
DateTimeRangeField¶
- class DateTimeRangeField(**options)¶
Stores a range of timestamps. Based on a
DateTimeField. Represented by atstzrangein the database and aDateTimeTZRangein Python.
ДатаРангеФилд¶
- class DateRangeField(**options)¶
Stores a range of dates. Based on a
DateField. Represented by adaterangein the database and aDateRangein Python.Независимо от границ, указанных при сохранении данных, PostgreSQL всегда возвращает диапазон в канонической форме, который включает нижнюю границу и исключает верхнюю границу, то есть
[).
Запрос полей диапазона¶
Существует ряд пользовательских поисков и преобразований для полей диапазона. Они доступны во всех вышеуказанных полях, но мы будем использовать следующий пример модели:
from django.contrib.postgres.fields import IntegerRangeField
from django.db import models
class Event(models.Model):
name = models.CharField(max_length=200)
ages = IntegerRangeField()
start = models.DateTimeField()
def __str__(self):
return self.name
We will also use the following example objects:
>>> import datetime
>>> from django.utils import timezone
>>> now = timezone.now()
>>> Event.objects.create(name='Soft play', ages=(0, 10), start=now)
>>> Event.objects.create(name='Pub trip', ages=(21, None), start=now - datetime.timedelta(days=1))
и NumericRange:
>>> from psycopg2.extras import NumericRange
Функции сдерживания¶
Как и в случае с другими полями PostgreSQL, здесь есть три стандартных оператора включения: contains, contained_by и overlap, использующие операторы SQL @>, <@ и && соответственно.
contains¶
>>> Event.objects.filter(ages__contains=NumericRange(4, 5))
<QuerySet [<Event: Soft play>]>
contained_by¶
>>> Event.objects.filter(ages__contained_by=NumericRange(0, 15))
<QuerySet [<Event: Soft play>]>
The contained_by lookup is also available on the non-range field types:
SmallAutoField,
AutoField, BigAutoField,
SmallIntegerField,
IntegerField,
BigIntegerField,
DecimalField, FloatField,
DateField, and
DateTimeField. For example:
>>> from psycopg2.extras import DateTimeTZRange
>>> Event.objects.filter(
... start__contained_by=DateTimeTZRange(
... timezone.now() - datetime.timedelta(hours=1),
... timezone.now() + datetime.timedelta(hours=1),
... ),
... )
<QuerySet [<Event: Soft play>]>
Support for SmallAutoField,
AutoField,
BigAutoField,
SmallIntegerField, and
DecimalField was added.
перекрытие¶
>>> Event.objects.filter(ages__overlap=NumericRange(8, 12))
<QuerySet [<Event: Soft play>]>
Функции сравнения¶
Поля диапазона поддерживают стандартные поиски: lt, gt, lte и gte. Это не особенно полезно — сначала сравниваются нижние границы, а затем только при необходимости верхние границы. Эта стратегия также используется для заказа по полю диапазона. Лучше использовать конкретные операторы сравнения диапазонов.
полностью_lt¶
Возвращаемые диапазоны строго меньше переданного диапазона. Другими словами, все точки в возвращаемом диапазоне меньше всех точек в переданном диапазоне.
>>> Event.objects.filter(ages__fully_lt=NumericRange(11, 15))
<QuerySet [<Event: Soft play>]>
полностью_gt¶
Возвращаемые диапазоны строго превышают переданный диапазон. Другими словами, все точки в возвращаемом диапазоне больше, чем все точки в переданном диапазоне.
>>> Event.objects.filter(ages__fully_gt=NumericRange(11, 15))
<QuerySet [<Event: Pub trip>]>
not_lt¶
Возвращаемые диапазоны не содержат точек меньше переданного диапазона, то есть нижняя граница возвращаемого диапазона является как минимум нижней границей переданного диапазона.
>>> Event.objects.filter(ages__not_lt=NumericRange(0, 15))
<QuerySet [<Event: Soft play>, <Event: Pub trip>]>
not_gt¶
Возвращаемые диапазоны не содержат точек, превышающих переданный диапазон, то есть верхняя граница возвращаемого диапазона не превышает верхней границы переданного диапазона.
>>> Event.objects.filter(ages__not_gt=NumericRange(3, 10))
<QuerySet [<Event: Soft play>]>
adjacent_to¶
Возвращенные диапазоны имеют общую границу с переданным диапазоном.
>>> Event.objects.filter(ages__adjacent_to=NumericRange(10, 21))
<QuerySet [<Event: Soft play>, <Event: Pub trip>]>
Запрос с использованием границ¶
There are three transforms available for use in queries. You can extract the lower or upper bound, or query based on emptiness.
startswith¶
Возвращенные объекты имеют заданную нижнюю границу. Может быть привязан к допустимым поискам для базового поля.
>>> Event.objects.filter(ages__startswith=21)
<QuerySet [<Event: Pub trip>]>
endswith¶
Возвращенные объекты имеют заданную верхнюю границу. Может быть привязан к допустимым поискам для базового поля.
>>> Event.objects.filter(ages__endswith=10)
<QuerySet [<Event: Soft play>]>
пустой¶
Возвращаемые объекты представляют собой пустые диапазоны. Может быть привязан к допустимому поиску для BooleanField.
>>> Event.objects.filter(ages__isempty=True)
<QuerySet []>
lower_inc¶
Возвращает объекты, имеющие включающие или исключающие нижние границы, в зависимости от переданного логического значения. Может быть привязан к допустимому поиску для BooleanField.
>>> Event.objects.filter(ages__lower_inc=True)
<QuerySet [<Event: Soft play>, <Event: Pub trip>]>
lower_inf¶
Возвращает объекты, имеющие неограниченную (бесконечную) или ограниченную нижнюю границу, в зависимости от переданного логического значения. Может быть привязан к допустимому поиску для BooleanField.
>>> Event.objects.filter(ages__lower_inf=True)
<QuerySet []>
upper_inc¶
Возвращает объекты, которые имеют инклюзивные или исключительные верхние границы, в зависимости от переданного логического значения. Может быть привязан к допустимому поиску для BooleanField.
>>> Event.objects.filter(ages__upper_inc=True)
<QuerySet []>
upper_inf¶
Возвращает объекты, имеющие неограниченную (бесконечную) или ограниченную верхнюю границу, в зависимости от переданного логического значения. Может быть привязан к допустимому поиску для BooleanField.
>>> Event.objects.filter(ages__upper_inf=True)
<QuerySet [<Event: Pub trip>]>
Определение собственных типов диапазонов¶
PostgreSQL allows the definition of custom range types. Django’s model and form
field implementations use base classes below, and psycopg2 provides a
register_range() to allow use of custom range
types.
- class RangeField(**options)¶
Базовый класс для полей модельного ряда.
- base_field¶
Используемый класс поля модели.
- range_type¶
The psycopg2 range type to use.
- form_field¶
Используемый класс поля формы. Должен быть подклассом
django.contrib.postgres.forms.BaseRangeField.
Операторы диапазона¶
- class RangeOperators¶
PostgreSQL предоставляет набор операторов SQL, которые можно использовать вместе с типами данных диапазона (полную информацию об операторах диапазона см. в документации PostgreSQL <https://www.postgresql.org/docs/current/functions-range.html#RANGE-OPERATORS-TABLE>`_). Этот класс задуман как удобный способ избежать опечаток. Имена операторов перекрываются с именами соответствующих поисков.
class RangeOperators:
EQUAL = '='
NOT_EQUAL = '<>'
CONTAINS = '@>'
CONTAINED_BY = '<@'
OVERLAPS = '&&'
FULLY_LT = '<<'
FULLY_GT = '>>'
NOT_LT = '&>'
NOT_GT = '&<'
ADJACENT_TO = '-|-'
Выражения RangeBoundary()¶
- class RangeBoundary(inclusive_lower=True, inclusive_upper=False)¶
- inclusive_lower¶
Если
True(по умолчанию), нижняя граница является включающей'[', в противном случае она является эксклюзивной'('.
- inclusive_upper¶
Если
False(по умолчанию), верхняя граница является исключающей')', в противном случае она включает']'.
Выражение RangeBoundary() представляет границы диапазона. Его можно использовать с пользовательскими функциями диапазона, которые ожидают границы, например, для определения ExclusionConstraint. Подробную информацию см. в документации PostgreSQL <https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INCLUSIVITY>`_.