1 from __future__ import annotations 2 3 import os 4 import typing as t 5 from datetime import timedelta 6 7 from .cli import AppGroup 8 from .globals import current_app 9 from .helpers import send_from_directory 10 from .sansio.blueprints import Blueprint as SansioBlueprint 11 from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa 12 from .sansio.scaffold import _sentinel 13 14 if t.TYPE_CHECKING: # pragma: no cover 15 from .wrappers import Response 16 17 18 class Blueprint(SansioBlueprint): 19 def __init__( 20 self, 21 name: str, 22 import_name: str, 23 static_folder: str | os.PathLike[str] | None = None, 24 static_url_path: str | None = None, 25 template_folder: str | os.PathLike[str] | None = None, 26 url_prefix: str | None = None, 27 subdomain: str | None = None, 28 url_defaults: dict[str, t.Any] | None = None, 29 root_path: str | None = None, 30 cli_group: str | None = _sentinel, # type: ignore 31 ) -> None: 32 super().__init__( 33 name, 34 import_name, 35 static_folder, 36 static_url_path, 37 template_folder, 38 url_prefix, 39 subdomain, 40 url_defaults, 41 root_path, 42 cli_group, 43 ) 44 45 #: The Click command group for registering CLI commands for this 46 #: object. The commands are available from the ``flask`` command 47 #: once the application has been discovered and blueprints have 48 #: been registered. 49 self.cli = AppGroup() 50 51 # Set the name of the Click group in case someone wants to add 52 # the app's commands to another CLI tool. 53 self.cli.name = self.name 54 55 def get_send_file_max_age(self, filename: str | None) -> int | None: 56 """Used by :func:`send_file` to determine the ``max_age`` cache 57 value for a given file path if it wasn't passed. 58 59 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from 60 the configuration of :data:`~flask.current_app`. This defaults 61 to ``None``, which tells the browser to use conditional requests 62 instead of a timed cache, which is usually preferable. 63 64 Note this is a duplicate of the same method in the Flask 65 class. 66 67 .. versionchanged:: 2.0 68 The default configuration is ``None`` instead of 12 hours. 69 70 .. versionadded:: 0.9 71 """ 72 value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] 73 74 if value is None: 75 return None 76 77 if isinstance(value, timedelta): 78 return int(value.total_seconds()) 79 80 return value # type: ignore[no-any-return] 81 82 def send_static_file(self, filename: str) -> Response: 83 """The view function used to serve files from 84 :attr:`static_folder`. A route is automatically registered for 85 this view at :attr:`static_url_path` if :attr:`static_folder` is 86 set. 87 88 Note this is a duplicate of the same method in the Flask 89 class. 90 91 .. versionadded:: 0.5 92 93 """ 94 if not self.has_static_folder: 95 raise RuntimeError("'static_folder' must be set to serve static_files.") 96 97 # send_file only knows to call get_send_file_max_age on the app, 98 # call it here so it works for blueprints too. 99 max_age = self.get_send_file_max_age(filename) 100 return send_from_directory( 101 t.cast(str, self.static_folder), filename, max_age=max_age 102 ) 103 104 def open_resource( 105 self, resource: str, mode: str = "rb", encoding: str | None = "utf-8" 106 ) -> t.IO[t.AnyStr]: 107 """Open a resource file relative to :attr:`root_path` for reading. The 108 blueprint-relative equivalent of the app's :meth:`~.Flask.open_resource` 109 method. 110 111 :param resource: Path to the resource relative to :attr:`root_path`. 112 :param mode: Open the file in this mode. Only reading is supported, 113 valid values are ``"r"`` (or ``"rt"``) and ``"rb"``. 114 :param encoding: Open the file with this encoding when opening in text 115 mode. This is ignored when opening in binary mode. 116 117 .. versionchanged:: 3.1 118 Added the ``encoding`` parameter. 119 """ 120 if mode not in {"r", "rt", "rb"}: 121 raise ValueError("Resources can only be opened for reading.") 122 123 path = os.path.join(self.root_path, resource) 124 125 if mode == "rb": 126 return open(path, mode) # pyright: ignore 127 128 return open(path, mode, encoding=encoding) 129