1from __future__ import annotations
2
3import os
4import typing as t
5from datetime import timedelta
6
7from .cli import AppGroup
8from .globals import current_app
9from .helpers import send_from_directory
10from .sansio.blueprints import Blueprint as SansioBlueprint
11from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa
12from .sansio.scaffold import _sentinel
13
14if t.TYPE_CHECKING: # pragma: no cover
15 from .wrappers import Response
16
17
18class 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)