Repository: elastic/elasticsearch-py

Issue:
delete request_timeout args error 
elasticsearch-7.2 :
I before use elasticsearch-5.6 version use the .indices.delete function, use request_timeout args because delete often timeout, now I update to 7.2 version ,this args can't use it , always report this error:   "TypeError: a float is required" , 
try trace source code, find this function will change int or float to string type , i find thie code from 5.0 never change ,but not error
so this is bug or design in this way:
https://github.com/elastic/elasticsearch-py/blob/master/elasticsearch/client/utils.py#L12

if not bug ,how do I use it? 
thanks!


Discussion:
alexk307: There are still references to `request_timeout` in the docs such as [here](https://github.com/elastic/elasticsearch-py/blob/master/elasticsearch/client/indices.py#L97).

The param itself will still work with respect to the transport layer, wondering if we should remove it from the docs or just keep it?
HonzaKral: It should be removed from the docstrings as well as those only list API specific params. There is a separate section in the docs for global params like `ignore` and `request_timeout`

Patch:
diff --git a/elasticsearch/client/__init__.py b/elasticsearch/client/__init__.py
index 9924133c..ae5ead60 100644
--- a/elasticsearch/client/__init__.py
+++ b/elasticsearch/client/__init__.py
@@ -12,7 +12,7 @@ from .nodes import NodesClient
 from .remote import RemoteClient
 from .snapshot import SnapshotClient
 from .tasks import TasksClient
-from .xpack import XPackClient
+from .xpack import XPackClient, XPACK_NAMESPACES
 from .utils import query_params, _make_path, SKIP_IN_PATH
 
 logger = logging.getLogger("elasticsearch")
@@ -214,6 +214,12 @@ class Elasticsearch(object):
         self.remote = RemoteClient(self)
         self.snapshot = SnapshotClient(self)
         self.tasks = TasksClient(self)
+
+        # new style access to x-pack features without .xpack step
+        for namespace, NamespacedClient in XPACK_NAMESPACES.items():
+            setattr(self, namespace, NamespacedClient(self))
+
+        # old style xpack access
         self.xpack = XPackClient(self)
 
     def __repr__(self):
diff --git a/elasticsearch/client/indices.py b/elasticsearch/client/indices.py
index 4457b4e7..b2645cee 100644
--- a/elasticsearch/client/indices.py
+++ b/elasticsearch/client/indices.py
@@ -81,7 +81,6 @@ class IndicesClient(NamespacedClient):
     @query_params(
         "master_timeout",
         "timeout",
-        "request_timeout",
         "wait_for_active_shards",
         "include_type_name",
     )
@@ -94,7 +93,6 @@ class IndicesClient(NamespacedClient):
         :arg body: The configuration for the index (`settings` and `mappings`)
         :arg master_timeout: Specify timeout for connection to master
         :arg timeout: Explicit operation timeout
-        :arg request_timeout: Explicit operation timeout
         :arg wait_for_active_shards: Set the number of active shards to wait for
             before the operation returns.
         :arg include_type_name: Specify whether requests and responses should include a
@@ -106,6 +104,32 @@ class IndicesClient(NamespacedClient):
             "PUT", _make_path(index), params=params, body=body
         )
 
+    @query_params(
+        "master_timeout",
+        "timeout",
+        "wait_for_active_shards"
+    )
+    def clone(self, index, target, body=None, params=None):
+        """
+        Clones an index
+        `<https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clone-index.html>`_
+
+        :arg index: The name of the source index to clone
+        :arg target: The name of the target index to clone into
+        :arg master_timeout: Specify timeout for connection to master
+        :arg timeout: Explicit operation timeout
+        :arg wait_for_active_shards: Set the number of active shards to wait for
+            before the operation returns.
+        """
+        if index in SKIP_IN_PATH:
+            raise ValueError("Empty value passed for a required argument 'index'.")
+        if target in SKIP_IN_PATH:
+            raise ValueError("Empty value passed for a required argument 'target'.")
+
+        return self.transport.perform_request(
+            "PUT", _make_path(index, '_clone', target), params=params, body=body
+        )
+
     @query_params(
         "allow_no_indices",
         "expand_wildcards",
@@ -149,7 +173,6 @@ class IndicesClient(NamespacedClient):
         "ignore_unavailable",
         "master_timeout",
         "timeout",
-        "request_timeout",
         "wait_for_active_shards",
     )
     def open(self, index, params=None):
