Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/scaffold.py: 56%

238 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:03 +0000

1import importlib.util 

2import os 

3import pathlib 

4import pkgutil 

5import sys 

6import typing as t 

7from collections import defaultdict 

8from functools import update_wrapper 

9from json import JSONDecoder 

10from json import JSONEncoder 

11 

12from jinja2 import FileSystemLoader 

13from werkzeug.exceptions import default_exceptions 

14from werkzeug.exceptions import HTTPException 

15 

16from . import typing as ft 

17from .cli import AppGroup 

18from .globals import current_app 

19from .helpers import get_root_path 

20from .helpers import locked_cached_property 

21from .helpers import send_from_directory 

22from .templating import _default_template_ctx_processor 

23 

24if t.TYPE_CHECKING: 

25 from .wrappers import Response 

26 

27# a singleton sentinel value for parameter defaults 

28_sentinel = object() 

29 

30F = t.TypeVar("F", bound=t.Callable[..., t.Any]) 

31 

32 

33def setupmethod(f: F) -> F: 

34 """Wraps a method so that it performs a check in debug mode if the 

35 first request was already handled. 

36 """ 

37 

38 def wrapper_func(self, *args: t.Any, **kwargs: t.Any) -> t.Any: 

39 if self._is_setup_finished(): 

40 raise AssertionError( 

41 "A setup function was called after the first request " 

42 "was handled. This usually indicates a bug in the" 

43 " application where a module was not imported and" 

44 " decorators or other functionality was called too" 

45 " late.\nTo fix this make sure to import all your view" 

46 " modules, database models, and everything related at a" 

47 " central place before the application starts serving" 

48 " requests." 

49 ) 

50 return f(self, *args, **kwargs) 

51 

52 return t.cast(F, update_wrapper(wrapper_func, f)) 

53 

54 

55class Scaffold: 

56 """Common behavior shared between :class:`~flask.Flask` and 

57 :class:`~flask.blueprints.Blueprint`. 

58 

59 :param import_name: The import name of the module where this object 

60 is defined. Usually :attr:`__name__` should be used. 

61 :param static_folder: Path to a folder of static files to serve. 

62 If this is set, a static route will be added. 

63 :param static_url_path: URL prefix for the static route. 

64 :param template_folder: Path to a folder containing template files. 

65 for rendering. If this is set, a Jinja loader will be added. 

66 :param root_path: The path that static, template, and resource files 

67 are relative to. Typically not set, it is discovered based on 

68 the ``import_name``. 

69 

70 .. versionadded:: 2.0 

71 """ 

72 

73 name: str 

74 _static_folder: t.Optional[str] = None 

75 _static_url_path: t.Optional[str] = None 

76 

77 #: JSON encoder class used by :func:`flask.json.dumps`. If a 

78 #: blueprint sets this, it will be used instead of the app's value. 

79 json_encoder: t.Optional[t.Type[JSONEncoder]] = None 

80 

81 #: JSON decoder class used by :func:`flask.json.loads`. If a 

82 #: blueprint sets this, it will be used instead of the app's value. 

83 json_decoder: t.Optional[t.Type[JSONDecoder]] = None 

84 

85 def __init__( 

86 self, 

87 import_name: str, 

88 static_folder: t.Optional[t.Union[str, os.PathLike]] = None, 

89 static_url_path: t.Optional[str] = None, 

90 template_folder: t.Optional[str] = None, 

91 root_path: t.Optional[str] = None, 

92 ): 

93 #: The name of the package or module that this object belongs 

94 #: to. Do not change this once it is set by the constructor. 

95 self.import_name = import_name 

96 

97 self.static_folder = static_folder # type: ignore 

98 self.static_url_path = static_url_path 

99 

100 #: The path to the templates folder, relative to 

101 #: :attr:`root_path`, to add to the template loader. ``None`` if 

102 #: templates should not be added. 

103 self.template_folder = template_folder 

104 

105 if root_path is None: 

106 root_path = get_root_path(self.import_name) 

107 

