We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Elasticsearch 7.5.1 changed positional arguments order
Similar to #454, Elasticsearch 7.5.1 broke the test suite in another way.

https://github.com/elastic/elasticsearch-py/issues/1099

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/compat.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import datetime as dt
5 import inspect
6 import sys
7 from functools import wraps
8 
9 string_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821
10 text_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821
11 string_types = tuple({string_type, text_type})
12 
13 if sys.version_info >= (3, 2):
14     from contextlib import ContextDecorator
15 else:
16     import functools
17 
18     class ContextDecorator(object):
19         def __call__(self, f):
20             @functools.wraps(f)
21             def decorated(*args, **kwds):
22                 with self:
23                     return f(*args, **kwds)
24 
25             return decorated
26 
27 
28 if sys.version_info >= (3, 0):
29     import queue
30 else:
31     import Queue as queue
32 
33 # datetime_to_timestamp converts a naive UTC datetime to a unix timestamp
34 if sys.version_info >= (3, 3):
35 
36     def datetime_to_timestamp(datetime_obj):
37         return datetime_obj.replace(tzinfo=dt.timezone.utc).timestamp()
38 
39 
40 else:
41     _EPOCH = dt.datetime(1970, 1, 1)
42 
43     def datetime_to_timestamp(datetime_obj):
44         return (datetime_obj - _EPOCH).total_seconds()
45 
46 
47 def text(value, encoding="utf-8", errors="strict"):
48     """
49     Convert a value to str on Python 3 and unicode on Python 2.
50     """
51     if isinstance(value, text_type):
52         return value
53     elif isinstance(value, bytes):
54         return text_type(value, encoding, errors)
55     else:
56         return text_type(value)
57 
58 
59 if sys.version_info >= (3, 0):
60     from urllib.parse import parse_qsl, urlencode
61 else:
62     from urllib import urlencode
63     from urlparse import parse_qsl
64 
65 
66 def kwargs_only(func):
67     """
68     Make a function only accept keyword arguments.
69     This can be dropped in Python 3 in lieu of:
70         def foo(*, bar=default):
71     Source: https://pypi.org/project/kwargs-only/
72     """
73     if hasattr(inspect, "signature"):  # pragma: no cover
74         # Python 3
75         signature = inspect.signature(func)
76         arg_names = list(signature.parameters.keys())
77     else:  # pragma: no cover
78         # Python 2
79         signature = inspect.getargspec(func)
80         arg_names = signature.args
81 
82     if len(arg_names) > 0 and arg_names[0] in ("self", "cls"):
83         allowable_args = 1
84     else:
85         allowable_args = 0
86 
87     @wraps(func)
88     def wrapper(*args, **kwargs):
89         if len(args) > allowable_args:
90             raise TypeError(
91                 "{} should only be called with keyword args".format(func.__name__)
92             )
93         return func(*args, **kwargs)
94 
95     return wrapper
96 
97 
98 __all__ = [
99     "ContextDecorator",
100     "datetime_to_timestamp",
101     "kwargs_only",
102     "parse_qsl",
103     "queue",
104     "string_type",
105     "text",
106     "text_type",
107     "urlencode",
108 ]
109
```

Path: `src/scout_apm/instruments/elasticsearch.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import logging
5 from collections import namedtuple
6 
7 import wrapt
8 
9 from scout_apm.core.tracked_request import TrackedRequest
10 
11 try:
12     from elasticsearch import Elasticsearch, Transport
13 except ImportError:  # pragma: no cover
14     Elasticsearch = None
15     Transport = None
16 
17 logger = logging.getLogger(__name__)
18 
19 
20 def ensure_installed():
21     logger.info("Ensuring elasticsearch instrumentation is installed.")
22 
23     if Elasticsearch is None:
24         logger.info("Unable to import elasticsearch.Elasticsearch")
25     else:
26         ensure_client_instrumented()
27         ensure_transport_instrumented()
28 
29 
30 ClientMethod = namedtuple("ClientMethod", ["name", "takes_index_argument"])
31 
32 CLIENT_METHODS = [
33     ClientMethod("bulk", True),
34     ClientMethod("clear_scroll", False),
35     ClientMethod("count", True),
36     ClientMethod("create", True),
37     ClientMethod("delete", True),
38     ClientMethod("delete_by_query", True),
39     ClientMethod("delete_by_query_rethrottle", False),
40     ClientMethod("delete_script", False),
41     ClientMethod("exists", True),
42     ClientMethod("exists_source", True),
43     ClientMethod("explain", True),
44     ClientMethod("field_caps", True),
45     ClientMethod("get", True),
46     ClientMethod("get_script", False),
47     ClientMethod("get_source", True),
48     ClientMethod("index", True),
49     ClientMethod("info", False),
50     ClientMethod("mget", True),
51     ClientMethod("msearch", True),
52     ClientMethod("msearch_template", True),
53     ClientMethod("mtermvectors", True),
54     ClientMethod("ping", False),
55     ClientMethod("put_script", False),
56     ClientMethod("rank_eval", True),
57     ClientMethod("reindex", False),
58     ClientMethod("reindex_rethrottle", False),
59     ClientMethod("render_search_template", False),
60     ClientMethod("scripts_painless_context", False),
61     ClientMethod("scripts_painless_execute", False),
62     ClientMethod("scroll", False),
63     ClientMethod("search", True),
64     ClientMethod("search_shards", True),
65     ClientMethod("search_template", True),
66     ClientMethod("termvectors", True),
67     ClientMethod("update", True),
68     ClientMethod("update_by_query", True),
69     ClientMethod("update_by_query_rethrottle", False),
70 ]
71 
72 
73 have_patched_client = False
74 
75 
76 def ensure_client_instrumented():
77     global have_patched_client
78 
79     if not have_patched_client:
80         for name, takes_index_argument in CLIENT_METHODS:
81             try:
82                 method = getattr(Elasticsearch, name)
83                 if takes_index_argument:
84                     wrapped = wrap_client_index_method(method)
85                 else:
86                     wrapped = wrap_client_method(method)
87                 setattr(Elasticsearch, name, wrapped)
88             except Exception as exc:
89                 # Workaround for version 7.5.0 removing scripts_painless_context:
90                 # https://github.com/elastic/elasticsearch-py/issues/1098
91                 if name != "scripts_painless_context":
92                     logger.warning(
93                         "Unable to instrument elasticsearch.Elasticsearch.%s: %r",
94                         name,
95                         exc,
96                         exc_info=exc,
97                     )
98 
99         have_patched_client = True
100 
101 
102 @wrapt.decorator
103 def wrap_client_index_method(wrapped, instance, args, kwargs):
104     def _get_index(index, *args, **kwargs):
105         return index
106 
107     try:
108         index = _get_index(*args, **kwargs)
109     except TypeError:
110         index = "Unknown"
111     else:
112         if not index:
113             index = "Unknown"
114         if isinstance(index, (list, tuple)):
115             index = ",".join(index)
116     index = index.title()
117     camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
118     operation = "Elasticsearch/{}/{}".format(index, camel_name)
119     tracked_request = TrackedRequest.instance()
120     tracked_request.start_span(operation=operation, ignore_children=True)
121 
122     try:
123         return wrapped(*args, **kwargs)
124     finally:
125         tracked_request.stop_span()
126 
127 
128 @wrapt.decorator
129 def wrap_client_method(wrapped, instance, args, kwargs):
130     camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
131     operation = "Elasticsearch/{}".format(camel_name)
132     tracked_request = TrackedRequest.instance()
133     tracked_request.start_span(operation=operation, ignore_children=True)
134 
135     try:
136         return wrapped(*args, **kwargs)
137     finally:
138         tracked_request.stop_span()
139 
140 
141 have_patched_transport = False
142 
143 
144 def ensure_transport_instrumented():
145     global have_patched_transport
146 
147     if not have_patched_transport:
148         try:
149             Transport.perform_request = wrapped_perform_request(
150                 Transport.perform_request
151             )
152         except Exception as exc:
153             logger.warning(
154                 "Unable to instrument elasticsearch.Transport.perform_request: %r",
155                 exc,
156                 exc_info=exc,
157             )
158 
159     have_patched_transport = True
160 
161 
162 def _sanitize_name(name):
163     try:
164         op = name.split("/")[-1]
165         op = op[1:]  # chop leading '_' from op
166         known_names = (
167             "bench",
168             "bulk",
169             "count",
170             "exists",
171             "explain",
172             "field_stats",
173             "health",
174             "mget",
175             "mlt",
176             "mpercolate",
177             "msearch",
178             "mtermvectors",
179             "percolate",
180             "query",
181             "scroll",
182             "search_shards",
183             "source",
184             "suggest",
185             "template",
186             "termvectors",
187             "update",
188             "search",
189         )
190         if op in known_names:
191             return op.title()
192         return "Unknown"
193     except Exception:
194         return "Unknown"
195 
196 
197 @wrapt.decorator
198 def wrapped_perform_request(wrapped, instance, args, kwargs):
199     try:
200         op = _sanitize_name(args[1])
201     except IndexError:
202         op = "Unknown"
203 
204     tracked_request = TrackedRequest.instance()
205     tracked_request.start_span(
206         operation="Elasticsearch/{}".format(op), ignore_children=True
207     )
208 
209     try:
210         return wrapped(*args, **kwargs)
211     finally:
212         tracked_request.stop_span()
213
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/compat.py b/src/scout_apm/compat.py
--- a/src/scout_apm/compat.py
+++ b/src/scout_apm/compat.py
@@ -63,8 +63,21 @@
     from urlparse import parse_qsl
 
 
