aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/werkzeug/wrappers
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
committersotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
commit5bf22fc7e3c392c8bd44315ca2d06d7dca7d084e (patch)
tree8dacb0f195df1c0788d36dd0064f6bbaa3143ede /venv/lib/python3.8/site-packages/werkzeug/wrappers
parentb832d364da8c2efe09e3f75828caf73c50d01ce3 (diff)
add code for analysis of data
Diffstat (limited to 'venv/lib/python3.8/site-packages/werkzeug/wrappers')
-rw-r--r--venv/lib/python3.8/site-packages/werkzeug/wrappers/__init__.py3
-rw-r--r--venv/lib/python3.8/site-packages/werkzeug/wrappers/request.py647
-rw-r--r--venv/lib/python3.8/site-packages/werkzeug/wrappers/response.py831
3 files changed, 1481 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/werkzeug/wrappers/__init__.py b/venv/lib/python3.8/site-packages/werkzeug/wrappers/__init__.py
new file mode 100644
index 0000000..b36f228
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/werkzeug/wrappers/__init__.py
@@ -0,0 +1,3 @@
+from .request import Request as Request
+from .response import Response as Response
+from .response import ResponseStream as ResponseStream
diff --git a/venv/lib/python3.8/site-packages/werkzeug/wrappers/request.py b/venv/lib/python3.8/site-packages/werkzeug/wrappers/request.py
new file mode 100644
index 0000000..344f28b
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/werkzeug/wrappers/request.py
@@ -0,0 +1,647 @@
+from __future__ import annotations
+
+import collections.abc as cabc
+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:: 3.0
+ The ``charset``, ``url_charset``, and ``encoding_errors`` parameters
+ were removed.
+
+ .. 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
+
+ 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: t.Any) -> cabc.Iterable[bytes]:
+ request = cls(args[-2])
+ with request:
+ try:
+ resp = f(*args[:-2] + (request,))
+ except HTTPException as e:
+ resp = t.cast("WSGIApplication", 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
+ """
+ return self.form_data_parser_class(
+ stream_factory=self._get_file_stream,
+ 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(
+ 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(errors="replace")
+ 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'."
+ )
diff --git a/venv/lib/python3.8/site-packages/werkzeug/wrappers/response.py b/venv/lib/python3.8/site-packages/werkzeug/wrappers/response.py
new file mode 100644
index 0000000..7f01287
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/werkzeug/wrappers/response.py
@@ -0,0 +1,831 @@
+from __future__ import annotations
+
+import json
+import typing as t
+from http import HTTPStatus
+from urllib.parse import urljoin
+
+from .._internal import _get_environ
+from ..datastructures import Headers
+from ..http import generate_etag
+from ..http import http_date
+from ..http import is_resource_modified
+from ..http import parse_etags
+from ..http import parse_range_header
+from ..http import remove_entity_headers
+from ..sansio.response import Response as _SansIOResponse
+from ..urls import iri_to_uri
+from ..utils import cached_property
+from ..wsgi import _RangeWrapper
+from ..wsgi import ClosingIterator
+from ..wsgi import get_current_url
+
+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]) -> t.Iterator[bytes]:
+ for item in iterable:
+ if isinstance(item, str):
+ yield item.encode()
+ 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()
+
+ 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.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)
+
+ @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 = 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 "utf-8"