108 #: Absolute path to the package on the filesystem. Used to look 

109 #: up resources contained in the package. 

110 self.root_path = root_path 

111 

112 #: The Click command group for registering CLI commands for this 

113 #: object. The commands are available from the ``flask`` command 

114 #: once the application has been discovered and blueprints have 

115 #: been registered. 

116 self.cli = AppGroup() 

117 

118 #: A dictionary mapping endpoint names to view functions. 

119 #: 

120 #: To register a view function, use the :meth:`route` decorator. 

121 #: 

122 #: This data structure is internal. It should not be modified 

123 #: directly and its format may change at any time. 

124 self.view_functions: t.Dict[str, t.Callable] = {} 

125 

126 #: A data structure of registered error handlers, in the format 

127 #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is 

128 #: the name of a blueprint the handlers are active for, or 

129 #: ``None`` for all requests. The ``code`` key is the HTTP 

130 #: status code for ``HTTPException``, or ``None`` for 

131 #: other exceptions. The innermost dictionary maps exception 

132 #: classes to handler functions. 

133 #: 

134 #: To register an error handler, use the :meth:`errorhandler` 

135 #: decorator. 

136 #: 

137 #: This data structure is internal. It should not be modified 

138 #: directly and its format may change at any time. 

139 self.error_handler_spec: t.Dict[ 

140 ft.AppOrBlueprintKey, 

141 t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ft.ErrorHandlerCallable]], 

142 ] = defaultdict(lambda: defaultdict(dict)) 

143 

144 #: A data structure of functions to call at the beginning of 

145 #: each request, in the format ``{scope: [functions]}``. The 

146 #: ``scope`` key is the name of a blueprint the functions are 

147 #: active for, or ``None`` for all requests. 

148 #: 

149 #: To register a function, use the :meth:`before_request` 

150 #: decorator. 

151 #: 

152 #: This data structure is internal. It should not be modified 

153 #: directly and its format may change at any time. 

154 self.before_request_funcs: t.Dict[ 

155 ft.AppOrBlueprintKey, t.List[ft.BeforeRequestCallable] 

156 ] = defaultdict(list) 

157 

158 #: A data structure of functions to call at the end of each 

159 #: request, in the format ``{scope: [functions]}``. The 

160 #: ``scope`` key is the name of a blueprint the functions are 

161 #: active for, or ``None`` for all requests. 

162 #: 

163 #: To register a function, use the :meth:`after_request` 

164 #: decorator. 

165 #: 

166 #: This data structure is internal. It should not be modified 

167 #: directly and its format may change at any time. 

168 self.after_request_funcs: t.Dict[ 

169 ft.AppOrBlueprintKey, t.List[ft.AfterRequestCallable] 

170 ] = defaultdict(list) 

171 

172 #: A data structure of functions to call at the end of each 

173 #: request even if an exception is raised, in the format 

174 #: ``{scope: [functions]}``. The ``scope`` key is the name of a 

175 #: blueprint the functions are active for, or ``None`` for all 

176 #: requests. 

177 #: 

178 #: To register a function, use the :meth:`teardown_request` 

179 #: decorator. 

180 #: 

181 #: This data structure is internal. It should not be modified 

182 #: directly and its format may change at any time. 

183 self.teardown_request_funcs: t.Dict[ 

184 ft.AppOrBlueprintKey, t.List[ft.TeardownCallable] 

185 ] = defaultdict(list) 

186 

187 #: A data structure of functions to call to pass extra context 

188 #: values when rendering templates, in the format 

189 #: ``{scope: [functions]}``. The ``scope`` key is the name of a 

190 #: blueprint the functions are active for, or ``None`` for all 

191 #: requests. 

192 #: 

193 #: To register a function, use the :meth:`context_processor` 

194 #: decorator. 

195 #: 

196 #: This data structure is internal. It should not be modified 

197 #: directly and its format may change at any time. 

198 self.template_context_processors: t.Dict[ 

199 ft.AppOrBlueprintKey, t.List[ft.TemplateContextProcessorCallable] 

200 ] = defaultdict(list, {None: [_default_template_ctx_processor]}) 