+if sys.version_info >= (3, 0):
+
+    def get_pos_args(func):
+        return inspect.getfullargspec(func).args
+
+
+else:
+
+    def get_pos_args(func):
+        return inspect.getargspec(func).args
+
+
 def kwargs_only(func):
     """
+    Source: https://pypi.org/project/kwargs-only/
     Make a function only accept keyword arguments.
     This can be dropped in Python 3 in lieu of:
         def foo(*, bar=default):
diff --git a/src/scout_apm/instruments/elasticsearch.py b/src/scout_apm/instruments/elasticsearch.py
--- a/src/scout_apm/instruments/elasticsearch.py
+++ b/src/scout_apm/instruments/elasticsearch.py
@@ -6,6 +6,7 @@
 
 import wrapt
 
+from scout_apm.compat import get_pos_args
 from scout_apm.core.tracked_request import TrackedRequest
 
 try:
@@ -57,7 +58,6 @@
     ClientMethod("reindex", False),
     ClientMethod("reindex_rethrottle", False),
     ClientMethod("render_search_template", False),
-    ClientMethod("scripts_painless_context", False),
     ClientMethod("scripts_painless_execute", False),
     ClientMethod("scroll", False),
     ClientMethod("search", True),
@@ -86,34 +86,50 @@
                     wrapped = wrap_client_method(method)
                 setattr(Elasticsearch, name, wrapped)
             except Exception as exc:
-                # Workaround for version 7.5.0 removing scripts_painless_context:
-                # https://github.com/elastic/elasticsearch-py/issues/1098
-                if name != "scripts_painless_context":
-                    logger.warning(
-                        "Unable to instrument elasticsearch.Elasticsearch.%s: %r",
-                        name,
-                        exc,
-                        exc_info=exc,
-                    )
+                logger.warning(
+                    "Unable to instrument elasticsearch.Elasticsearch.%s: %r",
+                    name,
+                    exc,
+                    exc_info=exc,
+                )
 
         have_patched_client = True
 
 
 @wrapt.decorator
 def wrap_client_index_method(wrapped, instance, args, kwargs):
-    def _get_index(index, *args, **kwargs):
-        return index
+    # elasticsearch-py 7.5.1 changed the order of arguments for client methods,
+    # so to be safe we need to inspect the wrapped method's positional
+    # arguments to see if we should pull it from there
+    if "index" in kwargs:
+        index = kwargs["index"]
+    else:
+        unwrapped = wrapped
+        while True:
+            try:
+                unwrapped = unwrapped.__wrapped__
+            except AttributeError:
+                break
 
-    try:
-        index = _get_index(*args, **kwargs)
-    except TypeError:
+        pos_args = get_pos_args(unwrapped)
+        try:
+            index_index = pos_args.index("index")
+        except ValueError:  # pragma: no cover
+            # This guards against the method not accepting an 'index' argument
+            # but they all do - for now
+            index = ""
+        else:
+            try:
+                index = args[index_index - 1]  # subtract 'self'
+            except IndexError:
+                index = ""
+
+    if isinstance(index, (list, tuple)):
+        index = ",".join(index)
+    if index == "":
         index = "Unknown"
-    else:
-        if not index:
-            index = "Unknown"
-        if isinstance(index, (list, tuple)):
-            index = ",".join(index)
     index = index.title()
+
     camel_name = "".join(c.title() for c in wrapped.__name__.split("_"))
     operation = "Elasticsearch/{}/{}".format(index, camel_name)
     tracked_request = TrackedRequest.instance()

{"golden_diff": "diff --git a/src/scout_apm/compat.py b/src/scout_apm/compat.py\n--- a/src/scout_apm/compat.py\n+++ b/src/scout_apm/compat.py\n@@ -63,8 +63,21 @@\n     from urlparse import parse_qsl\n \n \n+if sys.version_info >= (3, 0):\n+\n+    def get_pos_args(func):\n+        return inspect.getfullargspec(func).args\n+\n+\n+else:\n+\n+    def get_pos_args(func):\n+        return inspect.getargspec(func).args\n+\n+\n def kwargs_only(func):\n     \"\"\"\n+    Source: https://pypi.org/project/kwargs-only/\n     Make a function only accept keyword arguments.\n     This can be dropped in Python 3 in lieu of:\n         def foo(*, bar=default):\ndiff --git a/src/scout_apm/instruments/elasticsearch.py b/src/scout_apm/instruments/elasticsearch.py\n--- a/src/scout_apm/instruments/elasticsearch.py\n+++ b/src/scout_apm/instruments/elasticsearch.py\n@@ -6,6 +6,7 @@\n \n import wrapt\n \n+from scout_apm.compat import get_pos_args\n from scout_apm.core.tracked_request import TrackedRequest\n \n try:\n@@ -57,7 +58,6 @@\n     ClientMethod(\"reindex\", False),\n     ClientMethod(\"reindex_rethrottle\", False),\n     ClientMethod(\"render_search_template\", False),\n-    ClientMethod(\"scripts_painless_context\", False),\n     ClientMethod(\"scripts_painless_execute\", False),\n     ClientMethod(\"scroll\", False),\n     ClientMethod(\"search\", True),\n@@ -86,34 +86,50 @@\n                     wrapped = wrap_client_method(method)\n                 setattr(Elasticsearch, name, wrapped)\n             except Exception as exc:\n-                # Workaround for version 7.5.0 removing scripts_painless_context:\n-                # https://github.com/elastic/elasticsearch-py/issues/1098\n-                if name != \"scripts_painless_context\":\n-                    logger.warning(\n-                        \"Unable to instrument elasticsearch.Elasticsearch.%s: %r\",\n-                        name,\n-                        exc,\n-                        exc_info=exc,\n-                    )\n+                logger.warning(\n+                    \"Unable to instrument elasticsearch.Elasticsearch.%s: %r\",\n+                    name,\n+                    exc,\n+                    exc_info=exc,\n+                )\n \n         have_patched_client = True\n \n \n @wrapt.decorator\n def wrap_client_index_method(wrapped, instance, args, kwargs):\n-    def _get_index(index, *args, **kwargs):\n-        return index\n+    # elasticsearch-py 7.5.1 changed the order of arguments for client methods,\n+    # so to be safe we need to inspect the wrapped method's positional\n+    # arguments to see if we should pull it from there\n+    if \"index\" in kwargs:\n+        index = kwargs[\"index\"]\n+    else:\n+        unwrapped = wrapped\n+        while True:\n+            try:\n+                unwrapped = unwrapped.__wrapped__\n+            except AttributeError:\n+                break\n \n-    try:\n-        index = _get_index(*args, **kwargs)\n-    except TypeError:\n+        pos_args = get_pos_args(unwrapped)\n+        try:\n+            index_index = pos_args.index(\"index\")\n+        except ValueError:  # pragma: no cover\n+            # This guards against the method not accepting an 'index' argument\n+            # but they all do - for now\n+            index = \"\"\n+        else:\n+            try:\n+                index = args[index_index - 1]  # subtract 'self'\n+            except IndexError:\n+                index = \"\"\n+\n+    if isinstance(index, (list, tuple)):\n+        index = \",\".join(index)\n+    if index == \"\":\n         index = \"Unknown\"\n-    else:\n-        if not index:\n-            index = \"Unknown\"\n-        if isinstance(index, (list, tuple)):\n-            index = \",\".join(index)\n     index = index.title()\n+\n     camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n     operation = \"Elasticsearch/{}/{}\".format(index, camel_name)\n     tracked_request = TrackedRequest.instance()\n", "issue": "Elasticsearch 7.5.1 changed positional arguments order\nSimilar to #454, Elasticsearch 7.5.1 broke the test suite in another way.\r\n\r\nhttps://github.com/elastic/elasticsearch-py/issues/1099\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport inspect\nimport sys\nfrom functools import wraps\n\nstring_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821\ntext_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821\nstring_types = tuple({string_type, text_type})\n\nif sys.version_info >= (3, 2):\n    from contextlib import ContextDecorator\nelse:\n    import functools\n\n    class ContextDecorator(object):\n        def __call__(self, f):\n            @functools.wraps(f)\n            def decorated(*args, **kwds):\n                with self:\n                    return f(*args, **kwds)\n\n            return decorated\n\n\nif sys.version_info >= (3, 0):\n    import queue\nelse:\n    import Queue as queue\n\n# datetime_to_timestamp converts a naive UTC datetime to a unix timestamp\nif sys.version_info >= (3, 3):\n\n    def datetime_to_timestamp(datetime_obj):\n        return datetime_obj.replace(tzinfo=dt.timezone.utc).timestamp()\n\n\nelse:\n    _EPOCH = dt.datetime(1970, 1, 1)\n\n    def datetime_to_timestamp(datetime_obj):\n        return (datetime_obj - _EPOCH).total_seconds()\n\n\ndef text(value, encoding=\"utf-8\", errors=\"strict\"):\n    \"\"\"\n    Convert a value to str on Python 3 and unicode on Python 2.\n    \"\"\"\n    if isinstance(value, text_type):\n        return value\n    elif isinstance(value, bytes):\n        return text_type(value, encoding, errors)\n    else:\n        return text_type(value)\n\n\nif sys.version_info >= (3, 0):\n    from urllib.parse import parse_qsl, urlencode\nelse:\n    from urllib import urlencode\n    from urlparse import parse_qsl\n\n\ndef kwargs_only(func):\n    \"\"\"\n    Make a function only accept keyword arguments.\n    This can be dropped in Python 3 in lieu of:\n        def foo(*, bar=default):\n    Source: https://pypi.org/project/kwargs-only/\n    \"\"\"\n    if hasattr(inspect, \"signature\"):  # pragma: no cover\n        # Python 3\n        signature = inspect.signature(func)\n        arg_names = list(signature.parameters.keys())\n    else:  # pragma: no cover\n        # Python 2\n        signature = inspect.getargspec(func)\n        arg_names = signature.args\n\n    if len(arg_names) > 0 and arg_names[0] in (\"self\", \"cls\"):\n        allowable_args = 1\n    else:\n        allowable_args = 0\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        if len(args) > allowable_args:\n            raise TypeError(\n                \"{} should only be called with keyword args\".format(func.__name__)\n            )\n        return func(*args, **kwargs)\n\n    return wrapper\n\n\n__all__ = [\n    \"ContextDecorator\",\n    \"datetime_to_timestamp\",\n    \"kwargs_only\",\n    \"parse_qsl\",\n    \"queue\",\n    \"string_type\",\n    \"text\",\n    \"text_type\",\n    \"urlencode\",\n]\n", "path": "src/scout_apm/compat.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nfrom collections import namedtuple\n\nimport wrapt\n\nfrom scout_apm.core.tracked_request import TrackedRequest\n\ntry:\n    from elasticsearch import Elasticsearch, Transport\nexcept ImportError:  # pragma: no cover\n    Elasticsearch = None\n    Transport = None\n\nlogger = logging.getLogger(__name__)\n\n\ndef ensure_installed():\n    logger.info(\"Ensuring elasticsearch instrumentation is installed.\")\n\n    if Elasticsearch is None:\n        logger.info(\"Unable to import elasticsearch.Elasticsearch\")\n    else:\n        ensure_client_instrumented()\n        ensure_transport_instrumented()\n\n\nClientMethod = namedtuple(\"ClientMethod\", [\"name\", \"takes_index_argument\"])\n\nCLIENT_METHODS = [\n    ClientMethod(\"bulk\", True),\n    ClientMethod(\"clear_scroll\", False),\n    ClientMethod(\"count\", True),\n    ClientMethod(\"create\", True),\n    ClientMethod(\"delete\", True),\n    ClientMethod(\"delete_by_query\", True),\n    ClientMethod(\"delete_by_query_rethrottle\", False),\n    ClientMethod(\"delete_script\", False),\n    ClientMethod(\"exists\", True),\n    ClientMethod(\"exists_source\", True),\n    ClientMethod(\"explain\", True),\n    ClientMethod(\"field_caps\", True),\n    ClientMethod(\"get\", True),\n    ClientMethod(\"get_script\", False),\n    ClientMethod(\"get_source\", True),\n    ClientMethod(\"index\", True),\n    ClientMethod(\"info\", False),\n    ClientMethod(\"mget\", True),\n    ClientMethod(\"msearch\", True),\n    ClientMethod(\"msearch_template\", True),\n    ClientMethod(\"mtermvectors\", True),\n    ClientMethod(\"ping\", False),\n    ClientMethod(\"put_script\", False),\n    ClientMethod(\"rank_eval\", True),\n    ClientMethod(\"reindex\", False),\n    ClientMethod(\"reindex_rethrottle\", False),\n    ClientMethod(\"render_search_template\", False),\n    ClientMethod(\"scripts_painless_context\", False),\n    ClientMethod(\"scripts_painless_execute\", False),\n    ClientMethod(\"scroll\", False),\n    ClientMethod(\"search\", True),\n    ClientMethod(\"search_shards\", True),\n    ClientMethod(\"search_template\", True),\n    ClientMethod(\"termvectors\", True),\n    ClientMethod(\"update\", True),\n    ClientMethod(\"update_by_query\", True),\n    ClientMethod(\"update_by_query_rethrottle\", False),\n]\n\n\nhave_patched_client = False\n\n\ndef ensure_client_instrumented():\n    global have_patched_client\n\n    if not have_patched_client:\n        for name, takes_index_argument in CLIENT_METHODS:\n            try:\n                method = getattr(Elasticsearch, name)\n                if takes_index_argument:\n                    wrapped = wrap_client_index_method(method)\n                else:\n                    wrapped = wrap_client_method(method)\n                setattr(Elasticsearch, name, wrapped)\n            except Exception as exc:\n                # Workaround for version 7.5.0 removing scripts_painless_context:\n                # https://github.com/elastic/elasticsearch-py/issues/1098\n                if name != \"scripts_painless_context\":\n                    logger.warning(\n                        \"Unable to instrument elasticsearch.Elasticsearch.%s: %r\",\n                        name,\n                        exc,\n                        exc_info=exc,\n                    )\n\n        have_patched_client = True\n\n\n@wrapt.decorator\ndef wrap_client_index_method(wrapped, instance, args, kwargs):\n    def _get_index(index, *args, **kwargs):\n        return index\n\n    try:\n        index = _get_index(*args, **kwargs)\n    except TypeError:\n        index = \"Unknown\"\n    else:\n        if not index:\n            index = \"Unknown\"\n        if isinstance(index, (list, tuple)):\n            index = \",\".join(index)\n    index = index.title()\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}/{}\".format(index, camel_name)\n    tracked_request = TrackedRequest.instance()\n    tracked_request.start_span(operation=operation, ignore_children=True)\n\n    try:\n        return wrapped(*args, **kwargs)\n    finally:\n        tracked_request.stop_span()\n\n\n@wrapt.decorator\ndef wrap_client_method(wrapped, instance, args, kwargs):\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}\".format(camel_name)\n    tracked_request = TrackedRequest.instance()\n    tracked_request.start_span(operation=operation, ignore_children=True)\n\n    try:\n        return wrapped(*args, **kwargs)\n    finally:\n        tracked_request.stop_span()\n\n\nhave_patched_transport = False\n\n\ndef ensure_transport_instrumented():\n    global have_patched_transport\n\n    if not have_patched_transport:\n        try:\n            Transport.perform_request = wrapped_perform_request(\n                Transport.perform_request\n            )\n        except Exception as exc:\n            logger.warning(\n                \"Unable to instrument elasticsearch.Transport.perform_request: %r\",\n                exc,\n                exc_info=exc,\n            )\n\n    have_patched_transport = True\n\n\ndef _sanitize_name(name):\n    try:\n        op = name.split(\"/\")[-1]\n        op = op[1:]  # chop leading '_' from op\n        known_names = (\n            \"bench\",\n            \"bulk\",\n            \"count\",\n            \"exists\",\n            \"explain\",\n            \"field_stats\",\n            \"health\",\n            \"mget\",\n            \"mlt\",\n            \"mpercolate\",\n            \"msearch\",\n            \"mtermvectors\",\n            \"percolate\",\n            \"query\",\n            \"scroll\",\n            \"search_shards\",\n            \"source\",\n            \"suggest\",\n            \"template\",\n            \"termvectors\",\n            \"update\",\n            \"search\",\n        )\n        if op in known_names:\n            return op.title()\n        return \"Unknown\"\n    except Exception:\n        return \"Unknown\"\n\n\n@wrapt.decorator\ndef wrapped_perform_request(wrapped, instance, args, kwargs):\n    try:\n        op = _sanitize_name(args[1])\n    except IndexError:\n        op = \"Unknown\"\n\n    tracked_request = TrackedRequest.instance()\n    tracked_request.start_span(\n        operation=\"Elasticsearch/{}\".format(op), ignore_children=True\n    )\n\n    try:\n        return wrapped(*args, **kwargs)\n    finally:\n        tracked_request.stop_span()\n", "path": "src/scout_apm/instruments/elasticsearch.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport inspect\nimport sys\nfrom functools import wraps\n\nstring_type = str if sys.version_info[0] >= 3 else basestring  # noqa: F821\ntext_type = str if sys.version_info[0] >= 3 else unicode  # noqa: F821\nstring_types = tuple({string_type, text_type})\n\nif sys.version_info >= (3, 2):\n    from contextlib import ContextDecorator\nelse:\n    import functools\n\n    class ContextDecorator(object):\n        def __call__(self, f):\n            @functools.wraps(f)\n            def decorated(*args, **kwds):\n                with self:\n                    return f(*args, **kwds)\n\n            return decorated\n\n\nif sys.version_info >= (3, 0):\n    import queue\nelse:\n    import Queue as queue\n\n# datetime_to_timestamp converts a naive UTC datetime to a unix timestamp\nif sys.version_info >= (3, 3):\n\n    def datetime_to_timestamp(datetime_obj):\n        return datetime_obj.replace(tzinfo=dt.timezone.utc).timestamp()\n\n\nelse:\n    _EPOCH = dt.datetime(1970, 1, 1)\n\n    def datetime_to_timestamp(datetime_obj):\n        return (datetime_obj - _EPOCH).total_seconds()\n\n\ndef text(value, encoding=\"utf-8\", errors=\"strict\"):\n    \"\"\"\n    Convert a value to str on Python 3 and unicode on Python 2.\n    \"\"\"\n    if isinstance(value, text_type):\n        return value\n    elif isinstance(value, bytes):\n        return text_type(value, encoding, errors)\n    else:\n        return text_type(value)\n\n\nif sys.version_info >= (3, 0):\n    from urllib.parse import parse_qsl, urlencode\nelse:\n    from urllib import urlencode\n    from urlparse import parse_qsl\n\n\nif sys.version_info >= (3, 0):\n\n    def get_pos_args(func):\n        return inspect.getfullargspec(func).args\n\n\nelse:\n\n    def get_pos_args(func):\n        return inspect.getargspec(func).args\n\n\ndef kwargs_only(func):\n    \"\"\"\n    Source: https://pypi.org/project/kwargs-only/\n    Make a function only accept keyword arguments.\n    This can be dropped in Python 3 in lieu of:\n        def foo(*, bar=default):\n    Source: https://pypi.org/project/kwargs-only/\n    \"\"\"\n    if hasattr(inspect, \"signature\"):  # pragma: no cover\n        # Python 3\n        signature = inspect.signature(func)\n        arg_names = list(signature.parameters.keys())\n    else:  # pragma: no cover\n        # Python 2\n        signature = inspect.getargspec(func)\n        arg_names = signature.args\n\n    if len(arg_names) > 0 and arg_names[0] in (\"self\", \"cls\"):\n        allowable_args = 1\n    else:\n        allowable_args = 0\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        if len(args) > allowable_args:\n            raise TypeError(\n                \"{} should only be called with keyword args\".format(func.__name__)\n            )\n        return func(*args, **kwargs)\n\n    return wrapper\n\n\n__all__ = [\n    \"ContextDecorator\",\n    \"datetime_to_timestamp\",\n    \"kwargs_only\",\n    \"parse_qsl\",\n    \"queue\",\n    \"string_type\",\n    \"text\",\n    \"text_type\",\n    \"urlencode\",\n]\n", "path": "src/scout_apm/compat.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nfrom collections import namedtuple\n\nimport wrapt\n\nfrom scout_apm.compat import get_pos_args\nfrom scout_apm.core.tracked_request import TrackedRequest\n\ntry:\n    from elasticsearch import Elasticsearch, Transport\nexcept ImportError:  # pragma: no cover\n    Elasticsearch = None\n    Transport = None\n\nlogger = logging.getLogger(__name__)\n\n\ndef ensure_installed():\n    logger.info(\"Ensuring elasticsearch instrumentation is installed.\")\n\n    if Elasticsearch is None:\n        logger.info(\"Unable to import elasticsearch.Elasticsearch\")\n    else:\n        ensure_client_instrumented()\n        ensure_transport_instrumented()\n\n\nClientMethod = namedtuple(\"ClientMethod\", [\"name\", \"takes_index_argument\"])\n\nCLIENT_METHODS = [\n    ClientMethod(\"bulk\", True),\n    ClientMethod(\"clear_scroll\", False),\n    ClientMethod(\"count\", True),\n    ClientMethod(\"create\", True),\n    ClientMethod(\"delete\", True),\n    ClientMethod(\"delete_by_query\", True),\n    ClientMethod(\"delete_by_query_rethrottle\", False),\n    ClientMethod(\"delete_script\", False),\n    ClientMethod(\"exists\", True),\n    ClientMethod(\"exists_source\", True),\n    ClientMethod(\"explain\", True),\n    ClientMethod(\"field_caps\", True),\n    ClientMethod(\"get\", True),\n    ClientMethod(\"get_script\", False),\n    ClientMethod(\"get_source\", True),\n    ClientMethod(\"index\", True),\n    ClientMethod(\"info\", False),\n    ClientMethod(\"mget\", True),\n    ClientMethod(\"msearch\", True),\n    ClientMethod(\"msearch_template\", True),\n    ClientMethod(\"mtermvectors\", True),\n    ClientMethod(\"ping\", False),\n    ClientMethod(\"put_script\", False),\n    ClientMethod(\"rank_eval\", True),\n    ClientMethod(\"reindex\", False),\n    ClientMethod(\"reindex_rethrottle\", False),\n    ClientMethod(\"render_search_template\", False),\n    ClientMethod(\"scripts_painless_execute\", False),\n    ClientMethod(\"scroll\", False),\n    ClientMethod(\"search\", True),\n    ClientMethod(\"search_shards\", True),\n    ClientMethod(\"search_template\", True),\n    ClientMethod(\"termvectors\", True),\n    ClientMethod(\"update\", True),\n    ClientMethod(\"update_by_query\", True),\n    ClientMethod(\"update_by_query_rethrottle\", False),\n]\n\n\nhave_patched_client = False\n\n\ndef ensure_client_instrumented():\n    global have_patched_client\n\n    if not have_patched_client:\n        for name, takes_index_argument in CLIENT_METHODS:\n            try:\n                method = getattr(Elasticsearch, name)\n                if takes_index_argument:\n                    wrapped = wrap_client_index_method(method)\n                else:\n                    wrapped = wrap_client_method(method)\n                setattr(Elasticsearch, name, wrapped)\n            except Exception as exc:\n                logger.warning(\n                    \"Unable to instrument elasticsearch.Elasticsearch.%s: %r\",\n                    name,\n                    exc,\n                    exc_info=exc,\n                )\n\n        have_patched_client = True\n\n\n@wrapt.decorator\ndef wrap_client_index_method(wrapped, instance, args, kwargs):\n    # elasticsearch-py 7.5.1 changed the order of arguments for client methods,\n    # so to be safe we need to inspect the wrapped method's positional\n    # arguments to see if we should pull it from there\n    if \"index\" in kwargs:\n        index = kwargs[\"index\"]\n    else:\n        unwrapped = wrapped\n        while True:\n            try:\n                unwrapped = unwrapped.__wrapped__\n            except AttributeError:\n                break\n\n        pos_args = get_pos_args(unwrapped)\n        try:\n            index_index = pos_args.index(\"index\")\n        except ValueError:  # pragma: no cover\n            # This guards against the method not accepting an 'index' argument\n            # but they all do - for now\n            index = \"\"\n        else:\n            try:\n                index = args[index_index - 1]  # subtract 'self'\n            except IndexError:\n                index = \"\"\n\n    if isinstance(index, (list, tuple)):\n        index = \",\".join(index)\n    if index == \"\":\n        index = \"Unknown\"\n    index = index.title()\n\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}/{}\".format(index, camel_name)\n    tracked_request = TrackedRequest.instance()\n    tracked_request.start_span(operation=operation, ignore_children=True)\n\n    try:\n        return wrapped(*args, **kwargs)\n    finally:\n        tracked_request.stop_span()\n\n\n@wrapt.decorator\ndef wrap_client_method(wrapped, instance, args, kwargs):\n    camel_name = \"\".join(c.title() for c in wrapped.__name__.split(\"_\"))\n    operation = \"Elasticsearch/{}\".format(camel_name)\n    tracked_request = TrackedRequest.instance()\n    tracked_request.start_span(operation=operation, ignore_children=True)\n\n    try:\n        return wrapped(*args, **kwargs)\n    finally:\n        tracked_request.stop_span()\n\n\nhave_patched_transport = False\n\n\ndef ensure_transport_instrumented():\n    global have_patched_transport\n\n    if not have_patched_transport:\n        try:\n            Transport.perform_request = wrapped_perform_request(\n                Transport.perform_request\n            )\n        except Exception as exc:\n            logger.warning(\n                \"Unable to instrument elasticsearch.Transport.perform_request: %r\",\n                exc,\n                exc_info=exc,\n            )\n\n    have_patched_transport = True\n\n\ndef _sanitize_name(name):\n    try:\n        op = name.split(\"/\")[-1]\n        op = op[1:]  # chop leading '_' from op\n        known_names = (\n            \"bench\",\n            \"bulk\",\n            \"count\",\n            \"exists\",\n            \"explain\",\n            \"field_stats\",\n            \"health\",\n            \"mget\",\n            \"mlt\",\n            \"mpercolate\",\n            \"msearch\",\n            \"mtermvectors\",\n            \"percolate\",\n            \"query\",\n            \"scroll\",\n            \"search_shards\",\n            \"source\",\n            \"suggest\",\n            \"template\",\n            \"termvectors\",\n            \"update\",\n            \"search\",\n        )\n        if op in known_names:\n            return op.title()\n        return \"Unknown\"\n    except Exception:\n        return \"Unknown\"\n\n\n@wrapt.decorator\ndef wrapped_perform_request(wrapped, instance, args, kwargs):\n    try:\n        op = _sanitize_name(args[1])\n    except IndexError:\n        op = \"Unknown\"\n\n    tracked_request = TrackedRequest.instance()\n    tracked_request.start_span(\n        operation=\"Elasticsearch/{}\".format(op), ignore_children=True\n    )\n\n    try:\n        return wrapped(*args, **kwargs)\n    finally:\n        tracked_request.stop_span()\n", "path": "src/scout_apm/instruments/elasticsearch.py"}]}