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

192 statements  

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

1import os 

2import typing as t 

3from collections import defaultdict 

4from functools import update_wrapper 

5 

6from . import typing as ft 

7from .scaffold import _endpoint_from_view_func 

8from .scaffold import _sentinel 

9from .scaffold import Scaffold 

10 

11if t.TYPE_CHECKING: 

12 from .app import Flask 

13 

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

15 

16 

17class BlueprintSetupState: 

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

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

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

21 to all register callback functions. 

22 """ 

23 

24 def __init__( 

25 self, 

26 blueprint: "Blueprint", 

27 app: "Flask", 

28 options: t.Any, 

29 first_registration: bool, 

30 ) -> None: 

31 #: a reference to the current application 

32 self.app = app 

33 

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

35 self.blueprint = blueprint 

36 

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

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

39 self.options = options 

40 

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

42 #: application and not everything wants to be registered 

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

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

45 self.first_registration = first_registration 

46 

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

48 if subdomain is None: 

49 subdomain = self.blueprint.subdomain 

50 

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

52 #: otherwise. 

53 self.subdomain = subdomain 

54 

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

56 if url_prefix is None: 

57 url_prefix = self.blueprint.url_prefix 

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

59 #: blueprint. 

60 self.url_prefix = url_prefix 

61 

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

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

64 

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

66 #: URL that was defined with the blueprint. 

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

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

69 

70 def add_url_rule( 

71 self, 

72 rule: str, 

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

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

75 **options: t.Any, 

76 ) -> None: 

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

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

79 blueprint's name. 

80 """ 

81 if self.url_prefix is not None: 

82 if rule: 

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

84 else: 

85 rule = self.url_prefix 

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

87 if endpoint is None: 

88 endpoint = _endpoint_from_view_func(view_func) # type: ignore 

89 defaults = self.url_defaults 

90 if "defaults" in options: 

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

92 

93 self.app.add_url_rule( 

94 rule, 

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

96 view_func, 

97 defaults=defaults, 

98 **options, 

99 ) 

100 

101 

102class Blueprint(Scaffold): 

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

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

105 later. 

106 

107 A blueprint is an object that allows defining application functions 

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

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

110 application by recording them for later registration. 

111 

112 Decorating a function with a blueprint creates a deferred function 

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

114 when the blueprint is registered on an application. 

115 

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

117 

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

119 endpoint name. 

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

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

122 blueprint. 

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

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

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

126 by default. 

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

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

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

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

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

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

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

134 default. Blueprint templates have a lower precedence than those 

135 in the app's templates folder. 

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

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

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

139 default. 

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

141 will receive by default. 

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

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

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

145 manually instead. 

146 

147 .. versionchanged:: 1.1.0 

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

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

150 the ``flask`` command. 

151 

152 .. versionadded:: 0.7 

153 """ 

154 

155 warn_on_modifications = False 

156 _got_registered_once = False 

157 

158 #: Blueprint local JSON encoder class to use. Set to ``None`` to use 

159 #: the app's :class:`~flask.Flask.json_encoder`. 

160 json_encoder = None 

161 #: Blueprint local JSON decoder class to use. Set to ``None`` to use 

162 #: the app's :class:`~flask.Flask.json_decoder`. 

163 json_decoder = None 

164 

165 def __init__( 

166 self, 

167 name: str, 

168 import_name: str, 

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

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

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

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

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

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

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

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

177 ): 

178 super().__init__( 

179 import_name=import_name, 

180 static_folder=static_folder, 

181 static_url_path=static_url_path, 

182 template_folder=template_folder, 

183 root_path=root_path, 

184 ) 

185 

186 if "." in name: 

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

188 

189 self.name = name 

190 self.url_prefix = url_prefix 

191 self.subdomain = subdomain 

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

193 

194 if url_defaults is None: 

195 url_defaults = {} 

196 

197 self.url_values_defaults = url_defaults 

198 self.cli_group = cli_group 

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

200 

201 def _is_setup_finished(self) -> bool: 

202 return self.warn_on_modifications and self._got_registered_once 

203 

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

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

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

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

208 method. 

209 """ 

