Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/cli.py: 22%
423 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
1import ast
2import inspect
3import os
4import platform
5import re
6import sys
7import traceback
8from functools import update_wrapper
9from operator import attrgetter
10from threading import Lock
11from threading import Thread
13import click
14from werkzeug.utils import import_string
16from .globals import current_app
17from .helpers import get_debug_flag
18from .helpers import get_env
19from .helpers import get_load_dotenv
22class NoAppException(click.UsageError):
23 """Raised if an application cannot be found or loaded."""
26def find_best_app(module):
27 """Given a module instance this tries to find the best possible
28 application in the module or raises an exception.
29 """
30 from . import Flask
32 # Search for the most common names first.
33 for attr_name in ("app", "application"):
34 app = getattr(module, attr_name, None)
36 if isinstance(app, Flask):
37 return app
39 # Otherwise find the only object that is a Flask instance.
40 matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]
42 if len(matches) == 1:
43 return matches[0]
44 elif len(matches) > 1:
45 raise NoAppException(
46 "Detected multiple Flask applications in module"
47 f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
48 f" to specify the correct one."
49 )
51 # Search for app factory functions.
52 for attr_name in ("create_app", "make_app"):
53 app_factory = getattr(module, attr_name, None)
55 if inspect.isfunction(app_factory):
56 try:
57 app = app_factory()
59 if isinstance(app, Flask):
60 return app
61 except TypeError as e:
62 if not _called_with_wrong_args(app_factory):
63 raise
65 raise NoAppException(
66 f"Detected factory {attr_name!r} in module {module.__name__!r},"
67 " but could not call it without arguments. Use"
68 f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
69 " to specify arguments."
70 ) from e
72 raise NoAppException(
73 "Failed to find Flask application or factory in module"
74 f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
75 " to specify one."
76 )
79def _called_with_wrong_args(f):
80 """Check whether calling a function raised a ``TypeError`` because
81 the call failed or because something in the factory raised the
82 error.
84 :param f: The function that was called.
85 :return: ``True`` if the call failed.
86 """
87 tb = sys.exc_info()[2]
89 try:
90 while tb is not None:
91 if tb.tb_frame.f_code is f.__code__:
92 # In the function, it was called successfully.
93 return False
95 tb = tb.tb_next
97 # Didn't reach the function.
98 return True
99 finally:
100 # Delete tb to break a circular reference.
101 # https://docs.python.org/2/library/sys.html#sys.exc_info
102 del tb
105def find_app_by_string(module, app_name):
106 """Check if the given string is a variable name or a function. Call
107 a function to get the app instance, or return the variable directly.
108 """
109 from . import Flask
111 # Parse app_name as a single expression to determine if it's a valid
112 # attribute name or function call.
113 try:
114 expr = ast.parse(app_name.strip(), mode="eval").body
115 except SyntaxError:
116 raise NoAppException(
117 f"Failed to parse {app_name!r} as an attribute name or function call."
118 ) from None
120 if isinstance(expr, ast.Name):
121 name = expr.id
122 args = []
123 kwargs = {}
124 elif isinstance(expr, ast.Call):
125 # Ensure the function name is an attribute name only.
126 if not isinstance(expr.func, ast.Name):
127 raise NoAppException(
128 f"Function reference must be a simple name: {app_name!r}."
129 )
131 name = expr.func.id
133 # Parse the positional and keyword arguments as literals.
134 try:
135 args = [ast.literal_eval(arg) for arg in expr.args]
136 kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords}
137 except ValueError:
138 # literal_eval gives cryptic error messages, show a generic
139 # message with the full expression instead.
140 raise NoAppException(
141 f"Failed to parse arguments as literal values: {app_name!r}."
142 ) from None
143 else:
144 raise NoAppException(
145 f"Failed to parse {app_name!r} as an attribute name or function call."
146 )
148 try:
149 attr = getattr(module, name)
150 except AttributeError as e:
151 raise NoAppException(
152 f"Failed to find attribute {name!r} in {module.__name__!r}."
153 ) from e
155 # If the attribute is a function, call it with any args and kwargs
156 # to get the real application.
157 if inspect.isfunction(attr):
158 try:
159 app = attr(*args, **kwargs)
160 except TypeError as e:
161 if not _called_with_wrong_args(attr):
162 raise
164 raise NoAppException(
165 f"The factory {app_name!r} in module"
166 f" {module.__name__!r} could not be called with the"
167 " specified arguments."
168 ) from e
169 else:
170 app = attr
172 if isinstance(app, Flask):
173 return app
175 raise NoAppException(
176 "A valid Flask application was not obtained from"
177 f" '{module.__name__}:{app_name}'."
178 )
181def prepare_import(path):
182 """Given a filename this will try to calculate the python path, add it
183 to the search path and return the actual module name that is expected.
184 """
185 path = os.path.realpath(path)
187 fname, ext = os.path.splitext(path)
188 if ext == ".py":
189 path = fname
191 if os.path.basename(path) == "__init__":
192 path = os.path.dirname(path)
194 module_name = []
196 # move up until outside package structure (no __init__.py)
197 while True:
198 path, name = os.path.split(path)
199 module_name.append(name)
201 if not os.path.exists(os.path.join(path, "__init__.py")):
202 break
204 if sys.path[0] != path:
205 sys.path.insert(0, path)
207 return ".".join(module_name[::-1])
210def locate_app(module_name, app_name, raise_if_not_found=True):
211 __traceback_hide__ = True # noqa: F841
213 try:
214 __import__(module_name)
215 except ImportError:
216 # Reraise the ImportError if it occurred within the imported module.
217 # Determine this by checking whether the trace has a depth > 1.
218 if sys.exc_info()[2].tb_next:
219 raise NoAppException(
220 f"While importing {module_name!r}, an ImportError was"
221 f" raised:\n\n{traceback.format_exc()}"
222 ) from None
223 elif raise_if_not_found:
224 raise NoAppException(f"Could not import {module_name!r}.") from None
225 else:
226 return
228 module = sys.modules[module_name]
230 if app_name is None:
231 return find_best_app(module)
232 else:
233 return find_app_by_string(module, app_name)
236def get_version(ctx, param, value):
237 if not value or ctx.resilient_parsing:
238 return
240 import werkzeug
241 from . import __version__
243 click.echo(
244 f"Python {platform.python_version()}\n"
245 f"Flask {__version__}\n"
246 f"Werkzeug {werkzeug.__version__}",
247 color=ctx.color,
248 )
249 ctx.exit()
252version_option = click.Option(
253 ["--version"],
254 help="Show the flask version",
255 expose_value=False,
256 callback=get_version,
257 is_flag=True,
258 is_eager=True,
259)
262class DispatchingApp:
263 """Special application that dispatches to a Flask application which
264 is imported by name in a background thread. If an error happens
265 it is recorded and shown as part of the WSGI handling which in case
266 of the Werkzeug debugger means that it shows up in the browser.
267 """
269 def __init__(self, loader, use_eager_loading=None):
270 self.loader = loader
271 self._app = None
272 self._lock = Lock()
273 self._bg_loading_exc = None
275 if use_eager_loading is None:
276 use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
278 if use_eager_loading:
279 self._load_unlocked()
280 else:
281 self._load_in_background()
283 def _load_in_background(self):
284 # Store the Click context and push it in the loader thread so
285 # script_info is still available.
286 ctx = click.get_current_context(silent=True)
288 def _load_app():
289 __traceback_hide__ = True # noqa: F841
291 with self._lock:
292 if ctx is not None:
293 click.globals.push_context(ctx)
295 try:
296 self._load_unlocked()
297 except Exception as e:
298 self._bg_loading_exc = e
300 t = Thread(target=_load_app, args=())
301 t.start()
303 def _flush_bg_loading_exception(self):
304 __traceback_hide__ = True # noqa: F841
305 exc = self._bg_loading_exc
307 if exc is not None:
308 self._bg_loading_exc = None
309 raise exc
311 def _load_unlocked(self):
312 __traceback_hide__ = True # noqa: F841
313 self._app = rv = self.loader()
314 self._bg_loading_exc = None
315 return rv
317 def __call__(self, environ, start_response):
318 __traceback_hide__ = True # noqa: F841
319 if self._app is not None:
320 return self._app(environ, start_response)
321 self._flush_bg_loading_exception()
322 with self._lock:
323 if self._app is not None:
324 rv = self._app
325 else:
326 rv = self._load_unlocked()
327 return rv(environ, start_response)
330class ScriptInfo:
331 """Helper object to deal with Flask applications. This is usually not
332 necessary to interface with as it's used internally in the dispatching
333 to click. In future versions of Flask this object will most likely play
334 a bigger role. Typically it's created automatically by the
335 :class:`FlaskGroup` but you can also manually create it and pass it
336 onwards as click object.
337 """
339 def __init__(self, app_import_path=None, create_app=None, set_debug_flag=True):
340 #: Optionally the import path for the Flask application.
341 self.app_import_path = app_import_path or os.environ.get("FLASK_APP")
342 #: Optionally a function that is passed the script info to create
343 #: the instance of the application.
344 self.create_app = create_app
345 #: A dictionary with arbitrary data that can be associated with
346 #: this script info.
347 self.data = {}
348 self.set_debug_flag = set_debug_flag
349 self._loaded_app = None
351 def load_app(self):
352 """Loads the Flask app (if not yet loaded) and returns it. Calling
353 this multiple times will just result in the already loaded app to
354 be returned.
355 """
356 __traceback_hide__ = True # noqa: F841
358 if self._loaded_app is not None:
359 return self._loaded_app
361 if self.create_app is not None:
362 app = self.create_app()
363 else:
364 if self.app_import_path:
365 path, name = (
366 re.split(r":(?![\\/])", self.app_import_path, 1) + [None]
367 )[:2]
368 import_name = prepare_import(path)
369 app = locate_app(import_name, name)
370 else:
371 for path in ("wsgi.py", "app.py"):
372 import_name = prepare_import(path)
373 app = locate_app(import_name, None, raise_if_not_found=False)
375 if app:
376 break
378 if not app:
379 raise NoAppException(
380 "Could not locate a Flask application. You did not provide "
381 'the "FLASK_APP" environment variable, and a "wsgi.py" or '
382 '"app.py" module was not found in the current directory.'
383 )
385 if self.set_debug_flag:
386 # Update the app's debug flag through the descriptor so that
387 # other values repopulate as well.
388 app.debug = get_debug_flag()
390 self._loaded_app = app
391 return app
394pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
397def with_appcontext(f):
398 """Wraps a callback so that it's guaranteed to be executed with the
399 script's application context. If callbacks are registered directly
400 to the ``app.cli`` object then they are wrapped with this function
401 by default unless it's disabled.
402 """
404 @click.pass_context
405 def decorator(__ctx, *args, **kwargs):
406 with __ctx.ensure_object(ScriptInfo).load_app().app_context():
407 return __ctx.invoke(f, *args, **kwargs)
409 return update_wrapper(decorator, f)
412class AppGroup(click.Group):
413 """This works similar to a regular click :class:`~click.Group` but it
414 changes the behavior of the :meth:`command` decorator so that it
415 automatically wraps the functions in :func:`with_appcontext`.
417 Not to be confused with :class:`FlaskGroup`.
418 """
420 def command(self, *args, **kwargs):
421 """This works exactly like the method of the same name on a regular
422 :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
423 unless it's disabled by passing ``with_appcontext=False``.
424 """
425 wrap_for_ctx = kwargs.pop("with_appcontext", True)
427 def decorator(f):
428 if wrap_for_ctx:
429 f = with_appcontext(f)
430 return click.Group.command(self, *args, **kwargs)(f)
432 return decorator
434 def group(self, *args, **kwargs):
435 """This works exactly like the method of the same name on a regular
436 :class:`click.Group` but it defaults the group class to
437 :class:`AppGroup`.
438 """
439 kwargs.setdefault("cls", AppGroup)
440 return click.Group.group(self, *args, **kwargs)
443class FlaskGroup(AppGroup):
444 """Special subclass of the :class:`AppGroup` group that supports
445 loading more commands from the configured Flask app. Normally a
446 developer does not have to interface with this class but there are
447 some very advanced use cases for which it makes sense to create an
448 instance of this. see :ref:`custom-scripts`.
450 :param add_default_commands: if this is True then the default run and
451 shell commands will be added.
452 :param add_version_option: adds the ``--version`` option.
453 :param create_app: an optional callback that is passed the script info and
454 returns the loaded app.
455 :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
456 files to set environment variables. Will also change the working
457 directory to the directory containing the first file found.
458 :param set_debug_flag: Set the app's debug flag based on the active
459 environment
461 .. versionchanged:: 1.0
462 If installed, python-dotenv will be used to load environment variables
463 from :file:`.env` and :file:`.flaskenv` files.
464 """
466 def __init__(
467 self,
468 add_default_commands=True,
469 create_app=None,
470 add_version_option=True,
471 load_dotenv=True,
472 set_debug_flag=True,
473 **extra,
474 ):
475 params = list(extra.pop("params", None) or ())
477 if add_version_option:
478 params.append(version_option)
480 AppGroup.__init__(self, params=params, **extra)
481 self.create_app = create_app
482 self.load_dotenv = load_dotenv
483 self.set_debug_flag = set_debug_flag
485 if add_default_commands:
486 self.add_command(run_command)
487 self.add_command(shell_command)
488 self.add_command(routes_command)
490 self._loaded_plugin_commands = False
492 def _load_plugin_commands(self):
493 if self._loaded_plugin_commands:
494 return
496 if sys.version_info >= (3, 10):
497 from importlib import metadata
498 else:
499 # Use a backport on Python < 3.10. We technically have
500 # importlib.metadata on 3.8+, but the API changed in 3.10,
501 # so use the backport for consistency.
502 import importlib_metadata as metadata
504 for ep in metadata.entry_points(group="flask.commands"):
505 self.add_command(ep.load(), ep.name)
507 self._loaded_plugin_commands = True
509 def get_command(self, ctx, name):
510 self._load_plugin_commands()
511 # Look up built-in and plugin commands, which should be
512 # available even if the app fails to load.
513 rv = super().get_command(ctx, name)
515 if rv is not None:
516 return rv
518 info = ctx.ensure_object(ScriptInfo)
520 # Look up commands provided by the app, showing an error and
521 # continuing if the app couldn't be loaded.
522 try:
523 return info.load_app().cli.get_command(ctx, name)
524 except NoAppException as e:
525 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
527 def list_commands(self, ctx):
528 self._load_plugin_commands()
529 # Start with the built-in and plugin commands.
530 rv = set(super().list_commands(ctx))
531 info = ctx.ensure_object(ScriptInfo)
533 # Add commands provided by the app, showing an error and
534 # continuing if the app couldn't be loaded.
535 try:
536 rv.update(info.load_app().cli.list_commands(ctx))
537 except NoAppException as e:
538 # When an app couldn't be loaded, show the error message
539 # without the traceback.
540 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
541 except Exception:
542 # When any other errors occurred during loading, show the
543 # full traceback.
544 click.secho(f"{traceback.format_exc()}\n", err=True, fg="red")
546 return sorted(rv)
548 def main(self, *args, **kwargs):
549 # Set a global flag that indicates that we were invoked from the
550 # command line interface. This is detected by Flask.run to make the
551 # call into a no-op. This is necessary to avoid ugly errors when the
552 # script that is loaded here also attempts to start a server.
553 os.environ["FLASK_RUN_FROM_CLI"] = "true"
555 if get_load_dotenv(self.load_dotenv):
556 load_dotenv()
558 obj = kwargs.get("obj")
560 if obj is None:
561 obj = ScriptInfo(
562 create_app=self.create_app, set_debug_flag=self.set_debug_flag
563 )
565 kwargs["obj"] = obj
566 kwargs.setdefault("auto_envvar_prefix", "FLASK")
567 return super().main(*args, **kwargs)
570def _path_is_ancestor(path, other):
571 """Take ``other`` and remove the length of ``path`` from it. Then join it
572 to ``path``. If it is the original value, ``path`` is an ancestor of
573 ``other``."""
574 return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
577def load_dotenv(path=None):
578 """Load "dotenv" files in order of precedence to set environment variables.
580 If an env var is already set it is not overwritten, so earlier files in the
581 list are preferred over later files.
583 This is a no-op if `python-dotenv`_ is not installed.
585 .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
587 :param path: Load the file at this location instead of searching.
588 :return: ``True`` if a file was loaded.
590 .. versionchanged:: 1.1.0
591 Returns ``False`` when python-dotenv is not installed, or when
592 the given path isn't a file.
594 .. versionchanged:: 2.0
595 When loading the env files, set the default encoding to UTF-8.
597 .. versionadded:: 1.0
598 """
599 try:
600 import dotenv
601 except ImportError:
602 if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
603 click.secho(
604 " * Tip: There are .env or .flaskenv files present."
605 ' Do "pip install python-dotenv" to use them.',
606 fg="yellow",
607 err=True,
608 )
610 return False
612 # if the given path specifies the actual file then return True,
613 # else False
614 if path is not None:
615 if os.path.isfile(path):
616 return dotenv.load_dotenv(path, encoding="utf-8")
618 return False
620 new_dir = None
622 for name in (".env", ".flaskenv"):
623 path = dotenv.find_dotenv(name, usecwd=True)
625 if not path:
626 continue
628 if new_dir is None:
629 new_dir = os.path.dirname(path)
631 dotenv.load_dotenv(path, encoding="utf-8")
633 return new_dir is not None # at least one file was located and loaded
636def show_server_banner(env, debug, app_import_path, eager_loading):
637 """Show extra startup messages the first time the server is run,
638 ignoring the reloader.
639 """
640 if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
641 return
643 if app_import_path is not None:
644 message = f" * Serving Flask app {app_import_path!r}"
646 if not eager_loading:
647 message += " (lazy loading)"
649 click.echo(message)
651 click.echo(f" * Environment: {env}")
653 if env == "production":
654 click.secho(
655 " WARNING: This is a development server. Do not use it in"
656 " a production deployment.",
657 fg="red",
658 )
659 click.secho(" Use a production WSGI server instead.", dim=True)
661 if debug is not None:
662 click.echo(f" * Debug mode: {'on' if debug else 'off'}")
665class CertParamType(click.ParamType):
666 """Click option type for the ``--cert`` option. Allows either an
667 existing file, the string ``'adhoc'``, or an import for a
668 :class:`~ssl.SSLContext` object.
669 """
671 name = "path"
673 def __init__(self):
674 self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
676 def convert(self, value, param, ctx):
677 try:
678 import ssl
679 except ImportError:
680 raise click.BadParameter(
681 'Using "--cert" requires Python to be compiled with SSL support.',
682 ctx,
683 param,
684 ) from None
686 try:
687 return self.path_type(value, param, ctx)
688 except click.BadParameter:
689 value = click.STRING(value, param, ctx).lower()
691 if value == "adhoc":
692 try:
693 import cryptography # noqa: F401
694 except ImportError:
695 raise click.BadParameter(
696 "Using ad-hoc certificates requires the cryptography library.",
697 ctx,
698 param,
699 ) from None
701 return value
703 obj = import_string(value, silent=True)
705 if isinstance(obj, ssl.SSLContext):
706 return obj
708 raise
711def _validate_key(ctx, param, value):
712 """The ``--key`` option must be specified when ``--cert`` is a file.
713 Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
714 """
715 cert = ctx.params.get("cert")
716 is_adhoc = cert == "adhoc"
718 try:
719 import ssl
720 except ImportError:
721 is_context = False
722 else:
723 is_context = isinstance(cert, ssl.SSLContext)
725 if value is not None:
726 if is_adhoc:
727 raise click.BadParameter(
728 'When "--cert" is "adhoc", "--key" is not used.', ctx, param
729 )
731 if is_context:
732 raise click.BadParameter(
733 'When "--cert" is an SSLContext object, "--key is not used.', ctx, param
734 )
736 if not cert:
737 raise click.BadParameter('"--cert" must also be specified.', ctx, param)
739 ctx.params["cert"] = cert, value
741 else:
742 if cert and not (is_adhoc or is_context):
743 raise click.BadParameter('Required when using "--cert".', ctx, param)
745 return value
748class SeparatedPathType(click.Path):
749 """Click option type that accepts a list of values separated by the
750 OS's path separator (``:``, ``;`` on Windows). Each value is
751 validated as a :class:`click.Path` type.
752 """
754 def convert(self, value, param, ctx):
755 items = self.split_envvar_value(value)
756 super_convert = super().convert
757 return [super_convert(item, param, ctx) for item in items]
760@click.command("run", short_help="Run a development server.")
761@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.")
762@click.option("--port", "-p", default=5000, help="The port to bind to.")
763@click.option(
764 "--cert",
765 type=CertParamType(),
766 help="Specify a certificate file to use HTTPS.",
767 is_eager=True,
768)
769@click.option(
770 "--key",
771 type=click.Path(exists=True, dir_okay=False, resolve_path=True),
772 callback=_validate_key,
773 expose_value=False,
774 help="The key file to use when specifying a certificate.",
775)
776@click.option(
777 "--reload/--no-reload",
778 default=None,
779 help="Enable or disable the reloader. By default the reloader "
780 "is active if debug is enabled.",
781)
782@click.option(
783 "--debugger/--no-debugger",
784 default=None,
785 help="Enable or disable the debugger. By default the debugger "
786 "is active if debug is enabled.",
787)
788@click.option(
789 "--eager-loading/--lazy-loading",
790 default=None,
791 help="Enable or disable eager loading. By default eager "
792 "loading is enabled if the reloader is disabled.",
793)
794@click.option(
795 "--with-threads/--without-threads",
796 default=True,
797 help="Enable or disable multithreading.",
798)
799@click.option(
800 "--extra-files",
801 default=None,
802 type=SeparatedPathType(),
803 help=(
804 "Extra files that trigger a reload on change. Multiple paths"
805 f" are separated by {os.path.pathsep!r}."
806 ),
807)
808@click.option(
809 "--exclude-patterns",
810 default=None,
811 type=SeparatedPathType(),
812 help=(
813 "Files matching these fnmatch patterns will not trigger a reload"
814 " on change. Multiple patterns are separated by"
815 f" {os.path.pathsep!r}."
816 ),
817)
818@pass_script_info
819def run_command(
820 info,
821 host,
822 port,
823 reload,
824 debugger,
825 eager_loading,
826 with_threads,
827 cert,
828 extra_files,
829 exclude_patterns,
830):
831 """Run a local development server.
833 This server is for development purposes only. It does not provide
834 the stability, security, or performance of production WSGI servers.
836 The reloader and debugger are enabled by default if
837 FLASK_ENV=development or FLASK_DEBUG=1.
838 """
839 debug = get_debug_flag()
841 if reload is None:
842 reload = debug
844 if debugger is None:
845 debugger = debug
847 show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
848 app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
850 from werkzeug.serving import run_simple
852 run_simple(
853 host,
854 port,
855 app,
856 use_reloader=reload,
857 use_debugger=debugger,
858 threaded=with_threads,
859 ssl_context=cert,
860 extra_files=extra_files,
861 exclude_patterns=exclude_patterns,
862 )
865@click.command("shell", short_help="Run a shell in the app context.")
866@with_appcontext
867def shell_command() -> None:
868 """Run an interactive Python shell in the context of a given
869 Flask application. The application will populate the default
870 namespace of this shell according to its configuration.
872 This is useful for executing small snippets of management code
873 without having to manually configure the application.
874 """
875 import code
876 from .globals import _app_ctx_stack
878 app = _app_ctx_stack.top.app
879 banner = (
880 f"Python {sys.version} on {sys.platform}\n"
881 f"App: {app.import_name} [{app.env}]\n"
882 f"Instance: {app.instance_path}"
883 )
884 ctx: dict = {}
886 # Support the regular Python interpreter startup script if someone
887 # is using it.
888 startup = os.environ.get("PYTHONSTARTUP")
889 if startup and os.path.isfile(startup):
890 with open(startup) as f:
891 eval(compile(f.read(), startup, "exec"), ctx)
893 ctx.update(app.make_shell_context())
895 # Site, customize, or startup script can set a hook to call when
896 # entering interactive mode. The default one sets up readline with
897 # tab and history completion.
898 interactive_hook = getattr(sys, "__interactivehook__", None)
900 if interactive_hook is not None:
901 try:
902 import readline
903 from rlcompleter import Completer
904 except ImportError:
905 pass
906 else:
907 # rlcompleter uses __main__.__dict__ by default, which is
908 # flask.__main__. Use the shell context instead.
909 readline.set_completer(Completer(ctx).complete)
911 interactive_hook()
913 code.interact(banner=banner, local=ctx)
916@click.command("routes", short_help="Show the routes for the app.")
917@click.option(
918 "--sort",
919 "-s",
920 type=click.Choice(("endpoint", "methods", "rule", "match")),
921 default="endpoint",
922 help=(
923 'Method to sort routes by. "match" is the order that Flask will match '
924 "routes when dispatching a request."
925 ),
926)
927@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.")
928@with_appcontext
929def routes_command(sort: str, all_methods: bool) -> None:
930 """Show all registered routes with endpoints and methods."""
932 rules = list(current_app.url_map.iter_rules())
933 if not rules:
934 click.echo("No routes were registered.")
935 return
937 ignored_methods = set(() if all_methods else ("HEAD", "OPTIONS"))
939 if sort in ("endpoint", "rule"):
940 rules = sorted(rules, key=attrgetter(sort))
941 elif sort == "methods":
942 rules = sorted(rules, key=lambda rule: sorted(rule.methods)) # type: ignore
944 rule_methods = [
945 ", ".join(sorted(rule.methods - ignored_methods)) # type: ignore
946 for rule in rules
947 ]
949 headers = ("Endpoint", "Methods", "Rule")
950 widths = (
951 max(len(rule.endpoint) for rule in rules),
952 max(len(methods) for methods in rule_methods),
953 max(len(rule.rule) for rule in rules),
954 )
955 widths = [max(len(h), w) for h, w in zip(headers, widths)]
956 row = "{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}".format(*widths)
958 click.echo(row.format(*headers).strip())
959 click.echo(row.format(*("-" * width for width in widths)))
961 for rule, methods in zip(rules, rule_methods):
962 click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
965cli = FlaskGroup(
966 help="""\
967A general utility script for Flask applications.
969Provides commands from Flask, extensions, and the application. Loads the
970application defined in the FLASK_APP environment variable, or from a wsgi.py
971file. Setting the FLASK_ENV environment variable to 'development' will enable
972debug mode.
974\b
975 {prefix}{cmd} FLASK_APP=hello.py
976 {prefix}{cmd} FLASK_ENV=development
977 {prefix}flask run
978""".format(
979 cmd="export" if os.name == "posix" else "set",
980 prefix="$ " if os.name == "posix" else "> ",
981 )
982)
985def main() -> None:
986 cli.main()
989if __name__ == "__main__":
990 main()