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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

213 statements  

1from __future__ import annotations 

2 

3import importlib.util 

4import os 

5import pathlib 

6import sys 

7import typing as t 

8from collections import defaultdict 

9from functools import update_wrapper 

10 

11from jinja2 import BaseLoader 

12from jinja2 import FileSystemLoader 

13from werkzeug.exceptions import default_exceptions 

14from werkzeug.exceptions import HTTPException 

15from werkzeug.utils import cached_property 

16 

17from .. import typing as ft 

18from ..helpers import get_root_path 

19from ..templating import _default_template_ctx_processor 

20 

21if t.TYPE_CHECKING: # pragma: no cover 

22 from click import Group 

23 

24# a singleton sentinel value for parameter defaults 

25_sentinel = object() 

26 

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

28T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) 

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

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

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

32T_template_context_processor = t.TypeVar( 

33 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable 

34) 

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

36T_url_value_preprocessor = t.TypeVar( 

37 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable 

38) 

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

40 

41 

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

43 f_name = f.__name__ 

44 

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

46 self._check_setup_finished(f_name) 

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

48 

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

50 

51 

52class Scaffold: 

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

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

55 

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

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

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

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

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

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

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

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

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

65 the ``import_name``. 

66 

67 .. versionadded:: 2.0 

68 """ 

69 

70 cli: Group 

71 name: str 

72 _static_folder: str | None = None 

73 _static_url_path: str | None = None 

74 

75 def __init__( 

76 self, 

77 import_name: str, 

78 static_folder: str | os.PathLike[str] | None = None, 

79 static_url_path: str | None = None, 

80 template_folder: str | os.PathLike[str] | None = None, 

81 root_path: str | None = None, 

82 ): 

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

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

85 self.import_name = import_name 

86 

87 self.static_folder = static_folder # type: ignore 

88 self.static_url_path = static_url_path 

89 

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

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

92 #: templates should not be added. 

93 self.template_folder = template_folder 

94 

95 if root_path is None: 

96 root_path = get_root_path(self.import_name) 

97 

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

99 #: up resources contained in the package. 

100 self.root_path = root_path 

101 

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

103 #: 

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

105 #: 

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

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

108 self.view_functions: dict[str, ft.RouteCallable] = {} 

109 

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

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

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

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

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

115 #: other exceptions. The innermost dictionary maps exception 

116 #: classes to handler functions. 

117 #: 

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

119 #: decorator. 

120 #: 

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

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

123 self.error_handler_spec: dict[ 

124 ft.AppOrBlueprintKey, 

125 dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]], 

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

127 

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

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

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

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

132 #: 

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

134 #: decorator. 

135 #: 

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

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

138 self.before_request_funcs: dict[ 

139 ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable] 

140 ] = defaultdict(list) 

141 

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

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

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

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

146 #: 

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

148 #: decorator. 

149 #: 

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

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

152 self.after_request_funcs: dict[ 

153 ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]] 

154 ] = defaultdict(list) 

155 

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

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

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

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

160 #: requests. 

161 #: 

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

163 #: decorator. 

164 #: 

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

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

167 self.teardown_request_funcs: dict[ 

168 ft.AppOrBlueprintKey, list[ft.TeardownCallable] 

169 ] = defaultdict(list) 

170 

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

172 #: values when rendering templates, in the format 

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

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

175 #: requests. 

176 #: 

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

178 #: decorator. 

179 #: 

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

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

182 self.template_context_processors: dict[ 

183 ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable] 

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

185 

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

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

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

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

190 #: requests. 

191 #: 

192 #: To register a function, use the 

193 #: :meth:`url_value_preprocessor` decorator. 

194 #: 

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

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

197 self.url_value_preprocessors: dict[ 

198 ft.AppOrBlueprintKey, 

199 list[ft.URLValuePreprocessorCallable], 

200 ] = defaultdict(list) 

201 

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

203 #: arguments when generating URLs, 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 :meth:`url_defaults` 

209 #: 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_default_functions: dict[ 

214 ft.AppOrBlueprintKey, list[ft.URLDefaultCallable] 

215 ] = defaultdict(list) 

216 

217 def __repr__(self) -> str: 

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

219 

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

221 raise NotImplementedError 

222 

223 @property 

224 def static_folder(self) -> str | None: 

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

226 if no static folder is set. 

227 """ 

