diff --git a/CHANGES.rst b/CHANGES.rst
index 8159ea45..498d5e51 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -37,6 +37,14 @@ Unreleased
binary file instead. :issue:`4989`
+Version 2.2.4
+-------------
+
+Unreleased
+
+- Update for compatibility with Werkzeug 2.3.
+
+
Version 2.2.3
-------------
diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst
index 808a61a2..c281055f 100644
--- a/docs/errorhandling.rst
+++ b/docs/errorhandling.rst
@@ -69,7 +69,6 @@ See also:
- Sentry also supports catching errors from a worker queue
(RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs
`__ for more information.
-- `Getting started with Sentry `__
- `Flask-specific documentation `__
diff --git a/docs/installation.rst b/docs/installation.rst
index 276fdc3d..52f5f3ed 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -44,7 +44,7 @@ use them if you install them.
* `Watchdog`_ provides a faster, more efficient reloader for the development
server.
-.. _Blinker: https://pythonhosted.org/blinker/
+.. _Blinker: https://blinker.readthedocs.io/en/stable/
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
.. _watchdog: https://pythonhosted.org/watchdog/
diff --git a/requirements/build.txt b/requirements/build.txt
index f8a3ce75..e9cdf9da 100644
--- a/requirements/build.txt
+++ b/requirements/build.txt
@@ -12,6 +12,4 @@ packaging==23.0
pyproject-hooks==1.0.0
# via build
tomli==2.0.1
- # via
- # build
- # pyproject-hooks
+ # via build
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 780b527a..12a51ffb 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -28,19 +28,19 @@ filelock==3.9.0
# via
# tox
# virtualenv
-identify==2.5.18
+identify==2.5.19
# via pre-commit
nodeenv==1.7.0
# via pre-commit
pip-compile-multi==2.6.2
# via -r requirements/dev.in
-pip-tools==6.12.2
+pip-tools==6.12.3
# via pip-compile-multi
-platformdirs==3.0.0
+platformdirs==3.1.1
# via
# tox
# virtualenv
-pre-commit==3.1.0
+pre-commit==3.1.1
# via -r requirements/dev.in
pyproject-api==1.5.0
# via tox
@@ -48,11 +48,11 @@ pyproject-hooks==1.0.0
# via build
pyyaml==6.0
# via pre-commit
-toposort==1.9
+toposort==1.10
# via pip-compile-multi
tox==4.4.6
# via -r requirements/dev.in
-virtualenv==20.19.0
+virtualenv==20.20.0
# via
# pre-commit
# tox
diff --git a/requirements/docs.txt b/requirements/docs.txt
index 5c0568ca..a7355372 100644
--- a/requirements/docs.txt
+++ b/requirements/docs.txt
@@ -7,11 +7,11 @@
#
alabaster==0.7.13
# via sphinx
-babel==2.11.0
+babel==2.12.1
# via sphinx
certifi==2022.12.7
# via requests
-charset-normalizer==3.0.1
+charset-normalizer==3.1.0
# via requests
docutils==0.17.1
# via
@@ -35,8 +35,6 @@ pygments==2.14.0
# via
# sphinx
# sphinx-tabs
-pytz==2022.7.1
- # via babel
requests==2.28.2
# via sphinx
snowballstemmer==2.2.0
@@ -66,5 +64,5 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
-urllib3==1.26.14
+urllib3==1.26.15
# via requests
diff --git a/requirements/tests.in b/requirements/tests.in
index 41936997..87f72802 100644
--- a/requirements/tests.in
+++ b/requirements/tests.in
@@ -2,4 +2,4 @@ pytest
asgiref
blinker
greenlet ; python_version < "3.11"
-python-dotenv
+python-dotenv>=1; python_version >= "3.8"
diff --git a/requirements/tests.txt b/requirements/tests.txt
index 15cf399d..ba547a0e 100644
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -1,4 +1,4 @@
-# SHA1:69cf1e101a60350e9933c6f1f3b129bd9ed1ea7c
+# SHA1:30698f5f4f9cba5088318306829a15b0dc123b38
#
# This file is autogenerated by pip-compile-multi
# To update, run:
@@ -21,9 +21,9 @@ packaging==23.0
# via pytest
pluggy==1.0.0
# via pytest
-pytest==7.2.1
+pytest==7.2.2
# via -r requirements/tests.in
-python-dotenv==0.21.1
+python-dotenv==1.0.0 ; python_version >= "3.8"
# via -r requirements/tests.in
tomli==2.0.1
# via pytest
diff --git a/requirements/typing.txt b/requirements/typing.txt
index 33a8a120..bcf8e787 100644
--- a/requirements/typing.txt
+++ b/requirements/typing.txt
@@ -7,9 +7,9 @@
#
cffi==1.15.1
# via cryptography
-cryptography==39.0.1
+cryptography==39.0.2
# via -r requirements/typing.in
-mypy==1.0.1
+mypy==1.1.1
# via -r requirements/typing.in
mypy-extensions==1.0.0
# via mypy
@@ -21,9 +21,7 @@ types-contextvars==2.4.7.1
# via -r requirements/typing.in
types-dataclasses==0.6.6
# via -r requirements/typing.in
-types-docutils==0.19.1.6
- # via types-setuptools
-types-setuptools==67.4.0.1
+types-setuptools==67.6.0.0
# via -r requirements/typing.in
typing-extensions==4.5.0
# via mypy
diff --git a/src/flask/app.py b/src/flask/app.py
index 0070de89..3bbe1bb2 100644
--- a/src/flask/app.py
+++ b/src/flask/app.py
@@ -9,6 +9,7 @@ from collections.abc import Iterator as _abc_Iterator
from datetime import timedelta
from itertools import chain
from types import TracebackType
+from urllib.parse import quote as _url_quote
import click
from werkzeug.datastructures import Headers
@@ -25,7 +26,6 @@ from werkzeug.routing import RequestRedirect
from werkzeug.routing import RoutingException
from werkzeug.routing import Rule
from werkzeug.serving import is_running_from_reloader
-from werkzeug.urls import url_quote
from werkzeug.utils import cached_property
from werkzeug.utils import redirect as _wz_redirect
from werkzeug.wrappers import Response as BaseResponse
@@ -1710,7 +1710,8 @@ class Flask(Scaffold):
return self.handle_url_build_error(error, endpoint, values)
if _anchor is not None:
- rv = f"{rv}#{url_quote(_anchor)}"
+ _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@")
+ rv = f"{rv}#{_anchor}"
return rv
diff --git a/src/flask/testing.py b/src/flask/testing.py
index 3b21b093..8cb2d1bd 100644
--- a/src/flask/testing.py
+++ b/src/flask/testing.py
@@ -3,11 +3,11 @@ from contextlib import contextmanager
from contextlib import ExitStack
from copy import copy
from types import TracebackType
+from urllib.parse import urlsplit
import werkzeug.test
from click.testing import CliRunner
from werkzeug.test import Client
-from werkzeug.urls import url_parse
from werkzeug.wrappers import Request as BaseRequest
from .cli import ScriptInfo
@@ -68,7 +68,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
if url_scheme is None:
url_scheme = app.config["PREFERRED_URL_SCHEME"]
- url = url_parse(path)
+ url = urlsplit(path)
base_url = (
f"{url.scheme or url_scheme}://{url.netloc or http_host}"
f"/{app_root.lstrip('/')}"