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

241 statements  

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

1import json 

2import os 

3import typing as t 

4from collections import defaultdict 

5from functools import update_wrapper 

6 

7from . import typing as ft 

8from .scaffold import _endpoint_from_view_func 

9from .scaffold import _sentinel 

10from .scaffold import Scaffold 

11from .scaffold import setupmethod 

12 

13if t.TYPE_CHECKING: # pragma: no cover 

14 from .app import Flask 

15 

16DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] 

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

18T_before_first_request = t.TypeVar( 

19 "T_before_first_request", bound=ft.BeforeFirstRequestCallable 

20) 

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

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

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

24T_template_context_processor = t.TypeVar( 

25 "T_template_context_processor", bound=ft.TemplateContextProcessorCallable 

26) 

27T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) 

28T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) 

29T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) 

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

31T_url_value_preprocessor = t.TypeVar( 

32 "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable 

33) 

34 

35 

36class BlueprintSetupState: 

37 """Temporary holder object for registering a blueprint with the 

38 application. An instance of this class is created by the 

39 :meth:`~flask.Blueprint.make_setup_state` method and later passed 

40 to all register callback functions. 

41 """ 

42 

43 def __init__( 

44 self, 

45 blueprint: "Blueprint", 

46 app: "Flask", 

47 options: t.Any, 

48 first_registration: bool, 

49 ) -> None: 

50 #: a reference to the current application 

51 self.app = app 

52 

53 #: a reference to the blueprint that created this setup state. 

54 self.blueprint = blueprint 

55 

56 #: a dictionary with all options that were passed to the 

57 #: :meth:`~flask.Flask.register_blueprint` method. 

58 self.options = options 

59 

60 #: as blueprints can be registered multiple times with the 

61 #: application and not everything wants to be registered 

62 #: multiple times on it, this attribute can be used to figure 

63 #: out if the blueprint was registered in the past already. 

64 self.first_registration = first_registration 

65 

66 subdomain = self.options.get("subdomain") 

67 if subdomain is None: 

68 subdomain = self.blueprint.subdomain 

69 

70 #: The subdomain that the blueprint should be active for, ``None`` 

71 #: otherwise. 

72 self.subdomain = subdomain 

73 

74 url_prefix = self.options.get("url_prefix") 

75 if url_prefix is None: 

76 url_prefix = self.blueprint.url_prefix 

77 #: The prefix that should be used for all URLs defined on the 

78 #: blueprint. 

79 self.url_prefix = url_prefix 

80 

81 self.name = self.options.get("name", blueprint.name) 

82 self.name_prefix = self.options.get("name_prefix", "") 

83 

84 #: A dictionary with URL defaults that is added to each and every 

85 #: URL that was defined with the blueprint. 

86 self.url_defaults = dict(self.blueprint.url_values_defaults) 

87 self.url_defaults.update(self.options.get("url_defaults", ())) 

88 

89 def add_url_rule( 

90 self, 

91 rule: str, 

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

93 view_func: t.Optional[t.Callable] = None, 

94 **options: t.Any, 

95 ) -> None: 

96 """A helper method to register a rule (and optionally a view function) 

97 to the application. The endpoint is automatically prefixed with the 

98 blueprint's name. 

99 """ 

100 if self.url_prefix is not None: 

101 if rule: 

102 rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) 

103 else: 

104 rule = self.url_prefix 

105 options.setdefault("subdomain", self.subdomain) 

106 if endpoint is None: 

107 endpoint = _endpoint_from_view_func(view_func) # type: ignore 

108 defaults = self.url_defaults 

109 if "defaults" in options: 

110 defaults = dict(defaults, **options.pop("defaults")) 

111 

112 self.app.add_url_rule( 

113 rule, 

114 f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), 

115 view_func, 

116 defaults=defaults, 

117 **options, 

118 ) 

119 

120 

121class Blueprint(Scaffold): 

