add encoding parameter to open_resource

co-authored-by: mark <lopkophacked@protonmail.com>
This commit is contained in:
David Lord 2024-07-10 14:37:32 +00:00
parent 66af0e55ef
commit 28d5a4d718
No known key found for this signature in database
GPG key ID: 43368A7AA8CC5926
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 - Provide a configuration option to control automatic option
responses. :pr:`5496` 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 Version 3.0.3
------------- -------------

View file

@ -320,9 +320,10 @@ class Flask(App):
t.cast(str, self.static_folder), filename, max_age=max_age t.cast(str, self.static_folder), filename, max_age=max_age
) )
def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: def open_resource(
"""Open a resource file relative to :attr:`root_path` for self, resource: str, mode: str = "rb", encoding: str | None = None
reading. ) -> 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 For example, if the file ``schema.sql`` is next to the file
``app.py`` where the ``Flask`` app is defined, it can be opened ``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: with app.open_resource("schema.sql") as f:
conn.executescript(f.read()) conn.executescript(f.read())
:param resource: Path to the resource relative to :param resource: Path to the resource relative to :attr:`root_path`.
:attr:`root_path`. :param mode: Open the file in this mode. Only reading is supported,
:param mode: Open the file in this mode. Only reading is valid values are ``"r"`` (or ``"rt"``) and ``"rb"``.
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.
Note this is a duplicate of the same method in the Flask
class.
.. versionchanged:: 3.1
Added the ``encoding`` parameter.
""" """
if mode not in {"r", "rt", "rb"}: if mode not in {"r", "rt", "rb"}:
raise ValueError("Resources can only be opened for reading.") 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]: if mode == "rb":
"""Opens a resource from the application's instance folder return open(path, mode)
(:attr:`instance_path`). Otherwise works like
:meth:`open_resource`. Instance resources can also be opened for
writing.
:param resource: the name of the resource. To access resources within return open(path, mode, encoding=encoding)
subfolders use forward slashes as separator.
:param mode: resource file opening mode, default is 'rb'. 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: def create_jinja_environment(self) -> Environment:
"""Create the Jinja environment based on :attr:`jinja_options` """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 t.cast(str, self.static_folder), filename, max_age=max_age
) )
def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: def open_resource(
"""Open a resource file relative to :attr:`root_path` for self, resource: str, mode: str = "rb", encoding: str | None = "utf-8"
reading. ) -> 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 :param resource: Path to the resource relative to :attr:`root_path`.
``app.py`` where the ``Flask`` app is defined, it can be opened :param mode: Open the file in this mode. Only reading is supported,
with: valid values are ``"r"`` (or ``"rt"``) and ``"rb"``.
:param encoding: Open the file with this encoding when opening in text
.. code-block:: python mode. This is ignored when opening in binary mode.
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.
.. versionchanged:: 3.1
Added the ``encoding`` parameter.
""" """
if mode not in {"r", "rt", "rb"}: if mode not in {"r", "rt", "rb"}:
raise ValueError("Resources can only be opened for reading.") 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.data == b"Hello"
assert rv.mimetype == "text/html" 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: @pytest.mark.parametrize("mode", ("r", "rb", "rt"))
assert "<h1>Hello World!</h1>" in str(f.read()) def test_open_resource(mode):
app = flask.Flask(__name__)
@pytest.mark.parametrize("mode", ("w", "x", "a", "r+")) with app.open_resource("static/index.html", mode) as f:
def test_open_resource_exceptions(self, mode): assert "<h1>Hello World!</h1>" in str(f.read())
app = flask.Flask(__name__)
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"