201 

202 #: A data structure of functions to call to modify the keyword 

203 #: arguments passed to the view function, in the format 

204 #: ``{scope: [functions]}``. The ``scope`` key is the name of a 

205 #: blueprint the functions are active for, or ``None`` for all 

206 #: requests. 

207 #: 

208 #: To register a function, use the 

209 #: :meth:`url_value_preprocessor` decorator. 

210 #: 

211 #: This data structure is internal. It should not be modified 

212 #: directly and its format may change at any time. 

213 self.url_value_preprocessors: t.Dict[ 

214 ft.AppOrBlueprintKey, 

215 t.List[ft.URLValuePreprocessorCallable], 

216 ] = defaultdict(list) 

217 

218 #: A data structure of functions to call to modify the keyword 

219 #: arguments when generating URLs, in the format 

220 #: ``{scope: [functions]}``. The ``scope`` key is the name of a 

221 #: blueprint the functions are active for, or ``None`` for all 

222 #: requests. 

223 #: 

224 #: To register a function, use the :meth:`url_defaults` 

225 #: decorator. 

226 #: 

227 #: This data structure is internal. It should not be modified 

228 #: directly and its format may change at any time. 

229 self.url_default_functions: t.Dict[ 

230 ft.AppOrBlueprintKey, t.List[ft.URLDefaultCallable] 

231 ] = defaultdict(list) 

232 

233 def __repr__(self) -> str: 

234 return f"<{type(self).__name__} {self.name!r}>" 

235 

236 def _is_setup_finished(self) -> bool: 

237 raise NotImplementedError 

238 

239 @property 

240 def static_folder(self) -> t.Optional[str]: 

241 """The absolute path to the configured static folder. ``None`` 

242 if no static folder is set. 

243 """ 

244 if self._static_folder is not None: 

245 return os.path.join(self.root_path, self._static_folder) 

246 else: 

247 return None 

248 

249 @static_folder.setter 

250 def static_folder(self, value: t.Optional[t.Union[str, os.PathLike]]) -> None: 

251 if value is not None: 

252 value = os.fspath(value).rstrip(r"\/") 

253 

254 self._static_folder = value 

255 

256 @property 

257 def has_static_folder(self) -> bool: 

258 """``True`` if :attr:`static_folder` is set. 

259 

260 .. versionadded:: 0.5 

261 """ 

262 return self.static_folder is not None 

263 

264 @property 

265 def static_url_path(self) -> t.Optional[str]: 

266 """The URL prefix that the static route will be accessible from. 

267 

268 If it was not configured during init, it is derived from 

269 :attr:`static_folder`. 

270 """ 

271 if self._static_url_path is not None: 

272 return self._static_url_path 

273 

274 if self.static_folder is not None: 

275 basename = os.path.basename(self.static_folder) 

276 return f"/{basename}".rstrip("/") 

277 

278 return None 

279 

280 @static_url_path.setter 

281 def static_url_path(self, value: t.Optional[str]) -> None: 

282 if value is not None: 

283 value = value.rstrip("/") 

284 

285 self._static_url_path = value 

286 

287 def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]: 

288 """Used by :func:`send_file` to determine the ``max_age`` cache 

289 value for a given file path if it wasn't passed. 

290 

291 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from 

292 the configuration of :data:`~flask.current_app`. This defaults 

293 to ``None``, which tells the browser to use conditional requests 

294 instead of a timed cache, which is usually preferable. 

295 

296 .. versionchanged:: 2.0 

297 The default configuration is ``None`` instead of 12 hours. 

298 

299 .. versionadded:: 0.9 

300 """ 

301 value = current_app.send_file_max_age_default 

302 

303 if value is None: 

304 return None 

305 

306 return int(value.total_seconds()) 

307 

308 def send_static_file(self, filename: str) -> "Response": 

309 """The view function used to serve files from 

310 :attr:`static_folder`. A route is automatically registered for 

311 this view at :attr:`static_url_path` if :attr:`static_folder` is 

312 set. 

313 

314 .. versionadded:: 0.5 

315 """ 

