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

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 

12 

13import click 

14from werkzeug.utils import import_string 

15 

16from .globals import current_app 

17from .helpers import get_debug_flag 

18from .helpers import get_env 

19from .helpers import get_load_dotenv 

20 

21 

22class NoAppException(click.UsageError): 

23 """Raised if an application cannot be found or loaded.""" 

24 

25 

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 

31 

32 # Search for the most common names first. 

33 for attr_name in ("app", "application"): 

34 app = getattr(module, attr_name, None) 

35 

36 if isinstance(app, Flask): 

37 return app 

38 

39 # Otherwise find the only object that is a Flask instance. 

40 matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] 

41 

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 ) 

50 

51 # Search for app factory functions. 

52 for attr_name in ("create_app", "make_app"): 

53 app_factory = getattr(module, attr_name, None) 

54 

55 if inspect.isfunction(app_factory): 

56 try: 

57 app = app_factory() 

58 

59 if isinstance(app, Flask): 

60 return app 

61 except TypeError as e: 

62 if not _called_with_wrong_args(app_factory): 

63 raise 

64 

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 

71 

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 ) 

77 

78 

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. 

83 

84 :param f: The function that was called. 

85 :return: ``True`` if the call failed. 

86 """ 

87 tb = sys.exc_info()[2] 

88 

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 

94 

95 tb = tb.tb_next 

96 

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 

103 

104 

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 

110 

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 

119 

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 ) 

130 

131 name = expr.func.id 

132 

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 ) 

147 

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 

154 

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 

163 

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 

171 

172 if isinstance(app, Flask): 

173 return app 

174 

175 raise NoAppException( 

176 "A valid Flask application was not obtained from" 

177 f" '{module.__name__}:{app_name}'." 

178 ) 

179 

180 

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) 

186 

187 fname, ext = os.path.splitext(path) 

188 if ext == ".py": 

189 path = fname 

190 

191 if os.path.basename(path) == "__init__": 

192 path = os.path.dirname(path) 

193 

194 module_name = [] 

195 

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) 

200 

201 if not os.path.exists(os.path.join(path, "__init__.py")): 

202 break 

203 

204 if sys.path[0] != path: 

205 sys.path.insert(0, path) 

206 

207 return ".".join(module_name[::-1]) 

208 

209 

210def locate_app(module_name, app_name, raise_if_not_found=True): 

211 __traceback_hide__ = True # noqa: F841 

212 

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 

227 

228 module = sys.modules[module_name] 

229 

230 if app_name is None: 

231 return find_best_app(module) 

232 else: 

233 return find_app_by_string(module, app_name) 

234 

235 

236def get_version(ctx, param, value): 

237 if not value or ctx.resilient_parsing: 

238 return 

239 

240 import werkzeug 

241 from . import __version__ 

242 

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() 

250 

251 

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) 

260 

261 

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 """ 

268 

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 

274 

275 if use_eager_loading is None: 

276 use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true" 

277 

278 if use_eager_loading: 

279 self._load_unlocked() 

280 else: 

281 self._load_in_background() 

282 

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) 

287 

288 def _load_app(): 

289 __traceback_hide__ = True # noqa: F841 

290 

291 with self._lock: 

292 if ctx is not None: 

293 click.globals.push_context(ctx) 

294 

295 try: 

296 self._load_unlocked() 

297 except Exception as e: 

298 self._bg_loading_exc = e 

299 

300 t = Thread(target=_load_app, args=()) 

301 t.start() 

302 

303 def _flush_bg_loading_exception(self): 

304 __traceback_hide__ = True # noqa: F841 

305 exc = self._bg_loading_exc 

306 

307 if exc is not None: 

308 self._bg_loading_exc = None 

309 raise exc 

310 

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 

316 

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) 

328 

329 

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 """ 

338 

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 

350 

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 

357 

358 if self._loaded_app is not None: 

359 return self._loaded_app 

360 

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) 

374 

375 if app: 

376 break 

377 

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 ) 

384 

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() 

389 

390 self._loaded_app = app 

391 return app 

392 

393 

394pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True) 

395 

396 

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 """ 

403 

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) 

408 

409 return update_wrapper(decorator, f) 

410 

411 

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`. 

416 

417 Not to be confused with :class:`FlaskGroup`. 

418 """ 

419 

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) 

426 

427 def decorator(f): 

428 if wrap_for_ctx: 

429 f = with_appcontext(f) 

430 return click.Group.command(self, *args, **kwargs)(f) 

431 

432 return decorator 

433 

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) 

441 

442 

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`. 

449 

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 

