"""Define default values"""
# pylint: disable=line-too-long
import typing as t
from copy import deepcopy
from click import Choice, Path
from voluptuous import All, Any, Boolean, Coerce, Optional, Range, Schema
VERSION_MIN: t.Tuple = (9, 0, 0)
"""Minimum compatible Elasticsearch version"""
VERSION_MAX: t.Tuple = (9, 99, 99)
"""Maximum compatible Elasticsearch version"""
KEYS_TO_REDACT: t.List[str] = [
"password",
"basic_auth",
"bearer_auth",
"api_key",
"id",
"opaque_id",
"token",
]
"""
When doing configuration Schema validation, redact the value from any listed dictionary
key. This only happens if logging is at DEBUG level.
"""
CLIENT_SETTINGS: t.List[str] = [
"hosts",
"cloud_id",
"api_key",
"basic_auth",
"bearer_auth",
"opaque_id",
"headers",
"connections_per_node",
"http_compress",
"verify_certs",
"ca_certs",
"client_cert",
"client_key",
"ssl_assert_hostname",
"ssl_assert_fingerprint",
"ssl_version",
"ssl_context",
"ssl_show_warn",
"transport_class",
"request_timeout",
"node_class",
"node_pool_class",
"randomize_nodes_in_pool",
"node_selector_class",
"dead_node_backoff_factor",
"max_dead_node_backoff",
"serializer",
"serializers",
"default_mimetype",
"max_retries",
"retry_on_status",
"retry_on_timeout",
"sniff_on_start",
"sniff_before_requests",
"sniff_on_node_failures",
"sniff_timeout",
"min_delay_between_sniffing",
"sniffed_node_callback",
"meta_header",
"host_info_callback",
"_transport",
]
"""
Valid argument/option names for :py:class:`~.elasticsearch9.Elasticsearch`. Too large
to show
"""
OTHER_SETTINGS: t.List[str] = [
"master_only",
"skip_version_test",
"username",
"password",
"api_key",
]
"""Valid option names for :py:class:`~.es_client.builder.Builder`'s other settings"""
CLICK_SETTINGS: t.Dict[str, t.Dict] = {
"config": {"help": "Path to configuration file.", "type": Path(exists=True)},
"hosts": {"help": "Elasticsearch URL to connect to.", "multiple": True},
"cloud_id": {"help": "Elastic Cloud instance id"},
"api_token": {"help": "The base64 encoded API Key token", "type": str},
"id": {"help": 'API Key "id" value', "type": str},
"api_key": {"help": 'API Key "api_key" value', "type": str},
"username": {"help": "Elasticsearch username", "type": str},
"password": {"help": "Elasticsearch password", "type": str},
"bearer_auth": {"help": "Bearer authentication token", "type": str, "hidden": True},
"opaque_id": {"help": "X-Opaque-Id HTTP header value", "type": str, "hidden": True},
"request_timeout": {"help": "Request timeout in seconds", "type": float},
"http_compress": {
"help": "Enable HTTP compression",
"default": None,
"hidden": True,
},
"verify_certs": {"help": "Verify SSL/TLS certificate(s)", "default": None},
"ca_certs": {"help": "Path to CA certificate file or directory", "type": str},
"client_cert": {"help": "Path to client certificate file", "type": str},
"client_key": {"help": "Path to client key file", "type": str},
"ssl_assert_hostname": {
"help": "Hostname or IP address to verify on the node's certificate.",
"type": str,
"hidden": True,
},
"ssl_assert_fingerprint": {
"help": (
"SHA-256 fingerprint of the node's certificate. If this value is given "
"then root-of-trust verification isn't done and only the node's "
"certificate fingerprint is verified."
),
"type": str,
"hidden": True,
},
"ssl_version": {
"help": "Minimum acceptable TLS/SSL version",
"type": str,
"hidden": True,
},
"master-only": {
"help": "Only run if the single host provided is the elected master",
"default": None,
"hidden": True,
},
"skip_version_test": {
"help": "Elasticsearch version compatibility check",
"default": None,
"hidden": True,
},
}
"""Default settings used for building :py:class:`click.Option`. Too large to show."""
ES_DEFAULT: t.Dict = {"elasticsearch": {"client": {"hosts": ["http://127.0.0.1:9200"]}}}
"""Default settings for :py:class:`~.es_client.builder.Builder`"""
ENV_VAR_PREFIX: str = "ESCLIENT"
"""Environment variable prefix"""
LOGLEVEL: None = None
"""Default loglevel"""
LOGFILE: None = None
"""Default value for logfile"""
LOGFORMAT: t.Literal['default', 'ecs', 'json'] = 'default'
"""Default value for logformat"""
BLACKLIST: None = None
"""Default value for logging blacklist"""
LOGDEFAULTS: t.Dict = {
"loglevel": LOGLEVEL,
"logfile": LOGFILE,
"logformat": LOGFORMAT,
"blacklist": BLACKLIST,
}
"""All logging defaults in a single combined dictionary"""
LOGGING_SETTINGS: t.Dict[str, t.Dict] = {
"loglevel": {
"help": "Log level",
"type": Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
"default": None,
},
"logfile": {"help": "Log file", "type": str},
"logformat": {
"help": "Log output format",
"type": Choice(["default", "json", "ecs"]),
"default": None,
},
"blacklist": {
"help": "Named entities will not be logged",
"multiple": True,
"default": None,
"hidden": True,
},
}
"""
Default logging settings used for building :py:class:`click.Option`. Too large to show.
"""
SHOW_OPTION: t.Dict[str, bool] = {"hidden": False}
"""Override value to "unhide" a :py:class:`click.Option`"""
SHOW_ENVVAR: t.Dict[str, bool] = {"show_envvar": True}
"""Override value to make Click's help output show the associated environment variable
"""
OVERRIDE: t.Dict = {**SHOW_OPTION, **SHOW_ENVVAR}
"""Override value to combine these into a single constant"""
ONOFF: t.Dict[str, str] = {"on": "", "off": "no-"}
"""Default values for enable/disable click options"""
OPTION_DEFAULTS: t.Dict[str, t.Dict] = {
"config": {},
"hosts": {},
"cloud_id": {},
"api_token": {},
"id": {},
"api_key": {},
"username": {},
"password": {},
"bearer_auth": {},
"opaque_id": {},
"request_timeout": {},
"http_compress": {"onoff": ONOFF},
"verify_certs": {"onoff": ONOFF},
"ca_certs": {},
"client_cert": {},
"client_key": {},
"ssl_assert_hostname": {},
"ssl_assert_fingerprint": {},
"ssl_version": {},
"master-only": {"onoff": ONOFF},
"skip_version_test": {"onoff": ONOFF},
"loglevel": {"settings": LOGGING_SETTINGS["loglevel"]},
"logfile": {"settings": LOGGING_SETTINGS["logfile"]},
"logformat": {"settings": LOGGING_SETTINGS["logformat"]},
"blacklist": {"settings": LOGGING_SETTINGS["blacklist"]},
}
"""Default options for iteratively building Click decorators"""
def all_on() -> t.Dict[str, t.Dict]:
"""Return default options with all overrides enabled"""
options = deepcopy(OPTION_DEFAULTS)
retval = {}
# pylint: disable=consider-using-dict-items
for option in options:
retval[option] = options[option]
retval[option]["override"] = OVERRIDE
return retval
SHOW_EVERYTHING: t.Dict[str, t.Dict] = all_on()
"""Return options for iteratively building Click decorators with all overrides on"""
# Logging schema
[docs]
def config_logging() -> Schema:
"""
:returns: A validation schema of all acceptable logging configuration parameter
names and values with defaults for unset parameters.
:rtype: :py:class:`~.voluptuous.schema_builder.Schema`
Logging schema with defaults:
.. code-block:: yaml
logging:
loglevel: INFO
logfile: None
logformat: default
blacklist: ['elastic_transport', 'urllib3']
"""
return Schema(
{
Optional("loglevel", default="INFO"): Any(
None,
"NOTSET",
"DEBUG",
"INFO",
"WARNING",
"ERROR",
"CRITICAL",
All(Coerce(int), Any(0, 10, 20, 30, 40, 50)),
),
Optional("logfile", default=None): Any(None, str),
Optional("logformat", default="default"): Any(
None, All(Any(str), Any("default", "json", "ecs"))
),
Optional("blacklist", default=["elastic_transport", "urllib3"]): Any(
None, list
),
}
)
# All elasticsearch client options, with a few additional arguments.
[docs]
def config_schema() -> Schema:
"""
:returns: A validation schema of all acceptable client configuration parameter
names and values with defaults for unset parameters.
:rtype: :py:class:`~.voluptuous.schema_builder.Schema`
The validation schema for an :py:class:`~.elasticsearch9.Elasticsearch` client
object with defaults
"""
# pylint: disable=no-value-for-parameter
return Schema(
{
Optional("other_settings", default={}): {
Optional("master_only", default=False): Boolean(),
Optional("skip_version_test", default=False): Boolean(),
Optional("username", default=None): Any(None, str),
Optional("password", default=None): Any(None, str),
Optional("api_key", default={}): {
Optional("id"): Any(None, str),
Optional("api_key"): Any(None, str),
Optional("token"): Any(None, str),
},
},
Optional("client", default={}): {
Optional("hosts", default=None): Any(None, list, str),
Optional("cloud_id", default=None): Any(None, str),
Optional("api_key"): Any(None, tuple),
Optional("basic_auth"): Any(None, tuple),
Optional("bearer_auth"): Any(None, str),
Optional("opaque_id"): Any(None, str),
Optional("headers"): Any(None, dict),
Optional("connections_per_node"): Any(
None, All(Coerce(int), Range(min=1, max=100))
),
Optional("http_compress"): Boolean(),
Optional("verify_certs"): Boolean(),
Optional("ca_certs"): Any(None, str),
Optional("client_cert"): Any(None, str),
Optional("client_key"): Any(None, str),
#: Hostname or IP address to verify on the node's certificate.
#: This is useful if the certificate contains a different value
#: than the one supplied in ``host``. An example of this situation
#: is connecting to an IP address instead of a hostname.
#: Set to ``False`` to disable certificate hostname verification.
Optional("ssl_assert_hostname"): Any(None, str),
#: SHA-256 fingerprint of the node's certificate. If this value is
#: given then root-of-trust verification isn't done and only the
#: node's certificate fingerprint is verified.
#:
#: On CPython 3.10+ this also verifies if any certificate in the
#: chain including the Root CA matches this fingerprint. However
#: because this requires using private APIs support for this is
#: **experimental**.
Optional("ssl_assert_fingerprint"): Any(None, str),
Optional("ssl_version"): Any(
None, str
), # Minimum acceptable TLS/SSL version
#: Pre-configured :class:`ssl.SSLContext` OBJECT. If this value
#: is given then no other TLS options (besides
#: ``ssl_assert_fingerprint``) can be set on the
#: :class:`elastic_transport.NodeConfig`.
Optional("ssl_context"): Any(None, str),
# Keeping this here in case someone APIs it, but otherwise it's not
# likely to be used.
Optional("ssl_show_warn"): Boolean(),
Optional("transport_class"): Any(None, str),
Optional("request_timeout"): Any(
None, All(Coerce(float), Range(min=0.1, max=86400.0))
),
# node_class: Union[str, Type[BaseNode]] = Urllib3HttpNode,
Optional("node_class"): Any(None, str),
# node_pool_class: Type[NodePool] = NodePool,
Optional("node_pool_class"): Any(None, str),
Optional("randomize_nodes_in_pool"): Boolean(),
# node_selector_class: Optional[Union[str, Type[NodeSelector]]] = None,
Optional("node_selector_class"): Any(None, str),
Optional("dead_node_backoff_factor"): Any(None, float),
Optional("max_dead_node_backoff"): Any(None, float),
# One of:
# "Serializer"
# "JsonSerializer"
# "TextSerializer"
# "NdjsonSerializer"
# "CompatibilityModeJsonSerializer"
# "CompatibilityModeNdjsonSerializer"
# "MapboxVectorTileSerializer"
Optional("serializer"): Any(None, str), # ???
# :arg serializers: optional dict of serializer instances that will be
# used for deserializing data coming from the server. (key is the
# mimetype), e.g.: {'mimetype':'serializer'}
# "Serializer"
# "JsonSerializer"
# "TextSerializer"
# "NdjsonSerializer"
# "CompatibilityModeJsonSerializer"
# "CompatibilityModeNdjsonSerializer"
# "MapboxVectorTileSerializer"
Optional("serializers"): Any(None, dict),
Optional("default_mimetype"): Any(None, str),
Optional("max_retries"): Any(
None, All(Coerce(int), Range(min=1, max=100))
),
# retry_on_status: Collection[int] = (429, 502, 503, 504),
Optional("retry_on_status"): Any(None, tuple),
Optional("retry_on_timeout"): Boolean(),
Optional("sniff_on_start"): Boolean(),
Optional("sniff_before_requests"): Boolean(),
Optional("sniff_on_node_failure"): Boolean(),
Optional("sniff_timeout"): Any(
None, All(Coerce(float), Range(min=0.1, max=100.0))
),
Optional("min_delay_between_sniffing"): Any(
None, All(Coerce(float), Range(min=1, max=100.0))
),
# Optional[
# Callable[
# ["Transport", "SniffOptions"],
# Union[List[NodeConfig], List[NodeConfig]],
# ]
# ] = None,
Optional("sniffed_node_callback"): Any(None, str),
Optional("meta_header"): Boolean(),
# Cannot specify both 'request_timeout' and 'timeout'
# Optional('timeout', default=10.0): All(Coerce(float),
# Range(min=1, max=120)),
# Cannot specify both 'randomize_hosts' and 'randomize_nodes_in_pool'
# Optional('randomize_hosts', default=True): Boolean(),
Optional("host_info_callback"): Any(
None, str
), # ??? needs the name of a callback function
# Cannot specify both 'sniffer_timeout' and 'min_delay_between_sniffing'
# Optional('sniffer_timeout', default=0.5): All(Coerce(float),
# Range(min=0.1, max=10.0)),
# Cannot specify both 'sniff_on_connection_fail' and
# 'sniff_on_node_failure'
# Optional('sniff_on_connection_fail', default=False): Boolean(),
# Optional('http_auth'): Any(None, str),
# Favor basic_auth instead
Optional("_transport"): Any(None, str), # ???
},
}
)
def version_max() -> t.Tuple:
"""Return the max version"""
return VERSION_MAX
def version_min() -> t.Tuple:
"""Return the min version"""
return VERSION_MIN
def client_settings() -> t.List[str]:
"""Return the client settings"""
return CLIENT_SETTINGS
def config_settings() -> t.List[str]:
"""
Return only the client settings likely to be used in a config file or at the
command-line.
This means ignoring some that are valid in
:py:class:`~.elasticsearch9.Elasticsearch` but are handled different locally.
Namely, ``api_key`` is handled by :py:class:`~.es_client.builder.OtherArgs`.
"""
ignore = ["api_key"]
settings = []
for setting in CLIENT_SETTINGS:
if setting not in ignore:
settings.append(setting)
return settings
def other_settings() -> t.List[str]:
"""Return the other settings"""
return OTHER_SETTINGS