Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/cli.py: 25%
414 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1from __future__ import annotations
3import ast
4import inspect
5import os
6import platform
7import re
8import sys
9import traceback
10import typing as t
11from functools import update_wrapper
12from operator import attrgetter
14import click
15from click.core import ParameterSource
16from werkzeug import run_simple
17from werkzeug.serving import is_running_from_reloader
18from werkzeug.utils import import_string
20from .globals import current_app
21from .helpers import get_debug_flag
22from .helpers import get_load_dotenv
24if t.TYPE_CHECKING:
25 from .app import Flask
28class NoAppException(click.UsageError):
29 """Raised if an application cannot be found or loaded."""
32def find_best_app(module):
33 """Given a module instance this tries to find the best possible
34 application in the module or raises an exception.
35 """
36 from . import Flask
38 # Search for the most common names first.
39 for attr_name in ("app", "application"):
40 app = getattr(module, attr_name, None)
42 if isinstance(app, Flask):
43 return app
45 # Otherwise find the only object that is a Flask instance.
46 matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]
48 if len(matches) == 1:
49 return matches[0]
50 elif len(matches) > 1:
51 raise NoAppException(
52 "Detected multiple Flask applications in module"
53 f" '{module.__name__}'. Use '{module.__name__}:name'"
54 " to specify the correct one."
55 )
57 # Search for app factory functions.
58 for attr_name in ("create_app", "make_app"):
59 app_factory = getattr(module, attr_name, None)
61 if inspect.isfunction(app_factory):
62 try:
63 app = app_factory()
65 if isinstance(app, Flask):
66 return app
67 except TypeError as e:
68 if not _called_with_wrong_args(app_factory):
69 raise
71 raise NoAppException(
72 f"Detected factory '{attr_name}' in module '{module.__name__}',"
73 " but could not call it without arguments. Use"
74 f" '{module.__name__}:{attr_name}(args)'"
75 " to specify arguments."
76 ) from e
78 raise NoAppException(
79 "Failed to find Flask application or factory in module"
80 f" '{module.__name__}'. Use '{module.__name__}:name'"
81 " to specify one."
82 )
85def _called_with_wrong_args(f):
86 """Check whether calling a function raised a ``TypeError`` because
87 the call failed or because something in the factory raised the
88 error.
90 :param f: The function that was called.
91 :return: ``True`` if the call failed.
92 """
93 tb = sys.exc_info()[2]
95 try:
96 while tb is not None:
97 if tb.tb_frame.f_code is f.__code__:
98 # In the function, it was called successfully.
99 return False
101 tb = tb.tb_next
103 # Didn't reach the function.
104 return True
105 finally:
106 # Delete tb to break a circular reference.
107 # https://docs.python.org/2/library/sys.html#sys.exc_info
108 del tb
111def find_app_by_string(module, app_name):
112 """Check if the given string is a variable name or a function. Call
113 a function to get the app instance, or return the variable directly.
114 """
115 from . import Flask
117 # Parse app_name as a single expression to determine if it's a valid
118 # attribute name or function call.
119 try:
120 expr = ast.parse(app_name.strip(), mode="eval").body
121 except SyntaxError:
122 raise NoAppException(
123 f"Failed to parse {app_name!r} as an attribute name or function call."
124 ) from None
126 if isinstance(expr, ast.Name):
127 name = expr.id
128 args = []
129 kwargs = {}
130 elif isinstance(expr, ast.Call):
131 # Ensure the function name is an attribute name only.
132 if not isinstance(expr.func, ast.Name):
133 raise NoAppException(
134 f"Function reference must be a simple name: {app_name!r}."
135 )
137 name = expr.func.id
139 # Parse the positional and keyword arguments as literals.
140 try:
141 args = [ast.literal_eval(arg) for arg in expr.args]
142 kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords}
143 except ValueError:
144 # literal_eval gives cryptic error messages, show a generic
145 # message with the full expression instead.
146 raise NoAppException(
147 f"Failed to parse arguments as literal values: {app_name!r}."
148 ) from None
149 else:
150 raise NoAppException(
151 f"Failed to parse {app_name!r} as an attribute name or function call."
152 )
154 try:
155 attr = getattr(module, name)
156 except AttributeError as e:
157 raise NoAppException(
158 f"Failed to find attribute {name!r} in {module.__name__!r}."
159 ) from e
161 # If the attribute is a function, call it with any args and kwargs
162 # to get the real application.
163 if inspect.isfunction(attr):
164 try:
165 app = attr(*args, **kwargs)
166 except TypeError as e:
167 if not _called_with_wrong_args(attr):
168 raise
170 raise NoAppException(
171 f"The factory {app_name!r} in module"
172 f" {module.__name__!r} could not be called with the"
173 " specified arguments."
174 ) from e
175 else:
176 app = attr
178 if isinstance(app, Flask):
179 return app
181 raise NoAppException(
182 "A valid Flask application was not obtained from"
183 f" '{module.__name__}:{app_name}'."
184 )
187def prepare_import(path):
188 """Given a filename this will try to calculate the python path, add it
189 to the search path and return the actual module name that is expected.
190 """
191 path = os.path.realpath(path)
193 fname, ext = os.path.splitext(path)
194 if ext == ".py":
195 path = fname
197 if os.path.basename(path) == "__init__":
198 path = os.path.dirname(path)
200 module_name = []
202 # move up until outside package structure (no __init__.py)
203 while True:
204 path, name = os.path.split(path)
205 module_name.append(name)
207 if not os.path.exists(os.path.join(path, "__init__.py")):
208 break
210 if sys.path[0] != path:
211 sys.path.insert(0, path)
213 return ".".join(module_name[::-1])
216def locate_app(module_name, app_name, raise_if_not_found=True):
217 try:
218 __import__(module_name)
219 except ImportError:
220 # Reraise the ImportError if it occurred within the imported module.
221 # Determine this by checking whether the trace has a depth > 1.
222 if sys.exc_info()[2].tb_next:
223 raise NoAppException(
224 f"While importing {module_name!r}, an ImportError was"
225 f" raised:\n\n{traceback.format_exc()}"
226 ) from None
227 elif raise_if_not_found:
228 raise NoAppException(f"Could not import {module_name!r}.") from None
229 else:
230 return
232 module = sys.modules[module_name]
234 if app_name is None:
235 return find_best_app(module)
236 else:
237 return find_app_by_string(module, app_name)
240def get_version(ctx, param, value):
241 if not value or ctx.resilient_parsing:
242 return
244 import werkzeug
245 from . import __version__
247 click.echo(
248 f"Python {platform.python_version()}\n"
249 f"Flask {__version__}\n"
250 f"Werkzeug {werkzeug.__version__}",
251 color=ctx.color,
252 )
253 ctx.exit()
256version_option = click.Option(
257 ["--version"],
258 help="Show the Flask version.",
259 expose_value=False,
260 callback=get_version,
261 is_flag=True,
262 is_eager=True,
263)
266class ScriptInfo:
267 """Helper object to deal with Flask applications. This is usually not
268 necessary to interface with as it's used internally in the dispatching
269 to click. In future versions of Flask this object will most likely play
270 a bigger role. Typically it's created automatically by the
271 :class:`FlaskGroup` but you can also manually create it and pass it
272 onwards as click object.
273 """
275 def __init__(
276 self,
277 app_import_path: str | None = None,
278 create_app: t.Callable[..., Flask] | None = None,
279 set_debug_flag: bool = True,
280 ) -> None:
281 #: Optionally the import path for the Flask application.
282 self.app_import_path = app_import_path
283 #: Optionally a function that is passed the script info to create
284 #: the instance of the application.
285 self.create_app = create_app
286 #: A dictionary with arbitrary data that can be associated with
287 #: this script info.
288 self.data: t.Dict[t.Any, t.Any] = {}
289 self.set_debug_flag = set_debug_flag
290 self._loaded_app: Flask | None = None
292 def load_app(self) -> Flask:
293 """Loads the Flask app (if not yet loaded) and returns it. Calling
294 this multiple times will just result in the already loaded app to
295 be returned.
296 """
297 if self._loaded_app is not None:
298 return self._loaded_app
300 if self.create_app is not None:
301 app = self.create_app()
302 else:
303 if self.app_import_path:
304 path, name = (
305 re.split(r":(?![\\/])", self.app_import_path, 1) + [None]
306 )[:2]
307 import_name = prepare_import(path)
308 app = locate_app(import_name, name)
309 else:
310 for path in ("wsgi.py", "app.py"):
311 import_name = prepare_import(path)
312 app = locate_app(import_name, None, raise_if_not_found=False)
314 if app:
315 break
317 if not app:
318 raise NoAppException(
319 "Could not locate a Flask application. Use the"
320 " 'flask --app' option, 'FLASK_APP' environment"
321 " variable, or a 'wsgi.py' or 'app.py' file in the"
322 " current directory."
323 )
325 if self.set_debug_flag:
326 # Update the app's debug flag through the descriptor so that
327 # other values repopulate as well.
328 app.debug = get_debug_flag()
330 self._loaded_app = app
331 return app
334pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
337def with_appcontext(f):
338 """Wraps a callback so that it's guaranteed to be executed with the
339 script's application context.
341 Custom commands (and their options) registered under ``app.cli`` or
342 ``blueprint.cli`` will always have an app context available, this
343 decorator is not required in that case.
345 .. versionchanged:: 2.2
346 The app context is active for subcommands as well as the
347 decorated callback. The app context is always available to
348 ``app.cli`` command and parameter callbacks.
349 """
351 @click.pass_context
352 def decorator(__ctx, *args, **kwargs):
353 if not current_app:
354 app = __ctx.ensure_object(ScriptInfo).load_app()
355 __ctx.with_resource(app.app_context())
357 return __ctx.invoke(f, *args, **kwargs)
359 return update_wrapper(decorator, f)
362class AppGroup(click.Group):
363 """This works similar to a regular click :class:`~click.Group` but it
364 changes the behavior of the :meth:`command` decorator so that it
365 automatically wraps the functions in :func:`with_appcontext`.
367 Not to be confused with :class:`FlaskGroup`.
368 """
370 def command(self, *args, **kwargs):
371 """This works exactly like the method of the same name on a regular
372 :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
373 unless it's disabled by passing ``with_appcontext=False``.
374 """
375 wrap_for_ctx = kwargs.pop("with_appcontext", True)
377 def decorator(f):
378 if wrap_for_ctx:
379 f = with_appcontext(f)
380 return click.Group.command(self, *args, **kwargs)(f)
382 return decorator
384 def group(self, *args, **kwargs):
385 """This works exactly like the method of the same name on a regular
386 :class:`click.Group` but it defaults the group class to
387 :class:`AppGroup`.
388 """
389 kwargs.setdefault("cls", AppGroup)
390 return click.Group.group(self, *args, **kwargs)
393def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:
394 if value is None:
395 return None
397 info = ctx.ensure_object(ScriptInfo)
398 info.app_import_path = value
399 return value
402# This option is eager so the app will be available if --help is given.
403# --help is also eager, so --app must be before it in the param list.
404# no_args_is_help bypasses eager processing, so this option must be
405# processed manually in that case to ensure FLASK_APP gets picked up.
406_app_option = click.Option(
407 ["-A", "--app"],
408 metavar="IMPORT",
409 help=(
410 "The Flask application or factory function to load, in the form 'module:name'."
411 " Module can be a dotted import or file path. Name is not required if it is"
412 " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"
413 " pass arguments."
414 ),
415 is_eager=True,
416 expose_value=False,
417 callback=_set_app,
418)
421def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
422 # If the flag isn't provided, it will default to False. Don't use
423 # that, let debug be set by env in that case.
424 source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
426 if source is not None and source in (
427 ParameterSource.DEFAULT,
428 ParameterSource.DEFAULT_MAP,
429 ):
430 return None
432 # Set with env var instead of ScriptInfo.load so that it can be
433 # accessed early during a factory function.
434 os.environ["FLASK_DEBUG"] = "1" if value else "0"
435 return value
438_debug_option = click.Option(
439 ["--debug/--no-debug"],
440 help="Set debug mode.",
441 expose_value=False,
442 callback=_set_debug,
443)
446def _env_file_callback(
447 ctx: click.Context, param: click.Option, value: str | None
448) -> str | None:
449 if value is None:
450 return None
452 import importlib
454 try:
455 importlib.import_module("dotenv")
456 except ImportError:
457 raise click.BadParameter(
458 "python-dotenv must be installed to load an env file.",
459 ctx=ctx,
460 param=param,
461 ) from None
463 # Don't check FLASK_SKIP_DOTENV, that only disables automatically
464 # loading .env and .flaskenv files.
465 load_dotenv(value)
466 return value
469# This option is eager so env vars are loaded as early as possible to be
470# used by other options.
471_env_file_option = click.Option(
472 ["-e", "--env-file"],
473 type=click.Path(exists=True, dir_okay=False),
474 help="Load environment variables from this file. python-dotenv must be installed.",
475 is_eager=True,
476 expose_value=False,
477 callback=_env_file_callback,
478)
481class FlaskGroup(AppGroup):
482 """Special subclass of the :class:`AppGroup` group that supports
483 loading more commands from the configured Flask app. Normally a
484 developer does not have to interface with this class but there are
485 some very advanced use cases for which it makes sense to create an
486 instance of this. see :ref:`custom-scripts`.
488 :param add_default_commands: if this is True then the default run and
489 shell commands will be added.
490 :param add_version_option: adds the ``--version`` option.
491 :param create_app: an optional callback that is passed the script info and
492 returns the loaded app.
493 :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
494 files to set environment variables. Will also change the working
495 directory to the directory containing the first file found.
496 :param set_debug_flag: Set the app's debug flag.
498 .. versionchanged:: 2.2
499 Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
501 .. versionchanged:: 2.2
502 An app context is pushed when running ``app.cli`` commands, so
503 ``@with_appcontext`` is no longer required for those commands.
505 .. versionchanged:: 1.0
506 If installed, python-dotenv will be used to load environment variables
507 from :file:`.env` and :file:`.flaskenv` files.
508 """
510 def __init__(
511 self,
512 add_default_commands: bool = True,
513 create_app: t.Callable[..., Flask] | None = None,
514 add_version_option: bool = True,
515 load_dotenv: bool = True,
516 set_debug_flag: bool = True,
517 **extra: t.Any,
518 ) -> None:
519 params = list(extra.pop("params", None) or ())
520 # Processing is done with option callbacks instead of a group
521 # callback. This allows users to make a custom group callback
522 # without losing the behavior. --env-file must come first so
523 # that it is eagerly evaluated before --app.
524 params.extend((_env_file_option, _app_option, _debug_option))
526 if add_version_option:
527 params.append(version_option)
529 if "context_settings" not in extra:
530 extra["context_settings"] = {}
532 extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK")
534 super().__init__(params=params, **extra)
536 self.create_app = create_app
537 self.load_dotenv = load_dotenv
538 self.set_debug_flag = set_debug_flag
540 if add_default_commands:
541 self.add_command(run_command)
542 self.add_command(shell_command)
543 self.add_command(routes_command)
545 self._loaded_plugin_commands = False
547 def _load_plugin_commands(self):
548 if self._loaded_plugin_commands:
549 return
551 if sys.version_info >= (3, 10):
552 from importlib import metadata
553 else:
554 # Use a backport on Python < 3.10. We technically have
555 # importlib.metadata on 3.8+, but the API changed in 3.10,
556 # so use the backport for consistency.
557 import importlib_metadata as metadata
559 for ep in metadata.entry_points(group="flask.commands"):
560 self.add_command(ep.load(), ep.name)
562 self._loaded_plugin_commands = True
564 def get_command(self, ctx, name):
565 self._load_plugin_commands()
566 # Look up built-in and plugin commands, which should be
567 # available even if the app fails to load.
568 rv = super().get_command(ctx, name)
570 if rv is not None:
571 return rv
573 info = ctx.ensure_object(ScriptInfo)
575 # Look up commands provided by the app, showing an error and
576 # continuing if the app couldn't be loaded.
577 try:
578 app = info.load_app()
579 except NoAppException as e:
580 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
581 return None
583 # Push an app context for the loaded app unless it is already
584 # active somehow. This makes the context available to parameter
585 # and command callbacks without needing @with_appcontext.
586 if not current_app or current_app._get_current_object() is not app:
587 ctx.with_resource(app.app_context())
589 return app.cli.get_command(ctx, name)
591 def list_commands(self, ctx):
592 self._load_plugin_commands()
593 # Start with the built-in and plugin commands.
594 rv = set(super().list_commands(ctx))
595 info = ctx.ensure_object(ScriptInfo)
597 # Add commands provided by the app, showing an error and
598 # continuing if the app couldn't be loaded.
599 try:
600 rv.update(info.load_app().cli.list_commands(ctx))
601 except NoAppException as e:
602 # When an app couldn't be loaded, show the error message
603 # without the traceback.
604 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
605 except Exception:
606 # When any other errors occurred during loading, show the
607 # full traceback.
608 click.secho(f"{traceback.format_exc()}\n", err=True, fg="red")
610 return sorted(rv)
612 def make_context(
613 self,
614 info_name: str | None,
615 args: list[str],
616 parent: click.Context | None = None,
617 **extra: t.Any,
618 ) -> click.Context:
619 # Set a flag to tell app.run to become a no-op. If app.run was
620 # not in a __name__ == __main__ guard, it would start the server
621 # when importing, blocking whatever command is being called.
622 os.environ["FLASK_RUN_FROM_CLI"] = "true"
624 # Attempt to load .env and .flask env files. The --env-file
625 # option can cause another file to be loaded.
626 if get_load_dotenv(self.load_dotenv):
627 load_dotenv()
629 if "obj" not in extra and "obj" not in self.context_settings:
630 extra["obj"] = ScriptInfo(
631 create_app=self.create_app, set_debug_flag=self.set_debug_flag
632 )
634 return super().make_context(info_name, args, parent=parent, **extra)
636 def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
637 if not args and self.no_args_is_help:
638 # Attempt to load --env-file and --app early in case they
639 # were given as env vars. Otherwise no_args_is_help will not
640 # see commands from app.cli.
641 _env_file_option.handle_parse_result(ctx, {}, [])
642 _app_option.handle_parse_result(ctx, {}, [])
644 return super().parse_args(ctx, args)
647def _path_is_ancestor(path, other):
648 """Take ``other`` and remove the length of ``path`` from it. Then join it
649 to ``path``. If it is the original value, ``path`` is an ancestor of
650 ``other``."""
651 return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
654def load_dotenv(path: str | os.PathLike | None = None) -> bool:
655 """Load "dotenv" files in order of precedence to set environment variables.
657 If an env var is already set it is not overwritten, so earlier files in the
658 list are preferred over later files.
660 This is a no-op if `python-dotenv`_ is not installed.
662 .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
664 :param path: Load the file at this location instead of searching.
665 :return: ``True`` if a file was loaded.
667 .. versionchanged:: 2.0
668 The current directory is not changed to the location of the
669 loaded file.
671 .. versionchanged:: 2.0
672 When loading the env files, set the default encoding to UTF-8.
674 .. versionchanged:: 1.1.0
675 Returns ``False`` when python-dotenv is not installed, or when
676 the given path isn't a file.
678 .. versionadded:: 1.0
679 """
680 try:
681 import dotenv
682 except ImportError:
683 if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
684 click.secho(
685 " * Tip: There are .env or .flaskenv files present."
686 ' Do "pip install python-dotenv" to use them.',
687 fg="yellow",
688 err=True,
689 )
691 return False
693 # Always return after attempting to load a given path, don't load
694 # the default files.
695 if path is not None:
696 if os.path.isfile(path):
697 return dotenv.load_dotenv(path, encoding="utf-8")
699 return False
701 loaded = False
703 for name in (".env", ".flaskenv"):
704 path = dotenv.find_dotenv(name, usecwd=True)
706 if not path:
707 continue
709 dotenv.load_dotenv(path, encoding="utf-8")
710 loaded = True
712 return loaded # True if at least one file was located and loaded.
715def show_server_banner(debug, app_import_path):
716 """Show extra startup messages the first time the server is run,
717 ignoring the reloader.
718 """
719 if is_running_from_reloader():
720 return
722 if app_import_path is not None:
723 click.echo(f" * Serving Flask app '{app_import_path}'")
725 if debug is not None:
726 click.echo(f" * Debug mode: {'on' if debug else 'off'}")
729class CertParamType(click.ParamType):
730 """Click option type for the ``--cert`` option. Allows either an
731 existing file, the string ``'adhoc'``, or an import for a
732 :class:`~ssl.SSLContext` object.
733 """
735 name = "path"
737 def __init__(self):
738 self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
740 def convert(self, value, param, ctx):
741 try:
742 import ssl
743 except ImportError:
744 raise click.BadParameter(
745 'Using "--cert" requires Python to be compiled with SSL support.',
746 ctx,
747 param,
748 ) from None
750 try:
751 return self.path_type(value, param, ctx)
752 except click.BadParameter:
753 value = click.STRING(value, param, ctx).lower()
755 if value == "adhoc":
756 try:
757 import cryptography # noqa: F401
758 except ImportError:
759 raise click.BadParameter(
760 "Using ad-hoc certificates requires the cryptography library.",
761 ctx,
762 param,
763 ) from None
765 return value
767 obj = import_string(value, silent=True)
769 if isinstance(obj, ssl.SSLContext):
770 return obj
772 raise
775def _validate_key(ctx, param, value):
776 """The ``--key`` option must be specified when ``--cert`` is a file.
777 Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
778 """
779 cert = ctx.params.get("cert")
780 is_adhoc = cert == "adhoc"
782 try:
783 import ssl
784 except ImportError:
785 is_context = False
786 else:
787 is_context = isinstance(cert, ssl.SSLContext)
789 if value is not None:
790 if is_adhoc:
791 raise click.BadParameter(
792 'When "--cert" is "adhoc", "--key" is not used.', ctx, param
793 )
795 if is_context:
796 raise click.BadParameter(
797 'When "--cert" is an SSLContext object, "--key is not used.', ctx, param
798 )
800 if not cert:
801 raise click.BadParameter('"--cert" must also be specified.', ctx, param)
803 ctx.params["cert"] = cert, value
805 else:
806 if cert and not (is_adhoc or is_context):
807 raise click.BadParameter('Required when using "--cert".', ctx, param)
809 return value
812class SeparatedPathType(click.Path):
813 """Click option type that accepts a list of values separated by the
814 OS's path separator (``:``, ``;`` on Windows). Each value is
815 validated as a :class:`click.Path` type.
816 """
818 def convert(self, value, param, ctx):
819 items = self.split_envvar_value(value)
820 super_convert = super().convert
821 return [super_convert(item, param, ctx) for item in items]
824@click.command("run", short_help="Run a development server.")
825@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.")
826@click.option("--port", "-p", default=5000, help="The port to bind to.")
827@click.option(
828 "--cert",
829 type=CertParamType(),
830 help="Specify a certificate file to use HTTPS.",
831 is_eager=True,
832)
833@click.option(
834 "--key",
835 type=click.Path(exists=True, dir_okay=False, resolve_path=True),
836 callback=_validate_key,
837 expose_value=False,
838 help="The key file to use when specifying a certificate.",
839)
840@click.option(
841 "--reload/--no-reload",
842 default=None,
843 help="Enable or disable the reloader. By default the reloader "
844 "is active if debug is enabled.",
845)
846@click.option(
847 "--debugger/--no-debugger",
848 default=None,
849 help="Enable or disable the debugger. By default the debugger "
850 "is active if debug is enabled.",
851)
852@click.option(
853 "--with-threads/--without-threads",
854 default=True,
855 help="Enable or disable multithreading.",
856)
857@click.option(
858 "--extra-files",
859 default=None,
860 type=SeparatedPathType(),
861 help=(
862 "Extra files that trigger a reload on change. Multiple paths"
863 f" are separated by {os.path.pathsep!r}."
864 ),
865)
866@click.option(
867 "--exclude-patterns",
868 default=None,
869 type=SeparatedPathType(),
870 help=(
871 "Files matching these fnmatch patterns will not trigger a reload"
872 " on change. Multiple patterns are separated by"
873 f" {os.path.pathsep!r}."
874 ),
875)
876@pass_script_info
877def run_command(
878 info,
879 host,
880 port,
881 reload,
882 debugger,
883 with_threads,
884 cert,
885 extra_files,
886 exclude_patterns,
887):
888 """Run a local development server.
890 This server is for development purposes only. It does not provide
891 the stability, security, or performance of production WSGI servers.
893 The reloader and debugger are enabled by default with the '--debug'
894 option.
895 """
896 try:
897 app = info.load_app()
898 except Exception as e:
899 if is_running_from_reloader():
900 # When reloading, print out the error immediately, but raise
901 # it later so the debugger or server can handle it.
902 traceback.print_exc()
903 err = e
905 def app(environ, start_response):
906 raise err from None
908 else:
909 # When not reloading, raise the error immediately so the
910 # command fails.
911 raise e from None
913 debug = get_debug_flag()
915 if reload is None:
916 reload = debug
918 if debugger is None:
919 debugger = debug
921 show_server_banner(debug, info.app_import_path)
923 run_simple(
924 host,
925 port,
926 app,
927 use_reloader=reload,
928 use_debugger=debugger,
929 threaded=with_threads,
930 ssl_context=cert,
931 extra_files=extra_files,
932 exclude_patterns=exclude_patterns,
933 )
936run_command.params.insert(0, _debug_option)
939@click.command("shell", short_help="Run a shell in the app context.")
940@with_appcontext
941def shell_command() -> None:
942 """Run an interactive Python shell in the context of a given
943 Flask application. The application will populate the default
944 namespace of this shell according to its configuration.
946 This is useful for executing small snippets of management code
947 without having to manually configure the application.
948 """
949 import code
951 banner = (
952 f"Python {sys.version} on {sys.platform}\n"
953 f"App: {current_app.import_name}\n"
954 f"Instance: {current_app.instance_path}"
955 )
956 ctx: dict = {}
958 # Support the regular Python interpreter startup script if someone
959 # is using it.
960 startup = os.environ.get("PYTHONSTARTUP")
961 if startup and os.path.isfile(startup):
962 with open(startup) as f:
963 eval(compile(f.read(), startup, "exec"), ctx)
965 ctx.update(current_app.make_shell_context())
967 # Site, customize, or startup script can set a hook to call when
968 # entering interactive mode. The default one sets up readline with
969 # tab and history completion.
970 interactive_hook = getattr(sys, "__interactivehook__", None)
972 if interactive_hook is not None:
973 try:
974 import readline
975 from rlcompleter import Completer
976 except ImportError:
977 pass
978 else:
979 # rlcompleter uses __main__.__dict__ by default, which is
980 # flask.__main__. Use the shell context instead.
981 readline.set_completer(Completer(ctx).complete)
983 interactive_hook()
985 code.interact(banner=banner, local=ctx)
988@click.command("routes", short_help="Show the routes for the app.")
989@click.option(
990 "--sort",
991 "-s",
992 type=click.Choice(("endpoint", "methods", "rule", "match")),
993 default="endpoint",
994 help=(
995 'Method to sort routes by. "match" is the order that Flask will match '
996 "routes when dispatching a request."
997 ),
998)
999@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
1000@with_appcontext
1001def routes_command(sort: str, all_methods: bool) -> None:
1002 """Show all registered routes with endpoints and methods."""
1004 rules = list(current_app.url_map.iter_rules())
1005 if not rules:
1006 click.echo("No routes were registered.")
1007 return
1009 ignored_methods = set(() if all_methods else ("HEAD", "OPTIONS"))
1011 if sort in ("endpoint", "rule"):
1012 rules = sorted(rules, key=attrgetter(sort))
1013 elif sort == "methods":
1014 rules = sorted(rules, key=lambda rule: sorted(rule.methods)) # type: ignore
1016 rule_methods = [
1017 ", ".join(sorted(rule.methods - ignored_methods)) # type: ignore
1018 for rule in rules
1019 ]
1021 headers = ("Endpoint", "Methods", "Rule")
1022 widths = (
1023 max(len(rule.endpoint) for rule in rules),
1024 max(len(methods) for methods in rule_methods),
1025 max(len(rule.rule) for rule in rules),
1026 )
1027 widths = [max(len(h), w) for h, w in zip(headers, widths)]
1028 row = "{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}".format(*widths)
1030 click.echo(row.format(*headers).strip())
1031 click.echo(row.format(*("-" * width for width in widths)))
1033 for rule, methods in zip(rules, rule_methods):
1034 click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
1037cli = FlaskGroup(
1038 name="flask",
1039 help="""\
1040A general utility script for Flask applications.
1042An application to load must be given with the '--app' option,
1043'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file
1044in the current directory.
1045""",
1046)
1049def main() -> None:
1050 cli.main()
1053if __name__ == "__main__":
1054 main()