This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/main by this push:
     new 831e2ef  Use consistent response types throughout
831e2ef is described below

commit 831e2efcbc2354fd2a50b84383a4b8ea9019dec6
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Nov 4 15:33:21 2025 +0000

    Use consistent response types throughout
---
 atr/admin/__init__.py    | 63 ++++++++++++++++++++++++------------------------
 atr/api/__init__.py      |  2 +-
 atr/get/announce.py      |  3 +--
 atr/get/candidate.py     |  5 ++--
 atr/get/compose.py       |  3 +--
 atr/get/download.py      | 27 ++++++---------------
 atr/get/draft.py         |  6 +----
 atr/get/file.py          |  3 +--
 atr/get/finish.py        |  5 +---
 atr/get/ignores.py       |  3 +--
 atr/get/keys.py          |  5 ++--
 atr/get/preview.py       |  5 ++--
 atr/get/projects.py      |  9 ++-----
 atr/get/published.py     |  8 +++---
 atr/get/ref.py           |  3 +--
 atr/get/release.py       |  5 ++--
 atr/get/root.py          |  3 +--
 atr/get/start.py         |  4 +--
 atr/get/tokens.py        |  4 +--
 atr/get/upload.py        |  4 +--
 atr/get/vote.py          |  3 +--
 atr/get/voting.py        |  4 +--
 atr/post/announce.py     |  7 +-----
 atr/post/candidate.py    |  4 +--
 atr/post/distribution.py |  7 +-----
 atr/post/draft.py        | 18 ++++++--------
 atr/post/finish.py       |  7 ++----
 atr/post/ignores.py      |  7 +++---
 atr/post/keys.py         | 13 +++++-----
 atr/post/preview.py      |  7 ++----
 atr/post/projects.py     | 11 +++------
 atr/post/resolve.py      |  7 +++---
 atr/post/revisions.py    |  3 +--
 atr/post/sbom.py         |  8 ++----
 atr/post/start.py        |  4 +--
 atr/post/tokens.py       |  7 ++----
 atr/post/upload.py       |  4 +--
 atr/post/user.py         |  3 +--
 atr/post/vote.py         |  3 +--
 atr/post/voting.py       |  4 +--
 atr/shared/__init__.py   |  3 +--
 atr/shared/finish.py     | 22 ++++++++---------
 atr/shared/keys.py       |  5 ++--
 atr/shared/projects.py   | 11 +++------
 atr/shared/start.py      |  3 +--
 atr/shared/tokens.py     | 11 ++++-----
 atr/shared/upload.py     |  3 +--
 atr/shared/voting.py     |  7 +++---
 atr/web.py               |  8 ++++--
 49 files changed, 141 insertions(+), 233 deletions(-)

diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 003cd98..1be3b6c 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -32,7 +32,6 @@ import asfquart.base as base
 import asfquart.session
 import quart
 import sqlalchemy.orm as orm
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.admin as admin
 import atr.config as config
@@ -114,16 +113,16 @@ async def all_releases(session: web.Committer) -> str:
 
 
 @admin.get("/browse-as")
-async def browse_as_get(session: web.Committer) -> str | response.Response:
+async def browse_as_get(session: web.Committer) -> str | web.WerkzeugResponse:
     return await _browse_as(session)
 
 
 @admin.post("/browse-as")
-async def browse_as_post(session: web.Committer) -> str | response.Response:
+async def browse_as_post(session: web.Committer) -> str | web.WerkzeugResponse:
     return await _browse_as(session)
 
 
-async def _browse_as(session: web.Committer) -> str | response.Response:
+async def _browse_as(session: web.Committer) -> str | web.WerkzeugResponse:
     """Allows an admin to browse as another user."""
     # TODO: Enable this in debugging mode only?
     import atr.get.root as root
@@ -172,7 +171,7 @@ async def _browse_as(session: web.Committer) -> str | 
response.Response:
 
 
 @admin.get("/configuration")
-async def configuration(session: web.Committer) -> 
quart.wrappers.response.Response:
+async def configuration(session: web.Committer) -> web.QuartResponse:
     """Display the current application configuration values."""
 
     conf = config.get()
@@ -299,7 +298,7 @@ async def _data(session: web.Committer, model: str = 
"Committee") -> str:
 
 
 @admin.get("/delete-test-openpgp-keys")
-async def delete_test_openpgp_keys_get(session: web.Committer) -> 
quart.Response | response.Response:
+async def delete_test_openpgp_keys_get(session: web.Committer) -> web.Response:
     if not config.get().ALLOW_TESTS:
         raise base.ASFQuartException("Test operations are disabled in this 
environment", errorcode=403)
 
@@ -309,7 +308,7 @@ async def delete_test_openpgp_keys_get(session: 
web.Committer) -> quart.Response
 
 
 @admin.post("/delete-test-openpgp-keys")
-async def delete_test_openpgp_keys_post(session: web.Committer) -> 
quart.Response | response.Response:
+async def delete_test_openpgp_keys_post(session: web.Committer) -> 
web.Response:
     """Delete all test user OpenPGP keys and their links."""
     if not config.get().ALLOW_TESTS:
         raise base.ASFQuartException("Test operations are disabled in this 
environment", errorcode=403)
@@ -328,16 +327,16 @@ async def delete_test_openpgp_keys_post(session: 
web.Committer) -> quart.Respons
 
 
 @admin.get("/delete-committee-keys")
-async def delete_committee_keys_get(session: web.Committer) -> str | 
response.Response:
+async def delete_committee_keys_get(session: web.Committer) -> str | 
web.WerkzeugResponse:
     return await _delete_committee_keys(session)
 
 
 @admin.post("/delete-committee-keys")
-async def delete_committee_keys_post(session: web.Committer) -> str | 
response.Response:
+async def delete_committee_keys_post(session: web.Committer) -> str | 
web.WerkzeugResponse:
     return await _delete_committee_keys(session)
 
 
-async def _delete_committee_keys(session: web.Committer) -> str | 
response.Response:
+async def _delete_committee_keys(session: web.Committer) -> str | 
web.WerkzeugResponse:
     form = await DeleteCommitteeKeysForm.create_form()
     async with db.session() as data:
         all_committees = await 
data.committee(_public_signing_keys=True).order_by(sql.Committee.name).all()
@@ -389,16 +388,16 @@ async def _delete_committee_keys(session: web.Committer) 
-> str | response.Respo
 
 
 @admin.get("/delete-release")
-async def delete_release_get(session: web.Committer) -> str | 
response.Response:
+async def delete_release_get(session: web.Committer) -> str | 
web.WerkzeugResponse:
     return await _delete_release(session)
 
 
 @admin.post("/delete-release")
-async def delete_release_post(session: web.Committer) -> str | 
response.Response:
+async def delete_release_post(session: web.Committer) -> str | 
web.WerkzeugResponse:
     return await _delete_release(session)
 
 
-async def _delete_release(session: web.Committer) -> str | response.Response:
+async def _delete_release(session: web.Committer) -> str | 
web.WerkzeugResponse:
     """Page to delete selected releases and their associated data and files."""
     form = await DeleteReleaseForm.create_form()
 
@@ -428,7 +427,7 @@ async def _delete_release(session: web.Committer) -> str | 
response.Response:
 
 
 @admin.get("/env")
-async def env(session: web.Committer) -> quart.wrappers.response.Response:
+async def env(session: web.Committer) -> web.QuartResponse:
     """Display the environment variables."""
     env_vars = []
     for key, value in os.environ.items():
@@ -437,16 +436,16 @@ async def env(session: web.Committer) -> 
quart.wrappers.response.Response:
 
 
 @admin.get("/keys/check")
-async def keys_check_get(session: web.Committer) -> quart.Response:
+async def keys_check_get(session: web.Committer) -> web.QuartResponse:
     return await _keys_check(session)
 
 
 @admin.post("/keys/check")
-async def keys_check_post(session: web.Committer) -> quart.Response:
+async def keys_check_post(session: web.Committer) -> web.QuartResponse:
     return await _keys_check(session)
 
 
-async def _keys_check(session: web.Committer) -> quart.Response:
+async def _keys_check(session: web.Committer) -> web.QuartResponse:
     """Check public signing key details."""
     if quart.request.method != "POST":
         check_form = await CheckKeysForm.create_form()
@@ -462,16 +461,16 @@ async def _keys_check(session: web.Committer) -> 
quart.Response:
 
 
 @admin.get("/keys/regenerate-all")
-async def keys_regenerate_all_get(session: web.Committer) -> quart.Response:
+async def keys_regenerate_all_get(session: web.Committer) -> web.QuartResponse:
     return await _keys_regenerate_all(session)
 
 
 @admin.post("/keys/regenerate-all")
-async def keys_regenerate_all_post(session: web.Committer) -> quart.Response:
+async def keys_regenerate_all_post(session: web.Committer) -> 
web.QuartResponse:
     return await _keys_regenerate_all(session)
 
 
-async def _keys_regenerate_all(session: web.Committer) -> quart.Response:
+async def _keys_regenerate_all(session: web.Committer) -> web.QuartResponse:
     """Regenerate the KEYS file for all committees."""
     if quart.request.method != "POST":
         regenerate_form = await RegenerateKeysForm.create_form()
@@ -500,16 +499,16 @@ async def _keys_regenerate_all(session: web.Committer) -> 
quart.Response:
 
 
 @admin.get("/keys/update")
-async def keys_update_get(session: web.Committer) -> str | response.Response | 
tuple[Mapping[str, Any], int]:
+async def keys_update_get(session: web.Committer) -> str | 
web.WerkzeugResponse | tuple[Mapping[str, Any], int]:
     return await _keys_update(session)
 
 
 @admin.post("/keys/update")
-async def keys_update_post(session: web.Committer) -> str | response.Response 
| tuple[Mapping[str, Any], int]:
+async def keys_update_post(session: web.Committer) -> str | 
web.WerkzeugResponse | tuple[Mapping[str, Any], int]:
     return await _keys_update(session)
 
 
-async def _keys_update(session: web.Committer) -> str | response.Response | 
tuple[Mapping[str, Any], int]:
+async def _keys_update(session: web.Committer) -> str | web.WerkzeugResponse | 
tuple[Mapping[str, Any], int]:
     """Update keys from remote data."""
     if quart.request.method != "POST":
         empty_form = await forms.Empty.create_form()
@@ -582,20 +581,20 @@ async def _ldap(session: web.Committer) -> str:
 @admin.get("/ongoing-tasks/<project_name>/<version_name>/<revision>")
 async def ongoing_tasks_get(
     session: web.Committer, project_name: str, version_name: str, revision: str
-) -> quart.wrappers.response.Response:
+) -> web.QuartResponse:
     return await _ongoing_tasks(session, project_name, version_name, revision)
 
 
 @admin.post("/ongoing-tasks/<project_name>/<version_name>/<revision>")
 async def ongoing_tasks_post(
     session: web.Committer, project_name: str, version_name: str, revision: str
-) -> quart.wrappers.response.Response:
+) -> web.QuartResponse:
     return await _ongoing_tasks(session, project_name, version_name, revision)
 
 
 async def _ongoing_tasks(
     session: web.Committer, project_name: str, version_name: str, revision: str
-) -> quart.wrappers.response.Response:
+) -> web.QuartResponse:
     try:
         ongoing = await interaction.tasks_ongoing(project_name, version_name, 
revision)
         return web.TextResponse(str(ongoing))
