You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2025/03/27 12:17:27 UTC
(tooling-trusted-release) branch main updated: Use functions for public URL construction
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-release.git
The following commit(s) were added to refs/heads/main by this push:
new d021159 Use functions for public URL construction
d021159 is described below
commit d021159b6f91fbf126503624b038d1ac8ddcc26f
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Thu Mar 27 14:17:06 2025 +0200
Use functions for public URL construction
---
atr/routes/__init__.py | 27 ++++++++++++++++++++++++---
atr/routes/candidate.py | 4 ++--
atr/routes/candidate_draft.py | 14 +++++++-------
atr/routes/committees.py | 8 ++++----
atr/routes/dev.py | 2 +-
atr/routes/download.py | 2 +-
atr/routes/keys.py | 10 +++++-----
atr/routes/projects.py | 20 ++++++++++----------
atr/routes/release.py | 6 +++---
atr/templates/committee-directory.html | 2 +-
atr/templates/committee-view.html | 2 +-
atr/templates/includes/sidebar.html | 6 ++----
atr/templates/index.html | 2 +-
atr/templates/project-directory.html | 2 +-
atr/templates/project-view.html | 4 ++--
15 files changed, 65 insertions(+), 46 deletions(-)
diff --git a/atr/routes/__init__.py b/atr/routes/__init__.py
index e0a8f60..ead2d75 100644
--- a/atr/routes/__init__.py
+++ b/atr/routes/__init__.py
@@ -370,9 +370,7 @@ class RouteHandler(Protocol[R]):
# This decorator is an adaptor between @committer_get and @app_route functions
-def committer_route(
- path: str, methods: list[str] | None = None
-) -> Callable[[CommitterRouteHandler[R]], RouteHandler[R]]:
+def committer(path: str, methods: list[str] | None = None) -> Callable[[CommitterRouteHandler[R]], RouteHandler[R]]:
"""Decorator for committer GET routes that provides an enhanced session object."""
def decorator(func: CommitterRouteHandler[R]) -> RouteHandler[R]:
@@ -401,6 +399,29 @@ def committer_route(
return decorator
+def public(path: str, methods: list[str] | None = None) -> Callable[[RouteHandler[R]], RouteHandler[R]]:
+ """Decorator for public GET routes that provides an enhanced session object."""
+
+ def decorator(func: RouteHandler[R]) -> RouteHandler[R]:
+ async def wrapper(*args: Any, **kwargs: Any) -> R:
+ return await func(*args, **kwargs)
+
+ # Generate a unique endpoint name
+ endpoint = func.__module__ + "_" + func.__name__
+
+ # Set the name before applying decorators
+ wrapper.__name__ = func.__name__
+ wrapper.__doc__ = func.__doc__
+ wrapper.__annotations__["endpoint"] = endpoint
+
+ # Apply decorators in reverse order
+ decorated = app_route(path, methods=methods or ["GET"], endpoint=endpoint)(wrapper)
+
+ return decorated
+
+ return decorator
+
+
def _authentication_failed() -> NoReturn:
"""Handle authentication failure with an exception."""
# NOTE: This is a separate function to fix a problem with analysis flow in mypy
diff --git a/atr/routes/candidate.py b/atr/routes/candidate.py
index 6de758b..00f1c64 100644
--- a/atr/routes/candidate.py
+++ b/atr/routes/candidate.py
@@ -138,7 +138,7 @@ async def release_add_post(session: routes.CommitterSession, request: quart.Requ
# Root functions
-@routes.committer_route("/candidate/create", methods=["GET", "POST"])
+@routes.committer("/candidate/create", methods=["GET", "POST"])
async def create(session: routes.CommitterSession) -> response.Response | str:
"""Create a new release in the database."""
# For POST requests, handle the release creation
@@ -160,7 +160,7 @@ async def create(session: routes.CommitterSession) -> response.Response | str:
)
-@routes.committer_route("/candidate/review")
+@routes.committer("/candidate/review")
async def review(session: routes.CommitterSession) -> str:
"""Show all release candidates to which the user has access."""
async with db.session() as data:
diff --git a/atr/routes/candidate_draft.py b/atr/routes/candidate_draft.py
index 2de0b94..006c6ed 100644
--- a/atr/routes/candidate_draft.py
+++ b/atr/routes/candidate_draft.py
@@ -154,7 +154,7 @@ def _path_warnings_errors_metadata(
return warnings, errors
-@routes.committer_route("/candidate/draft/add")
+@routes.committer("/candidate/draft/add")
async def add(session: routes.CommitterSession) -> str:
"""Show a page to allow the user to rsync files to candidate drafts."""
# Do them outside of the template rendering call to ensure order
@@ -201,7 +201,7 @@ async def _add_one(
await f.write(chunk)
-@routes.committer_route("/candidate/draft/add/<project_name>/<version_name>", methods=["GET", "POST"])
+@routes.committer("/candidate/draft/add/<project_name>/<version_name>", methods=["GET", "POST"])
async def add_project(
session: routes.CommitterSession, project_name: str, version_name: str
) -> response.Response | str:
@@ -234,7 +234,7 @@ async def add_project(
)
-@routes.committer_route("/candidate/draft/files/<project_name>/<version_name>")
+@routes.committer("/candidate/draft/files/<project_name>/<version_name>")
async def files(session: routes.CommitterSession, project_name: str, version_name: str) -> str:
"""Show all the files in the rsync upload directory for a release."""
# Check that the user has access to the project
@@ -315,7 +315,7 @@ async def files(session: routes.CommitterSession, project_name: str, version_nam
)
-@routes.committer_route("/candidate/draft/checks/<project_name>/<version_name>/<path:file_path>")
+@routes.committer("/candidate/draft/checks/<project_name>/<version_name>/<path:file_path>")
async def checks(session: routes.CommitterSession, project_name: str, version_name: str, file_path: str) -> str:
"""Show the status of all checks for a specific file."""
# Check that the user has access to the project
@@ -367,7 +367,7 @@ async def checks(session: routes.CommitterSession, project_name: str, version_na
)
-@routes.committer_route("/candidate/draft/delete/<project_name>/<version_name>/<path:file_path>", methods=["POST"])
+@routes.committer("/candidate/draft/delete/<project_name>/<version_name>/<path:file_path>", methods=["POST"])
async def delete(
session: routes.CommitterSession, project_name: str, version_name: str, file_path: str
) -> response.Response:
@@ -395,7 +395,7 @@ async def delete(
return quart.redirect(util.as_url(files, project_name=project_name, version_name=version_name))
-@routes.committer_route("/candidate/draft/hashgen/<project_name>/<version_name>/<path:file_path>", methods=["POST"])
+@routes.committer("/candidate/draft/hashgen/<project_name>/<version_name>/<path:file_path>", methods=["POST"])
async def hashgen(
session: routes.CommitterSession, project_name: str, version_name: str, file_path: str
) -> response.Response:
@@ -451,7 +451,7 @@ async def hashgen(
return quart.redirect(util.as_url(files, project_name=project_name, version_name=version_name))
-@routes.committer_route("/candidate/draft/tools/<project_name>/<version_name>/<path:file_path>")
+@routes.committer("/candidate/draft/tools/<project_name>/<version_name>/<path:file_path>")
async def tools(session: routes.CommitterSession, project_name: str, version_name: str, file_path: str) -> str:
"""Show the tools for a specific file."""
# Check that the user has access to the project
diff --git a/atr/routes/committees.py b/atr/routes/committees.py
index 2c29c2b..e477c68 100644
--- a/atr/routes/committees.py
+++ b/atr/routes/committees.py
@@ -26,16 +26,16 @@ import atr.db.models as models
import atr.routes as routes
-@routes.app_route("/committees")
-async def root_committee_directory() -> str:
+@routes.public("/committees")
+async def directory() -> str:
"""Main committee directory page."""
async with db.session() as data:
committees = await data.committee(_projects=True).order_by(models.Committee.name).all()
return await quart.render_template("committee-directory.html", committees=committees)
-@routes.app_route("/committees/<name>")
-async def root_committee_view(name: str) -> str:
+@routes.public("/committees/<name>")
+async def view(name: str) -> str:
async with db.session() as data:
committee = await data.committee(name=name, _projects=True, _public_signing_keys=True).demand(
http.client.HTTPException(404)
diff --git a/atr/routes/dev.py b/atr/routes/dev.py
index 32d90af..32b94af 100644
--- a/atr/routes/dev.py
+++ b/atr/routes/dev.py
@@ -27,7 +27,7 @@ if asfquart.APP is ...:
raise RuntimeError("APP is not set")
-@routes.committer_route("/dev/send-email", methods=["GET", "POST"])
+@routes.committer("/dev/send-email", methods=["GET", "POST"])
async def send_email(session: routes.CommitterSession) -> quart.ResponseReturnValue:
"""Simple endpoint for testing email functionality."""
asf_id = session.uid
diff --git a/atr/routes/download.py b/atr/routes/download.py
index cf01c0b..b90f50c 100644
--- a/atr/routes/download.py
+++ b/atr/routes/download.py
@@ -29,7 +29,7 @@ import atr.routes.candidate as candidate
import atr.util as util
-@routes.committer_route("/download/<phase>/<project>/<version>/<path>")
+@routes.committer("/download/<phase>/<project>/<version>/<path>")
async def phase(
session: routes.CommitterSession, phase: str, project: str, version: str, path: pathlib.Path
) -> response.Response | quart.Response:
diff --git a/atr/routes/keys.py b/atr/routes/keys.py
index 2481d1b..a57ee3e 100644
--- a/atr/routes/keys.py
+++ b/atr/routes/keys.py
@@ -219,7 +219,7 @@ async def key_user_session_add(
}
-@routes.committer_route("/keys/add", methods=["GET", "POST"])
+@routes.committer("/keys/add", methods=["GET", "POST"])
async def add(session: routes.CommitterSession) -> str:
"""Add a new public signing key to the user's account."""
key_info = None
@@ -248,7 +248,7 @@ async def add(session: routes.CommitterSession) -> str:
)
-@routes.committer_route("/keys/delete", methods=["POST"])
+@routes.committer("/keys/delete", methods=["POST"])
async def delete(session: routes.CommitterSession) -> response.Response:
"""Delete a public signing key from the user's account."""
form = await routes.get_form(quart.request)
@@ -280,7 +280,7 @@ async def delete(session: routes.CommitterSession) -> response.Response:
return quart.redirect(util.as_url(review))
-@routes.committer_route("/keys/review")
+@routes.committer("/keys/review")
async def review(session: routes.CommitterSession) -> str:
"""Show all keys associated with the user's account."""
# Get all existing keys for the user
@@ -303,7 +303,7 @@ async def review(session: routes.CommitterSession) -> str:
)
-@routes.committer_route("/keys/ssh/add", methods=["GET", "POST"])
+@routes.committer("/keys/ssh/add", methods=["GET", "POST"])
async def ssh_add(session: routes.CommitterSession) -> response.Response | str:
"""Add a new SSH key to the user's account."""
# TODO: Make an auth.require wrapper that gives the session automatically
@@ -330,7 +330,7 @@ async def ssh_add(session: routes.CommitterSession) -> response.Response | str:
)
-@routes.committer_route("/keys/upload", methods=["GET", "POST"])
+@routes.committer("/keys/upload", methods=["GET", "POST"])
async def upload(session: routes.CommitterSession) -> str:
"""Upload a KEYS file containing multiple GPG keys."""
# Get committees for all projects the user is a member of
diff --git a/atr/routes/projects.py b/atr/routes/projects.py
index d20fbe3..7357f1b 100644
--- a/atr/routes/projects.py
+++ b/atr/routes/projects.py
@@ -31,16 +31,16 @@ import atr.routes as routes
import atr.util as util
-@routes.app_route("/projects")
-async def root_project_directory() -> str:
+@routes.public("/projects")
+async def directory() -> str:
"""Main project directory page."""
async with db.session() as data:
projects = await data.project(_committee=True).order_by(models.Project.full_name).all()
return await quart.render_template("project-directory.html", projects=projects)
-@routes.app_route("/projects/<name>")
-async def root_project_view(name: str) -> str:
+@routes.public("/projects/<name>")
+async def view(name: str) -> str:
async with db.session() as data:
project = await data.project(name=name, _committee_public_signing_keys=True, _vote_policy=True).demand(
http.client.HTTPException(404)
@@ -77,8 +77,8 @@ class VotePolicyForm(util.QuartFormTyped):
submit = wtforms.SubmitField("Save")
-@routes.app_route("/projects/<project_name>/voting-policy/add", methods=["GET", "POST"])
-async def root_projects_vote_policy_add(project_name: str) -> response.Response | str:
+@routes.committer("/projects/<project_name>/vote-policy/add", methods=["GET", "POST"])
+async def vote_policy_add(session: routes.CommitterSession, project_name: str) -> response.Response | str:
uid = await util.get_asf_id_or_die()
async with db.session() as data:
@@ -110,8 +110,8 @@ async def root_projects_vote_policy_add(project_name: str) -> response.Response
)
-@routes.committer_route("/projects/<project_name>/vote-policy/edit", methods=["GET", "POST"])
-async def projects_vote_policy_edit(session: routes.CommitterSession, project_name: str) -> response.Response | str:
+@routes.committer("/projects/<project_name>/vote-policy/edit", methods=["GET", "POST"])
+async def vote_policy_edit(session: routes.CommitterSession, project_name: str) -> response.Response | str:
uid = await util.get_asf_id_or_die()
async with db.session() as data:
@@ -161,7 +161,7 @@ async def _add_voting_policy(project: models.Project, form: VotePolicyForm, data
data.add(vote_policy)
await data.commit()
- return quart.redirect(quart.url_for("root_project_view", name=project_name))
+ return quart.redirect(util.as_url(view, name=project_name))
async def _edit_voting_policy(
@@ -176,4 +176,4 @@ async def _edit_voting_policy(
vote_policy.pause_for_rm = form.pause_for_rm.data
await data.commit()
- return quart.redirect(quart.url_for("root_project_view", name=project_name))
+ return quart.redirect(util.as_url(view, name=project_name))
diff --git a/atr/routes/release.py b/atr/routes/release.py
index 0aaa5d3..5392f96 100644
--- a/atr/routes/release.py
+++ b/atr/routes/release.py
@@ -57,7 +57,7 @@ async def release_delete_validate(data: db.Session, release_name: str, session_u
return release
-@routes.committer_route("/release/delete", methods=["POST"])
+@routes.committer("/release/delete", methods=["POST"])
async def delete(session: routes.CommitterSession) -> response.Response:
"""Delete a release and all its associated files."""
form = await routes.get_form(quart.request)
@@ -92,7 +92,7 @@ async def delete(session: routes.CommitterSession) -> response.Response:
return quart.redirect(util.as_url(candidate.review))
-@routes.committer_route("/release/bulk/<int:task_id>", methods=["GET"])
+@routes.committer("/release/bulk/<int:task_id>", methods=["GET"])
async def release_bulk_status(session: routes.CommitterSession, task_id: int) -> str | response.Response:
"""Show status for a bulk download task."""
async with db.session() as data:
@@ -130,7 +130,7 @@ async def release_bulk_status(session: routes.CommitterSession, task_id: int) ->
return await quart.render_template("release-bulk.html", task=task, release=release, TaskStatus=models.TaskStatus)
-@routes.committer_route("/release/vote", methods=["GET", "POST"])
+@routes.committer("/release/vote", methods=["GET", "POST"])
async def vote(session: routes.CommitterSession) -> response.Response | str:
"""Show the vote initiation form for a release."""
release_name = quart.request.args.get("release_name", "")
diff --git a/atr/templates/committee-directory.html b/atr/templates/committee-directory.html
index 6199122..318ba1b 100644
--- a/atr/templates/committee-directory.html
+++ b/atr/templates/committee-directory.html
@@ -27,7 +27,7 @@
{% for committee in committees %}
<div class="col">
<div class="card h-100 shadow-sm atr-cursor-pointer hover-lift project-card"
- data-project-url="{{ url_for('root_committee_view', name=committee.name) }}">
+ data-project-url="{{ as_url(routes.committees.view, name=committee.name) }}">
<div class="card-body">
<h3 class="card-title fs-4 mb-3">{{ committee.display_name }}</h3>
<div class="row g-3">
diff --git a/atr/templates/committee-view.html b/atr/templates/committee-view.html
index d870065..63207a5 100644
--- a/atr/templates/committee-view.html
+++ b/atr/templates/committee-view.html
@@ -33,7 +33,7 @@
<ul>
{% for project in committee.projects %}
<li>
- <a href="{{ url_for('root_project_view', name=project.name) }}">{{ project.display_name }}</a>
+ <a href="{{ as_url(routes.projects.view, name=project.name) }}">{{ project.display_name }}</a>
</li>
{% endfor %}
</ul>
diff --git a/atr/templates/includes/sidebar.html b/atr/templates/includes/sidebar.html
index c62a2d9..b43d8c0 100644
--- a/atr/templates/includes/sidebar.html
+++ b/atr/templates/includes/sidebar.html
@@ -36,13 +36,11 @@
</li>
<li>
<i class="fa-solid fa-diagram-project"></i>
- <a href="{{ url_for('root_committee_directory') }}"
- {% if request.endpoint == 'root_committee_directory' %}class="active"{% endif %}>Committees</a>
+ <a href="{{ as_url(routes.committees.directory) }}">Committees</a>
</li>
<li>
<i class="fa-solid fa-diagram-project"></i>
- <a href="{{ url_for('root_project_directory') }}"
- {% if request.endpoint == 'root_project_directory' %}class="active"{% endif %}>Projects</a>
+ <a href="{{ as_url(routes.projects.directory) }}">Projects</a>
</li>
</ul>
diff --git a/atr/templates/index.html b/atr/templates/index.html
index 2230730..5c37af8 100644
--- a/atr/templates/index.html
+++ b/atr/templates/index.html
@@ -29,7 +29,7 @@
<h2>Documentation</h2>
<ul>
<li>
- <a href="{{ url_for('root_project_directory') }}">Project directory and release manager assignments</a>
+ <a href="{{ as_url(routes.projects.directory) }}">Project directory and release manager assignments</a>
</li>
{% if current_user %}
<li>
diff --git a/atr/templates/project-directory.html b/atr/templates/project-directory.html
index 99f278c..0bb3925 100644
--- a/atr/templates/project-directory.html
+++ b/atr/templates/project-directory.html
@@ -27,7 +27,7 @@
{% for project in projects %}
<div class="col">
<div class="card h-100 shadow-sm atr-cursor-pointer hover-lift project-card {{ 'bg-body-secondary' if project.is_retired else '' }}"
- data-project-url="{{ url_for('root_project_view', name=project.name) }}">
+ data-project-url="{{ as_url(routes.projects.view, name=project.name) }}">
<div class="card-body">
<div class="row g-1">
<div class="col-sm">
diff --git a/atr/templates/project-view.html b/atr/templates/project-view.html
index 195e7e6..2a6216b 100644
--- a/atr/templates/project-view.html
+++ b/atr/templates/project-view.html
@@ -29,7 +29,7 @@
</div>
<div class="card-body">
<div class="d-flex flex-wrap gap-3 small mb-1">
- <a href="{{ url_for('root_committee_view', name=project.committee.name) }}">{{ project.committee.display_name }}</a>
+ <a href="{{ as_url(routes.committees.view, name=project.committee.name) }}">{{ project.committee.display_name }}</a>
</div>
</div>
</div>
@@ -118,7 +118,7 @@
{% elif (is_project_lead or is_admin) %}
<div>
<a class="btn btn-primary btn-sm"
- href="{{ url_for('root_projects_vote_policy_add', project_name=project.name) }}"><i class="fa-solid fa-plus"></i></a>
+ href="{{ as_url(routes.projects.vote_policy_add, project_name=project.name) }}"><i class="fa-solid fa-plus"></i></a>
</div>
{% endif %}
{% endif %}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org