316 if not self.has_static_folder: 

317 raise RuntimeError("'static_folder' must be set to serve static_files.") 

318 

319 # send_file only knows to call get_send_file_max_age on the app, 

320 # call it here so it works for blueprints too. 

321 max_age = self.get_send_file_max_age(filename) 

322 return send_from_directory( 

323 t.cast(str, self.static_folder), filename, max_age=max_age 

324 ) 

325 

326 @locked_cached_property 

327 def jinja_loader(self) -> t.Optional[FileSystemLoader]: 

328 """The Jinja loader for this object's templates. By default this 

329 is a class :class:`jinja2.loaders.FileSystemLoader` to 

330 :attr:`template_folder` if it is set. 

331 

332 .. versionadded:: 0.5 

333 """ 

334 if self.template_folder is not None: 

335 return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) 

336 else: 

337 return None 

338 

339 def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: 

340 """Open a resource file relative to :attr:`root_path` for 

341 reading. 

342 

343 For example, if the file ``schema.sql`` is next to the file 

344 ``app.py`` where the ``Flask`` app is defined, it can be opened 

345 with: 

346 

347 .. code-block:: python 

348 

349 with app.open_resource("schema.sql") as f: 

350 conn.executescript(f.read()) 

351 

352 :param resource: Path to the resource relative to 

353 :attr:`root_path`. 

354 :param mode: Open the file in this mode. Only reading is 

355 supported, valid values are "r" (or "rt") and "rb". 

356 """ 

357 if mode not in {"r", "rt", "rb"}: 

358 raise ValueError("Resources can only be opened for reading.") 

359 

360 return open(os.path.join(self.root_path, resource), mode) 

361 

