1import ast 2import inspect 3import os 4import platform 5import re 6import sys 7import traceback 8import warnings 9from functools import update_wrapper 10from operator import attrgetter 11from threading import Lock 12from threading import Thread 13 14import click 15from werkzeug.utils import import_string 16 17from .globals import current_app 18from .helpers import get_debug_flag 19from .helpers import get_env 20from .helpers import get_load_dotenv 21 22try: 23 import dotenv 24except ImportError: 25 dotenv = None 26 27try: 28 import ssl 29except ImportError: 30 ssl = None # type: ignore 31 32 33class NoAppException(click.UsageError): 34 """Raised if an application cannot be found or loaded.""" 35 36 37def find_best_app(script_info, module): 38 """Given a module instance this tries to find the best possible 39 application in the module or raises an exception. 40 """ 41 from . import Flask 42 43 # Search for the most common names first. 44 for attr_name in ("app", "application"): 45 app = getattr(module, attr_name, None) 46 47 if isinstance(app, Flask): 48 return app 49 50 # Otherwise find the only object that is a Flask instance. 51 matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] 52 53 if len(matches) == 1: 54 return matches[0] 55 elif len(matches) > 1: 56 raise NoAppException( 57 "Detected multiple Flask applications in module" 58 f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" 59 f" to specify the correct one." 60 ) 61 62 # Search for app factory functions. 63 for attr_name in ("create_app", "make_app"): 64 app_factory = getattr(module, attr_name, None) 65 66 if inspect.isfunction(app_factory): 67 try: 68 app = call_factory(script_info, app_factory) 69 70 if isinstance(app, Flask): 71 return app 72 except TypeError as e: 73 if not _called_with_wrong_args(app_factory): 74 raise 75 76 raise NoAppException( 77 f"Detected factory {attr_name!r} in module {module.__name__!r}," 78 " but could not call it without arguments. Use" 79 f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\"" 80 " to specify arguments." 81 ) from e 82 83 raise NoAppException( 84 "Failed to find Flask application or factory in module" 85 f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" 86 " to specify one." 87 ) 88 89 90def call_factory(script_info, app_factory, args=None, kwargs=None): 91 """Takes an app factory, a ``script_info` object and optionally a tuple 92 of arguments. Checks for the existence of a script_info argument and calls 93 the app_factory depending on that and the arguments provided. 94 """ 95 sig = inspect.signature(app_factory) 96 args = [] if args is None else args 97 kwargs = {} if kwargs is None else kwargs 98 99 if "script_info" in sig.parameters: 100 warnings.warn( 101 "The 'script_info' argument is deprecated and will not be" 102 " passed to the app factory function in Flask 2.1.", 103 DeprecationWarning, 104 ) 105 kwargs["script_info"] = script_info 106 107 if not args and len(sig.parameters) == 1: 108 first_parameter = next(iter(sig.parameters.values())) 109 110 if ( 111 first_parameter.default is inspect.Parameter.empty 112 # **kwargs is reported as an empty default, ignore it 113 and first_parameter.kind is not inspect.Parameter.VAR_KEYWORD 114 ): 115 warnings.warn( 116 "Script info is deprecated and will not be passed as the" 117 " single argument to the app factory function in Flask" 118 " 2.1.", 119 DeprecationWarning, 120 ) 121 args.append(script_info) 122 123 return app_factory(*args, **kwargs) 124 125 126def _called_with_wrong_args(f): 127 """Check whether calling a function raised a ``TypeError`` because 128 the call failed or because something in the factory raised the 129 error. 130 131 :param f: The function that was called. 132 :return: ``True`` if the call failed. 133 """ 134 tb = sys.exc_info()[2] 135 136 try: 137 while tb is not None: 138 if tb.tb_frame.f_code is f.__code__: 139 # In the function, it was called successfully. 140 return False 141 142 tb = tb.tb_next 143 144 # Didn't reach the function. 145 return True 146 finally: 147 # Delete tb to break a circular reference. 148 # https://docs.python.org/2/library/sys.html#sys.exc_info 149 del tb 150 151 152def find_app_by_string(script_info, module, app_name): 153 """Check if the given string is a variable name or a function. Call 154 a function to get the app instance, or return the variable directly. 155 """ 156 from . import Flask 157 158 # Parse app_name as a single expression to determine if it's a valid 159 # attribute name or function call. 160 try: 161 expr = ast.parse(app_name.strip(), mode="eval").body 162 except SyntaxError: 163 raise NoAppException( 164 f"Failed to parse {app_name!r} as an attribute name or function call." 165 ) from None 166 167 if isinstance(expr, ast.Name): 168 name = expr.id 169 args = kwargs = None 170 elif isinstance(expr, ast.Call): 171 # Ensure the function name is an attribute name only. 172 if not isinstance(expr.func, ast.Name): 173 raise NoAppException( 174 f"Function reference must be a simple name: {app_name!r}." 175 ) 176 177 name = expr.func.id 178 179 # Parse the positional and keyword arguments as literals. 180 try: 181 args = [ast.literal_eval(arg) for arg in expr.args] 182 kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords} 183 except ValueError: 184 # literal_eval gives cryptic error messages, show a generic 185 # message with the full expression instead. 186 raise NoAppException( 187 f"Failed to parse arguments as literal values: {app_name!r}." 188 ) from None 189 else: 190 raise NoAppException( 191 f"Failed to parse {app_name!r} as an attribute name or function call." 192 ) 193 194 try: 195 attr = getattr(module, name) 196 except AttributeError as e: 197 raise NoAppException( 198 f"Failed to find attribute {name!r} in {module.__name__!r}." 199 ) from e 200 201 # If the attribute is a function, call it with any args and kwargs 202 # to get the real application. 203 if inspect.isfunction(attr): 204 try: 205 app = call_factory(script_info, attr, args, kwargs) 206 except TypeError as e: 207 if not _called_with_wrong_args(attr): 208 raise 209 210 raise NoAppException( 211 f"The factory {app_name!r} in module" 212 f" {module.__name__!r} could not be called with the" 213 " specified arguments." 214 ) from e 215 else: 216 app = attr 217 218 if isinstance(app, Flask): 219 return app 220 221 raise NoAppException( 222 "A valid Flask application was not obtained from" 223 f" '{module.__name__}:{app_name}'." 224 ) 225 226 227def prepare_import(path): 228 """Given a filename this will try to calculate the python path, add it 229 to the search path and return the actual module name that is expected. 230 """ 231 path = os.path.realpath(path) 232 233 fname, ext = os.path.splitext(path) 234 if ext == ".py": 235 path = fname 236 237 if os.path.basename(path) == "__init__": 238 path = os.path.dirname(path) 239 240 module_name = [] 241 242 # move up until outside package structure (no __init__.py) 243 while True: 244 path, name = os.path.split(path) 245 module_name.append(name) 246 247 if not os.path.exists(os.path.join(path, "__init__.py")): 248 break 249 250 if sys.path[0] != path: 251 sys.path.insert(0, path) 252 253 return ".".join(module_name[::-1]) 254 255 256def locate_app(script_info, module_name, app_name, raise_if_not_found=True): 257 __traceback_hide__ = True # noqa: F841 258 259 try: 260 __import__(module_name) 261 except ImportError as e: 262 # Reraise the ImportError if it occurred within the imported module. 263 # Determine this by checking whether the trace has a depth > 1. 264 if sys.exc_info()[2].tb_next: 265 raise NoAppException( 266 f"While importing {module_name!r}, an ImportError was raised." 267 ) from e 268 elif raise_if_not_found: 269 raise NoAppException(f"Could not import {module_name!r}.") from e 270 else: 271 return 272 273 module = sys.modules[module_name] 274 275 if app_name is None: 276 return find_best_app(script_info, module) 277 else: 278 return find_app_by_string(script_info, module, app_name) 279 280 281def get_version(ctx, param, value): 282 if not value or ctx.resilient_parsing: 283 return 284 285 import werkzeug 286 from . import __version__ 287 288 click.echo( 289 f"Python {platform.python_version()}\n" 290 f"Flask {__version__}\n" 291 f"Werkzeug {werkzeug.__version__}", 292 color=ctx.color, 293 ) 294 ctx.exit() 295 296 297version_option = click.Option( 298 ["--version"], 299 help="Show the flask version", 300 expose_value=False, 301 callback=get_version, 302 is_flag=True, 303 is_eager=True, 304) 305 306 307class DispatchingApp: 308 """Special application that dispatches to a Flask application which 309 is imported by name in a background thread. If an error happens 310 it is recorded and shown as part of the WSGI handling which in case 311 of the Werkzeug debugger means that it shows up in the browser. 312 """ 313 314 def __init__(self, loader, use_eager_loading=None): 315 self.loader = loader 316 self._app = None 317 self._lock = Lock() 318 self._bg_loading_exc = None 319 320 if use_eager_loading is None: 321 use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true" 322 323 if use_eager_loading: 324 self._load_unlocked() 325 else: 326 self._load_in_background() 327 328 def _load_in_background(self): 329 def _load_app(): 330 __traceback_hide__ = True # noqa: F841 331 with self._lock: 332 try: 333 self._load_unlocked() 334 except Exception as e: 335 self._bg_loading_exc = e 336 337 t = Thread(target=_load_app, args=()) 338 t.start() 339 340 def _flush_bg_loading_exception(self): 341 __traceback_hide__ = True # noqa: F841 342 exc = self._bg_loading_exc 343 344 if exc is not None: 345 self._bg_loading_exc = None 346 raise exc 347 348 def _load_unlocked(self): 349 __traceback_hide__ = True # noqa: F841 350 self._app = rv = self.loader() 351 self._bg_loading_exc = None 352 return rv 353 354 def __call__(self, environ, start_response): 355 __traceback_hide__ = True # noqa: F841 356 if self._app is not None: 357 return self._app(environ, start_response) 358 self._flush_bg_loading_exception() 359 with self._lock: 360 if self._app is not None: 361 rv = self._app 362 else: 363 rv = self._load_unlocked() 364 return rv(environ, start_response) 365 366 367class ScriptInfo: 368 """Helper object to deal with Flask applications. This is usually not 369 necessary to interface with as it's used internally in the dispatching 370 to click. In future versions of Flask this object will most likely play 371 a bigger role. Typically it's created automatically by the 372 :class:`FlaskGroup` but you can also manually create it and pass it 373 onwards as click object. 374 """ 375 376 def __init__(self, app_import_path=None, create_app=None, set_debug_flag=True): 377 #: Optionally the import path for the Flask application. 378 self.app_import_path = app_import_path or os.environ.get("FLASK_APP") 379 #: Optionally a function that is passed the script info to create 380 #: the instance of the application. 381 self.create_app = create_app 382 #: A dictionary with arbitrary data that can be associated with 383 #: this script info. 384 self.data = {} 385 self.set_debug_flag = set_debug_flag 386 self._loaded_app = None 387 388 def load_app(self): 389 """Loads the Flask app (if not yet loaded) and returns it. Calling 390 this multiple times will just result in the already loaded app to 391 be returned. 392 """ 393 __traceback_hide__ = True # noqa: F841 394 395 if self._loaded_app is not None: 396 return self._loaded_app 397 398 if self.create_app is not None: 399 app = call_factory(self, self.create_app) 400 else: 401 if self.app_import_path: 402 path, name = ( 403 re.split(r":(?![\\/])", self.app_import_path, 1) + [None] 404 )[:2] 405 import_name = prepare_import(path) 406 app = locate_app(self, import_name, name) 407 else: 408 for path in ("wsgi.py", "app.py"): 409 import_name = prepare_import(path) 410 app = locate_app(self, import_name, None, raise_if_not_found=False) 411 412 if app: 413 break 414 415 if not app: 416 raise NoAppException( 417 "Could not locate a Flask application. You did not provide " 418 'the "FLASK_APP" environment variable, and a "wsgi.py" or ' 419 '"app.py" module was not found in the current directory.' 420 ) 421 422 if self.set_debug_flag: 423 # Update the app's debug flag through the descriptor so that 424 # other values repopulate as well. 425 app.debug = get_debug_flag() 426 427 self._loaded_app = app 428 return app 429 430 431pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True) 432 433 434def with_appcontext(f): 435 """Wraps a callback so that it's guaranteed to be executed with the 436 script's application context. If callbacks are registered directly 437 to the ``app.cli`` object then they are wrapped with this function 438 by default unless it's disabled. 439 """ 440 441 @click.pass_context 442 def decorator(__ctx, *args, **kwargs): 443 with __ctx.ensure_object(ScriptInfo).load_app().app_context(): 444 return __ctx.invoke(f, *args, **kwargs) 445 446 return update_wrapper(decorator, f) 447 448 449class AppGroup(click.Group): 450 """This works similar to a regular click :class:`~click.Group` but it 451 changes the behavior of the :meth:`command` decorator so that it 452 automatically wraps the functions in :func:`with_appcontext`. 453 454 Not to be confused with :class:`FlaskGroup`. 455 """ 456 457 def command(self, *args, **kwargs): 458 """This works exactly like the method of the same name on a regular 459 :class:`click.Group` but it wraps callbacks in :func:`with_appcontext` 460 unless it's disabled by passing ``with_appcontext=False``. 461 """ 462 wrap_for_ctx = kwargs.pop("with_appcontext", True) 463 464 def decorator(f): 465 if wrap_for_ctx: 466 f = with_appcontext(f) 467 return click.Group.command(self, *args, **kwargs)(f) 468 469 return decorator 470 471 def group(self, *args, **kwargs): 472 """This works exactly like the method of the same name on a regular 473 :class:`click.Group` but it defaults the group class to 474 :class:`AppGroup`. 475 """ 476 kwargs.setdefault("cls", AppGroup) 477 return click.Group.group(self, *args, **kwargs) 478 479 480class FlaskGroup(AppGroup): 481 """Special subclass of the :class:`AppGroup` group that supports 482 loading more commands from the configured Flask app. Normally a 483 developer does not have to interface with this class but there are 484 some very advanced use cases for which it makes sense to create an 485 instance of this. see :ref:`custom-scripts`. 486 487 :param add_default_commands: if this is True then the default run and 488 shell commands will be added. 489 :param add_version_option: adds the ``--version`` option. 490 :param create_app: an optional callback that is passed the script info and 491 returns the loaded app. 492 :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` 493 files to set environment variables. Will also change the working 494 directory to the directory containing the first file found. 495 :param set_debug_flag: Set the app's debug flag based on the active 496 environment 497 498 .. versionchanged:: 1.0 499 If installed, python-dotenv will be used to load environment variables 500 from :file:`.env` and :file:`.flaskenv` files. 501 """ 502 503 def __init__( 504 self, 505 add_default_commands=True, 506 create_app=None, 507 add_version_option=True, 508 load_dotenv=True, 509 set_debug_flag=True, 510 **extra, 511 ): 512 params = list(extra.pop("params", None) or ()) 513 514 if add_version_option: 515 params.append(version_option) 516 517 AppGroup.__init__(self, params=params, **extra) 518 self.create_app = create_app 519 self.load_dotenv = load_dotenv 520 self.set_debug_flag = set_debug_flag 521 522 if add_default_commands: 523 self.add_command(run_command) 524 self.add_command(shell_command) 525 self.add_command(routes_command) 526 527 self._loaded_plugin_commands = False 528 529 def _load_plugin_commands(self): 530 if self._loaded_plugin_commands: 531 return 532 try: 533 import pkg_resources 534 except ImportError: 535 self._loaded_plugin_commands = True 536 return 537 538 for ep in pkg_resources.iter_entry_points("flask.commands"): 539 self.add_command(ep.load(), ep.name) 540 self._loaded_plugin_commands = True 541 542 def get_command(self, ctx, name): 543 self._load_plugin_commands() 544 # Look up built-in and plugin commands, which should be 545 # available even if the app fails to load. 546 rv = super().get_command(ctx, name) 547 548 if rv is not None: 549 return rv 550 551 info = ctx.ensure_object(ScriptInfo) 552 553 # Look up commands provided by the app, showing an error and 554 # continuing if the app couldn't be loaded. 555 try: 556 return info.load_app().cli.get_command(ctx, name) 557 except NoAppException as e: 558 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") 559 560 def list_commands(self, ctx): 561 self._load_plugin_commands() 562 # Start with the built-in and plugin commands. 563 rv = set(super().list_commands(ctx)) 564 info = ctx.ensure_object(ScriptInfo) 565 566 # Add commands provided by the app, showing an error and 567 # continuing if the app couldn't be loaded. 568 try: 569 rv.update(info.load_app().cli.list_commands(ctx)) 570 except NoAppException as e: 571 # When an app couldn't be loaded, show the error message 572 # without the traceback. 573 click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") 574 except Exception: 575 # When any other errors occurred during loading, show the 576 # full traceback. 577 click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") 578 579 return sorted(rv) 580 581 def main(self, *args, **kwargs): 582 # Set a global flag that indicates that we were invoked from the 583 # command line interface. This is detected by Flask.run to make the 584 # call into a no-op. This is necessary to avoid ugly errors when the 585 # script that is loaded here also attempts to start a server. 586 os.environ["FLASK_RUN_FROM_CLI"] = "true" 587 588 if get_load_dotenv(self.load_dotenv): 589 load_dotenv() 590 591 obj = kwargs.get("obj") 592 593 if obj is None: 594 obj = ScriptInfo( 595 create_app=self.create_app, set_debug_flag=self.set_debug_flag 596 ) 597 598 kwargs["obj"] = obj 599 kwargs.setdefault("auto_envvar_prefix", "FLASK") 600 return super().main(*args, **kwargs) 601 602 603def _path_is_ancestor(path, other): 604 """Take ``other`` and remove the length of ``path`` from it. Then join it 605 to ``path``. If it is the original value, ``path`` is an ancestor of 606 ``other``.""" 607 return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other 608 609 610def load_dotenv(path=None): 611 """Load "dotenv" files in order of precedence to set environment variables. 612 613 If an env var is already set it is not overwritten, so earlier files in the 614 list are preferred over later files. 615 616 This is a no-op if `python-dotenv`_ is not installed. 617 618 .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme 619 620 :param path: Load the file at this location instead of searching. 621 :return: ``True`` if a file was loaded. 622 623 .. versionchanged:: 1.1.0 624 Returns ``False`` when python-dotenv is not installed, or when 625 the given path isn't a file. 626 627 .. versionchanged:: 2.0 628 When loading the env files, set the default encoding to UTF-8. 629 630 .. versionadded:: 1.0 631 """ 632 if dotenv is None: 633 if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): 634 click.secho( 635 " * Tip: There are .env or .flaskenv files present." 636 ' Do "pip install python-dotenv" to use them.', 637 fg="yellow", 638 err=True, 639 ) 640 641 return False 642 643 # if the given path specifies the actual file then return True, 644 # else False 645 if path is not None: 646 if os.path.isfile(path): 647 return dotenv.load_dotenv(path, encoding="utf-8") 648 649 return False 650 651 new_dir = None 652 653 for name in (".env", ".flaskenv"): 654 path = dotenv.find_dotenv(name, usecwd=True) 655 656 if not path: 657 continue 658 659 if new_dir is None: 660 new_dir = os.path.dirname(path) 661 662 dotenv.load_dotenv(path, encoding="utf-8") 663 664 return new_dir is not None # at least one file was located and loaded 665 666 667def show_server_banner(env, debug, app_import_path, eager_loading): 668 """Show extra startup messages the first time the server is run, 669 ignoring the reloader. 670 """ 671 if os.environ.get("WERKZEUG_RUN_MAIN") == "true": 672 return 673 674 if app_import_path is not None: 675 message = f" * Serving Flask app {app_import_path!r}" 676 677 if not eager_loading: 678 message += " (lazy loading)" 679 680 click.echo(message) 681 682 click.echo(f" * Environment: {env}") 683 684 if env == "production": 685 click.secho( 686 " WARNING: This is a development server. Do not use it in" 687 " a production deployment.", 688 fg="red", 689 ) 690 click.secho(" Use a production WSGI server instead.", dim=True) 691 692 if debug is not None: 693 click.echo(f" * Debug mode: {'on' if debug else 'off'}") 694 695 696class CertParamType(click.ParamType): 697 """Click option type for the ``--cert`` option. Allows either an 698 existing file, the string ``'adhoc'``, or an import for a 699 :class:`~ssl.SSLContext` object. 700 """ 701 702 name = "path" 703 704 def __init__(self): 705 self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) 706 707 def convert(self, value, param, ctx): 708 if ssl is None: 709 raise click.BadParameter( 710 'Using "--cert" requires Python to be compiled with SSL support.', 711 ctx, 712 param, 713 ) 714 715 try: 716 return self.path_type(value, param, ctx) 717 except click.BadParameter: 718 value = click.STRING(value, param, ctx).lower() 719 720 if value == "adhoc": 721 try: 722 import cryptography # noqa: F401 723 except ImportError: 724 raise click.BadParameter( 725 "Using ad-hoc certificates requires the cryptography library.", 726 ctx, 727 param, 728 ) from None 729 730 return value 731 732 obj = import_string(value, silent=True) 733 734 if isinstance(obj, ssl.SSLContext): 735 return obj 736 737 raise 738 739 740def _validate_key(ctx, param, value): 741 """The ``--key`` option must be specified when ``--cert`` is a file. 742 Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed. 743 """ 744 cert = ctx.params.get("cert") 745 is_adhoc = cert == "adhoc" 746 is_context = ssl and isinstance(cert, ssl.SSLContext) 747 748 if value is not None: 749 if is_adhoc: 750 raise click.BadParameter( 751 'When "--cert" is "adhoc", "--key" is not used.', ctx, param 752 ) 753 754 if is_context: 755 raise click.BadParameter( 756 'When "--cert" is an SSLContext object, "--key is not used.', ctx, param 757 ) 758 759 if not cert: 760 raise click.BadParameter('"--cert" must also be specified.', ctx, param) 761 762 ctx.params["cert"] = cert, value 763 764 else: 765 if cert and not (is_adhoc or is_context): 766 raise click.BadParameter('Required when using "--cert".', ctx, param) 767 768 return value 769 770 771class SeparatedPathType(click.Path): 772 """Click option type that accepts a list of values separated by the 773 OS's path separator (``:``, ``;`` on Windows). Each value is 774 validated as a :class:`click.Path` type. 775 """ 776 777 def convert(self, value, param, ctx): 778 items = self.split_envvar_value(value) 779 super_convert = super().convert 780 return [super_convert(item, param, ctx) for item in items] 781 782 783@click.command("run", short_help="Run a development server.") 784@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.") 785@click.option("--port", "-p", default=5000, help="The port to bind to.") 786@click.option( 787 "--cert", type=CertParamType(), help="Specify a certificate file to use HTTPS." 788) 789@click.option( 790 "--key", 791 type=click.Path(exists=True, dir_okay=False, resolve_path=True), 792 callback=_validate_key, 793 expose_value=False, 794 help="The key file to use when specifying a certificate.", 795) 796@click.option( 797 "--reload/--no-reload", 798 default=None, 799 help="Enable or disable the reloader. By default the reloader " 800 "is active if debug is enabled.", 801) 802@click.option( 803 "--debugger/--no-debugger", 804 default=None, 805 help="Enable or disable the debugger. By default the debugger " 806 "is active if debug is enabled.", 807) 808@click.option( 809 "--eager-loading/--lazy-loading", 810 default=None, 811 help="Enable or disable eager loading. By default eager " 812 "loading is enabled if the reloader is disabled.", 813) 814@click.option( 815 "--with-threads/--without-threads", 816 default=True, 817 help="Enable or disable multithreading.", 818) 819@click.option( 820 "--extra-files", 821 default=None, 822 type=SeparatedPathType(), 823 help=( 824 "Extra files that trigger a reload on change. Multiple paths" 825 f" are separated by {os.path.pathsep!r}." 826 ), 827) 828@pass_script_info 829def run_command( 830 info, host, port, reload, debugger, eager_loading, with_threads, cert, extra_files 831): 832 """Run a local development server. 833 834 This server is for development purposes only. It does not provide 835 the stability, security, or performance of production WSGI servers. 836 837 The reloader and debugger are enabled by default if 838 FLASK_ENV=development or FLASK_DEBUG=1. 839 """ 840 debug = get_debug_flag() 841 842 if reload is None: 843 reload = debug 844 845 if debugger is None: 846 debugger = debug 847 848 show_server_banner(get_env(), debug, info.app_import_path, eager_loading) 849 app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) 850 851 from werkzeug.serving import run_simple 852 853 run_simple( 854 host, 855 port, 856 app, 857 use_reloader=reload, 858 use_debugger=debugger, 859 threaded=with_threads, 860 ssl_context=cert, 861 extra_files=extra_files, 862 ) 863 864 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. 871 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 877 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 = {} 885 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) 892 893 ctx.update(app.make_shell_context()) 894 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) 899 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) 910 911 interactive_hook() 912 913 code.interact(banner=banner, local=ctx) 914 915 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.""" 931 932 rules = list(current_app.url_map.iter_rules()) 933 if not rules: 934 click.echo("No routes were registered.") 935 return 936 937 ignored_methods = set(() if all_methods else ("HEAD", "OPTIONS")) 938 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 943 944 rule_methods = [ 945 ", ".join(sorted(rule.methods - ignored_methods)) # type: ignore 946 for rule in rules 947 ] 948 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) 957 958 click.echo(row.format(*headers).strip()) 959 click.echo(row.format(*("-" * width for width in widths))) 960 961 for rule, methods in zip(rules, rule_methods): 962 click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip()) 963 964 965cli = FlaskGroup( 966 help="""\ 967A general utility script for Flask applications. 968 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. 973 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) 983 984 985def main() -> None: 986 if int(click.__version__[0]) < 8: 987 warnings.warn( 988 "Using the `flask` cli with Click 7 is deprecated and" 989 " will not be supported starting with Flask 2.1." 990 " Please upgrade to Click 8 as soon as possible.", 991 DeprecationWarning, 992 ) 993 # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed 994 cli.main(args=sys.argv[1:]) 995 996 997if __name__ == "__main__": 998 main() 999