cavisson-pythonagent 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
  2. cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
  3. cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
  4. cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
  5. cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
  6. pythonagent/__init__.py +22 -0
  7. pythonagent/agent/__init__.py +219 -0
  8. pythonagent/agent/internal/__init__.py +1 -0
  9. pythonagent/agent/internal/agent.py +1651 -0
  10. pythonagent/agent/internal/framesinfo.py +152 -0
  11. pythonagent/agent/internal/heap_dump.py +39 -0
  12. pythonagent/agent/internal/intercept_module.py +70 -0
  13. pythonagent/agent/internal/logs.py +122 -0
  14. pythonagent/agent/internal/metadata/__init__.py +0 -0
  15. pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
  16. pythonagent/agent/internal/proc_compat.py +75 -0
  17. pythonagent/agent/internal/profile.py +47 -0
  18. pythonagent/agent/internal/provider.py +83 -0
  19. pythonagent/agent/internal/thread_dump.py +85 -0
  20. pythonagent/agent/internal/udp.py +245 -0
  21. pythonagent/agent/internal/udp_message.py +800 -0
  22. pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
  23. pythonagent/agent/probes/Instrumentation/find.py +243 -0
  24. pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
  25. pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
  26. pythonagent/agent/probes/Instrumentation/parser.py +129 -0
  27. pythonagent/agent/probes/__init__.py +219 -0
  28. pythonagent/agent/probes/base.py +303 -0
  29. pythonagent/agent/probes/cache/__init__.py +51 -0
  30. pythonagent/agent/probes/cache/redis.py +119 -0
  31. pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
  32. pythonagent/agent/probes/coroutines/__init__.py +1 -0
  33. pythonagent/agent/probes/coroutines/asyncio.py +63 -0
  34. pythonagent/agent/probes/elasticdb/__init__.py +7 -0
  35. pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
  36. pythonagent/agent/probes/frameworks/__init__.py +27 -0
  37. pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
  38. pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
  39. pythonagent/agent/probes/frameworks/aisess.py +151 -0
  40. pythonagent/agent/probes/frameworks/asgi.py +340 -0
  41. pythonagent/agent/probes/frameworks/bottle.py +27 -0
  42. pythonagent/agent/probes/frameworks/cherry.py +25 -0
  43. pythonagent/agent/probes/frameworks/django.py +128 -0
  44. pythonagent/agent/probes/frameworks/falcon.py +21 -0
  45. pythonagent/agent/probes/frameworks/fastapi.py +35 -0
  46. pythonagent/agent/probes/frameworks/flask.py +30 -0
  47. pythonagent/agent/probes/frameworks/pyramid.py +56 -0
  48. pythonagent/agent/probes/frameworks/test.py +108 -0
  49. pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
  50. pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
  51. pythonagent/agent/probes/frameworks/wsgi.py +353 -0
  52. pythonagent/agent/probes/grpc/__init__.py +76 -0
  53. pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
  54. pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
  55. pythonagent/agent/probes/havoc/__init__.py +0 -0
  56. pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
  57. pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
  58. pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
  59. pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
  60. pythonagent/agent/probes/http/__init__.py +49 -0
  61. pythonagent/agent/probes/http/aiohttp_client.py +59 -0
  62. pythonagent/agent/probes/http/boto.py +12 -0
  63. pythonagent/agent/probes/http/httplib.py +110 -0
  64. pythonagent/agent/probes/http/httpx_client.py +116 -0
  65. pythonagent/agent/probes/http/requests.py +15 -0
  66. pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
  67. pythonagent/agent/probes/http/urllib3.py +16 -0
  68. pythonagent/agent/probes/langchain/__init__.py +21 -0
  69. pythonagent/agent/probes/langchain/base_tool.py +95 -0
  70. pythonagent/agent/probes/langchain/langchain_community.py +136 -0
  71. pythonagent/agent/probes/langchain/langchain_core.py +32 -0
  72. pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
  73. pythonagent/agent/probes/logging/__init__.py +106 -0
  74. pythonagent/agent/probes/message_brokers/__init__.py +4 -0
  75. pythonagent/agent/probes/message_brokers/pika.py +126 -0
  76. pythonagent/agent/probes/mongodb/__init__.py +6 -0
  77. pythonagent/agent/probes/mongodb/pymongo.py +286 -0
  78. pythonagent/agent/probes/openai/__init__.py +3 -0
  79. pythonagent/agent/probes/openai/openai.py +797 -0
  80. pythonagent/agent/probes/span.py +101 -0
  81. pythonagent/agent/probes/sql/__init__.py +13 -0
  82. pythonagent/agent/probes/sql/botocores3.py +51 -0
  83. pythonagent/agent/probes/sql/dbapi.py +285 -0
  84. pythonagent/agent/probes/sql/dynamodb.py +90 -0
  85. pythonagent/agent/probes/sql/mysql_connector.py +24 -0
  86. pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
  87. pythonagent/agent/probes/sql/mysqldb.py +43 -0
  88. pythonagent/agent/probes/sql/psycopg2.py +174 -0
  89. pythonagent/agent/probes/sql/pymysql.py +25 -0
  90. pythonagent/bootstrap/__init__.py +0 -0
  91. pythonagent/bootstrap/cav_gunicorn.py +26 -0
  92. pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
  93. pythonagent/bootstrap/run.py +47 -0
  94. pythonagent/bootstrap/sitecustomize.py +287 -0
  95. pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
  96. pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
  97. pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
  98. pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
  99. pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
  100. pythonagent/config.py +279 -0
  101. pythonagent/find.py +72 -0
  102. pythonagent/find_mod_cls_name.py +54 -0
  103. pythonagent/lang.py +131 -0
  104. pythonagent/lib.py +91 -0
  105. pythonagent/main/__init__.py +0 -0
  106. pythonagent/main/pytrace/__init__.py +79 -0
  107. pythonagent/main/pytrace/commands/__init__.py +0 -0
  108. pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
  109. pythonagent/main/pytrace/commands/run.py +401 -0
  110. pythonagent/main/pytrace/pytrace.py +133 -0
  111. pythonagent/main/wsgi.py +6 -0
  112. pythonagent/main.py +27 -0
  113. pythonagent/run.py +46 -0
  114. pythonagent/sqins.py +8 -0
  115. pythonagent/test.py +73 -0
  116. pythonagent/utils.py +168 -0
  117. pythonagent/vendor/__init__.py +0 -0
  118. pythonagent/vendor/pympler/__init__.py +1 -0
  119. pythonagent/vendor/pympler/asizeof.py +2810 -0
  120. pythonagent/vendor/pympler/charts.py +62 -0
  121. pythonagent/vendor/pympler/classtracker.py +590 -0
  122. pythonagent/vendor/pympler/classtracker_stats.py +780 -0
  123. pythonagent/vendor/pympler/garbagegraph.py +80 -0
  124. pythonagent/vendor/pympler/mprofile.py +97 -0
  125. pythonagent/vendor/pympler/muppy.py +275 -0
  126. pythonagent/vendor/pympler/panels.py +115 -0
  127. pythonagent/vendor/pympler/process.py +238 -0
  128. pythonagent/vendor/pympler/py.typed +0 -0
  129. pythonagent/vendor/pympler/refbrowser.py +451 -0
  130. pythonagent/vendor/pympler/refgraph.py +350 -0
  131. pythonagent/vendor/pympler/summary.py +321 -0
  132. pythonagent/vendor/pympler/tracker.py +267 -0
  133. pythonagent/vendor/pympler/util/__init__.py +0 -0
  134. pythonagent/vendor/pympler/util/bottle.py +3809 -0
  135. pythonagent/vendor/pympler/util/compat.py +23 -0
  136. pythonagent/vendor/pympler/util/stringutils.py +77 -0
  137. pythonagent/vendor/pympler/web.py +346 -0
  138. pythonagent/vendor/werkzeug/__init__.py +20 -0
  139. pythonagent/vendor/werkzeug/_compat.py +228 -0
  140. pythonagent/vendor/werkzeug/_internal.py +473 -0
  141. pythonagent/vendor/werkzeug/_reloader.py +341 -0
  142. pythonagent/vendor/werkzeug/datastructures.py +3120 -0
  143. pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
  144. pythonagent/vendor/werkzeug/debug/console.py +218 -0
  145. pythonagent/vendor/werkzeug/debug/repr.py +297 -0
  146. pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
  147. pythonagent/vendor/werkzeug/exceptions.py +829 -0
  148. pythonagent/vendor/werkzeug/filesystem.py +64 -0
  149. pythonagent/vendor/werkzeug/formparser.py +584 -0
  150. pythonagent/vendor/werkzeug/http.py +1307 -0
  151. pythonagent/vendor/werkzeug/local.py +420 -0
  152. pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
  153. pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
  154. pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
  155. pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
  156. pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
  157. pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
  158. pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
  159. pythonagent/vendor/werkzeug/posixemulation.py +117 -0
  160. pythonagent/vendor/werkzeug/routing.py +2210 -0
  161. pythonagent/vendor/werkzeug/security.py +249 -0
  162. pythonagent/vendor/werkzeug/serving.py +1117 -0
  163. pythonagent/vendor/werkzeug/test.py +1123 -0
  164. pythonagent/vendor/werkzeug/testapp.py +241 -0
  165. pythonagent/vendor/werkzeug/urls.py +1138 -0
  166. pythonagent/vendor/werkzeug/useragents.py +202 -0
  167. pythonagent/vendor/werkzeug/utils.py +778 -0
  168. pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
  169. pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
  170. pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
  171. pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
  172. pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
  173. pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
  174. pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
  175. pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
  176. pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
  177. pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
  178. pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
  179. pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
  180. pythonagent/vendor/werkzeug/wsgi.py +1000 -0
