Browse Source

Refactored the scripting interface greatly.

pull/1156/head
Armin Ronacher 11 years ago
parent
commit
932f7d7cbb
  1. 8
      docs/api.rst
  2. 18
      docs/cli.rst
  3. 4
      flask/app.py
  4. 69
      flask/cli.py

8
docs/api.rst

@ -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

@ -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
-----------------

4
flask/app.py

@ -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

69
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)
pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
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…
Cancel
Save