460 

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 """ 

465 

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 ()) 

476 

477 if add_version_option: 

478 params.append(version_option) 

479 

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 

484 

485 if add_default_commands: 

486 self.add_command(run_command) 

487 self.add_command(shell_command) 

488 self.add_command(routes_command) 

489 

490 self._loaded_plugin_commands = False 

491 

492 def _load_plugin_commands(self): 

493 if self._loaded_plugin_commands: 

494 return 

495 

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 

503 

504 for ep in metadata.entry_points(group="flask.commands"): 

505 self.add_command(ep.load(), ep.name) 

506 

507 self._loaded_plugin_commands = True 

508 

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) 

514 

515 if rv is not None: 

516 return rv 

517 

518 info = ctx.ensure_object(ScriptInfo) 

519 

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") 

526 

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) 

532 

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") 

545 

546 return sorted(rv) 

547 

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" 

554 

555 if get_load_dotenv(self.load_dotenv): 

556 load_dotenv() 

557 

558 obj = kwargs.get("obj") 

559 

560 if obj is None: 

561 obj = ScriptInfo( 

562 create_app=self.create_app, set_debug_flag=self.set_debug_flag 

563 ) 

564 

565 kwargs["obj"] = obj 

566 kwargs.setdefault("auto_envvar_prefix", "FLASK") 

567 return super().main(*args, **kwargs) 

568 

569 

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 

575 

576 

577def load_dotenv(path=None): 

578 """Load "dotenv" files in order of precedence to set environment variables. 

579 

580 If an env var is already set it is not overwritten, so earlier files in the 

581 list are preferred over later files. 

582 

583 This is a no-op if `python-dotenv`_ is not installed. 

584 

585 .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme 

586 

587 :param path: Load the file at this location instead of searching. 

588 :return: ``True`` if a file was loaded. 

589 

590 .. versionchanged:: 1.1.0 

591 Returns ``False`` when python-dotenv is not installed, or when 

592 the given path isn't a file. 

593 

594 .. versionchanged:: 2.0 

595 When loading the env files, set the default encoding to UTF-8. 

596 

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 ) 

609 

610 return False 

611 

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") 

617 

618 return False 

619 

620 new_dir = None 

621 

622 for name in (".env", ".flaskenv"): 

623 path = dotenv.find_dotenv(name, usecwd=True) 

624 

625 if not path: 

626 continue 

627 

628 if new_dir is None: 

629 new_dir = os.path.dirname(path) 

630 

631 dotenv.load_dotenv(path, encoding="utf-8") 

632 

633 return new_dir is not None # at least one file was located and loaded 

634 

635 

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 

642 

643 if app_import_path is not None: 

644 message = f" * Serving Flask app {app_import_path!r}" 

645 

646 if not eager_loading: 

647 message += " (lazy loading)" 

648 

649 click.echo(message) 

650 

651 click.echo(f" * Environment: {env}") 

652 

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) 

660 

661 if debug is not None: 

662 click.echo(f" * Debug mode: {'on' if debug else 'off'}") 

663 

664 

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 """ 

670 

671 name = "path" 

672 

673 def __init__(self): 

674 self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) 

675 

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 

685 

686 try: 

687 return self.path_type(value, param, ctx) 

688 except click.BadParameter: 

689 value = click.STRING(value, param, ctx).lower() 

690 

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 

700 

701 return value 

702 

703 obj = import_string(value, silent=True) 

704 

705 if isinstance(obj, ssl.SSLContext): 

706 return obj 

707 

708 raise 

709 

710 

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" 

717 

718 try: 

719 import ssl 

720 except ImportError: 

721 is_context = False 

722 else: 

723 is_context = isinstance(cert, ssl.SSLContext) 

724 

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 ) 

730 

731 if is_context: 

732 raise click.BadParameter( 

733 'When "--cert" is an SSLContext object, "--key is not used.', ctx, param 

734 ) 

735 

736 if not cert: 

737 raise click.BadParameter('"--cert" must also be specified.', ctx, param) 

738 

739 ctx.params["cert"] = cert, value 

740 

741 else: 

742 if cert and not (is_adhoc or is_context): 

743 raise click.BadParameter('Required when using "--cert".', ctx, param) 

744 

745 return value 

746 

747 

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 """ 

753 

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] 

758 

759 

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. 

832 

833 This server is for development purposes only. It does not provide 

834 the stability, security, or performance of production WSGI servers. 

835 

836 The reloader and debugger are enabled by default if 

837 FLASK_ENV=development or FLASK_DEBUG=1. 

838 """ 

839 debug = get_debug_flag() 

840 

841 if reload is None: 

842 reload = debug 

843 

844 if debugger is None: 

845 debugger = debug 

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 exclude_patterns=exclude_patterns, 

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 cli.main() 

987 

988 

989if __name__ == "__main__": 

990 main()