We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Wrong transaction name in Django 4.0+
**Describe the bug**:
When using Django versions 4.0+, the transaction names are incorrect.

Expected: `django_app.views.ListUsers`
Actual: `django_app.views.view`


**Environment (please complete the following information)**
- OS: Debian Bullseye
- Python version: 3.9.13
- Framework and version: Django 4.0.5
- APM Server version: 7.12.0
- Agent version: 6.9.1


**Additional context**

This issue seems to be introduced with the following [PR](https://github.com/django/django/pull/14124) to Django.
https://github.com/django/django/blob/e96320c91724830034033a9cb8afd9cf8c11e2fd/django/views/generic/base.py#L108-#L110

I will try to provide a fix shortly.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `elasticapm/utils/__init__.py`
Content:
```
1 #  BSD 3-Clause License
2 #
3 #  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details
4 #  Copyright (c) 2019, Elasticsearch BV
5 #  All rights reserved.
6 #
7 #  Redistribution and use in source and binary forms, with or without
8 #  modification, are permitted provided that the following conditions are met:
9 #
10 #  * Redistributions of source code must retain the above copyright notice, this
11 #    list of conditions and the following disclaimer.
12 #
13 #  * Redistributions in binary form must reproduce the above copyright notice,
14 #    this list of conditions and the following disclaimer in the documentation
15 #    and/or other materials provided with the distribution.
16 #
17 #  * Neither the name of the copyright holder nor the names of its
18 #    contributors may be used to endorse or promote products derived from
19 #    this software without specific prior written permission.
20 #
21 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 
31 import base64
32 import os
33 import re
34 import urllib.parse
35 from functools import partial
36 from types import FunctionType
37 from typing import Pattern
38 
39 from elasticapm.conf import constants
40 from elasticapm.utils import encoding
41 
42 try:
43     from functools import partialmethod
44 
45     partial_types = (partial, partialmethod)
46 except ImportError:
47     # Python 2
48     partial_types = (partial,)
49 
50 
51 default_ports = {"https": 443, "http": 80, "postgresql": 5432, "mysql": 3306, "mssql": 1433}
52 
53 
54 def varmap(func, var, context=None, name=None, **kwargs):
55     """
56     Executes ``func(key_name, value)`` on all values,
57     recursively discovering dict and list scoped
58     values.
59     """
60     if context is None:
61         context = set()
62     objid = id(var)
63     if objid in context:
64         return func(name, "<...>", **kwargs)
65     context.add(objid)
66     if isinstance(var, dict):
67         # iterate over a copy of the dictionary to avoid "dictionary changed size during iteration" issues
68         ret = func(name, dict((k, varmap(func, v, context, k, **kwargs)) for k, v in var.copy().items()), **kwargs)
69     elif isinstance(var, (list, tuple)):
70         ret = func(name, [varmap(func, f, context, name, **kwargs) for f in var], **kwargs)
71     else:
72         ret = func(name, var, **kwargs)
73     context.remove(objid)
74     return ret
75 
76 
77 def get_name_from_func(func: FunctionType) -> str:
78     # partials don't have `__module__` or `__name__`, so we use the values from the "inner" function
79     if isinstance(func, partial_types):
80         return "partial({})".format(get_name_from_func(func.func))
81     elif hasattr(func, "_partialmethod") and hasattr(func._partialmethod, "func"):
82         return "partial({})".format(get_name_from_func(func._partialmethod.func))
83 
84     module = func.__module__
85 
86     if hasattr(func, "__name__"):
87         view_name = func.__name__
88     else:  # Fall back if there's no __name__
89         view_name = func.__class__.__name__
90 
91     return "{0}.{1}".format(module, view_name)
92 
93 
94 def build_name_with_http_method_prefix(name, request):
95     return " ".join((request.method, name)) if name else name
96 
97 
98 def is_master_process() -> bool:
99     # currently only recognizes uwsgi master process
100     try:
101         import uwsgi
102 
103         return os.getpid() == uwsgi.masterpid()
104     except ImportError:
105         return False
106 
107 
108 def get_url_dict(url: str) -> dict:
109     parse_result = urllib.parse.urlparse(url)
110 
111     url_dict = {
112         "full": encoding.keyword_field(url),
113         "protocol": parse_result.scheme + ":",
114         "hostname": encoding.keyword_field(parse_result.hostname),
115         "pathname": encoding.keyword_field(parse_result.path),
116     }
117 
118     port = None if parse_result.port is None else str(parse_result.port)
119 
120     if port:
121         url_dict["port"] = port
122     if parse_result.query:
123         url_dict["search"] = encoding.keyword_field("?" + parse_result.query)
124     return url_dict
125 
126 
127 def sanitize_url(url: str) -> str:
128     if "@" not in url:
129         return url
130     parts = urllib.parse.urlparse(url)
131     return url.replace("%s:%s" % (parts.username, parts.password), "%s:%s" % (parts.username, constants.MASK))
132 
133 
134 def get_host_from_url(url: str) -> str:
135     parsed_url = urllib.parse.urlparse(url)
136     host = parsed_url.hostname or " "
137 
138     if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port:
139         host += ":" + str(parsed_url.port)
140 
141     return host
142 
143 
144 def url_to_destination_resource(url: str) -> str:
145     parts = urllib.parse.urlsplit(url)
146     hostname = parts.hostname if parts.hostname else ""
147     # preserve brackets for IPv6 URLs
148     if "://[" in url:
149         hostname = "[%s]" % hostname
150     try:
151         port = parts.port
152     except ValueError:
153         # Malformed port, just use None rather than raising an exception
154         port = None
155     default_port = default_ports.get(parts.scheme, None)
156     name = "%s://%s" % (parts.scheme, hostname)
157     resource = hostname
158     if not port and parts.scheme in default_ports:
159         port = default_ports[parts.scheme]
160     if port:
161         if port != default_port:
162             name += ":%d" % port
163         resource += ":%d" % port
164     return resource
165 
166 
167 def read_pem_file(file_obj) -> bytes:
168     cert = b""
169     for line in file_obj:
170         if line.startswith(b"-----BEGIN CERTIFICATE-----"):
171             break
172     # scan until we find the first END CERTIFICATE marker
173     for line in file_obj:
174         if line.startswith(b"-----END CERTIFICATE-----"):
175             break
176         cert += line.strip()
177     return base64.b64decode(cert)
178 
179 
180 def starmatch_to_regex(pattern: str) -> Pattern:
181     options = re.DOTALL
182     # check if we are case sensitive
183     if pattern.startswith("(?-i)"):
184         pattern = pattern[5:]
185     else:
186         options |= re.IGNORECASE
187     i, n = 0, len(pattern)
188     res = []
189     while i < n:
190         c = pattern[i]
191         i = i + 1
192         if c == "*":
193             res.append(".*")
194         else:
195             res.append(re.escape(c))
196     return re.compile(r"(?:%s)\Z" % "".join(res), options)
197 
198 
199 def nested_key(d: dict, *args):
200     """
201     Traverses a dictionary for nested keys. Returns `None` if the at any point
202     in the traversal a key cannot be found.
203 
204     Example:
205 
206         >>> from elasticapm.utils import nested_key
207         >>> d = {"a": {"b": {"c": 0}}}
208         >>> nested_key(d, "a", "b", "c")
209         0
210         >>> nested_key(d, "a", "b", "d")
211         None
212     """
213     for arg in args:
214         try:
215             d = d[arg]
216         except (TypeError, KeyError):
217             d = None
218             break
219     return d
220
```
--- 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/elasticapm/utils/__init__.py b/elasticapm/utils/__init__.py
--- a/elasticapm/utils/__init__.py
+++ b/elasticapm/utils/__init__.py
@@ -83,7 +83,9 @@
 
     module = func.__module__
 
-    if hasattr(func, "__name__"):
+    if hasattr(func, "view_class"):
+        view_name = func.view_class.__name__
+    elif hasattr(func, "__name__"):
         view_name = func.__name__
     else:  # Fall back if there's no __name__
         view_name = func.__class__.__name__

{"golden_diff": "diff --git a/elasticapm/utils/__init__.py b/elasticapm/utils/__init__.py\n--- a/elasticapm/utils/__init__.py\n+++ b/elasticapm/utils/__init__.py\n@@ -83,7 +83,9 @@\n \n     module = func.__module__\n \n-    if hasattr(func, \"__name__\"):\n+    if hasattr(func, \"view_class\"):\n+        view_name = func.view_class.__name__\n+    elif hasattr(func, \"__name__\"):\n         view_name = func.__name__\n     else:  # Fall back if there's no __name__\n         view_name = func.__class__.__name__\n", "issue": "Wrong transaction name in Django 4.0+\n**Describe the bug**:\r\nWhen using Django versions 4.0+, the transaction names are incorrect.\r\n\r\nExpected: `django_app.views.ListUsers`\r\nActual: `django_app.views.view`\r\n\r\n\r\n**Environment (please complete the following information)**\r\n- OS: Debian Bullseye\r\n- Python version: 3.9.13\r\n- Framework and version: Django 4.0.5\r\n- APM Server version: 7.12.0\r\n- Agent version: 6.9.1\r\n\r\n\r\n**Additional context**\r\n\r\nThis issue seems to be introduced with the following [PR](https://github.com/django/django/pull/14124) to Django.\r\nhttps://github.com/django/django/blob/e96320c91724830034033a9cb8afd9cf8c11e2fd/django/views/generic/base.py#L108-#L110\r\n\r\nI will try to provide a fix shortly.\n", "before_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\nimport base64\nimport os\nimport re\nimport urllib.parse\nfrom functools import partial\nfrom types import FunctionType\nfrom typing import Pattern\n\nfrom elasticapm.conf import constants\nfrom elasticapm.utils import encoding\n\ntry:\n    from functools import partialmethod\n\n    partial_types = (partial, partialmethod)\nexcept ImportError:\n    # Python 2\n    partial_types = (partial,)\n\n\ndefault_ports = {\"https\": 443, \"http\": 80, \"postgresql\": 5432, \"mysql\": 3306, \"mssql\": 1433}\n\n\ndef varmap(func, var, context=None, name=None, **kwargs):\n    \"\"\"\n    Executes ``func(key_name, value)`` on all values,\n    recursively discovering dict and list scoped\n    values.\n    \"\"\"\n    if context is None:\n        context = set()\n    objid = id(var)\n    if objid in context:\n        return func(name, \"<...>\", **kwargs)\n    context.add(objid)\n    if isinstance(var, dict):\n        # iterate over a copy of the dictionary to avoid \"dictionary changed size during iteration\" issues\n        ret = func(name, dict((k, varmap(func, v, context, k, **kwargs)) for k, v in var.copy().items()), **kwargs)\n    elif isinstance(var, (list, tuple)):\n        ret = func(name, [varmap(func, f, context, name, **kwargs) for f in var], **kwargs)\n    else:\n        ret = func(name, var, **kwargs)\n    context.remove(objid)\n    return ret\n\n\ndef get_name_from_func(func: FunctionType) -> str:\n    # partials don't have `__module__` or `__name__`, so we use the values from the \"inner\" function\n    if isinstance(func, partial_types):\n        return \"partial({})\".format(get_name_from_func(func.func))\n    elif hasattr(func, \"_partialmethod\") and hasattr(func._partialmethod, \"func\"):\n        return \"partial({})\".format(get_name_from_func(func._partialmethod.func))\n\n    module = func.__module__\n\n    if hasattr(func, \"__name__\"):\n        view_name = func.__name__\n    else:  # Fall back if there's no __name__\n        view_name = func.__class__.__name__\n\n    return \"{0}.{1}\".format(module, view_name)\n\n\ndef build_name_with_http_method_prefix(name, request):\n    return \" \".join((request.method, name)) if name else name\n\n\ndef is_master_process() -> bool:\n    # currently only recognizes uwsgi master process\n    try:\n        import uwsgi\n\n        return os.getpid() == uwsgi.masterpid()\n    except ImportError:\n        return False\n\n\ndef get_url_dict(url: str) -> dict:\n    parse_result = urllib.parse.urlparse(url)\n\n    url_dict = {\n        \"full\": encoding.keyword_field(url),\n        \"protocol\": parse_result.scheme + \":\",\n        \"hostname\": encoding.keyword_field(parse_result.hostname),\n        \"pathname\": encoding.keyword_field(parse_result.path),\n    }\n\n    port = None if parse_result.port is None else str(parse_result.port)\n\n    if port:\n        url_dict[\"port\"] = port\n    if parse_result.query:\n        url_dict[\"search\"] = encoding.keyword_field(\"?\" + parse_result.query)\n    return url_dict\n\n\ndef sanitize_url(url: str) -> str:\n    if \"@\" not in url:\n        return url\n    parts = urllib.parse.urlparse(url)\n    return url.replace(\"%s:%s\" % (parts.username, parts.password), \"%s:%s\" % (parts.username, constants.MASK))\n\n\ndef get_host_from_url(url: str) -> str:\n    parsed_url = urllib.parse.urlparse(url)\n    host = parsed_url.hostname or \" \"\n\n    if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port:\n        host += \":\" + str(parsed_url.port)\n\n    return host\n\n\ndef url_to_destination_resource(url: str) -> str:\n    parts = urllib.parse.urlsplit(url)\n    hostname = parts.hostname if parts.hostname else \"\"\n    # preserve brackets for IPv6 URLs\n    if \"://[\" in url:\n        hostname = \"[%s]\" % hostname\n    try:\n        port = parts.port\n    except ValueError:\n        # Malformed port, just use None rather than raising an exception\n        port = None\n    default_port = default_ports.get(parts.scheme, None)\n    name = \"%s://%s\" % (parts.scheme, hostname)\n    resource = hostname\n    if not port and parts.scheme in default_ports:\n        port = default_ports[parts.scheme]\n    if port:\n        if port != default_port:\n            name += \":%d\" % port\n        resource += \":%d\" % port\n    return resource\n\n\ndef read_pem_file(file_obj) -> bytes:\n    cert = b\"\"\n    for line in file_obj:\n        if line.startswith(b\"-----BEGIN CERTIFICATE-----\"):\n            break\n    # scan until we find the first END CERTIFICATE marker\n    for line in file_obj:\n        if line.startswith(b\"-----END CERTIFICATE-----\"):\n            break\n        cert += line.strip()\n    return base64.b64decode(cert)\n\n\ndef starmatch_to_regex(pattern: str) -> Pattern:\n    options = re.DOTALL\n    # check if we are case sensitive\n    if pattern.startswith(\"(?-i)\"):\n        pattern = pattern[5:]\n    else:\n        options |= re.IGNORECASE\n    i, n = 0, len(pattern)\n    res = []\n    while i < n:\n        c = pattern[i]\n        i = i + 1\n        if c == \"*\":\n            res.append(\".*\")\n        else:\n            res.append(re.escape(c))\n    return re.compile(r\"(?:%s)\\Z\" % \"\".join(res), options)\n\n\ndef nested_key(d: dict, *args):\n    \"\"\"\n    Traverses a dictionary for nested keys. Returns `None` if the at any point\n    in the traversal a key cannot be found.\n\n    Example:\n\n        >>> from elasticapm.utils import nested_key\n        >>> d = {\"a\": {\"b\": {\"c\": 0}}}\n        >>> nested_key(d, \"a\", \"b\", \"c\")\n        0\n        >>> nested_key(d, \"a\", \"b\", \"d\")\n        None\n    \"\"\"\n    for arg in args:\n        try:\n            d = d[arg]\n        except (TypeError, KeyError):\n            d = None\n            break\n    return d\n", "path": "elasticapm/utils/__init__.py"}], "after_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\nimport base64\nimport os\nimport re\nimport urllib.parse\nfrom functools import partial\nfrom types import FunctionType\nfrom typing import Pattern\n\nfrom elasticapm.conf import constants\nfrom elasticapm.utils import encoding\n\ntry:\n    from functools import partialmethod\n\n    partial_types = (partial, partialmethod)\nexcept ImportError:\n    # Python 2\n    partial_types = (partial,)\n\n\ndefault_ports = {\"https\": 443, \"http\": 80, \"postgresql\": 5432, \"mysql\": 3306, \"mssql\": 1433}\n\n\ndef varmap(func, var, context=None, name=None, **kwargs):\n    \"\"\"\n    Executes ``func(key_name, value)`` on all values,\n    recursively discovering dict and list scoped\n    values.\n    \"\"\"\n    if context is None:\n        context = set()\n    objid = id(var)\n    if objid in context:\n        return func(name, \"<...>\", **kwargs)\n    context.add(objid)\n    if isinstance(var, dict):\n        # iterate over a copy of the dictionary to avoid \"dictionary changed size during iteration\" issues\n        ret = func(name, dict((k, varmap(func, v, context, k, **kwargs)) for k, v in var.copy().items()), **kwargs)\n    elif isinstance(var, (list, tuple)):\n        ret = func(name, [varmap(func, f, context, name, **kwargs) for f in var], **kwargs)\n    else:\n        ret = func(name, var, **kwargs)\n    context.remove(objid)\n    return ret\n\n\ndef get_name_from_func(func: FunctionType) -> str:\n    # partials don't have `__module__` or `__name__`, so we use the values from the \"inner\" function\n    if isinstance(func, partial_types):\n        return \"partial({})\".format(get_name_from_func(func.func))\n    elif hasattr(func, \"_partialmethod\") and hasattr(func._partialmethod, \"func\"):\n        return \"partial({})\".format(get_name_from_func(func._partialmethod.func))\n\n    module = func.__module__\n\n    if hasattr(func, \"view_class\"):\n        view_name = func.view_class.__name__\n    elif hasattr(func, \"__name__\"):\n        view_name = func.__name__\n    else:  # Fall back if there's no __name__\n        view_name = func.__class__.__name__\n\n    return \"{0}.{1}\".format(module, view_name)\n\n\ndef build_name_with_http_method_prefix(name, request):\n    return \" \".join((request.method, name)) if name else name\n\n\ndef is_master_process() -> bool:\n    # currently only recognizes uwsgi master process\n    try:\n        import uwsgi\n\n        return os.getpid() == uwsgi.masterpid()\n    except ImportError:\n        return False\n\n\ndef get_url_dict(url: str) -> dict:\n    parse_result = urllib.parse.urlparse(url)\n\n    url_dict = {\n        \"full\": encoding.keyword_field(url),\n        \"protocol\": parse_result.scheme + \":\",\n        \"hostname\": encoding.keyword_field(parse_result.hostname),\n        \"pathname\": encoding.keyword_field(parse_result.path),\n    }\n\n    port = None if parse_result.port is None else str(parse_result.port)\n\n    if port:\n        url_dict[\"port\"] = port\n    if parse_result.query:\n        url_dict[\"search\"] = encoding.keyword_field(\"?\" + parse_result.query)\n    return url_dict\n\n\ndef sanitize_url(url: str) -> str:\n    if \"@\" not in url:\n        return url\n    parts = urllib.parse.urlparse(url)\n    return url.replace(\"%s:%s\" % (parts.username, parts.password), \"%s:%s\" % (parts.username, constants.MASK))\n\n\ndef get_host_from_url(url: str) -> str:\n    parsed_url = urllib.parse.urlparse(url)\n    host = parsed_url.hostname or \" \"\n\n    if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port:\n        host += \":\" + str(parsed_url.port)\n\n    return host\n\n\ndef url_to_destination_resource(url: str) -> str:\n    parts = urllib.parse.urlsplit(url)\n    hostname = parts.hostname if parts.hostname else \"\"\n    # preserve brackets for IPv6 URLs\n    if \"://[\" in url:\n        hostname = \"[%s]\" % hostname\n    try:\n        port = parts.port\n    except ValueError:\n        # Malformed port, just use None rather than raising an exception\n        port = None\n    default_port = default_ports.get(parts.scheme, None)\n    name = \"%s://%s\" % (parts.scheme, hostname)\n    resource = hostname\n    if not port and parts.scheme in default_ports:\n        port = default_ports[parts.scheme]\n    if port:\n        if port != default_port:\n            name += \":%d\" % port\n        resource += \":%d\" % port\n    return resource\n\n\ndef read_pem_file(file_obj) -> bytes:\n    cert = b\"\"\n    for line in file_obj:\n        if line.startswith(b\"-----BEGIN CERTIFICATE-----\"):\n            break\n    # scan until we find the first END CERTIFICATE marker\n    for line in file_obj:\n        if line.startswith(b\"-----END CERTIFICATE-----\"):\n            break\n        cert += line.strip()\n    return base64.b64decode(cert)\n\n\ndef starmatch_to_regex(pattern: str) -> Pattern:\n    options = re.DOTALL\n    # check if we are case sensitive\n    if pattern.startswith(\"(?-i)\"):\n        pattern = pattern[5:]\n    else:\n        options |= re.IGNORECASE\n    i, n = 0, len(pattern)\n    res = []\n    while i < n:\n        c = pattern[i]\n        i = i + 1\n        if c == \"*\":\n            res.append(\".*\")\n        else:\n            res.append(re.escape(c))\n    return re.compile(r\"(?:%s)\\Z\" % \"\".join(res), options)\n\n\ndef nested_key(d: dict, *args):\n    \"\"\"\n    Traverses a dictionary for nested keys. Returns `None` if the at any point\n    in the traversal a key cannot be found.\n\n    Example:\n\n        >>> from elasticapm.utils import nested_key\n        >>> d = {\"a\": {\"b\": {\"c\": 0}}}\n        >>> nested_key(d, \"a\", \"b\", \"c\")\n        0\n        >>> nested_key(d, \"a\", \"b\", \"d\")\n        None\n    \"\"\"\n    for arg in args:\n        try:\n            d = d[arg]\n        except (TypeError, KeyError):\n            d = None\n            break\n    return d\n", "path": "elasticapm/utils/__init__.py"}]}