1 from __future__
import annotations
4 import importlib
.metadata
12 from functools
import update_wrapper
13 from operator
import itemgetter
16 from click
.core
import ParameterSource
17 from werkzeug
import run_simple
18 from werkzeug
.serving
import is_running_from_reloader
19 from werkzeug
.utils
import import_string
21 from .globals import current_app
22 from .helpers
import get_debug_flag
23 from .helpers
import get_load_dotenv
26 from .app
import Flask
29 class NoAppException(click
.UsageError
):
30 """Raised if an application cannot be found or loaded."""
33 def find_best_app(module
):
34 """Given a module instance this tries to find the best possible
35 application in the module or raises an exception.
39 # Search for the most common names first.
40 for attr_name
in ("app", "application"):
41 app
= getattr(module
, attr_name
, None)
43 if isinstance(app
, Flask
):
46 # Otherwise find the only object that is a Flask instance.
47 matches
= [v
for v
in module
.__dict
__.values() if isinstance(v
, Flask
)]
51 elif len(matches
) > 1:
53 "Detected multiple Flask applications in module"
54 f
" '{module.__name__}'. Use '{module.__name__}:name'"
55 " to specify the correct one."
58 # Search for app factory functions.
59 for attr_name
in ("create_app", "make_app"):
60 app_factory
= getattr(module
, attr_name
, None)
62 if inspect
.isfunction(app_factory
):
66 if isinstance(app
, Flask
):
68 except TypeError as e
:
69 if not _called_with_wrong_args(app_factory
):
73 f
"Detected factory '{attr_name}' in module '{module.__name__}',"
74 " but could not call it without arguments. Use"
75 f
" '{module.__name__}:{attr_name}(args)'"
76 " to specify arguments."
80 "Failed to find Flask application or factory in module"
81 f
" '{module.__name__}'. Use '{module.__name__}:name'"
86 def _called_with_wrong_args(f
):
87 """Check whether calling a function raised a ``TypeError`` because
88 the call failed or because something in the factory raised the
91 :param f: The function that was called.
92 :return: ``True`` if the call failed.
94 tb
= sys
.exc_info()[2]
98 if tb
.tb_frame
.f_code
is f
.__code
__:
99 # In the function, it was called successfully.
104 # Didn't reach the function.
107 # Delete tb to break a circular reference.
108 # https://docs.python.org/2/library/sys.html#sys.exc_info
112 def find_app_by_string(module
, app_name
):
113 """Check if the given string is a variable name or a function. Call
114 a function to get the app instance, or return the variable directly.
118 # Parse app_name as a single expression to determine if it's a valid
119 # attribute name or function call.
121 expr
= ast
.parse(app_name
.strip(), mode
="eval").body
123 raise NoAppException(
124 f
"Failed to parse {app_name!r} as an attribute name or function call."
127 if isinstance(expr
, ast
.Name
):
131 elif isinstance(expr
, ast
.Call
):
132 # Ensure the function name is an attribute name only.
133 if not isinstance(expr
.func
, ast
.Name
):
134 raise NoAppException(
135 f
"Function reference must be a simple name: {app_name!r}."
140 # Parse the positional and keyword arguments as literals.
142 args
= [ast
.literal_eval(arg
) for arg
in expr
.args
]
143 kwargs
= {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords}
145 # literal_eval gives cryptic error messages, show a generic
146 # message with the full expression instead.
147 raise NoAppException(
148 f
"Failed to parse arguments as literal values: {app_name!r}."
151 raise NoAppException(
152 f
"Failed to parse {app_name!r} as an attribute name or function call."
156 attr
= getattr(module
, name
)
157 except AttributeError as e
:
158 raise NoAppException(
159 f
"Failed to find attribute {name!r} in {module.__name__!r}."
162 # If the attribute is a function, call it with any args and kwargs
163 # to get the real application.
164 if inspect
.isfunction(attr
):
166 app
= attr(*args
, **kwargs
)
167 except TypeError as e
:
168 if not _called_with_wrong_args(attr
):
171 raise NoAppException(
172 f
"The factory {app_name!r} in module"
173 f
" {module.__name__!r} could not be called with the"
174 " specified arguments."
179 if isinstance(app
, Flask
):
182 raise NoAppException(
183 "A valid Flask application was not obtained from"
184 f
" '{module.__name__}:{app_name}'."
188 def prepare_import(path
):
189 """Given a filename this will try to calculate the python path, add it
190 to the search path and return the actual module name that is expected.
192 path
= os
.path
.realpath(path
)
194 fname
, ext
= os
.path
.splitext(path
)
198 if os
.path
.basename(path
) == "__init__":
199 path
= os
.path
.dirname(path
)
203 # move up until outside package structure (no __init__.py)
205 path
, name
= os
.path
.split(path
)
206 module_name
.append(name
)
208 if not os
.path
.exists(os
.path
.join(path
, "__init__.py")):
211 if sys
.path
[0] != path
:
212 sys
.path
.insert(0, path
)
214 return ".".join(module_name
[::-1])
217 def locate_app(module_name
, app_name
, raise_if_not_found
=True):
219 __import__(module_name
)
221 # Reraise the ImportError if it occurred within the imported module.
222 # Determine this by checking whether the trace has a depth > 1.
223 if sys
.exc_info()[2].tb_next
:
224 raise NoAppException(
225 f
"While importing {module_name!r}, an ImportError was"
226 f
" raised:\n\n{traceback.format_exc()}"
228 elif raise_if_not_found
:
229 raise NoAppException(f
"Could not import {module_name!r}.") from None
233 module
= sys
.modules
[module_name
]
236 return find_best_app(module
)
238 return find_app_by_string(module
, app_name
)
241 def get_version(ctx
, param
, value
):
242 if not value
or ctx
.resilient_parsing
:
245 flask_version
= importlib
.metadata
.version("flask")
246 werkzeug_version
= importlib
.metadata
.version("werkzeug")
249 f
"Python {platform.python_version()}\n"
250 f
"Flask {flask_version}\n"
251 f
"Werkzeug {werkzeug_version}",
257 version_option
= click
.Option(
259 help="Show the Flask version.",
261 callback
=get_version
,
268 """Helper object to deal with Flask applications. This is usually not
269 necessary to interface with as it's used internally in the dispatching
270 to click. In future versions of Flask this object will most likely play
271 a bigger role. Typically it's created automatically by the
272 :class:`FlaskGroup` but you can also manually create it and pass it
273 onwards as click object.
278 app_import_path
: str |
None = None,
279 create_app
: t
.Callable
[..., Flask
] |
None = None,
280 set_debug_flag
: bool = True,
282 #: Optionally the import path for the Flask application.
283 self
.app_import_path
= app_import_path
284 #: Optionally a function that is passed the script info to create
285 #: the instance of the application.
286 self
.create_app
= create_app
287 #: A dictionary with arbitrary data that can be associated with
289 self
.data
: dict[t
.Any
, t
.Any
] = {}
290 self
.set_debug_flag
= set_debug_flag
291 self
._loaded
_app
: Flask |
None = None
293 def load_app(self
) -> Flask
:
294 """Loads the Flask app (if not yet loaded) and returns it. Calling
295 this multiple times will just result in the already loaded app to
298 if self
._loaded
_app
is not None:
299 return self
._loaded
_app
301 if self
.create_app
is not None:
302 app
= self
.create_app()
304 if self
.app_import_path
:
306 re
.split(r
":(?![\\/])", self
.app_import_path
, maxsplit
=1) + [None]
308 import_name
= prepare_import(path
)
309 app
= locate_app(import_name
, name
)
311 for path
in ("wsgi.py", "app.py"):
312 import_name
= prepare_import(path
)
313 app
= locate_app(import_name
, None, raise_if_not_found
=False)
319 raise NoAppException(
320 "Could not locate a Flask application. Use the"
321 " 'flask --app' option, 'FLASK_APP' environment"
322 " variable, or a 'wsgi.py' or 'app.py' file in the"
323 " current directory."
326 if self
.set_debug_flag
:
327 # Update the app's debug flag through the descriptor so that
328 # other values repopulate as well.
329 app
.debug
= get_debug_flag()
331 self
._loaded
_app
= app
335 pass_script_info
= click
.make_pass_decorator(ScriptInfo
, ensure
=True)
338 def with_appcontext(f
):
339 """Wraps a callback so that it's guaranteed to be executed with the
340 script's application context.
342 Custom commands (and their options) registered under ``app.cli`` or
343 ``blueprint.cli`` will always have an app context available, this
344 decorator is not required in that case.
346 .. versionchanged:: 2.2
347 The app context is active for subcommands as well as the
348 decorated callback. The app context is always available to
349 ``app.cli`` command and parameter callbacks.
353 def decorator(__ctx
, *args
, **kwargs
):
355 app
= __ctx
.ensure_object(ScriptInfo
).load_app()
356 __ctx
.with_resource(app
.app_context())
358 return __ctx
.invoke(f
, *args
, **kwargs
)
360 return update_wrapper(decorator
, f
)
363 class AppGroup(click
.Group
):
364 """This works similar to a regular click :class:`~click.Group` but it
365 changes the behavior of the :meth:`command` decorator so that it
366 automatically wraps the functions in :func:`with_appcontext`.
368 Not to be confused with :class:`FlaskGroup`.
371 def command(self
, *args
, **kwargs
):
372 """This works exactly like the method of the same name on a regular
373 :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
374 unless it's disabled by passing ``with_appcontext=False``.
376 wrap_for_ctx
= kwargs
.pop("with_appcontext", True)
380 f
= with_appcontext(f
)
381 return click
.Group
.command(self
, *args
, **kwargs
)(f
)
385 def group(self
, *args
, **kwargs
):
386 """This works exactly like the method of the same name on a regular
387 :class:`click.Group` but it defaults the group class to
390 kwargs
.setdefault("cls", AppGroup
)
391 return click
.Group
.group(self
, *args
, **kwargs
)
394 def _set_app(ctx
: click
.Context
, param
: click
.Option
, value
: str |
None) -> str |
None:
398 info
= ctx
.ensure_object(ScriptInfo
)
399 info
.app_import_path
= value
403 # This option is eager so the app will be available if --help is given.
404 # --help is also eager, so --app must be before it in the param list.
405 # no_args_is_help bypasses eager processing, so this option must be
406 # processed manually in that case to ensure FLASK_APP gets picked up.
407 _app_option
= click
.Option(
411 "The Flask application or factory function to load, in the form 'module:name'."
412 " Module can be a dotted import or file path. Name is not required if it is"
413 " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"
422 def _set_debug(ctx
: click
.Context
, param
: click
.Option
, value
: bool) -> bool |
None:
423 # If the flag isn't provided, it will default to False. Don't use
424 # that, let debug be set by env in that case.
425 source
= ctx
.get_parameter_source(param
.name
) # type: ignore[arg-type]
427 if source
is not None and source
in (
428 ParameterSource
.DEFAULT
,
429 ParameterSource
.DEFAULT_MAP
,
433 # Set with env var instead of ScriptInfo.load so that it can be
434 # accessed early during a factory function.
435 os
.environ
["FLASK_DEBUG"] = "1" if value
else "0"
439 _debug_option
= click
.Option(
440 ["--debug/--no-debug"],
441 help="Set debug mode.",
447 def _env_file_callback(
448 ctx
: click
.Context
, param
: click
.Option
, value
: str |
None
456 importlib
.import_module("dotenv")
458 raise click
.BadParameter(
459 "python-dotenv must be installed to load an env file.",
464 # Don't check FLASK_SKIP_DOTENV, that only disables automatically
465 # loading .env and .flaskenv files.
470 # This option is eager so env vars are loaded as early as possible to be
471 # used by other options.
472 _env_file_option
= click
.Option(
473 ["-e", "--env-file"],
474 type=click
.Path(exists
=True, dir_okay
=False),
475 help="Load environment variables from this file. python-dotenv must be installed.",
478 callback
=_env_file_callback
,
482 class FlaskGroup(AppGroup
):
483 """Special subclass of the :class:`AppGroup` group that supports
484 loading more commands from the configured Flask app. Normally a
485 developer does not have to interface with this class but there are
486 some very advanced use cases for which it makes sense to create an
487 instance of this. see :ref:`custom-scripts`.
489 :param add_default_commands: if this is True then the default run and
490 shell commands will be added.
491 :param add_version_option: adds the ``--version`` option.
492 :param create_app: an optional callback that is passed the script info and
493 returns the loaded app.
494 :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
495 files to set environment variables. Will also change the working
496 directory to the directory containing the first file found.
497 :param set_debug_flag: Set the app's debug flag.
499 .. versionchanged:: 2.2
500 Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
502 .. versionchanged:: 2.2
503 An app context is pushed when running ``app.cli`` commands, so
504 ``@with_appcontext`` is no longer required for those commands.
506 .. versionchanged:: 1.0
507 If installed, python-dotenv will be used to load environment variables
508 from :file:`.env` and :file:`.flaskenv` files.
513 add_default_commands
: bool = True,
514 create_app
: t
.Callable
[..., Flask
] |
None = None,
515 add_version_option
: bool = True,
516 load_dotenv
: bool = True,
517 set_debug_flag
: bool = True,
520 params
= list(extra
.pop("params", None) or ())
521 # Processing is done with option callbacks instead of a group
522 # callback. This allows users to make a custom group callback
523 # without losing the behavior. --env-file must come first so
524 # that it is eagerly evaluated before --app.
525 params
.extend((_env_file_option
, _app_option
, _debug_option
))
527 if add_version_option
:
528 params
.append(version_option
)
530 if "context_settings" not in extra
:
531 extra
["context_settings"] = {}
533 extra
["context_settings"].setdefault("auto_envvar_prefix", "FLASK")
535 super().__init
__(params
=params
, **extra
)
537 self
.create_app
= create_app
538 self
.load_dotenv
= load_dotenv
539 self
.set_debug_flag
= set_debug_flag
541 if add_default_commands
:
542 self
.add_command(run_command
)
543 self
.add_command(shell_command
)
544 self
.add_command(routes_command
)
546 self
._loaded
_plugin
_commands
= False
548 def _load_plugin_commands(self
):
549 if self
._loaded
_plugin
_commands
:
552 if sys
.version_info
>= (3, 10):
553 from importlib
import metadata
555 # Use a backport on Python < 3.10. We technically have
556 # importlib.metadata on 3.8+, but the API changed in 3.10,
557 # so use the backport for consistency.
558 import importlib_metadata
as metadata
560 for ep
in metadata
.entry_points(group
="flask.commands"):
561 self
.add_command(ep
.load(), ep
.name
)
563 self
._loaded
_plugin
_commands
= True
565 def get_command(self
, ctx
, name
):
566 self
._load
_plugin
_commands
()
567 # Look up built-in and plugin commands, which should be
568 # available even if the app fails to load.
569 rv
= super().get_command(ctx
, name
)
574 info
= ctx
.ensure_object(ScriptInfo
)
576 # Look up commands provided by the app, showing an error and
577 # continuing if the app couldn't be loaded.
579 app
= info
.load_app()
580 except NoAppException
as e
:
581 click
.secho(f
"Error: {e.format_message()}\n", err
=True, fg
="red")
584 # Push an app context for the loaded app unless it is already
585 # active somehow. This makes the context available to parameter
586 # and command callbacks without needing @with_appcontext.
587 if not current_app
or current_app
._get
_current
_object
() is not app
:
588 ctx
.with_resource(app
.app_context())
590 return app
.cli
.get_command(ctx
, name
)
592 def list_commands(self
, ctx
):
593 self
._load
_plugin
_commands
()
594 # Start with the built-in and plugin commands.
595 rv
= set(super().list_commands(ctx
))
596 info
= ctx
.ensure_object(ScriptInfo
)
598 # Add commands provided by the app, showing an error and
599 # continuing if the app couldn't be loaded.
601 rv
.update(info
.load_app().cli
.list_commands(ctx
))
602 except NoAppException
as e
:
603 # When an app couldn't be loaded, show the error message
604 # without the traceback.
605 click
.secho(f
"Error: {e.format_message()}\n", err
=True, fg
="red")
607 # When any other errors occurred during loading, show the
609 click
.secho(f
"{traceback.format_exc()}\n", err
=True, fg
="red")
615 info_name
: str |
None,
617 parent
: click
.Context |
None = None,
620 # Set a flag to tell app.run to become a no-op. If app.run was
621 # not in a __name__ == __main__ guard, it would start the server
622 # when importing, blocking whatever command is being called.
623 os
.environ
["FLASK_RUN_FROM_CLI"] = "true"
625 # Attempt to load .env and .flask env files. The --env-file
626 # option can cause another file to be loaded.
627 if get_load_dotenv(self
.load_dotenv
):
630 if "obj" not in extra
and "obj" not in self
.context_settings
:
631 extra
["obj"] = ScriptInfo(
632 create_app
=self
.create_app
, set_debug_flag
=self
.set_debug_flag
635 return super().make_context(info_name
, args
, parent
=parent
, **extra
)
637 def parse_args(self
, ctx
: click
.Context
, args
: list[str]) -> list[str]:
638 if not args
and self
.no_args_is_help
:
639 # Attempt to load --env-file and --app early in case they
640 # were given as env vars. Otherwise no_args_is_help will not
641 # see commands from app.cli.
642 _env_file_option
.handle_parse_result(ctx
, {}, [])
643 _app_option
.handle_parse_result(ctx
, {}, [])
645 return super().parse_args(ctx
, args
)
648 def _path_is_ancestor(path
, other
):
649 """Take ``other`` and remove the length of ``path`` from it. Then join it
650 to ``path``. If it is the original value, ``path`` is an ancestor of
652 return os
.path
.join(path
, other
[len(path
) :].lstrip(os
.sep
)) == other
655 def load_dotenv(path
: str | os
.PathLike |
None = None) -> bool:
656 """Load "dotenv" files in order of precedence to set environment variables.
658 If an env var is already set it is not overwritten, so earlier files in the
659 list are preferred over later files.
661 This is a no-op if `python-dotenv`_ is not installed.
663 .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
665 :param path: Load the file at this location instead of searching.
666 :return: ``True`` if a file was loaded.
668 .. versionchanged:: 2.0
669 The current directory is not changed to the location of the
672 .. versionchanged:: 2.0
673 When loading the env files, set the default encoding to UTF-8.
675 .. versionchanged:: 1.1.0
676 Returns ``False`` when python-dotenv is not installed, or when
677 the given path isn't a file.
679 .. versionadded:: 1.0
684 if path
or os
.path
.isfile(".env") or os
.path
.isfile(".flaskenv"):
686 " * Tip: There are .env or .flaskenv files present."
687 ' Do "pip install python-dotenv" to use them.',
694 # Always return after attempting to load a given path, don't load
697 if os
.path
.isfile(path
):
698 return dotenv
.load_dotenv(path
, encoding
="utf-8")
704 for name
in (".env", ".flaskenv"):
705 path
= dotenv
.find_dotenv(name
, usecwd
=True)
710 dotenv
.load_dotenv(path
, encoding
="utf-8")
713 return loaded
# True if at least one file was located and loaded.
716 def show_server_banner(debug
, app_import_path
):
717 """Show extra startup messages the first time the server is run,
718 ignoring the reloader.
720 if is_running_from_reloader():
723 if app_import_path
is not None:
724 click
.echo(f
" * Serving Flask app '{app_import_path}'")
726 if debug
is not None:
727 click
.echo(f
" * Debug mode: {'on' if debug else 'off'}")
730 class CertParamType(click
.ParamType
):
731 """Click option type for the ``--cert`` option. Allows either an
732 existing file, the string ``'adhoc'``, or an import for a
733 :class:`~ssl.SSLContext` object.
739 self
.path_type
= click
.Path(exists
=True, dir_okay
=False, resolve_path
=True)
741 def convert(self
, value
, param
, ctx
):
745 raise click
.BadParameter(
746 'Using "--cert" requires Python to be compiled with SSL support.',
752 return self
.path_type(value
, param
, ctx
)
753 except click
.BadParameter
:
754 value
= click
.STRING(value
, param
, ctx
).lower()
758 import cryptography
# noqa: F401
760 raise click
.BadParameter(
761 "Using ad-hoc certificates requires the cryptography library.",
768 obj
= import_string(value
, silent
=True)
770 if isinstance(obj
, ssl
.SSLContext
):
776 def _validate_key(ctx
, param
, value
):
777 """The ``--key`` option must be specified when ``--cert`` is a file.
778 Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
780 cert
= ctx
.params
.get("cert")
781 is_adhoc
= cert
== "adhoc"
788 is_context
= isinstance(cert
, ssl
.SSLContext
)
790 if value
is not None:
792 raise click
.BadParameter(
793 'When "--cert" is "adhoc", "--key" is not used.', ctx
, param
797 raise click
.BadParameter(
798 'When "--cert" is an SSLContext object, "--key is not used.', ctx
, param
802 raise click
.BadParameter('"--cert" must also be specified.', ctx
, param
)
804 ctx
.params
["cert"] = cert
, value
807 if cert
and not (is_adhoc
or is_context
):
808 raise click
.BadParameter('Required when using "--cert".', ctx
, param
)
813 class SeparatedPathType(click
.Path
):
814 """Click option type that accepts a list of values separated by the
815 OS's path separator (``:``, ``;`` on Windows). Each value is
816 validated as a :class:`click.Path` type.
819 def convert(self
, value
, param
, ctx
):
820 items
= self
.split_envvar_value(value
)
821 super_convert
= super().convert
822 return [super_convert(item
, param
, ctx
) for item
in items
]
825 @click.command("run", short_help
="Run a development server.")
826 @click.option("--host", "-h", default
="127.0.0.1", help="The interface to bind to.")
827 @click.option("--port", "-p", default
=5000, help="The port to bind to.")
830 type=CertParamType(),
831 help="Specify a certificate file to use HTTPS.",
836 type=click
.Path(exists
=True, dir_okay
=False, resolve_path
=True),
837 callback
=_validate_key
,
839 help="The key file to use when specifying a certificate.",
842 "--reload/--no-reload",
844 help="Enable or disable the reloader. By default the reloader "
845 "is active if debug is enabled.",
848 "--debugger/--no-debugger",
850 help="Enable or disable the debugger. By default the debugger "
851 "is active if debug is enabled.",
854 "--with-threads/--without-threads",
856 help="Enable or disable multithreading.",
861 type=SeparatedPathType(),
863 "Extra files that trigger a reload on change. Multiple paths"
864 f
" are separated by {os.path.pathsep!r}."
868 "--exclude-patterns",
870 type=SeparatedPathType(),
872 "Files matching these fnmatch patterns will not trigger a reload"
873 " on change. Multiple patterns are separated by"
874 f
" {os.path.pathsep!r}."
889 """Run a local development server.
891 This server is for development purposes only. It does not provide
892 the stability, security, or performance of production WSGI servers.
894 The reloader and debugger are enabled by default with the '--debug'
898 app
= info
.load_app()
899 except Exception as e
:
900 if is_running_from_reloader():
901 # When reloading, print out the error immediately, but raise
902 # it later so the debugger or server can handle it.
903 traceback
.print_exc()
906 def app(environ
, start_response
):
910 # When not reloading, raise the error immediately so the
914 debug
= get_debug_flag()
922 show_server_banner(debug
, info
.app_import_path
)
929 use_debugger
=debugger
,
930 threaded
=with_threads
,
932 extra_files
=extra_files
,
933 exclude_patterns
=exclude_patterns
,
937 run_command
.params
.insert(0, _debug_option
)
940 @click.command("shell", short_help
="Run a shell in the app context.")
942 def shell_command() -> None:
943 """Run an interactive Python shell in the context of a given
944 Flask application. The application will populate the default
945 namespace of this shell according to its configuration.
947 This is useful for executing small snippets of management code
948 without having to manually configure the application.
953 f
"Python {sys.version} on {sys.platform}\n"
954 f
"App: {current_app.import_name}\n"
955 f
"Instance: {current_app.instance_path}"
959 # Support the regular Python interpreter startup script if someone
961 startup
= os
.environ
.get("PYTHONSTARTUP")
962 if startup
and os
.path
.isfile(startup
):
963 with open(startup
) as f
:
964 eval(compile(f
.read(), startup
, "exec"), ctx
)
966 ctx
.update(current_app
.make_shell_context())
968 # Site, customize, or startup script can set a hook to call when
969 # entering interactive mode. The default one sets up readline with
970 # tab and history completion.
971 interactive_hook
= getattr(sys
, "__interactivehook__", None)
973 if interactive_hook
is not None:
976 from rlcompleter
import Completer
980 # rlcompleter uses __main__.__dict__ by default, which is
981 # flask.__main__. Use the shell context instead.
982 readline
.set_completer(Completer(ctx
).complete
)
986 code
.interact(banner
=banner
, local
=ctx
)
989 @click.command("routes", short_help
="Show the routes for the app.")
993 type=click
.Choice(("endpoint", "methods", "domain", "rule", "match")),
996 "Method to sort routes by. 'match' is the order that Flask will match routes"
997 " when dispatching a request."
1000 @click.option("--all-methods", is_flag
=True, help="Show HEAD and OPTIONS methods.")
1002 def routes_command(sort
: str, all_methods
: bool) -> None:
1003 """Show all registered routes with endpoints and methods."""
1004 rules
= list(current_app
.url_map
.iter_rules())
1007 click
.echo("No routes were registered.")
1010 ignored_methods
= set() if all_methods
else {"HEAD", "OPTIONS"}
1011 host_matching
= current_app
.url_map
.host_matching
1012 has_domain
= any(rule
.host
if host_matching
else rule
.subdomain
for rule
in rules
)
1018 ", ".join(sorted((rule
.methods
or set()) - ignored_methods
)),
1022 row
.append((rule
.host
if host_matching
else rule
.subdomain
) or "")
1024 row
.append(rule
.rule
)
1027 headers
= ["Endpoint", "Methods"]
1028 sorts
= ["endpoint", "methods"]
1031 headers
.append("Host" if host_matching
else "Subdomain")
1032 sorts
.append("domain")
1034 headers
.append("Rule")
1035 sorts
.append("rule")
1038 rows
.sort(key
=itemgetter(sorts
.index(sort
)))
1042 rows
.insert(0, headers
)
1043 widths
= [max(len(row
[i
]) for row
in rows
) for i
in range(len(headers
))]
1044 rows
.insert(1, ["-" * w
for w
in widths
])
1045 template
= " ".join(f
"{{{i}:<{w}}}" for i
, w
in enumerate(widths
))
1048 click
.echo(template
.format(*row
))
1054 A general utility script for Flask applications.
1056 An application to load must be given with the '--app' option,
1057 'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file
1058 in the current directory.
1067 if __name__
== "__main__":