362 def _method_route( 

363 self, 

364 method: str, 

365 rule: str, 

366 options: dict, 

367 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 

368 if "methods" in options: 

369 raise TypeError("Use the 'route' decorator to use the 'methods' argument.") 

370 

371 return self.route(rule, methods=[method], **options) 

372 

373 def get( 

374 self, rule: str, **options: t.Any 

375 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 

376 """Shortcut for :meth:`route` with ``methods=["GET"]``. 

377 

378 .. versionadded:: 2.0 

379 """ 

380 return self._method_route("GET", rule, options) 

381 

382 def post( 

383 self, rule: str, **options: t.Any 

384 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 

385 """Shortcut for :meth:`route` with ``methods=["POST"]``. 

386 

387 .. versionadded:: 2.0 

388 """ 

389 return self._method_route("POST", rule, options) 

390 

391 def put( 

392 self, rule: str, **options: t.Any 

393 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 

394 """Shortcut for :meth:`route` with ``methods=["PUT"]``. 

395 

396 .. versionadded:: 2.0 

397 """ 

398 return self._method_route("PUT", rule, options) 

399 

400 def delete( 

401 self, rule: str, **options: t.Any 

402 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 

403 """Shortcut for :meth:`route` with ``methods=["DELETE"]``. 

404 

405 .. versionadded:: 2.0 

406 """ 

407 return self._method_route("DELETE", rule, options) 

408 

409 def patch( 

410 self, rule: str, **options: t.Any 

411 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 

412 """Shortcut for :meth:`route` with ``methods=["PATCH"]``. 

413 

414 .. versionadded:: 2.0 

415 """ 

416 return self._method_route("PATCH", rule, options) 

417 

418 def route( 

419 self, rule: str, **options: t.Any 

420 ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 

421 """Decorate a view function to register it with the given URL 

422 rule and options. Calls :meth:`add_url_rule`, which has more 

423 details about the implementation. 

424 

425 .. code-block:: python 

426 

427 @app.route("/") 

428 def index(): 

429 return "Hello, World!" 

430 

431 See :ref:`url-route-registrations`. 

432 

433 The endpoint name for the route defaults to the name of the view 

434 function if the ``endpoint`` parameter isn't passed. 

435 

436 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and 

437 ``OPTIONS`` are added automatically. 

438 

439 :param rule: The URL rule string. 

440 :param options: Extra options passed to the 

441 :class:`~werkzeug.routing.Rule` object. 

442 """ 

443 

444 def decorator(f: ft.RouteDecorator) -> ft.RouteDecorator: 

445 endpoint = options.pop("endpoint", None) 

446 self.add_url_rule(rule, endpoint, f, **options) 

447 return f 

448 

449 return decorator 

450 

451 @setupmethod 

452 def add_url_rule( 

453 self, 

454 rule: str, 

455 endpoint: t.Optional[str] = None, 

456 view_func: t.Optional[ft.ViewCallable] = None, 

457 provide_automatic_options: t.Optional[bool] = None, 

458 **options: t.Any, 

459 ) -> None: 

460 """Register a rule for routing incoming requests and building 

461 URLs. The :meth:`route` decorator is a shortcut to call this 

462 with the ``view_func`` argument. These are equivalent: 

463 

464 .. code-block:: python 

465 

466 @app.route("/") 

467 def index(): 

468 ... 

469 

470 .. code-block:: python 

471 

472 def index(): 

473 ... 

474 

475 app.add_url_rule("/", view_func=index) 

476 

477 See :ref:`url-route-registrations`. 

478 

479 The endpoint name for the route defaults to the name of the view 

480 function if the ``endpoint`` parameter isn't passed. An error 

481 will be raised if a function has already been registered for the 

482 endpoint. 

483 

484 The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is 

485 always added automatically, and ``OPTIONS`` is added 

486 automatically by default. 

487 

488 ``view_func`` does not necessarily need to be passed, but if the 

489 rule should participate in routing an endpoint name must be 

490 associated with a view function at some point with the 

491 :meth:`endpoint` decorator. 

492 

493 .. code-block:: python 

494 

495 app.add_url_rule("/", endpoint="index") 

496 

497 @app.endpoint("index") 

498 def index(): 

499 ... 

500 

501 If ``view_func`` has a ``required_methods`` attribute, those 

502 methods are added to the passed and automatic methods. If it 

503 has a ``provide_automatic_methods`` attribute, it is used as the 

504 default if the parameter is not passed. 

505 

506 :param rule: The URL rule string. 

507 :param endpoint: The endpoint name to associate with the rule 

508 and view function. Used when routing and building URLs. 

509 Defaults to ``view_func.__name__``. 

510 :param view_func: The view function to associate with the 

511 endpoint name. 

512 :param provide_automatic_options: Add the ``OPTIONS`` method and 

513 respond to ``OPTIONS`` requests automatically. 

514 :param options: Extra options passed to the 

515 :class:`~werkzeug.routing.Rule` object. 

516 """ 

517 raise NotImplementedError 

518 

519 def endpoint(self, endpoint: str) -> t.Callable: 

520 """Decorate a view function to register it for the given 

521 endpoint. Used if a rule is added without a ``view_func`` with 

522 :meth:`add_url_rule`. 

523 

524 .. code-block:: python 

525 

526 app.add_url_rule("/ex", endpoint="example") 

527 

528 @app.endpoint("example") 

529 def example(): 

530 ... 

531 

532 :param endpoint: The endpoint name to associate with the view 

533 function. 

534 """ 

535 

536 def decorator(f): 

537 self.view_functions[endpoint] = f 

538 return f 

539 

540 return decorator 

541 

542 @setupmethod 

543 def before_request(self, f: ft.BeforeRequestCallable) -> ft.BeforeRequestCallable: 

544 """Register a function to run before each request. 

545 

546 For example, this can be used to open a database connection, or 

547 to load the logged in user from the session. 

548 

549 .. code-block:: python 

550 

551 @app.before_request 

552 def load_user(): 

553 if "user_id" in session: 

554 g.user = db.session.get(session["user_id"]) 

555 

556 The function will be called without any arguments. If it returns 

557 a non-``None`` value, the value is handled as if it was the 

558 return value from the view, and further request handling is 

559 stopped. 

560 """ 

561 self.before_request_funcs.setdefault(None, []).append(f) 

562 return f 

563 

564 @setupmethod 

565 def after_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable: 

566 """Register a function to run after each request to this object. 

567 

568 The function is called with the response object, and must return 

569 a response object. This allows the functions to modify or 

570 replace the response before it is sent. 

571 

572 If a function raises an exception, any remaining 

573 ``after_request`` functions will not be called. Therefore, this 

574 should not be used for actions that must execute, such as to 

575 close resources. Use :meth:`teardown_request` for that. 

576 """ 

577 self.after_request_funcs.setdefault(None, []).append(f) 

578 return f 

579 

580 @setupmethod 

581 def teardown_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable: 

582 """Register a function to be run at the end of each request, 

583 regardless of whether there was an exception or not. These functions 

584 are executed when the request context is popped, even if not an 

585 actual request was performed. 

586 

587 Example:: 

588 

589 ctx = app.test_request_context() 

590 ctx.push() 

591 ... 

592 ctx.pop() 

593 

594 When ``ctx.pop()`` is executed in the above example, the teardown 

595 functions are called just before the request context moves from the 

596 stack of active contexts. This becomes relevant if you are using 

597 such constructs in tests. 

598 

599 Teardown functions must avoid raising exceptions. If 

600 they execute code that might fail they 

601 will have to surround the execution of that code with try/except 

602 statements and log any errors. 

603 

604 When a teardown function was called because of an exception it will 

605 be passed an error object. 

606 

607 The return values of teardown functions are ignored. 

608 

609 .. admonition:: Debug Note 

610 

611 In debug mode Flask will not tear down a request on an exception 

612 immediately. Instead it will keep it alive so that the interactive 

613 debugger can still access it. This behavior can be controlled 

614 by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. 

615 """ 

616 self.teardown_request_funcs.setdefault(None, []).append(f) 

617 return f 

618 

619 @setupmethod 

620 def context_processor( 

621 self, f: ft.TemplateContextProcessorCallable 

622 ) -> ft.TemplateContextProcessorCallable: 

623 """Registers a template context processor function.""" 

624 self.template_context_processors[None].append(f) 

625 return f 

626 

627 @setupmethod 

628 def url_value_preprocessor( 

629 self, f: ft.URLValuePreprocessorCallable 

630 ) -> ft.URLValuePreprocessorCallable: 

631 """Register a URL value preprocessor function for all view 

632 functions in the application. These functions will be called before the 

633 :meth:`before_request` functions. 

634 

635 The function can modify the values captured from the matched url before 

636 they are passed to the view. For example, this can be used to pop a 

637 common language code value and place it in ``g`` rather than pass it to 

638 every view. 

639 

640 The function is passed the endpoint name and values dict. The return 

641 value is ignored. 

642 """ 

643 self.url_value_preprocessors[None].append(f) 

644 return f 

645 

646 @setupmethod 

647 def url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable: 

648 """Callback function for URL defaults for all view functions of the 

649 application. It's called with the endpoint and values and should 

650 update the values passed in place. 

651 """ 

652 self.url_default_functions[None].append(f) 

653 return f 

654 

655 @setupmethod 

656 def errorhandler( 

657 self, code_or_exception: t.Union[t.Type[Exception], int] 

658 ) -> t.Callable[[ft.ErrorHandlerDecorator], ft.ErrorHandlerDecorator]: 

659 """Register a function to handle errors by code or exception class. 

660 

661 A decorator that is used to register a function given an 

662 error code. Example:: 

663 

664 @app.errorhandler(404) 

665 def page_not_found(error): 

666 return 'This page does not exist', 404 

667 

668 You can also register handlers for arbitrary exceptions:: 

669 

670 @app.errorhandler(DatabaseError) 

671 def special_exception_handler(error): 

672 return 'Database connection failed', 500 

673 

674 .. versionadded:: 0.7 

675 Use :meth:`register_error_handler` instead of modifying 

676 :attr:`error_handler_spec` directly, for application wide error 

677 handlers. 

678 

679 .. versionadded:: 0.7 

680 One can now additionally also register custom exception types 

681 that do not necessarily have to be a subclass of the 

682 :class:`~werkzeug.exceptions.HTTPException` class. 

683 

684 :param code_or_exception: the code as integer for the handler, or 

685 an arbitrary exception 

686 """ 

687 

688 def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator: 

689 self.register_error_handler(code_or_exception, f) 

690 return f 

691 

692 return decorator 

693 

694 @setupmethod 

695 def register_error_handler( 

696 self, 

697 code_or_exception: t.Union[t.Type[Exception], int], 

698 f: ft.ErrorHandlerCallable, 

699 ) -> None: 

700 """Alternative error attach function to the :meth:`errorhandler` 

701 decorator that is more straightforward to use for non decorator 

702 usage. 

703 

704 .. versionadded:: 0.7 

705 """ 

706 if isinstance(code_or_exception, HTTPException): # old broken behavior 

707 raise ValueError( 

708 "Tried to register a handler for an exception instance" 

709 f" {code_or_exception!r}. Handlers can only be" 

710 " registered for exception classes or HTTP error codes." 

711 ) 

712 

713 try: 

714 exc_class, code = self._get_exc_class_and_code(code_or_exception) 

715 except KeyError: 

716 raise KeyError( 

717 f"'{code_or_exception}' is not a recognized HTTP error" 

718 " code. Use a subclass of HTTPException with that code" 

719 " instead." 

720 ) from None 

721 

722 self.error_handler_spec[None][code][exc_class] = f 

723 

724 @staticmethod 

725 def _get_exc_class_and_code( 

726 exc_class_or_code: t.Union[t.Type[Exception], int] 

727 ) -> t.Tuple[t.Type[Exception], t.Optional[int]]: 

728 """Get the exception class being handled. For HTTP status codes 

729 or ``HTTPException`` subclasses, return both the exception and 

730 status code. 

731 

732 :param exc_class_or_code: Any exception class, or an HTTP status 

733 code as an integer. 

734 """ 

735 exc_class: t.Type[Exception] 

736 if isinstance(exc_class_or_code, int): 

737 exc_class = default_exceptions[exc_class_or_code] 

738 else: 

739 exc_class = exc_class_or_code 

740 

741 assert issubclass( 

742 exc_class, Exception 

743 ), "Custom exceptions must be subclasses of Exception." 

744 

745 if issubclass(exc_class, HTTPException): 

746 return exc_class, exc_class.code 

747 else: 

748 return exc_class, None 

749 

750 

751def _endpoint_from_view_func(view_func: t.Callable) -> str: 

752 """Internal helper that returns the default endpoint for a given 

753 function. This always is the function name. 

754 """ 

755 assert view_func is not None, "expected view func if endpoint is not provided." 

756 return view_func.__name__ 

757 

758 

759def _matching_loader_thinks_module_is_package(loader, mod_name): 

760 """Attempt to figure out if the given name is a package or a module. 

761 

762 :param: loader: The loader that handled the name. 

763 :param mod_name: The name of the package or module. 

764 """ 

765 # Use loader.is_package if it's available. 

766 if hasattr(loader, "is_package"): 

767 return loader.is_package(mod_name) 

768 

769 cls = type(loader) 

770 

771 # NamespaceLoader doesn't implement is_package, but all names it 

772 # loads must be packages. 

773 if cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader": 

774 return True 

775 

776 # Otherwise we need to fail with an error that explains what went 

777 # wrong. 

778 raise AttributeError( 

779 f"'{cls.__name__}.is_package()' must be implemented for PEP 302" 

780 f" import hooks." 

781 ) 

782 

783 

784def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool: 

785 # Path.is_relative_to doesn't exist until Python 3.9 

786 try: 

787 path.relative_to(base) 

788 return True 

789 except ValueError: 

790 return False 

791 

792 

793def _find_package_path(import_name): 

794 """Find the path that contains the package or module.""" 

795 root_mod_name, _, _ = import_name.partition(".") 

796 

797 try: 

798 root_spec = importlib.util.find_spec(root_mod_name) 

799 

800 if root_spec is None: 

801 raise ValueError("not found") 

802 # ImportError: the machinery told us it does not exist 

803 # ValueError: 

804 # - the module name was invalid 

805 # - the module name is __main__ 

806 # - *we* raised `ValueError` due to `root_spec` being `None` 

807 except (ImportError, ValueError): 

808 pass # handled below 

809 else: 

810 # namespace package 

811 if root_spec.origin in {"namespace", None}: 

812 package_spec = importlib.util.find_spec(import_name) 

813 if package_spec is not None and package_spec.submodule_search_locations: 

814 # Pick the path in the namespace that contains the submodule. 

815 package_path = pathlib.Path( 

816 os.path.commonpath(package_spec.submodule_search_locations) 

817 ) 

818 search_locations = ( 

819 location 

820 for location in root_spec.submodule_search_locations 

821 if _path_is_relative_to(package_path, location) 

822 ) 

823 else: 

824 # Pick the first path. 

825 search_locations = iter(root_spec.submodule_search_locations) 

826 return os.path.dirname(next(search_locations)) 

827 # a package (with __init__.py) 

828 elif root_spec.submodule_search_locations: 

829 return os.path.dirname(os.path.dirname(root_spec.origin)) 

830 # just a normal module 

831 else: 

832 return os.path.dirname(root_spec.origin) 

833 

834 # we were unable to find the `package_path` using PEP 451 loaders 

835 loader = pkgutil.get_loader(root_mod_name) 

836 

837 if loader is None or root_mod_name == "__main__": 

838 # import name is not found, or interactive/main module 

839 return os.getcwd() 

840 

841 if hasattr(loader, "get_filename"): 

842 filename = loader.get_filename(root_mod_name) 

843 elif hasattr(loader, "archive"): 

844 # zipimporter's loader.archive points to the .egg or .zip file. 

845 filename = loader.archive 

846 else: 

847 # At least one loader is missing both get_filename and archive: 

848 # Google App Engine's HardenedModulesHook, use __file__. 

849 filename = importlib.import_module(root_mod_name).__file__ 

850 

851 package_path = os.path.abspath(os.path.dirname(filename)) 

852 

853 # If the imported name is a package, filename is currently pointing 

854 # to the root of the package, need to get the current directory. 

855 if _matching_loader_thinks_module_is_package(loader, root_mod_name): 

856 package_path = os.path.dirname(package_path) 

857 

858 return package_path 

859 

860 

861def find_package(import_name: str): 

862 """Find the prefix that a package is installed under, and the path 

863 that it would be imported from. 

864 

865 The prefix is the directory containing the standard directory 

866 hierarchy (lib, bin, etc.). If the package is not installed to the 

867 system (:attr:`sys.prefix`) or a virtualenv (``site-packages``), 

868 ``None`` is returned. 

869 

870 The path is the entry in :attr:`sys.path` that contains the package 

871 for import. If the package is not installed, it's assumed that the 

872 package was imported from the current working directory. 

873 """ 

874 package_path = _find_package_path(import_name) 

875 py_prefix = os.path.abspath(sys.prefix) 

876 

877 # installed to the system 

878 if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix): 

879 return py_prefix, package_path 

880 

881 site_parent, site_folder = os.path.split(package_path) 

882 

883 # installed to a virtualenv 

884 if site_folder.lower() == "site-packages": 

885 parent, folder = os.path.split(site_parent) 

886 

887 # Windows (prefix/lib/site-packages) 

888 if folder.lower() == "lib": 

889 return parent, package_path 

890 

891 # Unix (prefix/lib/pythonX.Y/site-packages) 

892 if os.path.basename(parent).lower() == "lib": 

893 return os.path.dirname(parent), package_path 

894 

895 # something else (prefix/site-packages) 

896 return site_parent, package_path 

897 

898 # not installed 

899 return None, package_path