@@ -0,0 +1,219 @@
1
+ """
2
+ Basic HTTP Proxy
3
+ ================
4
+
5
+ .. autoclass:: ProxyMiddleware
6
+
7
+ :copyright: 2007 Pallets
8
+ :license: BSD-3-Clause
9
+ """
10
+ import socket
11
+
12
+ from ..datastructures import EnvironHeaders
13
+ from ..http import is_hop_by_hop_header
14
+ from ..urls import url_parse
15
+ from ..urls import url_quote
16
+ from ..wsgi import get_input_stream
17
+
18
+ try:
19
+ from http import client
20
+ except ImportError:
21
+ import httplib as client
22
+
23
+
24
+ class ProxyMiddleware(object):
25
+ """Proxy requests under a path to an external server, routing other
26
+ requests to the app.
27
+
28
+ This middleware can only proxy HTTP requests, as that is the only
29
+ protocol handled by the WSGI server. Other protocols, such as
30
+ websocket requests, cannot be proxied at this layer. This should
31
+ only be used for development, in production a real proxying server
32
+ should be used.
33
+
34
+ The middleware takes a dict that maps a path prefix to a dict
35
+ describing the host to be proxied to::
36
+
37
+ app = ProxyMiddleware(app, {
38
+ "/static/": {
39
+ "target": "http://127.0.0.1:5001/",
40
+ }
41
+ })
42
+
43
+ Each host has the following options:
44
+
45
+ ``target``:
46
+ The target URL to dispatch to. This is required.
47
+ ``remove_prefix``:
48
+ Whether to remove the prefix from the URL before dispatching it
49
+ to the target. The default is ``False``.
50
+ ``host``:
51
+ ``"<auto>"`` (default):
52
+ The host header is automatically rewritten to the URL of the
53
+ target.
54
+ ``None``:
55
+ The host header is unmodified from the client request.
56
+ Any other value:
57
+ The host header is overwritten with the value.
58
+ ``headers``:
59
+ A dictionary of headers to be sent with the request to the
60
+ target. The default is ``{}``.
61
+ ``ssl_context``:
62
+ A :class:`ssl.SSLContext` defining how to verify requests if the
63
+ target is HTTPS. The default is ``None``.
64
+
65
+ In the example above, everything under ``"/static/"`` is proxied to
66
+ the server on port 5001. The host header is rewritten to the target,
67
+ and the ``"/static/"`` prefix is removed from the URLs.
68
+
69
+ :param app: The WSGI application to wrap.
70
+ :param targets: Proxy target configurations. See description above.
71
+ :param chunk_size: Size of chunks to read from input stream and
72
+ write to target.
73
+ :param timeout: Seconds before an operation to a target fails.
74
+
75
+ .. versionadded:: 0.14
76
+ """
77
+
78
+ def __init__(self, app, targets, chunk_size=2 << 13, timeout=10):
79
+ def _set_defaults(opts):
80
+ opts.setdefault("remove_prefix", False)
81
+ opts.setdefault("host", "<auto>")
82
+ opts.setdefault("headers", {})
83
+ opts.setdefault("ssl_context", None)
84
+ return opts
85
+
86
+ self.app = app
87
+ self.targets = dict(
88
+ ("/%s/" % k.strip("/"), _set_defaults(v)) for k, v in targets.items()
89
+ )
90
+ self.chunk_size = chunk_size
91
+ self.timeout = timeout
92
+
93
+ def proxy_to(self, opts, path, prefix):
94
+ target = url_parse(opts["target"])
95
+
96
+ def application(environ, start_response):
97
+ headers = list(EnvironHeaders(environ).items())
98
+ headers[:] = [
99
+ (k, v)
100
+ for k, v in headers
101
+ if not is_hop_by_hop_header(k)
102
+ and k.lower() not in ("content-length", "host")
103
+ ]
104
+ headers.append(("Connection", "close"))
105
+
106
+ if opts["host"] == "<auto>":
107
+ headers.append(("Host", target.ascii_host))
108
+ elif opts["host"] is None:
109
+ headers.append(("Host", environ["HTTP_HOST"]))
110
+ else:
111
+ headers.append(("Host", opts["host"]))
112
+
113
+ headers.extend(opts["headers"].items())
114
+ remote_path = path
115
+
116
+ if opts["remove_prefix"]:
117
+ remote_path = "%s/%s" % (
118
+ target.path.rstrip("/"),
119
+ remote_path[len(prefix) :].lstrip("/"),
120
+ )
121
+
122
+ content_length = environ.get("CONTENT_LENGTH")
123
+ chunked = False
124
+
125
+ if content_length not in ("", None):
126
+ headers.append(("Content-Length", content_length))
127
+ elif content_length is not None:
128
+ headers.append(("Transfer-Encoding", "chunked"))
129
+ chunked = True
130
+
131
+ try:
132
+ if target.scheme == "http":
133
+ con = client.HTTPConnection(
134
+ target.ascii_host, target.port or 80, timeout=self.timeout
135
+ )
136
+ elif target.scheme == "https":
137
+ con = client.HTTPSConnection(
138
+ target.ascii_host,
139
+ target.port or 443,
140
+ timeout=self.timeout,
141
+ context=opts["ssl_context"],
142
+ )
143
+ else:
144
+ raise RuntimeError(
145
+ "Target scheme must be 'http' or 'https', got '{}'.".format(
146
+ target.scheme
147
+ )
148
+ )
149
+
150
+ con.connect()
151
+ remote_url = url_quote(remote_path)
152
+ querystring = environ["QUERY_STRING"]
153
+
154
+ if querystring:
155
+ remote_url = remote_url + "?" + querystring
156
+
157
+ con.putrequest(environ["REQUEST_METHOD"], remote_url, skip_host=True)
158
+
159
+ for k, v in headers:
160
+ if k.lower() == "connection":
161
+ v = "close"
162
+
163
+ con.putheader(k, v)
164
+
165
+ con.endheaders()
166
+ stream = get_input_stream(environ)
167
+
168
+ while 1:
169
+ data = stream.read(self.chunk_size)
170
+
171
+ if not data:
172
+ break
173
+
174
+ if chunked:
175
+ con.send(b"%x\r\n%s\r\n" % (len(data), data))
176
+ else:
177
+ con.send(data)
178
+
179
+ resp = con.getresponse()
180
+ except socket.error:
181
+ from ..exceptions import BadGateway
182
+
183
+ return BadGateway()(environ, start_response)
184
+
185
+ start_response(
186
+ "%d %s" % (resp.status, resp.reason),
187
+ [
188
+ (k.title(), v)
189
+ for k, v in resp.getheaders()
190
+ if not is_hop_by_hop_header(k)
191
+ ],
192
+ )
193
+
194
+ def read():
195
+ while 1:
196
+ try:
197
+ data = resp.read(self.chunk_size)
198
+ except socket.error:
199
+ break
200
+
201
+ if not data:
202
+ break
203
+
204
+ yield data
205
+
206
+ return read()
207
+
208
+ return application
209
+
210
+ def __call__(self, environ, start_response):
211
+ path = environ["PATH_INFO"]
212
+ app = self.app
213
+
214
+ for prefix, opts in self.targets.items():
215
+ if path.startswith(prefix):
216
+ app = self.proxy_to(opts, path, prefix)
217
+ break
218
+
219
+ return app(environ, start_response)
@@ -0,0 +1,408 @@
1
+ """
2
+ WSGI Protocol Linter
3
+ ====================
4
+
5
+ This module provides a middleware that performs sanity checks on the
6
+ behavior of the WSGI server and application. It checks that the
7
+ :pep:`3333` WSGI spec is properly implemented. It also warns on some
8
+ common HTTP errors such as non-empty responses for 304 status codes.
9
+
10
+ .. autoclass:: LintMiddleware
11
+
12
+ :copyright: 2007 Pallets
13
+ :license: BSD-3-Clause
14
+ """
15
+ from warnings import warn
16
+
17
+ from .._compat import implements_iterator
18
+ from .._compat import PY2
19
+ from .._compat import string_types
20
+ from ..datastructures import Headers
21
+ from ..http import is_entity_header
22
+ from ..wsgi import FileWrapper
23
+
24
+ try:
25
+ from urllib.parse import urlparse
26
+ except ImportError:
27
+ from urlparse import urlparse
28
+
29
+
30
+ class WSGIWarning(Warning):
31
+ """Warning class for WSGI warnings."""
32
+
33
+
34
+ class HTTPWarning(Warning):
35
+ """Warning class for HTTP warnings."""
36
+
37
+
38
+ def check_string(context, obj, stacklevel=3):
39
+ if type(obj) is not str:
40
+ warn(
41
+ "'%s' requires strings, got '%s'" % (context, type(obj).__name__),
42
+ WSGIWarning,
43
+ )
44
+
45
+
46
+ class InputStream(object):
47
+ def __init__(self, stream):
48
+ self._stream = stream
49
+
50
+ def read(self, *args):
51
+ if len(args) == 0:
52
+ warn(
53
+ "WSGI does not guarantee an EOF marker on the input stream, thus making"
54
+ " calls to 'wsgi.input.read()' unsafe. Conforming servers may never"
55
+ " return from this call.",
56
+ WSGIWarning,
57
+ stacklevel=2,
58
+ )
59
+ elif len(args) != 1:
60
+ warn(
61
+ "Too many parameters passed to 'wsgi.input.read()'.",
62
+ WSGIWarning,
63
+ stacklevel=2,
64
+ )
65
+ return self._stream.read(*args)
66
+
67
+ def readline(self, *args):
68
+ if len(args) == 0:
69
+ warn(
70
+ "Calls to 'wsgi.input.readline()' without arguments are unsafe. Use"
71
+ " 'wsgi.input.read()' instead.",
72
+ WSGIWarning,
73
+ stacklevel=2,
74
+ )
75
+ elif len(args) == 1:
76
+ warn(
77
+ "'wsgi.input.readline()' was called with a size hint. WSGI does not"
78
+ " support this, although it's available on all major servers.",
79
+ WSGIWarning,
80
+ stacklevel=2,
81
+ )
82
+ else:
83
+ raise TypeError("Too many arguments passed to 'wsgi.input.readline()'.")
84
+ return self._stream.readline(*args)
85
+
86
+ def __iter__(self):
87
+ try:
88
+ return iter(self._stream)
89
+ except TypeError:
90
+ warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2)
91
+ return iter(())
92
+
93
+ def close(self):
94
+ warn("The application closed the input stream!", WSGIWarning, stacklevel=2)
95
+ self._stream.close()
96
+
97
+
98
+ class ErrorStream(object):
99
+ def __init__(self, stream):
100
+ self._stream = stream
101
+
102
+ def write(self, s):
103
+ check_string("wsgi.error.write()", s)
104
+ self._stream.write(s)
105
+
106
+ def flush(self):
107
+ self._stream.flush()
108
+
109
+ def writelines(self, seq):
110
+ for line in seq:
111
+ self.write(line)
112
+
113
+ def close(self):
114
+ warn("The application closed the error stream!", WSGIWarning, stacklevel=2)
115
+ self._stream.close()
116
+
117
+
118
+ class GuardedWrite(object):
119
+ def __init__(self, write, chunks):
120
+ self._write = write
121
+ self._chunks = chunks
122
+
123
+ def __call__(self, s):
124
+ check_string("write()", s)
125
+ self._write.write(s)
126
+ self._chunks.append(len(s))
127
+
128
+
129
+ @implements_iterator
130
+ class GuardedIterator(object):
131
+ def __init__(self, iterator, headers_set, chunks):
132
+ self._iterator = iterator
133
+ if PY2:
134
+ self._next = iter(iterator).next
135
+ else:
136
+ self._next = iter(iterator).__next__
137
+ self.closed = False
138
+ self.headers_set = headers_set
139
+ self.chunks = chunks
140
+
141
+ def __iter__(self):
142
+ return self
143
+
144
+ def __next__(self):
145
+ if self.closed:
146
+ warn("Iterated over closed 'app_iter'.", WSGIWarning, stacklevel=2)
147
+
148
+ rv = self._next()
149
+
150
+ if not self.headers_set:
151
+ warn(
152
+ "The application returned before it started the response.",
153
+ WSGIWarning,
154
+ stacklevel=2,
155
+ )
156
+
157
+ check_string("application iterator items", rv)
158
+ self.chunks.append(len(rv))
159
+ return rv
160
+
161
+ def close(self):
162
+ self.closed = True
163
+
164
+ if hasattr(self._iterator, "close"):
165
+ self._iterator.close()
166
+
167
+ if self.headers_set:
168
+ status_code, headers = self.headers_set
169
+ bytes_sent = sum(self.chunks)
170
+ content_length = headers.get("content-length", type=int)
171
+
172
+ if status_code == 304:
173
+ for key, _value in headers:
174
+ key = key.lower()
175
+ if key not in ("expires", "content-location") and is_entity_header(
176
+ key
177
+ ):
178
+ warn(
179
+ "Entity header %r found in 304 response." % key, HTTPWarning
180
+ )
181
+ if bytes_sent:
182
+ warn("304 responses must not have a body.", HTTPWarning)
183
+ elif 100 <= status_code < 200 or status_code == 204:
184
+ if content_length != 0:
185
+ warn(
186
+ "%r responses must have an empty content length." % status_code,
187
+ HTTPWarning,
188
+ )
189
+ if bytes_sent:
190
+ warn(
191
+ "%r responses must not have a body." % status_code, HTTPWarning
192
+ )
193
+ elif content_length is not None and content_length != bytes_sent:
194
+ warn(
195
+ "Content-Length and the number of bytes sent to the client do not"
196
+ " match.",
197
+ WSGIWarning,
198
+ )
199
+
200
+ def __del__(self):
201
+ if not self.closed:
202
+ try:
203
+ warn(
204
+ "Iterator was garbage collected before it was closed.", WSGIWarning
205
+ )
206
+ except Exception:
207
+ pass
208
+
209
+
210
+ class LintMiddleware(object):
211
+ """Warns about common errors in the WSGI and HTTP behavior of the
212
+ server and wrapped application. Some of the issues it check are:
213
+
214
+ - invalid status codes
215
+ - non-bytestrings sent to the WSGI server
216
+ - strings returned from the WSGI application
217
+ - non-empty conditional responses
218
+ - unquoted etags
219
+ - relative URLs in the Location header
220
+ - unsafe calls to wsgi.input
221
+ - unclosed iterators
222
+
223
+ Error information is emitted using the :mod:`warnings` module.
224
+
225
+ :param app: The WSGI application to wrap.
226
+
227
+ .. code-block:: python
228
+
229
+ from pythonagent.vendor.werkzeug.middleware.lint import LintMiddleware
230
+ app = LintMiddleware(app)
231
+ """
232
+
233
+ def __init__(self, app):
234
+ self.app = app
235
+
236
+ def check_environ(self, environ):
237
+ if type(environ) is not dict:
238
+ warn(
239
+ "WSGI environment is not a standard Python dict.",
240
+ WSGIWarning,
241
+ stacklevel=4,
242
+ )
243
+ for key in (
244
+ "REQUEST_METHOD",
245
+ "SERVER_NAME",
246
+ "SERVER_PORT",
247
+ "wsgi.version",
248
+ "wsgi.input",
249
+ "wsgi.errors",
250
+ "wsgi.multithread",
251
+ "wsgi.multiprocess",
252
+ "wsgi.run_once",
253
+ ):
254
+ if key not in environ:
255
+ warn(
256
+ "Required environment key %r not found" % key,
257
+ WSGIWarning,
258
+ stacklevel=3,
259
+ )
260
+ if environ["wsgi.version"] != (1, 0):
261
+ warn("Environ is not a WSGI 1.0 environ.", WSGIWarning, stacklevel=3)
262
+
263
+ script_name = environ.get("SCRIPT_NAME", "")
264
+ path_info = environ.get("PATH_INFO", "")
265
+
266
+ if script_name and script_name[0] != "/":
267
+ warn(
268
+ "'SCRIPT_NAME' does not start with a slash: %r" % script_name,
269
+ WSGIWarning,
270
+ stacklevel=3,
271
+ )
272
+
273
+ if path_info and path_info[0] != "/":
274
+ warn(
275
+ "'PATH_INFO' does not start with a slash: %r" % path_info,
276
+ WSGIWarning,
277
+ stacklevel=3,
278
+ )
279
+
280
+ def check_start_response(self, status, headers, exc_info):
281
+ check_string("status", status)
282
+ status_code = status.split(None, 1)[0]
283
+
284
+ if len(status_code) != 3 or not status_code.isdigit():
285
+ warn(WSGIWarning("Status code must be three digits"), stacklevel=3)
286
+
287
+ if len(status) < 4 or status[3] != " ":
288
+ warn(
289
+ WSGIWarning(
290
+ "Invalid value for status %r. Valid "
291
+ "status strings are three digits, a space "
292
+ "and a status explanation"
293
+ ),
294
+ stacklevel=3,
295
+ )
296
+
297
+ status_code = int(status_code)
298
+
299
+ if status_code < 100:
300
+ warn(WSGIWarning("status code < 100 detected"), stacklevel=3)
301
+
302
+ if type(headers) is not list:
303
+ warn(WSGIWarning("header list is not a list"), stacklevel=3)
304
+
305
+ for item in headers:
306
+ if type(item) is not tuple or len(item) != 2:
307
+ warn(WSGIWarning("Headers must tuple 2-item tuples"), stacklevel=3)
308
+ name, value = item
309
+ if type(name) is not str or type(value) is not str:
310
+ warn(WSGIWarning("header items must be strings"), stacklevel=3)
311
+ if name.lower() == "status":
312
+ warn(
313
+ WSGIWarning(
314
+ "The status header is not supported due to "
315
+ "conflicts with the CGI spec."
316
+ ),
317
+ stacklevel=3,
318
+ )
319
+
320
+ if exc_info is not None and not isinstance(exc_info, tuple):
321
+ warn(WSGIWarning("invalid value for exc_info"), stacklevel=3)
322
+
323
+ headers = Headers(headers)
324
+ self.check_headers(headers)
325
+
326
+ return status_code, headers
327
+
328
+ def check_headers(self, headers):
329
+ etag = headers.get("etag")
330
+
331
+ if etag is not None:
332
+ if etag.startswith(("W/", "w/")):
333
+ if etag.startswith("w/"):
334
+ warn(
335
+ HTTPWarning("weak etag indicator should be upcase."),
336
+ stacklevel=4,
337
+ )
338
+
339
+ etag = etag[2:]
340
+
341
+ if not (etag[:1] == etag[-1:] == '"'):
342
+ warn(HTTPWarning("unquoted etag emitted."), stacklevel=4)
343
+
344
+ location = headers.get("location")
345
+
346
+ if location is not None:
347
+ if not urlparse(location).netloc:
348
+ warn(
349
+ HTTPWarning("absolute URLs required for location header"),
350
+ stacklevel=4,
351
+ )
352
+
353
+ def check_iterator(self, app_iter):
354
+ if isinstance(app_iter, string_types):
355
+ warn(
356
+ "The application returned astring. The response will send one character"
357
+ " at a time to the client, which will kill performance. Return a list"
358
+ " or iterable instead.",
359
+ WSGIWarning,
360
+ stacklevel=3,
361
+ )
362
+
363
+ def __call__(self, *args, **kwargs):
364
+ if len(args) != 2:
365
+ warn("A WSGI app takes two arguments.", WSGIWarning, stacklevel=2)
366
+
367
+ if kwargs:
368
+ warn(
369
+ "A WSGI app does not take keyword arguments.", WSGIWarning, stacklevel=2
370
+ )
371
+
372
+ environ, start_response = args
373
+
374
+ self.check_environ(environ)
375
+ environ["wsgi.input"] = InputStream(environ["wsgi.input"])
376
+ environ["wsgi.errors"] = ErrorStream(environ["wsgi.errors"])
377
+
378
+ # Hook our own file wrapper in so that applications will always
379
+ # iterate to the end and we can check the content length.
380
+ environ["wsgi.file_wrapper"] = FileWrapper
381
+
382
+ headers_set = []
383
+ chunks = []
384
+
385
+ def checking_start_response(*args, **kwargs):
386
+ if len(args) not in (2, 3):
387
+ warn(
388
+ "Invalid number of arguments: %s, expected 2 or 3." % len(args),
389
+ WSGIWarning,
390
+ stacklevel=2,
391
+ )
392
+
393
+ if kwargs:
394
+ warn("'start_response' does not take keyword arguments.", WSGIWarning)
395
+
396
+ status, headers = args[:2]
397
+
398
+ if len(args) == 3:
399
+ exc_info = args[2]
400
+ else:
401
+ exc_info = None
402
+
403
+ headers_set[:] = self.check_start_response(status, headers, exc_info)
404
+ return GuardedWrite(start_response(status, headers, exc_info), chunks)
405
+
406
+ app_iter = self.app(environ, checking_start_response)
407
+ self.check_iterator(app_iter)
408
+ return GuardedIterator(app_iter, headers_set, chunks)