blueprints.py 4.4 KB

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