up follow livre
This commit is contained in:
		
							parent
							
								
									b4b4398bb0
								
							
						
					
					
						commit
						3a7a3849ae
					
				
					 12242 changed files with 2564461 additions and 6914 deletions
				
			
		
							
								
								
									
										831
									
								
								venv/lib/python3.13/site-packages/werkzeug/wrappers/response.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										831
									
								
								venv/lib/python3.13/site-packages/werkzeug/wrappers/response.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -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" | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tykayn
						Tykayn