aboutsummaryrefslogtreecommitdiffstats
path: root/python/flask/cli.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/flask/cli.py')
-rw-r--r--python/flask/cli.py910
1 files changed, 910 insertions, 0 deletions
diff --git a/python/flask/cli.py b/python/flask/cli.py
new file mode 100644
index 0000000..3eb93b3
--- /dev/null
+++ b/python/flask/cli.py
@@ -0,0 +1,910 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.cli
+ ~~~~~~~~~
+
+ A simple command line application to run flask apps.
+
+ :copyright: © 2010 by the Pallets team.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import print_function
+
+import ast
+import inspect
+import os
+import platform
+import re
+import ssl
+import sys
+import traceback
+from functools import update_wrapper
+from operator import attrgetter
+from threading import Lock, Thread
+
+import click
+from werkzeug.utils import import_string
+
+from . import __version__
+from ._compat import getargspec, iteritems, reraise, text_type
+from .globals import current_app
+from .helpers import get_debug_flag, get_env, get_load_dotenv
+
+try:
+ import dotenv
+except ImportError:
+ dotenv = None
+
+
+class NoAppException(click.UsageError):
+ """Raised if an application cannot be found or loaded."""
+
+
+def find_best_app(script_info, module):
+ """Given a module instance this tries to find the best possible
+ application in the module or raises an exception.
+ """
+ from . import Flask
+
+ # Search for the most common names first.
+ for attr_name in ('app', 'application'):
+ app = getattr(module, attr_name, None)
+
+ if isinstance(app, Flask):
+ return app
+
+ # Otherwise find the only object that is a Flask instance.
+ matches = [
+ v for k, v in iteritems(module.__dict__) if isinstance(v, Flask)
+ ]
+
+ if len(matches) == 1:
+ return matches[0]
+ elif len(matches) > 1:
+ raise NoAppException(
+ 'Detected multiple Flask applications in module "{module}". Use '
+ '"FLASK_APP={module}:name" to specify the correct '
+ 'one.'.format(module=module.__name__)
+ )
+
+ # Search for app factory functions.
+ for attr_name in ('create_app', 'make_app'):
+ app_factory = getattr(module, attr_name, None)
+
+ if inspect.isfunction(app_factory):
+ try:
+ app = call_factory(script_info, app_factory)
+
+ if isinstance(app, Flask):
+ return app
+ except TypeError:
+ if not _called_with_wrong_args(app_factory):
+ raise
+ raise NoAppException(
+ 'Detected factory "{factory}" in module "{module}", but '
+ 'could not call it without arguments. Use '
+ '"FLASK_APP=\'{module}:{factory}(args)\'" to specify '
+ 'arguments.'.format(
+ factory=attr_name, module=module.__name__
+ )
+ )
+
+ raise NoAppException(
+ 'Failed to find Flask application or factory in module "{module}". '
+ 'Use "FLASK_APP={module}:name to specify one.'.format(
+ module=module.__name__
+ )
+ )
+
+
+def call_factory(script_info, app_factory, arguments=()):
+ """Takes an app factory, a ``script_info` object and optionally a tuple
+ of arguments. Checks for the existence of a script_info argument and calls
+ the app_factory depending on that and the arguments provided.
+ """
+ args_spec = getargspec(app_factory)
+ arg_names = args_spec.args
+ arg_defaults = args_spec.defaults
+
+ if 'script_info' in arg_names:
+ return app_factory(*arguments, script_info=script_info)
+ elif arguments:
+ return app_factory(*arguments)
+ elif not arguments and len(arg_names) == 1 and arg_defaults is None:
+ return app_factory(script_info)
+
+ return app_factory()
+
+
+def _called_with_wrong_args(factory):
+ """Check whether calling a function raised a ``TypeError`` because
+ the call failed or because something in the factory raised the
+ error.
+
+ :param factory: the factory function that was called
+ :return: true if the call failed
+ """
+ tb = sys.exc_info()[2]
+
+ try:
+ while tb is not None:
+ if tb.tb_frame.f_code is factory.__code__:
+ # in the factory, it was called successfully
+ return False
+
+ tb = tb.tb_next
+
+ # didn't reach the factory
+ return True
+ finally:
+ del tb
+
+
+def find_app_by_string(script_info, module, app_name):
+ """Checks if the given string is a variable name or a function. If it is a
+ function, it checks for specified arguments and whether it takes a
+ ``script_info`` argument and calls the function with the appropriate
+ arguments.
+ """
+ from flask import Flask
+ match = re.match(r'^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$', app_name)
+
+ if not match:
+ raise NoAppException(
+ '"{name}" is not a valid variable name or function '
+ 'expression.'.format(name=app_name)
+ )
+
+ name, args = match.groups()
+
+ try:
+ attr = getattr(module, name)
+ except AttributeError as e:
+ raise NoAppException(e.args[0])
+
+ if inspect.isfunction(attr):
+ if args:
+ try:
+ args = ast.literal_eval('({args},)'.format(args=args))
+ except (ValueError, SyntaxError)as e:
+ raise NoAppException(
+ 'Could not parse the arguments in '
+ '"{app_name}".'.format(e=e, app_name=app_name)
+ )
+ else:
+ args = ()
+
+ try:
+ app = call_factory(script_info, attr, args)
+ except TypeError as e:
+ if not _called_with_wrong_args(attr):
+ raise
+
+ raise NoAppException(
+ '{e}\nThe factory "{app_name}" in module "{module}" could not '
+ 'be called with the specified arguments.'.format(
+ e=e, app_name=app_name, module=module.__name__
+ )
+ )
+ else:
+ app = attr
+
+ if isinstance(app, Flask):
+ return app
+
+ raise NoAppException(
+ 'A valid Flask application was not obtained from '
+ '"{module}:{app_name}".'.format(
+ module=module.__name__, app_name=app_name
+ )
+ )
+
+
+def prepare_import(path):
+ """Given a filename this will try to calculate the python path, add it
+ to the search path and return the actual module name that is expected.
+ """
+ path = os.path.realpath(path)
+
+ if os.path.splitext(path)[1] == '.py':
+ path = os.path.splitext(path)[0]
+
+ if os.path.basename(path) == '__init__':
+ path = os.path.dirname(path)
+
+ module_name = []
+
+ # move up until outside package structure (no __init__.py)
+ while True:
+ path, name = os.path.split(path)
+ module_name.append(name)
+
+ if not os.path.exists(os.path.join(path, '__init__.py')):
+ break
+
+ if sys.path[0] != path:
+ sys.path.insert(0, path)
+
+ return '.'.join(module_name[::-1])
+
+
+def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
+ __traceback_hide__ = True
+
+ try:
+ __import__(module_name)
+ except ImportError:
+ # Reraise the ImportError if it occurred within the imported module.
+ # Determine this by checking whether the trace has a depth > 1.
+ if sys.exc_info()[-1].tb_next:
+ raise NoAppException(
+ 'While importing "{name}", an ImportError was raised:'
+ '\n\n{tb}'.format(name=module_name, tb=traceback.format_exc())
+ )
+ elif raise_if_not_found:
+ raise NoAppException(
+ 'Could not import "{name}".'.format(name=module_name)
+ )
+ else:
+ return
+
+ module = sys.modules[module_name]
+
+ if app_name is None:
+ return find_best_app(script_info, module)
+ else:
+ return find_app_by_string(script_info, module, app_name)
+
+
+def get_version(ctx, param, value):
+ if not value or ctx.resilient_parsing:
+ return
+ import werkzeug
+ message = (
+ 'Python %(python)s\n'
+ 'Flask %(flask)s\n'
+ 'Werkzeug %(werkzeug)s'
+ )
+ click.echo(message % {
+ 'python': platform.python_version(),
+ 'flask': __version__,
+ 'werkzeug': werkzeug.__version__,
+ }, color=ctx.color)
+ ctx.exit()
+
+
+version_option = click.Option(
+ ['--version'],
+ help='Show the flask version',
+ expose_value=False,
+ callback=get_version,
+ is_flag=True,
+ is_eager=True
+)
+
+
+class DispatchingApp(object):
+ """Special application that dispatches to a Flask application which
+ is imported by name in a background thread. If an error happens
+ it is recorded and shown as part of the WSGI handling which in case
+ of the Werkzeug debugger means that it shows up in the browser.
+ """
+
+ def __init__(self, loader, use_eager_loading=False):
+ self.loader = loader
+ self._app = None
+ self._lock = Lock()
+ self._bg_loading_exc_info = None
+ if use_eager_loading:
+ self._load_unlocked()
+ else:
+ self._load_in_background()
+
+ def _load_in_background(self):
+ def _load_app():
+ __traceback_hide__ = True
+ with self._lock:
+ try:
+ self._load_unlocked()
+ except Exception:
+ self._bg_loading_exc_info = sys.exc_info()
+ t = Thread(target=_load_app, args=())
+ t.start()
+
+ def _flush_bg_loading_exception(self):
+ __traceback_hide__ = True
+ exc_info = self._bg_loading_exc_info
+ if exc_info is not None:
+ self._bg_loading_exc_info = None
+ reraise(*exc_info)
+
+ def _load_unlocked(self):
+ __traceback_hide__ = True
+ self._app = rv = self.loader()
+ self._bg_loading_exc_info = None
+ return rv
+
+ def __call__(self, environ, start_response):
+ __traceback_hide__ = True
+ if self._app is not None:
+ return self._app(environ, start_response)
+ self._flush_bg_loading_exception()
+ with self._lock:
+ if self._app is not None:
+ rv = self._app
+ else:
+ rv = self._load_unlocked()
+ return rv(environ, start_response)
+
+
+class ScriptInfo(object):
+ """Helper object to deal with Flask applications. This is usually not
+ necessary to interface with as it's used internally in the dispatching
+ to click. In future versions of Flask this object will most likely play
+ a bigger role. Typically it's created automatically by the
+ :class:`FlaskGroup` but you can also manually create it and pass it
+ onwards as click object.
+ """
+
+ def __init__(self, app_import_path=None, create_app=None,
+ set_debug_flag=True):
+ #: Optionally the import path for the Flask application.
+ self.app_import_path = app_import_path or os.environ.get('FLASK_APP')
+ #: Optionally a function that is passed the script info to create
+ #: the instance of the application.
+ self.create_app = create_app
+ #: A dictionary with arbitrary data that can be associated with
+ #: this script info.
+ self.data = {}
+ self.set_debug_flag = set_debug_flag
+ self._loaded_app = None
+
+ def load_app(self):
+ """Loads the Flask app (if not yet loaded) and returns it. Calling
+ this multiple times will just result in the already loaded app to
+ be returned.
+ """
+ __traceback_hide__ = True
+
+ if self._loaded_app is not None:
+ return self._loaded_app
+
+ app = None
+
+ if self.create_app is not None:
+ app = call_factory(self, self.create_app)
+ else:
+ if self.app_import_path:
+ path, name = (re.split(r':(?![\\/])', self.app_import_path, 1) + [None])[:2]
+ import_name = prepare_import(path)
+ app = locate_app(self, import_name, name)
+ else:
+ for path in ('wsgi.py', 'app.py'):
+ import_name = prepare_import(path)
+ app = locate_app(self, import_name, None,
+ raise_if_not_found=False)
+
+ if app:
+ break
+
+ if not app:
+ raise NoAppException(
+ 'Could not locate a Flask application. You did not provide '
+ 'the "FLASK_APP" environment variable, and a "wsgi.py" or '
+ '"app.py" module was not found in the current directory.'
+ )
+
+ if self.set_debug_flag:
+ # Update the app's debug flag through the descriptor so that
+ # other values repopulate as well.
+ app.debug = get_debug_flag()
+
+ self._loaded_app = app
+ return app
+
+
+pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
+
+
+def with_appcontext(f):
+ """Wraps a callback so that it's guaranteed to be executed with the
+ script's application context. If callbacks are registered directly
+ to the ``app.cli`` object then they are wrapped with this function
+ by default unless it's disabled.
+ """
+ @click.pass_context
+ def decorator(__ctx, *args, **kwargs):
+ with __ctx.ensure_object(ScriptInfo).load_app().app_context():
+ return __ctx.invoke(f, *args, **kwargs)
+ return update_wrapper(decorator, f)
+
+
+class AppGroup(click.Group):
+ """This works similar to a regular click :class:`~click.Group` but it
+ changes the behavior of the :meth:`command` decorator so that it
+ automatically wraps the functions in :func:`with_appcontext`.
+
+ Not to be confused with :class:`FlaskGroup`.
+ """
+
+ def command(self, *args, **kwargs):
+ """This works exactly like the method of the same name on a regular
+ :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
+ unless it's disabled by passing ``with_appcontext=False``.
+ """
+ wrap_for_ctx = kwargs.pop('with_appcontext', True)
+ def decorator(f):
+ if wrap_for_ctx:
+ f = with_appcontext(f)
+ return click.Group.command(self, *args, **kwargs)(f)
+ return decorator
+
+ def group(self, *args, **kwargs):
+ """This works exactly like the method of the same name on a regular
+ :class:`click.Group` but it defaults the group class to
+ :class:`AppGroup`.
+ """
+ kwargs.setdefault('cls', AppGroup)
+ return click.Group.group(self, *args, **kwargs)
+
+
+class FlaskGroup(AppGroup):
+ """Special subclass of the :class:`AppGroup` group that supports
+ loading more commands from the configured Flask app. Normally a
+ developer does not have to interface with this class but there are
+ some very advanced use cases for which it makes sense to create an
+ instance of this.
+
+ For information as of why this is useful see :ref:`custom-scripts`.
+
+ :param add_default_commands: if this is True then the default run and
+ shell commands wil be added.
+ :param add_version_option: adds the ``--version`` option.
+ :param create_app: an optional callback that is passed the script info and
+ returns the loaded app.
+ :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
+ files to set environment variables. Will also change the working
+ directory to the directory containing the first file found.
+ :param set_debug_flag: Set the app's debug flag based on the active
+ environment
+
+ .. versionchanged:: 1.0
+ If installed, python-dotenv will be used to load environment variables
+ from :file:`.env` and :file:`.flaskenv` files.
+ """
+
+ def __init__(self, add_default_commands=True, create_app=None,
+ add_version_option=True, load_dotenv=True,
+ set_debug_flag=True, **extra):
+ params = list(extra.pop('params', None) or ())
+
+ if add_version_option:
+ params.append(version_option)
+
+ AppGroup.__init__(self, params=params, **extra)
+ self.create_app = create_app
+ self.load_dotenv = load_dotenv
+ self.set_debug_flag = set_debug_flag
+
+ if add_default_commands:
+ self.add_command(run_command)
+ self.add_command(shell_command)
+ self.add_command(routes_command)
+
+ self._loaded_plugin_commands = False
+
+ def _load_plugin_commands(self):
+ if self._loaded_plugin_commands:
+ return
+ try:
+ import pkg_resources
+ except ImportError:
+ self._loaded_plugin_commands = True
+ return
+
+ for ep in pkg_resources.iter_entry_points('flask.commands'):
+ self.add_command(ep.load(), ep.name)
+ self._loaded_plugin_commands = True
+
+ def get_command(self, ctx, name):
+ self._load_plugin_commands()
+
+ # We load built-in commands first as these should always be the
+ # same no matter what the app does. If the app does want to
+ # override this it needs to make a custom instance of this group
+ # and not attach the default commands.
+ #
+ # This also means that the script stays functional in case the
+ # application completely fails.
+ rv = AppGroup.get_command(self, ctx, name)
+ if rv is not None:
+ return rv
+
+ info = ctx.ensure_object(ScriptInfo)
+ try:
+ rv = info.load_app().cli.get_command(ctx, name)
+ if rv is not None:
+ return rv
+ except NoAppException:
+ pass
+
+ def list_commands(self, ctx):
+ self._load_plugin_commands()
+
+ # The commands available is the list of both the application (if
+ # available) plus the builtin commands.
+ rv = set(click.Group.list_commands(self, ctx))
+ info = ctx.ensure_object(ScriptInfo)
+ try:
+ rv.update(info.load_app().cli.list_commands(ctx))
+ except Exception:
+ # Here we intentionally swallow all exceptions as we don't
+ # want the help page to break if the app does not exist.
+ # If someone attempts to use the command we try to create
+ # the app again and this will give us the error.
+ # However, we will not do so silently because that would confuse
+ # users.
+ traceback.print_exc()
+ return sorted(rv)
+
+ def main(self, *args, **kwargs):
+ # Set a global flag that indicates that we were invoked from the
+ # command line interface. This is detected by Flask.run to make the
+ # call into a no-op. This is necessary to avoid ugly errors when the
+ # script that is loaded here also attempts to start a server.
+ os.environ['FLASK_RUN_FROM_CLI'] = 'true'
+
+ if get_load_dotenv(self.load_dotenv):
+ load_dotenv()
+
+ obj = kwargs.get('obj')
+
+ if obj is None:
+ obj = ScriptInfo(create_app=self.create_app,
+ set_debug_flag=self.set_debug_flag)
+
+ kwargs['obj'] = obj
+ kwargs.setdefault('auto_envvar_prefix', 'FLASK')
+ return super(FlaskGroup, self).main(*args, **kwargs)
+
+
+def _path_is_ancestor(path, other):
+ """Take ``other`` and remove the length of ``path`` from it. Then join it
+ to ``path``. If it is the original value, ``path`` is an ancestor of
+ ``other``."""
+ return os.path.join(path, other[len(path):].lstrip(os.sep)) == other
+
+
+def load_dotenv(path=None):
+ """Load "dotenv" files in order of precedence to set environment variables.
+
+ If an env var is already set it is not overwritten, so earlier files in the
+ list are preferred over later files.
+
+ Changes the current working directory to the location of the first file
+ found, with the assumption that it is in the top level project directory
+ and will be where the Python path should import local packages from.
+
+ This is a no-op if `python-dotenv`_ is not installed.
+
+ .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
+
+ :param path: Load the file at this location instead of searching.
+ :return: ``True`` if a file was loaded.
+
+ .. versionadded:: 1.0
+ """
+ if dotenv is None:
+ if path or os.path.exists('.env') or os.path.exists('.flaskenv'):
+ click.secho(
+ ' * Tip: There are .env files present.'
+ ' Do "pip install python-dotenv" to use them.',
+ fg='yellow')
+ return
+
+ if path is not None:
+ return dotenv.load_dotenv(path)
+
+ new_dir = None
+
+ for name in ('.env', '.flaskenv'):
+ path = dotenv.find_dotenv(name, usecwd=True)
+
+ if not path:
+ continue
+
+ if new_dir is None:
+ new_dir = os.path.dirname(path)
+
+ dotenv.load_dotenv(path)
+
+ if new_dir and os.getcwd() != new_dir:
+ os.chdir(new_dir)
+
+ return new_dir is not None # at least one file was located and loaded
+
+
+def show_server_banner(env, debug, app_import_path, eager_loading):
+ """Show extra startup messages the first time the server is run,
+ ignoring the reloader.
+ """
+ if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
+ return
+
+ if app_import_path is not None:
+ message = ' * Serving Flask app "{0}"'.format(app_import_path)
+
+ if not eager_loading:
+ message += ' (lazy loading)'
+
+ click.echo(message)
+
+ click.echo(' * Environment: {0}'.format(env))
+
+ if env == 'production':
+ click.secho(
+ ' WARNING: This is a development server. '
+ 'Do not use it in a production deployment.', fg='red')
+ click.secho(' Use a production WSGI server instead.', dim=True)
+
+ if debug is not None:
+ click.echo(' * Debug mode: {0}'.format('on' if debug else 'off'))
+
+
+class CertParamType(click.ParamType):
+ """Click option type for the ``--cert`` option. Allows either an
+ existing file, the string ``'adhoc'``, or an import for a
+ :class:`~ssl.SSLContext` object.
+ """
+
+ name = 'path'
+
+ def __init__(self):
+ self.path_type = click.Path(
+ exists=True, dir_okay=False, resolve_path=True)
+
+ def convert(self, value, param, ctx):
+ try:
+ return self.path_type(value, param, ctx)
+ except click.BadParameter:
+ value = click.STRING(value, param, ctx).lower()
+
+ if value == 'adhoc':
+ try:
+ import OpenSSL
+ except ImportError:
+ raise click.BadParameter(
+ 'Using ad-hoc certificates requires pyOpenSSL.',
+ ctx, param)
+
+ return value
+
+ obj = import_string(value, silent=True)
+
+ if sys.version_info < (2, 7, 9):
+ if obj:
+ return obj
+ else:
+ if isinstance(obj, ssl.SSLContext):
+ return obj
+
+ raise
+
+
+def _validate_key(ctx, param, value):
+ """The ``--key`` option must be specified when ``--cert`` is a file.
+ Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
+ """
+ cert = ctx.params.get('cert')
+ is_adhoc = cert == 'adhoc'
+
+ if sys.version_info < (2, 7, 9):
+ is_context = cert and not isinstance(cert, (text_type, bytes))
+ else:
+ is_context = isinstance(cert, ssl.SSLContext)
+
+ if value is not None:
+ if is_adhoc:
+ raise click.BadParameter(
+ 'When "--cert" is "adhoc", "--key" is not used.',
+ ctx, param)
+
+ if is_context:
+ raise click.BadParameter(
+ 'When "--cert" is an SSLContext object, "--key is not used.',
+ ctx, param)
+
+ if not cert:
+ raise click.BadParameter(
+ '"--cert" must also be specified.',
+ ctx, param)
+
+ ctx.params['cert'] = cert, value
+
+ else:
+ if cert and not (is_adhoc or is_context):
+ raise click.BadParameter(
+ 'Required when using "--cert".',
+ ctx, param)
+
+ return value
+
+
+@click.command('run', short_help='Run a development server.')
+@click.option('--host', '-h', default='127.0.0.1',
+ help='The interface to bind to.')
+@click.option('--port', '-p', default=5000,
+ help='The port to bind to.')
+@click.option('--cert', type=CertParamType(),
+ help='Specify a certificate file to use HTTPS.')
+@click.option('--key',
+ type=click.Path(exists=True, dir_okay=False, resolve_path=True),
+ callback=_validate_key, expose_value=False,
+ help='The key file to use when specifying a certificate.')
+@click.option('--reload/--no-reload', default=None,
+ help='Enable or disable the reloader. By default the reloader '
+ 'is active if debug is enabled.')
+@click.option('--debugger/--no-debugger', default=None,
+ help='Enable or disable the debugger. By default the debugger '
+ 'is active if debug is enabled.')
+@click.option('--eager-loading/--lazy-loader', default=None,
+ help='Enable or disable eager loading. By default eager '
+ 'loading is enabled if the reloader is disabled.')
+@click.option('--with-threads/--without-threads', default=True,
+ help='Enable or disable multithreading.')
+@pass_script_info
+def run_command(info, host, port, reload, debugger, eager_loading,
+ with_threads, cert):
+ """Run a local development server.
+
+ This server is for development purposes only. It does not provide
+ the stability, security, or performance of production WSGI servers.
+
+ The reloader and debugger are enabled by default if
+ FLASK_ENV=development or FLASK_DEBUG=1.
+ """
+ debug = get_debug_flag()
+
+ if reload is None:
+ reload = debug
+
+ if debugger is None:
+ debugger = debug
+
+ if eager_loading is None:
+ eager_loading = not reload
+
+ show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
+ app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
+
+ from werkzeug.serving import run_simple
+ run_simple(host, port, app, use_reloader=reload, use_debugger=debugger,
+ threaded=with_threads, ssl_context=cert)
+
+
+@click.command('shell', short_help='Run a shell in the app context.')
+@with_appcontext
+def shell_command():
+ """Run an interactive Python shell in the context of a given
+ Flask application. The application will populate the default
+ namespace of this shell according to it's configuration.
+
+ This is useful for executing small snippets of management code
+ without having to manually configure the application.
+ """
+ import code
+ from flask.globals import _app_ctx_stack
+ app = _app_ctx_stack.top.app
+ banner = 'Python %s on %s\nApp: %s [%s]\nInstance: %s' % (
+ sys.version,
+ sys.platform,
+ app.import_name,
+ app.env,
+ app.instance_path,
+ )
+ ctx = {}
+
+ # Support the regular Python interpreter startup script if someone
+ # is using it.
+ startup = os.environ.get('PYTHONSTARTUP')
+ if startup and os.path.isfile(startup):
+ with open(startup, 'r') as f:
+ eval(compile(f.read(), startup, 'exec'), ctx)
+
+ ctx.update(app.make_shell_context())
+
+ code.interact(banner=banner, local=ctx)
+
+
+@click.command('routes', short_help='Show the routes for the app.')
+@click.option(
+ '--sort', '-s',
+ type=click.Choice(('endpoint', 'methods', 'rule', 'match')),
+ default='endpoint',
+ help=(
+ 'Method to sort routes by. "match" is the order that Flask will match '
+ 'routes when dispatching a request.'
+ )
+)
+@click.option(
+ '--all-methods',
+ is_flag=True,
+ help="Show HEAD and OPTIONS methods."
+)
+@with_appcontext
+def routes_command(sort, all_methods):
+ """Show all registered routes with endpoints and methods."""
+
+ rules = list(current_app.url_map.iter_rules())
+ if not rules:
+ click.echo('No routes were registered.')
+ return
+
+ ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
+
+ if sort in ('endpoint', 'rule'):
+ rules = sorted(rules, key=attrgetter(sort))
+ elif sort == 'methods':
+ rules = sorted(rules, key=lambda rule: sorted(rule.methods))
+
+ rule_methods = [
+ ', '.join(sorted(rule.methods - ignored_methods)) for rule in rules
+ ]
+
+ headers = ('Endpoint', 'Methods', 'Rule')
+ widths = (
+ max(len(rule.endpoint) for rule in rules),
+ max(len(methods) for methods in rule_methods),
+ max(len(rule.rule) for rule in rules),
+ )
+ widths = [max(len(h), w) for h, w in zip(headers, widths)]
+ row = '{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}'.format(*widths)
+
+ click.echo(row.format(*headers).strip())
+ click.echo(row.format(*('-' * width for width in widths)))
+
+ for rule, methods in zip(rules, rule_methods):
+ click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
+
+
+cli = FlaskGroup(help="""\
+A general utility script for Flask applications.
+
+Provides commands from Flask, extensions, and the application. Loads the
+application defined in the FLASK_APP environment variable, or from a wsgi.py
+file. Setting the FLASK_ENV environment variable to 'development' will enable
+debug mode.
+
+\b
+ {prefix}{cmd} FLASK_APP=hello.py
+ {prefix}{cmd} FLASK_ENV=development
+ {prefix}flask run
+""".format(
+ cmd='export' if os.name == 'posix' else 'set',
+ prefix='$ ' if os.name == 'posix' else '> '
+))
+
+
+def main(as_module=False):
+ args = sys.argv[1:]
+
+ if as_module:
+ this_module = 'flask'
+
+ if sys.version_info < (2, 7):
+ this_module += '.cli'
+
+ name = 'python -m ' + this_module
+
+ # Python rewrites "python -m flask" to the path to the file in argv.
+ # Restore the original command so that the reloader works.
+ sys.argv = ['-m', this_module] + args
+ else:
+ name = None
+
+ cli.main(args=args, prog_name=name)
+
+
+if __name__ == '__main__':
+ main(as_module=True)