Browse Source

Implemented simplified CLI interface

pull/1820/head
Armin Ronacher 8 years ago
parent
commit
523e271183
  1. 2
      Makefile
  2. 5
      flask/app.py
  3. 124
      flask/cli.py
  4. 8
      flask/helpers.py
  5. 3
      tests/test_cli.py

2
Makefile

@ -3,7 +3,7 @@
all: clean-pyc test
test:
py.test tests examples
FLASK_DEBUG= py.test tests examples
tox-test:
tox

5
flask/app.py

@ -22,7 +22,8 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \
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
@ -289,7 +290,7 @@ class Flask(_PackageBoundObject):
#: Default configuration parameters.
default_config = ImmutableDict({
'DEBUG': False,
'DEBUG': get_debug_flag(default=False),
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,

124
flask/cli.py

@ -17,6 +17,7 @@ from functools import update_wrapper
import click
from ._compat import iteritems, reraise
from .helpers import get_debug_flag
class NoAppException(click.UsageError):
@ -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
@ -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
@ -185,11 +196,12 @@ class ScriptInfo(object):
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
@ -210,29 +222,6 @@ def with_appcontext(f):
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
@ -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:
@ -342,33 +318,6 @@ class FlaskGroup(AppGroup):
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.')
@ -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
@ -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.')
@ -464,15 +412,21 @@ cli = FlaskGroup(help="""\
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):

8
flask/helpers.py

@ -14,7 +14,6 @@ import sys
import pkgutil
import posixpath
import mimetypes
from datetime import timedelta
from time import time
from zlib import adler32
from threading import RLock
@ -54,6 +53,13 @@ _os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
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.

3
tests/test_cli.py

@ -19,7 +19,7 @@ from click.testing import CliRunner
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):
@ -123,7 +123,6 @@ def test_flaskgroup():
return Flask("flaskgroup")
@click.group(cls=FlaskGroup, create_app=create_app)
@script_info_option('--config', script_info_key='config')
def cli(**params):
pass

Loading…
Cancel
Save