Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-elastic-transport for
openSUSE:Factory checked in at 2024-05-24 19:50:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-elastic-transport (Old)
and /work/SRC/openSUSE:Factory/.python-elastic-transport.new.24587 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-elastic-transport"
Fri May 24 19:50:02 2024 rev:9 rq:1176152 version:8.13.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-elastic-transport/python-elastic-transport.changes
2024-01-21 23:07:17.955193639 +0100
+++
/work/SRC/openSUSE:Factory/.python-elastic-transport.new.24587/python-elastic-transport.changes
2024-05-24 19:50:04.283167292 +0200
@@ -1,0 +2,12 @@
+Thu May 23 08:17:35 UTC 2024 - Markéta Machová <[email protected]>
+
+- Add requests232.patch to fix compatibility with new requests
+
+-------------------------------------------------------------------
+Fri May 17 13:29:03 UTC 2024 - Markéta Machová <[email protected]>
+
+- update to 8.13.0
+ * Support the HTTPX client with asyncio
+ * Added optional orjson serializer support
+
+-------------------------------------------------------------------
Old:
----
elastic-transport-python-8.12.0.tar.gz
New:
----
elastic-transport-python-8.13.0.tar.gz
requests232.patch
BETA DEBUG BEGIN:
New:
- Add requests232.patch to fix compatibility with new requests
BETA DEBUG END:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-elastic-transport.spec ++++++
--- /var/tmp/diff_new_pack.nuittB/_old 2024-05-24 19:50:05.131198258 +0200
+++ /var/tmp/diff_new_pack.nuittB/_new 2024-05-24 19:50:05.135198404 +0200
@@ -18,12 +18,14 @@
%{?sle15_python_module_pythons}
Name: python-elastic-transport
-Version: 8.12.0
+Version: 8.13.0
Release: 0
Summary: Transport classes and utilities shared among Python Elastic
client libraries
License: Apache-2.0
URL: https://github.com/elastic/elastic-transport-python
Source:
https://github.com/elastic/elastic-transport-python/archive/refs/tags/v%{version}.tar.gz#/elastic-transport-python-%{version}.tar.gz
+# PATCH-FIX-UPSTREAM
https://github.com/elastic/elastic-transport-python/pull/164 Fix requests 2.32
compatibility
+Patch: requests232.patch
BuildRequires: %{python_module base >= 3.7}
BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
@@ -31,18 +33,19 @@
Requires: python-certifi
Requires: python-urllib3
BuildArch: noarch
-%if 0%{python_version_nodots} < 37
-Requires: python-dataclasses
-%endif
# SECTION test requirements
BuildRequires: %{python_module aiohttp}
BuildRequires: %{python_module certifi}
-BuildRequires: %{python_module dataclasses if %python-base < 3.7}
+BuildRequires: %{python_module httpx}
+BuildRequires: %{python_module opentelemetry-api}
+BuildRequires: %{python_module opentelemetry-sdk}
+BuildRequires: %{python_module orjson}
BuildRequires: %{python_module pytest-asyncio}
BuildRequires: %{python_module pytest-httpserver}
BuildRequires: %{python_module pytest-mock}
BuildRequires: %{python_module pytest}
BuildRequires: %{python_module requests}
+BuildRequires: %{python_module respx}
BuildRequires: %{python_module trustme}
BuildRequires: %{python_module urllib3}
# /SECTION
@@ -52,7 +55,7 @@
Transport classes and utilities shared among Python Elastic client libraries
%prep
-%setup -q -n elastic-transport-python-%{version}
+%autosetup -p1 -n elastic-transport-python-%{version}
sed -i '/addopts/d' setup.cfg
sed -i 's/from mock/from unittest.mock/' tests/node/test_http_*.py
@@ -68,6 +71,7 @@
donttest="(test_http_aiohttp and not TestAiohttpHttpNode)"
donttest="$donttest or test_tls_versions"
donttest="$donttest or test_assert_fingerprint_in_cert_chain"
+donttest="$donttest or (test_ssl_assert_fingerprint and httpx)"
# gh#elastic/elastic-transport-python#96
donttest="$donttest or
test_url_to_node_config[https://[::1]:0/-https://[::1]:0-]"
%pytest -k "not ($donttest)"
++++++ elastic-transport-python-8.12.0.tar.gz ->
elastic-transport-python-8.13.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/elastic-transport-python-8.12.0/CHANGELOG.md
new/elastic-transport-python-8.13.0/CHANGELOG.md
--- old/elastic-transport-python-8.12.0/CHANGELOG.md 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/CHANGELOG.md 2024-03-27
07:15:44.000000000 +0100
@@ -1,5 +1,10 @@
# Changelog
+## 8.13.0
+
+- Support the HTTPX client with asyncio (#137, contributed by @b4sus)
+- Added optional orjson serializer support (#152)
+
## 8.12.0
- Fix basic auth built from percent-encoded URLs (#143)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/elastic-transport-python-8.12.0/README.md
new/elastic-transport-python-8.13.0/README.md
--- old/elastic-transport-python-8.12.0/README.md 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/README.md 2024-03-27
07:15:44.000000000 +0100
@@ -3,7 +3,7 @@
[](https://pypi.org/elastic-transport)
[](https://pypi.org/elastic-transport)
[](https://pepy.tech/project/elastic-transport)
-[](https://github.com/elastic/elastic-transport-python/actions)
+[](https://github.com/elastic/elastic-transport-python/actions)
Transport classes and utilities shared among Python Elastic client libraries
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/docs/sphinx/installation.rst
new/elastic-transport-python-8.13.0/docs/sphinx/installation.rst
--- old/elastic-transport-python-8.12.0/docs/sphinx/installation.rst
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/docs/sphinx/installation.rst
2024-03-27 07:15:44.000000000 +0100
@@ -10,3 +10,5 @@
Install the ``requests`` package to use
:class:`elastic_transport.RequestsHttpNode`.
Install the ``aiohttp`` package to use
:class:`elastic_transport.AiohttpHttpNode`.
+
+Install the ``httpx`` package to use
:class:`elastic_transport.HttpxAsyncHttpNode`.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/docs/sphinx/nodes.rst
new/elastic-transport-python-8.13.0/docs/sphinx/nodes.rst
--- old/elastic-transport-python-8.12.0/docs/sphinx/nodes.rst 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/docs/sphinx/nodes.rst 2024-03-27
07:15:44.000000000 +0100
@@ -22,6 +22,9 @@
.. autoclass:: AiohttpHttpNode
:members:
+.. autoclass:: HttpxAsyncHttpNode
+ :members:
+
Custom node classes
-------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/docs/sphinx/serializers.rst
new/elastic-transport-python-8.13.0/docs/sphinx/serializers.rst
--- old/elastic-transport-python-8.12.0/docs/sphinx/serializers.rst
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/docs/sphinx/serializers.rst
2024-03-27 07:15:44.000000000 +0100
@@ -9,6 +9,9 @@
.. autoclass:: JsonSerializer
:members:
+.. autoclass:: OrjsonSerializer
+ :members:
+
.. autoclass:: TextSerializer
:members:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/__init__.py
new/elastic-transport-python-8.13.0/elastic_transport/__init__.py
--- old/elastic-transport-python-8.12.0/elastic_transport/__init__.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/__init__.py
2024-03-27 07:15:44.000000000 +0100
@@ -36,10 +36,12 @@
AiohttpHttpNode,
BaseAsyncNode,
BaseNode,
+ HttpxAsyncHttpNode,
RequestsHttpNode,
Urllib3HttpNode,
)
from ._node_pool import NodePool, NodeSelector, RandomSelector,
RoundRobinSelector
+from ._otel import OpenTelemetrySpan
from ._response import ApiResponse as ApiResponse
from ._response import BinaryApiResponse as BinaryApiResponse
from ._response import HeadApiResponse as HeadApiResponse
@@ -70,6 +72,7 @@
"ConnectionTimeout",
"HeadApiResponse",
"HttpHeaders",
+ "HttpxAsyncHttpNode",
"JsonSerializer",
"ListApiResponse",
"NdjsonSerializer",
@@ -77,6 +80,7 @@
"NodePool",
"NodeSelector",
"ObjectApiResponse",
+ "OpenTelemetrySpan",
"RandomSelector",
"RequestsHttpNode",
"RoundRobinSelector",
@@ -95,6 +99,13 @@
"Urllib3HttpNode",
]
+try:
+ from elastic_transport._serializer import OrjsonSerializer # noqa: F401
+
+ __all__.append("OrjsonSerializer")
+except ImportError:
+ pass
+
_logger = logging.getLogger("elastic_transport")
_logger.addHandler(logging.NullHandler())
del _logger
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_async_transport.py
new/elastic-transport-python-8.13.0/elastic_transport/_async_transport.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_async_transport.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_async_transport.py
2024-03-27 07:15:44.000000000 +0100
@@ -30,7 +30,7 @@
Union,
)
-from ._compat import await_if_coro, get_running_loop
+from ._compat import await_if_coro
from ._exceptions import (
ConnectionError,
ConnectionTimeout,
@@ -40,6 +40,7 @@
from ._models import DEFAULT, DefaultType, HttpHeaders, NodeConfig,
SniffOptions
from ._node import AiohttpHttpNode, BaseAsyncNode
from ._node_pool import NodePool, NodeSelector
+from ._otel import OpenTelemetrySpan
from ._serializer import Serializer
from ._transport import (
DEFAULT_CLIENT_META_SERVICE,
@@ -173,7 +174,7 @@
# sniffing. Uses '_sniffing_task' instead.
self._sniffing_lock = None # type: ignore[assignment]
- async def perform_request( # type: ignore[override,return]
+ async def perform_request( # type: ignore[override, return]
self,
method: str,
target: str,
@@ -185,6 +186,7 @@
retry_on_timeout: Union[bool, DefaultType] = DEFAULT,
request_timeout: Union[Optional[float], DefaultType] = DEFAULT,
client_meta: Union[Tuple[Tuple[str, str], ...], DefaultType] = DEFAULT,
+ otel_span: Union[OpenTelemetrySpan, DefaultType] = DEFAULT,
) -> TransportApiResponse:
"""
Perform the actual request. Retrieve a node from the node
@@ -208,6 +210,7 @@
:arg retry_on_timeout: Set to true to retry after timeout errors.
:arg request_timeout: Amount of time to wait for a response to fail
with a timeout error.
:arg client_meta: Extra client metadata key-value pairs to send in the
client meta header.
+ :arg otel_span: OpenTelemetry span used to add metadata to the span.
:returns: Tuple of the :class:`elastic_transport.ApiResponseMeta` with
the deserialized response.
"""
await self._async_call()
@@ -219,6 +222,7 @@
max_retries = resolve_default(max_retries, self.max_retries)
retry_on_timeout = resolve_default(retry_on_timeout,
self.retry_on_timeout)
retry_on_status = resolve_default(retry_on_status,
self.retry_on_status)
+ otel_span = resolve_default(otel_span, OpenTelemetrySpan(None))
if self.meta_header:
request_headers["x-elastic-client-meta"] = ",".join(
@@ -237,6 +241,7 @@
request_body = self.serializers.dumps(
body, mimetype=request_headers["content-type"]
)
+ otel_span.set_db_statement(request_body)
else:
request_body = None
@@ -255,6 +260,7 @@
node: BaseAsyncNode = self.node_pool.get() # type:
ignore[assignment]
start_time = self._loop.time()
try:
+ otel_span.set_node_metadata(node.host, node.port,
node.base_url, target)
resp = await node.perform_request(
method,
target,
@@ -459,6 +465,6 @@
"""
if self._loop is not None:
return # Call at most once!
- self._loop = get_running_loop()
+ self._loop = asyncio.get_running_loop()
if self._sniff_on_start:
await self.sniff(True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_compat.py
new/elastic-transport-python-8.13.0/elastic_transport/_compat.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_compat.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_compat.py
2024-03-27 07:15:44.000000000 +0100
@@ -15,7 +15,6 @@
# specific language governing permissions and limitations
# under the License.
-import asyncio
import inspect
import sys
from pathlib import Path
@@ -30,16 +29,6 @@
else:
from collections import OrderedDict as ordered_dict
-try:
- from asyncio import get_running_loop
-except ImportError:
-
- def get_running_loop() -> asyncio.AbstractEventLoop:
- loop = asyncio.get_event_loop()
- if not loop.is_running():
- raise RuntimeError("no running event loop")
- return loop
-
T = TypeVar("T")
@@ -118,7 +107,6 @@
__all__ = [
"await_if_coro",
- "get_running_loop",
"ordered_dict",
"quote",
"urlparse",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_node/__init__.py
new/elastic-transport-python-8.13.0/elastic_transport/_node/__init__.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_node/__init__.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_node/__init__.py
2024-03-27 07:15:44.000000000 +0100
@@ -18,6 +18,7 @@
from ._base import BaseNode, NodeApiResponse
from ._base_async import BaseAsyncNode
from ._http_aiohttp import AiohttpHttpNode
+from ._http_httpx import HttpxAsyncHttpNode
from ._http_requests import RequestsHttpNode
from ._http_urllib3 import Urllib3HttpNode
@@ -28,4 +29,5 @@
"NodeApiResponse",
"RequestsHttpNode",
"Urllib3HttpNode",
+ "HttpxAsyncHttpNode",
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_node/_base.py
new/elastic-transport-python-8.13.0/elastic_transport/_node/_base.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_node/_base.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_node/_base.py
2024-03-27 07:15:44.000000000 +0100
@@ -269,18 +269,20 @@
_SSL_PROTOCOL_VERSION_TO_TLS_VERSION[_tls_version_value] =
_tls_version_value
# Because we're setting a minimum version we binary OR all the options
together.
- _SSL_PROTOCOL_VERSION_TO_OPTIONS[
- _protocol_value
- ] = _SSL_PROTOCOL_VERSION_DEFAULT | sum(
- getattr(ssl, f"OP_NO_{_attr}", 0) for _attr in
_SSL_PROTOCOL_VERSION_ATTRS[:i]
+ _SSL_PROTOCOL_VERSION_TO_OPTIONS[_protocol_value] = (
+ _SSL_PROTOCOL_VERSION_DEFAULT
+ | sum(
+ getattr(ssl, f"OP_NO_{_attr}", 0)
+ for _attr in _SSL_PROTOCOL_VERSION_ATTRS[:i]
+ )
)
# TLSv1.3 is unique, doesn't have a PROTOCOL_TLSvX counterpart. So we have to
set it manually.
if _HAS_TLS_VERSION:
try:
- _SSL_PROTOCOL_VERSION_TO_TLS_VERSION[
+ _SSL_PROTOCOL_VERSION_TO_TLS_VERSION[ssl.TLSVersion.TLSv1_3] = (
ssl.TLSVersion.TLSv1_3
- ] = ssl.TLSVersion.TLSv1_3
+ )
except AttributeError: # pragma: nocover
pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_aiohttp.py
new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_aiohttp.py
---
old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_aiohttp.py
2024-01-19 09:51:06.000000000 +0100
+++
new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_aiohttp.py
2024-03-27 07:15:44.000000000 +0100
@@ -25,7 +25,7 @@
import warnings
from typing import Optional, Union
-from .._compat import get_running_loop, warn_stacklevel
+from .._compat import warn_stacklevel
from .._exceptions import ConnectionError, ConnectionTimeout, SecurityWarning,
TlsError
from .._models import ApiResponseMeta, HttpHeaders, NodeConfig
from ..client_utils import DEFAULT, DefaultType, client_meta_version
@@ -248,7 +248,7 @@
a chance to set AiohttpHttpNode.loop
"""
if self._loop is None:
- self._loop = get_running_loop()
+ self._loop = asyncio.get_running_loop()
self.session = aiohttp.ClientSession(
headers=self.headers,
skip_auto_headers=("accept", "accept-encoding", "user-agent"),
@@ -259,7 +259,7 @@
limit_per_host=self._connections_per_node,
use_dns_cache=True,
enable_cleanup_closed=True,
- ssl=self._ssl_context,
+ ssl=self._ssl_context or False,
),
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_httpx.py
new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_httpx.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_httpx.py
1970-01-01 01:00:00.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_httpx.py
2024-03-27 07:15:44.000000000 +0100
@@ -0,0 +1,194 @@
+# Licensed to Elasticsearch B.V. under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch B.V. licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import gzip
+import os.path
+import ssl
+import time
+import warnings
+from typing import Optional, Union
+
+from .._compat import warn_stacklevel
+from .._exceptions import ConnectionError, ConnectionTimeout, SecurityWarning,
TlsError
+from .._models import ApiResponseMeta, HttpHeaders, NodeConfig
+from ..client_utils import DEFAULT, DefaultType, client_meta_version
+from ._base import (
+ BUILTIN_EXCEPTIONS,
+ DEFAULT_CA_CERTS,
+ RERAISE_EXCEPTIONS,
+ NodeApiResponse,
+ ssl_context_from_node_config,
+)
+from ._base_async import BaseAsyncNode
+
+try:
+ import httpx
+
+ _HTTPX_AVAILABLE = True
+ _HTTPX_META_VERSION = client_meta_version(httpx.__version__)
+except ImportError:
+ _HTTPX_AVAILABLE = False
+ _HTTPX_META_VERSION = ""
+
+
+class HttpxAsyncHttpNode(BaseAsyncNode):
+ _CLIENT_META_HTTP_CLIENT = ("hx", _HTTPX_META_VERSION)
+
+ def __init__(self, config: NodeConfig):
+ if not _HTTPX_AVAILABLE: # pragma: nocover
+ raise ValueError("You must have 'httpx' installed to use
HttpxNode")
+ super().__init__(config)
+
+ if config.ssl_assert_fingerprint:
+ raise ValueError(
+ "httpx does not support certificate pinning.
https://github.com/encode/httpx/issues/761"
+ )
+
+ # TODO switch to Literal[False] when dropping Python 3.7 support
+ ssl_context: Union[ssl.SSLContext, bool] = False
+ if config.scheme == "https":
+ if config.ssl_context is not None:
+ ssl_context = ssl_context_from_node_config(config)
+ else:
+ ssl_context = ssl_context_from_node_config(config)
+
+ ca_certs = (
+ DEFAULT_CA_CERTS if config.ca_certs is None else
config.ca_certs
+ )
+ if config.verify_certs:
+ if not ca_certs:
+ raise ValueError(
+ "Root certificates are missing for certificate "
+ "validation. Either pass them in using the
ca_certs parameter or "
+ "install certifi to use it automatically."
+ )
+ else:
+ if config.ssl_show_warn:
+ warnings.warn(
+ f"Connecting to {self.base_url!r} using TLS with
verify_certs=False is insecure",
+ stacklevel=warn_stacklevel(),
+ category=SecurityWarning,
+ )
+
+ if ca_certs is not None:
+ if os.path.isfile(ca_certs):
+ ssl_context.load_verify_locations(cafile=ca_certs)
+ elif os.path.isdir(ca_certs):
+ ssl_context.load_verify_locations(capath=ca_certs)
+ else:
+ raise ValueError("ca_certs parameter is not a path")
+
+ # Use client_cert and client_key variables for SSL certificate
configuration.
+ if config.client_cert and not
os.path.isfile(config.client_cert):
+ raise ValueError("client_cert is not a path to a file")
+ if config.client_key and not os.path.isfile(config.client_key):
+ raise ValueError("client_key is not a path to a file")
+ if config.client_cert and config.client_key:
+ ssl_context.load_cert_chain(config.client_cert,
config.client_key)
+ elif config.client_cert:
+ ssl_context.load_cert_chain(config.client_cert)
+
+ self.client = httpx.AsyncClient(
+ base_url=f"{config.scheme}://{config.host}:{config.port}",
+ limits=httpx.Limits(max_connections=config.connections_per_node),
+ verify=ssl_context or False,
+ timeout=config.request_timeout,
+ )
+
+ async def perform_request( # type: ignore[override]
+ self,
+ method: str,
+ target: str,
+ body: Optional[bytes] = None,
+ headers: Optional[HttpHeaders] = None,
+ request_timeout: Union[DefaultType, Optional[float]] = DEFAULT,
+ ) -> NodeApiResponse:
+ resolved_headers = self._headers.copy()
+ if headers:
+ resolved_headers.update(headers)
+
+ if body:
+ if self._http_compress:
+ resolved_body = gzip.compress(body)
+ resolved_headers["content-encoding"] = "gzip"
+ else:
+ resolved_body = body
+ else:
+ resolved_body = None
+
+ try:
+ start = time.perf_counter()
+ if request_timeout is DEFAULT:
+ resp = await self.client.request(
+ method,
+ target,
+ content=resolved_body,
+ headers=dict(resolved_headers),
+ )
+ else:
+ resp = await self.client.request(
+ method,
+ target,
+ content=resolved_body,
+ headers=dict(resolved_headers),
+ timeout=request_timeout,
+ )
+ resp.raise_for_status()
+ response_body = resp.read()
+ duration = time.perf_counter() - start
+ except RERAISE_EXCEPTIONS + BUILTIN_EXCEPTIONS:
+ raise
+ except Exception as e:
+ err: Exception
+ if isinstance(e, (TimeoutError, httpx.TimeoutException)):
+ err = ConnectionTimeout(
+ "Connection timed out during request", errors=(e,)
+ )
+ elif isinstance(e, ssl.SSLError):
+ err = TlsError(str(e), errors=(e,))
+ else:
+ err = ConnectionError(str(e), errors=(e,))
+ self._log_request(
+ method=method,
+ target=target,
+ headers=resolved_headers,
+ body=body,
+ exception=err,
+ )
+ raise err from None
+
+ meta = ApiResponseMeta(
+ resp.status_code,
+ resp.http_version,
+ HttpHeaders(resp.headers),
+ duration,
+ self.config,
+ )
+
+ self._log_request(
+ method=method,
+ target=target,
+ headers=resolved_headers,
+ body=body,
+ meta=meta,
+ response=response_body,
+ )
+
+ return NodeApiResponse(meta, response_body)
+
+ async def close(self) -> None: # type: ignore[override]
+ await self.client.aclose()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_requests.py
new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_requests.py
---
old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_requests.py
2024-01-19 09:51:06.000000000 +0100
+++
new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_requests.py
2024-03-27 07:15:44.000000000 +0100
@@ -75,9 +75,9 @@
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
- pool_kwargs[
- "assert_fingerprint"
- ] = self._node_config.ssl_assert_fingerprint
+ pool_kwargs["assert_fingerprint"] = (
+ self._node_config.ssl_assert_fingerprint
+ )
pool_kwargs["cert_reqs"] = "CERT_NONE"
pool_kwargs["assert_hostname"] = False
@@ -203,9 +203,11 @@
)
prepared_request = self.session.prepare_request(request)
send_kwargs = {
- "timeout": request_timeout
- if request_timeout is not DEFAULT
- else self.config.request_timeout
+ "timeout": (
+ request_timeout
+ if request_timeout is not DEFAULT
+ else self.config.request_timeout
+ )
}
send_kwargs.update(
self.session.merge_environment_settings( # type: ignore[arg-type]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_urllib3.py
new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_urllib3.py
---
old/elastic-transport-python-8.12.0/elastic_transport/_node/_http_urllib3.py
2024-01-19 09:51:06.000000000 +0100
+++
new/elastic-transport-python-8.13.0/elastic_transport/_node/_http_urllib3.py
2024-03-27 07:15:44.000000000 +0100
@@ -24,7 +24,7 @@
try:
from importlib import metadata
except ImportError:
- import importlib_metadata as metadata # type:
ignore[import-not-found,no-redef]
+ import importlib_metadata as metadata # type: ignore[no-redef]
import urllib3
from urllib3.exceptions import ConnectTimeoutError, NewConnectionError,
ReadTimeoutError
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_node_pool.py
new/elastic-transport-python-8.13.0/elastic_transport/_node_pool.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_node_pool.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_node_pool.py
2024-03-27 07:15:44.000000000 +0100
@@ -272,12 +272,10 @@
)
@overload
- def resurrect(self, force: "Literal[True]" = ...) -> BaseNode:
- ...
+ def resurrect(self, force: "Literal[True]" = ...) -> BaseNode: ...
@overload
- def resurrect(self, force: "Literal[False]" = ...) -> Optional[BaseNode]:
- ...
+ def resurrect(self, force: "Literal[False]" = ...) -> Optional[BaseNode]:
...
def resurrect(self, force: bool = False) -> Optional[BaseNode]:
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_otel.py
new/elastic-transport-python-8.13.0/elastic_transport/_otel.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_otel.py
1970-01-01 01:00:00.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_otel.py
2024-03-27 07:15:44.000000000 +0100
@@ -0,0 +1,86 @@
+# Licensed to Elasticsearch B.V. under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch B.V. licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Mapping
+
+if TYPE_CHECKING:
+ from typing import Literal
+
+ from opentelemetry.trace import Span
+
+
+# A list of the Elasticsearch endpoints that qualify as "search" endpoints.
The search query in
+# the request body may be captured for these endpoints, depending on the body
capture strategy.
+SEARCH_ENDPOINTS = (
+ "search",
+ "async_search.submit",
+ "msearch",
+ "eql.search",
+ "esql.query",
+ "terms_enum",
+ "search_template",
+ "msearch_template",
+ "render_search_template",
+)
+
+
+class OpenTelemetrySpan:
+ def __init__(
+ self,
+ otel_span: Span | None,
+ endpoint_id: str | None = None,
+ # TODO import Literal at the top-level when dropping Python 3.7
+ body_strategy: 'Literal["omit", "raw"]' = "omit",
+ ):
+ self.otel_span = otel_span
+ self.body_strategy = body_strategy
+ self.endpoint_id = endpoint_id
+
+ def set_node_metadata(
+ self, host: str, port: int, base_url: str, target: str
+ ) -> None:
+ if self.otel_span is None:
+ return
+
+ # url.full does not contain auth info which is passed as headers
+ self.otel_span.set_attribute("url.full", base_url + target)
+ self.otel_span.set_attribute("server.address", host)
+ self.otel_span.set_attribute("server.port", port)
+
+ def set_elastic_cloud_metadata(self, headers: Mapping[str, str]) -> None:
+ if self.otel_span is None:
+ return
+
+ cluster_name = headers.get("X-Found-Handling-Cluster")
+ if cluster_name is not None:
+ self.otel_span.set_attribute("db.elasticsearch.cluster.name",
cluster_name)
+ node_name = headers.get("X-Found-Handling-Instance")
+ if node_name is not None:
+ self.otel_span.set_attribute("db.elasticsearch.node.name",
node_name)
+
+ def set_db_statement(self, serialized_body: bytes) -> None:
+ if self.otel_span is None:
+ return
+
+ if self.body_strategy == "omit":
+ return
+ elif self.body_strategy == "raw" and self.endpoint_id in
SEARCH_ENDPOINTS:
+ self.otel_span.set_attribute(
+ "db.statement", serialized_body.decode("utf-8")
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_response.py
new/elastic-transport-python-8.13.0/elastic_transport/_response.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_response.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_response.py
2024-03-27 07:15:44.000000000 +0100
@@ -151,12 +151,10 @@
return iter(self.body)
@overload
- def __getitem__(self, item: slice) -> bytes:
- ...
+ def __getitem__(self, item: slice) -> bytes: ...
@overload
- def __getitem__(self, item: int) -> int:
- ...
+ def __getitem__(self, item: int) -> int: ...
def __getitem__(self, item: Union[int, slice]) -> Union[int, bytes]:
return self.body[item]
@@ -201,12 +199,10 @@
"""API responses which are a list of items. Can be NDJSON or a JSON list"""
@overload
- def __getitem__(self, item: slice) -> List[_ListItemBodyType]:
- ...
+ def __getitem__(self, item: slice) -> List[_ListItemBodyType]: ...
@overload
- def __getitem__(self, item: int) -> _ListItemBodyType:
- ...
+ def __getitem__(self, item: int) -> _ListItemBodyType: ...
def __getitem__(
self, item: Union[int, slice]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_serializer.py
new/elastic-transport-python-8.13.0/elastic_transport/_serializer.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_serializer.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_serializer.py
2024-03-27 07:15:44.000000000 +0100
@@ -24,8 +24,15 @@
from ._exceptions import SerializationError
+try:
+ import orjson
+except ModuleNotFoundError:
+ orjson = None # type: ignore[assignment]
+
class Serializer:
+ """Serializer interface."""
+
mimetype: ClassVar[str]
def loads(self, data: bytes) -> Any: # pragma: nocover
@@ -36,6 +43,8 @@
class TextSerializer(Serializer):
+ """Text serializer to and from UTF-8."""
+
mimetype: ClassVar[str] = "text/*"
def loads(self, data: bytes) -> str:
@@ -62,6 +71,8 @@
class JsonSerializer(Serializer):
+ """JSON serializer relying on the standard library json module."""
+
mimetype: ClassVar[str] = "application/json"
def default(self, data: Any) -> Any:
@@ -81,14 +92,15 @@
).encode("utf-8", "surrogatepass")
def json_loads(self, data: bytes) -> Any:
+ return json.loads(data)
+
+ def loads(self, data: bytes) -> Any:
# Sometimes responses use Content-Type: json but actually
# don't contain any data. We should return something instead
# of erroring in these cases.
if data == b"":
return None
- return json.loads(data)
- def loads(self, data: bytes) -> Any:
try:
return self.json_loads(data)
except (ValueError, TypeError) as e:
@@ -115,7 +127,26 @@
)
+if orjson is not None:
+
+ class OrjsonSerializer(JsonSerializer):
+ """JSON serializer relying on the orjson package.
+
+ Only available if orjson if installed. It is faster, especially for
vectors, but is also stricter.
+ """
+
+ def json_dumps(self, data: Any) -> bytes:
+ return orjson.dumps(
+ data, default=self.default, option=orjson.OPT_SERIALIZE_NUMPY
+ )
+
+ def json_loads(self, data: bytes) -> Any:
+ return orjson.loads(data)
+
+
class NdjsonSerializer(JsonSerializer):
+ """Newline delimited JSON (NDJSON) serializer relying on the standard
library json module."""
+
mimetype: ClassVar[str] = "application/x-ndjson"
def loads(self, data: bytes) -> Any:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_transport.py
new/elastic-transport-python-8.13.0/elastic_transport/_transport.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_transport.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_transport.py
2024-03-27 07:15:44.000000000 +0100
@@ -52,8 +52,15 @@
NodeConfig,
SniffOptions,
)
-from ._node import AiohttpHttpNode, BaseNode, RequestsHttpNode, Urllib3HttpNode
+from ._node import (
+ AiohttpHttpNode,
+ BaseNode,
+ HttpxAsyncHttpNode,
+ RequestsHttpNode,
+ Urllib3HttpNode,
+)
from ._node_pool import NodePool, NodeSelector
+from ._otel import OpenTelemetrySpan
from ._serializer import DEFAULT_SERIALIZERS, Serializer, SerializerCollection
from ._version import __version__
from .client_utils import client_meta_version, resolve_default
@@ -63,6 +70,7 @@
"urllib3": Urllib3HttpNode,
"requests": RequestsHttpNode,
"aiohttp": AiohttpHttpNode,
+ "httpxasync": HttpxAsyncHttpNode,
}
# These are HTTP status errors that shouldn't be considered
# 'errors' for marking a node as dead. These errors typically
@@ -114,7 +122,7 @@
] = None,
meta_header: bool = True,
client_meta_service: Tuple[str, str] = DEFAULT_CLIENT_META_SERVICE,
- ) -> None:
+ ):
"""
:arg node_configs: List of 'NodeConfig' instances to create initial
set of nodes.
:arg node_class: subclass of :class:`~elastic_transport.BaseNode` to
use
@@ -257,6 +265,7 @@
retry_on_timeout: Union[bool, DefaultType] = DEFAULT,
request_timeout: Union[Optional[float], DefaultType] = DEFAULT,
client_meta: Union[Tuple[Tuple[str, str], ...], DefaultType] = DEFAULT,
+ otel_span: Union[OpenTelemetrySpan, DefaultType] = DEFAULT,
) -> TransportApiResponse:
"""
Perform the actual request. Retrieve a node from the node
@@ -280,6 +289,8 @@
:arg retry_on_timeout: Set to true to retry after timeout errors.
:arg request_timeout: Amount of time to wait for a response to fail
with a timeout error.
:arg client_meta: Extra client metadata key-value pairs to send in the
client meta header.
+ :arg otel_span: OpenTelemetry span used to add metadata to the span.
+
:returns: Tuple of the :class:`elastic_transport.ApiResponseMeta` with
the deserialized response.
"""
if headers is DEFAULT:
@@ -289,6 +300,7 @@
max_retries = resolve_default(max_retries, self.max_retries)
retry_on_timeout = resolve_default(retry_on_timeout,
self.retry_on_timeout)
retry_on_status = resolve_default(retry_on_status,
self.retry_on_status)
+ otel_span = resolve_default(otel_span, OpenTelemetrySpan(None))
if self.meta_header:
request_headers["x-elastic-client-meta"] = ",".join(
@@ -307,6 +319,7 @@
request_body = self.serializers.dumps(
body, mimetype=request_headers["content-type"]
)
+ otel_span.set_db_statement(request_body)
else:
request_body = None
@@ -325,7 +338,8 @@
node = self.node_pool.get()
start_time = time.time()
try:
- meta, raw_data = node.perform_request(
+ otel_span.set_node_metadata(node.host, node.port,
node.base_url, target)
+ resp = node.perform_request(
method,
target,
body=request_body,
@@ -338,26 +352,32 @@
method,
node.base_url,
target,
- meta.status,
+ resp.meta.status,
time.time() - start_time,
)
)
if method != "HEAD":
- data = self.serializers.loads(raw_data, meta.mimetype)
+ body = self.serializers.loads(resp.body,
resp.meta.mimetype)
else:
- data = None
+ body = None
- if meta.status in retry_on_status:
+ if resp.meta.status in retry_on_status:
retry = True
# Keep track of the last response we see so we can return
# it in case the retried request returns with a transport
error.
- last_response = TransportApiResponse(meta, data)
+ last_response = TransportApiResponse(resp.meta, body)
except TransportError as e:
_logger.info(
"%s %s%s [status:%s duration:%.3fs]"
- % (method, node.base_url, target, "N/A", time.time() -
start_time)
+ % (
+ method,
+ node.base_url,
+ target,
+ "N/A",
+ time.time() - start_time,
+ )
)
if isinstance(e, ConnectionTimeout):
@@ -404,8 +424,8 @@
# If we got back a response we need to check if that status
# is indicative of a healthy node even if it's a non-2XX status
if (
- 200 <= meta.status < 299
- or meta.status in NOT_DEAD_NODE_HTTP_STATUSES
+ 200 <= resp.meta.status < 299
+ or resp.meta.status in NOT_DEAD_NODE_HTTP_STATUSES
):
self.node_pool.mark_live(node)
else:
@@ -422,11 +442,11 @@
# We either got a response we're happy with or
# we've exhausted all of our retries so we return it.
if not retry or attempt >= max_retries:
- return TransportApiResponse(meta, data)
+ return TransportApiResponse(resp.meta, body)
else:
_logger.warning(
"Retrying request after non-successful status %d
(attempt %d of %d)",
- meta.status,
+ resp.meta.status,
attempt,
max_retries,
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/elastic_transport/_version.py
new/elastic-transport-python-8.13.0/elastic_transport/_version.py
--- old/elastic-transport-python-8.12.0/elastic_transport/_version.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/elastic_transport/_version.py
2024-03-27 07:15:44.000000000 +0100
@@ -15,4 +15,4 @@
# specific language governing permissions and limitations
# under the License.
-__version__ = "8.12.0"
+__version__ = "8.13.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/elastic-transport-python-8.12.0/noxfile.py
new/elastic-transport-python-8.13.0/noxfile.py
--- old/elastic-transport-python-8.12.0/noxfile.py 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/noxfile.py 2024-03-27
07:15:44.000000000 +0100
@@ -29,7 +29,7 @@
@nox.session()
def format(session):
- session.install("black~=23.0", "isort", "pyupgrade")
+ session.install("black~=24.0", "isort", "pyupgrade")
session.run("black", "--target-version=py37", *SOURCE_FILES)
session.run("isort", *SOURCE_FILES)
session.run("python", "utils/license-headers.py", "fix", *SOURCE_FILES)
@@ -41,7 +41,7 @@
def lint(session):
session.install(
"flake8",
- "black~=23.0",
+ "black~=24.0",
"isort",
"mypy==1.7.1",
"types-requests",
@@ -54,7 +54,7 @@
session.install(".[develop]")
session.run("black", "--check", "--target-version=py37", *SOURCE_FILES)
session.run("isort", "--check", *SOURCE_FILES)
- session.run("flake8", "--ignore=E501,W503,E203", *SOURCE_FILES)
+ session.run("flake8", "--ignore=E501,W503,E203,E704", *SOURCE_FILES)
session.run("python", "utils/license-headers.py", "check", *SOURCE_FILES)
session.run("mypy", "--strict", "--show-error-codes", "elastic_transport/")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/elastic-transport-python-8.12.0/setup.py
new/elastic-transport-python-8.13.0/setup.py
--- old/elastic-transport-python-8.12.0/setup.py 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/setup.py 2024-03-27
07:15:44.000000000 +0100
@@ -52,7 +52,6 @@
install_requires=[
"urllib3>=1.26.2, <3",
"certifi",
- "dataclasses; python_version<'3.7'",
"importlib-metadata; python_version<'3.8'",
],
python_requires=">=3.7",
@@ -67,6 +66,11 @@
"mock",
"requests",
"aiohttp",
+ "httpx",
+ "respx",
+ "opentelemetry-api",
+ "opentelemetry-sdk",
+ "orjson",
# Override Read the Docs default (sphinx<2)
"sphinx>2",
"furo",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/tests/async_/test_async_transport.py
new/elastic-transport-python-8.13.0/tests/async_/test_async_transport.py
--- old/elastic-transport-python-8.12.0/tests/async_/test_async_transport.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/async_/test_async_transport.py
2024-03-27 07:15:44.000000000 +0100
@@ -30,6 +30,7 @@
AsyncTransport,
ConnectionError,
ConnectionTimeout,
+ HttpxAsyncHttpNode,
NodeConfig,
RequestsHttpNode,
SniffingError,
@@ -38,7 +39,6 @@
TransportWarning,
Urllib3HttpNode,
)
-from elastic_transport._compat import get_running_loop
from elastic_transport._node._base import DEFAULT_USER_AGENT
from elastic_transport.client_utils import DEFAULT
from tests.conftest import AsyncDummyNode
@@ -303,11 +303,14 @@
t = AsyncTransport([NodeConfig("http", "localhost", 80)],
node_class="aiohttp")
assert isinstance(t.node_pool.get(), AiohttpHttpNode)
+ t = AsyncTransport([NodeConfig("http", "localhost", 80)],
node_class="httpxasync")
+ assert isinstance(t.node_pool.get(), HttpxAsyncHttpNode)
+
with pytest.raises(ValueError) as e:
AsyncTransport([NodeConfig("http", "localhost", 80)],
node_class="huh?")
assert str(e.value) == (
"Unknown option for node_class: 'huh?'. "
- "Available options are: 'aiohttp', 'requests', 'urllib3'"
+ "Available options are: 'aiohttp', 'httpxasync', 'requests', 'urllib3'"
)
@@ -337,21 +340,29 @@
@pytest.mark.parametrize(
- "node_class",
- ["aiohttp", AiohttpHttpNode],
+ "node_class, client_short_name",
+ [
+ ("aiohttp", "ai"),
+ (AiohttpHttpNode, "ai"),
+ ("httpxasync", "hx"),
+ (HttpxAsyncHttpNode, "hx"),
+ ],
)
@pytest.mark.asyncio
-async def test_transport_client_meta_node_class(node_class):
+async def test_transport_client_meta_node_class(node_class, client_short_name):
t = AsyncTransport([NodeConfig("http", "localhost", 80)],
node_class=node_class)
assert (
t._transport_client_meta[3] ==
t.node_pool.node_class._CLIENT_META_HTTP_CLIENT
)
- assert t._transport_client_meta[3][0] == "ai"
+ assert t._transport_client_meta[3][0] == client_short_name
assert re.match(
- r"^et=[0-9.]+p?,py=[0-9.]+p?,t=[0-9.]+p?,ai=[0-9.]+p?$",
+
rf"^et=[0-9.]+p?,py=[0-9.]+p?,t=[0-9.]+p?,{client_short_name}=[0-9.]+p?$",
",".join(f"{k}={v}" for k, v in t._transport_client_meta),
)
+
[email protected]
+async def test_transport_default_client_meta_node_class():
# Defaults to aiohttp
t = AsyncTransport(
[NodeConfig("http", "localhost", 80)], client_meta_service=("es",
"8.0.0p")
@@ -520,7 +531,7 @@
NodeConfig("http", "localhost", 81),
]
- loop = get_running_loop()
+ loop = asyncio.get_running_loop()
sniffed_at = 0.0
# Test that we accept both sync and async sniff_callbacks
@@ -638,7 +649,7 @@
sniff_callback=sniff_callback,
)
- loop = get_running_loop()
+ loop = asyncio.get_running_loop()
start = loop.time()
async def run_requests():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/tests/node/test_base.py
new/elastic-transport-python-8.13.0/tests/node/test_base.py
--- old/elastic-transport-python-8.12.0/tests/node/test_base.py 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/node/test_base.py 2024-03-27
07:15:44.000000000 +0100
@@ -19,6 +19,7 @@
from elastic_transport import (
AiohttpHttpNode,
+ HttpxAsyncHttpNode,
NodeConfig,
RequestsHttpNode,
Urllib3HttpNode,
@@ -27,7 +28,7 @@
@pytest.mark.parametrize(
- "node_cls", [Urllib3HttpNode, RequestsHttpNode, AiohttpHttpNode]
+ "node_cls", [Urllib3HttpNode, RequestsHttpNode, AiohttpHttpNode,
HttpxAsyncHttpNode]
)
def test_unknown_parameter(node_cls):
with pytest.raises(TypeError):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/tests/node/test_http_httpx.py
new/elastic-transport-python-8.13.0/tests/node/test_http_httpx.py
--- old/elastic-transport-python-8.12.0/tests/node/test_http_httpx.py
1970-01-01 01:00:00.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/node/test_http_httpx.py
2024-03-27 07:15:44.000000000 +0100
@@ -0,0 +1,157 @@
+# Licensed to Elasticsearch B.V. under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch B.V. licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import gzip
+import ssl
+import warnings
+
+import pytest
+import respx
+
+from elastic_transport import HttpxAsyncHttpNode, NodeConfig
+from elastic_transport._node._base import DEFAULT_USER_AGENT
+
+
+def create_node(node_config: NodeConfig):
+ return HttpxAsyncHttpNode(node_config)
+
+
+class TestHttpxAsyncNodeCreation:
+ def test_ssl_context(self):
+ ssl_context = ssl.create_default_context()
+ with warnings.catch_warnings(record=True) as w:
+ node = create_node(
+ NodeConfig(
+ scheme="https",
+ host="localhost",
+ port=80,
+ ssl_context=ssl_context,
+ )
+ )
+ assert node.client._transport._pool._ssl_context is ssl_context
+ assert len(w) == 0
+
+ def test_uses_https_if_verify_certs_is_off(self):
+ with warnings.catch_warnings(record=True) as w:
+ _ = create_node(NodeConfig("https", "localhost", 443,
verify_certs=False))
+ assert (
+ str(w[0].message)
+ == "Connecting to 'https://localhost:443' using TLS with
verify_certs=False is insecure"
+ )
+
+ def test_no_warn_when_uses_https_if_verify_certs_is_off(self):
+ with warnings.catch_warnings(record=True) as w:
+ _ = create_node(
+ NodeConfig(
+ "https",
+ "localhost",
+ 443,
+ verify_certs=False,
+ ssl_show_warn=False,
+ )
+ )
+ assert 0 == len(w)
+
+ def test_ca_certs_with_verify_ssl_false_raises_error(self):
+ with pytest.raises(ValueError) as exc:
+ create_node(
+ NodeConfig(
+ "https",
+ "localhost",
+ 443,
+ ca_certs="/ca/certs",
+ verify_certs=False,
+ )
+ )
+ assert (
+ str(exc.value) == "You cannot use 'ca_certs' when
'verify_certs=False'"
+ )
+
+
[email protected]
+class TestHttpxAsyncNode:
+ @respx.mock
+ async def test_simple_request(self):
+ node = create_node(NodeConfig(scheme="http", host="localhost",
port=80))
+ respx.get("http://localhost/index")
+ await node.perform_request(
+ "GET", "/index", b"hello world", headers={"key": "value"}
+ )
+ request = respx.calls.last.request
+ assert request.content == b"hello world"
+ assert {
+ "key": "value",
+ "connection": "keep-alive",
+ "user-agent": DEFAULT_USER_AGENT,
+ }.items() <= request.headers.items()
+
+ @respx.mock
+ async def test_compression(self):
+ node = create_node(
+ NodeConfig(scheme="http", host="localhost", port=80,
http_compress=True)
+ )
+ respx.get("http://localhost/index")
+ await node.perform_request("GET", "/index", b"hello world")
+ request = respx.calls.last.request
+ assert gzip.decompress(request.content) == b"hello world"
+ assert {"content-encoding": "gzip"}.items() <= request.headers.items()
+
+ @respx.mock
+ async def test_default_timeout(self):
+ node = create_node(
+ NodeConfig(scheme="http", host="localhost", port=80,
request_timeout=10)
+ )
+ respx.get("http://localhost/index")
+ await node.perform_request("GET", "/index", b"hello world")
+ request = respx.calls.last.request
+ assert request.extensions["timeout"]["connect"] == 10
+
+ @respx.mock
+ async def test_overwritten_timeout(self):
+ node = create_node(
+ NodeConfig(scheme="http", host="localhost", port=80,
request_timeout=10)
+ )
+ respx.get("http://localhost/index")
+ await node.perform_request("GET", "/index", b"hello world",
request_timeout=15)
+ request = respx.calls.last.request
+ assert request.extensions["timeout"]["connect"] == 15
+
+ @respx.mock
+ async def test_merge_headers(self):
+ node = create_node(
+ NodeConfig("http", "localhost", 80, headers={"h1": "v1", "h2":
"v2"})
+ )
+ respx.get("http://localhost/index")
+ await node.perform_request(
+ "GET", "/index", b"hello world", headers={"h2": "v2p", "h3": "v3"}
+ )
+ request = respx.calls.last.request
+ assert request.headers["h1"] == "v1"
+ assert request.headers["h2"] == "v2p"
+ assert request.headers["h3"] == "v3"
+
+
+def test_ssl_assert_fingerprint(httpbin_cert_fingerprint):
+ with pytest.raises(ValueError, match="httpx does not support certificate
pinning"):
+ HttpxAsyncHttpNode(
+ NodeConfig(
+ scheme="https",
+ host="httpbin.org",
+ port=443,
+ ssl_assert_fingerprint=httpbin_cert_fingerprint,
+ )
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/tests/node/test_tls_versions.py
new/elastic-transport-python-8.13.0/tests/node/test_tls_versions.py
--- old/elastic-transport-python-8.12.0/tests/node/test_tls_versions.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/node/test_tls_versions.py
2024-03-27 07:15:44.000000000 +0100
@@ -23,6 +23,7 @@
from elastic_transport import (
AiohttpHttpNode,
+ HttpxAsyncHttpNode,
NodeConfig,
RequestsHttpNode,
TlsError,
@@ -36,7 +37,8 @@
TLSv1_2_URL = "https://tls-v1-2.badssl.com:1012"
node_classes = pytest.mark.parametrize(
- "node_class", [AiohttpHttpNode, Urllib3HttpNode, RequestsHttpNode]
+ "node_class",
+ [AiohttpHttpNode, Urllib3HttpNode, RequestsHttpNode, HttpxAsyncHttpNode],
)
supported_version_params = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/elastic-transport-python-8.12.0/tests/test_otel.py
new/elastic-transport-python-8.13.0/tests/test_otel.py
--- old/elastic-transport-python-8.12.0/tests/test_otel.py 1970-01-01
01:00:00.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/test_otel.py 2024-03-27
07:15:44.000000000 +0100
@@ -0,0 +1,99 @@
+# Licensed to Elasticsearch B.V. under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Elasticsearch B.V. licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from opentelemetry.sdk.trace import TracerProvider, export
+from opentelemetry.sdk.trace.export.in_memory_span_exporter import
InMemorySpanExporter
+
+from elastic_transport import JsonSerializer
+from elastic_transport._otel import OpenTelemetrySpan
+
+
+def setup_tracing():
+ tracer_provider = TracerProvider()
+ memory_exporter = InMemorySpanExporter()
+ span_processor = export.SimpleSpanProcessor(memory_exporter)
+ tracer_provider.add_span_processor(span_processor)
+ tracer = tracer_provider.get_tracer(__name__)
+
+ return tracer, memory_exporter
+
+
+def test_no_span():
+ # With telemetry disabled, those calls should not raise
+ span = OpenTelemetrySpan(None)
+ span.set_db_statement(JsonSerializer().dumps({"timeout": "1m"}))
+ span.set_node_metadata(
+ "localhost",
+ 9200,
+ "http://localhost:9200/",
+ "_ml/anomaly_detectors/my-job/_open",
+ )
+ span.set_elastic_cloud_metadata(
+ {
+ "X-Found-Handling-Cluster": "e9106fc68e3044f0b1475b04bf4ffd5f",
+ "X-Found-Handling-Instance": "instance-0000000001",
+ }
+ )
+
+
+def test_detailed_span():
+ tracer, memory_exporter = setup_tracing()
+ with tracer.start_as_current_span("ml.open_job") as otel_span:
+ span = OpenTelemetrySpan(
+ otel_span,
+ endpoint_id="my-job/_open",
+ body_strategy="omit",
+ )
+
+ span.set_db_statement(JsonSerializer().dumps({"timeout": "1m"}))
+ span.set_node_metadata(
+ "localhost",
+ 9200,
+ "http://localhost:9200/",
+ "_ml/anomaly_detectors/my-job/_open",
+ )
+ span.set_elastic_cloud_metadata(
+ {
+ "X-Found-Handling-Cluster": "e9106fc68e3044f0b1475b04bf4ffd5f",
+ "X-Found-Handling-Instance": "instance-0000000001",
+ }
+ )
+
+ spans = memory_exporter.get_finished_spans()
+ assert len(spans) == 1
+ assert spans[0].name == "ml.open_job"
+ assert spans[0].attributes == {
+ "url.full": "http://localhost:9200/_ml/anomaly_detectors/my-job/_open",
+ "server.address": "localhost",
+ "server.port": 9200,
+ "db.elasticsearch.cluster.name": "e9106fc68e3044f0b1475b04bf4ffd5f",
+ "db.elasticsearch.node.name": "instance-0000000001",
+ }
+
+
+def test_db_statement():
+ tracer, memory_exporter = setup_tracing()
+ with tracer.start_as_current_span("search") as otel_span:
+ span = OpenTelemetrySpan(otel_span, endpoint_id="search",
body_strategy="raw")
+ span.set_db_statement(JsonSerializer().dumps({"query": {"match_all":
{}}}))
+
+ spans = memory_exporter.get_finished_spans()
+ assert len(spans) == 1
+ assert spans[0].name == "search"
+ assert spans[0].attributes == {
+ "db.statement": '{"query":{"match_all":{}}}',
+ }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/tests/test_package.py
new/elastic-transport-python-8.13.0/tests/test_package.py
--- old/elastic-transport-python-8.12.0/tests/test_package.py 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/test_package.py 2024-03-27
07:15:44.000000000 +0100
@@ -25,8 +25,11 @@
@modules
def test__all__sorted(module):
- print(sorted(module.__all__))
- assert module.__all__ == sorted(module.__all__)
+ module_all = module.__all__.copy()
+ # Optional dependencies are added at the end
+ if "OrjsonSerializer" in module_all:
+ module_all.remove("OrjsonSerializer")
+ assert module_all == sorted(module_all)
@modules
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/tests/test_serializer.py
new/elastic-transport-python-8.13.0/tests/test_serializer.py
--- old/elastic-transport-python-8.12.0/tests/test_serializer.py
2024-01-19 09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/test_serializer.py
2024-03-27 07:15:44.000000000 +0100
@@ -24,6 +24,7 @@
from elastic_transport import (
JsonSerializer,
NdjsonSerializer,
+ OrjsonSerializer,
SerializationError,
SerializerCollection,
TextSerializer,
@@ -33,66 +34,86 @@
serializers = SerializerCollection(DEFAULT_SERIALIZERS)
-def test_date_serialization():
- assert b'{"d":"2010-10-01"}' == JsonSerializer().dumps({"d": date(2010,
10, 1)})
[email protected](params=[JsonSerializer, OrjsonSerializer])
+def json_serializer(request: pytest.FixtureRequest):
+ yield request.param()
-def test_decimal_serialization():
- assert b'{"d":3.8}' == JsonSerializer().dumps({"d": Decimal("3.8")})
+def test_date_serialization(json_serializer):
+ assert b'{"d":"2010-10-01"}' == json_serializer.dumps({"d": date(2010, 10,
1)})
-def test_uuid_serialization():
- assert b'{"d":"00000000-0000-0000-0000-000000000003"}' ==
JsonSerializer().dumps(
+def test_decimal_serialization(json_serializer):
+ assert b'{"d":3.8}' == json_serializer.dumps({"d": Decimal("3.8")})
+
+
+def test_uuid_serialization(json_serializer):
+ assert b'{"d":"00000000-0000-0000-0000-000000000003"}' ==
json_serializer.dumps(
{"d": uuid.UUID("00000000-0000-0000-0000-000000000003")}
)
def test_serializes_nan():
assert b'{"d":NaN}' == JsonSerializer().dumps({"d": float("NaN")})
+ # NaN is invalid JSON, and orjson silently converts it to null
+ assert b'{"d":null}' == OrjsonSerializer().dumps({"d": float("NaN")})
-def test_raises_serialization_error_on_dump_error():
+def test_raises_serialization_error_on_dump_error(json_serializer):
with pytest.raises(SerializationError):
- JsonSerializer().dumps(object())
+ json_serializer.dumps(object())
with pytest.raises(SerializationError):
TextSerializer().dumps({})
-def test_raises_serialization_error_on_load_error():
+def test_raises_serialization_error_on_load_error(json_serializer):
with pytest.raises(SerializationError):
- JsonSerializer().loads(object())
+ json_serializer.loads(object())
with pytest.raises(SerializationError):
- JsonSerializer().loads(b"{{")
+ json_serializer.loads(b"{{")
-def test_unicode_is_handled():
- j = JsonSerializer()
+def test_json_unicode_is_handled(json_serializer):
assert (
- j.dumps({"ä½ å¥½": "ä½ å¥½"})
+ json_serializer.dumps({"ä½ å¥½": "ä½ å¥½"})
== b'{"\xe4\xbd\xa0\xe5\xa5\xbd":"\xe4\xbd\xa0\xe5\xa5\xbd"}'
)
- assert j.loads(b'{"\xe4\xbd\xa0\xe5\xa5\xbd":"\xe4\xbd\xa0\xe5\xa5\xbd"}')
== {
- "ä½ å¥½": "ä½ å¥½"
- }
+ assert json_serializer.loads(
+ b'{"\xe4\xbd\xa0\xe5\xa5\xbd":"\xe4\xbd\xa0\xe5\xa5\xbd"}'
+ ) == {"ä½ å¥½": "ä½ å¥½"}
- t = TextSerializer()
- assert t.dumps("ä½ å¥½") == b"\xe4\xbd\xa0\xe5\xa5\xbd"
- assert t.loads(b"\xe4\xbd\xa0\xe5\xa5\xbd") == "ä½ å¥½"
+def test_text_unicode_is_handled():
+ text_serializer = TextSerializer()
+ assert text_serializer.dumps("ä½ å¥½") == b"\xe4\xbd\xa0\xe5\xa5\xbd"
+ assert text_serializer.loads(b"\xe4\xbd\xa0\xe5\xa5\xbd") == "ä½ å¥½"
-def test_unicode_surrogates_handled():
- j = JsonSerializer()
+
+def test_json_unicode_surrogates_handled():
assert (
- j.dumps({"key": "ä½ å¥½\uda6a"})
+ JsonSerializer().dumps({"key": "ä½ å¥½\uda6a"})
== b'{"key":"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa"}'
)
- assert j.loads(b'{"key":"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa"}') == {
- "key": "ä½ å¥½\uda6a"
- }
-
- t = TextSerializer()
- assert t.dumps("ä½ å¥½\uda6a") == b"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa"
- assert t.loads(b"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa") == "ä½ å¥½\uda6a"
+ assert JsonSerializer().loads(
+ b'{"key":"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa"}'
+ ) == {"key": "ä½ å¥½\uda6a"}
+
+ # orjson is strict about UTF-8
+ with pytest.raises(SerializationError):
+ OrjsonSerializer().dumps({"key": "ä½ å¥½\uda6a"})
+
+ with pytest.raises(SerializationError):
+
OrjsonSerializer().loads(b'{"key":"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa"}')
+
+
+def test_text_unicode_surrogates_handled(json_serializer):
+ text_serializer = TextSerializer()
+ assert (
+ text_serializer.dumps("ä½ å¥½\uda6a") ==
b"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa"
+ )
+ assert (
+ text_serializer.loads(b"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa") == "ä½
好\uda6a"
+ )
def test_deserializes_json_by_default():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/elastic-transport-python-8.12.0/tests/test_transport.py
new/elastic-transport-python-8.13.0/tests/test_transport.py
--- old/elastic-transport-python-8.12.0/tests/test_transport.py 2024-01-19
09:51:06.000000000 +0100
+++ new/elastic-transport-python-8.13.0/tests/test_transport.py 2024-03-27
07:15:44.000000000 +0100
@@ -327,7 +327,7 @@
Transport([NodeConfig("http", "localhost", 80)], node_class="huh?")
assert str(e.value) == (
"Unknown option for node_class: 'huh?'. "
- "Available options are: 'aiohttp', 'requests', 'urllib3'"
+ "Available options are: 'aiohttp', 'httpxasync', 'requests', 'urllib3'"
)
++++++ requests232.patch ++++++
>From d49d5dda344f0a458c020a8a3c0032480e6b57d5 Mon Sep 17 00:00:00 2001
From: Quentin Pradet <[email protected]>
Date: Thu, 23 May 2024 11:48:10 +0400
Subject: [PATCH 1/3] Fix requests 2.32 compatibility
---
elastic_transport/_node/_http_requests.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/elastic_transport/_node/_http_requests.py
b/elastic_transport/_node/_http_requests.py
index 19cec37..e439865 100644
--- a/elastic_transport/_node/_http_requests.py
+++ b/elastic_transport/_node/_http_requests.py
@@ -169,7 +169,16 @@ def __init__(self, config: NodeConfig):
)
# Preload the HTTPConnectionPool so initialization issues
# are raised here instead of in perform_request()
- adapter.get_connection(self.base_url) # type: ignore[no-untyped-call]
+ if hasattr(adapter, "get_connection_with_tls_context"):
+ adapter.get_connection_with_tls_context(
+ requests.Request(url=self.base_url), verify=self.session.verify
+ )
+ else:
+ # elastic-transport is not vulnerable to CVE-2024-35195 because it
uses
+ # requests.Session and an SSLContext without using the verify
parameter.
+ # We should remove this branch when requiring requests 2.32 or
later.
+ adapter.get_connection(self.base_url) # type: ignore
[no-untyped-call]
+
self.session.mount(prefix=f"{self.scheme}://", adapter=adapter)
def perform_request(
>From 56d1e6832d0b438ee7cee3ab0f8dea6a14a89eb8 Mon Sep 17 00:00:00 2001
From: Quentin Pradet <[email protected]>
Date: Thu, 23 May 2024 11:59:42 +0400
Subject: [PATCH 2/3] Fix lint
---
elastic_transport/_node/_http_requests.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/elastic_transport/_node/_http_requests.py
b/elastic_transport/_node/_http_requests.py
index e439865..4b2d502 100644
--- a/elastic_transport/_node/_http_requests.py
+++ b/elastic_transport/_node/_http_requests.py
@@ -170,14 +170,16 @@ def __init__(self, config: NodeConfig):
# Preload the HTTPConnectionPool so initialization issues
# are raised here instead of in perform_request()
if hasattr(adapter, "get_connection_with_tls_context"):
+ request = requests.Request(url=self.base_url)
+ prepared_request = self.session.prepare_request(request)
adapter.get_connection_with_tls_context(
- requests.Request(url=self.base_url), verify=self.session.verify
+ prepared_request, verify=self.session.verify
)
else:
# elastic-transport is not vulnerable to CVE-2024-35195 because it
uses
# requests.Session and an SSLContext without using the verify
parameter.
# We should remove this branch when requiring requests 2.32 or
later.
- adapter.get_connection(self.base_url) # type: ignore
[no-untyped-call]
+ adapter.get_connection(self.base_url)
self.session.mount(prefix=f"{self.scheme}://", adapter=adapter)
>From 8a222e2c9b81dc9f7a1e4583de59ecc0d4c4d803 Mon Sep 17 00:00:00 2001
From: Quentin Pradet <[email protected]>
Date: Thu, 23 May 2024 12:02:53 +0400
Subject: [PATCH 3/3] Fix prepared request
---
elastic_transport/_node/_http_requests.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/elastic_transport/_node/_http_requests.py
b/elastic_transport/_node/_http_requests.py
index 4b2d502..941e3cc 100644
--- a/elastic_transport/_node/_http_requests.py
+++ b/elastic_transport/_node/_http_requests.py
@@ -170,7 +170,7 @@ def __init__(self, config: NodeConfig):
# Preload the HTTPConnectionPool so initialization issues
# are raised here instead of in perform_request()
if hasattr(adapter, "get_connection_with_tls_context"):
- request = requests.Request(url=self.base_url)
+ request = requests.Request(method="GET", url=self.base_url)
prepared_request = self.session.prepare_request(request)
adapter.get_connection_with_tls_context(
prepared_request, verify=self.session.verify