122 """Represents a blueprint, a collection of routes and other 

123 app-related functions that can be registered on a real application 

124 later. 

125 

126 A blueprint is an object that allows defining application functions 

127 without requiring an application object ahead of time. It uses the 

128 same decorators as :class:`~flask.Flask`, but defers the need for an 

129 application by recording them for later registration. 

130 

131 Decorating a function with a blueprint creates a deferred function 

132 that is called with :class:`~flask.blueprints.BlueprintSetupState` 

133 when the blueprint is registered on an application. 

134 

135 See :doc:`/blueprints` for more information. 

136 

137 :param name: The name of the blueprint. Will be prepended to each 

138 endpoint name. 

139 :param import_name: The name of the blueprint package, usually 

140 ``__name__``. This helps locate the ``root_path`` for the 

141 blueprint. 

142 :param static_folder: A folder with static files that should be 

143 served by the blueprint's static route. The path is relative to 

144 the blueprint's root path. Blueprint static files are disabled 

145 by default. 

146 :param static_url_path: The url to serve static files from. 

147 Defaults to ``static_folder``. If the blueprint does not have 

148 a ``url_prefix``, the app's static route will take precedence, 

149 and the blueprint's static files won't be accessible. 

150 :param template_folder: A folder with templates that should be added 

151 to the app's template search path. The path is relative to the 

152 blueprint's root path. Blueprint templates are disabled by 

153 default. Blueprint templates have a lower precedence than those 

154 in the app's templates folder. 

155 :param url_prefix: A path to prepend to all of the blueprint's URLs, 

156 to make them distinct from the rest of the app's routes. 

157 :param subdomain: A subdomain that blueprint routes will match on by 

158 default. 

159 :param url_defaults: A dict of default values that blueprint routes 

160 will receive by default. 

161 :param root_path: By default, the blueprint will automatically set 

162 this based on ``import_name``. In certain situations this 

163 automatic detection can fail, so the path can be specified 

164 manually instead. 

165 

166 .. versionchanged:: 1.1.0 

167 Blueprints have a ``cli`` group to register nested CLI commands. 

168 The ``cli_group`` parameter controls the name of the group under 

169 the ``flask`` command. 

170 

171 .. versionadded:: 0.7 

172 """ 

173 

174 _got_registered_once = False 

175 

176 _json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None 

177 _json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None 

178 

179 @property # type: ignore[override] 

180 def json_encoder( # type: ignore[override] 

181 self, 

182 ) -> t.Union[t.Type[json.JSONEncoder], None]: 

183 """Blueprint-local JSON encoder class to use. Set to ``None`` to use the app's. 

184 

185 .. deprecated:: 2.2 

186 Will be removed in Flask 2.3. Customize 

187 :attr:`json_provider_class` instead. 

188 

189 .. versionadded:: 0.10 

190 """ 

191 import warnings 

192 

193 warnings.warn( 

194 "'bp.json_encoder' is deprecated and will be removed in Flask 2.3." 

195 " Customize 'app.json_provider_class' or 'app.json' instead.", 

196 DeprecationWarning, 

197 stacklevel=2, 

198 ) 

199 return self._json_encoder 

200 

201 @json_encoder.setter 

202 def json_encoder(self, value: t.Union[t.Type[json.JSONEncoder], None]) -> None: 

203 import warnings 

204 

205 warnings.warn( 

206 "'bp.json_encoder' is deprecated and will be removed in Flask 2.3." 

207 " Customize 'app.json_provider_class' or 'app.json' instead.", 

208 DeprecationWarning, 

209 stacklevel=2, 

210 ) 

211 self._json_encoder = value 

212 

213 @property # type: ignore[override] 

214 def json_decoder( # type: ignore[override] 

215 self, 

216 ) -> t.Union[t.Type[json.JSONDecoder], None]: 

217 """Blueprint-local JSON decoder class to use. Set to ``None`` to use the app's. 

218 

219 .. deprecated:: 2.2 

220 Will be removed in Flask 2.3. Customize 

221 :attr:`json_provider_class` instead. 

222 

223 .. versionadded:: 0.10 

224 """ 

225 import warnings 

226 

227 warnings.warn( 

228 "'bp.json_decoder' is deprecated and will be removed in Flask 2.3." 

229 " Customize 'app.json_provider_class' or 'app.json' instead.", 

230 DeprecationWarning, 

231 stacklevel=2, 

232 ) 

