1import ast
2import inspect
3import os
4import platform
5import re
6import sys
7import traceback
8import warnings
9from functools import update_wrapper
10from operator import attrgetter
11from threading import Lock
12from threading import Thread
13
14import click
15from werkzeug.utils import import_string
16
17from .globals import current_app
18from .helpers import get_debug_flag
19from .helpers import get_env
20from .helpers import get_load_dotenv
21
22try:
23    import dotenv
24except ImportError:
25    dotenv = None
26
27try:
28    import ssl
29except ImportError:
30    ssl = None  # type: ignore
31
32
33class NoAppException(click.UsageError):
34    """Raised if an application cannot be found or loaded."""
35
36
37def find_best_app(script_info, module):
38    """Given a module instance this tries to find the best possible
39    application in the module or raises an exception.
40    """
41    from . import Flask
42
43    # Search for the most common names first.
44    for attr_name in ("app", "application"):
45        app = getattr(module, attr_name, None)
46
47        if isinstance(app, Flask):
48            return app
49
50    # Otherwise find the only object that is a Flask instance.
51    matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]
52
53    if len(matches) == 1:
54        return matches[0]
55    elif len(matches) > 1:
56        raise NoAppException(
57            "Detected multiple Flask applications in module"
58            f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
59            f" to specify the correct one."
60        )
61
62    # Search for app factory functions.
63    for attr_name in ("create_app", "make_app"):
64        app_factory = getattr(module, attr_name, None)
65
66        if inspect.isfunction(app_factory):
67            try:
68                app = call_factory(script_info, app_factory)
69
70                if isinstance(app, Flask):
71                    return app
72            except TypeError as e:
73                if not _called_with_wrong_args(app_factory):
74                    raise
75
76                raise NoAppException(
77                    f"Detected factory {attr_name!r} in module {module.__name__!r},"
78                    " but could not call it without arguments. Use"
79                    f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
80                    " to specify arguments."
81                ) from e
82
83    raise NoAppException(
84        "Failed to find Flask application or factory in module"
85        f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
86        " to specify one."
87    )
88
89
90def call_factory(script_info, app_factory, args=None, kwargs=None):
91    """Takes an app factory, a ``script_info` object and  optionally a tuple
92    of arguments. Checks for the existence of a script_info argument and calls
93    the app_factory depending on that and the arguments provided.
94    """
95    sig = inspect.signature(app_factory)
96    args = [] if args is None else args
97    kwargs = {} if kwargs is None else kwargs
98
99    if "script_info" in sig.parameters:
100        warnings.warn(
101            "The 'script_info' argument is deprecated and will not be"
102            " passed to the app factory function in Flask 2.1.",
103            DeprecationWarning,
104        )
105        kwargs["script_info"] = script_info
106
107    if not args and len(sig.parameters) == 1:
108        first_parameter = next(iter(sig.parameters.values()))
109
110        if (
111            first_parameter.default is inspect.Parameter.empty
112            # **kwargs is reported as an empty default, ignore it
113            and first_parameter.kind is not inspect.Parameter.VAR_KEYWORD
114        ):
115            warnings.warn(
116                "Script info is deprecated and will not be passed as the"
117                " single argument to the app factory function in Flask"
118                " 2.1.",
119                DeprecationWarning,
120            )
121            args.append(script_info)
122
123    return app_factory(*args, **kwargs)
124
125
126def _called_with_wrong_args(f):
127    """Check whether calling a function raised a ``TypeError`` because
128    the call failed or because something in the factory raised the
129    error.
130
131    :param f: The function that was called.
132    :return: ``True`` if the call failed.
133    """
134    tb = sys.exc_info()[2]
135
136    try:
137        while tb is not None:
138            if tb.tb_frame.f_code is f.__code__:
139                # In the function, it was called successfully.
140                return False
141
142            tb = tb.tb_next
143
144        # Didn't reach the function.
145        return True
146    finally:
147        # Delete tb to break a circular reference.
148        # https://docs.python.org/2/library/sys.html#sys.exc_info
149        del tb
150
151
152def find_app_by_string(script_info, module, app_name):
153    """Check if the given string is a variable name or a function. Call
154    a function to get the app instance, or return the variable directly.
155    """
156    from . import Flask
157
158    # Parse app_name as a single expression to determine if it's a valid
159    # attribute name or function call.
160    try:
161        expr = ast.parse(app_name.strip(), mode="eval").body
162    except SyntaxError:
163        raise NoAppException(
164            f"Failed to parse {app_name!r} as an attribute name or function call."
165        ) from None
166
167    if isinstance(expr, ast.Name):
168        name = expr.id
169        args = kwargs = None
170    elif isinstance(expr, ast.Call):
171        # Ensure the function name is an attribute name only.
172        if not isinstance(expr.func, ast.Name):
173            raise NoAppException(
174                f"Function reference must be a simple name: {app_name!r}."
175            )
176
177        name = expr.func.id
178
179        # Parse the positional and keyword arguments as literals.
180        try:
181            args = [ast.literal_eval(arg) for arg in expr.args]
182            kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords}
183        except ValueError:
184            # literal_eval gives cryptic error messages, show a generic
185            # message with the full expression instead.
186            raise NoAppException(
187                f"Failed to parse arguments as literal values: {app_name!r}."
188            ) from None
189    else:
190        raise NoAppException(
191            f"Failed to parse {app_name!r} as an attribute name or function call."
192        )
193
194    try:
195        attr = getattr(module, name)
196    except AttributeError as e:
197        raise NoAppException(
198            f"Failed to find attribute {name!r} in {module.__name__!r}."
199        ) from e
200
201    # If the attribute is a function, call it with any args and kwargs
202    # to get the real application.
203    if inspect.isfunction(attr):
204        try:
205            app = call_factory(script_info, attr, args, kwargs)
206        except TypeError as e:
207            if not _called_with_wrong_args(attr):
208                raise
209
210            raise NoAppException(
211                f"The factory {app_name!r} in module"
212                f" {module.__name__!r} could not be called with the"
213                " specified arguments."
214            ) from e
215    else:
216        app = attr
217
218    if isinstance(app, Flask):
219        return app
220
221    raise NoAppException(
222        "A valid Flask application was not obtained from"
223        f" '{module.__name__}:{app_name}'."
224    )
225
226
227def prepare_import(path):
228    """Given a filename this will try to calculate the python path, add it
229    to the search path and return the actual module name that is expected.
230    """
231    path = os.path.realpath(path)
232
233    fname, ext = os.path.splitext(path)
234    if ext == ".py":
235        path = fname
236
237    if os.path.basename(path) == "__init__":
238        path = os.path.dirname(path)
239
240    module_name = []
241
242    # move up until outside package structure (no __init__.py)
243    while True:
244        path, name = os.path.split(path)
245        module_name.append(name)
246
247        if not os.path.exists(os.path.join(path, "__init__.py")):
248            break
249
250    if sys.path[0] != path:
251        sys.path.insert(0, path)
252
253    return ".".join(module_name[::-1])
254
255
256def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
257    __traceback_hide__ = True  # noqa: F841
258
259    try:
260        __import__(module_name)
261    except ImportError as e:
262        # Reraise the ImportError if it occurred within the imported module.
263        # Determine this by checking whether the trace has a depth > 1.
264        if sys.exc_info()[2].tb_next:
265            raise NoAppException(
266                f"While importing {module_name!r}, an ImportError was raised."
267            ) from e
268        elif raise_if_not_found:
269            raise NoAppException(f"Could not import {module_name!r}.") from e
270        else:
271            return
272
273    module = sys.modules[module_name]
274
275    if app_name is None:
276        return find_best_app(script_info, module)
277    else:
278        return find_app_by_string(script_info, module, app_name)
279
280
281def get_version(ctx, param, value):
282    if not value or ctx.resilient_parsing:
283        return
284
285    import werkzeug
286    from . import __version__
287
288    click.echo(
289        f"Python {platform.python_version()}\n"
290        f"Flask {__version__}\n"
291        f"Werkzeug {werkzeug.__version__}",
292        color=ctx.color,
293    )
294    ctx.exit()
295
296
297version_option = click.Option(
298    ["--version"],
299    help="Show the flask version",
300    expose_value=False,
301    callback=get_version,
302    is_flag=True,
303    is_eager=True,
304)
305
306
307class DispatchingApp:
308    """Special application that dispatches to a Flask application which
309    is imported by name in a background thread.  If an error happens
310    it is recorded and shown as part of the WSGI handling which in case
311    of the Werkzeug debugger means that it shows up in the browser.
312    """
313
314    def __init__(self, loader, use_eager_loading=None):
315        self.loader = loader
316        self._app = None
317        self._lock = Lock()
318        self._bg_loading_exc = None
319
320        if use_eager_loading is None:
321            use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
322
323        if use_eager_loading:
324            self._load_unlocked()
325        else:
326            self._load_in_background()
327
328    def _load_in_background(self):
329        def _load_app():
330            __traceback_hide__ = True  # noqa: F841
331            with self._lock:
332                try:
333                    self._load_unlocked()
334                except Exception as e:
335                    self._bg_loading_exc = e
336
337        t = Thread(target=_load_app, args=())
338        t.start()
339
340    def _flush_bg_loading_exception(self):
341        __traceback_hide__ = True  # noqa: F841
342        exc = self._bg_loading_exc
343
344        if exc is not None:
345            self._bg_loading_exc = None
346            raise exc
347
348    def _load_unlocked(self):
349        __traceback_hide__ = True  # noqa: F841
350        self._app = rv = self.loader()
351        self._bg_loading_exc = None
352        return rv
353
354    def __call__(self, environ, start_response):
355        __traceback_hide__ = True  # noqa: F841
356        if self._app is not None:
357            return self._app(environ, start_response)
358        self._flush_bg_loading_exception()
359        with self._lock:
360            if self._app is not None:
361                rv = self._app
362            else:
363                rv = self._load_unlocked()
364            return rv(environ, start_response)
365
366
367class ScriptInfo:
368    """Helper object to deal with Flask applications.  This is usually not
369    necessary to interface with as it's used internally in the dispatching
370    to click.  In future versions of Flask this object will most likely play
371    a bigger role.  Typically it's created automatically by the
372    :class:`FlaskGroup` but you can also manually create it and pass it
373    onwards as click object.
374    """
375
376    def __init__(self, app_import_path=None, create_app=None, set_debug_flag=True):
377        #: Optionally the import path for the Flask application.
378        self.app_import_path = app_import_path or os.environ.get("FLASK_APP")
379        #: Optionally a function that is passed the script info to create
380        #: the instance of the application.
381        self.create_app = create_app
382        #: A dictionary with arbitrary data that can be associated with
383        #: this script info.
384        self.data = {}
385        self.set_debug_flag = set_debug_flag
386        self._loaded_app = None
387
388    def load_app(self):
389        """Loads the Flask app (if not yet loaded) and returns it.  Calling
390        this multiple times will just result in the already loaded app to
391        be returned.
392        """
393        __traceback_hide__ = True  # noqa: F841
394
395        if self._loaded_app is not None:
396            return self._loaded_app
397
398        if self.create_app is not None:
399            app = call_factory(self, self.create_app)
400        else:
401            if self.app_import_path:
402                path, name = (
403                    re.split(r":(?![\\/])", self.app_import_path, 1) + [None]
404                )[:2]
405                import_name = prepare_import(path)
406                app = locate_app(self, import_name, name)
407            else:
408                for path in ("wsgi.py", "app.py"):
409                    import_name = prepare_import(path)
410                    app = locate_app(self, import_name, None, raise_if_not_found=False)
411
412                    if app:
413                        break
414
415        if not app:
416            raise NoAppException(
417                "Could not locate a Flask application. You did not provide "
418                'the "FLASK_APP" environment variable, and a "wsgi.py" or '
419                '"app.py" module was not found in the current directory.'
420            )
421
422        if self.set_debug_flag:
423            # Update the app's debug flag through the descriptor so that
424            # other values repopulate as well.
425            app.debug = get_debug_flag()
426
427        self._loaded_app = app
428        return app
429
430
431pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
432
433
434def with_appcontext(f):
435    """Wraps a callback so that it's guaranteed to be executed with the
436    script's application context.  If callbacks are registered directly
437    to the ``app.cli`` object then they are wrapped with this function
438    by default unless it's disabled.
439    """
440
441    @click.pass_context
442    def decorator(__ctx, *args, **kwargs):
443        with __ctx.ensure_object(ScriptInfo).load_app().app_context():
444            return __ctx.invoke(f, *args, **kwargs)
445
446    return update_wrapper(decorator, f)
447
448
449class AppGroup(click.Group):
450    """This works similar to a regular click :class:`~click.Group` but it
451    changes the behavior of the :meth:`command` decorator so that it
452    automatically wraps the functions in :func:`with_appcontext`.
453
454    Not to be confused with :class:`FlaskGroup`.
455    """
456
457    def command(self, *args, **kwargs):
458        """This works exactly like the method of the same name on a regular
459        :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
460        unless it's disabled by passing ``with_appcontext=False``.
461        """
462        wrap_for_ctx = kwargs.pop("with_appcontext", True)
463
464        def decorator(f):
465            if wrap_for_ctx:
466                f = with_appcontext(f)
467            return click.Group.command(self, *args, **kwargs)(f)
468
469        return decorator
470
471    def group(self, *args, **kwargs):
472        """This works exactly like the method of the same name on a regular
473        :class:`click.Group` but it defaults the group class to
474        :class:`AppGroup`.
475        """
476        kwargs.setdefault("cls", AppGroup)
477        return click.Group.group(self, *args, **kwargs)
478
479
480class FlaskGroup(AppGroup):
481    """Special subclass of the :class:`AppGroup` group that supports
482    loading more commands from the configured Flask app.  Normally a
483    developer does not have to interface with this class but there are
484    some very advanced use cases for which it makes sense to create an
485    instance of this. see :ref:`custom-scripts`.
486
487    :param add_default_commands: if this is True then the default run and
488        shell commands will be added.
489    :param add_version_option: adds the ``--version`` option.
490    :param create_app: an optional callback that is passed the script info and
491        returns the loaded app.
492    :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
493        files to set environment variables. Will also change the working
494        directory to the directory containing the first file found.
495    :param set_debug_flag: Set the app's debug flag based on the active
496        environment
497
498    .. versionchanged:: 1.0
499        If installed, python-dotenv will be used to load environment variables
500        from :file:`.env` and :file:`.flaskenv` files.
501    """
502
503    def __init__(
504        self,
505        add_default_commands=True,
506        create_app=None,
507        add_version_option=True,
508        load_dotenv=True,
509        set_debug_flag=True,
510        **extra,
511    ):
512        params = list(extra.pop("params", None) or ())
513
514        if add_version_option:
515            params.append(version_option)
516
517        AppGroup.__init__(self, params=params, **extra)
518        self.create_app = create_app
519        self.load_dotenv = load_dotenv
520        self.set_debug_flag = set_debug_flag
521
522        if add_default_commands:
523            self.add_command(run_command)
524            self.add_command(shell_command)
525            self.add_command(routes_command)
526
527        self._loaded_plugin_commands = False
528
529    def _load_plugin_commands(self):
530        if self._loaded_plugin_commands:
531            return
532        try:
533            import pkg_resources
534        except ImportError:
535            self._loaded_plugin_commands = True
536            return
537
538        for ep in pkg_resources.iter_entry_points("flask.commands"):
539            self.add_command(ep.load(), ep.name)
540        self._loaded_plugin_commands = True
541
542    def get_command(self, ctx, name):
543        self._load_plugin_commands()
544        # Look up built-in and plugin commands, which should be
545        # available even if the app fails to load.
546        rv = super().get_command(ctx, name)
547
548        if rv is not None:
549            return rv
550
551        info = ctx.ensure_object(ScriptInfo)
552
553        # Look up commands provided by the app, showing an error and
554        # continuing if the app couldn't be loaded.
555        try:
556            return info.load_app().cli.get_command(ctx, name)
557        except NoAppException as e:
558            click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
559
560    def list_commands(self, ctx):
561        self._load_plugin_commands()
562        # Start with the built-in and plugin commands.
563        rv = set(super().list_commands(ctx))
564        info = ctx.ensure_object(ScriptInfo)
565
566        # Add commands provided by the app, showing an error and
567        # continuing if the app couldn't be loaded.
568        try:
569            rv.update(info.load_app().cli.list_commands(ctx))
570        except NoAppException as e:
571            # When an app couldn't be loaded, show the error message
572            # without the traceback.
573            click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
574        except Exception:
575            # When any other errors occurred during loading, show the
576            # full traceback.
577            click.secho(f"{traceback.format_exc()}\n", err=True, fg="red")
578
579        return sorted(rv)
580
581    def main(self, *args, **kwargs):
582        # Set a global flag that indicates that we were invoked from the
583        # command line interface. This is detected by Flask.run to make the
584        # call into a no-op. This is necessary to avoid ugly errors when the
585        # script that is loaded here also attempts to start a server.
586        os.environ["FLASK_RUN_FROM_CLI"] = "true"
587
588        if get_load_dotenv(self.load_dotenv):
589            load_dotenv()
590
591        obj = kwargs.get("obj")
592
593        if obj is None:
594            obj = ScriptInfo(
595                create_app=self.create_app, set_debug_flag=self.set_debug_flag
596            )
597
598        kwargs["obj"] = obj
599        kwargs.setdefault("auto_envvar_prefix", "FLASK")
600        return super().main(*args, **kwargs)
601
602
603def _path_is_ancestor(path, other):
604    """Take ``other`` and remove the length of ``path`` from it. Then join it
605    to ``path``. If it is the original value, ``path`` is an ancestor of
606    ``other``."""
607    return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
608
609
610def load_dotenv(path=None):
611    """Load "dotenv" files in order of precedence to set environment variables.
612
613    If an env var is already set it is not overwritten, so earlier files in the
614    list are preferred over later files.
615
616    This is a no-op if `python-dotenv`_ is not installed.
617
618    .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
619
620    :param path: Load the file at this location instead of searching.
621    :return: ``True`` if a file was loaded.
622
623    .. versionchanged:: 1.1.0
624        Returns ``False`` when python-dotenv is not installed, or when
625        the given path isn't a file.
626
627    .. versionchanged:: 2.0
628        When loading the env files, set the default encoding to UTF-8.
629
630    .. versionadded:: 1.0
631    """
632    if dotenv is None:
633        if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
634            click.secho(
635                " * Tip: There are .env or .flaskenv files present."
636                ' Do "pip install python-dotenv" to use them.',
637                fg="yellow",
638                err=True,
639            )
640
641        return False
642
643    # if the given path specifies the actual file then return True,
644    # else False
645    if path is not None:
646        if os.path.isfile(path):
647            return dotenv.load_dotenv(path, encoding="utf-8")
648
649        return False
650
651    new_dir = None
652
653    for name in (".env", ".flaskenv"):
654        path = dotenv.find_dotenv(name, usecwd=True)
655
656        if not path:
657            continue
658
659        if new_dir is None:
660            new_dir = os.path.dirname(path)
661
662        dotenv.load_dotenv(path, encoding="utf-8")
663
664    return new_dir is not None  # at least one file was located and loaded
665
666
667def show_server_banner(env, debug, app_import_path, eager_loading):
668    """Show extra startup messages the first time the server is run,
669    ignoring the reloader.
670    """
671    if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
672        return
673
674    if app_import_path is not None:
675        message = f" * Serving Flask app {app_import_path!r}"
676
677        if not eager_loading:
678            message += " (lazy loading)"
679
680        click.echo(message)
681
682    click.echo(f" * Environment: {env}")
683
684    if env == "production":
685        click.secho(
686            "   WARNING: This is a development server. Do not use it in"
687            " a production deployment.",
688            fg="red",
689        )
690        click.secho("   Use a production WSGI server instead.", dim=True)
691
692    if debug is not None:
693        click.echo(f" * Debug mode: {'on' if debug else 'off'}")
694
695
696class CertParamType(click.ParamType):
697    """Click option type for the ``--cert`` option. Allows either an
698    existing file, the string ``'adhoc'``, or an import for a
699    :class:`~ssl.SSLContext` object.
700    """
701
702    name = "path"
703
704    def __init__(self):
705        self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
706
707    def convert(self, value, param, ctx):
708        if ssl is None:
709            raise click.BadParameter(
710                'Using "--cert" requires Python to be compiled with SSL support.',
711                ctx,
712                param,
713            )
714
715        try:
716            return self.path_type(value, param, ctx)
717        except click.BadParameter:
718            value = click.STRING(value, param, ctx).lower()
719
720            if value == "adhoc":
721                try:
722                    import cryptography  # noqa: F401
723                except ImportError:
724                    raise click.BadParameter(
725                        "Using ad-hoc certificates requires the cryptography library.",
726                        ctx,
727                        param,
728                    ) from None
729
730                return value
731
732            obj = import_string(value, silent=True)
733
734            if isinstance(obj, ssl.SSLContext):
735                return obj
736
737            raise
738
739
740def _validate_key(ctx, param, value):
741    """The ``--key`` option must be specified when ``--cert`` is a file.
742    Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
743    """
744    cert = ctx.params.get("cert")
745    is_adhoc = cert == "adhoc"
746    is_context = ssl and isinstance(cert, ssl.SSLContext)
747
748    if value is not None:
749        if is_adhoc:
750            raise click.BadParameter(
751                'When "--cert" is "adhoc", "--key" is not used.', ctx, param
752            )
753
754        if is_context:
755            raise click.BadParameter(
756                'When "--cert" is an SSLContext object, "--key is not used.', ctx, param
757            )
758
759        if not cert:
760            raise click.BadParameter('"--cert" must also be specified.', ctx, param)
761
762        ctx.params["cert"] = cert, value
763
764    else:
765        if cert and not (is_adhoc or is_context):
766            raise click.BadParameter('Required when using "--cert".', ctx, param)
767
768    return value
769
770
771class SeparatedPathType(click.Path):
772    """Click option type that accepts a list of values separated by the
773    OS's path separator (``:``, ``;`` on Windows). Each value is
774    validated as a :class:`click.Path` type.
775    """
776
777    def convert(self, value, param, ctx):
778        items = self.split_envvar_value(value)
779        super_convert = super().convert
780        return [super_convert(item, param, ctx) for item in items]
781
782
783@click.command("run", short_help="Run a development server.")
784@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.")
785@click.option("--port", "-p", default=5000, help="The port to bind to.")
786@click.option(
787    "--cert", type=CertParamType(), help="Specify a certificate file to use HTTPS."
788)
789@click.option(
790    "--key",
791    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
792    callback=_validate_key,
793    expose_value=False,
794    help="The key file to use when specifying a certificate.",
795)
796@click.option(
797    "--reload/--no-reload",
798    default=None,
799    help="Enable or disable the reloader. By default the reloader "
800    "is active if debug is enabled.",
801)
802@click.option(
803    "--debugger/--no-debugger",
804    default=None,
805    help="Enable or disable the debugger. By default the debugger "
806    "is active if debug is enabled.",
807)
808@click.option(
809    "--eager-loading/--lazy-loading",
810    default=None,
811    help="Enable or disable eager loading. By default eager "
812    "loading is enabled if the reloader is disabled.",
813)
814@click.option(
815    "--with-threads/--without-threads",
816    default=True,
817    help="Enable or disable multithreading.",
818)
819@click.option(
820    "--extra-files",
821    default=None,
822    type=SeparatedPathType(),
823    help=(
824        "Extra files that trigger a reload on change. Multiple paths"
825        f" are separated by {os.path.pathsep!r}."
826    ),
827)
828@pass_script_info
829def run_command(
830    info, host, port, reload, debugger, eager_loading, with_threads, cert, extra_files
831):
832    """Run a local development server.
833
834    This server is for development purposes only. It does not provide
835    the stability, security, or performance of production WSGI servers.
836
837    The reloader and debugger are enabled by default if
838    FLASK_ENV=development or FLASK_DEBUG=1.
839    """
840    debug = get_debug_flag()
841
842    if reload is None:
843        reload = debug
844
845    if debugger is None:
846        debugger = debug
847
848    show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
849    app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
850
851    from werkzeug.serving import run_simple
852
853    run_simple(
854        host,
855        port,
856        app,
857        use_reloader=reload,
858        use_debugger=debugger,
859        threaded=with_threads,
860        ssl_context=cert,
861        extra_files=extra_files,
862    )
863
864
865@click.command("shell", short_help="Run a shell in the app context.")
866@with_appcontext
867def shell_command() -> None:
868    """Run an interactive Python shell in the context of a given
869    Flask application.  The application will populate the default
870    namespace of this shell according to its configuration.
871
872    This is useful for executing small snippets of management code
873    without having to manually configure the application.
874    """
875    import code
876    from .globals import _app_ctx_stack
877
878    app = _app_ctx_stack.top.app
879    banner = (
880        f"Python {sys.version} on {sys.platform}\n"
881        f"App: {app.import_name} [{app.env}]\n"
882        f"Instance: {app.instance_path}"
883    )
884    ctx: dict = {}
885
886    # Support the regular Python interpreter startup script if someone
887    # is using it.
888    startup = os.environ.get("PYTHONSTARTUP")
889    if startup and os.path.isfile(startup):
890        with open(startup) as f:
891            eval(compile(f.read(), startup, "exec"), ctx)
892
893    ctx.update(app.make_shell_context())
894
895    # Site, customize, or startup script can set a hook to call when
896    # entering interactive mode. The default one sets up readline with
897    # tab and history completion.
898    interactive_hook = getattr(sys, "__interactivehook__", None)
899
900    if interactive_hook is not None:
901        try:
902            import readline
903            from rlcompleter import Completer
904        except ImportError:
905            pass
906        else:
907            # rlcompleter uses __main__.__dict__ by default, which is
908            # flask.__main__. Use the shell context instead.
909            readline.set_completer(Completer(ctx).complete)
910
911        interactive_hook()
912
913    code.interact(banner=banner, local=ctx)
914
915
916@click.command("routes", short_help="Show the routes for the app.")
917@click.option(
918    "--sort",
919    "-s",
920    type=click.Choice(("endpoint", "methods", "rule", "match")),
921    default="endpoint",
922    help=(
923        'Method to sort routes by. "match" is the order that Flask will match '
924        "routes when dispatching a request."
925    ),
926)
927@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
928@with_appcontext
929def routes_command(sort: str, all_methods: bool) -> None:
930    """Show all registered routes with endpoints and methods."""
931
932    rules = list(current_app.url_map.iter_rules())
933    if not rules:
934        click.echo("No routes were registered.")
935        return
936
937    ignored_methods = set(() if all_methods else ("HEAD", "OPTIONS"))
938
939    if sort in ("endpoint", "rule"):
940        rules = sorted(rules, key=attrgetter(sort))
941    elif sort == "methods":
942        rules = sorted(rules, key=lambda rule: sorted(rule.methods))  # type: ignore
943
944    rule_methods = [
945        ", ".join(sorted(rule.methods - ignored_methods))  # type: ignore
946        for rule in rules
947    ]
948
949    headers = ("Endpoint", "Methods", "Rule")
950    widths = (
951        max(len(rule.endpoint) for rule in rules),
952        max(len(methods) for methods in rule_methods),
953        max(len(rule.rule) for rule in rules),
954    )
955    widths = [max(len(h), w) for h, w in zip(headers, widths)]
956    row = "{{0:<{0}}}  {{1:<{1}}}  {{2:<{2}}}".format(*widths)
957
958    click.echo(row.format(*headers).strip())
959    click.echo(row.format(*("-" * width for width in widths)))
960
961    for rule, methods in zip(rules, rule_methods):
962        click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
963
964
965cli = FlaskGroup(
966    help="""\
967A general utility script for Flask applications.
968
969Provides commands from Flask, extensions, and the application. Loads the
970application defined in the FLASK_APP environment variable, or from a wsgi.py
971file. Setting the FLASK_ENV environment variable to 'development' will enable
972debug mode.
973
974\b
975  {prefix}{cmd} FLASK_APP=hello.py
976  {prefix}{cmd} FLASK_ENV=development
977  {prefix}flask run
978""".format(
979        cmd="export" if os.name == "posix" else "set",
980        prefix="$ " if os.name == "posix" else "> ",
981    )
982)
983
984
985def main() -> None:
986    if int(click.__version__[0]) < 8:
987        warnings.warn(
988            "Using the `flask` cli with Click 7 is deprecated and"
989            " will not be supported starting with Flask 2.1."
990            " Please upgrade to Click 8 as soon as possible.",
991            DeprecationWarning,
992        )
993    # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed
994    cli.main(args=sys.argv[1:])
995
996
997if __name__ == "__main__":
998    main()
999