Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
cli.py1110 linesDownload Raw Back to flask
1from __future__ import annotations2 3import ast4import collections.abc as cabc5import importlib.metadata6import inspect7import os8import platform9import re10import sys11import traceback12import typing as t13from functools import update_wrapper14from operator import itemgetter15from types import ModuleType16 17import click18from click.core import ParameterSource19from werkzeug import run_simple20from werkzeug.serving import is_running_from_reloader21from werkzeug.utils import import_string22 23from .globals import current_app24from .helpers import get_debug_flag25from .helpers import get_load_dotenv26 27if t.TYPE_CHECKING:28    import ssl29 30    from _typeshed.wsgi import StartResponse31    from _typeshed.wsgi import WSGIApplication32    from _typeshed.wsgi import WSGIEnvironment33 34    from .app import Flask35 36 37class NoAppException(click.UsageError):38    """Raised if an application cannot be found or loaded."""39 40 41def find_best_app(module: ModuleType) -> Flask:42    """Given a module instance this tries to find the best possible43    application in the module or raises an exception.44    """45    from . import Flask46 47    # Search for the most common names first.48    for attr_name in ("app", "application"):49        app = getattr(module, attr_name, None)50 51        if isinstance(app, Flask):52            return app53 54    # Otherwise find the only object that is a Flask instance.55    matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]56 57    if len(matches) == 1:58        return matches[0]59    elif len(matches) > 1:60        raise NoAppException(61            "Detected multiple Flask applications in module"62            f" '{module.__name__}'. Use '{module.__name__}:name'"63            " to specify the correct one."64        )65 66    # Search for app factory functions.67    for attr_name in ("create_app", "make_app"):68        app_factory = getattr(module, attr_name, None)69 70        if inspect.isfunction(app_factory):71            try:72                app = app_factory()73 74                if isinstance(app, Flask):75                    return app76            except TypeError as e:77                if not _called_with_wrong_args(app_factory):78                    raise79 80                raise NoAppException(81                    f"Detected factory '{attr_name}' in module '{module.__name__}',"82                    " but could not call it without arguments. Use"83                    f" '{module.__name__}:{attr_name}(args)'"84                    " to specify arguments."85                ) from e86 87    raise NoAppException(88        "Failed to find Flask application or factory in module"89        f" '{module.__name__}'. Use '{module.__name__}:name'"90        " to specify one."91    )92 93 94def _called_with_wrong_args(f: t.Callable[..., Flask]) -> bool:95    """Check whether calling a function raised a ``TypeError`` because96    the call failed or because something in the factory raised the97    error.98 99    :param f: The function that was called.100    :return: ``True`` if the call failed.101    """102    tb = sys.exc_info()[2]103 104    try:105        while tb is not None:106            if tb.tb_frame.f_code is f.__code__:107                # In the function, it was called successfully.108                return False109 110            tb = tb.tb_next111 112        # Didn't reach the function.113        return True114    finally:115        # Delete tb to break a circular reference.116        # https://docs.python.org/2/library/sys.html#sys.exc_info117        del tb118 119 120def find_app_by_string(module: ModuleType, app_name: str) -> Flask:121    """Check if the given string is a variable name or a function. Call122    a function to get the app instance, or return the variable directly.123    """124    from . import Flask125 126    # Parse app_name as a single expression to determine if it's a valid127    # attribute name or function call.128    try:129        expr = ast.parse(app_name.strip(), mode="eval").body130    except SyntaxError:131        raise NoAppException(132            f"Failed to parse {app_name!r} as an attribute name or function call."133        ) from None134 135    if isinstance(expr, ast.Name):136        name = expr.id137        args = []138        kwargs = {}139    elif isinstance(expr, ast.Call):140        # Ensure the function name is an attribute name only.141        if not isinstance(expr.func, ast.Name):142            raise NoAppException(143                f"Function reference must be a simple name: {app_name!r}."144            )145 146        name = expr.func.id147 148        # Parse the positional and keyword arguments as literals.149        try:150            args = [ast.literal_eval(arg) for arg in expr.args]151            kwargs = {152                kw.arg: ast.literal_eval(kw.value)153                for kw in expr.keywords154                if kw.arg is not None155            }156        except ValueError:157            # literal_eval gives cryptic error messages, show a generic158            # message with the full expression instead.159            raise NoAppException(160                f"Failed to parse arguments as literal values: {app_name!r}."161            ) from None162    else:163        raise NoAppException(164            f"Failed to parse {app_name!r} as an attribute name or function call."165        )166 167    try:168        attr = getattr(module, name)169    except AttributeError as e:170        raise NoAppException(171            f"Failed to find attribute {name!r} in {module.__name__!r}."172        ) from e173 174    # If the attribute is a function, call it with any args and kwargs175    # to get the real application.176    if inspect.isfunction(attr):177        try:178            app = attr(*args, **kwargs)179        except TypeError as e:180            if not _called_with_wrong_args(attr):181                raise182 183            raise NoAppException(184                f"The factory {app_name!r} in module"185                f" {module.__name__!r} could not be called with the"186                " specified arguments."187            ) from e188    else:189        app = attr190 191    if isinstance(app, Flask):192        return app193 194    raise NoAppException(195        "A valid Flask application was not obtained from"196        f" '{module.__name__}:{app_name}'."197    )198 199 200def prepare_import(path: str) -> str:201    """Given a filename this will try to calculate the python path, add it202    to the search path and return the actual module name that is expected.203    """204    path = os.path.realpath(path)205 206    fname, ext = os.path.splitext(path)207    if ext == ".py":208        path = fname209 210    if os.path.basename(path) == "__init__":211        path = os.path.dirname(path)212 213    module_name = []214 215    # move up until outside package structure (no __init__.py)216    while True:217        path, name = os.path.split(path)218        module_name.append(name)219 220        if not os.path.exists(os.path.join(path, "__init__.py")):221            break222 223    if sys.path[0] != path:224        sys.path.insert(0, path)225 226    return ".".join(module_name[::-1])227 228 229@t.overload230def locate_app(231    module_name: str, app_name: str | None, raise_if_not_found: t.Literal[True] = True232) -> Flask: ...233 234 235@t.overload236def locate_app(237    module_name: str, app_name: str | None, raise_if_not_found: t.Literal[False] = ...238) -> Flask | None: ...239 240 241def locate_app(242    module_name: str, app_name: str | None, raise_if_not_found: bool = True243) -> Flask | None:244    try:245        __import__(module_name)246    except ImportError:247        # Reraise the ImportError if it occurred within the imported module.248        # Determine this by checking whether the trace has a depth > 1.249        if sys.exc_info()[2].tb_next:  # type: ignore[union-attr]250            raise NoAppException(251                f"While importing {module_name!r}, an ImportError was"252                f" raised:\n\n{traceback.format_exc()}"253            ) from None254        elif raise_if_not_found:255            raise NoAppException(f"Could not import {module_name!r}.") from None256        else:257            return None258 259    module = sys.modules[module_name]260 261    if app_name is None:262        return find_best_app(module)263    else:264        return find_app_by_string(module, app_name)265 266 267def get_version(ctx: click.Context, param: click.Parameter, value: t.Any) -> None:268    if not value or ctx.resilient_parsing:269        return270 271    flask_version = importlib.metadata.version("flask")272    werkzeug_version = importlib.metadata.version("werkzeug")273 274    click.echo(275        f"Python {platform.python_version()}\n"276        f"Flask {flask_version}\n"277        f"Werkzeug {werkzeug_version}",278        color=ctx.color,279    )280    ctx.exit()281 282 283version_option = click.Option(284    ["--version"],285    help="Show the Flask version.",286    expose_value=False,287    callback=get_version,288    is_flag=True,289    is_eager=True,290)291 292 293class ScriptInfo:294    """Helper object to deal with Flask applications.  This is usually not295    necessary to interface with as it's used internally in the dispatching296    to click.  In future versions of Flask this object will most likely play297    a bigger role.  Typically it's created automatically by the298    :class:`FlaskGroup` but you can also manually create it and pass it299    onwards as click object.300    """301 302    def __init__(303        self,304        app_import_path: str | None = None,305        create_app: t.Callable[..., Flask] | None = None,306        set_debug_flag: bool = True,307    ) -> None:308        #: Optionally the import path for the Flask application.309        self.app_import_path = app_import_path310        #: Optionally a function that is passed the script info to create311        #: the instance of the application.312        self.create_app = create_app313        #: A dictionary with arbitrary data that can be associated with314        #: this script info.315        self.data: dict[t.Any, t.Any] = {}316        self.set_debug_flag = set_debug_flag317        self._loaded_app: Flask | None = None318 319    def load_app(self) -> Flask:320        """Loads the Flask app (if not yet loaded) and returns it.  Calling321        this multiple times will just result in the already loaded app to322        be returned.323        """324        if self._loaded_app is not None:325            return self._loaded_app326 327        if self.create_app is not None:328            app: Flask | None = self.create_app()329        else:330            if self.app_import_path:331                path, name = (332                    re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None]333                )[:2]334                import_name = prepare_import(path)335                app = locate_app(import_name, name)336            else:337                for path in ("wsgi.py", "app.py"):338                    import_name = prepare_import(path)339                    app = locate_app(import_name, None, raise_if_not_found=False)340 341                    if app is not None:342                        break343 344        if app is None:345            raise NoAppException(346                "Could not locate a Flask application. Use the"347                " 'flask --app' option, 'FLASK_APP' environment"348                " variable, or a 'wsgi.py' or 'app.py' file in the"349                " current directory."350            )351 352        if self.set_debug_flag:353            # Update the app's debug flag through the descriptor so that354            # other values repopulate as well.355            app.debug = get_debug_flag()356 357        self._loaded_app = app358        return app359 360 361pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)362 363F = t.TypeVar("F", bound=t.Callable[..., t.Any])364 365 366def with_appcontext(f: F) -> F:367    """Wraps a callback so that it's guaranteed to be executed with the368    script's application context.369 370    Custom commands (and their options) registered under ``app.cli`` or371    ``blueprint.cli`` will always have an app context available, this372    decorator is not required in that case.373 374    .. versionchanged:: 2.2375        The app context is active for subcommands as well as the376        decorated callback. The app context is always available to377        ``app.cli`` command and parameter callbacks.378    """379 380    @click.pass_context381    def decorator(ctx: click.Context, /, *args: t.Any, **kwargs: t.Any) -> t.Any:382        if not current_app:383            app = ctx.ensure_object(ScriptInfo).load_app()384            ctx.with_resource(app.app_context())385 386        return ctx.invoke(f, *args, **kwargs)387 388    return update_wrapper(decorator, f)  # type: ignore[return-value]389 390 391class AppGroup(click.Group):392    """This works similar to a regular click :class:`~click.Group` but it393    changes the behavior of the :meth:`command` decorator so that it394    automatically wraps the functions in :func:`with_appcontext`.395 396    Not to be confused with :class:`FlaskGroup`.397    """398 399    def command(  # type: ignore[override]400        self, *args: t.Any, **kwargs: t.Any401    ) -> t.Callable[[t.Callable[..., t.Any]], click.Command]:402        """This works exactly like the method of the same name on a regular403        :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`404        unless it's disabled by passing ``with_appcontext=False``.405        """406        wrap_for_ctx = kwargs.pop("with_appcontext", True)407 408        def decorator(f: t.Callable[..., t.Any]) -> click.Command:409            if wrap_for_ctx:410                f = with_appcontext(f)411            return super(AppGroup, self).command(*args, **kwargs)(f)  # type: ignore[no-any-return]412 413        return decorator414 415    def group(  # type: ignore[override]416        self, *args: t.Any, **kwargs: t.Any417    ) -> t.Callable[[t.Callable[..., t.Any]], click.Group]:418        """This works exactly like the method of the same name on a regular419        :class:`click.Group` but it defaults the group class to420        :class:`AppGroup`.421        """422        kwargs.setdefault("cls", AppGroup)423        return super().group(*args, **kwargs)  # type: ignore[no-any-return]424 425 426def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:427    if value is None:428        return None429 430    info = ctx.ensure_object(ScriptInfo)431    info.app_import_path = value432    return value433 434 435# This option is eager so the app will be available if --help is given.436# --help is also eager, so --app must be before it in the param list.437# no_args_is_help bypasses eager processing, so this option must be438# processed manually in that case to ensure FLASK_APP gets picked up.439_app_option = click.Option(440    ["-A", "--app"],441    metavar="IMPORT",442    help=(443        "The Flask application or factory function to load, in the form 'module:name'."444        " Module can be a dotted import or file path. Name is not required if it is"445        " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"446        " pass arguments."447    ),448    is_eager=True,449    expose_value=False,450    callback=_set_app,451)452 453 454def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:455    # If the flag isn't provided, it will default to False. Don't use456    # that, let debug be set by env in that case.457    source = ctx.get_parameter_source(param.name)  # type: ignore[arg-type]458 459    if source is not None and source in (460        ParameterSource.DEFAULT,461        ParameterSource.DEFAULT_MAP,462    ):463        return None464 465    # Set with env var instead of ScriptInfo.load so that it can be466    # accessed early during a factory function.467    os.environ["FLASK_DEBUG"] = "1" if value else "0"468    return value469 470 471_debug_option = click.Option(472    ["--debug/--no-debug"],473    help="Set debug mode.",474    expose_value=False,475    callback=_set_debug,476)477 478 479def _env_file_callback(480    ctx: click.Context, param: click.Option, value: str | None481) -> str | None:482    if value is None:483        return None484 485    import importlib486 487    try:488        importlib.import_module("dotenv")489    except ImportError:490        raise click.BadParameter(491            "python-dotenv must be installed to load an env file.",492            ctx=ctx,493            param=param,494        ) from None495 496    # Don't check FLASK_SKIP_DOTENV, that only disables automatically497    # loading .env and .flaskenv files.498    load_dotenv(value)499    return value500 501 502# This option is eager so env vars are loaded as early as possible to be503# used by other options.504_env_file_option = click.Option(505    ["-e", "--env-file"],506    type=click.Path(exists=True, dir_okay=False),507    help="Load environment variables from this file. python-dotenv must be installed.",508    is_eager=True,509    expose_value=False,510    callback=_env_file_callback,511)512 513 514class FlaskGroup(AppGroup):515    """Special subclass of the :class:`AppGroup` group that supports516    loading more commands from the configured Flask app.  Normally a517    developer does not have to interface with this class but there are518    some very advanced use cases for which it makes sense to create an519    instance of this. see :ref:`custom-scripts`.520 521    :param add_default_commands: if this is True then the default run and522        shell commands will be added.523    :param add_version_option: adds the ``--version`` option.524    :param create_app: an optional callback that is passed the script info and525        returns the loaded app.526    :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`527        files to set environment variables. Will also change the working528        directory to the directory containing the first file found.529    :param set_debug_flag: Set the app's debug flag.530 531    .. versionchanged:: 2.2532        Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.533 534    .. versionchanged:: 2.2535        An app context is pushed when running ``app.cli`` commands, so536        ``@with_appcontext`` is no longer required for those commands.537 538    .. versionchanged:: 1.0539        If installed, python-dotenv will be used to load environment variables540        from :file:`.env` and :file:`.flaskenv` files.541    """542 543    def __init__(544        self,545        add_default_commands: bool = True,546        create_app: t.Callable[..., Flask] | None = None,547        add_version_option: bool = True,548        load_dotenv: bool = True,549        set_debug_flag: bool = True,550        **extra: t.Any,551    ) -> None:552        params = list(extra.pop("params", None) or ())553        # Processing is done with option callbacks instead of a group554        # callback. This allows users to make a custom group callback555        # without losing the behavior. --env-file must come first so556        # that it is eagerly evaluated before --app.557        params.extend((_env_file_option, _app_option, _debug_option))558 559        if add_version_option:560            params.append(version_option)561 562        if "context_settings" not in extra:563            extra["context_settings"] = {}564 565        extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK")566 567        super().__init__(params=params, **extra)568 569        self.create_app = create_app570        self.load_dotenv = load_dotenv571        self.set_debug_flag = set_debug_flag572 573        if add_default_commands:574            self.add_command(run_command)575            self.add_command(shell_command)576            self.add_command(routes_command)577 578        self._loaded_plugin_commands = False579 580    def _load_plugin_commands(self) -> None:581        if self._loaded_plugin_commands:582            return583 584        if sys.version_info >= (3, 10):585            from importlib import metadata586        else:587            # Use a backport on Python < 3.10. We technically have588            # importlib.metadata on 3.8+, but the API changed in 3.10,589            # so use the backport for consistency.590            import importlib_metadata as metadata591 592        for ep in metadata.entry_points(group="flask.commands"):593            self.add_command(ep.load(), ep.name)594 595        self._loaded_plugin_commands = True596 597    def get_command(self, ctx: click.Context, name: str) -> click.Command | None:598        self._load_plugin_commands()599        # Look up built-in and plugin commands, which should be600        # available even if the app fails to load.601        rv = super().get_command(ctx, name)602 603        if rv is not None:604            return rv605 606        info = ctx.ensure_object(ScriptInfo)607 608        # Look up commands provided by the app, showing an error and609        # continuing if the app couldn't be loaded.610        try:611            app = info.load_app()612        except NoAppException as e:613            click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")614            return None615 616        # Push an app context for the loaded app unless it is already617        # active somehow. This makes the context available to parameter618        # and command callbacks without needing @with_appcontext.619        if not current_app or current_app._get_current_object() is not app:  # type: ignore[attr-defined]620            ctx.with_resource(app.app_context())621 622        return app.cli.get_command(ctx, name)623 624    def list_commands(self, ctx: click.Context) -> list[str]:625        self._load_plugin_commands()626        # Start with the built-in and plugin commands.627        rv = set(super().list_commands(ctx))628        info = ctx.ensure_object(ScriptInfo)629 630        # Add commands provided by the app, showing an error and631        # continuing if the app couldn't be loaded.632        try:633            rv.update(info.load_app().cli.list_commands(ctx))634        except NoAppException as e:635            # When an app couldn't be loaded, show the error message636            # without the traceback.637            click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")638        except Exception:639            # When any other errors occurred during loading, show the640            # full traceback.641            click.secho(f"{traceback.format_exc()}\n", err=True, fg="red")642 643        return sorted(rv)644 645    def make_context(646        self,647        info_name: str | None,648        args: list[str],649        parent: click.Context | None = None,650        **extra: t.Any,651    ) -> click.Context:652        # Set a flag to tell app.run to become a no-op. If app.run was653        # not in a __name__ == __main__ guard, it would start the server654        # when importing, blocking whatever command is being called.655        os.environ["FLASK_RUN_FROM_CLI"] = "true"656 657        # Attempt to load .env and .flask env files. The --env-file658        # option can cause another file to be loaded.659        if get_load_dotenv(self.load_dotenv):660            load_dotenv()661 662        if "obj" not in extra and "obj" not in self.context_settings:663            extra["obj"] = ScriptInfo(664                create_app=self.create_app, set_debug_flag=self.set_debug_flag665            )666 667        return super().make_context(info_name, args, parent=parent, **extra)668 669    def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:670        if not args and self.no_args_is_help:671            # Attempt to load --env-file and --app early in case they672            # were given as env vars. Otherwise no_args_is_help will not673            # see commands from app.cli.674            _env_file_option.handle_parse_result(ctx, {}, [])675            _app_option.handle_parse_result(ctx, {}, [])676 677        return super().parse_args(ctx, args)678 679 680def _path_is_ancestor(path: str, other: str) -> bool:681    """Take ``other`` and remove the length of ``path`` from it. Then join it682    to ``path``. If it is the original value, ``path`` is an ancestor of683    ``other``."""684    return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other685 686 687def load_dotenv(path: str | os.PathLike[str] | None = None) -> bool:688    """Load "dotenv" files in order of precedence to set environment variables.689 690    If an env var is already set it is not overwritten, so earlier files in the691    list are preferred over later files.692 693    This is a no-op if `python-dotenv`_ is not installed.694 695    .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme696 697    :param path: Load the file at this location instead of searching.698    :return: ``True`` if a file was loaded.699 700    .. versionchanged:: 2.0701        The current directory is not changed to the location of the702        loaded file.703 704    .. versionchanged:: 2.0705        When loading the env files, set the default encoding to UTF-8.706 707    .. versionchanged:: 1.1.0708        Returns ``False`` when python-dotenv is not installed, or when709        the given path isn't a file.710 711    .. versionadded:: 1.0712    """713    try:714        import dotenv715    except ImportError:716        if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):717            click.secho(718                " * Tip: There are .env or .flaskenv files present."719                ' Do "pip install python-dotenv" to use them.',720                fg="yellow",721                err=True,722            )723 724        return False725 726    # Always return after attempting to load a given path, don't load727    # the default files.728    if path is not None:729        if os.path.isfile(path):730            return dotenv.load_dotenv(path, encoding="utf-8")731 732        return False733 734    loaded = False735 736    for name in (".env", ".flaskenv"):737        path = dotenv.find_dotenv(name, usecwd=True)738 739        if not path:740            continue741 742        dotenv.load_dotenv(path, encoding="utf-8")743        loaded = True744 745    return loaded  # True if at least one file was located and loaded.746 747 748def show_server_banner(debug: bool, app_import_path: str | None) -> None:749    """Show extra startup messages the first time the server is run,750    ignoring the reloader.751    """752    if is_running_from_reloader():753        return754 755    if app_import_path is not None:756        click.echo(f" * Serving Flask app '{app_import_path}'")757 758    if debug is not None:759        click.echo(f" * Debug mode: {'on' if debug else 'off'}")760 761 762class CertParamType(click.ParamType):763    """Click option type for the ``--cert`` option. Allows either an764    existing file, the string ``'adhoc'``, or an import for a765    :class:`~ssl.SSLContext` object.766    """767 768    name = "path"769 770    def __init__(self) -> None:771        self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)772 773    def convert(774        self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None775    ) -> t.Any:776        try:777            import ssl778        except ImportError:779            raise click.BadParameter(780                'Using "--cert" requires Python to be compiled with SSL support.',781                ctx,782                param,783            ) from None784 785        try:786            return self.path_type(value, param, ctx)787        except click.BadParameter:788            value = click.STRING(value, param, ctx).lower()789 790            if value == "adhoc":791                try:792                    import cryptography  # noqa: F401793                except ImportError:794                    raise click.BadParameter(795                        "Using ad-hoc certificates requires the cryptography library.",796                        ctx,797                        param,798                    ) from None799 800                return value801 802            obj = import_string(value, silent=True)803 804            if isinstance(obj, ssl.SSLContext):805                return obj806 807            raise808 809 810def _validate_key(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any:811    """The ``--key`` option must be specified when ``--cert`` is a file.812    Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.813    """814    cert = ctx.params.get("cert")815    is_adhoc = cert == "adhoc"816 817    try:818        import ssl819    except ImportError:820        is_context = False821    else:822        is_context = isinstance(cert, ssl.SSLContext)823 824    if value is not None:825        if is_adhoc:826            raise click.BadParameter(827                'When "--cert" is "adhoc", "--key" is not used.', ctx, param828            )829 830        if is_context:831            raise click.BadParameter(832                'When "--cert" is an SSLContext object, "--key" is not used.',833                ctx,834                param,835            )836 837        if not cert:838            raise click.BadParameter('"--cert" must also be specified.', ctx, param)839 840        ctx.params["cert"] = cert, value841 842    else:843        if cert and not (is_adhoc or is_context):844            raise click.BadParameter('Required when using "--cert".', ctx, param)845 846    return value847 848 849class SeparatedPathType(click.Path):850    """Click option type that accepts a list of values separated by the851    OS's path separator (``:``, ``;`` on Windows). Each value is852    validated as a :class:`click.Path` type.853    """854 855    def convert(856        self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None857    ) -> t.Any:858        items = self.split_envvar_value(value)859        # can't call no-arg super() inside list comprehension until Python 3.12860        super_convert = super().convert861        return [super_convert(item, param, ctx) for item in items]862 863 864@click.command("run", short_help="Run a development server.")865@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.")866@click.option("--port", "-p", default=5000, help="The port to bind to.")867@click.option(868    "--cert",869    type=CertParamType(),870    help="Specify a certificate file to use HTTPS.",871    is_eager=True,872)873@click.option(874    "--key",875    type=click.Path(exists=True, dir_okay=False, resolve_path=True),876    callback=_validate_key,877    expose_value=False,878    help="The key file to use when specifying a certificate.",879)880@click.option(881    "--reload/--no-reload",882    default=None,883    help="Enable or disable the reloader. By default the reloader "884    "is active if debug is enabled.",885)886@click.option(887    "--debugger/--no-debugger",888    default=None,889    help="Enable or disable the debugger. By default the debugger "890    "is active if debug is enabled.",891)892@click.option(893    "--with-threads/--without-threads",894    default=True,895    help="Enable or disable multithreading.",896)897@click.option(898    "--extra-files",899    default=None,900    type=SeparatedPathType(),901    help=(902        "Extra files that trigger a reload on change. Multiple paths"903        f" are separated by {os.path.pathsep!r}."904    ),905)906@click.option(907    "--exclude-patterns",908    default=None,909    type=SeparatedPathType(),910    help=(911        "Files matching these fnmatch patterns will not trigger a reload"912        " on change. Multiple patterns are separated by"913        f" {os.path.pathsep!r}."914    ),915)916@pass_script_info917def run_command(918    info: ScriptInfo,919    host: str,920    port: int,921    reload: bool,922    debugger: bool,923    with_threads: bool,924    cert: ssl.SSLContext | tuple[str, str | None] | t.Literal["adhoc"] | None,925    extra_files: list[str] | None,926    exclude_patterns: list[str] | None,927) -> None:928    """Run a local development server.929 930    This server is for development purposes only. It does not provide931    the stability, security, or performance of production WSGI servers.932 933    The reloader and debugger are enabled by default with the '--debug'934    option.935    """936    try:937        app: WSGIApplication = info.load_app()938    except Exception as e:939        if is_running_from_reloader():940            # When reloading, print out the error immediately, but raise941            # it later so the debugger or server can handle it.942            traceback.print_exc()943            err = e944 945            def app(946                environ: WSGIEnvironment, start_response: StartResponse947            ) -> cabc.Iterable[bytes]:948                raise err from None949 950        else:951            # When not reloading, raise the error immediately so the952            # command fails.953            raise e from None954 955    debug = get_debug_flag()956 957    if reload is None:958        reload = debug959 960    if debugger is None:961        debugger = debug962 963    show_server_banner(debug, info.app_import_path)964 965    run_simple(966        host,967        port,968        app,969        use_reloader=reload,970        use_debugger=debugger,971        threaded=with_threads,972        ssl_context=cert,973        extra_files=extra_files,974        exclude_patterns=exclude_patterns,975    )976 977 978run_command.params.insert(0, _debug_option)979 980 981@click.command("shell", short_help="Run a shell in the app context.")982@with_appcontext983def shell_command() -> None:984    """Run an interactive Python shell in the context of a given985    Flask application.  The application will populate the default986    namespace of this shell according to its configuration.987 988    This is useful for executing small snippets of management code989    without having to manually configure the application.990    """991    import code992 993    banner = (994        f"Python {sys.version} on {sys.platform}\n"995        f"App: {current_app.import_name}\n"996        f"Instance: {current_app.instance_path}"997    )998    ctx: dict[str, t.Any] = {}999 1000    # Support the regular Python interpreter startup script if someone1001    # is using it.1002    startup = os.environ.get("PYTHONSTARTUP")1003    if startup and os.path.isfile(startup):1004        with open(startup) as f:1005            eval(compile(f.read(), startup, "exec"), ctx)1006 1007    ctx.update(current_app.make_shell_context())1008 1009    # Site, customize, or startup script can set a hook to call when1010    # entering interactive mode. The default one sets up readline with1011    # tab and history completion.1012    interactive_hook = getattr(sys, "__interactivehook__", None)1013 1014    if interactive_hook is not None:1015        try:1016            import readline1017            from rlcompleter import Completer1018        except ImportError:1019            pass1020        else:1021            # rlcompleter uses __main__.__dict__ by default, which is1022            # flask.__main__. Use the shell context instead.1023            readline.set_completer(Completer(ctx).complete)1024 1025        interactive_hook()1026 1027    code.interact(banner=banner, local=ctx)1028 1029 1030@click.command("routes", short_help="Show the routes for the app.")1031@click.option(1032    "--sort",1033    "-s",1034    type=click.Choice(("endpoint", "methods", "domain", "rule", "match")),1035    default="endpoint",1036    help=(1037        "Method to sort routes by. 'match' is the order that Flask will match routes"1038        " when dispatching a request."1039    ),1040)1041@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")1042@with_appcontext1043def routes_command(sort: str, all_methods: bool) -> None:1044    """Show all registered routes with endpoints and methods."""1045    rules = list(current_app.url_map.iter_rules())1046 1047    if not rules:1048        click.echo("No routes were registered.")1049        return1050 1051    ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"}1052    host_matching = current_app.url_map.host_matching1053    has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules)1054    rows = []1055 1056    for rule in rules:1057        row = [1058            rule.endpoint,1059            ", ".join(sorted((rule.methods or set()) - ignored_methods)),1060        ]1061 1062        if has_domain:1063            row.append((rule.host if host_matching else rule.subdomain) or "")1064 1065        row.append(rule.rule)1066        rows.append(row)1067 1068    headers = ["Endpoint", "Methods"]1069    sorts = ["endpoint", "methods"]1070 1071    if has_domain:1072        headers.append("Host" if host_matching else "Subdomain")1073        sorts.append("domain")1074 1075    headers.append("Rule")1076    sorts.append("rule")1077 1078    try:1079        rows.sort(key=itemgetter(sorts.index(sort)))1080    except ValueError:1081        pass1082 1083    rows.insert(0, headers)1084    widths = [max(len(row[i]) for row in rows) for i in range(len(headers))]1085    rows.insert(1, ["-" * w for w in widths])1086    template = "  ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths))1087 1088    for row in rows:1089        click.echo(template.format(*row))1090 1091 1092cli = FlaskGroup(1093    name="flask",1094    help="""\1095A general utility script for Flask applications.1096 1097An application to load must be given with the '--app' option,1098'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file1099in the current directory.1100""",1101)1102 1103 1104def main() -> None:1105    cli.main()1106 1107 1108if __name__ == "__main__":1109    main()1110 
codekingpro/portable-devtools · Team Ai