233 return self._json_decoder 

234 

235 @json_decoder.setter 

236 def json_decoder(self, value: t.Union[t.Type[json.JSONDecoder], None]) -> None: 

237 import warnings 

238 

239 warnings.warn( 

240 "'bp.json_decoder' is deprecated and will be removed in Flask 2.3." 

241 " Customize 'app.json_provider_class' or 'app.json' instead.", 

242 DeprecationWarning, 

243 stacklevel=2, 

244 ) 

245 self._json_decoder = value 

246 

247 def __init__( 

248 self, 

249 name: str, 

250 import_name: str, 

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

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

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

254 url_prefix: t.Optional[str] = None, 

255 subdomain: t.Optional[str] = None, 

256 url_defaults: t.Optional[dict] = None, 

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

258 cli_group: t.Optional[str] = _sentinel, # type: ignore 

259 ): 

260 super().__init__( 

261 import_name=import_name, 

262 static_folder=static_folder, 

263 static_url_path=static_url_path, 

264 template_folder=template_folder, 

265 root_path=root_path, 

266 ) 

267 

268 if "." in name: 

269 raise ValueError("'name' may not contain a dot '.' character.") 

270 

271 self.name = name 

272 self.url_prefix = url_prefix 

273 self.subdomain = subdomain 

274 self.deferred_functions: t.List[DeferredSetupFunction] = [] 

275 

276 if url_defaults is None: 

277 url_defaults = {} 

278 

279 self.url_values_defaults = url_defaults 

280 self.cli_group = cli_group 

281 self._blueprints: t.List[t.Tuple["Blueprint", dict]] = [] 

282 

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

284 if self._got_registered_once: 

285 import warnings 

286 

287 warnings.warn( 

288 f"The setup method '{f_name}' can no longer be called on" 

289 f" the blueprint '{self.name}'. It has already been" 

290 " registered at least once, any changes will not be" 

291 " applied consistently.\n" 

292 "Make sure all imports, decorators, functions, etc." 

293 " needed to set up the blueprint are done before" 

294 " registering it.\n" 

295 "This warning will become an exception in Flask 2.3.", 

296 UserWarning, 

297 stacklevel=3, 

298 ) 

299 

300 @setupmethod 

301 def record(self, func: t.Callable) -> None: 

302 """Registers a function that is called when the blueprint is 

303 registered on the application. This function is called with the 

304 state as argument as returned by the :meth:`make_setup_state` 

305 method. 

306 """ 

307 self.deferred_functions.append(func) 

308 

309 @setupmethod 

310 def record_once(self, func: t.Callable) -> None: 

311 """Works like :meth:`record` but wraps the function in another 

312 function that will ensure the function is only called once. If the 

313 blueprint is registered a second time on the application, the 

314 function passed is not called. 

315 """ 

316 

317 def wrapper(state: BlueprintSetupState) -> None: 

318 if state.first_registration: 

319 func(state) 

320 

321 self.record(update_wrapper(wrapper, func)) 

322 

323 def make_setup_state( 

324 self, app: "Flask", options: dict, first_registration: bool = False 

325 ) -> BlueprintSetupState: 

326 """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` 

327 object that is later passed to the register callback functions. 

328 Subclasses can override this to return a subclass of the setup state. 

329 """ 

330 return BlueprintSetupState(self, app, options, first_registration) 

331 

332 @setupmethod 

333 def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: 

334 """Register a :class:`~flask.Blueprint` on this blueprint. Keyword 

335 arguments passed to this method will override the defaults set 

336 on the blueprint. 

337 

338 .. versionchanged:: 2.0.1 

339 The ``name`` option can be used to change the (pre-dotted) 

340 name the blueprint is registered with. This allows the same 

341 blueprint to be registered multiple times with unique names 

342 for ``url_for``. 

343 

344 .. versionadded:: 2.0 

345 """ 

346 if blueprint is self: 

347 raise ValueError("Cannot register a blueprint on itself") 

348 self._blueprints.append((blueprint, options)) 

349 

