|
@@ -1,3 +1,5 @@
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
import ast
|
|
|
import inspect
|
|
|
import os
|
|
@@ -5,19 +7,23 @@ import platform
|
|
|
import re
|
|
|
import sys
|
|
|
import traceback
|
|
|
+import typing as t
|
|
|
from functools import update_wrapper
|
|
|
from operator import attrgetter
|
|
|
-from threading import Lock
|
|
|
-from threading import Thread
|
|
|
|
|
|
import click
|
|
|
+from click.core import ParameterSource
|
|
|
+from werkzeug import run_simple
|
|
|
+from werkzeug.serving import is_running_from_reloader
|
|
|
from werkzeug.utils import import_string
|
|
|
|
|
|
from .globals import current_app
|
|
|
from .helpers import get_debug_flag
|
|
|
-from .helpers import get_env
|
|
|
from .helpers import get_load_dotenv
|
|
|
|
|
|
+if t.TYPE_CHECKING:
|
|
|
+ from .app import Flask
|
|
|
+
|
|
|
|
|
|
class NoAppException(click.UsageError):
|
|
|
"""Raised if an application cannot be found or loaded."""
|
|
@@ -44,8 +50,8 @@ def find_best_app(module):
|
|
|
elif len(matches) > 1:
|
|
|
raise NoAppException(
|
|
|
"Detected multiple Flask applications in module"
|
|
|
- f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
|
|
|
- f" to specify the correct one."
|
|
|
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
|
|
|
+ " to specify the correct one."
|
|
|
)
|
|
|
|
|
|
# Search for app factory functions.
|
|
@@ -63,15 +69,15 @@ def find_best_app(module):
|
|
|
raise
|
|
|
|
|
|
raise NoAppException(
|
|
|
- f"Detected factory {attr_name!r} in module {module.__name__!r},"
|
|
|
+ f"Detected factory '{attr_name}' in module '{module.__name__}',"
|
|
|
" but could not call it without arguments. Use"
|
|
|
- f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
|
|
|
+ f" '{module.__name__}:{attr_name}(args)'"
|
|
|
" to specify arguments."
|
|
|
) from e
|
|
|
|
|
|
raise NoAppException(
|
|
|
"Failed to find Flask application or factory in module"
|
|
|
- f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
|
|
|
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
|
|
|
" to specify one."
|
|
|
)
|
|
|
|
|
@@ -208,8 +214,6 @@ def prepare_import(path):
|
|
|
|
|
|
|
|
|
def locate_app(module_name, app_name, raise_if_not_found=True):
|
|
|
- __traceback_hide__ = True # noqa: F841
|
|
|
-
|
|
|
try:
|
|
|
__import__(module_name)
|
|
|
except ImportError:
|
|
@@ -251,7 +255,7 @@ def get_version(ctx, param, value):
|
|
|
|
|
|
version_option = click.Option(
|
|
|
["--version"],
|
|
|
- help="Show the flask version",
|
|
|
+ help="Show the Flask version.",
|
|
|
expose_value=False,
|
|
|
callback=get_version,
|
|
|
is_flag=True,
|
|
@@ -259,74 +263,6 @@ version_option = click.Option(
|
|
|
)
|
|
|
|
|
|
|
|
|
-class DispatchingApp:
|
|
|
- """Special application that dispatches to a Flask application which
|
|
|
- is imported by name in a background thread. If an error happens
|
|
|
- it is recorded and shown as part of the WSGI handling which in case
|
|
|
- of the Werkzeug debugger means that it shows up in the browser.
|
|
|
- """
|
|
|
-
|
|
|
- def __init__(self, loader, use_eager_loading=None):
|
|
|
- self.loader = loader
|
|
|
- self._app = None
|
|
|
- self._lock = Lock()
|
|
|
- self._bg_loading_exc = None
|
|
|
-
|
|
|
- if use_eager_loading is None:
|
|
|
- use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
|
|
|
-
|
|
|
- if use_eager_loading:
|
|
|
- self._load_unlocked()
|
|
|
- else:
|
|
|
- self._load_in_background()
|
|
|
-
|
|
|
- def _load_in_background(self):
|
|
|
- # Store the Click context and push it in the loader thread so
|
|
|
- # script_info is still available.
|
|
|
- ctx = click.get_current_context(silent=True)
|
|
|
-
|
|
|
- def _load_app():
|
|
|
- __traceback_hide__ = True # noqa: F841
|
|
|
-
|
|
|
- with self._lock:
|
|
|
- if ctx is not None:
|
|
|
- click.globals.push_context(ctx)
|
|
|
-
|
|
|
- try:
|
|
|
- self._load_unlocked()
|
|
|
- except Exception as e:
|
|
|
- self._bg_loading_exc = e
|
|
|
-
|
|
|
- t = Thread(target=_load_app, args=())
|
|
|
- t.start()
|
|
|
-
|
|
|
- def _flush_bg_loading_exception(self):
|
|
|
- __traceback_hide__ = True # noqa: F841
|
|
|
- exc = self._bg_loading_exc
|
|
|
-
|
|
|
- if exc is not None:
|
|
|
- self._bg_loading_exc = None
|
|
|
- raise exc
|
|
|
-
|
|
|
- def _load_unlocked(self):
|
|
|
- __traceback_hide__ = True # noqa: F841
|
|
|
- self._app = rv = self.loader()
|
|
|
- self._bg_loading_exc = None
|
|
|
- return rv
|
|
|
-
|
|
|
- def __call__(self, environ, start_response):
|
|
|
- __traceback_hide__ = True # noqa: F841
|
|
|
- if self._app is not None:
|
|
|
- return self._app(environ, start_response)
|
|
|
- self._flush_bg_loading_exception()
|
|
|
- with self._lock:
|
|
|
- if self._app is not None:
|
|
|
- rv = self._app
|
|
|
- else:
|
|
|
- rv = self._load_unlocked()
|
|
|
- return rv(environ, start_response)
|
|
|
-
|
|
|
-
|
|
|
class ScriptInfo:
|
|
|
"""Helper object to deal with Flask applications. This is usually not
|
|
|
necessary to interface with as it's used internally in the dispatching
|
|
@@ -336,25 +272,28 @@ class ScriptInfo:
|
|
|
onwards as click object.
|
|
|
"""
|
|
|
|
|
|
- def __init__(self, app_import_path=None, create_app=None, set_debug_flag=True):
|
|
|
+ def __init__(
|
|
|
+ self,
|
|
|
+ app_import_path: str | None = None,
|
|
|
+ create_app: t.Callable[..., Flask] | None = None,
|
|
|
+ set_debug_flag: bool = True,
|
|
|
+ ) -> None:
|
|
|
#: Optionally the import path for the Flask application.
|
|
|
- self.app_import_path = app_import_path or os.environ.get("FLASK_APP")
|
|
|
+ self.app_import_path = app_import_path
|
|
|
#: Optionally a function that is passed the script info to create
|
|
|
#: the instance of the application.
|
|
|
self.create_app = create_app
|
|
|
#: A dictionary with arbitrary data that can be associated with
|
|
|
#: this script info.
|
|
|
- self.data = {}
|
|
|
+ self.data: t.Dict[t.Any, t.Any] = {}
|
|
|
self.set_debug_flag = set_debug_flag
|
|
|
- self._loaded_app = None
|
|
|
+ self._loaded_app: Flask | None = None
|
|
|
|
|
|
- def load_app(self):
|
|
|
+ def load_app(self) -> Flask:
|
|
|
"""Loads the Flask app (if not yet loaded) and returns it. Calling
|
|
|
this multiple times will just result in the already loaded app to
|
|
|
be returned.
|
|
|
"""
|
|
|
- __traceback_hide__ = True # noqa: F841
|
|
|
-
|
|
|
if self._loaded_app is not None:
|
|
|
return self._loaded_app
|
|
|
|
|
@@ -377,9 +316,10 @@ class ScriptInfo:
|
|
|
|
|
|
if not app:
|
|
|
raise NoAppException(
|
|
|
- "Could not locate a Flask application. You did not provide "
|
|
|
- 'the "FLASK_APP" environment variable, and a "wsgi.py" or '
|
|
|
- '"app.py" module was not found in the current directory.'
|
|
|
+ "Could not locate a Flask application. Use the"
|
|
|
+ " 'flask --app' option, 'FLASK_APP' environment"
|
|
|
+ " variable, or a 'wsgi.py' or 'app.py' file in the"
|
|
|
+ " current directory."
|
|
|
)
|
|
|
|
|
|
if self.set_debug_flag:
|
|
@@ -396,15 +336,25 @@ pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
|
|
|
|
|
|
def with_appcontext(f):
|
|
|
"""Wraps a callback so that it's guaranteed to be executed with the
|
|
|
- script's application context. If callbacks are registered directly
|
|
|
- to the ``app.cli`` object then they are wrapped with this function
|
|
|
- by default unless it's disabled.
|
|
|
+ script's application context.
|
|
|
+
|
|
|
+ Custom commands (and their options) registered under ``app.cli`` or
|
|
|
+ ``blueprint.cli`` will always have an app context available, this
|
|
|
+ decorator is not required in that case.
|
|
|
+
|
|
|
+ .. versionchanged:: 2.2
|
|
|
+ The app context is active for subcommands as well as the
|
|
|
+ decorated callback. The app context is always available to
|
|
|
+ ``app.cli`` command and parameter callbacks.
|
|
|
"""
|
|
|
|
|
|
@click.pass_context
|
|
|
def decorator(__ctx, *args, **kwargs):
|
|
|
- with __ctx.ensure_object(ScriptInfo).load_app().app_context():
|
|
|
- return __ctx.invoke(f, *args, **kwargs)
|
|
|
+ if not current_app:
|
|
|
+ app = __ctx.ensure_object(ScriptInfo).load_app()
|
|
|
+ __ctx.with_resource(app.app_context())
|
|
|
+
|
|
|
+ return __ctx.invoke(f, *args, **kwargs)
|
|
|
|
|
|
return update_wrapper(decorator, f)
|
|
|
|
|
@@ -440,6 +390,94 @@ class AppGroup(click.Group):
|
|
|
return click.Group.group(self, *args, **kwargs)
|
|
|
|
|
|
|
|
|
+def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+
|
|
|
+ info = ctx.ensure_object(ScriptInfo)
|
|
|
+ info.app_import_path = value
|
|
|
+ return value
|
|
|
+
|
|
|
+
|
|
|
+# This option is eager so the app will be available if --help is given.
|
|
|
+# --help is also eager, so --app must be before it in the param list.
|
|
|
+# no_args_is_help bypasses eager processing, so this option must be
|
|
|
+# processed manually in that case to ensure FLASK_APP gets picked up.
|
|
|
+_app_option = click.Option(
|
|
|
+ ["-A", "--app"],
|
|
|
+ metavar="IMPORT",
|
|
|
+ help=(
|
|
|
+ "The Flask application or factory function to load, in the form 'module:name'."
|
|
|
+ " Module can be a dotted import or file path. Name is not required if it is"
|
|
|
+ " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"
|
|
|
+ " pass arguments."
|
|
|
+ ),
|
|
|
+ is_eager=True,
|
|
|
+ expose_value=False,
|
|
|
+ callback=_set_app,
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
|
|
|
+ # If the flag isn't provided, it will default to False. Don't use
|
|
|
+ # that, let debug be set by env in that case.
|
|
|
+ source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
|
|
|
+
|
|
|
+ if source is not None and source in (
|
|
|
+ ParameterSource.DEFAULT,
|
|
|
+ ParameterSource.DEFAULT_MAP,
|
|
|
+ ):
|
|
|
+ return None
|
|
|
+
|
|
|
+ # Set with env var instead of ScriptInfo.load so that it can be
|
|
|
+ # accessed early during a factory function.
|
|
|
+ os.environ["FLASK_DEBUG"] = "1" if value else "0"
|
|
|
+ return value
|
|
|
+
|
|
|
+
|
|
|
+_debug_option = click.Option(
|
|
|
+ ["--debug/--no-debug"],
|
|
|
+ help="Set debug mode.",
|
|
|
+ expose_value=False,
|
|
|
+ callback=_set_debug,
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+def _env_file_callback(
|
|
|
+ ctx: click.Context, param: click.Option, value: str | None
|
|
|
+) -> str | None:
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+
|
|
|
+ import importlib
|
|
|
+
|
|
|
+ try:
|
|
|
+ importlib.import_module("dotenv")
|
|
|
+ except ImportError:
|
|
|
+ raise click.BadParameter(
|
|
|
+ "python-dotenv must be installed to load an env file.",
|
|
|
+ ctx=ctx,
|
|
|
+ param=param,
|
|
|
+ ) from None
|
|
|
+
|
|
|
+ # Don't check FLASK_SKIP_DOTENV, that only disables automatically
|
|
|
+ # loading .env and .flaskenv files.
|
|
|
+ load_dotenv(value)
|
|
|
+ return value
|
|
|
+
|
|
|
+
|
|
|
+# This option is eager so env vars are loaded as early as possible to be
|
|
|
+# used by other options.
|
|
|
+_env_file_option = click.Option(
|
|
|
+ ["-e", "--env-file"],
|
|
|
+ type=click.Path(exists=True, dir_okay=False),
|
|
|
+ help="Load environment variables from this file. python-dotenv must be installed.",
|
|
|
+ is_eager=True,
|
|
|
+ expose_value=False,
|
|
|
+ callback=_env_file_callback,
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
class FlaskGroup(AppGroup):
|
|
|
"""Special subclass of the :class:`AppGroup` group that supports
|
|
|
loading more commands from the configured Flask app. Normally a
|
|
@@ -455,8 +493,14 @@ class FlaskGroup(AppGroup):
|
|
|
:param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
|
|
|
files to set environment variables. Will also change the working
|
|
|
directory to the directory containing the first file found.
|
|
|
- :param set_debug_flag: Set the app's debug flag based on the active
|
|
|
- environment
|
|
|
+ :param set_debug_flag: Set the app's debug flag.
|
|
|
+
|
|
|
+ .. versionchanged:: 2.2
|
|
|
+ Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
|
|
|
+
|
|
|
+ .. versionchanged:: 2.2
|
|
|
+ An app context is pushed when running ``app.cli`` commands, so
|
|
|
+ ``@with_appcontext`` is no longer required for those commands.
|
|
|
|
|
|
.. versionchanged:: 1.0
|
|
|
If installed, python-dotenv will be used to load environment variables
|
|
@@ -465,19 +509,30 @@ class FlaskGroup(AppGroup):
|
|
|
|
|
|
def __init__(
|
|
|
self,
|
|
|
- add_default_commands=True,
|
|
|
- create_app=None,
|
|
|
- add_version_option=True,
|
|
|
- load_dotenv=True,
|
|
|
- set_debug_flag=True,
|
|
|
- **extra,
|
|
|
- ):
|
|
|
+ add_default_commands: bool = True,
|
|
|
+ create_app: t.Callable[..., Flask] | None = None,
|
|
|
+ add_version_option: bool = True,
|
|
|
+ load_dotenv: bool = True,
|
|
|
+ set_debug_flag: bool = True,
|
|
|
+ **extra: t.Any,
|
|
|
+ ) -> None:
|
|
|
params = list(extra.pop("params", None) or ())
|
|
|
+ # Processing is done with option callbacks instead of a group
|
|
|
+ # callback. This allows users to make a custom group callback
|
|
|
+ # without losing the behavior. --env-file must come first so
|
|
|
+ # that it is eagerly evaluated before --app.
|
|
|
+ params.extend((_env_file_option, _app_option, _debug_option))
|
|
|
|
|
|
if add_version_option:
|
|
|
params.append(version_option)
|
|
|
|
|
|
- AppGroup.__init__(self, params=params, **extra)
|
|
|
+ if "context_settings" not in extra:
|
|
|
+ extra["context_settings"] = {}
|
|
|
+
|
|
|
+ extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK")
|
|
|
+
|
|
|
+ super().__init__(params=params, **extra)
|
|
|
+
|
|
|
self.create_app = create_app
|
|
|
self.load_dotenv = load_dotenv
|
|
|
self.set_debug_flag = set_debug_flag
|
|
@@ -520,9 +575,18 @@ class FlaskGroup(AppGroup):
|
|
|
# Look up commands provided by the app, showing an error and
|
|
|
# continuing if the app couldn't be loaded.
|
|
|
try:
|
|
|
- return info.load_app().cli.get_command(ctx, name)
|
|
|
+ app = info.load_app()
|
|
|
except NoAppException as e:
|
|
|
click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
|
|
|
+ return None
|
|
|
+
|
|
|
+ # Push an app context for the loaded app unless it is already
|
|
|
+ # active somehow. This makes the context available to parameter
|
|
|
+ # and command callbacks without needing @with_appcontext.
|
|
|
+ if not current_app or current_app._get_current_object() is not app:
|
|
|
+ ctx.with_resource(app.app_context())
|
|
|
+
|
|
|
+ return app.cli.get_command(ctx, name)
|
|
|
|
|
|
def list_commands(self, ctx):
|
|
|
self._load_plugin_commands()
|
|
@@ -545,26 +609,39 @@ class FlaskGroup(AppGroup):
|
|
|
|
|
|
return sorted(rv)
|
|
|
|
|
|
- def main(self, *args, **kwargs):
|
|
|
- # Set a global flag that indicates that we were invoked from the
|
|
|
- # command line interface. This is detected by Flask.run to make the
|
|
|
- # call into a no-op. This is necessary to avoid ugly errors when the
|
|
|
- # script that is loaded here also attempts to start a server.
|
|
|
+ def make_context(
|
|
|
+ self,
|
|
|
+ info_name: str | None,
|
|
|
+ args: list[str],
|
|
|
+ parent: click.Context | None = None,
|
|
|
+ **extra: t.Any,
|
|
|
+ ) -> click.Context:
|
|
|
+ # Set a flag to tell app.run to become a no-op. If app.run was
|
|
|
+ # not in a __name__ == __main__ guard, it would start the server
|
|
|
+ # when importing, blocking whatever command is being called.
|
|
|
os.environ["FLASK_RUN_FROM_CLI"] = "true"
|
|
|
|
|
|
+ # Attempt to load .env and .flask env files. The --env-file
|
|
|
+ # option can cause another file to be loaded.
|
|
|
if get_load_dotenv(self.load_dotenv):
|
|
|
load_dotenv()
|
|
|
|
|
|
- obj = kwargs.get("obj")
|
|
|
-
|
|
|
- if obj is None:
|
|
|
- obj = ScriptInfo(
|
|
|
+ if "obj" not in extra and "obj" not in self.context_settings:
|
|
|
+ extra["obj"] = ScriptInfo(
|
|
|
create_app=self.create_app, set_debug_flag=self.set_debug_flag
|
|
|
)
|
|
|
|
|
|
- kwargs["obj"] = obj
|
|
|
- kwargs.setdefault("auto_envvar_prefix", "FLASK")
|
|
|
- return super().main(*args, **kwargs)
|
|
|
+ return super().make_context(info_name, args, parent=parent, **extra)
|
|
|
+
|
|
|
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
|
|
|
+ if not args and self.no_args_is_help:
|
|
|
+ # Attempt to load --env-file and --app early in case they
|
|
|
+ # were given as env vars. Otherwise no_args_is_help will not
|
|
|
+ # see commands from app.cli.
|
|
|
+ _env_file_option.handle_parse_result(ctx, {}, [])
|
|
|
+ _app_option.handle_parse_result(ctx, {}, [])
|
|
|
+
|
|
|
+ return super().parse_args(ctx, args)
|
|
|
|
|
|
|
|
|
def _path_is_ancestor(path, other):
|
|
@@ -574,7 +651,7 @@ def _path_is_ancestor(path, other):
|
|
|
return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
|
|
|
|
|
|
|
|
|
-def load_dotenv(path=None):
|
|
|
+def load_dotenv(path: str | os.PathLike | None = None) -> bool:
|
|
|
"""Load "dotenv" files in order of precedence to set environment variables.
|
|
|
|
|
|
If an env var is already set it is not overwritten, so earlier files in the
|
|
@@ -587,13 +664,17 @@ def load_dotenv(path=None):
|
|
|
:param path: Load the file at this location instead of searching.
|
|
|
:return: ``True`` if a file was loaded.
|
|
|
|
|
|
- .. versionchanged:: 1.1.0
|
|
|
- Returns ``False`` when python-dotenv is not installed, or when
|
|
|
- the given path isn't a file.
|
|
|
+ .. versionchanged:: 2.0
|
|
|
+ The current directory is not changed to the location of the
|
|
|
+ loaded file.
|
|
|
|
|
|
.. versionchanged:: 2.0
|
|
|
When loading the env files, set the default encoding to UTF-8.
|
|
|
|
|
|
+ .. versionchanged:: 1.1.0
|
|
|
+ Returns ``False`` when python-dotenv is not installed, or when
|
|
|
+ the given path isn't a file.
|
|
|
+
|
|
|
.. versionadded:: 1.0
|
|
|
"""
|
|
|
try:
|
|
@@ -609,15 +690,15 @@ def load_dotenv(path=None):
|
|
|
|
|
|
return False
|
|
|
|
|
|
- # if the given path specifies the actual file then return True,
|
|
|
- # else False
|
|
|
+ # Always return after attempting to load a given path, don't load
|
|
|
+ # the default files.
|
|
|
if path is not None:
|
|
|
if os.path.isfile(path):
|
|
|
return dotenv.load_dotenv(path, encoding="utf-8")
|
|
|
|
|
|
return False
|
|
|
|
|
|
- new_dir = None
|
|
|
+ loaded = False
|
|
|
|
|
|
for name in (".env", ".flaskenv"):
|
|
|
path = dotenv.find_dotenv(name, usecwd=True)
|
|
@@ -625,38 +706,21 @@ def load_dotenv(path=None):
|
|
|
if not path:
|
|
|
continue
|
|
|
|
|
|
- if new_dir is None:
|
|
|
- new_dir = os.path.dirname(path)
|
|
|
-
|
|
|
dotenv.load_dotenv(path, encoding="utf-8")
|
|
|
+ loaded = True
|
|
|
|
|
|
- return new_dir is not None # at least one file was located and loaded
|
|
|
+ return loaded # True if at least one file was located and loaded.
|
|
|
|
|
|
|
|
|
-def show_server_banner(env, debug, app_import_path, eager_loading):
|
|
|
+def show_server_banner(debug, app_import_path):
|
|
|
"""Show extra startup messages the first time the server is run,
|
|
|
ignoring the reloader.
|
|
|
"""
|
|
|
- if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
|
|
+ if is_running_from_reloader():
|
|
|
return
|
|
|
|
|
|
if app_import_path is not None:
|
|
|
- message = f" * Serving Flask app {app_import_path!r}"
|
|
|
-
|
|
|
- if not eager_loading:
|
|
|
- message += " (lazy loading)"
|
|
|
-
|
|
|
- click.echo(message)
|
|
|
-
|
|
|
- click.echo(f" * Environment: {env}")
|
|
|
-
|
|
|
- if env == "production":
|
|
|
- click.secho(
|
|
|
- " WARNING: This is a development server. Do not use it in"
|
|
|
- " a production deployment.",
|
|
|
- fg="red",
|
|
|
- )
|
|
|
- click.secho(" Use a production WSGI server instead.", dim=True)
|
|
|
+ click.echo(f" * Serving Flask app '{app_import_path}'")
|
|
|
|
|
|
if debug is not None:
|
|
|
click.echo(f" * Debug mode: {'on' if debug else 'off'}")
|
|
@@ -785,12 +849,6 @@ class SeparatedPathType(click.Path):
|
|
|
help="Enable or disable the debugger. By default the debugger "
|
|
|
"is active if debug is enabled.",
|
|
|
)
|
|
|
-@click.option(
|
|
|
- "--eager-loading/--lazy-loading",
|
|
|
- default=None,
|
|
|
- help="Enable or disable eager loading. By default eager "
|
|
|
- "loading is enabled if the reloader is disabled.",
|
|
|
-)
|
|
|
@click.option(
|
|
|
"--with-threads/--without-threads",
|
|
|
default=True,
|
|
@@ -822,7 +880,6 @@ def run_command(
|
|
|
port,
|
|
|
reload,
|
|
|
debugger,
|
|
|
- eager_loading,
|
|
|
with_threads,
|
|
|
cert,
|
|
|
extra_files,
|
|
@@ -833,9 +890,26 @@ def run_command(
|
|
|
This server is for development purposes only. It does not provide
|
|
|
the stability, security, or performance of production WSGI servers.
|
|
|
|
|
|
- The reloader and debugger are enabled by default if
|
|
|
- FLASK_ENV=development or FLASK_DEBUG=1.
|
|
|
+ The reloader and debugger are enabled by default with the '--debug'
|
|
|
+ option.
|
|
|
"""
|
|
|
+ try:
|
|
|
+ app = info.load_app()
|
|
|
+ except Exception as e:
|
|
|
+ if is_running_from_reloader():
|
|
|
+ # When reloading, print out the error immediately, but raise
|
|
|
+ # it later so the debugger or server can handle it.
|
|
|
+ traceback.print_exc()
|
|
|
+ err = e
|
|
|
+
|
|
|
+ def app(environ, start_response):
|
|
|
+ raise err from None
|
|
|
+
|
|
|
+ else:
|
|
|
+ # When not reloading, raise the error immediately so the
|
|
|
+ # command fails.
|
|
|
+ raise e from None
|
|
|
+
|
|
|
debug = get_debug_flag()
|
|
|
|
|
|
if reload is None:
|
|
@@ -844,10 +918,7 @@ def run_command(
|
|
|
if debugger is None:
|
|
|
debugger = debug
|
|
|
|
|
|
- show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
|
|
|
- app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
|
|
|
-
|
|
|
- from werkzeug.serving import run_simple
|
|
|
+ show_server_banner(debug, info.app_import_path)
|
|
|
|
|
|
run_simple(
|
|
|
host,
|
|
@@ -862,6 +933,9 @@ def run_command(
|
|
|
)
|
|
|
|
|
|
|
|
|
+run_command.params.insert(0, _debug_option)
|
|
|
+
|
|
|
+
|
|
|
@click.command("shell", short_help="Run a shell in the app context.")
|
|
|
@with_appcontext
|
|
|
def shell_command() -> None:
|
|
@@ -873,13 +947,11 @@ def shell_command() -> None:
|
|
|
without having to manually configure the application.
|
|
|
"""
|
|
|
import code
|
|
|
- from .globals import _app_ctx_stack
|
|
|
|
|
|
- app = _app_ctx_stack.top.app
|
|
|
banner = (
|
|
|
f"Python {sys.version} on {sys.platform}\n"
|
|
|
- f"App: {app.import_name} [{app.env}]\n"
|
|
|
- f"Instance: {app.instance_path}"
|
|
|
+ f"App: {current_app.import_name}\n"
|
|
|
+ f"Instance: {current_app.instance_path}"
|
|
|
)
|
|
|
ctx: dict = {}
|
|
|
|
|
@@ -890,7 +962,7 @@ def shell_command() -> None:
|
|
|
with open(startup) as f:
|
|
|
eval(compile(f.read(), startup, "exec"), ctx)
|
|
|
|
|
|
- ctx.update(app.make_shell_context())
|
|
|
+ ctx.update(current_app.make_shell_context())
|
|
|
|
|
|
# Site, customize, or startup script can set a hook to call when
|
|
|
# entering interactive mode. The default one sets up readline with
|
|
@@ -963,22 +1035,14 @@ def routes_command(sort: str, all_methods: bool) -> None:
|
|
|
|
|
|
|
|
|
cli = FlaskGroup(
|
|
|
+ name="flask",
|
|
|
help="""\
|
|
|
A general utility script for Flask applications.
|
|
|
|
|
|
-Provides commands from Flask, extensions, and the application. Loads the
|
|
|
-application defined in the FLASK_APP environment variable, or from a wsgi.py
|
|
|
-file. Setting the FLASK_ENV environment variable to 'development' will enable
|
|
|
-debug mode.
|
|
|
-
|
|
|
-\b
|
|
|
- {prefix}{cmd} FLASK_APP=hello.py
|
|
|
- {prefix}{cmd} FLASK_ENV=development
|
|
|
- {prefix}flask run
|
|
|
-""".format(
|
|
|
- cmd="export" if os.name == "posix" else "set",
|
|
|
- prefix="$ " if os.name == "posix" else "> ",
|
|
|
- )
|
|
|
+An application to load must be given with the '--app' option,
|
|
|
+'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file
|
|
|
+in the current directory.
|
|
|
+""",
|
|
|
)
|
|
|
|
|
|
|