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

254 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1import importlib.util 

2import json 

3import os 

4import pathlib 

5import pkgutil 

6import sys 

7import typing as t 

8from collections import defaultdict 

9from datetime import timedelta 

10from functools import update_wrapper 

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: # pragma: no cover 

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

31T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable) 

32T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) 

33T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) 

34T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) 

35T_template_context_processor = t.TypeVar( 

36 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable 

37) 

38T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) 

39T_url_value_preprocessor = t.TypeVar( 

40 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable 

41) 

42T_route = t.TypeVar("T_route", bound=ft.RouteCallable) 

43 

44 

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

46 f_name = f.__name__ 

47 

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

49 self._check_setup_finished(f_name) 

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 #: 

80 #: .. deprecated:: 2.2 

81 #: Will be removed in Flask 2.3. 

82 json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None 

83 

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

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

86 #: 

87 #: .. deprecated:: 2.2 

88 #: Will be removed in Flask 2.3. 

89 json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None 

90 

91 def __init__( 

92 self, 

93 import_name: str, 

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

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

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

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

98 ): 

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

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

101 self.import_name = import_name 

102 

103 self.static_folder = static_folder # type: ignore 

104 self.static_url_path = static_url_path 

105 

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

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

108 #: templates should not be added. 

109 self.template_folder = template_folder 

110 

111 if root_path is None: 

112 root_path = get_root_path(self.import_name) 

113 

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

115 #: up resources contained in the package. 

116 self.root_path = root_path 

117 

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

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

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

121 #: been registered. 

122 self.cli = AppGroup() 

123 

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

125 #: 

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

127 #: 

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

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

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

131 

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

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

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

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

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

137 #: other exceptions. The innermost dictionary maps exception 

138 #: classes to handler functions. 

139 #: 

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

141 #: decorator. 

142 #: 

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

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

