Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
response.py839 linesDownload Raw Back to wrappers
1from __future__ import annotations2 3import json4import typing as t5from http import HTTPStatus6from urllib.parse import urljoin7 8from .._internal import _get_environ9from ..datastructures import Headers10from ..http import generate_etag11from ..http import http_date12from ..http import is_resource_modified13from ..http import parse_etags14from ..http import parse_range_header15from ..http import remove_entity_headers16from ..sansio.response import Response as _SansIOResponse17from ..urls import iri_to_uri18from ..utils import cached_property19from ..wsgi import _RangeWrapper20from ..wsgi import ClosingIterator21from ..wsgi import get_current_url22 23if t.TYPE_CHECKING:24    from _typeshed.wsgi import StartResponse25    from _typeshed.wsgi import WSGIApplication26    from _typeshed.wsgi import WSGIEnvironment27 28    from .request import Request29 30 31def _iter_encoded(iterable: t.Iterable[str | bytes]) -> t.Iterator[bytes]:32    for item in iterable:33        if isinstance(item, str):34            yield item.encode()35        else:36            yield item37 38 39class Response(_SansIOResponse):40    """Represents an outgoing WSGI HTTP response with body, status, and41    headers. Has properties and methods for using the functionality42    defined by various HTTP specs.43 44    The response body is flexible to support different use cases. The45    simple form is passing bytes, or a string which will be encoded as46    UTF-8. Passing an iterable of bytes or strings makes this a47    streaming response. A generator is particularly useful for building48    a CSV file in memory or using SSE (Server Sent Events). A file-like49    object is also iterable, although the50    :func:`~werkzeug.utils.send_file` helper should be used in that51    case.52 53    The response object is itself a WSGI application callable. When54    called (:meth:`__call__`) with ``environ`` and ``start_response``,55    it will pass its status and headers to ``start_response`` then56    return its body as an iterable.57 58    .. code-block:: python59 60        from werkzeug.wrappers.response import Response61 62        def index():63            return Response("Hello, World!")64 65        def application(environ, start_response):66            path = environ.get("PATH_INFO") or "/"67 68            if path == "/":69                response = index()70            else:71                response = Response("Not Found", status=404)72 73            return response(environ, start_response)74 75    :param response: The data for the body of the response. A string or76        bytes, or tuple or list of strings or bytes, for a fixed-length77        response, or any other iterable of strings or bytes for a78        streaming response. Defaults to an empty body.79    :param status: The status code for the response. Either an int, in80        which case the default status message is added, or a string in81        the form ``{code} {message}``, like ``404 Not Found``. Defaults82        to 200.83    :param headers: A :class:`~werkzeug.datastructures.Headers` object,84        or a list of ``(key, value)`` tuples that will be converted to a85        ``Headers`` object.86    :param mimetype: The mime type (content type without charset or87        other parameters) of the response. If the value starts with88        ``text/`` (or matches some other special cases), the charset89        will be added to create the ``content_type``.90    :param content_type: The full content type of the response.91        Overrides building the value from ``mimetype``.92    :param direct_passthrough: Pass the response body directly through93        as the WSGI iterable. This can be used when the body is a binary94        file or other iterator of bytes, to skip some unnecessary95        checks. Use :func:`~werkzeug.utils.send_file` instead of setting96        this manually.97 98    .. versionchanged:: 2.199        Old ``BaseResponse`` and mixin classes were removed.100 101    .. versionchanged:: 2.0102        Combine ``BaseResponse`` and mixins into a single ``Response``103        class.104 105    .. versionchanged:: 0.5106        The ``direct_passthrough`` parameter was added.107    """108 109    #: if set to `False` accessing properties on the response object will110    #: not try to consume the response iterator and convert it into a list.111    #:112    #: .. versionadded:: 0.6.2113    #:114    #:    That attribute was previously called `implicit_seqence_conversion`.115    #:    (Notice the typo).  If you did use this feature, you have to adapt116    #:    your code to the name change.117    implicit_sequence_conversion = True118 119    #: If a redirect ``Location`` header is a relative URL, make it an120    #: absolute URL, including scheme and domain.121    #:122    #: .. versionchanged:: 2.1123    #:     This is disabled by default, so responses will send relative124    #:     redirects.125    #:126    #: .. versionadded:: 0.8127    autocorrect_location_header = False128 129    #: Should this response object automatically set the content-length130    #: header if possible?  This is true by default.131    #:132    #: .. versionadded:: 0.8133    automatically_set_content_length = True134 135    #: The response body to send as the WSGI iterable. A list of strings136    #: or bytes represents a fixed-length response, any other iterable137    #: is a streaming response. Strings are encoded to bytes as UTF-8.138    #:139    #: Do not set to a plain string or bytes, that will cause sending140    #: the response to be very inefficient as it will iterate one byte141    #: at a time.142    response: t.Iterable[str] | t.Iterable[bytes]143 144    def __init__(145        self,146        response: t.Iterable[bytes] | bytes | t.Iterable[str] | str | None = None,147        status: int | str | HTTPStatus | None = None,148        headers: t.Mapping[str, str | t.Iterable[str]]149        | t.Iterable[tuple[str, str]]150        | None = None,151        mimetype: str | None = None,152        content_type: str | None = None,153        direct_passthrough: bool = False,154    ) -> None:155        super().__init__(156            status=status,157            headers=headers,158            mimetype=mimetype,159            content_type=content_type,160        )161 162        #: Pass the response body directly through as the WSGI iterable.163        #: This can be used when the body is a binary file or other164        #: iterator of bytes, to skip some unnecessary checks. Use165        #: :func:`~werkzeug.utils.send_file` instead of setting this166        #: manually.167        self.direct_passthrough = direct_passthrough168        self._on_close: list[t.Callable[[], t.Any]] = []169 170        # we set the response after the headers so that if a class changes171        # the charset attribute, the data is set in the correct charset.172        if response is None:173            self.response = []174        elif isinstance(response, (str, bytes, bytearray)):175            self.set_data(response)176        else:177            self.response = response178 179    def call_on_close(self, func: t.Callable[[], t.Any]) -> t.Callable[[], t.Any]:180        """Adds a function to the internal list of functions that should181        be called as part of closing down the response.  Since 0.7 this182        function also returns the function that was passed so that this183        can be used as a decorator.184 185        .. versionadded:: 0.6186        """187        self._on_close.append(func)188        return func189 190    def __repr__(self) -> str:191        if self.is_sequence:192            body_info = f"{sum(map(len, self.iter_encoded()))} bytes"193        else:194            body_info = "streamed" if self.is_streamed else "likely-streamed"195        return f"<{type(self).__name__} {body_info} [{self.status}]>"196 197    @classmethod198    def force_type(199        cls, response: Response, environ: WSGIEnvironment | None = None200    ) -> Response:201        """Enforce that the WSGI response is a response object of the current202        type.  Werkzeug will use the :class:`Response` internally in many203        situations like the exceptions.  If you call :meth:`get_response` on an204        exception you will get back a regular :class:`Response` object, even205        if you are using a custom subclass.206 207        This method can enforce a given response type, and it will also208        convert arbitrary WSGI callables into response objects if an environ209        is provided::210 211            # convert a Werkzeug response object into an instance of the212            # MyResponseClass subclass.213            response = MyResponseClass.force_type(response)214 215            # convert any WSGI application into a response object216            response = MyResponseClass.force_type(response, environ)217 218        This is especially useful if you want to post-process responses in219        the main dispatcher and use functionality provided by your subclass.220 221        Keep in mind that this will modify response objects in place if222        possible!223 224        :param response: a response object or wsgi application.225        :param environ: a WSGI environment object.226        :return: a response object.227        """228        if not isinstance(response, Response):229            if environ is None:230                raise TypeError(231                    "cannot convert WSGI application into response"232                    " objects without an environ"233                )234 235            from ..test import run_wsgi_app236 237            response = Response(*run_wsgi_app(response, environ))238 239        response.__class__ = cls240        return response241 242    @classmethod243    def from_app(244        cls, app: WSGIApplication, environ: WSGIEnvironment, buffered: bool = False245    ) -> Response:246        """Create a new response object from an application output.  This247        works best if you pass it an application that returns a generator all248        the time.  Sometimes applications may use the `write()` callable249        returned by the `start_response` function.  This tries to resolve such250        edge cases automatically.  But if you don't get the expected output251        you should set `buffered` to `True` which enforces buffering.252 253        :param app: the WSGI application to execute.254        :param environ: the WSGI environment to execute against.255        :param buffered: set to `True` to enforce buffering.256        :return: a response object.257        """258        from ..test import run_wsgi_app259 260        return cls(*run_wsgi_app(app, environ, buffered))261 262    @t.overload263    def get_data(self, as_text: t.Literal[False] = False) -> bytes: ...264 265    @t.overload266    def get_data(self, as_text: t.Literal[True]) -> str: ...267 268    def get_data(self, as_text: bool = False) -> bytes | str:269        """The string representation of the response body.  Whenever you call270        this property the response iterable is encoded and flattened.  This271        can lead to unwanted behavior if you stream big data.272 273        This behavior can be disabled by setting274        :attr:`implicit_sequence_conversion` to `False`.275 276        If `as_text` is set to `True` the return value will be a decoded277        string.278 279        .. versionadded:: 0.9280        """281        self._ensure_sequence()282        rv = b"".join(self.iter_encoded())283 284        if as_text:285            return rv.decode()286 287        return rv288 289    def set_data(self, value: bytes | str) -> None:290        """Sets a new string as response.  The value must be a string or291        bytes. If a string is set it's encoded to the charset of the292        response (utf-8 by default).293 294        .. versionadded:: 0.9295        """296        if isinstance(value, str):297            value = value.encode()298        self.response = [value]299        if self.automatically_set_content_length:300            self.headers["Content-Length"] = str(len(value))301 302    data = property(303        get_data,304        set_data,305        doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.",306    )307 308    def calculate_content_length(self) -> int | None:309        """Returns the content length if available or `None` otherwise."""310        try:311            self._ensure_sequence()312        except RuntimeError:313            return None314        return sum(len(x) for x in self.iter_encoded())315 316    def _ensure_sequence(self, mutable: bool = False) -> None:317        """This method can be called by methods that need a sequence.  If318        `mutable` is true, it will also ensure that the response sequence319        is a standard Python list.320 321        .. versionadded:: 0.6322        """323        if self.is_sequence:324            # if we need a mutable object, we ensure it's a list.325            if mutable and not isinstance(self.response, list):326                self.response = list(self.response)  # type: ignore327            return328        if self.direct_passthrough:329            raise RuntimeError(330                "Attempted implicit sequence conversion but the"331                " response object is in direct passthrough mode."332            )333        if not self.implicit_sequence_conversion:334            raise RuntimeError(335                "The response object required the iterable to be a"336                " sequence, but the implicit conversion was disabled."337                " Call make_sequence() yourself."338            )339        self.make_sequence()340 341    def make_sequence(self) -> None:342        """Converts the response iterator in a list.  By default this happens343        automatically if required.  If `implicit_sequence_conversion` is344        disabled, this method is not automatically called and some properties345        might raise exceptions.  This also encodes all the items.346 347        .. versionadded:: 0.6348        """349        if not self.is_sequence:350            # if we consume an iterable we have to ensure that the close351            # method of the iterable is called if available when we tear352            # down the response353            close = getattr(self.response, "close", None)354            self.response = list(self.iter_encoded())355            if close is not None:356                self.call_on_close(close)357 358    def iter_encoded(self) -> t.Iterator[bytes]:359        """Iter the response encoded with the encoding of the response.360        If the response object is invoked as WSGI application the return361        value of this method is used as application iterator unless362        :attr:`direct_passthrough` was activated.363        """364        # Encode in a separate function so that self.response is fetched365        # early.  This allows us to wrap the response with the return366        # value from get_app_iter or iter_encoded.367        return _iter_encoded(self.response)368 369    @property370    def is_streamed(self) -> bool:371        """If the response is streamed (the response is not an iterable with372        a length information) this property is `True`.  In this case streamed373        means that there is no information about the number of iterations.374        This is usually `True` if a generator is passed to the response object.375 376        This is useful for checking before applying some sort of post377        filtering that should not take place for streamed responses.378        """379        try:380            len(self.response)  # type: ignore381        except (TypeError, AttributeError):382            return True383        return False384 385    @property386    def is_sequence(self) -> bool:387        """If the iterator is buffered, this property will be `True`.  A388        response object will consider an iterator to be buffered if the389        response attribute is a list or tuple.390 391        .. versionadded:: 0.6392        """393        return isinstance(self.response, (tuple, list))394 395    def close(self) -> None:396        """Close the wrapped response if possible.  You can also use the object397        in a with statement which will automatically close it.398 399        .. versionadded:: 0.9400           Can now be used in a with statement.401        """402        if hasattr(self.response, "close"):403            self.response.close()404        for func in self._on_close:405            func()406 407    def __enter__(self) -> Response:408        return self409 410    def __exit__(self, exc_type, exc_value, tb):  # type: ignore411        self.close()412 413    def freeze(self) -> None:414        """Make the response object ready to be pickled. Does the415        following:416 417        *   Buffer the response into a list, ignoring418            :attr:`implicity_sequence_conversion` and419            :attr:`direct_passthrough`.420        *   Set the ``Content-Length`` header.421        *   Generate an ``ETag`` header if one is not already set.422 423        .. versionchanged:: 2.1424            Removed the ``no_etag`` parameter.425 426        .. versionchanged:: 2.0427            An ``ETag`` header is always added.428 429        .. versionchanged:: 0.6430            The ``Content-Length`` header is set.431        """432        # Always freeze the encoded response body, ignore433        # implicit_sequence_conversion and direct_passthrough.434        self.response = list(self.iter_encoded())435        self.headers["Content-Length"] = str(sum(map(len, self.response)))436        self.add_etag()437 438    def get_wsgi_headers(self, environ: WSGIEnvironment) -> Headers:439        """This is automatically called right before the response is started440        and returns headers modified for the given environment.  It returns a441        copy of the headers from the response with some modifications applied442        if necessary.443 444        For example the location header (if present) is joined with the root445        URL of the environment.  Also the content length is automatically set446        to zero here for certain status codes.447 448        .. versionchanged:: 0.6449           Previously that function was called `fix_headers` and modified450           the response object in place.  Also since 0.6, IRIs in location451           and content-location headers are handled properly.452 453           Also starting with 0.6, Werkzeug will attempt to set the content454           length if it is able to figure it out on its own.  This is the455           case if all the strings in the response iterable are already456           encoded and the iterable is buffered.457 458        :param environ: the WSGI environment of the request.459        :return: returns a new :class:`~werkzeug.datastructures.Headers`460                 object.461        """462        headers = Headers(self.headers)463        location: str | None = None464        content_location: str | None = None465        content_length: str | int | None = None466        status = self.status_code467 468        # iterate over the headers to find all values in one go.  Because469        # get_wsgi_headers is used each response that gives us a tiny470        # speedup.471        for key, value in headers:472            ikey = key.lower()473            if ikey == "location":474                location = value475            elif ikey == "content-location":476                content_location = value477            elif ikey == "content-length":478                content_length = value479 480        if location is not None:481            location = iri_to_uri(location)482 483            if self.autocorrect_location_header:484                # Make the location header an absolute URL.485                current_url = get_current_url(environ, strip_querystring=True)486                current_url = iri_to_uri(current_url)487                location = urljoin(current_url, location)488 489            headers["Location"] = location490 491        # make sure the content location is a URL492        if content_location is not None:493            headers["Content-Location"] = iri_to_uri(content_location)494 495        if 100 <= status < 200 or status == 204:496            # Per section 3.3.2 of RFC 7230, "a server MUST NOT send a497            # Content-Length header field in any response with a status498            # code of 1xx (Informational) or 204 (No Content)."499            headers.remove("Content-Length")500        elif status == 304:501            remove_entity_headers(headers)502 503        # if we can determine the content length automatically, we504        # should try to do that.  But only if this does not involve505        # flattening the iterator or encoding of strings in the506        # response. We however should not do that if we have a 304507        # response.508        if (509            self.automatically_set_content_length510            and self.is_sequence511            and content_length is None512            and status not in (204, 304)513            and not (100 <= status < 200)514        ):515            content_length = sum(len(x) for x in self.iter_encoded())516            headers["Content-Length"] = str(content_length)517 518        return headers519 520    def get_app_iter(self, environ: WSGIEnvironment) -> t.Iterable[bytes]:521        """Returns the application iterator for the given environ.  Depending522        on the request method and the current status code the return value523        might be an empty response rather than the one from the response.524 525        If the request method is `HEAD` or the status code is in a range526        where the HTTP specification requires an empty response, an empty527        iterable is returned.528 529        .. versionadded:: 0.6530 531        :param environ: the WSGI environment of the request.532        :return: a response iterable.533        """534        status = self.status_code535        if (536            environ["REQUEST_METHOD"] == "HEAD"537            or 100 <= status < 200538            or status in (204, 304)539        ):540            iterable: t.Iterable[bytes] = ()541        elif self.direct_passthrough:542            return self.response  # type: ignore543        else:544            iterable = self.iter_encoded()545        return ClosingIterator(iterable, self.close)546 547    def get_wsgi_response(548        self, environ: WSGIEnvironment549    ) -> tuple[t.Iterable[bytes], str, list[tuple[str, str]]]:550        """Returns the final WSGI response as tuple.  The first item in551        the tuple is the application iterator, the second the status and552        the third the list of headers.  The response returned is created553        specially for the given environment.  For example if the request554        method in the WSGI environment is ``'HEAD'`` the response will555        be empty and only the headers and status code will be present.556 557        .. versionadded:: 0.6558 559        :param environ: the WSGI environment of the request.560        :return: an ``(app_iter, status, headers)`` tuple.561        """562        headers = self.get_wsgi_headers(environ)563        app_iter = self.get_app_iter(environ)564        return app_iter, self.status, headers.to_wsgi_list()565 566    def __call__(567        self, environ: WSGIEnvironment, start_response: StartResponse568    ) -> t.Iterable[bytes]:569        """Process this response as WSGI application.570 571        :param environ: the WSGI environment.572        :param start_response: the response callable provided by the WSGI573                               server.574        :return: an application iterator575        """576        app_iter, status, headers = self.get_wsgi_response(environ)577        start_response(status, headers)578        return app_iter579 580    # JSON581 582    #: A module or other object that has ``dumps`` and ``loads``583    #: functions that match the API of the built-in :mod:`json` module.584    json_module = json585 586    @property587    def json(self) -> t.Any | None:588        """The parsed JSON data if :attr:`mimetype` indicates JSON589        (:mimetype:`application/json`, see :attr:`is_json`).590 591        Calls :meth:`get_json` with default arguments.592        """593        return self.get_json()594 595    @t.overload596    def get_json(self, force: bool = ..., silent: t.Literal[False] = ...) -> t.Any: ...597 598    @t.overload599    def get_json(self, force: bool = ..., silent: bool = ...) -> t.Any | None: ...600 601    def get_json(self, force: bool = False, silent: bool = False) -> t.Any | None:602        """Parse :attr:`data` as JSON. Useful during testing.603 604        If the mimetype does not indicate JSON605        (:mimetype:`application/json`, see :attr:`is_json`), this606        returns ``None``.607 608        Unlike :meth:`Request.get_json`, the result is not cached.609 610        :param force: Ignore the mimetype and always try to parse JSON.611        :param silent: Silence parsing errors and return ``None``612            instead.613        """614        if not (force or self.is_json):615            return None616 617        data = self.get_data()618 619        try:620            return self.json_module.loads(data)621        except ValueError:622            if not silent:623                raise624 625            return None626 627    # Stream628 629    @cached_property630    def stream(self) -> ResponseStream:631        """The response iterable as write-only stream."""632        return ResponseStream(self)633 634    def _wrap_range_response(self, start: int, length: int) -> None:635        """Wrap existing Response in case of Range Request context."""636        if self.status_code == 206:637            self.response = _RangeWrapper(self.response, start, length)  # type: ignore638 639    def _is_range_request_processable(self, environ: WSGIEnvironment) -> bool:640        """Return ``True`` if `Range` header is present and if underlying641        resource is considered unchanged when compared with `If-Range` header.642        """643        return (644            "HTTP_IF_RANGE" not in environ645            or not is_resource_modified(646                environ,647                self.headers.get("etag"),648                None,649                self.headers.get("last-modified"),650                ignore_if_range=False,651            )652        ) and "HTTP_RANGE" in environ653 654    def _process_range_request(655        self,656        environ: WSGIEnvironment,657        complete_length: int | None,658        accept_ranges: bool | str,659    ) -> bool:660        """Handle Range Request related headers (RFC7233).  If `Accept-Ranges`661        header is valid, and Range Request is processable, we set the headers662        as described by the RFC, and wrap the underlying response in a663        RangeWrapper.664 665        Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise.666 667        :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`668                 if `Range` header could not be parsed or satisfied.669 670        .. versionchanged:: 3.2671            Adds the ``Accept-Ranges`` header if ``accept_ranges`` is passed,672            even if this is not a satisfiable range request.673 674        .. versionchanged:: 2.0675            Returns ``False`` if the length is 0.676        """677        from ..exceptions import RequestedRangeNotSatisfiable678 679        if not accept_ranges:680            return False681 682        if accept_ranges is True:683            accept_ranges = "bytes"684 685        self.accept_ranges = accept_ranges686 687        if not (complete_length and self._is_range_request_processable(environ)):688            return False689 690        parsed_range = parse_range_header(environ.get("HTTP_RANGE"))691 692        if parsed_range is None:693            raise RequestedRangeNotSatisfiable(complete_length)694 695        range_tuple = parsed_range.range_for_length(complete_length)696        content_range_header = parsed_range.to_content_range_header(complete_length)697 698        if range_tuple is None or content_range_header is None:699            raise RequestedRangeNotSatisfiable(complete_length)700 701        content_length = range_tuple[1] - range_tuple[0]702        self.content_length = content_length703        self.content_range = content_range_header704        self.status_code = 206705        self._wrap_range_response(range_tuple[0], content_length)706        return True707 708    def make_conditional(709        self,710        request_or_environ: WSGIEnvironment | Request,711        accept_ranges: bool | str = False,712        complete_length: int | None = None,713    ) -> Response:714        """Make the response conditional to the request.  This method works715        best if an etag was defined for the response already.  The `add_etag`716        method can be used to do that.  If called without etag just the date717        header is set.718 719        This does nothing if the request method in the request or environ is720        anything but GET or HEAD.721 722        For optimal performance when handling range requests, it's recommended723        that your response data object implements `seekable`, `seek` and `tell`724        methods as described by :py:class:`io.IOBase`.  Objects returned by725        :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods.726 727        It does not remove the body of the response because that's something728        the :meth:`__call__` function does for us automatically.729 730        Returns self so that you can do ``return resp.make_conditional(req)``731        but modifies the object in-place.732 733        :param request_or_environ: a request object or WSGI environment to be734                                   used to make the response conditional735                                   against.736        :param accept_ranges: This parameter dictates the value of737                              `Accept-Ranges` header. If ``False`` (default),738                              the header is not set. If ``True``, it will be set739                              to ``"bytes"``. If it's a string, it will use this740                              value.741        :param complete_length: Will be used only in valid Range Requests.742                                It will set `Content-Range` complete length743                                value and compute `Content-Length` real value.744                                This parameter is mandatory for successful745                                Range Requests completion.746        :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`747                 if `Range` header could not be parsed or satisfied.748 749        .. versionchangedd: 3.2750            Adds the ``Accept-Ranges`` header if ``accept_ranges`` is passed,751            even if this is not a satisfiable range request.752 753        .. versionchanged:: 2.0754            Range processing is skipped if length is 0 instead of755            raising a 416 Range Not Satisfiable error.756        """757        environ = _get_environ(request_or_environ)758        if environ["REQUEST_METHOD"] in ("GET", "HEAD"):759            # if the date is not in the headers, add it now.  We however760            # will not override an already existing header.  Unfortunately761            # this header will be overridden by many WSGI servers including762            # wsgiref.763            if "date" not in self.headers:764                self.headers["Date"] = http_date()765            is206 = self._process_range_request(environ, complete_length, accept_ranges)766            if not is206 and not is_resource_modified(767                environ,768                self.headers.get("etag"),769                None,770                self.headers.get("last-modified"),771            ):772                if parse_etags(environ.get("HTTP_IF_MATCH")):773                    self.status_code = 412774                else:775                    self.status_code = 304776            if (777                self.automatically_set_content_length778                and "content-length" not in self.headers779            ):780                length = self.calculate_content_length()781                if length is not None:782                    self.headers["Content-Length"] = str(length)783        return self784 785    def add_etag(self, overwrite: bool = False, weak: bool = False) -> None:786        """Add an etag for the current response if there is none yet.787 788        .. versionchanged:: 2.0789            SHA-1 is used to generate the value. MD5 may not be790            available in some environments.791        """792        if overwrite or "etag" not in self.headers:793            self.set_etag(generate_etag(self.get_data()), weak)794 795 796class ResponseStream:797    """A file descriptor like object used by :meth:`Response.stream` to798    represent the body of the stream. It directly pushes into the799    response iterable of the response object.800    """801 802    mode = "wb+"803 804    def __init__(self, response: Response):805        self.response = response806        self.closed = False807 808    def write(self, value: bytes) -> int:809        if self.closed:810            raise ValueError("I/O operation on closed file")811        self.response._ensure_sequence(mutable=True)812        self.response.response.append(value)  # type: ignore813        self.response.headers.pop("Content-Length", None)814        return len(value)815 816    def writelines(self, seq: t.Iterable[bytes]) -> None:817        for item in seq:818            self.write(item)819 820    def close(self) -> None:821        self.closed = True822 823    def flush(self) -> None:824        if self.closed:825            raise ValueError("I/O operation on closed file")826 827    def isatty(self) -> bool:828        if self.closed:829            raise ValueError("I/O operation on closed file")830        return False831 832    def tell(self) -> int:833        self.response._ensure_sequence()834        return sum(map(len, self.response.response))835 836    @property837    def encoding(self) -> str:838        return "utf-8"839 
codekingpro/portable-devtools · Team Ai