Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
testing.py299 linesDownload Raw Back to flask
1from __future__ import annotations2 3import importlib.metadata4import typing as t5from contextlib import contextmanager6from contextlib import ExitStack7from copy import copy8from types import TracebackType9from urllib.parse import urlsplit10 11import werkzeug.test12from click.testing import CliRunner13from click.testing import Result14from werkzeug.test import Client15from werkzeug.wrappers import Request as BaseRequest16 17from .cli import ScriptInfo18from .sessions import SessionMixin19 20if t.TYPE_CHECKING:  # pragma: no cover21    from _typeshed.wsgi import WSGIEnvironment22    from werkzeug.test import TestResponse23 24    from .app import Flask25 26 27class EnvironBuilder(werkzeug.test.EnvironBuilder):28    """An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the29    application.30 31    :param app: The Flask application to configure the environment from.32    :param path: URL path being requested.33    :param base_url: Base URL where the app is being served, which34        ``path`` is relative to. If not given, built from35        :data:`PREFERRED_URL_SCHEME`, ``subdomain``,36        :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.37    :param subdomain: Subdomain name to append to :data:`SERVER_NAME`.38    :param url_scheme: Scheme to use instead of39        :data:`PREFERRED_URL_SCHEME`.40    :param json: If given, this is serialized as JSON and passed as41        ``data``. Also defaults ``content_type`` to42        ``application/json``.43    :param args: other positional arguments passed to44        :class:`~werkzeug.test.EnvironBuilder`.45    :param kwargs: other keyword arguments passed to46        :class:`~werkzeug.test.EnvironBuilder`.47    """48 49    def __init__(50        self,51        app: Flask,52        path: str = "/",53        base_url: str | None = None,54        subdomain: str | None = None,55        url_scheme: str | None = None,56        *args: t.Any,57        **kwargs: t.Any,58    ) -> None:59        assert not (base_url or subdomain or url_scheme) or (60            base_url is not None61        ) != bool(subdomain or url_scheme), (62            'Cannot pass "subdomain" or "url_scheme" with "base_url".'63        )64 65        if base_url is None:66            http_host = app.config.get("SERVER_NAME") or "localhost"67            app_root = app.config["APPLICATION_ROOT"]68 69            if subdomain:70                http_host = f"{subdomain}.{http_host}"71 72            if url_scheme is None:73                url_scheme = app.config["PREFERRED_URL_SCHEME"]74 75            url = urlsplit(path)76            base_url = (77                f"{url.scheme or url_scheme}://{url.netloc or http_host}"78                f"/{app_root.lstrip('/')}"79            )80            path = url.path81 82            if url.query:83                path = f"{path}?{url.query}"84 85        self.app = app86        super().__init__(path, base_url, *args, **kwargs)87 88    def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str:89        """Serialize ``obj`` to a JSON-formatted string.90 91        The serialization will be configured according to the config associated92        with this EnvironBuilder's ``app``.93        """94        return self.app.json.dumps(obj, **kwargs)95 96 97_werkzeug_version = ""98 99 100def _get_werkzeug_version() -> str:101    global _werkzeug_version102 103    if not _werkzeug_version:104        _werkzeug_version = importlib.metadata.version("werkzeug")105 106    return _werkzeug_version107 108 109class FlaskClient(Client):110    """Works like a regular Werkzeug test client but has knowledge about111    Flask's contexts to defer the cleanup of the request context until112    the end of a ``with`` block. For general information about how to113    use this class refer to :class:`werkzeug.test.Client`.114 115    .. versionchanged:: 0.12116       `app.test_client()` includes preset default environment, which can be117       set after instantiation of the `app.test_client()` object in118       `client.environ_base`.119 120    Basic usage is outlined in the :doc:`/testing` chapter.121    """122 123    application: Flask124 125    def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:126        super().__init__(*args, **kwargs)127        self.preserve_context = False128        self._new_contexts: list[t.ContextManager[t.Any]] = []129        self._context_stack = ExitStack()130        self.environ_base = {131            "REMOTE_ADDR": "127.0.0.1",132            "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}",133        }134 135    @contextmanager136    def session_transaction(137        self, *args: t.Any, **kwargs: t.Any138    ) -> t.Iterator[SessionMixin]:139        """When used in combination with a ``with`` statement this opens a140        session transaction.  This can be used to modify the session that141        the test client uses.  Once the ``with`` block is left the session is142        stored back.143 144        ::145 146            with client.session_transaction() as session:147                session['value'] = 42148 149        Internally this is implemented by going through a temporary test150        request context and since session handling could depend on151        request variables this function accepts the same arguments as152        :meth:`~flask.Flask.test_request_context` which are directly153        passed through.154        """155        if self._cookies is None:156            raise TypeError(157                "Cookies are disabled. Create a client with 'use_cookies=True'."158            )159 160        app = self.application161        ctx = app.test_request_context(*args, **kwargs)162        self._add_cookies_to_wsgi(ctx.request.environ)163 164        with ctx:165            sess = app.session_interface.open_session(app, ctx.request)166 167        if sess is None:168            raise RuntimeError("Session backend did not open a session.")169 170        yield sess171        resp = app.response_class()172 173        if app.session_interface.is_null_session(sess):174            return175 176        with ctx:177            app.session_interface.save_session(app, sess, resp)178 179        self._update_cookies_from_response(180            ctx.request.host.partition(":")[0],181            ctx.request.path,182            resp.headers.getlist("Set-Cookie"),183        )184 185    def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment:186        out = {**self.environ_base, **other}187 188        if self.preserve_context:189            out["werkzeug.debug.preserve_context"] = self._new_contexts.append190 191        return out192 193    def _request_from_builder_args(194        self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]195    ) -> BaseRequest:196        kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {}))197        builder = EnvironBuilder(self.application, *args, **kwargs)198 199        try:200            return builder.get_request()201        finally:202            builder.close()203 204    def open(205        self,206        *args: t.Any,207        buffered: bool = False,208        follow_redirects: bool = False,209        **kwargs: t.Any,210    ) -> TestResponse:211        if args and isinstance(212            args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest)213        ):214            if isinstance(args[0], werkzeug.test.EnvironBuilder):215                builder = copy(args[0])216                builder.environ_base = self._copy_environ(builder.environ_base or {})  # type: ignore[arg-type]217                request = builder.get_request()218            elif isinstance(args[0], dict):219                request = EnvironBuilder.from_environ(220                    args[0], app=self.application, environ_base=self._copy_environ({})221                ).get_request()222            else:223                # isinstance(args[0], BaseRequest)224                request = copy(args[0])225                request.environ = self._copy_environ(request.environ)226        else:227            # request is None228            request = self._request_from_builder_args(args, kwargs)229 230        # Pop any previously preserved contexts. This prevents contexts231        # from being preserved across redirects or multiple requests232        # within a single block.233        self._context_stack.close()234 235        response = super().open(236            request,237            buffered=buffered,238            follow_redirects=follow_redirects,239        )240        response.json_module = self.application.json  # type: ignore[assignment]241 242        # Re-push contexts that were preserved during the request.243        for cm in self._new_contexts:244            self._context_stack.enter_context(cm)245 246        self._new_contexts.clear()247        return response248 249    def __enter__(self) -> FlaskClient:250        if self.preserve_context:251            raise RuntimeError("Cannot nest client invocations")252        self.preserve_context = True253        return self254 255    def __exit__(256        self,257        exc_type: type | None,258        exc_value: BaseException | None,259        tb: TracebackType | None,260    ) -> None:261        self.preserve_context = False262        self._context_stack.close()263 264 265class FlaskCliRunner(CliRunner):266    """A :class:`~click.testing.CliRunner` for testing a Flask app's267    CLI commands. Typically created using268    :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.269    """270 271    def __init__(self, app: Flask, **kwargs: t.Any) -> None:272        self.app = app273        super().__init__(**kwargs)274 275    def invoke(  # type: ignore276        self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any277    ) -> Result:278        """Invokes a CLI command in an isolated environment. See279        :meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for280        full method documentation. See :ref:`testing-cli` for examples.281 282        If the ``obj`` argument is not given, passes an instance of283        :class:`~flask.cli.ScriptInfo` that knows how to load the Flask284        app being tested.285 286        :param cli: Command object to invoke. Default is the app's287            :attr:`~flask.app.Flask.cli` group.288        :param args: List of strings to invoke the command with.289 290        :return: a :class:`~click.testing.Result` object.291        """292        if cli is None:293            cli = self.app.cli294 295        if "obj" not in kwargs:296            kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)297 298        return super().invoke(cli, args, **kwargs)299 
codekingpro/portable-devtools · Team Ai