Skip to content

Commit

Permalink
Implemented simplified CLI interface
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed May 26, 2016
1 parent 92f63a1 commit 523e271
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 91 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
all: clean-pyc test

test:
py.test tests examples
FLASK_DEBUG= py.test tests examples

tox-test:
tox
Expand Down
5 changes: 3 additions & 2 deletions flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
MethodNotAllowed, BadRequest, default_exceptions

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, \
get_debug_flag
from . import json, cli
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
Expand Down Expand Up @@ -289,7 +290,7 @@ def _set_request_globals_class(self, value):

#: Default configuration parameters.
default_config = ImmutableDict({
'DEBUG': False,
'DEBUG': get_debug_flag(default=False),
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
Expand Down
124 changes: 39 additions & 85 deletions flask/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import click

from ._compat import iteritems, reraise
from .helpers import get_debug_flag


class NoAppException(click.UsageError):
Expand Down Expand Up @@ -98,6 +99,15 @@ def locate_app(app_id):
return app


def find_default_import_path():
app = os.environ.get('FLASK_APP')
if app is None:
return
if os.path.isfile(app):
return prepare_exec_for_file(app)
return app


class DispatchingApp(object):
"""Special application that dispatches to a flask application which
is imported by name in a background thread. If an error happens
Expand Down Expand Up @@ -158,12 +168,13 @@ class ScriptInfo(object):
to click.
"""

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
def __init__(self, app_import_path=None, create_app=None):
if create_app is None:
if app_import_path is None:
app_import_path = find_default_import_path()
self.app_import_path = app_import_path
else:
self.app_import_path = None
#: Optionally a function that is passed the script info to create
#: the instance of the application.
self.create_app = create_app
Expand All @@ -185,11 +196,12 @@ def load_app(self):
else:
if self.app_import_path is None:
raise NoAppException('Could not locate Flask application. '
'You did not provide FLASK_APP or the '
'--app parameter.')
'You did not provide the FLASK_APP '
'environment variable.')
rv = locate_app(self.app_import_path)
if self.debug is not None:
rv.debug = self.debug
debug = get_debug_flag()
if debug is not None:
rv.debug = debug
self._loaded_app = rv
return rv

Expand All @@ -210,29 +222,6 @@ def decorator(__ctx, *args, **kwargs):
return update_wrapper(decorator, f)


def set_debug_value(ctx, param, value):
ctx.ensure_object(ScriptInfo).debug = value


def set_app_value(ctx, param, 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 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
Expand Down Expand Up @@ -273,25 +262,12 @@ class FlaskGroup(AppGroup):
: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 :option:`--app` option. This gets
automatically disabled if a `create_app`
callback is defined.
:param add_debug_option: adds the default :option:`--debug` option.
:param create_app: an optional callback that is passed the script info
and returns the loaded app.
"""

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)

AppGroup.__init__(self, params=params, **extra)
def __init__(self, add_default_commands=True, create_app=None, **extra):
AppGroup.__init__(self, **extra)
self.create_app = create_app

if add_default_commands:
Expand Down Expand Up @@ -342,33 +318,6 @@ def main(self, *args, **kwargs):
return AppGroup.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, param, 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.')
Expand Down Expand Up @@ -400,10 +349,12 @@ def run_command(info, host, port, reload, debugger, eager_loading,
Flask is enabled and disabled otherwise.
"""
from werkzeug.serving import run_simple

debug = get_debug_flag()
if reload is None:
reload = info.debug
reload = bool(debug)
if debugger is None:
debugger = info.debug
debugger = bool(debug)
if eager_loading is None:
eager_loading = not reload

Expand All @@ -418,12 +369,9 @@ def run_command(info, host, port, reload, debugger, eager_loading,
# we won't print anything.
if info.app_import_path is not None:
print(' * Serving Flask app "%s"' % info.app_import_path)
if info.debug is not None:
print(' * Forcing debug %s' % (info.debug and 'on' or 'off'))

run_simple(host, port, app, use_reloader=reload,
use_debugger=debugger, threaded=with_threads,
passthrough_errors=True)
use_debugger=debugger, threaded=with_threads)


@click.command('shell', short_help='Runs a shell in the app context.')
Expand Down Expand Up @@ -464,15 +412,21 @@ def shell_command():
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.
variable) and then provides commands either provided by the application or
Flask itself.
The most useful commands are the "run" and "shell" command.
Example usage:
flask --app=hello --debug run
""")
\b
%(prefix)s%(cmd)s FLASK_APP=hello
%(prefix)s%(cmd)s FLASK_DEBUG=1
%(prefix)sflask run
""" % {
'cmd': os.name == 'posix' and 'export' or 'set',
'prefix': os.name == 'posix' and '$ ' or '',
})


def main(as_module=False):
Expand Down
8 changes: 7 additions & 1 deletion flask/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import pkgutil
import posixpath
import mimetypes
from datetime import timedelta
from time import time
from zlib import adler32
from threading import RLock
Expand Down Expand Up @@ -54,6 +53,13 @@
if sep not in (None, '/'))


def get_debug_flag(default=None):
val = os.environ.get('FLASK_DEBUG')
if not val:
return default
return val not in ('0', 'false', 'no')


def _endpoint_from_view_func(view_func):
"""Internal helper that returns the default endpoint for a given
function. This always is the function name.
Expand Down
3 changes: 1 addition & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from flask import Flask, current_app

from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
find_best_app, locate_app, script_info_option, with_appcontext
find_best_app, locate_app, with_appcontext


def test_cli_name(test_apps):
Expand Down Expand Up @@ -123,7 +123,6 @@ def create_app(info):
return Flask("flaskgroup")

@click.group(cls=FlaskGroup, create_app=create_app)
@script_info_option('--config', script_info_key='config')
def cli(**params):
pass

Expand Down

0 comments on commit 523e271

Please sign in to comment.