@@ -168,7 +191,6 @@ class IndicesClient(NamespacedClient):
             ignored when unavailable (missing or closed)
         :arg master_timeout: Specify timeout for connection to master
         :arg timeout: Explicit operation timeout
-        :arg request_timeout: Explicit operation timeout
         :arg wait_for_active_shards: Sets the number of active shards to wait
             for before the operation returns.
         """
@@ -183,7 +205,6 @@ class IndicesClient(NamespacedClient):
         "expand_wildcards",
         "ignore_unavailable",
         "master_timeout",
-        "request_timeout",
         "wait_for_active_shards",
     )
     def close(self, index, params=None):
@@ -202,7 +223,6 @@ class IndicesClient(NamespacedClient):
         :arg ignore_unavailable: Whether specified concrete indices should be
             ignored when unavailable (missing or closed)
         :arg master_timeout: Specify timeout for connection to master
-        :arg request_timeout: Explicit operation timeout
         :arg wait_for_active_shards: Sets the number of active shards to wait
             for before the operation returns.
         """
@@ -218,7 +238,6 @@ class IndicesClient(NamespacedClient):
         "ignore_unavailable",
         "timeout",
         "master_timeout",
-        "request_timeout",
     )
     def delete(self, index, params=None):
         """
@@ -235,7 +254,6 @@ class IndicesClient(NamespacedClient):
         :arg ignore_unavailable: Ignore unavailable indexes (default: false)
         :arg master_timeout: Specify timeout for connection to master
         :arg timeout: Explicit operation timeout
-        :arg request_timeout: Explicit operation timeout
         """
         if index in SKIP_IN_PATH:
             raise ValueError("Empty value passed for a required argument 'index'.")
@@ -306,7 +324,6 @@ class IndicesClient(NamespacedClient):
         "ignore_unavailable",
         "master_timeout",
         "timeout",
-        "request_timeout",
         "include_type_name",
     )
     def put_mapping(self, body, doc_type=None, index=None, params=None):
@@ -329,7 +346,6 @@ class IndicesClient(NamespacedClient):
             ignored when unavailable (missing or closed)
         :arg master_timeout: Specify timeout for connection to master
         :arg timeout: Explicit operation timeout
-        :arg request_timeout: Explicit operation timeout (For pre 7.x ES Clusters)
         :arg include_type_name: Specify whether requests and responses should include a
             type name (default: depends on Elasticsearch version).
         """
@@ -412,7 +428,7 @@ class IndicesClient(NamespacedClient):
             params=params,
         )
 
-    @query_params("master_timeout", "request_timeout")
+    @query_params("master_timeout")
     def put_alias(self, index, name, body=None, params=None):
         """
         Create an alias for a specific index/indices.
@@ -424,7 +440,6 @@ class IndicesClient(NamespacedClient):
         :arg name: The name of the alias to be created or updated
         :arg body: The settings for the alias, such as `routing` or `filter`
         :arg master_timeout: Specify timeout for connection to master
-        :arg request_timeout: Explicit timeout for the operation
         """
         for param in (index, name):
             if param in SKIP_IN_PATH:
@@ -479,7 +494,7 @@ class IndicesClient(NamespacedClient):
             "GET", _make_path(index, "_alias", name), params=params
         )
 
-    @query_params("master_timeout", "request_timeout", "timeout")
+    @query_params("master_timeout", "timeout")
     def update_aliases(self, body, params=None):
         """
         Update specified aliases.