350 def register(self, app: "Flask", options: dict) -> None: 

351 """Called by :meth:`Flask.register_blueprint` to register all 

352 views and callbacks registered on the blueprint with the 

353 application. Creates a :class:`.BlueprintSetupState` and calls 

354 each :meth:`record` callback with it. 

355 

356 :param app: The application this blueprint is being registered 

357 with. 

358 :param options: Keyword arguments forwarded from 

359 :meth:`~Flask.register_blueprint`. 

360 

361 .. versionchanged:: 2.0.1 

362 Nested blueprints are registered with their dotted name. 

363 This allows different blueprints with the same name to be 

364 nested at different locations. 

365 

366 .. versionchanged:: 2.0.1 

367 The ``name`` option can be used to change the (pre-dotted) 

368 name the blueprint is registered with. This allows the same 

369 blueprint to be registered multiple times with unique names 

370 for ``url_for``. 

371 

372 .. versionchanged:: 2.0.1 

373 Registering the same blueprint with the same name multiple 

374 times is deprecated and will become an error in Flask 2.1. 

375 """ 

376 name_prefix = options.get("name_prefix", "") 

377 self_name = options.get("name", self.name) 

378 name = f"{name_prefix}.{self_name}".lstrip(".") 

379 

380 if name in app.blueprints: 

381 bp_desc = "this" if app.blueprints[name] is self else "a different" 

382 existing_at = f" '{name}'" if self_name != name else "" 

383 

384 raise ValueError( 

385 f"The name '{self_name}' is already registered for" 

386 f" {bp_desc} blueprint{existing_at}. Use 'name=' to" 

387 f" provide a unique name." 

388 ) 

389 

390 first_bp_registration = not any(bp is self for bp in app.blueprints.values()) 

391 first_name_registration = name not in app.blueprints 

392 

393 app.blueprints[name] = self 

394 self._got_registered_once = True 

395 state = self.make_setup_state(app, options, first_bp_registration) 

396 

397 if self.has_static_folder: 

398 state.add_url_rule( 

399 f"{self.static_url_path}/<path:filename>", 

400 view_func=self.send_static_file, 

401 endpoint="static", 

402 ) 

403 

404 # Merge blueprint data into parent. 

405 if first_bp_registration or first_name_registration: 

406 

407 def extend(bp_dict, parent_dict): 

408 for key, values in bp_dict.items(): 

409 key = name if key is None else f"{name}.{key}" 

410 parent_dict[key].extend(values) 

411 

412 for key, value in self.error_handler_spec.items(): 

413 key = name if key is None else f"{name}.{key}" 

414 value = defaultdict( 

415 dict, 

416 { 

417 code: { 

418 exc_class: func for exc_class, func in code_values.items() 

419 } 

420 for code, code_values in value.items() 

421 }, 

422 ) 

423 app.error_handler_spec[key] = value 

424 

425 for endpoint, func in self.view_functions.items(): 

426 app.view_functions[endpoint] = func 

427 

428 extend(self.before_request_funcs, app.before_request_funcs) 

429 extend(self.after_request_funcs, app.after_request_funcs) 

430 extend( 

431 self.teardown_request_funcs, 

432 app.teardown_request_funcs, 

433 ) 

434 extend(self.url_default_functions, app.url_default_functions) 

435 extend(self.url_value_preprocessors, app.url_value_preprocessors) 

436 extend(self.template_context_processors, app.template_context_processors) 

437 

438 for deferred in self.deferred_functions: 

439 deferred(state) 

440 

441 cli_resolved_group = options.get("cli_group", self.cli_group) 

442 

443 if self.cli.commands: 

444 if cli_resolved_group is None: 

445 app.cli.commands.update(self.cli.commands) 

446 elif cli_resolved_group is _sentinel: 

447 self.cli.name = name 

448 app.cli.add_command(self.cli) 

449 else: 

450 self.cli.name = cli_resolved_group 

451 app.cli.add_command(self.cli) 

452 

453 for blueprint, bp_options in self._blueprints: 

454 bp_options = bp_options.copy() 

455 bp_url_prefix = bp_options.get("url_prefix") 

456 

