迁移到 8.0

编辑

客户端有重大更改,需要改变您使用客户端的方式。以下概述了从 7.x 升级到 8.0 时您需要考虑的所有更改。

启用兼容模式并升级 Elasticsearch

编辑

将您的 Elasticsearch 客户端升级到 7.16

$ python -m pip install --upgrade 'elasticsearch>=7.16,<8'

如果您有现有应用程序,请设置 ELASTIC_CLIENT_APIVERSIONING=1 环境变量以启用兼容模式。这将指示 Elasticsearch 服务器接受并响应 7.x 兼容的请求和响应。

完成此操作后,您可以将 Elasticsearch 服务器升级到 8.0.0

升级客户端

编辑

在您使用 7.16 客户端部署应用程序并使用 8.0.0 Elasticsearch 服务器后,您可以将客户端升级到 8.0。

$ python -m pip install --upgrade 'elasticsearch>=8,<9'

移除弃用警告

编辑

您可能会注意到,在将客户端升级到 8.0 后,您的代码会引发错误或 DeprecationWarning,以指示您在使用 8.0 客户端之前需要更改代码的位置。

严格的客户端配置

编辑

以前,当指定要连接的节点时,客户端会使用 scheme="http"host="localhost"port=9200 默认值。从 8.0 开始,这些默认值已被删除,而是需要显式配置 scheme、host 和 port,或者使用 cloud_id 进行配置,以避免混淆连接到哪个 Elasticsearch 实例。

做出此选择的原因是,从 8.0.0 开始,Elasticsearch 默认启用 HTTPS,因此不再假设 https://127.0.0.1:9200 是本地运行的集群。

请参阅关于连接到 Elasticsearch配置 HTTPS的文档。

对于快速示例,使用以下两种配置之一效果最佳

from elasticsearch import Elasticsearch

# If you're connecting to an instance on Elastic Cloud:
client = Elasticsearch(
    cloud_id="cluster-1:dXMa5Fx...",

    # Include your authentication like 'api_key'
    # 'basic_auth', or 'bearer_auth' here.
    basic_auth=("elastic", "<password>")
)

# If you're connecting to an instance hosted elsewhere:
client = Elasticsearch(
    # Notice that the scheme (https://) host (localhost),
    # and port (9200) are explicit here:
    "https://127.0.0.1:9200",

    # Include your authentication like 'api_key'
    # 'basic_auth', or 'bearer_auth' here:
    api_key="api_key"
)

API 的仅关键字参数

编辑

API 过去支持位置参数和关键字参数,但是文档中始终建议使用仅关键字参数。从 7.14 开始,使用位置参数会引发 DeprecationWarning,但仍然有效。

现在从 8.0 开始,为了更好地向前兼容新的 API 选项,API 现在需要仅关键字参数。当尝试使用位置参数时,将引发 TypeError

# 8.0+ SUPPORTED USAGE:
client.indices.get(index="*")

# 7.x UNSUPPORTED USAGE (Don't do this!):
client.indices.get("*")

开始使用 .options() 处理传输参数

编辑

以前,在客户端 API 方法中允许使用一些按请求的选项,如 api_keyignore。从 8.0 开始,所有 API 都已弃用此功能,并且如果不进行更改,则少量 API 可能会以意外方式中断。

受影响的参数有 headersapi_keyhttp_authopaque_idrequest_timeoutignore

from elasticsearch import Elasticsearch

client = Elasticsearch("https://127.0.0.1:9200")

# 8.0+ SUPPORTED USAGE:
client.options(api_key="api_key").search(index="blogs")

# 7.x DEPRECATED USAGE (Don't do this!):
client.search(index="blogs", api_key=("id", "api_key"))

其中一些参数已被重命名,以提高可读性并与其他 API 匹配。ignore 应为 ignore_statushttp_auth 应为 basic_auth

# 8.0+ SUPPORTED USAGES:
client.options(basic_auth=("username", "password")).search(...)
client.options(ignore_status=404).indices.delete(index=...)

# 7.x DEPRECATED USAGES (Don't do this!):
client.search(http_auth=("username", "password"), ...)
client.indices.delete(index=..., ignore=404)

由于客户端 API 和 Elasticsearch 的 API 之间存在冲突,因此此更改是突破性的,并且没有弃用期的 API

  • sql.query 使用 request_timeout
  • security.grant_api_key 使用 api_key
  • render_search_template 使用 params
  • search_template 使用 params

您应立即评估这些参数的使用情况,并开始使用 .options(...) 以避免意外行为。以下示例说明了如何迁移,不再使用带有 security.grant_api_key API 的按请求 api_key

# 8.0+ SUPPORTED USAGE:
resp = (
    client.options(
        # This is the API key being used for the request
        api_key="request-api-key"
    ).security.grant_api_key(
        # This is the API key being granted
        api_key={
            "name": "granted-api-key"
        },
        grant_type="password",
        username="elastic",
        password="changeme"
    )
)

