[7.x] Add support for the HTTP API compatibility header

Co-authored-by: Seth Michael Larson <[email protected]>
This commit is contained in:
github-actions[bot]
2021-04-26 16:55:59 -05:00
committed by GitHub
co-authored by Seth Michael Larson
parent 5ab523b703
commit 2c5f1d1569
9 changed files with 84 additions and 18 deletions
+23 -1
View File
@@ -60,7 +60,29 @@ To create an `SSLContext` object you only need to use one of cafile, capath or c
* `capath` is the directory of a collection of CA's * `capath` is the directory of a collection of CA's
* `cadata` is either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates. * `cadata` is either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates.
Please note that the use of SSLContext is only available for Urllib3. Please note that the use of SSLContext is only available for urllib3.
.. autoclass:: Urllib3HttpConnection .. autoclass:: Urllib3HttpConnection
:members: :members:
API Compatibility HTTP Header
-----------------------------
The Python client can be configured to emit an HTTP header
``Accept: application/vnd.elasticsearch+json; compatible-with=7``
which signals to Elasticsearch that the client is requesting
``7.x`` version of request and response bodies. This allows for
upgrading from 7.x to 8.x version of Elasticsearch without upgrading
everything at once. Elasticsearch should be upgraded first after
the compatibility header is configured and clients should be upgraded
second.
.. code-block:: python
from elasticsearch import Elasticsearch
client = Elasticsearch("http://...", headers={"accept": "application/vnd.elasticsearch+json; compatible-with=7"})
If you'd like to have the client emit the header without configuring ``headers`` you
can use the environment variable ``ELASTIC_CLIENT_APIVERSIONING=1``.
+9 -1
View File
@@ -19,6 +19,7 @@ import binascii
import gzip import gzip
import io import io
import logging import logging
import os
import re import re
import warnings import warnings
from platform import python_version from platform import python_version
@@ -28,7 +29,7 @@ try:
except ImportError: except ImportError:
import json import json
from .. import __versionstr__ from .. import __version__, __versionstr__
from ..exceptions import ( from ..exceptions import (
HTTP_EXCEPTIONS, HTTP_EXCEPTIONS,
ElasticsearchWarning, ElasticsearchWarning,
@@ -121,6 +122,13 @@ class Connection(object):
if opaque_id: if opaque_id:
self.headers["x-opaque-id"] = opaque_id self.headers["x-opaque-id"] = opaque_id
if os.getenv("ELASTIC_CLIENT_APIVERSIONING") == "1":
self.headers.setdefault(
"accept",
"application/vnd.elasticsearch+json;compatible-with=%s"
% (str(__version__[0]),),
)
self.headers.setdefault("content-type", "application/json") self.headers.setdefault("content-type", "application/json")
self.headers.setdefault("user-agent", self._get_default_user_agent()) self.headers.setdefault("user-agent", self._get_default_user_agent())
+6 -2
View File
@@ -154,8 +154,12 @@ class Deserializer(object):
if not mimetype: if not mimetype:
deserializer = self.default deserializer = self.default
else: else:
# split out charset # split out 'charset' and 'compatible-width' options
mimetype, _, _ = mimetype.partition(";") mimetype = mimetype.partition(";")[0].strip()
# Treat 'application/vnd.elasticsearch+json'
# as application/json for compatibility.
if mimetype == "application/vnd.elasticsearch+json":
mimetype = "application/json"
try: try:
deserializer = self.serializers[mimetype] deserializer = self.serializers[mimetype]
except KeyError: except KeyError:
+21
View File
@@ -18,6 +18,7 @@
import gzip import gzip
import io import io
import os
import re import re
import ssl import ssl
import warnings import warnings
@@ -171,6 +172,26 @@ class TestBaseConnection(TestCase):
Connection(meta_header=1) Connection(meta_header=1)
assert str(e.value) == "meta_header must be of type bool" assert str(e.value) == "meta_header must be of type bool"
def test_compatibility_accept_header(self):
try:
conn = Connection()
assert "accept" not in conn.headers
os.environ["ELASTIC_CLIENT_APIVERSIONING"] = "0"
conn = Connection()
assert "accept" not in conn.headers
os.environ["ELASTIC_CLIENT_APIVERSIONING"] = "1"
conn = Connection()
assert (
conn.headers["accept"]
== "application/vnd.elasticsearch+json;compatible-with=8"
)
finally:
os.environ.pop("ELASTIC_CLIENT_APIVERSIONING")
class TestUrllib3Connection(TestCase): class TestUrllib3Connection(TestCase):
def _get_mock_connection(self, connection_params={}, response_body=b"{}"): def _get_mock_connection(self, connection_params={}, response_body=b"{}"):
+11
View File
@@ -185,6 +185,17 @@ class TestDeserializer(TestCase):
self.de.loads('{"some":"data"}', "text/plain; charset=whatever"), self.de.loads('{"some":"data"}', "text/plain; charset=whatever"),
) )
def test_deserialize_compatibility_header(self):
for content_type in (
"application/vnd.elasticsearch+json;compatible-with=7",
"application/vnd.elasticsearch+json; compatible-with=7",
"application/vnd.elasticsearch+json;compatible-with=8",
"application/vnd.elasticsearch+json; compatible-with=8",
):
self.assertEqual(
{"some": "data"}, self.de.loads('{"some":"data"}', content_type)
)
def test_raises_serialization_error_on_unknown_mimetype(self): def test_raises_serialization_error_on_unknown_mimetype(self):
self.assertRaises(SerializationError, self.de.loads, "{}", "text/html") self.assertRaises(SerializationError, self.de.loads, "{}", "text/html")