145 self.error_handler_spec: t.Dict[ 

146 ft.AppOrBlueprintKey, 

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

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

149 

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

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

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

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

154 #: 

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

156 #: decorator. 

157 #: 

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

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

160 self.before_request_funcs: t.Dict[ 

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

162 ] = defaultdict(list) 

163 

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

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

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

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

168 #: 

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

170 #: decorator. 

171 #: 

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

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

174 self.after_request_funcs: t.Dict[ 

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

176 ] = defaultdict(list) 

177 

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

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

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

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

182 #: requests. 

183 #: 

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

185 #: decorator. 

186 #: 

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

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

189 self.teardown_request_funcs: t.Dict[ 

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

191 ] = defaultdict(list) 

192 

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

194 #: values when rendering templates, in the format 

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

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

197 #: requests. 

198 #: 

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

200 #: decorator. 

201 #: 

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

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

204 self.template_context_processors: t.Dict[ 

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

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

207 

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

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

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

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

212 #: requests. 

213 #: 

214 #: To register a function, use the 

215 #: :meth:`url_value_preprocessor` decorator. 

216 #: 

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

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

219 self.url_value_preprocessors: t.Dict[ 

220 ft.AppOrBlueprintKey, 

221 t.List[ft.URLValuePreprocessorCallable], 

222 ] = defaultdict(list) 

223 

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

225 #: arguments when generating URLs, in the format 

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

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

228 #: requests. 

229 #: 

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

231 #: decorator. 

232 #: 

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

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

235 self.url_default_functions: t.Dict[ 

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

237 ] = defaultdict(list) 

238 

239 def __repr__(self) -> str: 

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

241 

242 def _check_setup_finished(self, f_name: str) -> None: 

243 raise NotImplementedError 

244 

245 @property 

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

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

248 if no static folder is set. 

249 """ 

250 if self._static_folder is not None: 

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

252 else: 

253 return None 

254 

255 @static_folder.setter 

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

257 if value is not None: 

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

259 

260 self._static_folder = value 

261 

262 @property 

263 def has_static_folder(self) -> bool: 

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

265 

266 .. versionadded:: 0.5 

267 """ 

268 return self.static_folder is not None 

269 

270 @property 

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

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

273 

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

275 :attr:`static_folder`. 

276 """ 

277 if self._static_url_path is not None: 

278 return self._static_url_path 

279 

280 if self.static_folder is not None: 

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

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

283 

284 return None 

285 

286 @static_url_path.setter 

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

288 if value is not None: 

289 value = value.rstrip("/") 

290 

291 self._static_url_path = value 

292 

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

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

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

296 

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

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

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

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

301 

302 .. versionchanged:: 2.0 

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

304 

305 .. versionadded:: 0.9 

306 """ 

307 value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] 

308 

309 if value is None: 

310 return None 

311 

312 if isinstance(value, timedelta): 

313 return int(value.total_seconds()) 

314 

315 return value 

316 

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

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

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

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

321 set. 

322 

323 .. versionadded:: 0.5 

324 """ 

325 if not self.has_static_folder: 

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

327 

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

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

330 max_age = self.get_send_file_max_age(filename) 

331 return send_from_directory( 

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

333 ) 

334 

335 @locked_cached_property 

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

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

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

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

340 

341 .. versionadded:: 0.5 

342 """ 

343 if self.template_folder is not None: 

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

345 else: 

346 return None 

347 

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

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

350 reading. 

351 

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

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

354 with: 

355 

356 .. code-block:: python 

357 

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

359 conn.executescript(f.read()) 

360 

361 :param resource: Path to the resource relative to 

362 :attr:`root_path`. 

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

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

365 """ 

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

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

368 

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

370 

371 def _method_route( 

372 self, 

373 method: str, 

374 rule: str, 

375 options: dict, 

376 ) -> t.Callable[[T_route], T_route]: 

377 if "methods" in options: 

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

379 

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

381 

382 @setupmethod 

383 def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: 

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

385 

386 .. versionadded:: 2.0 

387 """ 

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

389 

390 @setupmethod 

391 def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: 

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

393 

394 .. versionadded:: 2.0 

395 """ 

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

397 

398 @setupmethod 

399 def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: 

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

401 

402 .. versionadded:: 2.0 

403 """ 

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

405 

406 @setupmethod 

407 def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: 

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

409 

410 .. versionadded:: 2.0 

411 """ 

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

413 

414 @setupmethod 

415 def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: 

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

417 

418 .. versionadded:: 2.0 

419 """ 

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

421 

422 @setupmethod 

423 def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: 

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

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

426 details about the implementation. 

427 

428 .. code-block:: python 

429 

430 @app.route("/") 

431 def index(): 

432 return "Hello, World!" 

433 

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

435 

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

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

438 

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

440 ``OPTIONS`` are added automatically. 

441 

442 :param rule: The URL rule string. 

443 :param options: Extra options passed to the 

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

445 """ 

446 

447 def decorator(f: T_route) -> T_route: 

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

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

450 return f 

451 

452 return decorator 

453 

454 @setupmethod 

455 def add_url_rule( 

456 self, 

457 rule: str, 

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

459 view_func: t.Optional[ft.RouteCallable] = None, 

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

461 **options: t.Any, 

462 ) -> None: 

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

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

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

466 

467 .. code-block:: python 

468 

469 @app.route("/") 

470 def index(): 

471 ... 

472 

473 .. code-block:: python 

474 

475 def index(): 

476 ... 

477 

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

479 

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

481 

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

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

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

485 endpoint. 

486 

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

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

489 automatically by default. 

490 

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

492 rule should participate in routing an endpoint name must be 

493 associated with a view function at some point with the 

494 :meth:`endpoint` decorator. 

495 

496 .. code-block:: python 

497 

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

499 

500 @app.endpoint("index") 

501 def index(): 

502 ... 

503 

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

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

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

507 default if the parameter is not passed. 

508 

509 :param rule: The URL rule string. 

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

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

512 Defaults to ``view_func.__name__``. 

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

514 endpoint name. 

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

516 respond to ``OPTIONS`` requests automatically. 

517 :param options: Extra options passed to the 

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

519 """ 

520 raise NotImplementedError 

521 

522 @setupmethod 

523 def endpoint(self, endpoint: str) -> t.Callable[[F], F]: 

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

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

526 :meth:`add_url_rule`. 

527 

528 .. code-block:: python 

529 

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

531 

532 @app.endpoint("example") 

533 def example(): 

534 ... 

535 

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

537 function. 

538 """ 

539 

540 def decorator(f: F) -> F: 

541 self.view_functions[endpoint] = f 

542 return f 

543 

544 return decorator 

545 

546 @setupmethod 

547 def before_request(self, f: T_before_request) -> T_before_request: 

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

549 

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

551 to load the logged in user from the session. 

552 

553 .. code-block:: python 

554 

555 @app.before_request 

556 def load_user(): 

557 if "user_id" in session: 

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

559 

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

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

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

563 stopped. 

564 """ 

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

566 return f 

567 

568 @setupmethod 

569 def after_request(self, f: T_after_request) -> T_after_request: 

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

571 

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

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

574 replace the response before it is sent. 

575 

576 If a function raises an exception, any remaining 

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

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

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

580 """ 

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

582 return f 

583 

584 @setupmethod 

585 def teardown_request(self, f: T_teardown) -> T_teardown: 

586 """Register a function to be called when the request context is 

587 popped. Typically this happens at the end of each request, but 

588 contexts may be pushed manually as well during testing. 

589 

590 .. code-block:: python 

591 

592 with app.test_request_context(): 

593 ... 

594 

595 When the ``with`` block exits (or ``ctx.pop()`` is called), the 

596 teardown functions are called just before the request context is 

597 made inactive. 

598 

599 When a teardown function was called because of an unhandled 

600 exception it will be passed an error object. If an 

601 :meth:`errorhandler` is registered, it will handle the exception 

602 and the teardown will not receive it. 

603 

604 Teardown functions must avoid raising exceptions. If they 

605 execute code that might fail they must surround that code with a 

606 ``try``/``except`` block and log any errors. 

607 

608 The return values of teardown functions are ignored. 

609 """ 

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

611 return f 

612 

613 @setupmethod 

614 def context_processor( 

615 self, 

616 f: T_template_context_processor, 

617 ) -> T_template_context_processor: 

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

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

620 return f 

621 

622 @setupmethod 

623 def url_value_preprocessor( 

624 self, 

625 f: T_url_value_preprocessor, 

626 ) -> T_url_value_preprocessor: 

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

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

629 :meth:`before_request` functions. 

630 

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

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

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

634 every view. 

635 

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

637 value is ignored. 

638 """ 

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

640 return f 

641 

642 @setupmethod 

643 def url_defaults(self, f: T_url_defaults) -> T_url_defaults: 

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

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

646 update the values passed in place. 

647 """ 

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

649 return f 

650 

651 @setupmethod 

652 def errorhandler( 

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

654 ) -> t.Callable[[T_error_handler], T_error_handler]: 

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

656 

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

658 error code. Example:: 

659 

660 @app.errorhandler(404) 

661 def page_not_found(error): 

662 return 'This page does not exist', 404 

663 

664 You can also register handlers for arbitrary exceptions:: 

665 

666 @app.errorhandler(DatabaseError) 

667 def special_exception_handler(error): 

668 return 'Database connection failed', 500 

669 

670 .. versionadded:: 0.7 

671 Use :meth:`register_error_handler` instead of modifying 

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

673 handlers. 

674 

675 .. versionadded:: 0.7 

676 One can now additionally also register custom exception types 

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

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

679 

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

681 an arbitrary exception 

682 """ 

683 

684 def decorator(f: T_error_handler) -> T_error_handler: 

685 self.register_error_handler(code_or_exception, f) 

686 return f 

687 

688 return decorator 

689 

690 @setupmethod 

691 def register_error_handler( 

692 self, 

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

694 f: ft.ErrorHandlerCallable, 

695 ) -> None: 

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

697 decorator that is more straightforward to use for non decorator 

698 usage. 

699 

700 .. versionadded:: 0.7 

701 """ 

702 exc_class, code = self._get_exc_class_and_code(code_or_exception) 

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

704 

705 @staticmethod 

706 def _get_exc_class_and_code( 

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

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

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

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

711 status code. 

712 

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

714 code as an integer. 

715 """ 

716 exc_class: t.Type[Exception] 

717 

718 if isinstance(exc_class_or_code, int): 

719 try: 

720 exc_class = default_exceptions[exc_class_or_code] 

721 except KeyError: 

722 raise ValueError( 

723 f"'{exc_class_or_code}' is not a recognized HTTP" 

724 " error code. Use a subclass of HTTPException with" 

725 " that code instead." 

726 ) from None 

727 else: 

728 exc_class = exc_class_or_code 

729 

730 if isinstance(exc_class, Exception): 

731 raise TypeError( 

732 f"{exc_class!r} is an instance, not a class. Handlers" 

733 " can only be registered for Exception classes or HTTP" 

734 " error codes." 

735 ) 

736 

737 if not issubclass(exc_class, Exception): 

738 raise ValueError( 

739 f"'{exc_class.__name__}' is not a subclass of Exception." 

740 " Handlers can only be registered for Exception classes" 

741 " or HTTP error codes." 

742 ) 

743 

744 if issubclass(exc_class, HTTPException): 

745 return exc_class, exc_class.code 

746 else: 

747 return exc_class, None 

748 

749 

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

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

752 function. This always is the function name. 

753 """ 

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

755 return view_func.__name__ 

756 

757 

758def _matching_loader_thinks_module_is_package(loader, mod_name): 

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

760 

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

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

763 """ 

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

765 if hasattr(loader, "is_package"): 

766 return loader.is_package(mod_name) 

767 

768 cls = type(loader) 

769 

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

771 # loads must be packages. 

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

773 return True 

774 

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

776 # wrong. 

777 raise AttributeError( 

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

779 f" import hooks." 

780 ) 

781 

782 

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

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

785 try: 

786 path.relative_to(base) 

787 return True 

788 except ValueError: 

789 return False 

790 

791 

792def _find_package_path(import_name): 

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

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

795 

796 try: 

797 root_spec = importlib.util.find_spec(root_mod_name) 

798 

799 if root_spec is None: 

800 raise ValueError("not found") 

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

802 # ValueError: 

803 # - the module name was invalid 

804 # - the module name is __main__ 

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

806 except (ImportError, ValueError): 

807 pass # handled below 

808 else: 

809 # namespace package 

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

811 package_spec = importlib.util.find_spec(import_name) 

812 if package_spec is not None and package_spec.submodule_search_locations: 

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

814 package_path = pathlib.Path( 

815 os.path.commonpath(package_spec.submodule_search_locations) 

816 ) 

817 search_locations = ( 

818 location 

819 for location in root_spec.submodule_search_locations 

820 if _path_is_relative_to(package_path, location) 

821 ) 

822 else: 

823 # Pick the first path. 

824 search_locations = iter(root_spec.submodule_search_locations) 

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

826 # a package (with __init__.py) 

827 elif root_spec.submodule_search_locations: 

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

829 # just a normal module 

830 else: 

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

832 

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

834 loader = pkgutil.get_loader(root_mod_name) 

835 

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

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

838 return os.getcwd() 

839 

840 if hasattr(loader, "get_filename"): 

841 filename = loader.get_filename(root_mod_name) 

842 elif hasattr(loader, "archive"): 

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

844 filename = loader.archive 

845 else: 

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

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

848 filename = importlib.import_module(root_mod_name).__file__ 

849 

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

851 

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

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

854 if _matching_loader_thinks_module_is_package(loader, root_mod_name): 

855 package_path = os.path.dirname(package_path) 

856 

857 return package_path 

858 

859 

860def find_package(import_name: str): 

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

862 that it would be imported from. 

863 

864 The prefix is the directory containing the standard directory 

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

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

867 ``None`` is returned. 

868 

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

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

871 package was imported from the current working directory. 

872 """ 

873 package_path = _find_package_path(import_name) 

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

875 

876 # installed to the system 

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

878 return py_prefix, package_path 

879 

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

881 

882 # installed to a virtualenv 

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

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

885 

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

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

888 return parent, package_path 

889 

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

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

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

893 

894 # something else (prefix/site-packages) 

895 return site_parent, package_path 

896 

897 # not installed 

898 return None, package_path