210 if self._got_registered_once and self.warn_on_modifications: 

211 from warnings import warn 

212 

213 warn( 

214 Warning( 

215 "The blueprint was already registered once but is" 

216 " getting modified now. These changes will not show" 

217 " up." 

218 ) 

219 ) 

220 self.deferred_functions.append(func) 

221 

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

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

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

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

226 function passed is not called. 

227 """ 

228 

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

230 if state.first_registration: 

231 func(state) 

232 

233 return self.record(update_wrapper(wrapper, func)) 

234 

235 def make_setup_state( 

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

237 ) -> BlueprintSetupState: 

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

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

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

241 """ 

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

243 

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

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

246 arguments passed to this method will override the defaults set 

247 on the blueprint. 

248 

249 .. versionchanged:: 2.0.1 

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

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

252 blueprint to be registered multiple times with unique names 

253 for ``url_for``. 

254 

255 .. versionadded:: 2.0 

256 """ 

257 if blueprint is self: 

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

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

260 

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

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

263 views and callbacks registered on the blueprint with the 

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

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

266 

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

268 with. 

269 :param options: Keyword arguments forwarded from 

270 :meth:`~Flask.register_blueprint`. 

271 

272 .. versionchanged:: 2.0.1 

273 Nested blueprints are registered with their dotted name. 

274 This allows different blueprints with the same name to be 

275 nested at different locations. 

276 

277 .. versionchanged:: 2.0.1 

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

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

280 blueprint to be registered multiple times with unique names 

281 for ``url_for``. 

282 

283 .. versionchanged:: 2.0.1 

284 Registering the same blueprint with the same name multiple 

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

286 """ 

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

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

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

290 

291 if name in app.blueprints: 

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

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

294 

295 raise ValueError( 

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

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

298 f" provide a unique name." 

299 ) 

300 

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

302 first_name_registration = name not in app.blueprints 

303 

304 app.blueprints[name] = self 

305 self._got_registered_once = True 

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

307 

308 if self.has_static_folder: 

309 state.add_url_rule( 

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

311 view_func=self.send_static_file, 

312 endpoint="static", 

313 ) 

314 

315 # Merge blueprint data into parent. 

316 if first_bp_registration or first_name_registration: 

317 

318 def extend(bp_dict, parent_dict): 

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

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

321 parent_dict[key].extend(values) 

322 

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

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

325 value = defaultdict( 

326 dict, 

327 { 

328 code: { 

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

330 } 

331 for code, code_values in value.items() 

332 }, 

333 ) 

334 app.error_handler_spec[key] = value 

335 

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

337 app.view_functions[endpoint] = func 

338 

339 extend(self.before_request_funcs, app.before_request_funcs) 

340 extend(self.after_request_funcs, app.after_request_funcs) 

341 extend( 

342 self.teardown_request_funcs, 

343 app.teardown_request_funcs, 

344 ) 

345 extend(self.url_default_functions, app.url_default_functions) 

346 extend(self.url_value_preprocessors, app.url_value_preprocessors) 

347 extend(self.template_context_processors, app.template_context_processors) 

348 

349 for deferred in self.deferred_functions: 

350 deferred(state) 

351 

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

353 

354 if self.cli.commands: 

355 if cli_resolved_group is None: 

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

357 elif cli_resolved_group is _sentinel: 

358 self.cli.name = name 

359 app.cli.add_command(self.cli) 

360 else: 

361 self.cli.name = cli_resolved_group 

362 app.cli.add_command(self.cli) 

363 

364 for blueprint, bp_options in self._blueprints: 

365 bp_options = bp_options.copy() 

366 bp_url_prefix = bp_options.get("url_prefix") 

367 

368 if bp_url_prefix is None: 

369 bp_url_prefix = blueprint.url_prefix 

370 

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

372 bp_options["url_prefix"] = ( 

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

374 ) 

375 elif bp_url_prefix is not None: 

376 bp_options["url_prefix"] = bp_url_prefix 

377 elif state.url_prefix is not None: 