228 if self._static_folder is not None: 

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

230 else: 

231 return None 

232 

233 @static_folder.setter 

234 def static_folder(self, value: str | os.PathLike[str] | None) -> None: 

235 if value is not None: 

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

237 

238 self._static_folder = value 

239 

240 @property 

241 def has_static_folder(self) -> bool: 

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

243 

244 .. versionadded:: 0.5 

245 """ 

246 return self.static_folder is not None 

247 

248 @property 

249 def static_url_path(self) -> str | None: 

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

251 

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

253 :attr:`static_folder`. 

254 """ 

255 if self._static_url_path is not None: 

256 return self._static_url_path 

257 

258 if self.static_folder is not None: 

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

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

261 

262 return None 

263 

264 @static_url_path.setter 

265 def static_url_path(self, value: str | None) -> None: 

266 if value is not None: 

267 value = value.rstrip("/") 

268 

269 self._static_url_path = value 

270 

271 @cached_property 

272 def jinja_loader(self) -> BaseLoader | None: 

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

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

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

276 

277 .. versionadded:: 0.5 

278 """ 

279 if self.template_folder is not None: 

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

281 else: 

282 return None 

283 

284 def _method_route( 

285 self, 

286 method: str, 

287 rule: str, 

288 options: dict[str, t.Any], 

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

290 if "methods" in options: 

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

292 

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

294 

295 @setupmethod 

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

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

298 

299 .. versionadded:: 2.0 

300 """ 

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

302 

303 @setupmethod 

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

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

306 

307 .. versionadded:: 2.0 

308 """ 

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

310 

311 @setupmethod 

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

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

314 

315 .. versionadded:: 2.0 

316 """ 

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

318 

319 @setupmethod 

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

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

322 

323 .. versionadded:: 2.0 

324 """ 

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

326 

327 @setupmethod 

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

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

330 

331 .. versionadded:: 2.0 

332 """ 

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

334 

335 @setupmethod 

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

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

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

339 details about the implementation. 

340 

341 .. code-block:: python 

342 

343 @app.route("/") 

344 def index(): 

345 return "Hello, World!" 

346 

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

348 

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

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

351 

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

353 ``OPTIONS`` are added automatically. 

354 

355 :param rule: The URL rule string. 

356 :param options: Extra options passed to the 

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

358 """ 

359 

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

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

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

363 return f 

364 

365 return decorator 

366 

367 @setupmethod 

368 def add_url_rule( 

369 self, 

370 rule: str, 

371 endpoint: str | None = None, 

372 view_func: ft.RouteCallable | None = None, 

373 provide_automatic_options: bool | None = None, 

374 **options: t.Any, 

375 ) -> None: 

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

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

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

379 

380 .. code-block:: python 

381 

382 @app.route("/") 

383 def index(): 

384 ... 

385 

386 .. code-block:: python 

387 

388 def index(): 

389 ... 

390 

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

392 

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

394 

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

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

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

398 endpoint. 

399 

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

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

402 automatically by default. 

403 

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

405 rule should participate in routing an endpoint name must be 

406 associated with a view function at some point with the 

407 :meth:`endpoint` decorator. 

408 

409 .. code-block:: python 

410 

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

412 

413 @app.endpoint("index") 

414 def index(): 

415 ... 

416 

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

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

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

420 default if the parameter is not passed. 

421 

422 :param rule: The URL rule string. 

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

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

425 Defaults to ``view_func.__name__``. 

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

427 endpoint name. 

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

429 respond to ``OPTIONS`` requests automatically. 

430 :param options: Extra options passed to the 

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

432 """ 

433 raise NotImplementedError 

434 

435 @setupmethod 

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

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

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

439 :meth:`add_url_rule`. 

440 

441 .. code-block:: python 

442 

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

444 

445 @app.endpoint("example") 

446 def example(): 

447 ... 

448 

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

450 function. 