@@ -487,7 +502,6 @@ class IndicesClient(NamespacedClient):
 
         :arg body: The definition of `actions` to perform
         :arg master_timeout: Specify timeout for connection to master
-        :arg request_timeout: Request timeout (For pre 7.x ES Clusters)
         :arg timeout: Request timeout
         """
         if body in SKIP_IN_PATH:
@@ -496,7 +510,7 @@ class IndicesClient(NamespacedClient):
             "POST", "/_aliases", params=params, body=body
         )
 
-    @query_params("master_timeout", "request_timeout", "timeout")
+    @query_params("master_timeout", "timeout")
     def delete_alias(self, index, name, params=None):
         """
         Delete specific alias.
@@ -508,7 +522,6 @@ class IndicesClient(NamespacedClient):
             wildcards); use `_all` to delete all aliases for the specified
             indices.
         :arg master_timeout: Specify timeout for connection to master
-        :arg request_timeout: Explicit timeout for the operation (for pre 7.x ES clusters)
         :arg timeout: Explicit timeout for the operation
         """
         for param in (index, name):
@@ -523,7 +536,6 @@ class IndicesClient(NamespacedClient):
         "flat_settings",
         "master_timeout",
         "order",
-        "request_timeout",
         "timeout",
         "include_type_name",
     )
@@ -541,7 +553,6 @@ class IndicesClient(NamespacedClient):
         :arg master_timeout: Specify timeout for connection to master
         :arg order: The order for this template when merging multiple matching
             ones (higher numbers are merged later, overriding the lower numbers)
-        :arg request_timeout: Explicit operation timeout (For pre ES 6 clusters)
         :arg timeout: Explicit operation timeout
         :arg include_type_name: Specify whether requests and responses should include a
             type name (default: depends on Elasticsearch version).
@@ -591,7 +602,7 @@ class IndicesClient(NamespacedClient):
             "GET", _make_path("_template", name), params=params
         )
 
-    @query_params("master_timeout", "request_timeout", "timeout")
+    @query_params("master_timeout", "timeout")
     def delete_template(self, name, params=None):
         """
         Delete an index template by its name.
@@ -599,7 +610,6 @@ class IndicesClient(NamespacedClient):
 
         :arg name: The name of the template
         :arg master_timeout: Specify timeout for connection to master
-        :arg request_timeout: Explicit operation timeout (for pre 7.x clusters)
         :arg timeout: Explicit operation timeout
         """
         if name in SKIP_IN_PATH:
@@ -1026,7 +1036,7 @@ class IndicesClient(NamespacedClient):
         )
 
     @query_params(
-        "master_timeout", "timeout", "request_timeout", "wait_for_active_shards"
+        "master_timeout", "timeout", "wait_for_active_shards"
     )
     def shrink(self, index, target, body=None, params=None):
         """
@@ -1046,7 +1056,6 @@ class IndicesClient(NamespacedClient):
         :arg body: The configuration for the target index (`settings` and
             `aliases`)
         :arg master_timeout: Specify timeout for connection to master
-        :arg request_timeout: Explicit operation timeout (For pre 7.x ES clusters)
         :arg timeout: Explicit operation timeout
         :arg wait_for_active_shards: Set the number of active shards to wait for
             on the shrunken index before the operation returns.
