From 523e27118359425048541d92892f20ee048c0b76 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 20:07:52 +0200 Subject: [PATCH] Implemented simplified CLI interface --- Makefile | 2 +- flask/app.py | 5 +- flask/cli.py | 124 +++++++++++++++------------------------------- flask/helpers.py | 8 ++- tests/test_cli.py | 3 +- 5 files changed, 51 insertions(+), 91 deletions(-) diff --git a/Makefile b/Makefile index 1268a1b4..350aa9a4 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: clean-pyc test test: - py.test tests examples + FLASK_DEBUG= py.test tests examples tox-test: tox diff --git a/flask/app.py b/flask/app.py index 416fcf08..b1ea0464 100644 --- a/flask/app.py +++ b/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, diff --git a/flask/cli.py b/flask/cli.py index 141c9eea..e873c108 100644 --- a/flask/cli.py +++ b/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): diff --git a/flask/helpers.py b/flask/helpers.py index 02e99e37..e8007b55 100644 --- a/flask/helpers.py +++ b/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. diff --git a/tests/test_cli.py b/tests/test_cli.py index 5a871e32..0a479857 100644 --- a/tests/test_cli.py +++ b/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