@@ -686,16 +685,16 @@ async def performance(session: web.Committer) -> str:
 
 
 @admin.get("/projects/update")
-async def projects_update_get(session: web.Committer) -> str | 
response.Response | tuple[Mapping[str, Any], int]:
+async def projects_update_get(session: web.Committer) -> str | 
web.WerkzeugResponse | tuple[Mapping[str, Any], int]:
     return await _projects_update(session)
 
 
 @admin.post("/projects/update")
-async def projects_update_post(session: web.Committer) -> str | 
response.Response | tuple[Mapping[str, Any], int]:
+async def projects_update_post(session: web.Committer) -> str | 
web.WerkzeugResponse | tuple[Mapping[str, Any], int]:
     return await _projects_update(session)
 
 
-async def _projects_update(session: web.Committer) -> str | response.Response 
| tuple[Mapping[str, Any], int]:
+async def _projects_update(session: web.Committer) -> str | 
web.WerkzeugResponse | tuple[Mapping[str, Any], int]:
     """Update projects from remote data."""
     if quart.request.method == "POST":
         try:
@@ -724,7 +723,7 @@ async def tasks_(session: web.Committer) -> str:
 @admin.get("/task-times/<project_name>/<version_name>/<revision_number>")
 async def task_times(
     session: web.Committer, project_name: str, version_name: str, 
revision_number: str
-) -> quart.wrappers.response.Response:
+) -> web.QuartResponse:
     values = []
     async with db.session() as data:
         tasks = await data.task(
@@ -740,7 +739,7 @@ async def task_times(
 
 
 @admin.get("/test")
-async def test(session: web.Committer) -> quart.wrappers.response.Response:
+async def test(session: web.Committer) -> web.QuartResponse:
     """Test the storage layer."""
     import atr.storage as storage
 
@@ -780,7 +779,7 @@ async def toggle_view_get(session: web.Committer) -> str:
 
 
 @admin.post("/toggle-view")
-async def toggle_view_post(session: web.Committer) -> response.Response:
+async def toggle_view_post(session: web.Committer) -> web.WerkzeugResponse:
     await util.validate_empty_form()
 
     app = asfquart.APP
diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index ac23fe7..ac6419e 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -154,7 +154,7 @@ async def checks_ongoing(
     ongoing_tasks_count, _latest_revision = await 
interaction.tasks_ongoing_revision(project, version, revision)
     # TODO: Is there a way to return just an int?
     # The ResponseReturnValue type in quart does not allow int
-    # And if we use quart.jsonify, we must return quart.Response which 
quart_schema tries to validate
+    # And if we use quart.jsonify, we must return web.QuartResponse which 
quart_schema tries to validate
     # ResponseValue = Union[
     #     "Response",
     #     "WerkzeugResponse",
diff --git a/atr/get/announce.py b/atr/get/announce.py
index f2f43ff..3ecdecd 100644
--- a/atr/get/announce.py
+++ b/atr/get/announce.py
@@ -15,7 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import werkzeug.wrappers.response as response
 
 # TODO: Improve upon the routes_release pattern
 import atr.blueprints.get as get
@@ -29,7 +28,7 @@ import atr.web as web
 
 
 @get.committer("/announce/<project_name>/<version_name>")
-async def selected(session: web.Committer, project_name: str, version_name: 
str) -> str | response.Response:
+async def selected(session: web.Committer, project_name: str, version_name: 
str) -> str | web.WerkzeugResponse:
     """Allow the user to announce a release preview."""
     await session.check_access(project_name)
 
diff --git a/atr/get/candidate.py b/atr/get/candidate.py
index 3b19391..dcce63c 100644
--- a/atr/get/candidate.py
+++ b/atr/get/candidate.py
@@ -18,7 +18,6 @@
 """candidate.py"""
 
 import asfquart.base as base
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.db as db
@@ -30,7 +29,7 @@ import atr.web as web
 
 
 @get.committer("/candidate/view/<project_name>/<version_name>")
-async def view(session: web.Committer, project_name: str, version_name: str) 
-> response.Response | str:
+async def view(session: web.Committer, project_name: str, version_name: str) 
-> web.WerkzeugResponse | str:
     """View all the files in the rsync upload directory for a release."""
     await session.check_access(project_name)
 
@@ -67,7 +66,7 @@ async def view(session: web.Committer, project_name: str, 
version_name: str) ->
 
@get.committer("/candidate/view/<project_name>/<version_name>/<path:file_path>")
 async def view_path(
     session: web.Committer, project_name: str, version_name: str, file_path: 
str
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     """View the content of a specific file in the release candidate."""
     await session.check_access(project_name)
 
diff --git a/atr/get/compose.py b/atr/get/compose.py
index 2696b38..304f246 100644
--- a/atr/get/compose.py
+++ b/atr/get/compose.py
@@ -16,7 +16,6 @@
 # under the License.
 
 import asfquart.base as base
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.db as db
@@ -27,7 +26,7 @@ import atr.web as web
 
 
 @get.committer("/compose/<project_name>/<version_name>")
-async def selected(session: web.Committer, project_name: str, version_name: 
str) -> response.Response | str:
+async def selected(session: web.Committer, project_name: str, version_name: 
str) -> web.WerkzeugResponse | str:
     """Show the contents of the release candidate draft."""
     await session.check_access(project_name)
 
diff --git a/atr/get/download.py b/atr/get/download.py
index c1d81ed..f9c6fa9 100644
--- a/atr/get/download.py
+++ b/atr/get/download.py
@@ -22,7 +22,6 @@ import aiofiles
 import aiofiles.os
 import asfquart.base as base
 import quart
-import werkzeug.wrappers.response as response
 import zipstream
 
 import atr.blueprints.get as get
@@ -37,7 +36,7 @@ import atr.web as web
 
 
 @get.committer("/download/all/<project_name>/<version_name>")
-async def all_selected(session: web.Committer, project_name: str, 
version_name: str) -> response.Response | str:
+async def all_selected(session: web.Committer, project_name: str, 
version_name: str) -> web.WerkzeugResponse | str:
     """Display download commands for a release."""
     import atr.get.root as root
 
@@ -64,25 +63,19 @@ async def all_selected(session: web.Committer, 
project_name: str, version_name:
 
 
 @get.public("/download/path/<project_name>/<version_name>/<path:file_path>")
-async def path(
-    session: web.Committer | None, project_name: str, version_name: str, 
file_path: str
-) -> response.Response | quart.Response:
+async def path(session: web.Committer | None, project_name: str, version_name: 
str, file_path: str) -> web.Response:
     """Download a file or list a directory from a release in any phase."""
     return await _download_or_list(project_name, version_name, file_path)
 
 
 @get.public("/download/path/<project_name>/<version_name>/")
-async def path_empty(
-    session: web.Committer | None, project_name: str, version_name: str
-) -> response.Response | quart.Response:
+async def path_empty(session: web.Committer | None, project_name: str, 
version_name: str) -> web.Response:
     """List files at the root of a release directory for download."""
     return await _download_or_list(project_name, version_name, ".")
 
 
 @get.public("/download/sh/<project_name>/<version_name>")
-async def sh_selected(
-    session: web.Committer | None, project_name: str, version_name: str
-) -> response.Response | quart.Response:
+async def sh_selected(session: web.Committer | None, project_name: str, 
version_name: str) -> web.Response:
     """Shell script to download a release."""
     conf = config.get()
     app_host = conf.APP_HOST
@@ -97,9 +90,7 @@ async def sh_selected(
 
 
 @get.public("/download/urls/<project_name>/<version_name>")
-async def urls_selected(
-    session: web.Committer | None, project_name: str, version_name: str
-) -> response.Response | quart.Response:
+async def urls_selected(session: web.Committer | None, project_name: str, 
version_name: str) -> web.Response:
     try:
         async with db.session() as data:
             release = await data.release(project_name=project_name, 
version=version_name).demand(
@@ -114,9 +105,7 @@ async def urls_selected(
 
 
 @get.committer("/download/zip/<project_name>/<version_name>")
-async def zip_selected(
-    session: web.Committer, project_name: str, version_name: str
-) -> response.Response | quart.wrappers.response.Response:
+async def zip_selected(session: web.Committer, project_name: str, 
version_name: str) -> web.Response:
     try:
         release = await session.release(project_name=project_name, 
version_name=version_name, phase=None)
     except ValueError as e:
@@ -146,7 +135,7 @@ async def zip_selected(
     return web.ZipResponse(stream_zip(files_to_zip), headers=headers)
 
 
-async def _download_or_list(project_name: str, version_name: str, file_path: 
str) -> response.Response | quart.Response:
+async def _download_or_list(project_name: str, version_name: str, file_path: 
str) -> web.Response:
     """Download a file or list a directory from a release in any phase."""
     import atr.get.root as root
 
@@ -200,7 +189,7 @@ async def _generate_file_url_list(release: sql.Release) -> 
str:
 
 async def _list(
     original_path: pathlib.Path, full_path: pathlib.Path, project_name: str, 
version_name: str, file_path: str
-) -> response.Response | quart.Response:
+) -> web.Response:
     # Build a list of files in the directory
     files: list[pathlib.Path] = []
     for file in await aiofiles.os.listdir(full_path):
diff --git a/atr/get/draft.py b/atr/get/draft.py
index ca9ad87..daa028a 100644
--- a/atr/get/draft.py
+++ b/atr/get/draft.py
@@ -19,7 +19,6 @@ from __future__ import annotations
 
 import datetime
 import pathlib
-from typing import TYPE_CHECKING
 
 import aiofiles.os
 import asfquart.base as base
@@ -30,9 +29,6 @@ import atr.template as template
 import atr.util as util
 import atr.web as web
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 
 @get.committer("/draft/tools/<project_name>/<version_name>/<path:file_path>")
 async def tools(session: web.Committer, project_name: str, version_name: str, 
file_path: str) -> str:
@@ -71,7 +67,7 @@ async def tools(session: web.Committer, project_name: str, 
version_name: str, fi
 # TODO: Should we deprecate this and ensure compose covers it all?
 # If we did that, we'd lose the exhaustive use of the abstraction
 @get.committer("/draft/view/<project_name>/<version_name>")
-async def view(session: web.Committer, project_name: str, version_name: str) 
-> response.Response | str:
+async def view(session: web.Committer, project_name: str, version_name: str) 
-> web.WerkzeugResponse | str:
     """View all the files in the rsync upload directory for a release."""
     await session.check_access(project_name)
 
diff --git a/atr/get/file.py b/atr/get/file.py
index 545ab9e..c8ccd79 100644
--- a/atr/get/file.py
+++ b/atr/get/file.py
@@ -15,7 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.template as template
@@ -26,7 +25,7 @@ import atr.web as web
 @get.committer("/file/<project_name>/<version_name>/<path:file_path>")
 async def selected_path(
     session: web.Committer, project_name: str, version_name: str, file_path: 
str
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     """View the content of a specific file in the release candidate draft."""
     # TODO: Make this independent of the release phase
     await session.check_access(project_name)
diff --git a/atr/get/finish.py b/atr/get/finish.py
index 9530bad..d0f015b 100644
--- a/atr/get/finish.py
+++ b/atr/get/finish.py
@@ -16,9 +16,6 @@
 # under the License.
 
 
-import quart.wrappers.response as quart_response
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.get as get
 import atr.shared as shared
 import atr.web as web
@@ -27,6 +24,6 @@ import atr.web as web
 @get.committer("/finish/<project_name>/<version_name>")
 async def selected(
     session: web.Committer, project_name: str, version_name: str
-) -> tuple[quart_response.Response, int] | response.Response | str:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse | str:
     """Finish a release preview."""
     return await shared.finish.selected(session, project_name, version_name)
diff --git a/atr/get/ignores.py b/atr/get/ignores.py
index 517509d..e32e604 100644
--- a/atr/get/ignores.py
+++ b/atr/get/ignores.py
@@ -18,7 +18,6 @@
 from typing import Final
 
 import markupsafe
-import werkzeug.wrappers.response as response
 import wtforms
 
 import atr.blueprints.get as get
@@ -51,7 +50,7 @@ document.querySelectorAll("table.page-details 
input.form-control").forEach(funct
 
 
 @get.committer("/ignores/<committee_name>")
-async def ignores(session: web.Committer, committee_name: str) -> str | 
response.Response:
+async def ignores(session: web.Committer, committee_name: str) -> str | 
web.WerkzeugResponse:
     async with storage.read() as read:
         ragp = read.as_general_public()
         ignores = await ragp.checks.ignores(committee_name)
diff --git a/atr/get/keys.py b/atr/get/keys.py
index 0cbd946..7f72def 100644
--- a/atr/get/keys.py
+++ b/atr/get/keys.py
@@ -19,7 +19,6 @@ import datetime
 
 import asfquart as asfquart
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.db as db
@@ -37,7 +36,7 @@ async def add(session: web.Committer) -> str:
 
 
 @get.committer("/keys/details/<fingerprint>")
-async def details(session: web.Committer, fingerprint: str) -> str | 
response.Response:
+async def details(session: web.Committer, fingerprint: str) -> str | 
web.WerkzeugResponse:
     """Display details for a specific OpenPGP key."""
     return await shared.keys.details(session, fingerprint)
 
@@ -88,7 +87,7 @@ async def keys(session: web.Committer) -> str:
 
 
 @get.committer("/keys/ssh/add")
-async def ssh_add(session: web.Committer) -> response.Response | str:
+async def ssh_add(session: web.Committer) -> web.WerkzeugResponse | str:
     """Add a new SSH key to the user's account."""
     return await shared.keys.ssh_add(session)
 
diff --git a/atr/get/preview.py b/atr/get/preview.py
index 0a9689b..0e8df81 100644
--- a/atr/get/preview.py
+++ b/atr/get/preview.py
@@ -15,7 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.models.sql as sql
@@ -25,7 +24,7 @@ import atr.web as web
 
 
 @get.committer("/preview/view/<project_name>/<version_name>")
-async def view(session: web.Committer, project_name: str, version_name: str) 
-> response.Response | str:
+async def view(session: web.Committer, project_name: str, version_name: str) 
-> web.WerkzeugResponse | str:
     """View all the files in the rsync upload directory for a release."""
     await session.check_access(project_name)
 
@@ -58,7 +57,7 @@ async def view(session: web.Committer, project_name: str, 
version_name: str) ->
 @get.committer("/preview/view/<project_name>/<version_name>/<path:file_path>")
 async def view_path(
     session: web.Committer, project_name: str, version_name: str, file_path: 
str
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     """View the content of a specific file in the release preview."""
     await session.check_access(project_name)
 
diff --git a/atr/get/projects.py b/atr/get/projects.py
index 5a6d682..2e4dec1 100644
--- a/atr/get/projects.py
+++ b/atr/get/projects.py
@@ -17,8 +17,6 @@
 
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
-
 import atr.blueprints.get as get
 import atr.config as config
 import atr.db as db
@@ -28,12 +26,9 @@ import atr.shared as shared
 import atr.template as template
 import atr.web as web
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 
 @get.committer("/project/add/<committee_name>")
-async def add_project(session: web.Committer, committee_name: str) -> 
response.Response | str:
+async def add_project(session: web.Committer, committee_name: str) -> 
web.WerkzeugResponse | str:
     return await shared.projects.add_project(session, committee_name)
 
 
@@ -72,5 +67,5 @@ async def select(session: web.Committer) -> str:
 
 
 @get.committer("/projects/<name>")
-async def view(session: web.Committer, name: str) -> response.Response | str:
+async def view(session: web.Committer, name: str) -> web.WerkzeugResponse | 
str:
     return await shared.projects.view(session, name)
diff --git a/atr/get/published.py b/atr/get/published.py
index 6d7e572..ad5c959 100644
--- a/atr/get/published.py
+++ b/atr/get/published.py
@@ -29,7 +29,7 @@ import atr.web as web
 
 
 @get.committer("/published/<path:path>")
-async def path(session: web.Committer, path: str) -> quart.Response:
+async def path(session: web.Committer, path: str) -> web.QuartResponse:
     """View the content of a specific file in the downloads directory."""
     # This route is for debugging
     # When developing locally, there is no proxy to view the downloads 
directory
@@ -38,7 +38,7 @@ async def path(session: web.Committer, path: str) -> 
quart.Response:
 
 
 @get.committer("/published/")
-async def root(session: web.Committer) -> quart.Response:
+async def root(session: web.Committer) -> web.QuartResponse:
     return await _path(session, "")
 
 
@@ -91,11 +91,11 @@ async def _directory_listing_pre(full_path: pathlib.Path, 
current_path: str, pre
             pre.text("\n")
 
 
-async def _file_content(full_path: pathlib.Path) -> quart.Response:
+async def _file_content(full_path: pathlib.Path) -> web.QuartResponse:
     return await quart.send_file(full_path)
 
 
-async def _path(session: web.Committer, path: str) -> quart.Response:
+async def _path(session: web.Committer, path: str) -> web.QuartResponse:
     downloads_path = util.get_downloads_dir()
     full_path = downloads_path / path
     if await aiofiles.os.path.isdir(full_path):
diff --git a/atr/get/ref.py b/atr/get/ref.py
index 78dcbef..807b050 100644
--- a/atr/get/ref.py
+++ b/atr/get/ref.py
@@ -19,7 +19,6 @@ import ast
 import pathlib
 
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.config as config
@@ -31,7 +30,7 @@ import atr.web as web
 
 
 @get.public("/ref/<path:ref_path>")
-async def resolve(session: web.Committer | None, ref_path: str) -> 
response.Response:
+async def resolve(session: web.Committer | None, ref_path: str) -> 
web.WerkzeugResponse:
     project_root = pathlib.Path(config.get().PROJECT_ROOT)
 
     if ":" in ref_path:
diff --git a/atr/get/release.py b/atr/get/release.py
index dac1e8a..4c17f9c 100644
--- a/atr/get/release.py
+++ b/atr/get/release.py
@@ -18,7 +18,6 @@
 import datetime
 
 import asfquart.base as base
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.db as db
@@ -94,7 +93,7 @@ async def select(session: web.Committer, project_name: str) 
-> str:
 
 
 @get.public("/release/view/<project_name>/<version_name>")
-async def view(session: web.Committer | None, project_name: str, version_name: 
str) -> response.Response | str:
+async def view(session: web.Committer | None, project_name: str, version_name: 
str) -> web.WerkzeugResponse | str:
     """View all the files in the rsync upload directory for a release."""
     async with db.session() as data:
         release_name = sql.release_name(project_name, version_name)
@@ -123,7 +122,7 @@ async def view(session: web.Committer | None, project_name: 
str, version_name: s
 @get.public("/release/view/<project_name>/<version_name>/<path:file_path>")
 async def view_path(
     session: web.Committer | None, project_name: str, version_name: str, 
file_path: str
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     """View the content of a specific file in the final release."""
     async with db.session() as data:
         release_name = sql.release_name(project_name, version_name)
diff --git a/atr/get/root.py b/atr/get/root.py
index 6fd33b5..0fa35c2 100644
--- a/atr/get/root.py
+++ b/atr/get/root.py
@@ -24,7 +24,6 @@ import asfquart.session
 import quart.wrappers.response as quart_response
 import sqlalchemy.orm as orm
 import sqlmodel
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.config as config
@@ -155,7 +154,7 @@ async def policies(session: web.Committer | None) -> str:
 
 
 @get.public("/test-login")
-async def test_login(session: web.Committer | None) -> response.Response:
+async def test_login(session: web.Committer | None) -> web.WerkzeugResponse:
     if not config.get().ALLOW_TESTS:
         raise base.ASFQuartException("Test login not enabled", errorcode=404)
 
diff --git a/atr/get/start.py b/atr/get/start.py
index 1ebbb7d..69b7f68 100644
--- a/atr/get/start.py
+++ b/atr/get/start.py
@@ -16,14 +16,12 @@
 # under the License.
 
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.get as get
 import atr.shared as shared
 import atr.web as web
 
 
 @get.committer("/start/<project_name>")
-async def selected(session: web.Committer, project_name: str) -> 
response.Response | str:
+async def selected(session: web.Committer, project_name: str) -> 
web.WerkzeugResponse | str:
     """Allow the user to start a new release draft, or handle its 
submission."""
     return await shared.start.selected(session, project_name)
diff --git a/atr/get/tokens.py b/atr/get/tokens.py
index 8425c0c..21a8d18 100644
--- a/atr/get/tokens.py
+++ b/atr/get/tokens.py
@@ -16,13 +16,11 @@
 # under the License.
 
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.get as get
 import atr.shared as shared
 import atr.web as web
 
 
 @get.committer("/tokens")
-async def tokens(session: web.Committer) -> str | response.Response:
+async def tokens(session: web.Committer) -> str | web.WerkzeugResponse:
     return await shared.tokens.tokens(session)
diff --git a/atr/get/upload.py b/atr/get/upload.py
index d4ce256..09fb5b3 100644
--- a/atr/get/upload.py
+++ b/atr/get/upload.py
@@ -16,13 +16,11 @@
 # under the License.
 
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.get as get
 import atr.shared as shared
 import atr.web as web
 
 
 @get.committer("/upload/<project_name>/<version_name>")
-async def selected(session: web.Committer, project_name: str, version_name: 
str) -> response.Response | str:
+async def selected(session: web.Committer, project_name: str, version_name: 
str) -> web.WerkzeugResponse | str:
     return await shared.upload.selected(session, project_name, version_name)
diff --git a/atr/get/vote.py b/atr/get/vote.py
index f2da908..9cd6b3a 100644
--- a/atr/get/vote.py
+++ b/atr/get/vote.py
@@ -16,7 +16,6 @@
 # under the License.
 
 import asfquart.base as base
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.get as get
 import atr.db as db
@@ -34,7 +33,7 @@ import atr.web as web
 
 
 @get.public("/vote/<project_name>/<version_name>")
-async def selected(session: web.Committer | None, project_name: str, 
version_name: str) -> response.Response | str:
+async def selected(session: web.Committer | None, project_name: str, 
version_name: str) -> web.WerkzeugResponse | str:
     """Show the contents of the release candidate draft."""
     async with db.session() as data:
         release = await data.release(
diff --git a/atr/get/voting.py b/atr/get/voting.py
index 2d4dc05..2334696 100644
--- a/atr/get/voting.py
+++ b/atr/get/voting.py
@@ -16,8 +16,6 @@
 # under the License.
 
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.get as get
 import atr.shared as shared
 import atr.web as web
@@ -26,5 +24,5 @@ import atr.web as web
 @get.committer("/voting/<project_name>/<version_name>/<revision>")
 async def selected_revision(
     session: web.Committer, project_name: str, version_name: str, revision: str
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     return await shared.voting.selected_revision(session, project_name, 
version_name, revision)
diff --git a/atr/post/announce.py b/atr/post/announce.py
index 176d0af..3c68862 100644
--- a/atr/post/announce.py
+++ b/atr/post/announce.py
@@ -17,13 +17,8 @@
 
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
-
 import quart
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 # TODO: Improve upon the routes_release pattern
 import atr.blueprints.post as post
 import atr.models.sql as sql
@@ -39,7 +34,7 @@ class AnnounceError(Exception):
 
 
 @post.committer("/announce/<project_name>/<version_name>")
-async def selected(session: web.Committer, project_name: str, version_name: 
str) -> str | response.Response:
+async def selected(session: web.Committer, project_name: str, version_name: 
str) -> str | web.WerkzeugResponse:
     """Handle the announcement form submission and promote the preview to 
release."""
     import atr.get as get
 
diff --git a/atr/post/candidate.py b/atr/post/candidate.py
index 1f2a426..8eefe62 100644
--- a/atr/post/candidate.py
+++ b/atr/post/candidate.py
@@ -15,14 +15,12 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.post as post
 import atr.web as web
 
 
 @post.committer("/candidate/delete")
-async def delete(session: web.Committer) -> response.Response:
+async def delete(session: web.Committer) -> web.WerkzeugResponse:
     """Delete a release candidate."""
     import atr.get as get
 
diff --git a/atr/post/distribution.py b/atr/post/distribution.py
index dbf1744..132cac3 100644
--- a/atr/post/distribution.py
+++ b/atr/post/distribution.py
@@ -17,8 +17,6 @@
 
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
-
 import quart
 
 import atr.blueprints.post as post
@@ -29,12 +27,9 @@ import atr.shared as shared
 import atr.storage as storage
 import atr.web as web
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 
 @post.committer("/distribution/delete/<project>/<version>")
-async def delete(session: web.Committer, project: str, version: str) -> 
response.Response:
+async def delete(session: web.Committer, project: str, version: str) -> 
web.WerkzeugResponse:
     form = await shared.distribution.DeleteForm.create_form(data=await 
quart.request.form)
     dd = distribution.DeleteData.model_validate(form.data)
 
diff --git a/atr/post/draft.py b/atr/post/draft.py
index e54da85..8fd7502 100644
--- a/atr/post/draft.py
+++ b/atr/post/draft.py
@@ -18,7 +18,6 @@
 from __future__ import annotations
 
 import pathlib
-from typing import TYPE_CHECKING
 
 import aiofiles.os
 import aioshutil
@@ -36,9 +35,6 @@ import atr.storage as storage
 import atr.util as util
 import atr.web as web
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 
 class VotePreviewForm(forms.Typed):
     body = forms.textarea("Body")
@@ -49,7 +45,7 @@ class VotePreviewForm(forms.Typed):
 
 
 @post.committer("/draft/delete")
-async def delete(session: web.Committer) -> response.Response:
+async def delete(session: web.Committer) -> web.WerkzeugResponse:
     """Delete a candidate draft and all its associated files."""
     import atr.get as get
 
@@ -95,7 +91,7 @@ async def delete(session: web.Committer) -> response.Response:
 
 
 @post.committer("/draft/delete-file/<project_name>/<version_name>")
-async def delete_file(session: web.Committer, project_name: str, version_name: 
str) -> response.Response:
+async def delete_file(session: web.Committer, project_name: str, version_name: 
str) -> web.WerkzeugResponse:
     """Delete a specific file from the release candidate, creating a new 
revision."""
     await session.check_access(project_name)
 
@@ -130,7 +126,7 @@ async def delete_file(session: web.Committer, project_name: 
str, version_name: s
 
 
 @post.committer("/draft/fresh/<project_name>/<version_name>")
-async def fresh(session: web.Committer, project_name: str, version_name: str) 
-> response.Response:
+async def fresh(session: web.Committer, project_name: str, version_name: str) 
-> web.WerkzeugResponse:
     """Restart all checks for a whole release candidate draft."""
     # Admin only button, but it's okay if users find and use this manually
     await session.check_access(project_name)
@@ -156,7 +152,7 @@ async def fresh(session: web.Committer, project_name: str, 
version_name: str) ->
 
 
 
@post.committer("/draft/hashgen/<project_name>/<version_name>/<path:file_path>")
-async def hashgen(session: web.Committer, project_name: str, version_name: 
str, file_path: str) -> response.Response:
+async def hashgen(session: web.Committer, project_name: str, version_name: 
str, file_path: str) -> web.WerkzeugResponse:
     """Generate an sha256 or sha512 hash file for a candidate draft file, 
creating a new revision."""
     await session.check_access(project_name)
 
@@ -189,7 +185,7 @@ async def hashgen(session: web.Committer, project_name: 
str, version_name: str,
 
 
 
@post.committer("/draft/sbomgen/<project_name>/<version_name>/<path:file_path>")
-async def sbomgen(session: web.Committer, project_name: str, version_name: 
str, file_path: str) -> response.Response:
+async def sbomgen(session: web.Committer, project_name: str, version_name: 
str, file_path: str) -> web.WerkzeugResponse:
     """Generate a CycloneDX SBOM file for a candidate draft file, creating a 
new revision."""
     await session.check_access(project_name)
 
@@ -246,7 +242,7 @@ async def sbomgen(session: web.Committer, project_name: 
str, version_name: str,
 
 
 @post.committer("/draft/svnload/<project_name>/<version_name>")
-async def svnload(session: web.Committer, project_name: str, version_name: 
str) -> response.Response | str:
+async def svnload(session: web.Committer, project_name: str, version_name: 
str) -> web.WerkzeugResponse | str:
     """Import files from SVN into a draft."""
 
     await session.check_access(project_name)
@@ -293,7 +289,7 @@ async def svnload(session: web.Committer, project_name: 
str, version_name: str)
 @post.committer("/draft/vote/preview/<project_name>/<version_name>")
 async def vote_preview(
     session: web.Committer, project_name: str, version_name: str
-) -> quart.wrappers.response.Response | response.Response | str:
+) -> web.QuartResponse | web.WerkzeugResponse | str:
     """Show the vote email preview for a release."""
     import atr.get as get
 
diff --git a/atr/post/finish.py b/atr/post/finish.py
index 4ea8b0e..356f095 100644
--- a/atr/post/finish.py
+++ b/atr/post/finish.py
@@ -17,19 +17,16 @@
 
 from collections.abc import Awaitable, Callable
 
-import quart.wrappers.response as quart_response
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.post as post
 import atr.shared as shared
 import atr.web as web
 
-type Respond = Callable[[int, str], Awaitable[tuple[quart_response.Response, 
int] | response.Response]]
+type Respond = Callable[[int, str], Awaitable[tuple[web.QuartResponse, int] | 
web.WerkzeugResponse]]
 
 
 @post.committer("/finish/<project_name>/<version_name>")
 async def selected(
     session: web.Committer, project_name: str, version_name: str
-) -> tuple[quart_response.Response, int] | response.Response | str:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse | str:
     """Finish a release preview."""
     return await shared.finish.selected(session, project_name, version_name)
diff --git a/atr/post/ignores.py b/atr/post/ignores.py
index 647da04..988a396 100644
--- a/atr/post/ignores.py
+++ b/atr/post/ignores.py
@@ -17,7 +17,6 @@
 
 
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.post as post
 import atr.get as get
@@ -28,7 +27,7 @@ import atr.web as web
 
 
 @post.committer("/ignores/<committee_name>/add")
-async def ignores_committee_add(session: web.Committer, committee_name: str) 
-> str | response.Response:
+async def ignores_committee_add(session: web.Committer, committee_name: str) 
-> str | web.WerkzeugResponse:
     data = await quart.request.form
     form = await shared.ignores.AddIgnoreForm.create_form(data=data)
     if not (await form.validate_on_submit()):
@@ -56,7 +55,7 @@ async def ignores_committee_add(session: web.Committer, 
committee_name: str) ->
 
 
 @post.committer("/ignores/<committee_name>/delete")
-async def ignores_committee_delete(session: web.Committer, committee_name: 
str) -> str | response.Response:
+async def ignores_committee_delete(session: web.Committer, committee_name: 
str) -> str | web.WerkzeugResponse:
     data = await quart.request.form
     form = await shared.ignores.DeleteIgnoreForm.create_form(data=data)
     if not (await form.validate_on_submit()):
@@ -86,7 +85,7 @@ async def ignores_committee_delete(session: web.Committer, 
committee_name: str)
 
 
 @post.committer("/ignores/<committee_name>/update")
-async def ignores_committee_update(session: web.Committer, committee_name: 
str) -> str | response.Response:
+async def ignores_committee_update(session: web.Committer, committee_name: 
str) -> str | web.WerkzeugResponse:
     data = await quart.request.form
     form = await shared.ignores.UpdateIgnoreForm.create_form(data=data)
     if not (await form.validate_on_submit()):
diff --git a/atr/post/keys.py b/atr/post/keys.py
index 246ef6f..1c13692 100644
--- a/atr/post/keys.py
+++ b/atr/post/keys.py
@@ -18,7 +18,6 @@
 
 import asfquart as asfquart
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.post as post
 import atr.get as get
@@ -38,7 +37,7 @@ async def add(session: web.Committer) -> str:
 
 
 @post.committer("/keys/delete")
-async def delete(session: web.Committer) -> response.Response:
+async def delete(session: web.Committer) -> web.WerkzeugResponse:
     """Delete a public signing key or SSH key from the user's account."""
     form = await shared.keys.DeleteKeyForm.create_form(data=await 
quart.request.form)
 
@@ -70,13 +69,15 @@ async def delete(session: web.Committer) -> 
response.Response:
 
 
 @post.committer("/keys/details/<fingerprint>")
-async def details(session: web.Committer, fingerprint: str) -> str | 
response.Response:
+async def details(session: web.Committer, fingerprint: str) -> str | 
web.WerkzeugResponse:
     """Display details for a specific OpenPGP key."""
     return await shared.keys.details(session, fingerprint)
 
 
 @post.committer("/keys/import/<project_name>/<version_name>")
-async def import_selected_revision(session: web.Committer, project_name: str, 
version_name: str) -> response.Response:
+async def import_selected_revision(
+    session: web.Committer, project_name: str, version_name: str
+) -> web.WerkzeugResponse:
     await util.validate_empty_form()
 
     async with storage.write() as write:
@@ -95,13 +96,13 @@ async def import_selected_revision(session: web.Committer, 
project_name: str, ve
 
 
 @post.committer("/keys/ssh/add")
-async def ssh_add(session: web.Committer) -> response.Response | str:
+async def ssh_add(session: web.Committer) -> web.WerkzeugResponse | str:
     """Add a new SSH key to the user's account."""
     return await shared.keys.ssh_add(session)
 
 
 @post.committer("/keys/update-committee-keys/<committee_name>")
-async def update_committee_keys(session: web.Committer, committee_name: str) 
-> response.Response:
+async def update_committee_keys(session: web.Committer, committee_name: str) 
-> web.WerkzeugResponse:
     """Generate and save the KEYS file for a specific committee."""
     form = await shared.keys.UpdateCommitteeKeysForm.create_form()
     if not await form.validate_on_submit():
diff --git a/atr/post/preview.py b/atr/post/preview.py
index 7bc2f6d..d036a23 100644
--- a/atr/post/preview.py
+++ b/atr/post/preview.py
@@ -16,7 +16,6 @@
 # under the License.
 
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.post as post
 import atr.construct as construct
@@ -45,9 +44,7 @@ class DeleteForm(forms.Typed):
 
 
 @post.committer("/preview/announce/<project_name>/<version_name>")
-async def announce_preview(
-    session: web.Committer, project_name: str, version_name: str
-) -> quart.wrappers.response.Response | str:
+async def announce_preview(session: web.Committer, project_name: str, 
version_name: str) -> web.QuartResponse | str:
     """Generate a preview of the announcement email body."""
 
     # TODO: Where does this come from? A static template?
@@ -77,7 +74,7 @@ async def announce_preview(
 
 
 @post.committer("/preview/delete")
-async def delete(session: web.Committer) -> response.Response:
+async def delete(session: web.Committer) -> web.WerkzeugResponse:
     """Delete a preview and all its associated files."""
     import atr.get.root as root
 
diff --git a/atr/post/projects.py b/atr/post/projects.py
index 21626af..5ce94c1 100644
--- a/atr/post/projects.py
+++ b/atr/post/projects.py
@@ -17,8 +17,6 @@
 
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
-
 import quart
 
 import atr.blueprints.post as post
@@ -28,17 +26,14 @@ import atr.storage as storage
 import atr.util as util
 import atr.web as web
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 
 @post.committer("/project/add/<committee_name>")
-async def add_project(session: web.Committer, committee_name: str) -> 
response.Response | str:
+async def add_project(session: web.Committer, committee_name: str) -> 
web.WerkzeugResponse | str:
     return await shared.projects.add_project(session, committee_name)
 
 
 @post.committer("/project/delete")
-async def delete(session: web.Committer) -> response.Response:
+async def delete(session: web.Committer) -> web.WerkzeugResponse:
     """Delete a project created by the user."""
     # TODO: This is not truly empty, so make a form object for this
     await util.validate_empty_form()
@@ -60,5 +55,5 @@ async def delete(session: web.Committer) -> response.Response:
 
 
 @post.committer("/projects/<name>")
-async def view(session: web.Committer, name: str) -> response.Response | str:
+async def view(session: web.Committer, name: str) -> web.WerkzeugResponse | 
str:
     return await shared.projects.view(session, name)
diff --git a/atr/post/resolve.py b/atr/post/resolve.py
index c85dcf8..98a3cfc 100644
--- a/atr/post/resolve.py
+++ b/atr/post/resolve.py
@@ -17,7 +17,6 @@
 
 
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.post as post
 import atr.db.interaction as interaction
@@ -33,7 +32,9 @@ import atr.web as web
 
 
 @post.committer("/resolve/manual/<project_name>/<version_name>")
-async def manual_selected_post(session: web.Committer, project_name: str, 
version_name: str) -> response.Response | str:
+async def manual_selected_post(
+    session: web.Committer, project_name: str, version_name: str
+) -> web.WerkzeugResponse | str:
     """Post the manual vote resolution page."""
     await session.check_access(project_name)
     release = await session.release(
@@ -71,7 +72,7 @@ async def manual_selected_post(session: web.Committer, 
project_name: str, versio
 
 
 @post.committer("/resolve/submit/<project_name>/<version_name>")
-async def submit_selected(session: web.Committer, project_name: str, 
version_name: str) -> response.Response | str:
+async def submit_selected(session: web.Committer, project_name: str, 
version_name: str) -> web.WerkzeugResponse | str:
     """Resolve a vote."""
     await session.check_access(project_name)
 
diff --git a/atr/post/revisions.py b/atr/post/revisions.py
index 51e0faa..effc0be 100644
--- a/atr/post/revisions.py
+++ b/atr/post/revisions.py
@@ -19,7 +19,6 @@
 import aioshutil
 import asfquart.base as base
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.post as post
 import atr.db as db
@@ -31,7 +30,7 @@ import atr.web as web
 
 
 @post.committer("/revisions/<project_name>/<version_name>")
-async def selected_post(session: web.Committer, project_name: str, 
version_name: str) -> response.Response:
+async def selected_post(session: web.Committer, project_name: str, 
version_name: str) -> web.WerkzeugResponse:
     """Set a specific revision as the latest for a candidate draft or release 
preview."""
     await session.check_access(project_name)
 
diff --git a/atr/post/sbom.py b/atr/post/sbom.py
index 354407a..4911009 100644
--- a/atr/post/sbom.py
+++ b/atr/post/sbom.py
@@ -18,7 +18,6 @@
 from __future__ import annotations
 
 import pathlib
-from typing import TYPE_CHECKING
 
 import asfquart.base as base
 import quart
@@ -31,12 +30,9 @@ import atr.storage as storage
 import atr.util as util
 import atr.web as web
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 
 @post.committer("/sbom/augment/<project_name>/<version_name>/<path:file_path>")
-async def augment(session: web.Committer, project_name: str, version_name: 
str, file_path: str) -> response.Response:
+async def augment(session: web.Committer, project_name: str, version_name: 
str, file_path: str) -> web.WerkzeugResponse:
     """Augment a CycloneDX SBOM file."""
     await session.check_access(project_name)
 
@@ -79,7 +75,7 @@ async def augment(session: web.Committer, project_name: str, 
version_name: str,
 
 
 @post.committer("/sbom/scan/<project_name>/<version_name>/<path:file_path>")
-async def scan(session: web.Committer, project_name: str, version_name: str, 
file_path: str) -> response.Response:
+async def scan(session: web.Committer, project_name: str, version_name: str, 
file_path: str) -> web.WerkzeugResponse:
     """Scan a CycloneDX SBOM file for vulnerabilities using OSV."""
     await session.check_access(project_name)
 
diff --git a/atr/post/start.py b/atr/post/start.py
index bc801e5..bb3aeb8 100644
--- a/atr/post/start.py
+++ b/atr/post/start.py
@@ -16,14 +16,12 @@
 # under the License.
 
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.post as post
 import atr.shared as shared
 import atr.web as web
 
 
 @post.committer("/start/<project_name>")
-async def selected(session: web.Committer, project_name: str) -> 
response.Response | str:
+async def selected(session: web.Committer, project_name: str) -> 
web.WerkzeugResponse | str:
     """Allow the user to start a new release draft, or handle its 
submission."""
     return await shared.start.selected(session, project_name)
diff --git a/atr/post/tokens.py b/atr/post/tokens.py
index 9482bf6..cae3e60 100644
--- a/atr/post/tokens.py
+++ b/atr/post/tokens.py
@@ -16,9 +16,6 @@
 # under the License.
 
 
-import quart
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.post as post
 import atr.jwtoken as jwtoken
 import atr.shared as shared
@@ -27,7 +24,7 @@ import atr.web as web
 
 
 @post.committer("/tokens/jwt")
-async def jwt_post(session: web.Committer) -> quart.Response:
+async def jwt_post(session: web.Committer) -> web.QuartResponse:
     await util.validate_empty_form()
 
     jwt_token = jwtoken.issue(session.uid)
@@ -35,5 +32,5 @@ async def jwt_post(session: web.Committer) -> quart.Response:
 
 
 @post.committer("/tokens")
-async def tokens(session: web.Committer) -> str | response.Response:
+async def tokens(session: web.Committer) -> str | web.WerkzeugResponse:
     return await shared.tokens.tokens(session)
diff --git a/atr/post/upload.py b/atr/post/upload.py
index 0d14c9b..5eb0f37 100644
--- a/atr/post/upload.py
+++ b/atr/post/upload.py
@@ -16,13 +16,11 @@
 # under the License.
 
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.post as post
 import atr.shared as shared
 import atr.web as web
 
 
 @post.committer("/upload/<project_name>/<version_name>")
-async def selected(session: web.Committer, project_name: str, version_name: 
str) -> response.Response | str:
+async def selected(session: web.Committer, project_name: str, version_name: 
str) -> web.WerkzeugResponse | str:
     return await shared.upload.selected(session, project_name, version_name)
diff --git a/atr/post/user.py b/atr/post/user.py
index 8db3963..aaf149a 100644
--- a/atr/post/user.py
+++ b/atr/post/user.py
@@ -17,7 +17,6 @@
 
 
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.post as post
 import atr.get as get
@@ -27,7 +26,7 @@ import atr.web as web
 
 
 @post.committer("/user/cache")
-async def session_post(session: web.Committer) -> response.Response:
+async def session_post(session: web.Committer) -> web.WerkzeugResponse:
     form_data = await quart.request.form
 
     cache_form = await shared.user.CacheForm.create_form(data=form_data)
diff --git a/atr/post/vote.py b/atr/post/vote.py
index a03da6d..81afd69 100644
--- a/atr/post/vote.py
+++ b/atr/post/vote.py
@@ -16,7 +16,6 @@
 # under the License.
 
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.blueprints.post as post
 import atr.forms as forms
@@ -28,7 +27,7 @@ import atr.web as web
 
 
 @post.committer("/vote/<project_name>/<version_name>")
-async def selected_post(session: web.Committer, project_name: str, 
version_name: str) -> response.Response:
+async def selected_post(session: web.Committer, project_name: str, 
version_name: str) -> web.WerkzeugResponse:
     """Handle submission of a vote."""
     await session.check_access(project_name)
 
diff --git a/atr/post/voting.py b/atr/post/voting.py
index 69dd0f3..11ab5be 100644
--- a/atr/post/voting.py
+++ b/atr/post/voting.py
@@ -16,8 +16,6 @@
 # under the License.
 
 
-import werkzeug.wrappers.response as response
-
 import atr.blueprints.post as post
 import atr.shared as shared
 import atr.web as web
@@ -26,6 +24,6 @@ import atr.web as web
 @post.committer("/voting/<project_name>/<version_name>/<revision>")
 async def selected_revision(
     session: web.Committer, project_name: str, version_name: str, revision: str
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     """Show the vote initiation form for a release."""
     return await shared.voting.selected_revision(session, project_name, 
version_name, revision)
diff --git a/atr/shared/__init__.py b/atr/shared/__init__.py
index 0675e01..a51a194 100644
--- a/atr/shared/__init__.py
+++ b/atr/shared/__init__.py
@@ -17,7 +17,6 @@
 
 from typing import TYPE_CHECKING, Final
 
-import werkzeug.wrappers.response as response
 import wtforms
 
 import atr.db as db
@@ -84,7 +83,7 @@ async def check(
     vote_task: sql.Task | None = None,
     can_vote: bool = False,
     can_resolve: bool = False,
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     base_path = util.release_directory(release)
 
     # TODO: This takes 180ms for providers
diff --git a/atr/shared/finish.py b/atr/shared/finish.py
index 8033ea3..d839dd3 100644
--- a/atr/shared/finish.py
+++ b/atr/shared/finish.py
@@ -23,9 +23,7 @@ from typing import Any
 import aiofiles.os
 import asfquart.base as base
 import quart
-import quart.wrappers.response as quart_response
 import werkzeug.datastructures as datastructures
-import werkzeug.wrappers.response as response
 import wtforms
 import wtforms.fields as fields
 
@@ -40,7 +38,7 @@ import atr.template as template
 import atr.util as util
 import atr.web as web
 
-type Respond = Callable[[int, str], Awaitable[tuple[quart_response.Response, 
int] | response.Response]]
+type Respond = Callable[[int, str], Awaitable[tuple[web.QuartResponse, int] | 
web.WerkzeugResponse]]
 
 
 class DeleteEmptyDirectoryForm(forms.Typed):
@@ -100,7 +98,7 @@ class RCTagAnalysisResult:
 
 async def selected(
     session: web.Committer, project_name: str, version_name: str
-) -> tuple[quart_response.Response, int] | response.Response | str:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse | str:
     """Finish a release preview."""
     await session.check_access(project_name)
 
@@ -109,7 +107,7 @@ async def selected(
     async def respond(
         http_status: int,
         msg: str,
-    ) -> tuple[quart_response.Response, int] | response.Response:
+    ) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse:
         """Helper to respond with JSON or flash message and redirect."""
         nonlocal session
         nonlocal project_name
@@ -248,7 +246,7 @@ async def _delete_empty_directory(
     project_name: str,
     version_name: str,
     respond: Respond,
-) -> tuple[quart_response.Response, int] | response.Response:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse:
     try:
         async with storage.write(session) as write:
             wacp = await write.as_project_committee_member(project_name)
@@ -269,7 +267,7 @@ async def _move_file_to_revision(
     project_name: str,
     version_name: str,
     respond: Respond,
-) -> tuple[quart_response.Response, int] | response.Response:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse:
     try:
         async with storage.write(session) as write:
             wacp = await write.as_project_committee_member(project_name)
@@ -310,7 +308,7 @@ async def _remove_rc_tags(
     project_name: str,
     version_name: str,
     respond: Respond,
-) -> tuple[quart_response.Response, int] | response.Response:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse:
     try:
         async with storage.write(session) as write:
             wacp = await write.as_project_committee_member(project_name)
@@ -362,7 +360,7 @@ async def _sources_and_targets(latest_revision_dir: 
pathlib.Path) -> tuple[list[
 
 async def _submission_process(
     args: ProcessFormDataArgs,
-) -> tuple[quart_response.Response, int] | response.Response | str | None:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse | str | None:
     delete_empty_directory = "submit_delete_empty_dir" in args.formdata
     remove_rc_tags = "submit_remove_rc_tags" in args.formdata
     move_file = ("source_files" in args.formdata) and ("target_directory" in 
args.formdata)
@@ -381,7 +379,7 @@ async def _submission_process(
 
 async def _submission_process_delete_empty_directory(
     args: ProcessFormDataArgs,
-) -> tuple[quart_response.Response, int] | response.Response | str | None:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse | str | None:
     if await args.delete_dir_form.validate_on_submit():
         dir_to_delete_str = args.delete_dir_form.directory_to_delete.data
         return await _delete_empty_directory(
@@ -402,7 +400,7 @@ async def _submission_process_delete_empty_directory(
 
 async def _submission_process_move_file(
     args: ProcessFormDataArgs,
-) -> tuple[quart_response.Response, int] | response.Response | str | None:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse | str | None:
     source_files_data = args.formdata.getlist("source_files")
     target_dir_data = args.formdata.get("target_directory")
 
@@ -419,7 +417,7 @@ async def _submission_process_move_file(
 
 async def _submission_process_remove_rc_tags(
     args: ProcessFormDataArgs,
-) -> tuple[quart_response.Response, int] | response.Response | str | None:
+) -> tuple[web.QuartResponse, int] | web.WerkzeugResponse | str | None:
     if await args.remove_rc_tags_form.validate_on_submit():
         return await _remove_rc_tags(args.session, args.project_name, 
args.version_name, args.respond)
     elif args.wants_json:
diff --git a/atr/shared/keys.py b/atr/shared/keys.py
index 563f8e8..3446b21 100644
--- a/atr/shared/keys.py
+++ b/atr/shared/keys.py
@@ -26,7 +26,6 @@ import asfquart as asfquart
 import asfquart.base as base
 import quart
 import werkzeug.datastructures as datastructures
-import werkzeug.wrappers.response as response
 import wtforms
 
 import atr.db as db
@@ -201,7 +200,7 @@ async def add(session: web.Committer) -> str:
     )
 
 
-async def details(session: web.Committer, fingerprint: str) -> str | 
response.Response:
+async def details(session: web.Committer, fingerprint: str) -> str | 
web.WerkzeugResponse:
     """Display details for a specific OpenPGP key."""
     fingerprint = fingerprint.lower()
     user_committees = []
@@ -263,7 +262,7 @@ async def details(session: web.Committer, fingerprint: str) 
-> str | response.Re
     )
 
 
-async def ssh_add(session: web.Committer) -> response.Response | str:
+async def ssh_add(session: web.Committer) -> web.WerkzeugResponse | str:
     """Add a new SSH key to the user's account."""
     # TODO: Make an auth.require wrapper that gives the session automatically
     # And the form if it's a POST handler? Might be hard to type
diff --git a/atr/shared/projects.py b/atr/shared/projects.py
index 1a98c87..8cf3ffd 100644
--- a/atr/shared/projects.py
+++ b/atr/shared/projects.py
@@ -20,7 +20,7 @@ from __future__ import annotations
 import datetime
 import http.client
 import re
-from typing import TYPE_CHECKING, Any
+from typing import Any
 
 import asfquart.base as base
 import quart
@@ -40,9 +40,6 @@ import atr.user as user
 import atr.util as util
 import atr.web as web
 
-if TYPE_CHECKING:
-    import werkzeug.wrappers.response as response
-
 
 class AddForm(forms.Typed):
     committee_name = forms.hidden()
@@ -229,7 +226,7 @@ class ReleasePolicyForm(forms.Typed):
         return not self.errors
 
 
-async def add_project(session: web.Committer, committee_name: str) -> 
response.Response | str:
+async def add_project(session: web.Committer, committee_name: str) -> 
web.WerkzeugResponse | str:
     await session.check_access_committee(committee_name)
 
     async with db.session() as data:
@@ -253,7 +250,7 @@ You must start with your committee label, and you must use 
lower case.
     return await template.render("project-add-project.html", form=form, 
committee_name=committee.display_name)
 
 
-async def view(session: web.Committer, name: str) -> response.Response | str:
+async def view(session: web.Committer, name: str) -> web.WerkzeugResponse | 
str:
     policy_form = None
     metadata_form = None
     can_edit = False
@@ -450,7 +447,7 @@ async def _policy_form_create(project: sql.Project) -> 
ReleasePolicyForm:
     return policy_form
 
 
-async def _project_add(form: AddForm, session: web.Committer) -> 
response.Response:
+async def _project_add(form: AddForm, session: web.Committer) -> 
web.WerkzeugResponse:
     form_values = await _project_add_validate(form)
     if form_values is None:
         return quart.redirect(util.as_url(get.projects.add_project, 
committee_name=form.committee_name.data))
diff --git a/atr/shared/start.py b/atr/shared/start.py
index f7eccdc..8566444 100644
--- a/atr/shared/start.py
+++ b/atr/shared/start.py
@@ -18,7 +18,6 @@
 
 import asfquart.base as base
 import quart
-import werkzeug.wrappers.response as response
 
 import atr.db as db
 import atr.db.interaction as interaction
@@ -40,7 +39,7 @@ class StartReleaseForm(forms.Typed):
     submit = forms.submit("Start new release")
 
 
-async def selected(session: web.Committer, project_name: str) -> 
response.Response | str:
+async def selected(session: web.Committer, project_name: str) -> 
web.WerkzeugResponse | str:
     """Allow the user to start a new release draft, or handle its 
submission."""
     await session.check_access(project_name)
 
diff --git a/atr/shared/tokens.py b/atr/shared/tokens.py
index 9525adc..7be60ae 100644
--- a/atr/shared/tokens.py
+++ b/atr/shared/tokens.py
@@ -26,7 +26,6 @@ import markupsafe
 import quart
 import sqlmodel
 import werkzeug.datastructures as datastructures
-import werkzeug.wrappers.response as response
 import wtforms.fields.core as core
 
 import atr.db as db
@@ -62,7 +61,7 @@ class IssueJWTForm(forms.Typed):
     submit = forms.submit("Generate JWT")
 
 
-async def tokens(session: web.Committer) -> str | response.Response:
+async def tokens(session: web.Committer) -> str | web.WerkzeugResponse:
     request_form = await quart.request.form
 
     if is_post := quart.request.method == "POST":
@@ -230,7 +229,7 @@ async def _delete_token(data: db.Session, uid: str, 
token_id: int) -> None:
         await data.delete(pat)
 
 
-async def _handle_post(session: web.Committer, request_form: 
datastructures.MultiDict) -> response.Response | None:
+async def _handle_post(session: web.Committer, request_form: 
datastructures.MultiDict) -> web.WerkzeugResponse | None:
     if "token_id" in request_form:
         return await _handle_delete_token_post(session, request_form)
 
@@ -242,7 +241,7 @@ async def _handle_post(session: web.Committer, 
request_form: datastructures.Mult
 
 async def _handle_add_token_post(
     session: web.Committer, request_form: datastructures.MultiDict
-) -> response.Response | None:
+) -> web.WerkzeugResponse | None:
     add_form = await AddTokenForm.create_form(data=request_form)
     if await add_form.validate_on_submit():
         label_val = str(add_form.label.data) if add_form.label.data else None
@@ -263,7 +262,7 @@ async def _handle_add_token_post(
 
 async def _handle_delete_token_post(
     session: web.Committer, request_form: datastructures.MultiDict
-) -> response.Response | None:
+) -> web.WerkzeugResponse | None:
     del_form = await DeleteTokenForm.create_form(data=request_form)
     if await del_form.validate_on_submit():
         token_id_val = int(str(del_form.token_id.data))
@@ -277,7 +276,7 @@ async def _handle_delete_token_post(
 
 async def _handle_issue_jwt_post(
     session: web.Committer, request_form: datastructures.MultiDict
-) -> response.Response | None:
+) -> web.WerkzeugResponse | None:
     issue_form = await IssueJWTForm.create_form(data=request_form)
     if await issue_form.validate_on_submit():
         jwt_token = jwtoken.issue(session.uid)
diff --git a/atr/shared/upload.py b/atr/shared/upload.py
index 0321898..75bfaa9 100644
--- a/atr/shared/upload.py
+++ b/atr/shared/upload.py
@@ -18,7 +18,6 @@
 import pathlib
 
 import quart
-import werkzeug.wrappers.response as response
 import wtforms
 
 import atr.db as db
@@ -61,7 +60,7 @@ class SvnImportForm(forms.Typed):
     submit = forms.submit("Queue SVN import task")
 
 
-async def selected(session: web.Committer, project_name: str, version_name: 
str) -> response.Response | str:
+async def selected(session: web.Committer, project_name: str, version_name: 
str) -> web.WerkzeugResponse | str:
     """Show a page to allow the user to add files to a candidate draft."""
     await session.check_access(project_name)
 
diff --git a/atr/shared/voting.py b/atr/shared/voting.py
index f0057ac..4a25a64 100644
--- a/atr/shared/voting.py
+++ b/atr/shared/voting.py
@@ -20,7 +20,6 @@ import aiofiles.os
 import asfquart.base as base
 import quart
 import quart_wtf.typing as typing
-import werkzeug.wrappers.response as response
 
 import atr.construct as construct
 import atr.db as db
@@ -56,7 +55,7 @@ class VoteInitiateForm(forms.Typed):
 
 async def selected_revision(
     session: web.Committer, project_name: str, version_name: str, revision: str
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     """Show the vote initiation form for a release."""
     await session.check_access(project_name)
 
@@ -114,7 +113,7 @@ async def start_vote_manual(
     selected_revision_number: str,
     session: web.Committer,
     _data: db.Session,
-) -> response.Response | str:
+) -> web.WerkzeugResponse | str:
     async with storage.write(session) as write:
         wacp = await 
write.as_project_committee_participant(release.project_name)
         # This verifies the state and sets the phase to RELEASE_CANDIDATE
@@ -196,7 +195,7 @@ async def _selected_revision_data(
     revision: str,
     data: db.Session,
     session: web.Committer,
-) -> response.Response | str | VoteInitiateForm:
+) -> web.WerkzeugResponse | str | VoteInitiateForm:
     committee = release.committee
     if committee is None:
         raise base.ASFQuartException("Release has no associated committee", 
errorcode=400)
diff --git a/atr/web.py b/atr/web.py
index 05f05cb..a843fde 100644
--- a/atr/web.py
+++ b/atr/web.py
@@ -40,6 +40,10 @@ if TYPE_CHECKING:
 
 R = TypeVar("R", covariant=True)
 
+type WerkzeugResponse = response.Response
+type QuartResponse = quart.Response
+type Response = WerkzeugResponse | QuartResponse
+
 
 class CommitterRouteFunction(Protocol[R]):
     """Protocol for @committer_get decorated functions."""
@@ -108,7 +112,7 @@ class Committer:
 
     async def redirect(
         self, route: CommitterRouteFunction[R], success: str | None = None, 
error: str | None = None, **kwargs: Any
-    ) -> response.Response:
+    ) -> WerkzeugResponse:
         """Redirect to a route with a success or error message."""
         return await redirect(route, success, error, **kwargs)
 
@@ -239,7 +243,7 @@ class ZipResponse(quart.Response):
 
 async def redirect[R](
     route: RouteFunction[R], success: str | None = None, error: str | None = 
None, **kwargs: Any
-) -> response.Response:
+) -> WerkzeugResponse:
     """Redirect to a route with a success or error message."""
     if success is not None:
         await quart.flash(success, "success")


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to