@@ -1082,7 +1091,6 @@ class IndicesClient(NamespacedClient):
     @query_params(
         "dry_run",
         "master_timeout",
-        "request_timeout",
         "timeout",
         "wait_for_active_shards",
         "include_type_name",
@@ -1105,7 +1113,6 @@ class IndicesClient(NamespacedClient):
             but not actually performed even if a condition matches. The default
             is false
         :arg master_timeout: Specify timeout for connection to master
-        :arg request_timeout: Explicit operation timeout (for pre 7.x ES clusters)
         :arg timeout: Explicit operation timeout
         :arg wait_for_active_shards: Set the number of active shards to wait for
             on the newly created rollover index before the operation returns.
@@ -1117,3 +1124,68 @@ class IndicesClient(NamespacedClient):
         return self.transport.perform_request(
             "POST", _make_path(alias, "_rollover", new_index), params=params, body=body
         )
+
+    # X-pack APIS
+    @query_params(
+        "allow_no_indices",
+        "expand_wildcards",
+        "ignore_unavailable",
+        "master_timeout",
+        "timeout",
+        "wait_for_active_shards",
+    )
+    def freeze(self, index, params=None):
+        """
+        `<https://www.elastic.co/guide/en/elasticsearch/reference/current/frozen.html>`_
+
+        :arg index: The name of the index to freeze
+        :arg allow_no_indices: Whether to ignore if a wildcard indices
+            expression resolves into no concrete indices. (This includes `_all`
+            string or when no indices have been specified)
+        :arg expand_wildcards: Whether to expand wildcard expression to concrete
+            indices that are open, closed or both., default 'closed', valid
+            choices are: 'open', 'closed', 'none', 'all'
+        :arg ignore_unavailable: Whether specified concrete indices should be
+            ignored when unavailable (missing or closed)
+        :arg master_timeout: Specify timeout for connection to master
+        :arg timeout: Explicit operation timeout
+        :arg wait_for_active_shards: Sets the number of active shards to wait
+            for before the operation returns.
+        """
+        if index in SKIP_IN_PATH:
+            raise ValueError("Empty value passed for a required argument 'index'.")
+        return self.transport.perform_request(
+            "POST", _make_path(index, "_freeze"), params=params
+        )
+
+    @query_params(
+        "allow_no_indices",
+        "expand_wildcards",
+        "ignore_unavailable",
+        "master_timeout",
+        "timeout",
+        "wait_for_active_shards",
+    )
+    def unfreeze(self, index, params=None):
+        """
+        `<https://www.elastic.co/guide/en/elasticsearch/reference/current/frozen.html>`_
+
+        :arg index: The name of the index to unfreeze
+        :arg allow_no_indices: Whether to ignore if a wildcard indices
+            expression resolves into no concrete indices. (This includes `_all`
+            string or when no indices have been specified)
+        :arg expand_wildcards: Whether to expand wildcard expression to concrete
+            indices that are open, closed or both., default 'closed', valid
+            choices are: 'open', 'closed', 'none', 'all'
+        :arg ignore_unavailable: Whether specified concrete indices should be
+            ignored when unavailable (missing or closed)
+        :arg master_timeout: Specify timeout for connection to master
+        :arg timeout: Explicit operation timeout
+        :arg wait_for_active_shards: Sets the number of active shards to wait
+            for before the operation returns.
+        """
+        if index in SKIP_IN_PATH:
+            raise ValueError("Empty value passed for a required argument 'index'.")
+        return self.transport.perform_request(
+            "POST", _make_path(index, "_unfreeze"), params=params
+        )
diff --git a/elasticsearch/client/xpack/__init__.py b/elasticsearch/client/xpack/__init__.py
index 21e45a26..12d6f093 100644
--- a/elasticsearch/client/xpack/__init__.py
+++ b/elasticsearch/client/xpack/__init__.py
@@ -5,7 +5,7 @@ from .data_frame import Data_FrameClient
 from .deprecation import DeprecationClient
 from .graph import GraphClient
 from .ilm import IlmClient
-from .indices import IndicesClient
+from ..indices import IndicesClient
 from .license import LicenseClient
 from .migration import MigrationClient
 from .ml import MlClient
@@ -16,27 +16,30 @@ from .sql import SqlClient
 from .ssl import SslClient
 from .watcher import WatcherClient
 
+XPACK_NAMESPACES = {
+    "ccr": CcrClient,
+    "data_frame": Data_FrameClient,
+    "deprecation": DeprecationClient,
+    "graph": GraphClient,
+    "ilm": IlmClient,
+    "indices": IndicesClient,
+    "license": LicenseClient,
+    "migration": MigrationClient,
+    "ml": MlClient,
+    "monitoring": MonitoringClient,
+    "rollup": RollupClient,
+    "security": SecurityClient,
+    "sql": SqlClient,
+    "ssl": SslClient,
+    "watcher": WatcherClient,
+}
 
 class XPackClient(NamespacedClient):
-    namespace = "xpack"
-
     def __init__(self, *args, **kwargs):
         super(XPackClient, self).__init__(*args, **kwargs)
-        self.ccr = CcrClient(self.client)
-        self.data_frame = Data_FrameClient(self.client)
-        self.deprecation = DeprecationClient(self.client)
-        self.graph = GraphClient(self.client)
-        self.ilm = IlmClient(self.client)
-        self.indices = IndicesClient(self.client)
-        self.license = LicenseClient(self.client)
-        self.migration = MigrationClient(self.client)
-        self.ml = MlClient(self.client)
-        self.monitoring = MonitoringClient(self.client)
-        self.rollup = RollupClient(self.client)
-        self.security = SecurityClient(self.client)
-        self.sql = SqlClient(self.client)
-        self.ssl = SslClient(self.client)
-        self.watcher = WatcherClient(self.client)
+
+        for namespace in XPACK_NAMESPACES:
+            setattr(self, namespace, getattr(self.client, namespace))
 
     @query_params("categories", "human")
     def info(self, params=None):
diff --git a/elasticsearch/client/xpack/indices.py b/elasticsearch/client/xpack/indices.py
deleted file mode 100644
index c4e48807..00000000
--- a/elasticsearch/client/xpack/indices.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from ..utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH
-
-
-class IndicesClient(NamespacedClient):
-    @query_params(
-        "allow_no_indices",
-        "expand_wildcards",
-        "ignore_unavailable",
-        "master_timeout",
-        "timeout",
-        "wait_for_active_shards",
-    )
-    def freeze(self, index, params=None):
-        """
-        `<https://www.elastic.co/guide/en/elasticsearch/reference/current/frozen.html>`_
-
-        :arg index: The name of the index to freeze
-        :arg allow_no_indices: Whether to ignore if a wildcard indices
-            expression resolves into no concrete indices. (This includes `_all`
-            string or when no indices have been specified)
-        :arg expand_wildcards: Whether to expand wildcard expression to concrete
-            indices that are open, closed or both., default 'closed', valid
-            choices are: 'open', 'closed', 'none', 'all'
-        :arg ignore_unavailable: Whether specified concrete indices should be
-            ignored when unavailable (missing or closed)
-        :arg master_timeout: Specify timeout for connection to master
-        :arg timeout: Explicit operation timeout
-        :arg wait_for_active_shards: Sets the number of active shards to wait
-            for before the operation returns.
-        """
-        if index in SKIP_IN_PATH:
-            raise ValueError("Empty value passed for a required argument 'index'.")
-        return self.transport.perform_request(
-            "POST", _make_path(index, "_freeze"), params=params
-        )
-
-    @query_params(
-        "allow_no_indices",
-        "expand_wildcards",
-        "ignore_unavailable",
-        "master_timeout",
-        "timeout",
-        "wait_for_active_shards",
-    )
-    def unfreeze(self, index, params=None):
-        """
-        `<https://www.elastic.co/guide/en/elasticsearch/reference/current/frozen.html>`_
-
-        :arg index: The name of the index to unfreeze
-        :arg allow_no_indices: Whether to ignore if a wildcard indices
-            expression resolves into no concrete indices. (This includes `_all`
-            string or when no indices have been specified)
-        :arg expand_wildcards: Whether to expand wildcard expression to concrete
-            indices that are open, closed or both., default 'closed', valid
-            choices are: 'open', 'closed', 'none', 'all'
-        :arg ignore_unavailable: Whether specified concrete indices should be
-            ignored when unavailable (missing or closed)
-        :arg master_timeout: Specify timeout for connection to master
-        :arg timeout: Explicit operation timeout
-        :arg wait_for_active_shards: Sets the number of active shards to wait
-            for before the operation returns.
-        """
-        if index in SKIP_IN_PATH:
-            raise ValueError("Empty value passed for a required argument 'index'.")
-        return self.transport.perform_request(
-            "POST", _make_path(index, "_unfreeze"), params=params
-        )
diff --git a/elasticsearch/transport.py b/elasticsearch/transport.py
index bfd2f866..9002f8d7 100644
--- a/elasticsearch/transport.py
+++ b/elasticsearch/transport.py
@@ -245,8 +245,16 @@ class Transport(object):
         if not address or ":" not in address:
             return None
 
-        host["host"], host["port"] = address.rsplit(":", 1)
-        host["port"] = int(host["port"])
+        if '/' in address:
+            # Support 7.x host/ip:port behavior where http.publish_host has been set.
+            fqdn, ipaddress = address.split('/', 1)
+            host["host"] = fqdn
+            _, host["port"] = ipaddress.rsplit(':', 1)
+            host["port"] = int(host["port"])
+
+        else:
+            host["host"], host["port"] = address.rsplit(":", 1)
+            host["port"] = int(host["port"])
 
         return self.host_info_callback(host_info, host)
 
diff --git a/setup.py b/setup.py
index dc459e03..a99d31bc 100644
--- a/setup.py
+++ b/setup.py
@@ -50,6 +50,7 @@ setup(
         "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
     ],
diff --git a/tox.ini b/tox.ini
index 860dc636..b8cb4eac 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pypy,py26,py27,py33,py34,py35,py36
+envlist = pypy,py26,py27,py33,py34,py35,py36,py37
 [testenv]
 whitelist_externals = git
 setenv =


Test patch:
diff --git a/test_elasticsearch/test_transport.py b/test_elasticsearch/test_transport.py
index f06ed6be..a1fbba9d 100644
--- a/test_elasticsearch/test_transport.py
+++ b/test_elasticsearch/test_transport.py
@@ -50,6 +50,31 @@ CLUSTER_NODES = """{
   }
 }"""
 
+CLUSTER_NODES_7x_PUBLISH_HOST = """{
+  "_nodes" : {
+    "total" : 1,
+    "successful" : 1,
+    "failed" : 0
+  },
+  "cluster_name" : "elasticsearch",
+  "nodes" : {
+    "SRZpKFZdQguhhvifmN6UVA" : {
+      "name" : "SRZpKFZ",
+      "transport_address" : "127.0.0.1:9300",
+      "host" : "127.0.0.1",
+      "ip" : "127.0.0.1",
+      "version" : "5.0.0",
+      "build_hash" : "253032b",
+      "roles" : [ "master", "data", "ingest" ],
+      "http" : {
+        "bound_address" : [ "[fe80::1]:9200", "[::1]:9200", "127.0.0.1:9200" ],
+        "publish_address" : "somehost.tld/1.1.1.1:123",
+        "max_content_length_in_bytes" : 104857600
+      }
+    }
+  }
+}"""
+
 
 class TestHostsInfoCallback(TestCase):
     def test_master_only_nodes_are_ignored(self):
@@ -287,3 +312,16 @@ class TestTransport(TestCase):
         self.assertEquals(1, len(t.connection_pool.connections))
         self.assertEquals("http://1.1.1.1:123", t.get_connection().host)
         self.assertTrue(time.time() - 1 < t.last_sniff < time.time() + 0.01)
+
+    def test_sniff_7x_publish_host(self):
+        # Test the response shaped when a 7.x node has publish_host set
+        # and the returend data is shaped in the fqdn/ip:port format.
+        t = Transport(
+            [{"data": CLUSTER_NODES_7x_PUBLISH_HOST}],
+            connection_class=DummyConnection,
+            sniff_timeout=42,
+        )
+        t.sniff_hosts()
+        # Ensure we parsed out the fqdn and port from the fqdn/ip:port string.
+        self.assertEqual(t.connection_pool.connection_opts[0][1],
+                         {'host': 'somehost.tld', 'port': 123})