Repository: elastic/elasticsearch-py

Issue:
TasksClient.get() requiring an argument on v7.5.1
<!--

** Please read the guidelines below. **

1.  GitHub is reserved for bug reports and feature requests. The best place to
    ask a general question is at the Elastic [forums](https://discuss.elastic.co).
    GitHub is not the place for general questions.

2.  Please fill out EITHER the feature request block or the bug report block
    below, and delete the other block.

3.  For issues with API definition please note that the API is [auto
    generated](https://github.com/elastic/elasticsearch-py/blob/master/CONTRIBUTING.md#api-code-generation)
    so any problems should be checked and reported against [the Elasticsearch
    repo](https://github.com/elastic/elasticsearch) instead.

Thank you!

-->

<!-- Bug report -->

**Elasticsearch version** (`bin/elasticsearch --version`): 7.1.1, 7.5.1

**`elasticsearch-py` version (`elasticsearch.__versionstr__`)**: 7.5.1

Please make sure the major version matches the Elasticsearch server you are running.

**Description of the problem including expected versus actual behavior**:

Elasticsearch().tasks.get() fails using elasticsearch-py 7.5.1, whereas it succeeds using 7.1.1.

```
>>> import elasticsearch
>>> elasticsearch.__versionstr__
'7.5.1'
>>> elasticsearch.Elasticsearch().tasks.get()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/nfox/myenv-7.5.1/lib/python3.7/site-packages/elasticsearch/client/utils.py", line 84, in _wrapped
    return func(*args, params=params, **kwargs)
TypeError: get() missing 1 required positional argument: 'task_id'
```

```
>>> import elasticsearch
>>> elasticsearch.__versionstr__
'7.1.0'
>>> elasticsearch.Elasticsearch().tasks.get()
{'nodes': {'GkSSDiZPTBq4Tlv5xV9wtg': {'name': 'e6a01a4d549f', 'transport_address': '172.17.0.3:9300', 'host': '172.17.0.3', 'ip': '172.17.0.3:9300', 'roles': ['ingest', 'master', 'data', 'ml'], 'attributes': {'ml.machine_memory': '4129972224', 'xpack.installed': 'true', 'ml.max_open_jobs': '20'}, 'tasks': {'GkSSDiZPTBq4Tlv5xV9wtg:56': {'node': 'GkSSDiZPTBq4Tlv5xV9wtg', 'id': 56, 'type': 'direct', 'action': 'cluster:monitor/tasks/lists[n]', 'start_time_in_millis': 1582238526445, 'running_time_in_nanos': 8142200, 'cancellable': False, 'parent_task_id': 'GkSSDiZPTBq4Tlv5xV9wtg:55', 'headers': {}}, 'GkSSDiZPTBq4Tlv5xV9wtg:55': {'node': 'GkSSDiZPTBq4Tlv5xV9wtg', 'id': 55, 'type': 'transport', 'action': 'cluster:monitor/tasks/lists', 'start_time_in_millis': 1582238526442, 'running_time_in_nanos': 11192200, 'cancellable': False, 'headers': {}}}}}}
```

I've verified this against running both ES 7.5.1 and ES 7.1.1 (via docker on localhost)

**Steps to reproduce**:
run `elasticsearch.Elasticsearch().tasks.get()` while using elasticsearch-py 7.5.1

Ultimately, this is breaking curator's DeleteSnapshot as it checks for tasks.

Comments:

Patch:
diff --git a/elasticsearch/client/tasks.py b/elasticsearch/client/tasks.py
index 0f253ca1..4c4f6d1f 100644
--- a/elasticsearch/client/tasks.py
+++ b/elasticsearch/client/tasks.py
@@ -1,3 +1,4 @@
+import warnings
 from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH
 
 
@@ -58,7 +59,7 @@ class TasksClient(NamespacedClient):
         )
 
     @query_params("timeout", "wait_for_completion")
-    def get(self, task_id, params=None, headers=None):
+    def get(self, task_id=None, params=None, headers=None):
         """
         Returns information about a task.
         `<https://www.elastic.co/guide/en/elasticsearch/reference/master/tasks.html>`_
@@ -70,7 +71,11 @@ class TasksClient(NamespacedClient):
             complete (default: false)
         """
         if task_id in SKIP_IN_PATH:
-            raise ValueError("Empty value passed for a required argument 'task_id'.")
+            warnings.warn(
+                "Calling client.tasks.get() without a task_id is deprecated "
+                "and will be removed in v8.0. Use client.tasks.list() instead.",
+                DeprecationWarning,
+            )
 
         return self.transport.perform_request(
             "GET", _make_path("_tasks", task_id), params=params, headers=headers
diff --git a/utils/generate_api.py b/utils/generate_api.py
index 0e76617d..183934e4 100644
--- a/utils/generate_api.py
+++ b/utils/generate_api.py
@@ -146,6 +146,13 @@ class API:
                 p in url.get("parts", {}) for url in self._def["url"]["paths"]
             )
 
+            # This piece of logic corresponds to calling
+            # client.tasks.get() w/o a task_id which was erroneously
+            # allowed in the 7.1 client library. This functionality
+            # is deprecated and will be removed in 8.x.
+            if self.namespace == "tasks" and self.name == "get":
+                parts["task_id"]["required"] = False
+
         for k, sub in SUBSTITUTIONS.items():
             if k in parts:
                 parts[sub] = parts.pop(k)
diff --git a/utils/templates/overrides/tasks/get b/utils/templates/overrides/tasks/get
new file mode 100644
index 00000000..ca855ab9
--- /dev/null
+++ b/utils/templates/overrides/tasks/get
@@ -0,0 +1,12 @@
+{% extends "base" %}
+{% block request %}
+        if task_id in SKIP_IN_PATH:
+            warnings.warn(
+                "Calling client.tasks.get() without a task_id is deprecated "
+                "and will be removed in v8.0. Use client.tasks.list() instead.",
+                DeprecationWarning,
+            )
+
+        {{ super()|trim }}
+{% endblock %}
+


Test patch:
diff --git a/test_elasticsearch/test_client/__init__.py b/test_elasticsearch/test_client/__init__.py
index b7fa18ad..5fa01f76 100644
--- a/test_elasticsearch/test_client/__init__.py
+++ b/test_elasticsearch/test_client/__init__.py
@@ -1,4 +1,5 @@
 from __future__ import unicode_literals
+import warnings
 
 from elasticsearch.client import _normalize_hosts, Elasticsearch
 
@@ -110,3 +111,27 @@ class TestClient(ElasticsearchTestCase):
         self.client.index(index="my-index", id=0, body={})
 
         self.assert_url_called("PUT", "/my-index/_doc/0")
+
+    def test_tasks_get_without_task_id_deprecated(self):
+        warnings.simplefilter("always", DeprecationWarning)
+        with warnings.catch_warnings(record=True) as w:
+            self.client.tasks.get()
+
+        self.assert_url_called("GET", "/_tasks")
+        self.assertEquals(len(w), 1)
+        self.assertIs(w[0].category, DeprecationWarning)
+        self.assertEquals(
+            str(w[0].message),
+            "Calling client.tasks.get() without a task_id is deprecated "
+            "and will be removed in v8.0. Use client.tasks.list() instead.",
+        )
+
+    def test_tasks_get_with_task_id_not_deprecated(self):
+        warnings.simplefilter("always", DeprecationWarning)
+        with warnings.catch_warnings(record=True) as w:
+            self.client.tasks.get("task-1")
+            self.client.tasks.get(task_id="task-2")
+
+        self.assert_url_called("GET", "/_tasks/task-1")
+        self.assert_url_called("GET", "/_tasks/task-2")
+        self.assertEquals(len(w), 0)