forked from orbit-oss/flask
Greatly refactored click integration and documented it a bit more.
This commit is contained in:
parent
81576c236a
commit
3569fc2441
4 changed files with 249 additions and 82 deletions
188
flask/cli.py
188
flask/cli.py
|
|
@ -81,7 +81,7 @@ def prepare_exec_for_file(filename):
|
|||
return '.'.join(module[::-1])
|
||||
|
||||
|
||||
def locate_app(app_id, debug=None):
|
||||
def locate_app(app_id):
|
||||
"""Attempts to locate the application."""
|
||||
if ':' in app_id:
|
||||
module, app_obj = app_id.split(':', 1)
|
||||
|
|
@ -98,8 +98,7 @@ def locate_app(app_id, debug=None):
|
|||
if app is None:
|
||||
raise RuntimeError('Failed to find application in module "%s"'
|
||||
% module)
|
||||
if debug is not None:
|
||||
app.debug = debug
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
|
@ -145,13 +144,24 @@ class ScriptInfo(object):
|
|||
"""
|
||||
|
||||
def __init__(self, app_import_path=None, debug=None, create_app=None):
|
||||
#: The application import path
|
||||
self.app_import_path = app_import_path
|
||||
#: The debug flag. If this is not None, the application will
|
||||
#: automatically have it's debug flag overridden with this value.
|
||||
self.debug = debug
|
||||
#: 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._loaded_app = None
|
||||
|
||||
def load_app(self):
|
||||
"""Loads the Flask app (if not yet loaded) and returns it."""
|
||||
"""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.
|
||||
"""
|
||||
if self._loaded_app is not None:
|
||||
return self._loaded_app
|
||||
if self.create_app is not None:
|
||||
|
|
@ -159,29 +169,12 @@ class ScriptInfo(object):
|
|||
else:
|
||||
if self.app_import_path is None:
|
||||
_no_such_app()
|
||||
rv = locate_app(self.app_import_path, self.debug)
|
||||
rv = locate_app(self.app_import_path)
|
||||
if self.debug is not None:
|
||||
rv.debug = self.debug
|
||||
self._loaded_app = rv
|
||||
return rv
|
||||
|
||||
def make_wsgi_app(self, use_eager_loading=False):
|
||||
"""Returns a WSGI app that loads the actual application at a later
|
||||
stage (on first request). This has the advantage over
|
||||
:meth:`load_app` that if used with a WSGI server, it will allow
|
||||
the server to intercept errors later during request handling
|
||||
instead of dying a horrible death.
|
||||
|
||||
If eager loading is disabled the loading will happen immediately.
|
||||
"""
|
||||
if self.app_import_path is not None:
|
||||
def loader():
|
||||
return locate_app(self.app_import_path, self.debug)
|
||||
else:
|
||||
if self.create_app is None:
|
||||
_no_such_app()
|
||||
def loader():
|
||||
return self.create_app(self)
|
||||
return DispatchingApp(loader, use_eager_loading=use_eager_loading)
|
||||
|
||||
@contextmanager
|
||||
def conditional_context(self, with_context=True):
|
||||
"""Creates an application context or not, depending on the given
|
||||
|
|
@ -189,17 +182,12 @@ class ScriptInfo(object):
|
|||
shortcut for a common operation.
|
||||
"""
|
||||
if with_context:
|
||||
with self.load_app(self).app_context() as ctx:
|
||||
with self.load_app().app_context() as ctx:
|
||||
yield ctx
|
||||
else:
|
||||
yield None
|
||||
|
||||
|
||||
#: A special decorator that informs a click callback to be passed the
|
||||
#: script info object as first argument. This is normally not useful
|
||||
#: unless you implement very special commands like the run command which
|
||||
#: does not want the application to be loaded yet. This can be combined
|
||||
#: with the :func:`without_appcontext` decorator.
|
||||
pass_script_info = click.make_pass_decorator(ScriptInfo)
|
||||
|
||||
|
||||
|
|
@ -213,54 +201,65 @@ def without_appcontext(f):
|
|||
return f
|
||||
|
||||
|
||||
class FlaskGroup(click.Group):
|
||||
"""Special subclass of the a regular click 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 usecases for which it makes sense to create an
|
||||
instance of this.
|
||||
def set_debug_value(ctx, value):
|
||||
ctx.ensure_object(ScriptInfo).debug = value
|
||||
|
||||
:param add_default_options: if this is True the app and debug option
|
||||
is automatically added.
|
||||
|
||||
def set_app_value(ctx, value):
|
||||
if value is not None:
|
||||
if os.path.isfile(value):
|
||||
value = prepare_exec_for_file(value)
|
||||
elif '.' not in sys.path:
|
||||
sys.path.insert(0, '.')
|
||||
ctx.ensure_object(ScriptInfo).app_import_path = value
|
||||
|
||||
|
||||
debug_option = click.Option(['--debug/--no-debug'],
|
||||
help='Enable or disable debug mode.',
|
||||
default=None, callback=set_debug_value)
|
||||
|
||||
|
||||
app_option = click.Option(['-a', '--app'],
|
||||
help='The application to run',
|
||||
callback=set_app_value, is_eager=True)
|
||||
|
||||
|
||||
class FlaskGroup(click.Group):
|
||||
"""Special subclass of the a regular click 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 usecases 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_app_option: adds the default ``--app`` option. This gets
|
||||
automatically disabled if a `create_app`
|
||||
callback is defined.
|
||||
:param add_debug_option: adds the default ``--debug`` option.
|
||||
:param create_app: an optional callback that is passed the script info
|
||||
and returns the loaded app.
|
||||
"""
|
||||
|
||||
def __init__(self, add_default_options=True,
|
||||
add_default_commands=True,
|
||||
create_app=None, **extra):
|
||||
click.Group.__init__(self, **extra)
|
||||
def __init__(self, add_default_commands=True, add_app_option=None,
|
||||
add_debug_option=True, create_app=None, **extra):
|
||||
params = list(extra.pop('params', None) or ())
|
||||
if add_app_option is None:
|
||||
add_app_option = create_app is None
|
||||
if add_app_option:
|
||||
params.append(app_option)
|
||||
if add_debug_option:
|
||||
params.append(debug_option)
|
||||
|
||||
click.Group.__init__(self, params=params, **extra)
|
||||
self.create_app = create_app
|
||||
if add_default_options:
|
||||
self.add_app_option()
|
||||
self.add_debug_option()
|
||||
|
||||
if add_default_commands:
|
||||
self.add_command(run_command)
|
||||
self.add_command(shell_command)
|
||||
|
||||
def add_app_option(self):
|
||||
"""Adds an option to the default command that defines an import
|
||||
path that points to an application.
|
||||
"""
|
||||
def set_app_id(ctx, value):
|
||||
if value is not None:
|
||||
if os.path.isfile(value):
|
||||
value = prepare_exec_for_file(value)
|
||||
elif '.' not in sys.path:
|
||||
sys.path.insert(0, '.')
|
||||
ctx.ensure_object(ScriptInfo).app_import_path = value
|
||||
|
||||
self.params.append(click.Option(['-a', '--app'],
|
||||
help='The application to run',
|
||||
callback=set_app_id, is_eager=True))
|
||||
|
||||
def add_debug_option(self):
|
||||
"""Adds an option that controls the debug flag."""
|
||||
def set_debug(ctx, value):
|
||||
ctx.ensure_object(ScriptInfo).debug = value
|
||||
|
||||
self.params.append(click.Option(['--debug/--no-debug'],
|
||||
help='Enable or disable debug mode.',
|
||||
default=None, callback=set_debug))
|
||||
|
||||
def get_command(self, ctx, name):
|
||||
info = ctx.ensure_object(ScriptInfo)
|
||||
# Find the command in the application first, if we can find it.
|
||||
|
|
@ -301,6 +300,33 @@ class FlaskGroup(click.Group):
|
|||
return click.Group.main(self, *args, **kwargs)
|
||||
|
||||
|
||||
def script_info_option(*args, **kwargs):
|
||||
"""This decorator works exactly like :func:`click.option` but is eager
|
||||
by default and stores the value in the :attr:`ScriptInfo.data`. This
|
||||
is useful to further customize an application factory in very complex
|
||||
situations.
|
||||
|
||||
:param script_info_key: this is a mandatory keyword argument which
|
||||
defines under which data key the value should
|
||||
be stored.
|
||||
"""
|
||||
try:
|
||||
key = kwargs.pop('script_info_key')
|
||||
except LookupError:
|
||||
raise TypeError('script_info_key not provided.')
|
||||
|
||||
real_callback = kwargs.get('callback')
|
||||
def callback(ctx, value):
|
||||
if real_callback is not None:
|
||||
value = real_callback(ctx, value)
|
||||
ctx.ensure_object(ScriptInfo).data[key] = value
|
||||
return value
|
||||
|
||||
kwargs['callback'] = callback
|
||||
kwargs.setdefault('is_eager', True)
|
||||
return click.option(*args, **kwargs)
|
||||
|
||||
|
||||
@click.command('run', short_help='Runs a development server.')
|
||||
@click.option('--host', '-h', default='127.0.0.1',
|
||||
help='The interface to bind to.')
|
||||
|
|
@ -340,7 +366,7 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
|||
if eager_loading is None:
|
||||
eager_loading = not reload
|
||||
|
||||
app = info.make_wsgi_app(use_eager_loading=eager_loading)
|
||||
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
|
||||
|
||||
# Extra startup messages. This depends a but on Werkzeug internals to
|
||||
# not double execute when the reloader kicks in.
|
||||
|
|
@ -388,19 +414,21 @@ def make_default_cli(app):
|
|||
return click.Group()
|
||||
|
||||
|
||||
cli = FlaskGroup(help='''\
|
||||
This shell command acts as general utility script for Flask applications.
|
||||
@click.group(cls=FlaskGroup)
|
||||
def cli(**params):
|
||||
"""
|
||||
This shell command acts as general utility script for Flask applications.
|
||||
|
||||
It loads the application configured (either through the FLASK_APP environment
|
||||
variable or the --app parameter) and then provides commands either provided
|
||||
by the application or Flask itself.
|
||||
It loads the application configured (either through the FLASK_APP environment
|
||||
variable or the --app parameter) and then provides commands either provided
|
||||
by the application or Flask itself.
|
||||
|
||||
The most useful commands are the "run" and "shell" command.
|
||||
The most useful commands are the "run" and "shell" command.
|
||||
|
||||
Example usage:
|
||||
Example usage:
|
||||
|
||||
flask --app=hello --debug run
|
||||
''')
|
||||
flask --app=hello --debug run
|
||||
"""
|
||||
|
||||
|
||||
def main(as_module=False):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue