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.
- cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
- cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
- cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
- cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
- cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
- pythonagent/__init__.py +22 -0
- pythonagent/agent/__init__.py +219 -0
- pythonagent/agent/internal/__init__.py +1 -0
- pythonagent/agent/internal/agent.py +1651 -0
- pythonagent/agent/internal/framesinfo.py +152 -0
- pythonagent/agent/internal/heap_dump.py +39 -0
- pythonagent/agent/internal/intercept_module.py +70 -0
- pythonagent/agent/internal/logs.py +122 -0
- pythonagent/agent/internal/metadata/__init__.py +0 -0
- pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
- pythonagent/agent/internal/proc_compat.py +75 -0
- pythonagent/agent/internal/profile.py +47 -0
- pythonagent/agent/internal/provider.py +83 -0
- pythonagent/agent/internal/thread_dump.py +85 -0
- pythonagent/agent/internal/udp.py +245 -0
- pythonagent/agent/internal/udp_message.py +800 -0
- pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
- pythonagent/agent/probes/Instrumentation/find.py +243 -0
- pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
- pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
- pythonagent/agent/probes/Instrumentation/parser.py +129 -0
- pythonagent/agent/probes/__init__.py +219 -0
- pythonagent/agent/probes/base.py +303 -0
- pythonagent/agent/probes/cache/__init__.py +51 -0
- pythonagent/agent/probes/cache/redis.py +119 -0
- pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
- pythonagent/agent/probes/coroutines/__init__.py +1 -0
- pythonagent/agent/probes/coroutines/asyncio.py +63 -0
- pythonagent/agent/probes/elasticdb/__init__.py +7 -0
- pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
- pythonagent/agent/probes/frameworks/__init__.py +27 -0
- pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
- pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
- pythonagent/agent/probes/frameworks/aisess.py +151 -0
- pythonagent/agent/probes/frameworks/asgi.py +340 -0
- pythonagent/agent/probes/frameworks/bottle.py +27 -0
- pythonagent/agent/probes/frameworks/cherry.py +25 -0
- pythonagent/agent/probes/frameworks/django.py +128 -0
- pythonagent/agent/probes/frameworks/falcon.py +21 -0
- pythonagent/agent/probes/frameworks/fastapi.py +35 -0
- pythonagent/agent/probes/frameworks/flask.py +30 -0
- pythonagent/agent/probes/frameworks/pyramid.py +56 -0
- pythonagent/agent/probes/frameworks/test.py +108 -0
- pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
- pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
- pythonagent/agent/probes/frameworks/wsgi.py +353 -0
- pythonagent/agent/probes/grpc/__init__.py +76 -0
- pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
- pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
- pythonagent/agent/probes/havoc/__init__.py +0 -0
- pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
- pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
- pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
- pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
- pythonagent/agent/probes/http/__init__.py +49 -0
- pythonagent/agent/probes/http/aiohttp_client.py +59 -0
- pythonagent/agent/probes/http/boto.py +12 -0
- pythonagent/agent/probes/http/httplib.py +110 -0
- pythonagent/agent/probes/http/httpx_client.py +116 -0
- pythonagent/agent/probes/http/requests.py +15 -0
- pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
- pythonagent/agent/probes/http/urllib3.py +16 -0
- pythonagent/agent/probes/langchain/__init__.py +21 -0
- pythonagent/agent/probes/langchain/base_tool.py +95 -0
- pythonagent/agent/probes/langchain/langchain_community.py +136 -0
- pythonagent/agent/probes/langchain/langchain_core.py +32 -0
- pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
- pythonagent/agent/probes/logging/__init__.py +106 -0
- pythonagent/agent/probes/message_brokers/__init__.py +4 -0
- pythonagent/agent/probes/message_brokers/pika.py +126 -0
- pythonagent/agent/probes/mongodb/__init__.py +6 -0
- pythonagent/agent/probes/mongodb/pymongo.py +286 -0
- pythonagent/agent/probes/openai/__init__.py +3 -0
- pythonagent/agent/probes/openai/openai.py +797 -0
- pythonagent/agent/probes/span.py +101 -0
- pythonagent/agent/probes/sql/__init__.py +13 -0
- pythonagent/agent/probes/sql/botocores3.py +51 -0
- pythonagent/agent/probes/sql/dbapi.py +285 -0
- pythonagent/agent/probes/sql/dynamodb.py +90 -0
- pythonagent/agent/probes/sql/mysql_connector.py +24 -0
- pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
- pythonagent/agent/probes/sql/mysqldb.py +43 -0
- pythonagent/agent/probes/sql/psycopg2.py +174 -0
- pythonagent/agent/probes/sql/pymysql.py +25 -0
- pythonagent/bootstrap/__init__.py +0 -0
- pythonagent/bootstrap/cav_gunicorn.py +26 -0
- pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
- pythonagent/bootstrap/run.py +47 -0
- pythonagent/bootstrap/sitecustomize.py +287 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
- pythonagent/config.py +279 -0
- pythonagent/find.py +72 -0
- pythonagent/find_mod_cls_name.py +54 -0
- pythonagent/lang.py +131 -0
- pythonagent/lib.py +91 -0
- pythonagent/main/__init__.py +0 -0
- pythonagent/main/pytrace/__init__.py +79 -0
- pythonagent/main/pytrace/commands/__init__.py +0 -0
- pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
- pythonagent/main/pytrace/commands/run.py +401 -0
- pythonagent/main/pytrace/pytrace.py +133 -0
- pythonagent/main/wsgi.py +6 -0
- pythonagent/main.py +27 -0
- pythonagent/run.py +46 -0
- pythonagent/sqins.py +8 -0
- pythonagent/test.py +73 -0
- pythonagent/utils.py +168 -0
- pythonagent/vendor/__init__.py +0 -0
- pythonagent/vendor/pympler/__init__.py +1 -0
- pythonagent/vendor/pympler/asizeof.py +2810 -0
- pythonagent/vendor/pympler/charts.py +62 -0
- pythonagent/vendor/pympler/classtracker.py +590 -0
- pythonagent/vendor/pympler/classtracker_stats.py +780 -0
- pythonagent/vendor/pympler/garbagegraph.py +80 -0
- pythonagent/vendor/pympler/mprofile.py +97 -0
- pythonagent/vendor/pympler/muppy.py +275 -0
- pythonagent/vendor/pympler/panels.py +115 -0
- pythonagent/vendor/pympler/process.py +238 -0
- pythonagent/vendor/pympler/py.typed +0 -0
- pythonagent/vendor/pympler/refbrowser.py +451 -0
- pythonagent/vendor/pympler/refgraph.py +350 -0
- pythonagent/vendor/pympler/summary.py +321 -0
- pythonagent/vendor/pympler/tracker.py +267 -0
- pythonagent/vendor/pympler/util/__init__.py +0 -0
- pythonagent/vendor/pympler/util/bottle.py +3809 -0
- pythonagent/vendor/pympler/util/compat.py +23 -0
- pythonagent/vendor/pympler/util/stringutils.py +77 -0
- pythonagent/vendor/pympler/web.py +346 -0
- pythonagent/vendor/werkzeug/__init__.py +20 -0
- pythonagent/vendor/werkzeug/_compat.py +228 -0
- pythonagent/vendor/werkzeug/_internal.py +473 -0
- pythonagent/vendor/werkzeug/_reloader.py +341 -0
- pythonagent/vendor/werkzeug/datastructures.py +3120 -0
- pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
- pythonagent/vendor/werkzeug/debug/console.py +218 -0
- pythonagent/vendor/werkzeug/debug/repr.py +297 -0
- pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
- pythonagent/vendor/werkzeug/exceptions.py +829 -0
- pythonagent/vendor/werkzeug/filesystem.py +64 -0
- pythonagent/vendor/werkzeug/formparser.py +584 -0
- pythonagent/vendor/werkzeug/http.py +1307 -0
- pythonagent/vendor/werkzeug/local.py +420 -0
- pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
- pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
- pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
- pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
- pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
- pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
- pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
- pythonagent/vendor/werkzeug/posixemulation.py +117 -0
- pythonagent/vendor/werkzeug/routing.py +2210 -0
- pythonagent/vendor/werkzeug/security.py +249 -0
- pythonagent/vendor/werkzeug/serving.py +1117 -0
- pythonagent/vendor/werkzeug/test.py +1123 -0
- pythonagent/vendor/werkzeug/testapp.py +241 -0
- pythonagent/vendor/werkzeug/urls.py +1138 -0
- pythonagent/vendor/werkzeug/useragents.py +202 -0
- pythonagent/vendor/werkzeug/utils.py +778 -0
- pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
- pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
- pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
- pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
- pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
- pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
- pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
- pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
- pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
- pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
- pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
- pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
- pythonagent/vendor/werkzeug/wsgi.py +1000 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
from .._compat import string_types
|
|
2
|
+
from .._internal import _get_environ
|
|
3
|
+
from ..datastructures import ContentRange
|
|
4
|
+
from ..datastructures import RequestCacheControl
|
|
5
|
+
from ..datastructures import ResponseCacheControl
|
|
6
|
+
from ..http import generate_etag
|
|
7
|
+
from ..http import http_date
|
|
8
|
+
from ..http import is_resource_modified
|
|
9
|
+
from ..http import parse_cache_control_header
|
|
10
|
+
from ..http import parse_content_range_header
|
|
11
|
+
from ..http import parse_date
|
|
12
|
+
from ..http import parse_etags
|
|
13
|
+
from ..http import parse_if_range_header
|
|
14
|
+
from ..http import parse_range_header
|
|
15
|
+
from ..http import quote_etag
|
|
16
|
+
from ..http import unquote_etag
|
|
17
|
+
from ..utils import cached_property
|
|
18
|
+
from ..utils import header_property
|
|
19
|
+
from ..wrappers.base_response import _clean_accept_ranges
|
|
20
|
+
from ..wsgi import _RangeWrapper
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ETagRequestMixin(object):
|
|
24
|
+
"""Add entity tag and cache descriptors to a request object or object with
|
|
25
|
+
a WSGI environment available as :attr:`~BaseRequest.environ`. This not
|
|
26
|
+
only provides access to etags but also to the cache control header.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def cache_control(self):
|
|
31
|
+
"""A :class:`~werkzeug.datastructures.RequestCacheControl` object
|
|
32
|
+
for the incoming cache control headers.
|
|
33
|
+
"""
|
|
34
|
+
cache_control = self.environ.get("HTTP_CACHE_CONTROL")
|
|
35
|
+
return parse_cache_control_header(cache_control, None, RequestCacheControl)
|
|
36
|
+
|
|
37
|
+
@cached_property
|
|
38
|
+
def if_match(self):
|
|
39
|
+
"""An object containing all the etags in the `If-Match` header.
|
|
40
|
+
|
|
41
|
+
:rtype: :class:`~werkzeug.datastructures.ETags`
|
|
42
|
+
"""
|
|
43
|
+
return parse_etags(self.environ.get("HTTP_IF_MATCH"))
|
|
44
|
+
|
|
45
|
+
@cached_property
|
|
46
|
+
def if_none_match(self):
|
|
47
|
+
"""An object containing all the etags in the `If-None-Match` header.
|
|
48
|
+
|
|
49
|
+
:rtype: :class:`~werkzeug.datastructures.ETags`
|
|
50
|
+
"""
|
|
51
|
+
return parse_etags(self.environ.get("HTTP_IF_NONE_MATCH"))
|
|
52
|
+
|
|
53
|
+
@cached_property
|
|
54
|
+
def if_modified_since(self):
|
|
55
|
+
"""The parsed `If-Modified-Since` header as datetime object."""
|
|
56
|
+
return parse_date(self.environ.get("HTTP_IF_MODIFIED_SINCE"))
|
|
57
|
+
|
|
58
|
+
@cached_property
|
|
59
|
+
def if_unmodified_since(self):
|
|
60
|
+
"""The parsed `If-Unmodified-Since` header as datetime object."""
|
|
61
|
+
return parse_date(self.environ.get("HTTP_IF_UNMODIFIED_SINCE"))
|
|
62
|
+
|
|
63
|
+
@cached_property
|
|
64
|
+
def if_range(self):
|
|
65
|
+
"""The parsed `If-Range` header.
|
|
66
|
+
|
|
67
|
+
.. versionadded:: 0.7
|
|
68
|
+
|
|
69
|
+
:rtype: :class:`~werkzeug.datastructures.IfRange`
|
|
70
|
+
"""
|
|
71
|
+
return parse_if_range_header(self.environ.get("HTTP_IF_RANGE"))
|
|
72
|
+
|
|
73
|
+
@cached_property
|
|
74
|
+
def range(self):
|
|
75
|
+
"""The parsed `Range` header.
|
|
76
|
+
|
|
77
|
+
.. versionadded:: 0.7
|
|
78
|
+
|
|
79
|
+
:rtype: :class:`~werkzeug.datastructures.Range`
|
|
80
|
+
"""
|
|
81
|
+
return parse_range_header(self.environ.get("HTTP_RANGE"))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ETagResponseMixin(object):
|
|
85
|
+
"""Adds extra functionality to a response object for etag and cache
|
|
86
|
+
handling. This mixin requires an object with at least a `headers`
|
|
87
|
+
object that implements a dict like interface similar to
|
|
88
|
+
:class:`~werkzeug.datastructures.Headers`.
|
|
89
|
+
|
|
90
|
+
If you want the :meth:`freeze` method to automatically add an etag, you
|
|
91
|
+
have to mixin this method before the response base class. The default
|
|
92
|
+
response class does not do that.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def cache_control(self):
|
|
97
|
+
"""The Cache-Control general-header field is used to specify
|
|
98
|
+
directives that MUST be obeyed by all caching mechanisms along the
|
|
99
|
+
request/response chain.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def on_update(cache_control):
|
|
103
|
+
if not cache_control and "cache-control" in self.headers:
|
|
104
|
+
del self.headers["cache-control"]
|
|
105
|
+
elif cache_control:
|
|
106
|
+
self.headers["Cache-Control"] = cache_control.to_header()
|
|
107
|
+
|
|
108
|
+
return parse_cache_control_header(
|
|
109
|
+
self.headers.get("cache-control"), on_update, ResponseCacheControl
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def _wrap_response(self, start, length):
|
|
113
|
+
"""Wrap existing Response in case of Range Request context."""
|
|
114
|
+
if self.status_code == 206:
|
|
115
|
+
self.response = _RangeWrapper(self.response, start, length)
|
|
116
|
+
|
|
117
|
+
def _is_range_request_processable(self, environ):
|
|
118
|
+
"""Return ``True`` if `Range` header is present and if underlying
|
|
119
|
+
resource is considered unchanged when compared with `If-Range` header.
|
|
120
|
+
"""
|
|
121
|
+
return (
|
|
122
|
+
"HTTP_IF_RANGE" not in environ
|
|
123
|
+
or not is_resource_modified(
|
|
124
|
+
environ,
|
|
125
|
+
self.headers.get("etag"),
|
|
126
|
+
None,
|
|
127
|
+
self.headers.get("last-modified"),
|
|
128
|
+
ignore_if_range=False,
|
|
129
|
+
)
|
|
130
|
+
) and "HTTP_RANGE" in environ
|
|
131
|
+
|
|
132
|
+
def _process_range_request(self, environ, complete_length=None, accept_ranges=None):
|
|
133
|
+
"""Handle Range Request related headers (RFC7233). If `Accept-Ranges`
|
|
134
|
+
header is valid, and Range Request is processable, we set the headers
|
|
135
|
+
as described by the RFC, and wrap the underlying response in a
|
|
136
|
+
RangeWrapper.
|
|
137
|
+
|
|
138
|
+
Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise.
|
|
139
|
+
|
|
140
|
+
:raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
|
|
141
|
+
if `Range` header could not be parsed or satisfied.
|
|
142
|
+
"""
|
|
143
|
+
from ..exceptions import RequestedRangeNotSatisfiable
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
accept_ranges is None
|
|
147
|
+
or complete_length is None
|
|
148
|
+
or not self._is_range_request_processable(environ)
|
|
149
|
+
):
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
parsed_range = parse_range_header(environ.get("HTTP_RANGE"))
|
|
153
|
+
|
|
154
|
+
if parsed_range is None:
|
|
155
|
+
raise RequestedRangeNotSatisfiable(complete_length)
|
|
156
|
+
|
|
157
|
+
range_tuple = parsed_range.range_for_length(complete_length)
|
|
158
|
+
content_range_header = parsed_range.to_content_range_header(complete_length)
|
|
159
|
+
|
|
160
|
+
if range_tuple is None or content_range_header is None:
|
|
161
|
+
raise RequestedRangeNotSatisfiable(complete_length)
|
|
162
|
+
|
|
163
|
+
content_length = range_tuple[1] - range_tuple[0]
|
|
164
|
+
self.headers["Content-Length"] = content_length
|
|
165
|
+
self.headers["Accept-Ranges"] = accept_ranges
|
|
166
|
+
self.content_range = content_range_header
|
|
167
|
+
self.status_code = 206
|
|
168
|
+
self._wrap_response(range_tuple[0], content_length)
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
def make_conditional(
|
|
172
|
+
self, request_or_environ, accept_ranges=False, complete_length=None
|
|
173
|
+
):
|
|
174
|
+
"""Make the response conditional to the request. This method works
|
|
175
|
+
best if an etag was defined for the response already. The `add_etag`
|
|
176
|
+
method can be used to do that. If called without etag just the date
|
|
177
|
+
header is set.
|
|
178
|
+
|
|
179
|
+
This does nothing if the request method in the request or environ is
|
|
180
|
+
anything but GET or HEAD.
|
|
181
|
+
|
|
182
|
+
For optimal performance when handling range requests, it's recommended
|
|
183
|
+
that your response data object implements `seekable`, `seek` and `tell`
|
|
184
|
+
methods as described by :py:class:`io.IOBase`. Objects returned by
|
|
185
|
+
:meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods.
|
|
186
|
+
|
|
187
|
+
It does not remove the body of the response because that's something
|
|
188
|
+
the :meth:`__call__` function does for us automatically.
|
|
189
|
+
|
|
190
|
+
Returns self so that you can do ``return resp.make_conditional(req)``
|
|
191
|
+
but modifies the object in-place.
|
|
192
|
+
|
|
193
|
+
:param request_or_environ: a request object or WSGI environment to be
|
|
194
|
+
used to make the response conditional
|
|
195
|
+
against.
|
|
196
|
+
:param accept_ranges: This parameter dictates the value of
|
|
197
|
+
`Accept-Ranges` header. If ``False`` (default),
|
|
198
|
+
the header is not set. If ``True``, it will be set
|
|
199
|
+
to ``"bytes"``. If ``None``, it will be set to
|
|
200
|
+
``"none"``. If it's a string, it will use this
|
|
201
|
+
value.
|
|
202
|
+
:param complete_length: Will be used only in valid Range Requests.
|
|
203
|
+
It will set `Content-Range` complete length
|
|
204
|
+
value and compute `Content-Length` real value.
|
|
205
|
+
This parameter is mandatory for successful
|
|
206
|
+
Range Requests completion.
|
|
207
|
+
:raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
|
|
208
|
+
if `Range` header could not be parsed or satisfied.
|
|
209
|
+
"""
|
|
210
|
+
environ = _get_environ(request_or_environ)
|
|
211
|
+
if environ["REQUEST_METHOD"] in ("GET", "HEAD"):
|
|
212
|
+
# if the date is not in the headers, add it now. We however
|
|
213
|
+
# will not override an already existing header. Unfortunately
|
|
214
|
+
# this header will be overriden by many WSGI servers including
|
|
215
|
+
# wsgiref.
|
|
216
|
+
if "date" not in self.headers:
|
|
217
|
+
self.headers["Date"] = http_date()
|
|
218
|
+
accept_ranges = _clean_accept_ranges(accept_ranges)
|
|
219
|
+
is206 = self._process_range_request(environ, complete_length, accept_ranges)
|
|
220
|
+
if not is206 and not is_resource_modified(
|
|
221
|
+
environ,
|
|
222
|
+
self.headers.get("etag"),
|
|
223
|
+
None,
|
|
224
|
+
self.headers.get("last-modified"),
|
|
225
|
+
):
|
|
226
|
+
if parse_etags(environ.get("HTTP_IF_MATCH")):
|
|
227
|
+
self.status_code = 412
|
|
228
|
+
else:
|
|
229
|
+
self.status_code = 304
|
|
230
|
+
if (
|
|
231
|
+
self.automatically_set_content_length
|
|
232
|
+
and "content-length" not in self.headers
|
|
233
|
+
):
|
|
234
|
+
length = self.calculate_content_length()
|
|
235
|
+
if length is not None:
|
|
236
|
+
self.headers["Content-Length"] = length
|
|
237
|
+
return self
|
|
238
|
+
|
|
239
|
+
def add_etag(self, overwrite=False, weak=False):
|
|
240
|
+
"""Add an etag for the current response if there is none yet."""
|
|
241
|
+
if overwrite or "etag" not in self.headers:
|
|
242
|
+
self.set_etag(generate_etag(self.get_data()), weak)
|
|
243
|
+
|
|
244
|
+
def set_etag(self, etag, weak=False):
|
|
245
|
+
"""Set the etag, and override the old one if there was one."""
|
|
246
|
+
self.headers["ETag"] = quote_etag(etag, weak)
|
|
247
|
+
|
|
248
|
+
def get_etag(self):
|
|
249
|
+
"""Return a tuple in the form ``(etag, is_weak)``. If there is no
|
|
250
|
+
ETag the return value is ``(None, None)``.
|
|
251
|
+
"""
|
|
252
|
+
return unquote_etag(self.headers.get("ETag"))
|
|
253
|
+
|
|
254
|
+
def freeze(self, no_etag=False):
|
|
255
|
+
"""Call this method if you want to make your response object ready for
|
|
256
|
+
pickeling. This buffers the generator if there is one. This also
|
|
257
|
+
sets the etag unless `no_etag` is set to `True`.
|
|
258
|
+
"""
|
|
259
|
+
if not no_etag:
|
|
260
|
+
self.add_etag()
|
|
261
|
+
super(ETagResponseMixin, self).freeze()
|
|
262
|
+
|
|
263
|
+
accept_ranges = header_property(
|
|
264
|
+
"Accept-Ranges",
|
|
265
|
+
doc="""The `Accept-Ranges` header. Even though the name would
|
|
266
|
+
indicate that multiple values are supported, it must be one
|
|
267
|
+
string token only.
|
|
268
|
+
|
|
269
|
+
The values ``'bytes'`` and ``'none'`` are common.
|
|
270
|
+
|
|
271
|
+
.. versionadded:: 0.7""",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def content_range(self):
|
|
276
|
+
"""The ``Content-Range`` header as a
|
|
277
|
+
:class:`~werkzeug.datastructures.ContentRange` object. Available
|
|
278
|
+
even if the header is not set.
|
|
279
|
+
|
|
280
|
+
.. versionadded:: 0.7
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
def on_update(rng):
|
|
284
|
+
if not rng:
|
|
285
|
+
del self.headers["content-range"]
|
|
286
|
+
else:
|
|
287
|
+
self.headers["Content-Range"] = rng.to_header()
|
|
288
|
+
|
|
289
|
+
rv = parse_content_range_header(self.headers.get("content-range"), on_update)
|
|
290
|
+
# always provide a content range object to make the descriptor
|
|
291
|
+
# more user friendly. It provides an unset() method that can be
|
|
292
|
+
# used to remove the header quickly.
|
|
293
|
+
if rv is None:
|
|
294
|
+
rv = ContentRange(None, None, None, on_update=on_update)
|
|
295
|
+
return rv
|
|
296
|
+
|
|
297
|
+
@content_range.setter
|
|
298
|
+
def content_range(self, value):
|
|
299
|
+
if not value:
|
|
300
|
+
del self.headers["content-range"]
|
|
301
|
+
elif isinstance(value, string_types):
|
|
302
|
+
self.headers["Content-Range"] = value
|
|
303
|
+
else:
|
|
304
|
+
self.headers["Content-Range"] = value.to_header()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
from .._compat import text_type
|
|
7
|
+
from ..exceptions import BadRequest
|
|
8
|
+
from ..utils import detect_utf_encoding
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import simplejson as _json
|
|
12
|
+
except ImportError:
|
|
13
|
+
import json as _json
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _JSONModule(object):
|
|
17
|
+
@staticmethod
|
|
18
|
+
def _default(o):
|
|
19
|
+
if isinstance(o, datetime.date):
|
|
20
|
+
return o.isoformat()
|
|
21
|
+
|
|
22
|
+
if isinstance(o, uuid.UUID):
|
|
23
|
+
return str(o)
|
|
24
|
+
|
|
25
|
+
if hasattr(o, "__html__"):
|
|
26
|
+
return text_type(o.__html__())
|
|
27
|
+
|
|
28
|
+
raise TypeError()
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def dumps(cls, obj, **kw):
|
|
32
|
+
kw.setdefault("separators", (",", ":"))
|
|
33
|
+
kw.setdefault("default", cls._default)
|
|
34
|
+
kw.setdefault("sort_keys", True)
|
|
35
|
+
return _json.dumps(obj, **kw)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def loads(s, **kw):
|
|
39
|
+
if isinstance(s, bytes):
|
|
40
|
+
# Needed for Python < 3.6
|
|
41
|
+
encoding = detect_utf_encoding(s)
|
|
42
|
+
s = s.decode(encoding)
|
|
43
|
+
|
|
44
|
+
return _json.loads(s, **kw)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class JSONMixin(object):
|
|
48
|
+
"""Mixin to parse :attr:`data` as JSON. Can be mixed in for both
|
|
49
|
+
:class:`~werkzeug.wrappers.Request` and
|
|
50
|
+
:class:`~werkzeug.wrappers.Response` classes.
|
|
51
|
+
|
|
52
|
+
If `simplejson`_ is installed it is preferred over Python's built-in
|
|
53
|
+
:mod:`json` module.
|
|
54
|
+
|
|
55
|
+
.. _simplejson: https://simplejson.readthedocs.io/en/latest/
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
#: A module or other object that has ``dumps`` and ``loads``
|
|
59
|
+
#: functions that match the API of the built-in :mod:`json` module.
|
|
60
|
+
json_module = _JSONModule
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def json(self):
|
|
64
|
+
"""The parsed JSON data if :attr:`mimetype` indicates JSON
|
|
65
|
+
(:mimetype:`application/json`, see :meth:`is_json`).
|
|
66
|
+
|
|
67
|
+
Calls :meth:`get_json` with default arguments.
|
|
68
|
+
"""
|
|
69
|
+
return self.get_json()
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def is_json(self):
|
|
73
|
+
"""Check if the mimetype indicates JSON data, either
|
|
74
|
+
:mimetype:`application/json` or :mimetype:`application/*+json`.
|
|
75
|
+
"""
|
|
76
|
+
mt = self.mimetype
|
|
77
|
+
return (
|
|
78
|
+
mt == "application/json"
|
|
79
|
+
or mt.startswith("application/")
|
|
80
|
+
and mt.endswith("+json")
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _get_data_for_json(self, cache):
|
|
84
|
+
try:
|
|
85
|
+
return self.get_data(cache=cache)
|
|
86
|
+
except TypeError:
|
|
87
|
+
# Response doesn't have cache param.
|
|
88
|
+
return self.get_data()
|
|
89
|
+
|
|
90
|
+
# Cached values for ``(silent=False, silent=True)``. Initialized
|
|
91
|
+
# with sentinel values.
|
|
92
|
+
_cached_json = (Ellipsis, Ellipsis)
|
|
93
|
+
|
|
94
|
+
def get_json(self, force=False, silent=False, cache=True):
|
|
95
|
+
"""Parse :attr:`data` as JSON.
|
|
96
|
+
|
|
97
|
+
If the mimetype does not indicate JSON
|
|
98
|
+
(:mimetype:`application/json`, see :meth:`is_json`), this
|
|
99
|
+
returns ``None``.
|
|
100
|
+
|
|
101
|
+
If parsing fails, :meth:`on_json_loading_failed` is called and
|
|
102
|
+
its return value is used as the return value.
|
|
103
|
+
|
|
104
|
+
:param force: Ignore the mimetype and always try to parse JSON.
|
|
105
|
+
:param silent: Silence parsing errors and return ``None``
|
|
106
|
+
instead.
|
|
107
|
+
:param cache: Store the parsed JSON to return for subsequent
|
|
108
|
+
calls.
|
|
109
|
+
"""
|
|
110
|
+
if cache and self._cached_json[silent] is not Ellipsis:
|
|
111
|
+
return self._cached_json[silent]
|
|
112
|
+
|
|
113
|
+
if not (force or self.is_json):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
data = self._get_data_for_json(cache=cache)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
rv = self.json_module.loads(data)
|
|
120
|
+
except ValueError as e:
|
|
121
|
+
if silent:
|
|
122
|
+
rv = None
|
|
123
|
+
|
|
124
|
+
if cache:
|
|
125
|
+
normal_rv, _ = self._cached_json
|
|
126
|
+
self._cached_json = (normal_rv, rv)
|
|
127
|
+
else:
|
|
128
|
+
rv = self.on_json_loading_failed(e)
|
|
129
|
+
|
|
130
|
+
if cache:
|
|
131
|
+
_, silent_rv = self._cached_json
|
|
132
|
+
self._cached_json = (rv, silent_rv)
|
|
133
|
+
else:
|
|
134
|
+
if cache:
|
|
135
|
+
self._cached_json = (rv, rv)
|
|
136
|
+
|
|
137
|
+
return rv
|
|
138
|
+
|
|
139
|
+
def on_json_loading_failed(self, e):
|
|
140
|
+
"""Called if :meth:`get_json` parsing fails and isn't silenced.
|
|
141
|
+
If this method returns a value, it is used as the return value
|
|
142
|
+
for :meth:`get_json`. The default implementation raises
|
|
143
|
+
:exc:`~werkzeug.exceptions.BadRequest`.
|
|
144
|
+
"""
|
|
145
|
+
raise BadRequest("Failed to decode JSON object: {0}".format(e))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from .accept import AcceptMixin
|
|
2
|
+
from .auth import AuthorizationMixin
|
|
3
|
+
from .base_request import BaseRequest
|
|
4
|
+
from .common_descriptors import CommonRequestDescriptorsMixin
|
|
5
|
+
from .cors import CORSRequestMixin
|
|
6
|
+
from .etag import ETagRequestMixin
|
|
7
|
+
from .user_agent import UserAgentMixin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Request(
|
|
11
|
+
BaseRequest,
|
|
12
|
+
AcceptMixin,
|
|
13
|
+
ETagRequestMixin,
|
|
14
|
+
UserAgentMixin,
|
|
15
|
+
AuthorizationMixin,
|
|
16
|
+
CORSRequestMixin,
|
|
17
|
+
CommonRequestDescriptorsMixin,
|
|
18
|
+
):
|
|
19
|
+
"""Full featured request object implementing the following mixins:
|
|
20
|
+
|
|
21
|
+
- :class:`AcceptMixin` for accept header parsing
|
|
22
|
+
- :class:`ETagRequestMixin` for etag and cache control handling
|
|
23
|
+
- :class:`UserAgentMixin` for user agent introspection
|
|
24
|
+
- :class:`AuthorizationMixin` for http auth handling
|
|
25
|
+
- :class:`~werkzeug.wrappers.cors.CORSRequestMixin` for Cross
|
|
26
|
+
Origin Resource Sharing headers
|
|
27
|
+
- :class:`CommonRequestDescriptorsMixin` for common headers
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StreamOnlyMixin(object):
|
|
33
|
+
"""If mixed in before the request object this will change the behavior
|
|
34
|
+
of it to disable handling of form parsing. This disables the
|
|
35
|
+
:attr:`files`, :attr:`form` attributes and will just provide a
|
|
36
|
+
:attr:`stream` attribute that however is always available.
|
|
37
|
+
|
|
38
|
+
.. versionadded:: 0.9
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
disable_data_descriptor = True
|
|
42
|
+
want_form_data_parsed = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PlainRequest(StreamOnlyMixin, Request):
|
|
46
|
+
"""A request object without special form parsing capabilities.
|
|
47
|
+
|
|
48
|
+
.. versionadded:: 0.9
|
|
49
|
+
"""
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from ..utils import cached_property
|
|
2
|
+
from .auth import WWWAuthenticateMixin
|
|
3
|
+
from .base_response import BaseResponse
|
|
4
|
+
from .common_descriptors import CommonResponseDescriptorsMixin
|
|
5
|
+
from .cors import CORSResponseMixin
|
|
6
|
+
from .etag import ETagResponseMixin
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ResponseStream(object):
|
|
10
|
+
"""A file descriptor like object used by the :class:`ResponseStreamMixin` to
|
|
11
|
+
represent the body of the stream. It directly pushes into the response
|
|
12
|
+
iterable of the response object.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
mode = "wb+"
|
|
16
|
+
|
|
17
|
+
def __init__(self, response):
|
|
18
|
+
self.response = response
|
|
19
|
+
self.closed = False
|
|
20
|
+
|
|
21
|
+
def write(self, value):
|
|
22
|
+
if self.closed:
|
|
23
|
+
raise ValueError("I/O operation on closed file")
|
|
24
|
+
self.response._ensure_sequence(mutable=True)
|
|
25
|
+
self.response.response.append(value)
|
|
26
|
+
self.response.headers.pop("Content-Length", None)
|
|
27
|
+
return len(value)
|
|
28
|
+
|
|
29
|
+
def writelines(self, seq):
|
|
30
|
+
for item in seq:
|
|
31
|
+
self.write(item)
|
|
32
|
+
|
|
33
|
+
def close(self):
|
|
34
|
+
self.closed = True
|
|
35
|
+
|
|
36
|
+
def flush(self):
|
|
37
|
+
if self.closed:
|
|
38
|
+
raise ValueError("I/O operation on closed file")
|
|
39
|
+
|
|
40
|
+
def isatty(self):
|
|
41
|
+
if self.closed:
|
|
42
|
+
raise ValueError("I/O operation on closed file")
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def tell(self):
|
|
46
|
+
self.response._ensure_sequence()
|
|
47
|
+
return sum(map(len, self.response.response))
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def encoding(self):
|
|
51
|
+
return self.response.charset
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ResponseStreamMixin(object):
|
|
55
|
+
"""Mixin for :class:`BaseResponse` subclasses. Classes that inherit from
|
|
56
|
+
this mixin will automatically get a :attr:`stream` property that provides
|
|
57
|
+
a write-only interface to the response iterable.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@cached_property
|
|
61
|
+
def stream(self):
|
|
62
|
+
"""The response iterable as write-only stream."""
|
|
63
|
+
return ResponseStream(self)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Response(
|
|
67
|
+
BaseResponse,
|
|
68
|
+
ETagResponseMixin,
|
|
69
|
+
WWWAuthenticateMixin,
|
|
70
|
+
CORSResponseMixin,
|
|
71
|
+
ResponseStreamMixin,
|
|
72
|
+
CommonResponseDescriptorsMixin,
|
|
73
|
+
):
|
|
74
|
+
"""Full featured response object implementing the following mixins:
|
|
75
|
+
|
|
76
|
+
- :class:`ETagResponseMixin` for etag and cache control handling
|
|
77
|
+
- :class:`WWWAuthenticateMixin` for HTTP authentication support
|
|
78
|
+
- :class:`~werkzeug.wrappers.cors.CORSResponseMixin` for Cross
|
|
79
|
+
Origin Resource Sharing headers
|
|
80
|
+
- :class:`ResponseStreamMixin` to add support for the ``stream``
|
|
81
|
+
property
|
|
82
|
+
- :class:`CommonResponseDescriptorsMixin` for various HTTP
|
|
83
|
+
descriptors
|
|
84
|
+
"""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from ..useragents import UserAgent
|
|
2
|
+
from ..utils import cached_property
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UserAgentMixin(object):
|
|
6
|
+
"""Adds a `user_agent` attribute to the request object which
|
|
7
|
+
contains the parsed user agent of the browser that triggered the
|
|
8
|
+
request as a :class:`~werkzeug.useragents.UserAgent` object.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@cached_property
|
|
12
|
+
def user_agent(self):
|
|
13
|
+
"""The current user agent."""
|
|
14
|
+
return UserAgent(self.environ)
|