457 if bp_url_prefix is None: 

458 bp_url_prefix = blueprint.url_prefix 

459 

460 if state.url_prefix is not None and bp_url_prefix is not None: 

461 bp_options["url_prefix"] = ( 

462 state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") 

463 ) 

464 elif bp_url_prefix is not None: 

465 bp_options["url_prefix"] = bp_url_prefix 

466 elif state.url_prefix is not None: 

467 bp_options["url_prefix"] = state.url_prefix 

468 

469 bp_options["name_prefix"] = name 

470 blueprint.register(app, bp_options) 

471 

472 @setupmethod 

473 def add_url_rule( 

474 self, 

475 rule: str, 

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

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

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

479 **options: t.Any, 

480 ) -> None: 

481 """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for 

482 the :func:`url_for` function is prefixed with the name of the blueprint. 

483 """ 

484 if endpoint and "." in endpoint: 

485 raise ValueError("'endpoint' may not contain a dot '.' character.") 

486 

487 if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: 

488 raise ValueError("'view_func' name may not contain a dot '.' character.") 

489 

490 self.record( 

491 lambda s: s.add_url_rule( 

492 rule, 

493 endpoint, 

494 view_func, 

495 provide_automatic_options=provide_automatic_options, 

496 **options, 

497 ) 

498 ) 

499 

500 @setupmethod 

501 def app_template_filter( 

502 self, name: t.Optional[str] = None 

503 ) -> t.Callable[[T_template_filter], T_template_filter]: 

504 """Register a custom template filter, available application wide. Like 

505 :meth:`Flask.template_filter` but for a blueprint. 

506 

507 :param name: the optional name of the filter, otherwise the 

508 function name will be used. 

509 """ 

510 

511 def decorator(f: T_template_filter) -> T_template_filter: 

512 self.add_app_template_filter(f, name=name) 

513 return f 

514 

515 return decorator 

516 

517 @setupmethod 

518 def add_app_template_filter( 

519 self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None 

520 ) -> None: 

521 """Register a custom template filter, available application wide. Like 

522 :meth:`Flask.add_template_filter` but for a blueprint. Works exactly 

523 like the :meth:`app_template_filter` decorator. 

524 

525 :param name: the optional name of the filter, otherwise the 

526 function name will be used. 

527 """ 

528 

529 def register_template(state: BlueprintSetupState) -> None: 

530 state.app.jinja_env.filters[name or f.__name__] = f 

531 

532 self.record_once(register_template) 

533 

534 @setupmethod 

535 def app_template_test( 

536 self, name: t.Optional[str] = None 

537 ) -> t.Callable[[T_template_test], T_template_test]: 

538 """Register a custom template test, available application wide. Like 

539 :meth:`Flask.template_test` but for a blueprint. 

540 

541 .. versionadded:: 0.10 

542 

543 :param name: the optional name of the test, otherwise the 

544 function name will be used. 

545 """ 

546 

547 def decorator(f: T_template_test) -> T_template_test: 

548 self.add_app_template_test(f, name=name) 

549 return f 

550 

551 return decorator 

552 

553 @setupmethod 

554 def add_app_template_test( 

555 self, f: ft.TemplateTestCallable, name: t.Optional[str] = None 

556 ) -> None: 

557 """Register a custom template test, available application wide. Like 

558 :meth:`Flask.add_template_test` but for a blueprint. Works exactly 

559 like the :meth:`app_template_test` decorator. 

560 

561 .. versionadded:: 0.10 

562 

563 :param name: the optional name of the test, otherwise the 

564 function name will be used. 

565 """ 

566 

567 def register_template(state: BlueprintSetupState) -> None: 

568 state.app.jinja_env.tests[name or f.__name__] = f 

569 

570 self.record_once(register_template) 

571 

572 @setupmethod 

573 def app_template_global( 

574 self, name: t.Optional[str] = None 

575 ) -> t.Callable[[T_template_global], T_template_global]: 

576 """Register a custom template global, available application wide. Like 

577 :meth:`Flask.template_global` but for a blueprint. 

578 

579 .. versionadded:: 0.10 

580 

581 :param name: the optional name of the global, otherwise the 

582 function name will be used. 

583 """ 

