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
|
||||
:members:
|
||||
|
||||
.. autoclass:: AppGroup
|
||||
:members:
|
||||
|
||||
.. autoclass:: ScriptInfo
|
||||
:members:
|
||||
|
||||
.. autofunction:: with_appcontext
|
||||
|
||||
.. autofunction:: pass_script_info
|
||||
|
||||
.. autofunction:: without_appcontext
|
||||
|
|
@ -774,8 +779,7 @@ Command Line Interface
|
|||
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.
|
||||
does not want the application to be loaded yet.
|
||||
|
||||
.. 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
|
||||
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
|
||||
-----------------
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \
|
|||
|
||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
||||
locked_cached_property, _endpoint_from_view_func, find_package
|
||||
from . import json
|
||||
from . import json, cli
|
||||
from .wrappers import Request, Response
|
||||
from .config import ConfigAttribute, Config
|
||||
from .ctx import RequestContext, AppContext, _AppCtxGlobals
|
||||
|
|
@ -544,7 +544,7 @@ class Flask(_PackageBoundObject):
|
|||
#: provided by Flask itself and can be overridden.
|
||||
#:
|
||||
#: 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):
|
||||
from warnings import warn
|
||||
|
|
|
|||
71
flask/cli.py
71
flask/cli.py
|
|
@ -12,7 +12,7 @@
|
|||
import os
|
||||
import sys
|
||||
from threading import Lock
|
||||
from contextlib import contextmanager
|
||||
from functools import update_wrapper
|
||||
|
||||
import click
|
||||
|
||||
|
|
@ -166,30 +166,21 @@ class ScriptInfo(object):
|
|||
self._loaded_app = rv
|
||||
return rv
|
||||
|
||||
@contextmanager
|
||||
def conditional_context(self, with_context=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, ensure=True)
|
||||
|
||||
|
||||
pass_script_info = click.make_pass_decorator(ScriptInfo)
|
||||
|
||||
|
||||
def without_appcontext(f):
|
||||
"""Marks a click callback so that it does not get a app context
|
||||
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.
|
||||
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.
|
||||
"""
|
||||
f.__flask_without_appcontext__ = True
|
||||
return f
|
||||
@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)
|
||||
|
||||
|
||||
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
|
||||
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.
|
||||
this. Not to be confused with :class:`AppGroup`.
|
||||
|
||||
For information as of why this is useful see :ref:`custom-scripts`.
|
||||
|
||||
|
|
@ -286,14 +277,6 @@ class FlaskGroup(click.Group):
|
|||
pass
|
||||
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):
|
||||
obj = kwargs.get('obj')
|
||||
if obj is None:
|
||||
|
|
@ -303,6 +286,30 @@ class FlaskGroup(click.Group):
|
|||
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):
|
||||
"""This decorator works exactly like :func:`click.option` but is eager
|
||||
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.')
|
||||
@click.option('--with-threads/--without-threads', default=False,
|
||||
help='Enable or disable multithreading.')
|
||||
@without_appcontext
|
||||
@pass_script_info
|
||||
def run_command(info, host, port, reload, debugger, eager_loading,
|
||||
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.')
|
||||
@with_appcontext
|
||||
def shell_command():
|
||||
"""Runs an interactive Python shell in the context of a given
|
||||
Flask application. The application will populate the default
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue