diff --git a/CHANGES.rst b/CHANGES.rst
index 64d5770c..049c7f6f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -21,6 +21,8 @@ Unreleased
corresponding ``json.JSONEncoder`` and ``JSONDecoder`` classes, are removed.
- The ``json.htmlsafe_dumps`` and ``htmlsafe_dump`` functions are removed.
+- Importing ``escape`` and ``Markup`` from ``flask`` is deprecated. Import them
+ directly from ``markupsafe`` instead. :pr:`4996`
- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``.
:pr:`4947`
- Ensure subdomains are applied with nested blueprints. :issue:`4834`
diff --git a/docs/api.rst b/docs/api.rst
index bf37700f..8729f6cd 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -217,10 +217,6 @@ Useful Functions and Classes
.. autofunction:: send_from_directory
-.. autofunction:: escape
-
-.. autoclass:: Markup
- :members: escape, unescape, striptags
Message Flashing
----------------
diff --git a/docs/security.rst b/docs/security.rst
index 777e5112..3992e8da 100644
--- a/docs/security.rst
+++ b/docs/security.rst
@@ -23,7 +23,7 @@ in templates, but there are still other places where you have to be
careful:
- generating HTML without the help of Jinja2
-- calling :class:`~flask.Markup` on data submitted by users
+- calling :class:`~markupsafe.Markup` on data submitted by users
- sending out HTML from uploaded files, never do that, use the
``Content-Disposition: attachment`` header to prevent that problem.
- sending out textfiles from uploaded files. Some browsers are using
diff --git a/docs/templating.rst b/docs/templating.rst
index f497de73..23cfee4c 100644
--- a/docs/templating.rst
+++ b/docs/templating.rst
@@ -115,7 +115,7 @@ markdown to HTML converter.
There are three ways to accomplish that:
-- In the Python code, wrap the HTML string in a :class:`~flask.Markup`
+- In the Python code, wrap the HTML string in a :class:`~markupsafe.Markup`
object before passing it to the template. This is in general the
recommended way.
- Inside the template, use the ``|safe`` filter to explicitly mark a
diff --git a/src/flask/__init__.py b/src/flask/__init__.py
index cc953ab1..2361bdb4 100644
--- a/src/flask/__init__.py
+++ b/src/flask/__init__.py
@@ -1,6 +1,3 @@
-from markupsafe import escape
-from markupsafe import Markup
-
from . import json as json
from .app import Flask as Flask
from .app import Request as Request
@@ -68,4 +65,28 @@ def __getattr__(name):
)
return __request_ctx_stack
+ if name == "escape":
+ import warnings
+ from markupsafe import escape
+
+ warnings.warn(
+ "'flask.escape' is deprecated and will be removed in Flask 2.4. Import"
+ " 'markupsafe.escape' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return escape
+
+ if name == "escape":
+ import warnings
+ from markupsafe import Markup
+
+ warnings.warn(
+ "'flask.Markup' is deprecated and will be removed in Flask 2.4. Import"
+ " 'markupsafe.Markup' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return Markup
+
raise AttributeError(name)
diff --git a/tests/test_basic.py b/tests/test_basic.py
index 48d41b1e..9c9d83e7 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -9,6 +9,7 @@ from platform import python_implementation
import pytest
import werkzeug.serving
+from markupsafe import Markup
from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import Forbidden
from werkzeug.exceptions import NotFound
@@ -472,7 +473,7 @@ def test_session_special_types(app, client):
def dump_session_contents():
flask.session["t"] = (1, 2, 3)
flask.session["b"] = b"\xff"
- flask.session["m"] = flask.Markup("")
+ flask.session["m"] = Markup("")
flask.session["u"] = the_uuid
flask.session["d"] = now
flask.session["t_tag"] = {" t": "not-a-tuple"}
@@ -486,8 +487,8 @@ def test_session_special_types(app, client):
assert s["t"] == (1, 2, 3)
assert type(s["b"]) == bytes
assert s["b"] == b"\xff"
- assert type(s["m"]) == flask.Markup
- assert s["m"] == flask.Markup("")
+ assert type(s["m"]) == Markup
+ assert s["m"] == Markup("")
assert s["u"] == the_uuid
assert s["d"] == now
assert s["t_tag"] == {" t": "not-a-tuple"}
@@ -611,7 +612,7 @@ def test_extended_flashing(app):
def index():
flask.flash("Hello World")
flask.flash("Hello World", "error")
- flask.flash(flask.Markup("Testing"), "warning")
+ flask.flash(Markup("Testing"), "warning")
return ""
@app.route("/test/")
@@ -620,7 +621,7 @@ def test_extended_flashing(app):
assert list(messages) == [
"Hello World",
"Hello World",
- flask.Markup("Testing"),
+ Markup("Testing"),
]
return ""
@@ -631,7 +632,7 @@ def test_extended_flashing(app):
assert list(messages) == [
("message", "Hello World"),
("error", "Hello World"),
- ("warning", flask.Markup("Testing")),
+ ("warning", Markup("Testing")),
]
return ""
@@ -650,7 +651,7 @@ def test_extended_flashing(app):
)
assert list(messages) == [
("message", "Hello World"),
- ("warning", flask.Markup("Testing")),
+ ("warning", Markup("Testing")),
]
return ""
@@ -659,7 +660,7 @@ def test_extended_flashing(app):
messages = flask.get_flashed_messages(category_filter=["message", "warning"])
assert len(messages) == 2
assert messages[0] == "Hello World"
- assert messages[1] == flask.Markup("Testing")
+ assert messages[1] == Markup("Testing")
return ""
# Create new test client on each test to clean flashed messages.
diff --git a/tests/test_json_tag.py b/tests/test_json_tag.py
index 7d11b963..677160a6 100644
--- a/tests/test_json_tag.py
+++ b/tests/test_json_tag.py
@@ -3,8 +3,8 @@ from datetime import timezone
from uuid import uuid4
import pytest
+from markupsafe import Markup
-from flask import Markup
from flask.json.tag import JSONTag
from flask.json.tag import TaggedJSONSerializer
diff --git a/tests/test_templating.py b/tests/test_templating.py
index 863417c0..c9fb3754 100644
--- a/tests/test_templating.py
+++ b/tests/test_templating.py
@@ -3,6 +3,7 @@ import logging
import pytest
import werkzeug.serving
from jinja2 import TemplateNotFound
+from markupsafe import Markup
import flask
@@ -73,7 +74,7 @@ def test_escaping(app, client):
@app.route("/")
def index():
return flask.render_template(
- "escaping_template.html", text=text, html=flask.Markup(text)
+ "escaping_template.html", text=text, html=Markup(text)
)
lines = client.get("/").data.splitlines()
@@ -93,7 +94,7 @@ def test_no_escaping(app, client):
@app.route("/")
def index():
return flask.render_template(
- "non_escaping_template.txt", text=text, html=flask.Markup(text)
+ "non_escaping_template.txt", text=text, html=Markup(text)
)
lines = client.get("/").data.splitlines()