584 

585 def decorator(f: T_template_global) -> T_template_global: 

586 self.add_app_template_global(f, name=name) 

587 return f 

588 

589 return decorator 

590 

591 @setupmethod 

592 def add_app_template_global( 

593 self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None 

594 ) -> None: 

595 """Register a custom template global, available application wide. Like 

596 :meth:`Flask.add_template_global` but for a blueprint. Works exactly 

597 like the :meth:`app_template_global` decorator. 

598 

599 .. versionadded:: 0.10 

600 

601 :param name: the optional name of the global, otherwise the 

602 function name will be used. 

603 """ 

604 

605 def register_template(state: BlueprintSetupState) -> None: 

606 state.app.jinja_env.globals[name or f.__name__] = f 

607 

608 self.record_once(register_template) 

609 

610 @setupmethod 

611 def before_app_request(self, f: T_before_request) -> T_before_request: 

612 """Like :meth:`Flask.before_request`. Such a function is executed 

613 before each request, even if outside of a blueprint. 

614 """ 

615 self.record_once( 

616 lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) 

617 ) 

618 return f 

619 

620 @setupmethod 

621 def before_app_first_request( 

622 self, f: T_before_first_request 

623 ) -> T_before_first_request: 

624 """Like :meth:`Flask.before_first_request`. Such a function is 

625 executed before the first request to the application. 

626 

627 .. deprecated:: 2.2 

628 Will be removed in Flask 2.3. Run setup code when creating 

629 the application instead. 

630 """ 

631 import warnings 

632 

633 warnings.warn( 

634 "'before_app_first_request' is deprecated and will be" 

635 " removed in Flask 2.3. Use 'record_once' instead to run" 

636 " setup code when registering the blueprint.", 

637 DeprecationWarning, 

638 stacklevel=2, 

639 ) 

640 self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) 

641 return f 

642 

643 @setupmethod 

644 def after_app_request(self, f: T_after_request) -> T_after_request: 

645 """Like :meth:`Flask.after_request` but for a blueprint. Such a function 

646 is executed after each request, even if outside of the blueprint. 

647 """ 

648 self.record_once( 

649 lambda s: s.app.after_request_funcs.setdefault(None, []).append(f) 

650 ) 

651 return f 

652 

653 @setupmethod 

654 def teardown_app_request(self, f: T_teardown) -> T_teardown: 

655 """Like :meth:`Flask.teardown_request` but for a blueprint. Such a 

656 function is executed when tearing down each request, even if outside of 

657 the blueprint. 

658 """ 

659 self.record_once( 

660 lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f) 

661 ) 

662 return f 

663 

664 @setupmethod 

665 def app_context_processor( 

666 self, f: T_template_context_processor 

667 ) -> T_template_context_processor: 

668 """Like :meth:`Flask.context_processor` but for a blueprint. Such a 

669 function is executed each request, even if outside of the blueprint. 

670 """ 

671 self.record_once( 

672 lambda s: s.app.template_context_processors.setdefault(None, []).append(f) 

673 ) 

674 return f 

675 

676 @setupmethod 

677 def app_errorhandler( 

678 self, code: t.Union[t.Type[Exception], int] 

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

680 """Like :meth:`Flask.errorhandler` but for a blueprint. This 

681 handler is used for all requests, even if outside of the blueprint. 

682 """ 

683 

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

685 self.record_once(lambda s: s.app.errorhandler(code)(f)) 

686 return f 

687 

688 return decorator 

689 

690 @setupmethod 

691 def app_url_value_preprocessor( 

692 self, f: T_url_value_preprocessor 

693 ) -> T_url_value_preprocessor: 

694 """Same as :meth:`url_value_preprocessor` but application wide.""" 

695 self.record_once( 

696 lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) 

697 ) 

698 return f 

699 

700 @setupmethod 

701 def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults: 

702 """Same as :meth:`url_defaults` but application wide.""" 

703 self.record_once( 

704 lambda s: s.app.url_default_functions.setdefault(None, []).append(f) 

705 ) 

706 return f