"Fossies" - the Fresh Open Source Software Archive

Member "flask-1.1.2/src/flask/cli.py" (3 Apr 2020, 31035 Bytes) of package /linux/www/flask-1.1.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "cli.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.1.1_vs_1.1.2.

    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 """
   11 from __future__ import print_function
   12 
   13 import ast
   14 import inspect
   15 import os
   16 import platform
   17 import re
   18 import sys
   19 import traceback
   20 from functools import update_wrapper
   21 from operator import attrgetter
   22 from threading import Lock
   23 from threading import Thread
   24 
   25 import click
   26 from werkzeug.utils import import_string
   27 
   28 from ._compat import getargspec
   29 from ._compat import itervalues
   30 from ._compat import reraise
   31 from ._compat import text_type
   32 from .globals import current_app
   33 from .helpers import get_debug_flag
   34 from .helpers import get_env
   35 from .helpers import get_load_dotenv
   36 
   37 try:
   38     import dotenv
   39 except ImportError:
   40     dotenv = None
   41 
   42 try:
   43     import ssl
   44 except ImportError:
   45     ssl = None
   46 
   47 
   48 class NoAppException(click.UsageError):
   49     """Raised if an application cannot be found or loaded."""
   50 
   51 
   52 def 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 
  103 def 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 
  122 def _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 
  148 def 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 
  207 def 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 
  236 def 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 
  262 def 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 
  282 version_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 
  292 class 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 
  347 class 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 
  413 pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
  414 
  415 
  416 def 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 
  431 class 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 
  462 class 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 
  589 def _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 
  596 def 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 
  657 def 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 
  686 class 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 
  732 def _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 
  767 class 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
  825 def 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
  866 def 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
  912 def 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 
  945 cli = FlaskGroup(
  946     help="""\
  947 A general utility script for Flask applications.
  948 
  949 Provides commands from Flask, extensions, and the application. Loads the
  950 application defined in the FLASK_APP environment variable, or from a wsgi.py
  951 file. Setting the FLASK_ENV environment variable to 'development' will enable
  952 debug 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 
  965 def main(as_module=False):
  966     # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed
  967     cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)
  968 
  969 
  970 if __name__ == "__main__":
  971     main(as_module=True)