# 7.x DEPRECATED USAGE (Don't do this!):
resp = (
    # This is the API key being used for the request
    client.security.grant_api_key(
        api_key=("request-id", "request-api-key"),
        body={
            # This is the API key being granted
            "api_key": {
                "name": "granted-api-key"
            },
            "grant_type": "password",
            "username": "elastic",
            "password": "changeme"
        }
    )
)

从 8.12 客户端开始,再次完全支持使用 body 参数,这意味着您还可以像这样使用 grant_api_key

# 8.12+ SUPPORTED USAGE:
resp = (
    client.options(
        # This is the API key being used for the request
        api_key="request-api-key"
    ).security.grant_api_key(
        body={
            # This is the API key being granted
            "api_key": {
                "name": "granted-api-key"
            },
            "grant_type": "password",
            "username": "elastic",
            "password": "changeme"
        }
    )
)

API 响应的更改

编辑

在 7.x 及更早版本中,API 方法的返回类型是原始反序列化的响应主体。这意味着无法访问 HTTP 状态代码、标头或传输层的其他信息。

在 8.0.0 中,响应不再是原始反序列化的响应主体,而是一个具有两个属性的对象,即 metabody。关于响应的传输层元数据,如 HTTP 状态、标头、版本以及哪个节点处理了请求,都可以在这里找到

>>> resp = client.search(...)

# Response is not longer a 'dict'
>>> resp
ObjectApiResponse({'took': 1, 'timed_out': False, ...})

# But can still be used like one:
>>> resp["hits"]["total"]
{'value': 5500, 'relation': 'eq'}

>>> resp.keys()
dict_keys(['took', 'timed_out', '_shards', 'hits'])

# HTTP status
>>> resp.meta.status
200

# HTTP headers
>>> resp.meta.headers['content-type']
'application/json'

# HTTP version
>>> resp.meta.http_version
'1.1'

由于响应不再是字典、列表、strbytes 实例,因此在响应对象上调用 isintance() 将返回 False。如果您需要直接访问底层反序列化的响应主体,可以使用 body 属性

>>> resp.body
{'took': 1, 'timed_out': False, ...}

# The response isn't a dict, but resp.body is.
>>> isinstance(resp, dict)
False

>>> isinstance(resp.body, dict)
True

if 条件中仍然可以使用使用 HEAD HTTP 方法的请求,但不能与 is 一起使用。

>>> resp = client.indices.exists(index=...)
>>> resp.body
True

>>> resp is True
False

>>> resp.body is True
True

>>> isinstance(resp, bool)
False

>>> isinstance(resp.body, bool)
True

错误类的更改

编辑

以前,elasticsearch.TransportError 是传输层错误(如超时、连接错误)和 API 层错误(如访问索引时“404 Not Found”)的基类。当您想要捕获 API 错误以检查其响应主体,而不是捕获传输层错误时,这非常令人困惑。

现在在 8.0 中,elasticsearch.TransportErrorelastic_transport.TransportError 的重新定义,并且只会是真正的传输层错误的基类。如果您想捕获 API 层错误,则可以使用新的 elasticsearch.ApiError 基类。

from elasticsearch import TransportError, Elasticsearch

try:
    client.indices.get(index="index-that-does-not-exist")

# In elasticsearch-py v7.x this would capture the resulting
# 'NotFoundError' that would be raised above. But in 8.0.0 this
# 'except TransportError' won't capture 'NotFoundError'.
except TransportError as err:
    print(f"TransportError: {err}")

也已删除 elasticsearch.ElasticsearchException 基类。如果您想捕获可以从库中引发的所有错误,则可以同时捕获 elasticsearch.ApiErrorelasticsearch.TransportError

from elasticsearch import TransportError, ApiError, Elasticsearch

try:
    client.search(...)
# This is the 'except' clause you should use if you *actually* want to
# capture both Transport errors and API errors in one clause:
except (ApiError, TransportError) as err:
    ...

# However I recommend you instead split each error into their own 'except'
# clause so you can have different behavior for TransportErrors. This
# construction wasn't possible in 7.x and earlier.
try:
    client.search(...)
except ApiError as err:
    ... # API errors handled here
except TransportError as err:
    ... # Transport errors handled here

elasticsearch.helpers.errors.BulkIndexErrorelasticsearch.helpers.errors.ScanError 现在使用 Exception 作为基类,而不是 ElasticsearchException

7.x 和 8.0 错误之间的另一个区别在于它们的属性。以前,有 status_codeinfoerror 属性,这些属性不是很实用,因为它们会混合不同的值类型,具体取决于错误是什么以及它来自哪个层(传输层与 API 层)。您可以通过 meta 检查错误并获取响应元数据,并通过 bodyApiError 实例获取响应

from elasticsearch import ApiError, Elasticsearch

try:
    client.indices.get(index="index-that-does-not-exist")
except ApiError as err:
    print(err.meta.status)
    # 404
    print(err.meta.headers)
    # {'content-length': '200', ...}
    print(err.body)
    # {
    #   'error': {
    #     'type': 'index_not_found_exception',
    #     'reason': 'no such index',
    #     'resource.type': 'index_or_alias',
    #     ...
    #   },
    #   'status': 404
    # }