451 """ 

452 

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

454 self.view_functions[endpoint] = f 

455 return f 

456 

457 return decorator 

458 

459 @setupmethod 

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

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

462 

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

464 to load the logged in user from the session. 

465 

466 .. code-block:: python 

467 

468 @app.before_request 

469 def load_user(): 

470 if "user_id" in session: 

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

472 

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

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

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

476 stopped. 

477 

478 This is available on both app and blueprint objects. When used on an app, this 

479 executes before every request. When used on a blueprint, this executes before 

480 every request that the blueprint handles. To register with a blueprint and 

481 execute before every request, use :meth:`.Blueprint.before_app_request`. 

482 """ 

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

484 return f 

485 

486 @setupmethod 

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

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

489 

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

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

492 replace the response before it is sent. 

493 

494 If a function raises an exception, any remaining 

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

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

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

498 

499 This is available on both app and blueprint objects. When used on an app, this 

500 executes after every request. When used on a blueprint, this executes after 

501 every request that the blueprint handles. To register with a blueprint and 

502 execute after every request, use :meth:`.Blueprint.after_app_request`. 

503 """ 

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

505 return f 

506 

507 @setupmethod 

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

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

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

511 contexts may be pushed manually as well during testing. 

512 

513 .. code-block:: python 

514 

515 with app.test_request_context(): 

516 ... 

517 

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

519 teardown functions are called just before the request context is 

520 made inactive. 

521 

522 When a teardown function was called because of an unhandled 

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

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

525 and the teardown will not receive it. 

526 

527 Teardown functions must avoid raising exceptions. If they 

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

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

530 

531 The return values of teardown functions are ignored. 

532 

533 This is available on both app and blueprint objects. When used on an app, this 

534 executes after every request. When used on a blueprint, this executes after 

535 every request that the blueprint handles. To register with a blueprint and 

536 execute after every request, use :meth:`.Blueprint.teardown_app_request`. 

537 """ 

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

539 return f 

540 

541 @setupmethod 

542 def context_processor( 

543 self, 

544 f: T_template_context_processor, 

545 ) -> T_template_context_processor: 

546 """Registers a template context processor function. These functions run before 

547 rendering a template. The keys of the returned dict are added as variables 

548 available in the template. 

549 

550 This is available on both app and blueprint objects. When used on an app, this 

551 is called for every rendered template. When used on a blueprint, this is called 

552 for templates rendered from the blueprint's views. To register with a blueprint 

553 and affect every template, use :meth:`.Blueprint.app_context_processor`. 

554 """ 

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

556 return f 

557 

558 @setupmethod 

559 def url_value_preprocessor( 

560 self, 

561 f: T_url_value_preprocessor, 

562 ) -> T_url_value_preprocessor: 

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

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

565 :meth:`before_request` functions. 

566 

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

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

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

570 every view. 

571 

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

573 value is ignored. 

574 

575 This is available on both app and blueprint objects. When used on an app, this 

576 is called for every request. When used on a blueprint, this is called for 

577 requests that the blueprint handles. To register with a blueprint and affect 

578 every request, use :meth:`.Blueprint.app_url_value_preprocessor`. 

579 """ 

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

581 return f 

582 

583 @setupmethod 

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

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

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

587 update the values passed in place. 

588 

589 This is available on both app and blueprint objects. When used on an app, this 

590 is called for every request. When used on a blueprint, this is called for 

591 requests that the blueprint handles. To register with a blueprint and affect 

592 every request, use :meth:`.Blueprint.app_url_defaults`. 

593 """ 

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

595 return f 

596 

597 @setupmethod 

598 def errorhandler( 

599 self, code_or_exception: type[Exception] | int 

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

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

602 

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

604 error code. Example:: 

605 

606 @app.errorhandler(404) 

607 def page_not_found(error): 

608 return 'This page does not exist', 404 

609 

610 You can also register handlers for arbitrary exceptions:: 

611 

612 @app.errorhandler(DatabaseError) 

613 def special_exception_handler(error): 

614 return 'Database connection failed', 500 

615 

616 This is available on both app and blueprint objects. When used on an app, this 

617 can handle errors from every request. When used on a blueprint, this can handle 

618 errors from requests that the blueprint handles. To register with a blueprint 

619 and affect every request, use :meth:`.Blueprint.app_errorhandler`. 

620 

621 .. versionadded:: 0.7 

622 Use :meth:`register_error_handler` instead of modifying 

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

624 handlers. 

625 

626 .. versionadded:: 0.7 

627 One can now additionally also register custom exception types 

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

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

630 

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

632 an arbitrary exception 

633 """ 

634 

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

636 self.register_error_handler(code_or_exception, f) 

637 return f 

638 

639 return decorator 

640 

641 @setupmethod 

642 def register_error_handler( 

643 self, 

644 code_or_exception: type[Exception] | int, 

645 f: ft.ErrorHandlerCallable, 

646 ) -> None: 

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

648 decorator that is more straightforward to use for non decorator 

649 usage. 

650 

651 .. versionadded:: 0.7 

652 """ 

653 exc_class, code = self._get_exc_class_and_code(code_or_exception) 

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

655 

656 @staticmethod 

657 def _get_exc_class_and_code( 

658 exc_class_or_code: type[Exception] | int, 

659 ) -> tuple[type[Exception], int | None]: 

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

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

662 status code. 

663 

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

665 code as an integer. 

666 """ 

667 exc_class: type[Exception] 

668 

669 if isinstance(exc_class_or_code, int): 

670 try: 

671 exc_class = default_exceptions[exc_class_or_code] 

672 except KeyError: 

673 raise ValueError( 

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

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

676 " that code instead." 

677 ) from None 

678 else: 

679 exc_class = exc_class_or_code 

680 

681 if isinstance(exc_class, Exception): 

682 raise TypeError( 

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

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

685 " error codes." 

686 ) 

687 

688 if not issubclass(exc_class, Exception): 

689 raise ValueError( 

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

691 " Handlers can only be registered for Exception classes" 

692 " or HTTP error codes." 

693 ) 

694 

695 if issubclass(exc_class, HTTPException): 

696 return exc_class, exc_class.code 

697 else: 

698 return exc_class, None 

699 

700 

701def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str: 

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

703 function. This always is the function name. 

704 """ 

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

706 return view_func.__name__ 

707 

708 

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

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

711 try: 

712 path.relative_to(base) 

713 return True 

714 except ValueError: 

715 return False 

716 

717 

718def _find_package_path(import_name: str) -> str: 

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

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

721 

722 try: 

723 root_spec = importlib.util.find_spec(root_mod_name) 

724 

725 if root_spec is None: 

726 raise ValueError("not found") 

727 except (ImportError, ValueError): 

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

729 # ValueError: 

730 # - the module name was invalid 

731 # - the module name is __main__ 

732 # - we raised `ValueError` due to `root_spec` being `None` 

733 return os.getcwd() 

734 

735 if root_spec.submodule_search_locations: 

736 if root_spec.origin is None or root_spec.origin == "namespace": 

737 # namespace package 

738 package_spec = importlib.util.find_spec(import_name) 

739 

740 if package_spec is not None and package_spec.submodule_search_locations: 

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

742 package_path = pathlib.Path( 

743 os.path.commonpath(package_spec.submodule_search_locations) 

744 ) 

745 search_location = next( 

746 location 

747 for location in root_spec.submodule_search_locations 

748 if _path_is_relative_to(package_path, location) 

749 ) 

750 else: 

751 # Pick the first path. 

752 search_location = root_spec.submodule_search_locations[0] 

753 

754 return os.path.dirname(search_location) 

755 else: 

756 # package with __init__.py 

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

758 else: 

759 # module 

760 return os.path.dirname(root_spec.origin) # type: ignore[type-var, return-value] 

761 

762 

763def find_package(import_name: str) -> tuple[str | None, str]: 

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

765 that it would be imported from. 

766 

767 The prefix is the directory containing the standard directory 

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

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

770 ``None`` is returned. 

771 

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

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

774 package was imported from the current working directory. 

775 """ 

776 package_path = _find_package_path(import_name) 

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

778 

779 # installed to the system 

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

781 return py_prefix, package_path 

782 

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

784 

785 # installed to a virtualenv 

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

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

788 

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

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

791 return parent, package_path 

792 

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

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

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

796 

797 # something else (prefix/site-packages) 

798 return site_parent, package_path 

799 

800 # not installed 

801 return None, package_path