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