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