Add encoding parameter to open resource (#5526)

This commit is contained in:
David Lord 2024-07-10 19:16:46 -07:00 committed by GitHub
commit a8956feba1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 79 additions and 51 deletions

View file

@ -3,7 +3,9 @@ Version 3.1.0
- Provide a configuration option to control automatic option
responses. :pr:`5496`
- ``Flask.open_resource``/``open_instance_resource`` and
``Blueprint.open_resource`` take an ``encoding`` parameter to use when
opening in text mode. It defaults to ``utf-8``. :issue:`5504`
Version 3.0.3
-------------

View file

@ -320,9 +320,10 @@ class Flask(App):
t.cast(str, self.static_folder), filename, max_age=max_age
)
def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
"""Open a resource file relative to :attr:`root_path` for
reading.
def open_resource(
self, resource: str, mode: str = "rb", encoding: str | None = None
) -> t.IO[t.AnyStr]:
"""Open a resource file relative to :attr:`root_path` for reading.
For example, if the file ``schema.sql`` is next to the file
``app.py`` where the ``Flask`` app is defined, it can be opened
@ -333,31 +334,46 @@ class Flask(App):
with app.open_resource("schema.sql") as f:
conn.executescript(f.read())
:param resource: Path to the resource relative to
:attr:`root_path`.
:param mode: Open the file in this mode. Only reading is
supported, valid values are "r" (or "rt") and "rb".
Note this is a duplicate of the same method in the Flask
class.
:param resource: Path to the resource relative to :attr:`root_path`.
:param mode: Open the file in this mode. Only reading is supported,
valid values are ``"r"`` (or ``"rt"``) and ``"rb"``.
:param encoding: Open the file with this encoding when opening in text
mode. This is ignored when opening in binary mode.
.. versionchanged:: 3.1
Added the ``encoding`` parameter.
"""
if mode not in {"r", "rt", "rb"}:
raise ValueError("Resources can only be opened for reading.")
return open(os.path.join(self.root_path, resource), mode)
path = os.path.join(self.root_path, resource)
def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
"""Opens a resource from the application's instance folder
(:attr:`instance_path`). Otherwise works like
:meth:`open_resource`. Instance resources can also be opened for
writing.
if mode == "rb":
return open(path, mode)
:param resource: the name of the resource. To access resources within
subfolders use forward slashes as separator.
:param mode: resource file opening mode, default is 'rb'.
return open(path, mode, encoding=encoding)
def open_instance_resource(
self, resource: str, mode: str = "rb", encoding: str | None = "utf-8"
) -> t.IO[t.AnyStr]:
"""Open a resource file relative to the application's instance folder
:attr:`instance_path`. Unlike :meth:`open_resource`, files in the
instance folder can be opened for writing.
:param resource: Path to the resource relative to :attr:`instance_path`.
:param mode: Open the file in this mode.
:param encoding: Open the file with this encoding when opening in text
mode. This is ignored when opening in binary mode.
.. versionchanged:: 3.1
Added the ``encoding`` parameter.
"""
return open(os.path.join(self.instance_path, resource), mode)
path = os.path.join(self.instance_path, resource)
if "b" in mode:
return open(path, mode)
return open(path, mode, encoding=encoding)
def create_jinja_environment(self) -> Environment:
"""Create the Jinja environment based on :attr:`jinja_options`

View file

@ -101,29 +101,28 @@ class Blueprint(SansioBlueprint):
t.cast(str, self.static_folder), filename, max_age=max_age
)
def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
"""Open a resource file relative to :attr:`root_path` for
reading.
def open_resource(
self, resource: str, mode: str = "rb", encoding: str | None = "utf-8"
) -> t.IO[t.AnyStr]:
"""Open a resource file relative to :attr:`root_path` for reading. The
blueprint-relative equivalent of the app's :meth:`~.Flask.open_resource`
method.
For example, if the file ``schema.sql`` is next to the file
``app.py`` where the ``Flask`` app is defined, it can be opened
with:
.. code-block:: python
with app.open_resource("schema.sql") as f:
conn.executescript(f.read())
:param resource: Path to the resource relative to
:attr:`root_path`.
:param mode: Open the file in this mode. Only reading is
supported, valid values are "r" (or "rt") and "rb".
Note this is a duplicate of the same method in the Flask
class.
:param resource: Path to the resource relative to :attr:`root_path`.
:param mode: Open the file in this mode. Only reading is supported,
valid values are ``"r"`` (or ``"rt"``) and ``"rb"``.
:param encoding: Open the file with this encoding when opening in text
mode. This is ignored when opening in binary mode.
.. versionchanged:: 3.1
Added the ``encoding`` parameter.
"""
if mode not in {"r", "rt", "rb"}:
raise ValueError("Resources can only be opened for reading.")
return open(os.path.join(self.root_path, resource), mode)
path = os.path.join(self.root_path, resource)
if mode == "rb":
return open(path, mode)
return open(path, mode, encoding=encoding)

View file

@ -334,16 +334,27 @@ class TestHelpers:
assert rv.data == b"Hello"
assert rv.mimetype == "text/html"
@pytest.mark.parametrize("mode", ("r", "rb", "rt"))
def test_open_resource(self, mode):
app = flask.Flask(__name__)
with app.open_resource("static/index.html", mode) as f:
assert "<h1>Hello World!</h1>" in str(f.read())
@pytest.mark.parametrize("mode", ("r", "rb", "rt"))
def test_open_resource(mode):
app = flask.Flask(__name__)
@pytest.mark.parametrize("mode", ("w", "x", "a", "r+"))
def test_open_resource_exceptions(self, mode):
app = flask.Flask(__name__)
with app.open_resource("static/index.html", mode) as f:
assert "<h1>Hello World!</h1>" in str(f.read())
with pytest.raises(ValueError):
app.open_resource("static/index.html", mode)
@pytest.mark.parametrize("mode", ("w", "x", "a", "r+"))
def test_open_resource_exceptions(mode):
app = flask.Flask(__name__)
with pytest.raises(ValueError):
app.open_resource("static/index.html", mode)
@pytest.mark.parametrize("encoding", ("utf-8", "utf-16-le"))
def test_open_resource_with_encoding(tmp_path, encoding):
app = flask.Flask(__name__, root_path=os.fspath(tmp_path))
(tmp_path / "test").write_text("test", encoding=encoding)
with app.open_resource("test", mode="rt", encoding=encoding) as f:
assert f.read() == "test"