Refactored the scripting interface greatly.
This commit is contained in:
parent
66ef55ce0a
commit
932f7d7cbb
4 changed files with 65 additions and 36 deletions
|
|
@ -762,9 +762,14 @@ Command Line Interface
|
||||||
.. autoclass:: FlaskGroup
|
.. autoclass:: FlaskGroup
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: AppGroup
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: ScriptInfo
|
.. autoclass:: ScriptInfo
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autofunction:: with_appcontext
|
||||||
|
|
||||||
.. autofunction:: pass_script_info
|
.. autofunction:: pass_script_info
|
||||||
|
|
||||||
.. autofunction:: without_appcontext
|
.. autofunction:: without_appcontext
|
||||||
|
|
@ -774,8 +779,7 @@ Command Line Interface
|
||||||
A special decorator that informs a click callback to be passed the
|
A special decorator that informs a click callback to be passed the
|
||||||
script info object as first argument. This is normally not useful
|
script info object as first argument. This is normally not useful
|
||||||
unless you implement very special commands like the run command which
|
unless you implement very special commands like the run command which
|
||||||
does not want the application to be loaded yet. This can be combined
|
does not want the application to be loaded yet.
|
||||||
with the :func:`without_appcontext` decorator.
|
|
||||||
|
|
||||||
.. autodata:: run_command
|
.. autodata:: run_command
|
||||||
|
|
||||||
|
|
|
||||||
18
docs/cli.rst
18
docs/cli.rst
|
|
@ -100,6 +100,24 @@ The command will then show up on the command line::
|
||||||
$ flask -a hello.py initdb
|
$ flask -a hello.py initdb
|
||||||
Init the db
|
Init the db
|
||||||
|
|
||||||
|
Application Context
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Most commands operate on the application so it makes a lot of sense if
|
||||||
|
they have the application context setup. Because of this, if you register
|
||||||
|
a callback on ``app.cli`` with the :meth:`~flask.cli.AppGroup.command` the
|
||||||
|
callback will automatically be wrapped through :func:`cli.with_appcontext`
|
||||||
|
which informs the cli system to ensure that an application context is set
|
||||||
|
up. This behavior is not available if a command is lated later with
|
||||||
|
:func:`~click.Group.add_command` or through other means.
|
||||||
|
|
||||||
|
It can also be disabled by passing ``with_appcontext=False`` to the
|
||||||
|
decorator::
|
||||||
|
|
||||||
|
@app.cli.command(with_appcontext=False)
|
||||||
|
def example():
|
||||||
|
pass
|
||||||
|
|
||||||
Factory Functions
|
Factory Functions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \
|
||||||
|
|
||||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
||||||
locked_cached_property, _endpoint_from_view_func, find_package
|
locked_cached_property, _endpoint_from_view_func, find_package
|
||||||
from . import json
|
from . import json, cli
|
||||||
from .wrappers import Request, Response
|
from .wrappers import Request, Response
|
||||||
from .config import ConfigAttribute, Config
|
from .config import ConfigAttribute, Config
|
||||||
from .ctx import RequestContext, AppContext, _AppCtxGlobals
|
from .ctx import RequestContext, AppContext, _AppCtxGlobals
|
||||||
|
|
@ -544,7 +544,7 @@ class Flask(_PackageBoundObject):
|
||||||
#: provided by Flask itself and can be overridden.
|
#: provided by Flask itself and can be overridden.
|
||||||
#:
|
#:
|
||||||
#: This is an instance of a :class:`click.Group` object.
|
#: This is an instance of a :class:`click.Group` object.
|
||||||
self.cli = click.Group(self)
|
self.cli = cli.AppGroup(self)
|
||||||
|
|
||||||
def _get_error_handlers(self):
|
def _get_error_handlers(self):
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
|
||||||
71
flask/cli.py
71
flask/cli.py
|
|
@ -12,7 +12,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from contextlib import contextmanager
|
from functools import update_wrapper
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
|
@ -166,30 +166,21 @@ class ScriptInfo(object):
|
||||||
self._loaded_app = rv
|
self._loaded_app = rv
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def conditional_context(self, with_context=True):
|
pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
|
||||||
"""Creates an application context or not, depending on the given
|
|
||||||
parameter but always works as context manager. This is just a
|
|
||||||
shortcut for a common operation.
|
|
||||||
"""
|
|
||||||
if with_context:
|
|
||||||
with self.load_app().app_context() as ctx:
|
|
||||||
yield ctx
|
|
||||||
else:
|
|
||||||
yield None
|
|
||||||
|
|
||||||
|
|
||||||
pass_script_info = click.make_pass_decorator(ScriptInfo)
|
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
|
||||||
def without_appcontext(f):
|
to the ``app.cli`` object then they are wrapped with this function
|
||||||
"""Marks a click callback so that it does not get a app context
|
by default unless it's disabled.
|
||||||
created. This only works for commands directly registered to
|
|
||||||
the toplevel system. This really is only useful for very
|
|
||||||
special commands like the runserver one.
|
|
||||||
"""
|
"""
|
||||||
f.__flask_without_appcontext__ = True
|
@click.pass_context
|
||||||
return f
|
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)
|
||||||
|
|
||||||
|
|
||||||
def set_debug_value(ctx, param, value):
|
def set_debug_value(ctx, param, value):
|
||||||
|
|
@ -220,7 +211,7 @@ class FlaskGroup(click.Group):
|
||||||
more commands from the configured Flask app. Normally a developer
|
more commands from the configured Flask app. Normally a developer
|
||||||
does not have to interface with this class but there are some very
|
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
|
advanced usecases for which it makes sense to create an instance of
|
||||||
this.
|
this. Not to be confused with :class:`AppGroup`.
|
||||||
|
|
||||||
For information as of why this is useful see :ref:`custom-scripts`.
|
For information as of why this is useful see :ref:`custom-scripts`.
|
||||||
|
|
||||||
|
|
@ -286,14 +277,6 @@ class FlaskGroup(click.Group):
|
||||||
pass
|
pass
|
||||||
return sorted(rv)
|
return sorted(rv)
|
||||||
|
|
||||||
def invoke_subcommand(self, ctx, cmd, cmd_name, args):
|
|
||||||
with_context = cmd.callback is None or \
|
|
||||||
not getattr(cmd.callback, '__flask_without_appcontext__', False)
|
|
||||||
|
|
||||||
with ctx.find_object(ScriptInfo).conditional_context(with_context):
|
|
||||||
return click.Group.invoke_subcommand(
|
|
||||||
self, ctx, cmd, cmd_name, args)
|
|
||||||
|
|
||||||
def main(self, *args, **kwargs):
|
def main(self, *args, **kwargs):
|
||||||
obj = kwargs.get('obj')
|
obj = kwargs.get('obj')
|
||||||
if obj is None:
|
if obj is None:
|
||||||
|
|
@ -303,6 +286,30 @@ class FlaskGroup(click.Group):
|
||||||
return click.Group.main(self, *args, **kwargs)
|
return click.Group.main(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
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 __init__(self, app):
|
||||||
|
click.Group.__init__(self)
|
||||||
|
|
||||||
|
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(*args, **kwargs)(f)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def script_info_option(*args, **kwargs):
|
def script_info_option(*args, **kwargs):
|
||||||
"""This decorator works exactly like :func:`click.option` but is eager
|
"""This decorator works exactly like :func:`click.option` but is eager
|
||||||
by default and stores the value in the :attr:`ScriptInfo.data`. This
|
by default and stores the value in the :attr:`ScriptInfo.data`. This
|
||||||
|
|
@ -346,7 +353,6 @@ def script_info_option(*args, **kwargs):
|
||||||
'loading is enabled if the reloader is disabled.')
|
'loading is enabled if the reloader is disabled.')
|
||||||
@click.option('--with-threads/--without-threads', default=False,
|
@click.option('--with-threads/--without-threads', default=False,
|
||||||
help='Enable or disable multithreading.')
|
help='Enable or disable multithreading.')
|
||||||
@without_appcontext
|
|
||||||
@pass_script_info
|
@pass_script_info
|
||||||
def run_command(info, host, port, reload, debugger, eager_loading,
|
def run_command(info, host, port, reload, debugger, eager_loading,
|
||||||
with_threads):
|
with_threads):
|
||||||
|
|
@ -388,6 +394,7 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
||||||
|
|
||||||
|
|
||||||
@click.command('shell', short_help='Runs a shell in the app context.')
|
@click.command('shell', short_help='Runs a shell in the app context.')
|
||||||
|
@with_appcontext
|
||||||
def shell_command():
|
def shell_command():
|
||||||
"""Runs an interactive Python shell in the context of a given
|
"""Runs an interactive Python shell in the context of a given
|
||||||
Flask application. The application will populate the default
|
Flask application. The application will populate the default
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue