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

214 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-22 06:29 +0000

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 

11import click 

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 ..cli import AppGroup 

19from ..helpers import get_root_path 

20from ..templating import _default_template_ctx_processor 

21 

22# a singleton sentinel value for parameter defaults 

23_sentinel = object() 

24 

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

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

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

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

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

30T_template_context_processor = t.TypeVar( 

31 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable 

32) 

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

34T_url_value_preprocessor = t.TypeVar( 

35 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable 

36) 

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

38 

39 

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

41 f_name = f.__name__ 

42 

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

44 self._check_setup_finished(f_name) 

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

46 

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

48 

49 

50class Scaffold: 

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

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

53 

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

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

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

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

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

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

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

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

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

63 the ``import_name``. 

64 

65 .. versionadded:: 2.0 

66 """ 

67 

68 name: str 

69 _static_folder: str | None = None 

70 _static_url_path: str | None = None 

71 

72 def __init__( 

73 self, 

74 import_name: str, 

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

76 static_url_path: str | None = None, 

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

78 root_path: str | None = None, 

79 ): 

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

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

82 self.import_name = import_name 

83 

84 self.static_folder = static_folder # type: ignore 

85 self.static_url_path = static_url_path 

86 

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

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

89 #: templates should not be added. 

90 self.template_folder = template_folder 

91 

92 if root_path is None: 

93 root_path = get_root_path(self.import_name) 

94 

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

96 #: up resources contained in the package. 

97 self.root_path = root_path 

98 

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

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

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

102 #: been registered. 

103 self.cli: click.Group = AppGroup() 

104 

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

106 #: 

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

108 #: 

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

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

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

112 

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

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

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

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

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

118 #: other exceptions. The innermost dictionary maps exception 

119 #: classes to handler functions. 

120 #: 

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

122 #: decorator. 

123 #: 

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

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

126 self.error_handler_spec: dict[ 

127 ft.AppOrBlueprintKey, 

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

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

130 

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

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

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

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

135 #: 

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

137 #: decorator. 

138 #: 

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

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

141 self.before_request_funcs: dict[ 

142 ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable] 

143 ] = defaultdict(list) 

144 

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

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

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

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

149 #: 

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

151 #: decorator. 

152 #: 

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

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

155 self.after_request_funcs: dict[ 

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

157 ] = defaultdict(list) 

158 

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

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

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

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

163 #: requests. 

164 #: 

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

166 #: decorator. 

167 #: 

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

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

170 self.teardown_request_funcs: dict[ 

171 ft.AppOrBlueprintKey, list[ft.TeardownCallable] 

172 ] = defaultdict(list) 

173 

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

175 #: values when rendering templates, in the format 

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

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

178 #: requests. 

179 #: 

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

181 #: decorator. 

182 #: 

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

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

185 self.template_context_processors: dict[ 

186 ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable] 

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

188 

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

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

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

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

193 #: requests. 

194 #: 

195 #: To register a function, use the 

196 #: :meth:`url_value_preprocessor` decorator. 

197 #: 

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

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

200 self.url_value_preprocessors: dict[ 

201 ft.AppOrBlueprintKey, 

202 list[ft.URLValuePreprocessorCallable], 

203 ] = defaultdict(list) 

204 

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

206 #: arguments when generating URLs, in the format 

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

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

209 #: requests. 

210 #: 

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

212 #: decorator. 

213 #: 

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

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

216 self.url_default_functions: dict[ 

217 ft.AppOrBlueprintKey, list[ft.URLDefaultCallable] 

218 ] = defaultdict(list) 

219 

220 def __repr__(self) -> str: 

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

222 

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

224 raise NotImplementedError 

225 

226 @property 

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

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

229 if no static folder is set. 

230 """ 

231 if self._static_folder is not None: 

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

233 else: 

234 return None 

235 

236 @static_folder.setter 

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

238 if value is not None: 

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

240 

241 self._static_folder = value 

242 

243 @property 

244 def has_static_folder(self) -> bool: 

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

246 

247 .. versionadded:: 0.5 

248 """ 

249 return self.static_folder is not None 

250 

251 @property 

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

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

254 

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

256 :attr:`static_folder`. 

257 """ 

258 if self._static_url_path is not None: 

259 return self._static_url_path 

260 

261 if self.static_folder is not None: 

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

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

264 

265 return None 

266 

267 @static_url_path.setter 

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

269 if value is not None: 

270 value = value.rstrip("/") 

271 

272 self._static_url_path = value 

273 

274 @cached_property 

275 def jinja_loader(self) -> FileSystemLoader | None: 

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

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

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

279 

280 .. versionadded:: 0.5 

281 """ 

282 if self.template_folder is not None: 

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

284 else: 

285 return None 

286 

287 def _method_route( 

288 self, 

289 method: str, 

290 rule: str, 

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

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

293 if "methods" in options: 

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

295 

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

297 

298 @setupmethod 

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

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

301 

302 .. versionadded:: 2.0 

303 """ 

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

305 

306 @setupmethod 

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

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

309 

310 .. versionadded:: 2.0 

311 """ 

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

313 

314 @setupmethod 

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

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

317 

318 .. versionadded:: 2.0 

319 """ 

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

321 

322 @setupmethod 

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

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

325 

326 .. versionadded:: 2.0 

327 """ 

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

329 

330 @setupmethod 

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

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

333 

334 .. versionadded:: 2.0 

335 """ 

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

337 

338 @setupmethod 

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

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

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

342 details about the implementation. 

343 

344 .. code-block:: python 

345 

346 @app.route("/") 

347 def index(): 

348 return "Hello, World!" 

349 

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

351 

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

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

354 

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

356 ``OPTIONS`` are added automatically. 

357 

358 :param rule: The URL rule string. 

359 :param options: Extra options passed to the 

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

361 """ 

362 

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

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

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

366 return f 

367 

368 return decorator 

369 

370 @setupmethod 

371 def add_url_rule( 

372 self, 

373 rule: str, 

374 endpoint: str | None = None, 

375 view_func: ft.RouteCallable | None = None, 

376 provide_automatic_options: bool | None = None, 

377 **options: t.Any, 

378 ) -> None: 

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

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

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

382 

383 .. code-block:: python 

384 

385 @app.route("/") 

386 def index(): 

387 ... 

388 

389 .. code-block:: python 

390 

391 def index(): 

392 ... 

393 

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

395 

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

397 

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

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

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

401 endpoint. 

402 

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

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

405 automatically by default. 

406 

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

408 rule should participate in routing an endpoint name must be 

409 associated with a view function at some point with the 

410 :meth:`endpoint` decorator. 

411 

412 .. code-block:: python 

413 

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

415 

416 @app.endpoint("index") 

417 def index(): 

418 ... 

419 

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

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

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

423 default if the parameter is not passed. 

424 

425 :param rule: The URL rule string. 

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

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

428 Defaults to ``view_func.__name__``. 

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

430 endpoint name. 

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

432 respond to ``OPTIONS`` requests automatically. 

433 :param options: Extra options passed to the 

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

435 """ 

436 raise NotImplementedError 

437 

438 @setupmethod 

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

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

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

442 :meth:`add_url_rule`. 

443 

444 .. code-block:: python 

445 

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

447 

448 @app.endpoint("example") 

449 def example(): 

450 ... 

451 

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

453 function. 

454 """ 

455 

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

457 self.view_functions[endpoint] = f 

458 return f 

459 

460 return decorator 

461 

462 @setupmethod 

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

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

465 

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

467 to load the logged in user from the session. 

468 

469 .. code-block:: python 

470 

471 @app.before_request 

472 def load_user(): 

473 if "user_id" in session: 

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

475 

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

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

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

479 stopped. 

480 

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

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

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

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

485 """ 

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

487 return f 

488 

489 @setupmethod 

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

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

492 

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

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

495 replace the response before it is sent. 

496 

497 If a function raises an exception, any remaining 

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

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

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

501 

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

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

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

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

506 """ 

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

508 return f 

509 

510 @setupmethod 

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

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

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

514 contexts may be pushed manually as well during testing. 

515 

516 .. code-block:: python 

517 

518 with app.test_request_context(): 

519 ... 

520 

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

522 teardown functions are called just before the request context is 

523 made inactive. 

524 

525 When a teardown function was called because of an unhandled 

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

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

528 and the teardown will not receive it. 

529 

530 Teardown functions must avoid raising exceptions. If they 

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

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

533 

534 The return values of teardown functions are ignored. 

535 

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

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

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

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

540 """ 

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

542 return f 

543 

544 @setupmethod 

545 def context_processor( 

546 self, 

547 f: T_template_context_processor, 

548 ) -> T_template_context_processor: 

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

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

551 available in the template. 

552 

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

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

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

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

557 """ 

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

559 return f 

560 

561 @setupmethod 

562 def url_value_preprocessor( 

563 self, 

564 f: T_url_value_preprocessor, 

565 ) -> T_url_value_preprocessor: 

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

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

568 :meth:`before_request` functions. 

569 

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

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

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

573 every view. 

574 

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

576 value is ignored. 

577 

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

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

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

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

582 """ 

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

584 return f 

585 

586 @setupmethod 

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

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

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

590 update the values passed in place. 

591 

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

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

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

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

596 """ 

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

598 return f 

599 

600 @setupmethod 

601 def errorhandler( 

602 self, code_or_exception: type[Exception] | int 

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

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

605 

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

607 error code. Example:: 

608 

609 @app.errorhandler(404) 

610 def page_not_found(error): 

611 return 'This page does not exist', 404 

612 

613 You can also register handlers for arbitrary exceptions:: 

614 

615 @app.errorhandler(DatabaseError) 

616 def special_exception_handler(error): 

617 return 'Database connection failed', 500 

618 

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

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

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

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

623 

624 .. versionadded:: 0.7 

625 Use :meth:`register_error_handler` instead of modifying 

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

627 handlers. 

628 

629 .. versionadded:: 0.7 

630 One can now additionally also register custom exception types 

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

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

633 

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

635 an arbitrary exception 

636 """ 

637 

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

639 self.register_error_handler(code_or_exception, f) 

640 return f 

641 

642 return decorator 

643 

644 @setupmethod 

645 def register_error_handler( 

646 self, 

647 code_or_exception: type[Exception] | int, 

648 f: ft.ErrorHandlerCallable, 

649 ) -> None: 

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

651 decorator that is more straightforward to use for non decorator 

652 usage. 

653 

654 .. versionadded:: 0.7 

655 """ 

656 exc_class, code = self._get_exc_class_and_code(code_or_exception) 

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

658 

659 @staticmethod 

660 def _get_exc_class_and_code( 

661 exc_class_or_code: type[Exception] | int, 

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

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

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

665 status code. 

666 

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

668 code as an integer. 

669 """ 

670 exc_class: type[Exception] 

671 

672 if isinstance(exc_class_or_code, int): 

673 try: 

674 exc_class = default_exceptions[exc_class_or_code] 

675 except KeyError: 

676 raise ValueError( 

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

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

679 " that code instead." 

680 ) from None 

681 else: 

682 exc_class = exc_class_or_code 

683 

684 if isinstance(exc_class, Exception): 

685 raise TypeError( 

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

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

688 " error codes." 

689 ) 

690 

691 if not issubclass(exc_class, Exception): 

692 raise ValueError( 

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

694 " Handlers can only be registered for Exception classes" 

695 " or HTTP error codes." 

696 ) 

697 

698 if issubclass(exc_class, HTTPException): 

699 return exc_class, exc_class.code 

700 else: 

701 return exc_class, None 

702 

703 

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

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

706 function. This always is the function name. 

707 """ 

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

709 return view_func.__name__ 

710 

711 

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

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

714 try: 

715 path.relative_to(base) 

716 return True 

717 except ValueError: 

718 return False 

719 

720 

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

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

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

724 

725 try: 

726 root_spec = importlib.util.find_spec(root_mod_name) 

727 

728 if root_spec is None: 

729 raise ValueError("not found") 

730 except (ImportError, ValueError): 

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

732 # ValueError: 

733 # - the module name was invalid 

734 # - the module name is __main__ 

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

736 return os.getcwd() 

737 

738 if root_spec.submodule_search_locations: 

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

740 # namespace package 

741 package_spec = importlib.util.find_spec(import_name) 

742 

743 if package_spec is not None and package_spec.submodule_search_locations: 

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

745 package_path = pathlib.Path( 

746 os.path.commonpath(package_spec.submodule_search_locations) 

747 ) 

748 search_location = next( 

749 location 

750 for location in root_spec.submodule_search_locations 

751 if _path_is_relative_to(package_path, location) 

752 ) 

753 else: 

754 # Pick the first path. 

755 search_location = root_spec.submodule_search_locations[0] 

756 

757 return os.path.dirname(search_location) 

758 else: 

759 # package with __init__.py 

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

761 else: 

762 # module 

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

764 

765 

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

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

768 that it would be imported from. 

769 

770 The prefix is the directory containing the standard directory 

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

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

773 ``None`` is returned. 

774 

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

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

777 package was imported from the current working directory. 

778 """ 

779 package_path = _find_package_path(import_name) 

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

781 

782 # installed to the system 

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

784 return py_prefix, package_path 

785 

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

787 

788 # installed to a virtualenv 

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

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

791 

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

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

794 return parent, package_path 

795 

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

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

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

799 

800 # something else (prefix/site-packages) 

801 return site_parent, package_path 

802 

803 # not installed 

804 return None, package_path