Skip to content

Commit 523e271

Browse files
committed
Implemented simplified CLI interface
1 parent 92f63a1 commit 523e271

File tree

5 files changed

+51
-91
lines changed

5 files changed

+51
-91
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
all: clean-pyc test
44

55
test:
6-
py.test tests examples
6+
FLASK_DEBUG= py.test tests examples
77

88
tox-test:
99
tox

flask/app.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
MethodNotAllowed, BadRequest, default_exceptions
2323

2424
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
25-
locked_cached_property, _endpoint_from_view_func, find_package
25+
locked_cached_property, _endpoint_from_view_func, find_package, \
26+
get_debug_flag
2627
from . import json, cli
2728
from .wrappers import Request, Response
2829
from .config import ConfigAttribute, Config
@@ -289,7 +290,7 @@ def _set_request_globals_class(self, value):
289290

290291
#: Default configuration parameters.
291292
default_config = ImmutableDict({
292-
'DEBUG': False,
293+
'DEBUG': get_debug_flag(default=False),
293294
'TESTING': False,
294295
'PROPAGATE_EXCEPTIONS': None,
295296
'PRESERVE_CONTEXT_ON_EXCEPTION': None,

flask/cli.py

Lines changed: 39 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import click
1818

1919
from ._compat import iteritems, reraise
20+
from .helpers import get_debug_flag
2021

2122

2223
class NoAppException(click.UsageError):
@@ -98,6 +99,15 @@ def locate_app(app_id):
9899
return app
99100

100101

102+
def find_default_import_path():
103+
app = os.environ.get('FLASK_APP')
104+
if app is None:
105+
return
106+
if os.path.isfile(app):
107+
return prepare_exec_for_file(app)
108+
return app
109+
110+
101111
class DispatchingApp(object):
102112
"""Special application that dispatches to a flask application which
103113
is imported by name in a background thread. If an error happens
@@ -158,12 +168,13 @@ class ScriptInfo(object):
158168
to click.
159169
"""
160170

161-
def __init__(self, app_import_path=None, debug=None, create_app=None):
162-
#: The application import path
163-
self.app_import_path = app_import_path
164-
#: The debug flag. If this is not None, the application will
165-
#: automatically have it's debug flag overridden with this value.
166-
self.debug = debug
171+
def __init__(self, app_import_path=None, create_app=None):
172+
if create_app is None:
173+
if app_import_path is None:
174+
app_import_path = find_default_import_path()
175+
self.app_import_path = app_import_path
176+
else:
177+
self.app_import_path = None
167178
#: Optionally a function that is passed the script info to create
168179
#: the instance of the application.
169180
self.create_app = create_app
@@ -185,11 +196,12 @@ def load_app(self):
185196
else:
186197
if self.app_import_path is None:
187198
raise NoAppException('Could not locate Flask application. '
188-
'You did not provide FLASK_APP or the '
189-
'--app parameter.')
199+
'You did not provide the FLASK_APP '
200+
'environment variable.')
190201
rv = locate_app(self.app_import_path)
191-
if self.debug is not None:
192-
rv.debug = self.debug
202+
debug = get_debug_flag()
203+
if debug is not None:
204+
rv.debug = debug
193205
self._loaded_app = rv
194206
return rv
195207

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

212224

213-
def set_debug_value(ctx, param, value):
214-
ctx.ensure_object(ScriptInfo).debug = value
215-
216-
217-
def set_app_value(ctx, param, value):
218-
if value is not None:
219-
if os.path.isfile(value):
220-
value = prepare_exec_for_file(value)
221-
elif '.' not in sys.path:
222-
sys.path.insert(0, '.')
223-
ctx.ensure_object(ScriptInfo).app_import_path = value
224-
225-
226-
debug_option = click.Option(['--debug/--no-debug'],
227-
help='Enable or disable debug mode.',
228-
default=None, callback=set_debug_value)
229-
230-
231-
app_option = click.Option(['-a', '--app'],
232-
help='The application to run',
233-
callback=set_app_value, is_eager=True)
234-
235-
236225
class AppGroup(click.Group):
237226
"""This works similar to a regular click :class:`~click.Group` but it
238227
changes the behavior of the :meth:`command` decorator so that it
@@ -273,25 +262,12 @@ class FlaskGroup(AppGroup):
273262
274263
:param add_default_commands: if this is True then the default run and
275264
shell commands wil be added.
276-
:param add_app_option: adds the default :option:`--app` option. This gets
277-
automatically disabled if a `create_app`
278-
callback is defined.
279-
:param add_debug_option: adds the default :option:`--debug` option.
280265
:param create_app: an optional callback that is passed the script info
281266
and returns the loaded app.
282267
"""
283268

284-
def __init__(self, add_default_commands=True, add_app_option=None,
285-
add_debug_option=True, create_app=None, **extra):
286-
params = list(extra.pop('params', None) or ())
287-
if add_app_option is None:
288-
add_app_option = create_app is None
289-
if add_app_option:
290-
params.append(app_option)
291-
if add_debug_option:
292-
params.append(debug_option)
293-
294-
AppGroup.__init__(self, params=params, **extra)
269+
def __init__(self, add_default_commands=True, create_app=None, **extra):
270+
AppGroup.__init__(self, **extra)
295271
self.create_app = create_app
296272

297273
if add_default_commands:
@@ -342,33 +318,6 @@ def main(self, *args, **kwargs):
342318
return AppGroup.main(self, *args, **kwargs)
343319

344320

345-
def script_info_option(*args, **kwargs):
346-
"""This decorator works exactly like :func:`click.option` but is eager
347-
by default and stores the value in the :attr:`ScriptInfo.data`. This
348-
is useful to further customize an application factory in very complex
349-
situations.
350-
351-
:param script_info_key: this is a mandatory keyword argument which
352-
defines under which data key the value should
353-
be stored.
354-
"""
355-
try:
356-
key = kwargs.pop('script_info_key')
357-
except LookupError:
358-
raise TypeError('script_info_key not provided.')
359-
360-
real_callback = kwargs.get('callback')
361-
def callback(ctx, param, value):
362-
if real_callback is not None:
363-
value = real_callback(ctx, value)
364-
ctx.ensure_object(ScriptInfo).data[key] = value
365-
return value
366-
367-
kwargs['callback'] = callback
368-
kwargs.setdefault('is_eager', True)
369-
return click.option(*args, **kwargs)
370-
371-
372321
@click.command('run', short_help='Runs a development server.')
373322
@click.option('--host', '-h', default='127.0.0.1',
374323
help='The interface to bind to.')
@@ -400,10 +349,12 @@ def run_command(info, host, port, reload, debugger, eager_loading,
400349
Flask is enabled and disabled otherwise.
401350
"""
402351
from werkzeug.serving import run_simple
352+
353+
debug = get_debug_flag()
403354
if reload is None:
404-
reload = info.debug
355+
reload = bool(debug)
405356
if debugger is None:
406-
debugger = info.debug
357+
debugger = bool(debug)
407358
if eager_loading is None:
408359
eager_loading = not reload
409360

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

424373
run_simple(host, port, app, use_reloader=reload,
425-
use_debugger=debugger, threaded=with_threads,
426-
passthrough_errors=True)
374+
use_debugger=debugger, threaded=with_threads)
427375

428376

429377
@click.command('shell', short_help='Runs a shell in the app context.')
@@ -464,15 +412,21 @@ def shell_command():
464412
This shell command acts as general utility script for Flask applications.
465413
466414
It loads the application configured (either through the FLASK_APP environment
467-
variable or the --app parameter) and then provides commands either provided
468-
by the application or Flask itself.
415+
variable) and then provides commands either provided by the application or
416+
Flask itself.
469417
470418
The most useful commands are the "run" and "shell" command.
471419
472420
Example usage:
473421
474-
flask --app=hello --debug run
475-
""")
422+
\b
423+
%(prefix)s%(cmd)s FLASK_APP=hello
424+
%(prefix)s%(cmd)s FLASK_DEBUG=1
425+
%(prefix)sflask run
426+
""" % {
427+
'cmd': os.name == 'posix' and 'export' or 'set',
428+
'prefix': os.name == 'posix' and '$ ' or '',
429+
})
476430

477431

478432
def main(as_module=False):

flask/helpers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import pkgutil
1515
import posixpath
1616
import mimetypes
17-
from datetime import timedelta
1817
from time import time
1918
from zlib import adler32
2019
from threading import RLock
@@ -54,6 +53,13 @@
5453
if sep not in (None, '/'))
5554

5655

56+
def get_debug_flag(default=None):
57+
val = os.environ.get('FLASK_DEBUG')
58+
if not val:
59+
return default
60+
return val not in ('0', 'false', 'no')
61+
62+
5763
def _endpoint_from_view_func(view_func):
5864
"""Internal helper that returns the default endpoint for a given
5965
function. This always is the function name.

tests/test_cli.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from flask import Flask, current_app
2020

2121
from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
22-
find_best_app, locate_app, script_info_option, with_appcontext
22+
find_best_app, locate_app, with_appcontext
2323

2424

2525
def test_cli_name(test_apps):
@@ -123,7 +123,6 @@ def create_app(info):
123123
return Flask("flaskgroup")
124124

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

0 commit comments

Comments
 (0)