378 bp_options["url_prefix"] = state.url_prefix 

379 

380 bp_options["name_prefix"] = name 

381 blueprint.register(app, bp_options) 

382 

383 def add_url_rule( 

384 self, 

385 rule: str, 

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

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

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

389 **options: t.Any, 

390 ) -> None: 

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

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

393 """ 

394 if endpoint and "." in endpoint: 

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

396 

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

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

399 

400 self.record( 

401 lambda s: s.add_url_rule( 

402 rule, 

403 endpoint, 

404 view_func, 

405 provide_automatic_options=provide_automatic_options, 

406 **options, 

407 ) 

408 ) 

409 

410 def app_template_filter( 

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

412 ) -> t.Callable[[ft.TemplateFilterCallable], ft.TemplateFilterCallable]: 

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

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

415 

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

417 function name will be used. 

418 """ 

419 

420 def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable: 

421 self.add_app_template_filter(f, name=name) 

422 return f 

423 

424 return decorator 

425 

426 def add_app_template_filter( 

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

428 ) -> None: 

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

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

431 like the :meth:`app_template_filter` decorator. 

432 

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

434 function name will be used. 

435 """ 

436 

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

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

439 

440 self.record_once(register_template) 

441 

442 def app_template_test( 

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

444 ) -> t.Callable[[ft.TemplateTestCallable], ft.TemplateTestCallable]: 

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

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

447 

448 .. versionadded:: 0.10 

449 

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

451 function name will be used. 

452 """ 

453 

454 def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable: 

455 self.add_app_template_test(f, name=name) 

456 return f 

457 

458 return decorator 

459 

460 def add_app_template_test( 

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

462 ) -> None: 

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

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

465 like the :meth:`app_template_test` decorator. 

466 

467 .. versionadded:: 0.10 

468 

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

470 function name will be used. 

471 """ 

472 

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

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

475 

476 self.record_once(register_template) 

477 

478 def app_template_global( 

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

480 ) -> t.Callable[[ft.TemplateGlobalCallable], ft.TemplateGlobalCallable]: 

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

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

483 

484 .. versionadded:: 0.10 

485 

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

487 function name will be used. 

488 """ 

489 

490 def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable: 

491 self.add_app_template_global(f, name=name) 

492 return f 

493 

494 return decorator 

495 

496 def add_app_template_global( 

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

498 ) -> None: 

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

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

501 like the :meth:`app_template_global` decorator. 

502 

503 .. versionadded:: 0.10 

504 

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

506 function name will be used. 

507 """ 

508 

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

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

511 

512 self.record_once(register_template) 

513 

514 def before_app_request( 

515 self, f: ft.BeforeRequestCallable 

516 ) -> ft.BeforeRequestCallable: 

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

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

519 """ 

520 self.record_once( 

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

522 ) 

523 return f 

524 

525 def before_app_first_request( 

526 self, f: ft.BeforeFirstRequestCallable 

527 ) -> ft.BeforeFirstRequestCallable: 

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

529 executed before the first request to the application. 

530 """ 

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

532 return f 

533 

534 def after_app_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable: 

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

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

537 """ 

538 self.record_once( 

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

540 ) 

541 return f 

542 

543 def teardown_app_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable: 

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

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

546 the blueprint. 

547 """ 

548 self.record_once( 

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

550 ) 

551 return f 

552 

553 def app_context_processor( 

554 self, f: ft.TemplateContextProcessorCallable 

555 ) -> ft.TemplateContextProcessorCallable: 

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

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

558 """ 

559 self.record_once( 

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

561 ) 

562 return f 

563 

564 def app_errorhandler( 

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

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

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

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

569 """ 

570 

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

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

573 return f 

574 

575 return decorator 

576 

577 def app_url_value_preprocessor( 

578 self, f: ft.URLValuePreprocessorCallable 

579 ) -> ft.URLValuePreprocessorCallable: 

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

581 self.record_once( 

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

583 ) 

584 return f 

585 

586 def app_url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable: 

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

588 self.record_once( 

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

590 ) 

591 return f