|
|
|
@ -144,57 +144,62 @@ class ScriptInfo(object):
|
|
|
|
|
to click. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
def __init__(self, app_import_path=None, debug=None, load_callback=None): |
|
|
|
|
def __init__(self, app_import_path=None, debug=None, create_app=None): |
|
|
|
|
self.app_import_path = app_import_path |
|
|
|
|
self.debug = debug |
|
|
|
|
self.load_callback = load_callback |
|
|
|
|
self.create_app = create_app |
|
|
|
|
self._loaded_app = None |
|
|
|
|
|
|
|
|
|
def get_app_import_path(self): |
|
|
|
|
"""Return the actual application import path or fails if it is |
|
|
|
|
not yet set. |
|
|
|
|
""" |
|
|
|
|
if self.app_import_path is not None: |
|
|
|
|
return self.app_import_path |
|
|
|
|
_no_such_app() |
|
|
|
|
|
|
|
|
|
def load_app(self): |
|
|
|
|
"""Loads the app (if not yet loaded) and returns it.""" |
|
|
|
|
"""Loads the Flask app (if not yet loaded) and returns it.""" |
|
|
|
|
if self._loaded_app is not None: |
|
|
|
|
return self._loaded_app |
|
|
|
|
if self.load_callback is not None: |
|
|
|
|
rv = self.load_callback() |
|
|
|
|
if self.create_app is not None: |
|
|
|
|
rv = self.create_app(self) |
|
|
|
|
else: |
|
|
|
|
rv = locate_app(self.get_app_import_path(), self.debug) |
|
|
|
|
if self.app_import_path is None: |
|
|
|
|
_no_such_app() |
|
|
|
|
rv = locate_app(self.app_import_path, self.debug) |
|
|
|
|
self._loaded_app = rv |
|
|
|
|
return rv |
|
|
|
|
|
|
|
|
|
def make_wsgi_app(self, use_eager_loading=False): |
|
|
|
|
"""Returns a WSGI app that loads the actual application at a |
|
|
|
|
later stage. |
|
|
|
|
"""Returns a WSGI app that loads the actual application at a later |
|
|
|
|
stage (on first request). This has the advantage over |
|
|
|
|
:meth:`load_app` that if used with a WSGI server, it will allow |
|
|
|
|
the server to intercept errors later during request handling |
|
|
|
|
instead of dying a horrible death. |
|
|
|
|
|
|
|
|
|
If eager loading is disabled the loading will happen immediately. |
|
|
|
|
""" |
|
|
|
|
if self.app_import_path is not None: |
|
|
|
|
def loader(): |
|
|
|
|
return locate_app(self.app_import_path, self.debug) |
|
|
|
|
else: |
|
|
|
|
if self.load_callback is None: |
|
|
|
|
if self.create_app is None: |
|
|
|
|
_no_such_app() |
|
|
|
|
def loader(): |
|
|
|
|
return self.load_callback() |
|
|
|
|
return self.create_app(self) |
|
|
|
|
return DispatchingApp(loader, use_eager_loading=use_eager_loading) |
|
|
|
|
|
|
|
|
|
@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. |
|
|
|
|
"""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: |
|
|
|
|
with self.load_app(self).app_context() as ctx: |
|
|
|
|
yield ctx |
|
|
|
|
else: |
|
|
|
|
yield None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#: 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. |
|
|
|
|
pass_script_info = click.make_pass_decorator(ScriptInfo) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -208,10 +213,56 @@ def without_appcontext(f):
|
|
|
|
|
return f |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContextGroupMixin(object): |
|
|
|
|
class FlaskGroup(click.Group): |
|
|
|
|
"""Special subclass of the a regular click group that supports |
|
|
|
|
loading 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. |
|
|
|
|
|
|
|
|
|
:param add_default_options: if this is True the app and debug option |
|
|
|
|
is automatically added. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
def __init__(self, add_default_options=True, |
|
|
|
|
add_default_commands=True, |
|
|
|
|
create_app=None, **extra): |
|
|
|
|
click.Group.__init__(self, **extra) |
|
|
|
|
self.create_app = create_app |
|
|
|
|
if add_default_options: |
|
|
|
|
self.add_app_option() |
|
|
|
|
self.add_debug_option() |
|
|
|
|
if add_default_commands: |
|
|
|
|
self.add_command(run_command) |
|
|
|
|
self.add_command(shell_command) |
|
|
|
|
|
|
|
|
|
def add_app_option(self): |
|
|
|
|
"""Adds an option to the default command that defines an import |
|
|
|
|
path that points to an application. |
|
|
|
|
""" |
|
|
|
|
def set_app_id(ctx, 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 |
|
|
|
|
|
|
|
|
|
self.params.append(click.Option(['-a', '--app'], |
|
|
|
|
help='The application to run', |
|
|
|
|
callback=set_app_id, is_eager=True)) |
|
|
|
|
|
|
|
|
|
def add_debug_option(self): |
|
|
|
|
"""Adds an option that controls the debug flag.""" |
|
|
|
|
def set_debug(ctx, value): |
|
|
|
|
ctx.ensure_object(ScriptInfo).debug = value |
|
|
|
|
|
|
|
|
|
self.params.append(click.Option(['--debug/--no-debug'], |
|
|
|
|
help='Enable or disable debug mode.', |
|
|
|
|
default=None, callback=set_debug)) |
|
|
|
|
|
|
|
|
|
def get_command(self, ctx, name): |
|
|
|
|
info = ctx.find_object(ScriptInfo) |
|
|
|
|
info = ctx.ensure_object(ScriptInfo) |
|
|
|
|
# Find the command in the application first, if we can find it. |
|
|
|
|
# If the app is not available, we just ignore this silently. |
|
|
|
|
try: |
|
|
|
@ -220,13 +271,13 @@ class ContextGroupMixin(object):
|
|
|
|
|
return rv |
|
|
|
|
except NoAppException: |
|
|
|
|
pass |
|
|
|
|
return super(ContextGroupMixin, self).get_command(ctx, name) |
|
|
|
|
return click.Group.get_command(self, ctx, name) |
|
|
|
|
|
|
|
|
|
def list_commands(self, ctx): |
|
|
|
|
# The commands available is the list of both the application (if |
|
|
|
|
# available) plus the builtin commands. |
|
|
|
|
rv = set(super(ContextGroupMixin, self).list_commands(ctx)) |
|
|
|
|
info = ctx.find_object(ScriptInfo) |
|
|
|
|
rv = set(click.Group.list_commands(self, ctx)) |
|
|
|
|
info = ctx.ensure_object(ScriptInfo) |
|
|
|
|
try: |
|
|
|
|
rv.update(info.load_app().cli.list_commands(ctx)) |
|
|
|
|
except NoAppException: |
|
|
|
@ -238,52 +289,19 @@ class ContextGroupMixin(object):
|
|
|
|
|
not getattr(cmd.callback, '__flask_without_appcontext__', False) |
|
|
|
|
|
|
|
|
|
with ctx.find_object(ScriptInfo).conditional_context(with_context): |
|
|
|
|
return super(ContextGroupMixin, self).invoke_subcommand( |
|
|
|
|
ctx, cmd, cmd_name, args) |
|
|
|
|
|
|
|
|
|
return click.Group.invoke_subcommand( |
|
|
|
|
self, ctx, cmd, cmd_name, args) |
|
|
|
|
|
|
|
|
|
class FlaskGroup(ContextGroupMixin, click.Group): |
|
|
|
|
"""Special subclass of the a regular click group that supports |
|
|
|
|
loading more commands from the configured Flask app. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
def __init__(self, help=None): |
|
|
|
|
def set_app_id(ctx, 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 |
|
|
|
|
def set_debug(ctx, value): |
|
|
|
|
ctx.ensure_object(ScriptInfo).debug = value |
|
|
|
|
def main(self, *args, **kwargs): |
|
|
|
|
obj = kwargs.get('obj') |
|
|
|
|
if obj is None: |
|
|
|
|
obj = ScriptInfo(create_app=self.create_app) |
|
|
|
|
kwargs['obj'] = obj |
|
|
|
|
kwargs.setdefault('auto_envvar_prefix', 'FLASK') |
|
|
|
|
return click.Group.main(self, *args, **kwargs) |
|
|
|
|
|
|
|
|
|
click.Group.__init__(self, help=help, params=[ |
|
|
|
|
click.Option(['-a', '--app'], |
|
|
|
|
help='The application to run', |
|
|
|
|
callback=set_app_id, is_eager=True), |
|
|
|
|
click.Option(['--debug/--no-debug'], |
|
|
|
|
help='Enable or disable debug mode.', |
|
|
|
|
default=None, callback=set_debug) |
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
|
|
The most useful commands are the "run" and "shell" command. |
|
|
|
|
|
|
|
|
|
Example usage: |
|
|
|
|
|
|
|
|
|
flask --app=hello --debug run |
|
|
|
|
''') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command('run', short_help='Runs a development server.') |
|
|
|
|
@click.command('run', short_help='Runs a development server.') |
|
|
|
|
@click.option('--host', '-h', default='127.0.0.1', |
|
|
|
|
help='The interface to bind to.') |
|
|
|
|
@click.option('--port', '-p', default=5000, |
|
|
|
@ -303,7 +321,17 @@ Example usage:
|
|
|
|
|
@pass_script_info |
|
|
|
|
def run_command(info, host, port, reload, debugger, eager_loading, |
|
|
|
|
with_threads): |
|
|
|
|
"""Runs a local development server for the Flask application.""" |
|
|
|
|
"""Runs a local development server for the Flask application. |
|
|
|
|
|
|
|
|
|
This local server is recommended for development purposes only but it |
|
|
|
|
can also be used for simple intranet deployments. By default it will |
|
|
|
|
not support any sort of concurrency at all to simplify debugging. This |
|
|
|
|
can be changed with the --with-threads option which will enable basic |
|
|
|
|
multithreading. |
|
|
|
|
|
|
|
|
|
The reloader and debugger are by default enabled if the debug flag of |
|
|
|
|
Flask is enabled and disabled otherwise. |
|
|
|
|
""" |
|
|
|
|
from werkzeug.serving import run_simple |
|
|
|
|
if reload is None: |
|
|
|
|
reload = info.debug |
|
|
|
@ -330,7 +358,7 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
|
|
|
|
use_debugger=debugger, threaded=with_threads) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@cli.command('shell', short_help='Runs a shell in the app context.') |
|
|
|
|
@click.command('shell', short_help='Runs a shell in the app context.') |
|
|
|
|
def shell_command(): |
|
|
|
|
"""Runs an interactive Python shell in the context of a given |
|
|
|
|
Flask application. The application will populate the default |
|
|
|
@ -360,6 +388,21 @@ def make_default_cli(app):
|
|
|
|
|
return click.Group() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
|
|
The most useful commands are the "run" and "shell" command. |
|
|
|
|
|
|
|
|
|
Example usage: |
|
|
|
|
|
|
|
|
|
flask --app=hello --debug run |
|
|
|
|
''') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(as_module=False): |
|
|
|
|
this_module = __package__ + '.cli' |
|
|
|
|
args = sys.argv[1:] |
|
|
|
@ -377,8 +420,7 @@ def main(as_module=False):
|
|
|
|
|
else: |
|
|
|
|
name = None |
|
|
|
|
|
|
|
|
|
cli.main(args=args, prog_name=name, obj=ScriptInfo(), |
|
|
|
|
auto_envvar_prefix='FLASK') |
|
|
|
|
cli.main(args=args, prog_name=name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|