tests versuch 2
This commit is contained in:
parent
fdf385fe06
commit
c88f7df83a
2363 changed files with 408191 additions and 0 deletions
|
@ -0,0 +1,3 @@
|
|||
from .request import Request as Request
|
||||
from .response import Response as Response
|
||||
from .response import ResponseStream
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
653
venv/lib/python3.11/site-packages/werkzeug/wrappers/request.py
Normal file
653
venv/lib/python3.11/site-packages/werkzeug/wrappers/request.py
Normal file
|
@ -0,0 +1,653 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import json
|
||||
import typing as t
|
||||
from io import BytesIO
|
||||
|
||||
from .._internal import _wsgi_decoding_dance
|
||||
from ..datastructures import CombinedMultiDict
|
||||
from ..datastructures import EnvironHeaders
|
||||
from ..datastructures import FileStorage
|
||||
from ..datastructures import ImmutableMultiDict
|
||||
from ..datastructures import iter_multi_items
|
||||
from ..datastructures import MultiDict
|
||||
from ..exceptions import BadRequest
|
||||
from ..exceptions import UnsupportedMediaType
|
||||
from ..formparser import default_stream_factory
|
||||
from ..formparser import FormDataParser
|
||||
from ..sansio.request import Request as _SansIORequest
|
||||
from ..utils import cached_property
|
||||
from ..utils import environ_property
|
||||
from ..wsgi import _get_server
|
||||
from ..wsgi import get_input_stream
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class Request(_SansIORequest):
|
||||
"""Represents an incoming WSGI HTTP request, with headers and body
|
||||
taken from the WSGI environment. Has properties and methods for
|
||||
using the functionality defined by various HTTP specs. The data in
|
||||
requests object is read-only.
|
||||
|
||||
Text data is assumed to use UTF-8 encoding, which should be true for
|
||||
the vast majority of modern clients. Using an encoding set by the
|
||||
client is unsafe in Python due to extra encodings it provides, such
|
||||
as ``zip``. To change the assumed encoding, subclass and replace
|
||||
:attr:`charset`.
|
||||
|
||||
:param environ: The WSGI environ is generated by the WSGI server and
|
||||
contains information about the server configuration and client
|
||||
request.
|
||||
:param populate_request: Add this request object to the WSGI environ
|
||||
as ``environ['werkzeug.request']``. Can be useful when
|
||||
debugging.
|
||||
:param shallow: Makes reading from :attr:`stream` (and any method
|
||||
that would read from it) raise a :exc:`RuntimeError`. Useful to
|
||||
prevent consuming the form data in middleware, which would make
|
||||
it unavailable to the final application.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Old ``BaseRequest`` and mixin classes were removed.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Remove the ``disable_data_descriptor`` attribute.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Combine ``BaseRequest`` and mixins into a single ``Request``
|
||||
class.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
Read-only mode is enforced with immutable classes for all data.
|
||||
"""
|
||||
|
||||
#: the maximum content length. This is forwarded to the form data
|
||||
#: parsing function (:func:`parse_form_data`). When set and the
|
||||
#: :attr:`form` or :attr:`files` attribute is accessed and the
|
||||
#: parsing fails because more than the specified value is transmitted
|
||||
#: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#:
|
||||
#: .. versionadded:: 0.5
|
||||
max_content_length: int | None = None
|
||||
|
||||
#: the maximum form field size. This is forwarded to the form data
|
||||
#: parsing function (:func:`parse_form_data`). When set and the
|
||||
#: :attr:`form` or :attr:`files` attribute is accessed and the
|
||||
#: data in memory for post data is longer than the specified value a
|
||||
#: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#:
|
||||
#: .. versionadded:: 0.5
|
||||
max_form_memory_size: int | None = None
|
||||
|
||||
#: The maximum number of multipart parts to parse, passed to
|
||||
#: :attr:`form_data_parser_class`. Parsing form data with more than this
|
||||
#: many parts will raise :exc:`~.RequestEntityTooLarge`.
|
||||
#:
|
||||
#: .. versionadded:: 2.2.3
|
||||
max_form_parts = 1000
|
||||
|
||||
#: The form data parser that should be used. Can be replaced to customize
|
||||
#: the form date parsing.
|
||||
form_data_parser_class: type[FormDataParser] = FormDataParser
|
||||
|
||||
#: The WSGI environment containing HTTP headers and information from
|
||||
#: the WSGI server.
|
||||
environ: WSGIEnvironment
|
||||
|
||||
#: Set when creating the request object. If ``True``, reading from
|
||||
#: the request body will cause a ``RuntimeException``. Useful to
|
||||
#: prevent modifying the stream from middleware.
|
||||
shallow: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
environ: WSGIEnvironment,
|
||||
populate_request: bool = True,
|
||||
shallow: bool = False,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
method=environ.get("REQUEST_METHOD", "GET"),
|
||||
scheme=environ.get("wsgi.url_scheme", "http"),
|
||||
server=_get_server(environ),
|
||||
root_path=_wsgi_decoding_dance(environ.get("SCRIPT_NAME") or ""),
|
||||
path=_wsgi_decoding_dance(environ.get("PATH_INFO") or ""),
|
||||
query_string=environ.get("QUERY_STRING", "").encode("latin1"),
|
||||
headers=EnvironHeaders(environ),
|
||||
remote_addr=environ.get("REMOTE_ADDR"),
|
||||
)
|
||||
self.environ = environ
|
||||
self.shallow = shallow
|
||||
|
||||
if populate_request and not shallow:
|
||||
self.environ["werkzeug.request"] = self
|
||||
|
||||
@classmethod
|
||||
def from_values(cls, *args: t.Any, **kwargs: t.Any) -> Request:
|
||||
"""Create a new request object based on the values provided. If
|
||||
environ is given missing values are filled from there. This method is
|
||||
useful for small scripts when you need to simulate a request from an URL.
|
||||
Do not use this method for unittesting, there is a full featured client
|
||||
object (:class:`Client`) that allows to create multipart requests,
|
||||
support for cookies etc.
|
||||
|
||||
This accepts the same options as the
|
||||
:class:`~werkzeug.test.EnvironBuilder`.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
This method now accepts the same arguments as
|
||||
:class:`~werkzeug.test.EnvironBuilder`. Because of this the
|
||||
`environ` parameter is now called `environ_overrides`.
|
||||
|
||||
:return: request object
|
||||
"""
|
||||
from ..test import EnvironBuilder
|
||||
|
||||
kwargs.setdefault(
|
||||
"charset", cls.charset if not isinstance(cls.charset, property) else None
|
||||
)
|
||||
builder = EnvironBuilder(*args, **kwargs)
|
||||
try:
|
||||
return builder.get_request(cls)
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
@classmethod
|
||||
def application(cls, f: t.Callable[[Request], WSGIApplication]) -> WSGIApplication:
|
||||
"""Decorate a function as responder that accepts the request as
|
||||
the last argument. This works like the :func:`responder`
|
||||
decorator but the function is passed the request object as the
|
||||
last argument and the request object will be closed
|
||||
automatically::
|
||||
|
||||
@Request.application
|
||||
def my_wsgi_app(request):
|
||||
return Response('Hello World!')
|
||||
|
||||
As of Werkzeug 0.14 HTTP exceptions are automatically caught and
|
||||
converted to responses instead of failing.
|
||||
|
||||
:param f: the WSGI callable to decorate
|
||||
:return: a new WSGI callable
|
||||
"""
|
||||
#: return a callable that wraps the -2nd argument with the request
|
||||
#: and calls the function with all the arguments up to that one and
|
||||
#: the request. The return value is then called with the latest
|
||||
#: two arguments. This makes it possible to use this decorator for
|
||||
#: both standalone WSGI functions as well as bound methods and
|
||||
#: partially applied functions.
|
||||
from ..exceptions import HTTPException
|
||||
|
||||
@functools.wraps(f)
|
||||
def application(*args): # type: ignore
|
||||
request = cls(args[-2])
|
||||
with request:
|
||||
try:
|
||||
resp = f(*args[:-2] + (request,))
|
||||
except HTTPException as e:
|
||||
resp = e.get_response(args[-2])
|
||||
return resp(*args[-2:])
|
||||
|
||||
return t.cast("WSGIApplication", application)
|
||||
|
||||
def _get_file_stream(
|
||||
self,
|
||||
total_content_length: int | None,
|
||||
content_type: str | None,
|
||||
filename: str | None = None,
|
||||
content_length: int | None = None,
|
||||
) -> t.IO[bytes]:
|
||||
"""Called to get a stream for the file upload.
|
||||
|
||||
This must provide a file-like class with `read()`, `readline()`
|
||||
and `seek()` methods that is both writeable and readable.
|
||||
|
||||
The default implementation returns a temporary file if the total
|
||||
content length is higher than 500KB. Because many browsers do not
|
||||
provide a content length for the files only the total content
|
||||
length matters.
|
||||
|
||||
:param total_content_length: the total content length of all the
|
||||
data in the request combined. This value
|
||||
is guaranteed to be there.
|
||||
:param content_type: the mimetype of the uploaded file.
|
||||
:param filename: the filename of the uploaded file. May be `None`.
|
||||
:param content_length: the length of this file. This value is usually
|
||||
not provided because webbrowsers do not provide
|
||||
this value.
|
||||
"""
|
||||
return default_stream_factory(
|
||||
total_content_length=total_content_length,
|
||||
filename=filename,
|
||||
content_type=content_type,
|
||||
content_length=content_length,
|
||||
)
|
||||
|
||||
@property
|
||||
def want_form_data_parsed(self) -> bool:
|
||||
"""``True`` if the request method carries content. By default
|
||||
this is true if a ``Content-Type`` is sent.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
return bool(self.environ.get("CONTENT_TYPE"))
|
||||
|
||||
def make_form_data_parser(self) -> FormDataParser:
|
||||
"""Creates the form data parser. Instantiates the
|
||||
:attr:`form_data_parser_class` with some parameters.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
charset = self._charset if self._charset != "utf-8" else None
|
||||
errors = self._encoding_errors if self._encoding_errors != "replace" else None
|
||||
return self.form_data_parser_class(
|
||||
stream_factory=self._get_file_stream,
|
||||
charset=charset,
|
||||
errors=errors,
|
||||
max_form_memory_size=self.max_form_memory_size,
|
||||
max_content_length=self.max_content_length,
|
||||
max_form_parts=self.max_form_parts,
|
||||
cls=self.parameter_storage_class,
|
||||
)
|
||||
|
||||
def _load_form_data(self) -> None:
|
||||
"""Method used internally to retrieve submitted data. After calling
|
||||
this sets `form` and `files` on the request object to multi dicts
|
||||
filled with the incoming form data. As a matter of fact the input
|
||||
stream will be empty afterwards. You can also call this method to
|
||||
force the parsing of the form data.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
# abort early if we have already consumed the stream
|
||||
if "form" in self.__dict__:
|
||||
return
|
||||
|
||||
if self.want_form_data_parsed:
|
||||
parser = self.make_form_data_parser()
|
||||
data = parser.parse(
|
||||
self._get_stream_for_parsing(),
|
||||
self.mimetype,
|
||||
self.content_length,
|
||||
self.mimetype_params,
|
||||
)
|
||||
else:
|
||||
data = (
|
||||
self.stream,
|
||||
self.parameter_storage_class(),
|
||||
self.parameter_storage_class(),
|
||||
)
|
||||
|
||||
# inject the values into the instance dict so that we bypass
|
||||
# our cached_property non-data descriptor.
|
||||
d = self.__dict__
|
||||
d["stream"], d["form"], d["files"] = data
|
||||
|
||||
def _get_stream_for_parsing(self) -> t.IO[bytes]:
|
||||
"""This is the same as accessing :attr:`stream` with the difference
|
||||
that if it finds cached data from calling :meth:`get_data` first it
|
||||
will create a new stream out of the cached data.
|
||||
|
||||
.. versionadded:: 0.9.3
|
||||
"""
|
||||
cached_data = getattr(self, "_cached_data", None)
|
||||
if cached_data is not None:
|
||||
return BytesIO(cached_data)
|
||||
return self.stream
|
||||
|
||||
def close(self) -> None:
|
||||
"""Closes associated resources of this request object. This
|
||||
closes all file handles explicitly. You can also use the request
|
||||
object in a with statement which will automatically close it.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
files = self.__dict__.get("files")
|
||||
for _key, value in iter_multi_items(files or ()):
|
||||
value.close()
|
||||
|
||||
def __enter__(self) -> Request:
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb) -> None: # type: ignore
|
||||
self.close()
|
||||
|
||||
@cached_property
|
||||
def stream(self) -> t.IO[bytes]:
|
||||
"""The WSGI input stream, with safety checks. This stream can only be consumed
|
||||
once.
|
||||
|
||||
Use :meth:`get_data` to get the full data as bytes or text. The :attr:`data`
|
||||
attribute will contain the full bytes only if they do not represent form data.
|
||||
The :attr:`form` attribute will contain the parsed form data in that case.
|
||||
|
||||
Unlike :attr:`input_stream`, this stream guards against infinite streams or
|
||||
reading past :attr:`content_length` or :attr:`max_content_length`.
|
||||
|
||||
If ``max_content_length`` is set, it can be enforced on streams if
|
||||
``wsgi.input_terminated`` is set. Otherwise, an empty stream is returned.
|
||||
|
||||
If the limit is reached before the underlying stream is exhausted (such as a
|
||||
file that is too large, or an infinite stream), the remaining contents of the
|
||||
stream cannot be read safely. Depending on how the server handles this, clients
|
||||
may show a "connection reset" failure instead of seeing the 413 response.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Check ``max_content_length`` preemptively and while reading.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
The stream is always set (but may be consumed) even if form parsing was
|
||||
accessed first.
|
||||
"""
|
||||
if self.shallow:
|
||||
raise RuntimeError(
|
||||
"This request was created with 'shallow=True', reading"
|
||||
" from the input stream is disabled."
|
||||
)
|
||||
|
||||
return get_input_stream(
|
||||
self.environ, max_content_length=self.max_content_length
|
||||
)
|
||||
|
||||
input_stream = environ_property[t.IO[bytes]](
|
||||
"wsgi.input",
|
||||
doc="""The raw WSGI input stream, without any safety checks.
|
||||
|
||||
This is dangerous to use. It does not guard against infinite streams or reading
|
||||
past :attr:`content_length` or :attr:`max_content_length`.
|
||||
|
||||
Use :attr:`stream` instead.
|
||||
""",
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def data(self) -> bytes:
|
||||
"""The raw data read from :attr:`stream`. Will be empty if the request
|
||||
represents form data.
|
||||
|
||||
To get the raw data even if it represents form data, use :meth:`get_data`.
|
||||
"""
|
||||
return self.get_data(parse_form_data=True)
|
||||
|
||||
@t.overload
|
||||
def get_data( # type: ignore
|
||||
self,
|
||||
cache: bool = True,
|
||||
as_text: t.Literal[False] = False,
|
||||
parse_form_data: bool = False,
|
||||
) -> bytes:
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def get_data(
|
||||
self,
|
||||
cache: bool = True,
|
||||
as_text: t.Literal[True] = ...,
|
||||
parse_form_data: bool = False,
|
||||
) -> str:
|
||||
...
|
||||
|
||||
def get_data(
|
||||
self, cache: bool = True, as_text: bool = False, parse_form_data: bool = False
|
||||
) -> bytes | str:
|
||||
"""This reads the buffered incoming data from the client into one
|
||||
bytes object. By default this is cached but that behavior can be
|
||||
changed by setting `cache` to `False`.
|
||||
|
||||
Usually it's a bad idea to call this method without checking the
|
||||
content length first as a client could send dozens of megabytes or more
|
||||
to cause memory problems on the server.
|
||||
|
||||
Note that if the form data was already parsed this method will not
|
||||
return anything as form data parsing does not cache the data like
|
||||
this method does. To implicitly invoke form data parsing function
|
||||
set `parse_form_data` to `True`. When this is done the return value
|
||||
of this method will be an empty string if the form parser handles
|
||||
the data. This generally is not necessary as if the whole data is
|
||||
cached (which is the default) the form parser will used the cached
|
||||
data to parse the form data. Please be generally aware of checking
|
||||
the content length first in any case before calling this method
|
||||
to avoid exhausting server memory.
|
||||
|
||||
If `as_text` is set to `True` the return value will be a decoded
|
||||
string.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
rv = getattr(self, "_cached_data", None)
|
||||
if rv is None:
|
||||
if parse_form_data:
|
||||
self._load_form_data()
|
||||
rv = self.stream.read()
|
||||
if cache:
|
||||
self._cached_data = rv
|
||||
if as_text:
|
||||
rv = rv.decode(self._charset, self._encoding_errors)
|
||||
return rv
|
||||
|
||||
@cached_property
|
||||
def form(self) -> ImmutableMultiDict[str, str]:
|
||||
"""The form parameters. By default an
|
||||
:class:`~werkzeug.datastructures.ImmutableMultiDict`
|
||||
is returned from this function. This can be changed by setting
|
||||
:attr:`parameter_storage_class` to a different type. This might
|
||||
be necessary if the order of the form data is important.
|
||||
|
||||
Please keep in mind that file uploads will not end up here, but instead
|
||||
in the :attr:`files` attribute.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
|
||||
Previous to Werkzeug 0.9 this would only contain form data for POST
|
||||
and PUT requests.
|
||||
"""
|
||||
self._load_form_data()
|
||||
return self.form
|
||||
|
||||
@cached_property
|
||||
def values(self) -> CombinedMultiDict[str, str]:
|
||||
"""A :class:`werkzeug.datastructures.CombinedMultiDict` that
|
||||
combines :attr:`args` and :attr:`form`.
|
||||
|
||||
For GET requests, only ``args`` are present, not ``form``.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
For GET requests, only ``args`` are present, not ``form``.
|
||||
"""
|
||||
sources = [self.args]
|
||||
|
||||
if self.method != "GET":
|
||||
# GET requests can have a body, and some caching proxies
|
||||
# might not treat that differently than a normal GET
|
||||
# request, allowing form data to "invisibly" affect the
|
||||
# cache without indication in the query string / URL.
|
||||
sources.append(self.form)
|
||||
|
||||
args = []
|
||||
|
||||
for d in sources:
|
||||
if not isinstance(d, MultiDict):
|
||||
d = MultiDict(d)
|
||||
|
||||
args.append(d)
|
||||
|
||||
return CombinedMultiDict(args)
|
||||
|
||||
@cached_property
|
||||
def files(self) -> ImmutableMultiDict[str, FileStorage]:
|
||||
""":class:`~werkzeug.datastructures.MultiDict` object containing
|
||||
all uploaded files. Each key in :attr:`files` is the name from the
|
||||
``<input type="file" name="">``. Each value in :attr:`files` is a
|
||||
Werkzeug :class:`~werkzeug.datastructures.FileStorage` object.
|
||||
|
||||
It basically behaves like a standard file object you know from Python,
|
||||
with the difference that it also has a
|
||||
:meth:`~werkzeug.datastructures.FileStorage.save` function that can
|
||||
store the file on the filesystem.
|
||||
|
||||
Note that :attr:`files` will only contain data if the request method was
|
||||
POST, PUT or PATCH and the ``<form>`` that posted to the request had
|
||||
``enctype="multipart/form-data"``. It will be empty otherwise.
|
||||
|
||||
See the :class:`~werkzeug.datastructures.MultiDict` /
|
||||
:class:`~werkzeug.datastructures.FileStorage` documentation for
|
||||
more details about the used data structure.
|
||||
"""
|
||||
self._load_form_data()
|
||||
return self.files
|
||||
|
||||
@property
|
||||
def script_root(self) -> str:
|
||||
"""Alias for :attr:`self.root_path`. ``environ["SCRIPT_ROOT"]``
|
||||
without a trailing slash.
|
||||
"""
|
||||
return self.root_path
|
||||
|
||||
@cached_property
|
||||
def url_root(self) -> str:
|
||||
"""Alias for :attr:`root_url`. The URL with scheme, host, and
|
||||
root path. For example, ``https://example.com/app/``.
|
||||
"""
|
||||
return self.root_url
|
||||
|
||||
remote_user = environ_property[str](
|
||||
"REMOTE_USER",
|
||||
doc="""If the server supports user authentication, and the
|
||||
script is protected, this attribute contains the username the
|
||||
user has authenticated as.""",
|
||||
)
|
||||
is_multithread = environ_property[bool](
|
||||
"wsgi.multithread",
|
||||
doc="""boolean that is `True` if the application is served by a
|
||||
multithreaded WSGI server.""",
|
||||
)
|
||||
is_multiprocess = environ_property[bool](
|
||||
"wsgi.multiprocess",
|
||||
doc="""boolean that is `True` if the application is served by a
|
||||
WSGI server that spawns multiple processes.""",
|
||||
)
|
||||
is_run_once = environ_property[bool](
|
||||
"wsgi.run_once",
|
||||
doc="""boolean that is `True` if the application will be
|
||||
executed only once in a process lifetime. This is the case for
|
||||
CGI for example, but it's not guaranteed that the execution only
|
||||
happens one time.""",
|
||||
)
|
||||
|
||||
# JSON
|
||||
|
||||
#: A module or other object that has ``dumps`` and ``loads``
|
||||
#: functions that match the API of the built-in :mod:`json` module.
|
||||
json_module = json
|
||||
|
||||
@property
|
||||
def json(self) -> t.Any | None:
|
||||
"""The parsed JSON data if :attr:`mimetype` indicates JSON
|
||||
(:mimetype:`application/json`, see :attr:`is_json`).
|
||||
|
||||
Calls :meth:`get_json` with default arguments.
|
||||
|
||||
If the request content type is not ``application/json``, this
|
||||
will raise a 415 Unsupported Media Type error.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Raise a 415 error instead of 400.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Raise a 400 error if the content type is incorrect.
|
||||
"""
|
||||
return self.get_json()
|
||||
|
||||
# Cached values for ``(silent=False, silent=True)``. Initialized
|
||||
# with sentinel values.
|
||||
_cached_json: tuple[t.Any, t.Any] = (Ellipsis, Ellipsis)
|
||||
|
||||
@t.overload
|
||||
def get_json(
|
||||
self, force: bool = ..., silent: t.Literal[False] = ..., cache: bool = ...
|
||||
) -> t.Any:
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def get_json(
|
||||
self, force: bool = ..., silent: bool = ..., cache: bool = ...
|
||||
) -> t.Any | None:
|
||||
...
|
||||
|
||||
def get_json(
|
||||
self, force: bool = False, silent: bool = False, cache: bool = True
|
||||
) -> t.Any | None:
|
||||
"""Parse :attr:`data` as JSON.
|
||||
|
||||
If the mimetype does not indicate JSON
|
||||
(:mimetype:`application/json`, see :attr:`is_json`), or parsing
|
||||
fails, :meth:`on_json_loading_failed` is called and
|
||||
its return value is used as the return value. By default this
|
||||
raises a 415 Unsupported Media Type resp.
|
||||
|
||||
:param force: Ignore the mimetype and always try to parse JSON.
|
||||
:param silent: Silence mimetype and parsing errors, and
|
||||
return ``None`` instead.
|
||||
:param cache: Store the parsed JSON to return for subsequent
|
||||
calls.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Raise a 415 error instead of 400.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Raise a 400 error if the content type is incorrect.
|
||||
"""
|
||||
if cache and self._cached_json[silent] is not Ellipsis:
|
||||
return self._cached_json[silent]
|
||||
|
||||
if not (force or self.is_json):
|
||||
if not silent:
|
||||
return self.on_json_loading_failed(None)
|
||||
else:
|
||||
return None
|
||||
|
||||
data = self.get_data(cache=cache)
|
||||
|
||||
try:
|
||||
rv = self.json_module.loads(data)
|
||||
except ValueError as e:
|
||||
if silent:
|
||||
rv = None
|
||||
|
||||
if cache:
|
||||
normal_rv, _ = self._cached_json
|
||||
self._cached_json = (normal_rv, rv)
|
||||
else:
|
||||
rv = self.on_json_loading_failed(e)
|
||||
|
||||
if cache:
|
||||
_, silent_rv = self._cached_json
|
||||
self._cached_json = (rv, silent_rv)
|
||||
else:
|
||||
if cache:
|
||||
self._cached_json = (rv, rv)
|
||||
|
||||
return rv
|
||||
|
||||
def on_json_loading_failed(self, e: ValueError | None) -> t.Any:
|
||||
"""Called if :meth:`get_json` fails and isn't silenced.
|
||||
|
||||
If this method returns a value, it is used as the return value
|
||||
for :meth:`get_json`. The default implementation raises
|
||||
:exc:`~werkzeug.exceptions.BadRequest`.
|
||||
|
||||
:param e: If parsing failed, this is the exception. It will be
|
||||
``None`` if the content type wasn't ``application/json``.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Raise a 415 error instead of 400.
|
||||
"""
|
||||
if e is not None:
|
||||
raise BadRequest(f"Failed to decode JSON object: {e}")
|
||||
|
||||
raise UnsupportedMediaType(
|
||||
"Did not attempt to load JSON data because the request"
|
||||
" Content-Type was not 'application/json'."
|
||||
)
|
835
venv/lib/python3.11/site-packages/werkzeug/wrappers/response.py
Normal file
835
venv/lib/python3.11/site-packages/werkzeug/wrappers/response.py
Normal file
|
@ -0,0 +1,835 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import typing as t
|
||||
from http import HTTPStatus
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from ..datastructures import Headers
|
||||
from ..http import remove_entity_headers
|
||||
from ..sansio.response import Response as _SansIOResponse
|
||||
from ..urls import _invalid_iri_to_uri
|
||||
from ..urls import iri_to_uri
|
||||
from ..utils import cached_property
|
||||
from ..wsgi import ClosingIterator
|
||||
from ..wsgi import get_current_url
|
||||
from werkzeug._internal import _get_environ
|
||||
from werkzeug.http import generate_etag
|
||||
from werkzeug.http import http_date
|
||||
from werkzeug.http import is_resource_modified
|
||||
from werkzeug.http import parse_etags
|
||||
from werkzeug.http import parse_range_header
|
||||
from werkzeug.wsgi import _RangeWrapper
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
from .request import Request
|
||||
|
||||
|
||||
def _iter_encoded(iterable: t.Iterable[str | bytes], charset: str) -> t.Iterator[bytes]:
|
||||
for item in iterable:
|
||||
if isinstance(item, str):
|
||||
yield item.encode(charset)
|
||||
else:
|
||||
yield item
|
||||
|
||||
|
||||
class Response(_SansIOResponse):
|
||||
"""Represents an outgoing WSGI HTTP response with body, status, and
|
||||
headers. Has properties and methods for using the functionality
|
||||
defined by various HTTP specs.
|
||||
|
||||
The response body is flexible to support different use cases. The
|
||||
simple form is passing bytes, or a string which will be encoded as
|
||||
UTF-8. Passing an iterable of bytes or strings makes this a
|
||||
streaming response. A generator is particularly useful for building
|
||||
a CSV file in memory or using SSE (Server Sent Events). A file-like
|
||||
object is also iterable, although the
|
||||
:func:`~werkzeug.utils.send_file` helper should be used in that
|
||||
case.
|
||||
|
||||
The response object is itself a WSGI application callable. When
|
||||
called (:meth:`__call__`) with ``environ`` and ``start_response``,
|
||||
it will pass its status and headers to ``start_response`` then
|
||||
return its body as an iterable.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.wrappers.response import Response
|
||||
|
||||
def index():
|
||||
return Response("Hello, World!")
|
||||
|
||||
def application(environ, start_response):
|
||||
path = environ.get("PATH_INFO") or "/"
|
||||
|
||||
if path == "/":
|
||||
response = index()
|
||||
else:
|
||||
response = Response("Not Found", status=404)
|
||||
|
||||
return response(environ, start_response)
|
||||
|
||||
:param response: The data for the body of the response. A string or
|
||||
bytes, or tuple or list of strings or bytes, for a fixed-length
|
||||
response, or any other iterable of strings or bytes for a
|
||||
streaming response. Defaults to an empty body.
|
||||
:param status: The status code for the response. Either an int, in
|
||||
which case the default status message is added, or a string in
|
||||
the form ``{code} {message}``, like ``404 Not Found``. Defaults
|
||||
to 200.
|
||||
:param headers: A :class:`~werkzeug.datastructures.Headers` object,
|
||||
or a list of ``(key, value)`` tuples that will be converted to a
|
||||
``Headers`` object.
|
||||
:param mimetype: The mime type (content type without charset or
|
||||
other parameters) of the response. If the value starts with
|
||||
``text/`` (or matches some other special cases), the charset
|
||||
will be added to create the ``content_type``.
|
||||
:param content_type: The full content type of the response.
|
||||
Overrides building the value from ``mimetype``.
|
||||
:param direct_passthrough: Pass the response body directly through
|
||||
as the WSGI iterable. This can be used when the body is a binary
|
||||
file or other iterator of bytes, to skip some unnecessary
|
||||
checks. Use :func:`~werkzeug.utils.send_file` instead of setting
|
||||
this manually.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Old ``BaseResponse`` and mixin classes were removed.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Combine ``BaseResponse`` and mixins into a single ``Response``
|
||||
class.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
The ``direct_passthrough`` parameter was added.
|
||||
"""
|
||||
|
||||
#: if set to `False` accessing properties on the response object will
|
||||
#: not try to consume the response iterator and convert it into a list.
|
||||
#:
|
||||
#: .. versionadded:: 0.6.2
|
||||
#:
|
||||
#: That attribute was previously called `implicit_seqence_conversion`.
|
||||
#: (Notice the typo). If you did use this feature, you have to adapt
|
||||
#: your code to the name change.
|
||||
implicit_sequence_conversion = True
|
||||
|
||||
#: If a redirect ``Location`` header is a relative URL, make it an
|
||||
#: absolute URL, including scheme and domain.
|
||||
#:
|
||||
#: .. versionchanged:: 2.1
|
||||
#: This is disabled by default, so responses will send relative
|
||||
#: redirects.
|
||||
#:
|
||||
#: .. versionadded:: 0.8
|
||||
autocorrect_location_header = False
|
||||
|
||||
#: Should this response object automatically set the content-length
|
||||
#: header if possible? This is true by default.
|
||||
#:
|
||||
#: .. versionadded:: 0.8
|
||||
automatically_set_content_length = True
|
||||
|
||||
#: The response body to send as the WSGI iterable. A list of strings
|
||||
#: or bytes represents a fixed-length response, any other iterable
|
||||
#: is a streaming response. Strings are encoded to bytes as UTF-8.
|
||||
#:
|
||||
#: Do not set to a plain string or bytes, that will cause sending
|
||||
#: the response to be very inefficient as it will iterate one byte
|
||||
#: at a time.
|
||||
response: t.Iterable[str] | t.Iterable[bytes]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
response: t.Iterable[bytes] | bytes | t.Iterable[str] | str | None = None,
|
||||
status: int | str | HTTPStatus | None = None,
|
||||
headers: t.Mapping[str, str | t.Iterable[str]]
|
||||
| t.Iterable[tuple[str, str]]
|
||||
| None = None,
|
||||
mimetype: str | None = None,
|
||||
content_type: str | None = None,
|
||||
direct_passthrough: bool = False,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
status=status,
|
||||
headers=headers,
|
||||
mimetype=mimetype,
|
||||
content_type=content_type,
|
||||
)
|
||||
|
||||
#: Pass the response body directly through as the WSGI iterable.
|
||||
#: This can be used when the body is a binary file or other
|
||||
#: iterator of bytes, to skip some unnecessary checks. Use
|
||||
#: :func:`~werkzeug.utils.send_file` instead of setting this
|
||||
#: manually.
|
||||
self.direct_passthrough = direct_passthrough
|
||||
self._on_close: list[t.Callable[[], t.Any]] = []
|
||||
|
||||
# we set the response after the headers so that if a class changes
|
||||
# the charset attribute, the data is set in the correct charset.
|
||||
if response is None:
|
||||
self.response = []
|
||||
elif isinstance(response, (str, bytes, bytearray)):
|
||||
self.set_data(response)
|
||||
else:
|
||||
self.response = response
|
||||
|
||||
def call_on_close(self, func: t.Callable[[], t.Any]) -> t.Callable[[], t.Any]:
|
||||
"""Adds a function to the internal list of functions that should
|
||||
be called as part of closing down the response. Since 0.7 this
|
||||
function also returns the function that was passed so that this
|
||||
can be used as a decorator.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
self._on_close.append(func)
|
||||
return func
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.is_sequence:
|
||||
body_info = f"{sum(map(len, self.iter_encoded()))} bytes"
|
||||
else:
|
||||
body_info = "streamed" if self.is_streamed else "likely-streamed"
|
||||
return f"<{type(self).__name__} {body_info} [{self.status}]>"
|
||||
|
||||
@classmethod
|
||||
def force_type(
|
||||
cls, response: Response, environ: WSGIEnvironment | None = None
|
||||
) -> Response:
|
||||
"""Enforce that the WSGI response is a response object of the current
|
||||
type. Werkzeug will use the :class:`Response` internally in many
|
||||
situations like the exceptions. If you call :meth:`get_response` on an
|
||||
exception you will get back a regular :class:`Response` object, even
|
||||
if you are using a custom subclass.
|
||||
|
||||
This method can enforce a given response type, and it will also
|
||||
convert arbitrary WSGI callables into response objects if an environ
|
||||
is provided::
|
||||
|
||||
# convert a Werkzeug response object into an instance of the
|
||||
# MyResponseClass subclass.
|
||||
response = MyResponseClass.force_type(response)
|
||||
|
||||
# convert any WSGI application into a response object
|
||||
response = MyResponseClass.force_type(response, environ)
|
||||
|
||||
This is especially useful if you want to post-process responses in
|
||||
the main dispatcher and use functionality provided by your subclass.
|
||||
|
||||
Keep in mind that this will modify response objects in place if
|
||||
possible!
|
||||
|
||||
:param response: a response object or wsgi application.
|
||||
:param environ: a WSGI environment object.
|
||||
:return: a response object.
|
||||
"""
|
||||
if not isinstance(response, Response):
|
||||
if environ is None:
|
||||
raise TypeError(
|
||||
"cannot convert WSGI application into response"
|
||||
" objects without an environ"
|
||||
)
|
||||
|
||||
from ..test import run_wsgi_app
|
||||
|
||||
response = Response(*run_wsgi_app(response, environ))
|
||||
|
||||
response.__class__ = cls
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def from_app(
|
||||
cls, app: WSGIApplication, environ: WSGIEnvironment, buffered: bool = False
|
||||
) -> Response:
|
||||
"""Create a new response object from an application output. This
|
||||
works best if you pass it an application that returns a generator all
|
||||
the time. Sometimes applications may use the `write()` callable
|
||||
returned by the `start_response` function. This tries to resolve such
|
||||
edge cases automatically. But if you don't get the expected output
|
||||
you should set `buffered` to `True` which enforces buffering.
|
||||
|
||||
:param app: the WSGI application to execute.
|
||||
:param environ: the WSGI environment to execute against.
|
||||
:param buffered: set to `True` to enforce buffering.
|
||||
:return: a response object.
|
||||
"""
|
||||
from ..test import run_wsgi_app
|
||||
|
||||
return cls(*run_wsgi_app(app, environ, buffered))
|
||||
|
||||
@t.overload
|
||||
def get_data(self, as_text: t.Literal[False] = False) -> bytes:
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def get_data(self, as_text: t.Literal[True]) -> str:
|
||||
...
|
||||
|
||||
def get_data(self, as_text: bool = False) -> bytes | str:
|
||||
"""The string representation of the response body. Whenever you call
|
||||
this property the response iterable is encoded and flattened. This
|
||||
can lead to unwanted behavior if you stream big data.
|
||||
|
||||
This behavior can be disabled by setting
|
||||
:attr:`implicit_sequence_conversion` to `False`.
|
||||
|
||||
If `as_text` is set to `True` the return value will be a decoded
|
||||
string.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
self._ensure_sequence()
|
||||
rv = b"".join(self.iter_encoded())
|
||||
|
||||
if as_text:
|
||||
return rv.decode(self._charset)
|
||||
|
||||
return rv
|
||||
|
||||
def set_data(self, value: bytes | str) -> None:
|
||||
"""Sets a new string as response. The value must be a string or
|
||||
bytes. If a string is set it's encoded to the charset of the
|
||||
response (utf-8 by default).
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
value = value.encode(self._charset)
|
||||
self.response = [value]
|
||||
if self.automatically_set_content_length:
|
||||
self.headers["Content-Length"] = str(len(value))
|
||||
|
||||
data = property(
|
||||
get_data,
|
||||
set_data,
|
||||
doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.",
|
||||
)
|
||||
|
||||
def calculate_content_length(self) -> int | None:
|
||||
"""Returns the content length if available or `None` otherwise."""
|
||||
try:
|
||||
self._ensure_sequence()
|
||||
except RuntimeError:
|
||||
return None
|
||||
return sum(len(x) for x in self.iter_encoded())
|
||||
|
||||
def _ensure_sequence(self, mutable: bool = False) -> None:
|
||||
"""This method can be called by methods that need a sequence. If
|
||||
`mutable` is true, it will also ensure that the response sequence
|
||||
is a standard Python list.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
if self.is_sequence:
|
||||
# if we need a mutable object, we ensure it's a list.
|
||||
if mutable and not isinstance(self.response, list):
|
||||
self.response = list(self.response) # type: ignore
|
||||
return
|
||||
if self.direct_passthrough:
|
||||
raise RuntimeError(
|
||||
"Attempted implicit sequence conversion but the"
|
||||
" response object is in direct passthrough mode."
|
||||
)
|
||||
if not self.implicit_sequence_conversion:
|
||||
raise RuntimeError(
|
||||
"The response object required the iterable to be a"
|
||||
" sequence, but the implicit conversion was disabled."
|
||||
" Call make_sequence() yourself."
|
||||
)
|
||||
self.make_sequence()
|
||||
|
||||
def make_sequence(self) -> None:
|
||||
"""Converts the response iterator in a list. By default this happens
|
||||
automatically if required. If `implicit_sequence_conversion` is
|
||||
disabled, this method is not automatically called and some properties
|
||||
might raise exceptions. This also encodes all the items.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
if not self.is_sequence:
|
||||
# if we consume an iterable we have to ensure that the close
|
||||
# method of the iterable is called if available when we tear
|
||||
# down the response
|
||||
close = getattr(self.response, "close", None)
|
||||
self.response = list(self.iter_encoded())
|
||||
if close is not None:
|
||||
self.call_on_close(close)
|
||||
|
||||
def iter_encoded(self) -> t.Iterator[bytes]:
|
||||
"""Iter the response encoded with the encoding of the response.
|
||||
If the response object is invoked as WSGI application the return
|
||||
value of this method is used as application iterator unless
|
||||
:attr:`direct_passthrough` was activated.
|
||||
"""
|
||||
# Encode in a separate function so that self.response is fetched
|
||||
# early. This allows us to wrap the response with the return
|
||||
# value from get_app_iter or iter_encoded.
|
||||
return _iter_encoded(self.response, self._charset)
|
||||
|
||||
@property
|
||||
def is_streamed(self) -> bool:
|
||||
"""If the response is streamed (the response is not an iterable with
|
||||
a length information) this property is `True`. In this case streamed
|
||||
means that there is no information about the number of iterations.
|
||||
This is usually `True` if a generator is passed to the response object.
|
||||
|
||||
This is useful for checking before applying some sort of post
|
||||
filtering that should not take place for streamed responses.
|
||||
"""
|
||||
try:
|
||||
len(self.response) # type: ignore
|
||||
except (TypeError, AttributeError):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_sequence(self) -> bool:
|
||||
"""If the iterator is buffered, this property will be `True`. A
|
||||
response object will consider an iterator to be buffered if the
|
||||
response attribute is a list or tuple.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
return isinstance(self.response, (tuple, list))
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the wrapped response if possible. You can also use the object
|
||||
in a with statement which will automatically close it.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
Can now be used in a with statement.
|
||||
"""
|
||||
if hasattr(self.response, "close"):
|
||||
self.response.close()
|
||||
for func in self._on_close:
|
||||
func()
|
||||
|
||||
def __enter__(self) -> Response:
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb): # type: ignore
|
||||
self.close()
|
||||
|
||||
def freeze(self) -> None:
|
||||
"""Make the response object ready to be pickled. Does the
|
||||
following:
|
||||
|
||||
* Buffer the response into a list, ignoring
|
||||
:attr:`implicity_sequence_conversion` and
|
||||
:attr:`direct_passthrough`.
|
||||
* Set the ``Content-Length`` header.
|
||||
* Generate an ``ETag`` header if one is not already set.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Removed the ``no_etag`` parameter.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
An ``ETag`` header is always added.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
The ``Content-Length`` header is set.
|
||||
"""
|
||||
# Always freeze the encoded response body, ignore
|
||||
# implicit_sequence_conversion and direct_passthrough.
|
||||
self.response = list(self.iter_encoded())
|
||||
self.headers["Content-Length"] = str(sum(map(len, self.response)))
|
||||
self.add_etag()
|
||||
|
||||
def get_wsgi_headers(self, environ: WSGIEnvironment) -> Headers:
|
||||
"""This is automatically called right before the response is started
|
||||
and returns headers modified for the given environment. It returns a
|
||||
copy of the headers from the response with some modifications applied
|
||||
if necessary.
|
||||
|
||||
For example the location header (if present) is joined with the root
|
||||
URL of the environment. Also the content length is automatically set
|
||||
to zero here for certain status codes.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
Previously that function was called `fix_headers` and modified
|
||||
the response object in place. Also since 0.6, IRIs in location
|
||||
and content-location headers are handled properly.
|
||||
|
||||
Also starting with 0.6, Werkzeug will attempt to set the content
|
||||
length if it is able to figure it out on its own. This is the
|
||||
case if all the strings in the response iterable are already
|
||||
encoded and the iterable is buffered.
|
||||
|
||||
:param environ: the WSGI environment of the request.
|
||||
:return: returns a new :class:`~werkzeug.datastructures.Headers`
|
||||
object.
|
||||
"""
|
||||
headers = Headers(self.headers)
|
||||
location: str | None = None
|
||||
content_location: str | None = None
|
||||
content_length: str | int | None = None
|
||||
status = self.status_code
|
||||
|
||||
# iterate over the headers to find all values in one go. Because
|
||||
# get_wsgi_headers is used each response that gives us a tiny
|
||||
# speedup.
|
||||
for key, value in headers:
|
||||
ikey = key.lower()
|
||||
if ikey == "location":
|
||||
location = value
|
||||
elif ikey == "content-location":
|
||||
content_location = value
|
||||
elif ikey == "content-length":
|
||||
content_length = value
|
||||
|
||||
if location is not None:
|
||||
location = _invalid_iri_to_uri(location)
|
||||
|
||||
if self.autocorrect_location_header:
|
||||
# Make the location header an absolute URL.
|
||||
current_url = get_current_url(environ, strip_querystring=True)
|
||||
current_url = iri_to_uri(current_url)
|
||||
location = urljoin(current_url, location)
|
||||
|
||||
headers["Location"] = location
|
||||
|
||||
# make sure the content location is a URL
|
||||
if content_location is not None:
|
||||
headers["Content-Location"] = iri_to_uri(content_location)
|
||||
|
||||
if 100 <= status < 200 or status == 204:
|
||||
# Per section 3.3.2 of RFC 7230, "a server MUST NOT send a
|
||||
# Content-Length header field in any response with a status
|
||||
# code of 1xx (Informational) or 204 (No Content)."
|
||||
headers.remove("Content-Length")
|
||||
elif status == 304:
|
||||
remove_entity_headers(headers)
|
||||
|
||||
# if we can determine the content length automatically, we
|
||||
# should try to do that. But only if this does not involve
|
||||
# flattening the iterator or encoding of strings in the
|
||||
# response. We however should not do that if we have a 304
|
||||
# response.
|
||||
if (
|
||||
self.automatically_set_content_length
|
||||
and self.is_sequence
|
||||
and content_length is None
|
||||
and status not in (204, 304)
|
||||
and not (100 <= status < 200)
|
||||
):
|
||||
content_length = sum(len(x) for x in self.iter_encoded())
|
||||
headers["Content-Length"] = str(content_length)
|
||||
|
||||
return headers
|
||||
|
||||
def get_app_iter(self, environ: WSGIEnvironment) -> t.Iterable[bytes]:
|
||||
"""Returns the application iterator for the given environ. Depending
|
||||
on the request method and the current status code the return value
|
||||
might be an empty response rather than the one from the response.
|
||||
|
||||
If the request method is `HEAD` or the status code is in a range
|
||||
where the HTTP specification requires an empty response, an empty
|
||||
iterable is returned.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
:param environ: the WSGI environment of the request.
|
||||
:return: a response iterable.
|
||||
"""
|
||||
status = self.status_code
|
||||
if (
|
||||
environ["REQUEST_METHOD"] == "HEAD"
|
||||
or 100 <= status < 200
|
||||
or status in (204, 304)
|
||||
):
|
||||
iterable: t.Iterable[bytes] = ()
|
||||
elif self.direct_passthrough:
|
||||
return self.response # type: ignore
|
||||
else:
|
||||
iterable = self.iter_encoded()
|
||||
return ClosingIterator(iterable, self.close)
|
||||
|
||||
def get_wsgi_response(
|
||||
self, environ: WSGIEnvironment
|
||||
) -> tuple[t.Iterable[bytes], str, list[tuple[str, str]]]:
|
||||
"""Returns the final WSGI response as tuple. The first item in
|
||||
the tuple is the application iterator, the second the status and
|
||||
the third the list of headers. The response returned is created
|
||||
specially for the given environment. For example if the request
|
||||
method in the WSGI environment is ``'HEAD'`` the response will
|
||||
be empty and only the headers and status code will be present.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
:param environ: the WSGI environment of the request.
|
||||
:return: an ``(app_iter, status, headers)`` tuple.
|
||||
"""
|
||||
headers = self.get_wsgi_headers(environ)
|
||||
app_iter = self.get_app_iter(environ)
|
||||
return app_iter, self.status, headers.to_wsgi_list()
|
||||
|
||||
def __call__(
|
||||
self, environ: WSGIEnvironment, start_response: StartResponse
|
||||
) -> t.Iterable[bytes]:
|
||||
"""Process this response as WSGI application.
|
||||
|
||||
:param environ: the WSGI environment.
|
||||
:param start_response: the response callable provided by the WSGI
|
||||
server.
|
||||
:return: an application iterator
|
||||
"""
|
||||
app_iter, status, headers = self.get_wsgi_response(environ)
|
||||
start_response(status, headers)
|
||||
return app_iter
|
||||
|
||||
# JSON
|
||||
|
||||
#: A module or other object that has ``dumps`` and ``loads``
|
||||
#: functions that match the API of the built-in :mod:`json` module.
|
||||
json_module = json
|
||||
|
||||
@property
|
||||
def json(self) -> t.Any | None:
|
||||
"""The parsed JSON data if :attr:`mimetype` indicates JSON
|
||||
(:mimetype:`application/json`, see :attr:`is_json`).
|
||||
|
||||
Calls :meth:`get_json` with default arguments.
|
||||
"""
|
||||
return self.get_json()
|
||||
|
||||
@t.overload
|
||||
def get_json(self, force: bool = ..., silent: t.Literal[False] = ...) -> t.Any:
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def get_json(self, force: bool = ..., silent: bool = ...) -> t.Any | None:
|
||||
...
|
||||
|
||||
def get_json(self, force: bool = False, silent: bool = False) -> t.Any | None:
|
||||
"""Parse :attr:`data` as JSON. Useful during testing.
|
||||
|
||||
If the mimetype does not indicate JSON
|
||||
(:mimetype:`application/json`, see :attr:`is_json`), this
|
||||
returns ``None``.
|
||||
|
||||
Unlike :meth:`Request.get_json`, the result is not cached.
|
||||
|
||||
:param force: Ignore the mimetype and always try to parse JSON.
|
||||
:param silent: Silence parsing errors and return ``None``
|
||||
instead.
|
||||
"""
|
||||
if not (force or self.is_json):
|
||||
return None
|
||||
|
||||
data = self.get_data()
|
||||
|
||||
try:
|
||||
return self.json_module.loads(data)
|
||||
except ValueError:
|
||||
if not silent:
|
||||
raise
|
||||
|
||||
return None
|
||||
|
||||
# Stream
|
||||
|
||||
@cached_property
|
||||
def stream(self) -> ResponseStream:
|
||||
"""The response iterable as write-only stream."""
|
||||
return ResponseStream(self)
|
||||
|
||||
def _wrap_range_response(self, start: int, length: int) -> None:
|
||||
"""Wrap existing Response in case of Range Request context."""
|
||||
if self.status_code == 206:
|
||||
self.response = _RangeWrapper(self.response, start, length) # type: ignore
|
||||
|
||||
def _is_range_request_processable(self, environ: WSGIEnvironment) -> bool:
|
||||
"""Return ``True`` if `Range` header is present and if underlying
|
||||
resource is considered unchanged when compared with `If-Range` header.
|
||||
"""
|
||||
return (
|
||||
"HTTP_IF_RANGE" not in environ
|
||||
or not is_resource_modified(
|
||||
environ,
|
||||
self.headers.get("etag"),
|
||||
None,
|
||||
self.headers.get("last-modified"),
|
||||
ignore_if_range=False,
|
||||
)
|
||||
) and "HTTP_RANGE" in environ
|
||||
|
||||
def _process_range_request(
|
||||
self,
|
||||
environ: WSGIEnvironment,
|
||||
complete_length: int | None,
|
||||
accept_ranges: bool | str,
|
||||
) -> bool:
|
||||
"""Handle Range Request related headers (RFC7233). If `Accept-Ranges`
|
||||
header is valid, and Range Request is processable, we set the headers
|
||||
as described by the RFC, and wrap the underlying response in a
|
||||
RangeWrapper.
|
||||
|
||||
Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise.
|
||||
|
||||
:raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
|
||||
if `Range` header could not be parsed or satisfied.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Returns ``False`` if the length is 0.
|
||||
"""
|
||||
from ..exceptions import RequestedRangeNotSatisfiable
|
||||
|
||||
if (
|
||||
not accept_ranges
|
||||
or complete_length is None
|
||||
or complete_length == 0
|
||||
or not self._is_range_request_processable(environ)
|
||||
):
|
||||
return False
|
||||
|
||||
if accept_ranges is True:
|
||||
accept_ranges = "bytes"
|
||||
|
||||
parsed_range = parse_range_header(environ.get("HTTP_RANGE"))
|
||||
|
||||
if parsed_range is None:
|
||||
raise RequestedRangeNotSatisfiable(complete_length)
|
||||
|
||||
range_tuple = parsed_range.range_for_length(complete_length)
|
||||
content_range_header = parsed_range.to_content_range_header(complete_length)
|
||||
|
||||
if range_tuple is None or content_range_header is None:
|
||||
raise RequestedRangeNotSatisfiable(complete_length)
|
||||
|
||||
content_length = range_tuple[1] - range_tuple[0]
|
||||
self.headers["Content-Length"] = str(content_length)
|
||||
self.headers["Accept-Ranges"] = accept_ranges
|
||||
self.content_range = content_range_header # type: ignore
|
||||
self.status_code = 206
|
||||
self._wrap_range_response(range_tuple[0], content_length)
|
||||
return True
|
||||
|
||||
def make_conditional(
|
||||
self,
|
||||
request_or_environ: WSGIEnvironment | Request,
|
||||
accept_ranges: bool | str = False,
|
||||
complete_length: int | None = None,
|
||||
) -> Response:
|
||||
"""Make the response conditional to the request. This method works
|
||||
best if an etag was defined for the response already. The `add_etag`
|
||||
method can be used to do that. If called without etag just the date
|
||||
header is set.
|
||||
|
||||
This does nothing if the request method in the request or environ is
|
||||
anything but GET or HEAD.
|
||||
|
||||
For optimal performance when handling range requests, it's recommended
|
||||
that your response data object implements `seekable`, `seek` and `tell`
|
||||
methods as described by :py:class:`io.IOBase`. Objects returned by
|
||||
:meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods.
|
||||
|
||||
It does not remove the body of the response because that's something
|
||||
the :meth:`__call__` function does for us automatically.
|
||||
|
||||
Returns self so that you can do ``return resp.make_conditional(req)``
|
||||
but modifies the object in-place.
|
||||
|
||||
:param request_or_environ: a request object or WSGI environment to be
|
||||
used to make the response conditional
|
||||
against.
|
||||
:param accept_ranges: This parameter dictates the value of
|
||||
`Accept-Ranges` header. If ``False`` (default),
|
||||
the header is not set. If ``True``, it will be set
|
||||
to ``"bytes"``. If it's a string, it will use this
|
||||
value.
|
||||
:param complete_length: Will be used only in valid Range Requests.
|
||||
It will set `Content-Range` complete length
|
||||
value and compute `Content-Length` real value.
|
||||
This parameter is mandatory for successful
|
||||
Range Requests completion.
|
||||
:raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
|
||||
if `Range` header could not be parsed or satisfied.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Range processing is skipped if length is 0 instead of
|
||||
raising a 416 Range Not Satisfiable error.
|
||||
"""
|
||||
environ = _get_environ(request_or_environ)
|
||||
if environ["REQUEST_METHOD"] in ("GET", "HEAD"):
|
||||
# if the date is not in the headers, add it now. We however
|
||||
# will not override an already existing header. Unfortunately
|
||||
# this header will be overridden by many WSGI servers including
|
||||
# wsgiref.
|
||||
if "date" not in self.headers:
|
||||
self.headers["Date"] = http_date()
|
||||
is206 = self._process_range_request(environ, complete_length, accept_ranges)
|
||||
if not is206 and not is_resource_modified(
|
||||
environ,
|
||||
self.headers.get("etag"),
|
||||
None,
|
||||
self.headers.get("last-modified"),
|
||||
):
|
||||
if parse_etags(environ.get("HTTP_IF_MATCH")):
|
||||
self.status_code = 412
|
||||
else:
|
||||
self.status_code = 304
|
||||
if (
|
||||
self.automatically_set_content_length
|
||||
and "content-length" not in self.headers
|
||||
):
|
||||
length = self.calculate_content_length()
|
||||
if length is not None:
|
||||
self.headers["Content-Length"] = str(length)
|
||||
return self
|
||||
|
||||
def add_etag(self, overwrite: bool = False, weak: bool = False) -> None:
|
||||
"""Add an etag for the current response if there is none yet.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
SHA-1 is used to generate the value. MD5 may not be
|
||||
available in some environments.
|
||||
"""
|
||||
if overwrite or "etag" not in self.headers:
|
||||
self.set_etag(generate_etag(self.get_data()), weak)
|
||||
|
||||
|
||||
class ResponseStream:
|
||||
"""A file descriptor like object used by :meth:`Response.stream` to
|
||||
represent the body of the stream. It directly pushes into the
|
||||
response iterable of the response object.
|
||||
"""
|
||||
|
||||
mode = "wb+"
|
||||
|
||||
def __init__(self, response: Response):
|
||||
self.response = response
|
||||
self.closed = False
|
||||
|
||||
def write(self, value: bytes) -> int:
|
||||
if self.closed:
|
||||
raise ValueError("I/O operation on closed file")
|
||||
self.response._ensure_sequence(mutable=True)
|
||||
self.response.response.append(value) # type: ignore
|
||||
self.response.headers.pop("Content-Length", None)
|
||||
return len(value)
|
||||
|
||||
def writelines(self, seq: t.Iterable[bytes]) -> None:
|
||||
for item in seq:
|
||||
self.write(item)
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
def flush(self) -> None:
|
||||
if self.closed:
|
||||
raise ValueError("I/O operation on closed file")
|
||||
|
||||
def isatty(self) -> bool:
|
||||
if self.closed:
|
||||
raise ValueError("I/O operation on closed file")
|
||||
return False
|
||||
|
||||
def tell(self) -> int:
|
||||
self.response._ensure_sequence()
|
||||
return sum(map(len, self.response.response))
|
||||
|
||||
@property
|
||||
def encoding(self) -> str:
|
||||
return self.response._charset
|
Loading…
Add table
Add a link
Reference in a new issue