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 f0b337e Move root, sbom, and start routes to the new layout
f0b337e is described below
commit f0b337e44deace48416192115491f1c7e2ab0149
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Oct 28 16:13:07 2025 +0000
Move root, sbom, and start routes to the new layout
---
atr/admin/__init__.py | 2 +-
atr/get/__init__.py | 6 ++
atr/get/download.py | 5 +-
atr/{routes => get}/root.py | 37 ++++-----
atr/{routes => get}/sbom.py | 107 ++----------------------
atr/{post/candidate.py => get/start.py} | 15 ++--
atr/post/__init__.py | 4 +
atr/post/candidate.py | 4 +-
atr/post/draft.py | 16 ++--
atr/post/preview.py | 3 +-
atr/post/sbom.py | 120 +++++++++++++++++++++++++++
atr/post/{candidate.py => start.py} | 13 ++-
atr/routes/__init__.py | 6 --
atr/routes/voting.py | 3 +-
atr/shared/__init__.py | 2 +
atr/shared/finish.py | 4 +-
atr/{routes => shared}/start.py | 3 +-
atr/templates/check-selected-path-table.html | 2 +-
atr/templates/check-selected.html | 2 +-
atr/templates/error.html | 2 +-
atr/templates/finish-selected.html | 2 +-
atr/templates/includes/sidebar.html | 12 +--
atr/templates/index-committer.html | 2 +-
atr/templates/notfound.html | 2 +-
atr/templates/project-view.html | 2 +-
atr/templates/releases-finished.html | 2 +-
atr/templates/revisions-selected.html | 2 +-
atr/templates/start-selected.html | 4 +-
atr/templates/todo.html | 19 -----
29 files changed, 206 insertions(+), 197 deletions(-)
diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index c369435..749028b 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -126,7 +126,7 @@ async def browse_as_post(session: web.Committer) -> str |
response.Response:
async def _browse_as(session: web.Committer) -> str | response.Response:
"""Allows an admin to browse as another user."""
# TODO: Enable this in debugging mode only?
- from atr.routes import root
+ import atr.get.root as root
form = await BrowseAsUserForm.create_form()
if not (await form.validate_on_submit()):
diff --git a/atr/get/__init__.py b/atr/get/__init__.py
index b142cc5..53a0ddd 100644
--- a/atr/get/__init__.py
+++ b/atr/get/__init__.py
@@ -39,6 +39,9 @@ import atr.get.release as release
import atr.get.report as report
import atr.get.resolve as resolve
import atr.get.revisions as revisions
+import atr.get.root as root
+import atr.get.sbom as sbom
+import atr.get.start as start
import atr.get.vote as vote
ROUTES_MODULE: Final[Literal[True]] = True
@@ -64,5 +67,8 @@ __all__ = [
"report",
"resolve",
"revisions",
+ "root",
+ "sbom",
+ "start",
"vote",
]
diff --git a/atr/get/download.py b/atr/get/download.py
index bcfef95..c1d81ed 100644
--- a/atr/get/download.py
+++ b/atr/get/download.py
@@ -31,7 +31,6 @@ import atr.db as db
import atr.htm as htm
import atr.mapping as mapping
import atr.models.sql as sql
-import atr.routes.root as root
import atr.template as template
import atr.util as util
import atr.web as web
@@ -40,6 +39,8 @@ 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:
"""Display download commands for a release."""
+ import atr.get.root as root
+
async with db.session() as data:
release = await session.release(project_name=project_name,
version_name=version_name, phase=None, data=data)
if not release:
@@ -147,6 +148,8 @@ async def zip_selected(
async def _download_or_list(project_name: str, version_name: str, file_path:
str) -> response.Response | quart.Response:
"""Download a file or list a directory from a release in any phase."""
+ import atr.get.root as root
+
# await session.check_access(project_name)
# Check that path is relative
diff --git a/atr/routes/root.py b/atr/get/root.py
similarity index 87%
rename from atr/routes/root.py
rename to atr/get/root.py
index 2744e15..6fd33b5 100644
--- a/atr/routes/root.py
+++ b/atr/get/root.py
@@ -15,8 +15,6 @@
# specific language governing permissions and limitations
# under the License.
-"""root.py"""
-
import pathlib
from typing import Final
@@ -28,14 +26,15 @@ import sqlalchemy.orm as orm
import sqlmodel
import werkzeug.wrappers.response as response
+import atr.blueprints.get as get
import atr.config as config
import atr.db as db
import atr.htm as htm
import atr.models.sql as sql
-import atr.route as route
import atr.template as template
import atr.user as user
import atr.util as util
+import atr.web as web
_POLICIES: Final = htm.div[
htm.h1["Release policy"],
@@ -61,14 +60,14 @@ _POLICIES: Final = htm.div[
]
[email protected]("/about")
-async def about(session: route.CommitterSession) -> str:
[email protected]("/about")
+async def about(session: web.Committer) -> str:
"""About page."""
return await template.render("about.html")
[email protected]("/")
-async def index(session: route.CommitterSession | None) ->
quart_response.Response | str:
[email protected]("/")
+async def index(session: web.Committer | None) -> quart_response.Response |
str:
"""Show public info or an entry portal for participants."""
session_data = await asfquart.session.read()
if session_data:
@@ -142,21 +141,21 @@ async def index(session: route.CommitterSession | None)
-> quart_response.Respon
return await template.render("index-public.html")
[email protected]("/miscellaneous/resolved.json")
-async def resolved_json(session: route.CommitterSession | None) ->
quart_response.Response:
[email protected]("/miscellaneous/resolved.json")
+async def resolved_json(session: web.Committer | None) ->
quart_response.Response:
json_path = pathlib.Path(config.get().PROJECT_ROOT) / "atr" / "static" /
"json" / "resolved.json"
async with aiofiles.open(json_path) as f:
content = await f.read()
return quart_response.Response(content, mimetype="application/json")
[email protected]("/policies")
-async def policies(session: route.CommitterSession | None) -> str:
[email protected]("/policies")
+async def policies(session: web.Committer | None) -> str:
return await template.blank("Policies", content=_POLICIES)
[email protected]("/test-login")
-async def test_login(session: route.CommitterSession | None) ->
response.Response:
[email protected]("/test-login")
+async def test_login(session: web.Committer | None) -> response.Response:
if not config.get().ALLOW_TESTS:
raise base.ASFQuartException("Test login not enabled", errorcode=404)
@@ -172,16 +171,10 @@ async def test_login(session: route.CommitterSession |
None) -> response.Respons
}
asfquart.session.write(session_data)
- return await route.redirect(index)
-
-
[email protected]("/todo", methods=["POST"])
-async def todo(session: route.CommitterSession) -> str:
- """POST target for development."""
- return await template.render("todo.html")
+ return await web.redirect(index)
[email protected]("/tutorial")
-async def tutorial(session: route.CommitterSession) -> str:
[email protected]("/tutorial")
+async def tutorial(session: web.Committer) -> str:
"""Tutorial page."""
return await template.render("tutorial.html")
diff --git a/atr/routes/sbom.py b/atr/get/sbom.py
similarity index 74%
rename from atr/routes/sbom.py
rename to atr/get/sbom.py
index 8ae5245..8e432d2 100644
--- a/atr/routes/sbom.py
+++ b/atr/get/sbom.py
@@ -18,78 +18,29 @@
from __future__ import annotations
import json
-import pathlib
from typing import TYPE_CHECKING, Any
import asfquart.base as base
import markupsafe
-import quart
+import atr.blueprints.get as get
import atr.db as db
import atr.forms as forms
import atr.htm as htm
-import atr.log as log
import atr.models.results as results
import atr.models.sql as sql
-import atr.route as route
+import atr.post as post
import atr.sbom as sbom
-import atr.storage as storage
import atr.template as template
import atr.util as util
+import atr.web as web
if TYPE_CHECKING:
import collections.abc
- import werkzeug.wrappers.response as response
-
[email protected]("/sbom/augment/<project_name>/<version_name>/<path:file_path>",
methods=["POST"])
-async def augment(
- session: route.CommitterSession, project_name: str, version_name: str,
file_path: str
-) -> response.Response:
- """Augment a CycloneDX SBOM file."""
- await session.check_access(project_name)
-
- await util.validate_empty_form()
- rel_path = pathlib.Path(file_path)
-
- # Check that the file is a .cdx.json archive before creating a revision
- if not (file_path.endswith(".cdx.json")):
- raise base.ASFQuartException("SBOM augmentation is only supported for
.cdx.json files", errorcode=400)
-
- try:
- async with db.session() as data:
- release = await data.release(project_name=project_name,
version=version_name).demand(
- RuntimeError("Release does not exist for new revision
creation")
- )
- revision_number = release.latest_revision_number
- if revision_number is None:
- raise RuntimeError("No revision number found for new revision
creation")
- log.info(f"Augmenting SBOM for {project_name} {version_name}
{revision_number} {rel_path}")
- async with storage.write_as_project_committee_member(project_name) as
wacm:
- sbom_task = await wacm.sbom.augment_cyclonedx(project_name,
version_name, revision_number, rel_path)
-
- except Exception as e:
- log.exception("Error augmenting SBOM:")
- await quart.flash(f"Error augmenting SBOM: {e!s}", "error")
- return await session.redirect(
- report,
- project=project_name,
- version=version_name,
- file_path=str(rel_path),
- )
-
- return await session.redirect(
- report,
- success=f"SBOM augmentation task queued for {rel_path.name} (task ID:
{util.unwrap(sbom_task.id)})",
- project=project_name,
- version=version_name,
- file_path=str(rel_path),
- )
-
-
[email protected]("/sbom/report/<project>/<version>/<path:file_path>")
-async def report(session: route.CommitterSession, project: str, version: str,
file_path: str) -> str:
[email protected]("/sbom/report/<project>/<version>/<path:file_path>")
+async def report(session: web.Committer, project: str, version: str,
file_path: str) -> str:
await session.check_access(project)
await session.release(project, version)
async with db.session() as data:
@@ -144,7 +95,7 @@ async def report(session: route.CommitterSession, project:
str, version: str, fi
# TODO: Add a field to the SBOM to show that it's been augmented
# And then don't allow it to be augmented again
action = util.as_url(
- augment,
+ post.sbom.augment,
project_name=project,
version_name=version,
file_path=file_path,
@@ -200,50 +151,6 @@ async def report(session: route.CommitterSession, project:
str, version: str, fi
return await template.blank("SBOM report", content=block.collect())
[email protected]("/sbom/scan/<project_name>/<version_name>/<path:file_path>",
methods=["POST"])
-async def scan(
- session: route.CommitterSession, project_name: str, version_name: str,
file_path: str
-) -> response.Response:
- """Scan a CycloneDX SBOM file for vulnerabilities using OSV."""
- await session.check_access(project_name)
-
- await util.validate_empty_form()
- rel_path = pathlib.Path(file_path)
-
- if not (file_path.endswith(".cdx.json")):
- raise base.ASFQuartException("OSV scanning is only supported for
.cdx.json files", errorcode=400)
-
- try:
- async with db.session() as data:
- release = await data.release(project_name=project_name,
version=version_name).demand(
- RuntimeError("Release does not exist for OSV scan")
- )
- revision_number = release.latest_revision_number
- if revision_number is None:
- raise RuntimeError("No revision number found for OSV scan")
- log.info(f"Starting OSV scan for {project_name} {version_name}
{revision_number} {rel_path}")
- async with storage.write_as_project_committee_member(project_name) as
wacm:
- sbom_task = await wacm.sbom.osv_scan_cyclonedx(project_name,
version_name, revision_number, rel_path)
-
- except Exception as e:
- log.exception("Error starting OSV scan:")
- await quart.flash(f"Error starting OSV scan: {e!s}", "error")
- return await session.redirect(
- report,
- project=project_name,
- version=version_name,
- file_path=str(rel_path),
- )
-
- return await session.redirect(
- report,
- success=f"OSV vulnerability scan queued for {rel_path.name} (task ID:
{util.unwrap(sbom_task.id)})",
- project=project_name,
- version=version_name,
- file_path=str(rel_path),
- )
-
-
def _extract_vulnerability_severity(vuln: dict[str, Any]) -> str:
"""Extract severity information from vulnerability data."""
db_specific = vuln.get("database_specific", {})
@@ -323,7 +230,7 @@ def _vulnerability_scan_button(
block.p["No vulnerability scan has been performed for this revision."]
action = util.as_url(
- scan,
+ post.sbom.scan,
project_name=project,
version_name=version,
file_path=file_path,
diff --git a/atr/post/candidate.py b/atr/get/start.py
similarity index 70%
copy from atr/post/candidate.py
copy to atr/get/start.py
index ca0b011..1ebbb7d 100644
--- a/atr/post/candidate.py
+++ b/atr/get/start.py
@@ -15,16 +15,15 @@
# specific language governing permissions and limitations
# under the License.
+
import werkzeug.wrappers.response as response
-import atr.blueprints.post as post
+import atr.blueprints.get as get
+import atr.shared as shared
import atr.web as web
[email protected]("/candidate/delete")
-async def delete(session: web.Committer) -> response.Response:
- """Delete a release candidate."""
- import atr.routes.root as root
-
- # TODO: We need to never retire revisions, if allowing release deletion
- return await session.redirect(root.index, error="Not yet implemented")
[email protected]("/start/<project_name>")
+async def selected(session: web.Committer, project_name: str) ->
response.Response | 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/__init__.py b/atr/post/__init__.py
index 5f4d930..e3ce6cf 100644
--- a/atr/post/__init__.py
+++ b/atr/post/__init__.py
@@ -28,6 +28,8 @@ import atr.post.preview as preview
import atr.post.projects as projects
import atr.post.resolve as resolve
import atr.post.revisions as revisions
+import atr.post.sbom as sbom
+import atr.post.start as start
import atr.post.vote as vote
ROUTES_MODULE: Final[Literal[True]] = True
@@ -44,5 +46,7 @@ __all__ = [
"projects",
"resolve",
"revisions",
+ "sbom",
+ "start",
"vote",
]
diff --git a/atr/post/candidate.py b/atr/post/candidate.py
index ca0b011..1f2a426 100644
--- a/atr/post/candidate.py
+++ b/atr/post/candidate.py
@@ -24,7 +24,7 @@ import atr.web as web
@post.committer("/candidate/delete")
async def delete(session: web.Committer) -> response.Response:
"""Delete a release candidate."""
- import atr.routes.root as root
+ import atr.get as get
# TODO: We need to never retire revisions, if allowing release deletion
- return await session.redirect(root.index, error="Not yet implemented")
+ return await session.redirect(get.root.index, error="Not yet implemented")
diff --git a/atr/post/draft.py b/atr/post/draft.py
index 0860d3f..11830e3 100644
--- a/atr/post/draft.py
+++ b/atr/post/draft.py
@@ -51,26 +51,26 @@ class VotePreviewForm(forms.Typed):
@post.committer("/draft/delete")
async def delete(session: web.Committer) -> response.Response:
"""Delete a candidate draft and all its associated files."""
- import atr.routes.root as root
+ import atr.get as get
form = await shared.draft.DeleteForm.create_form(data=await
quart.request.form)
if not await form.validate_on_submit():
for _field, errors in form.errors.items():
for error in errors:
await quart.flash(f"{error}", "error")
- return await session.redirect(root.index)
+ return await session.redirect(get.root.index)
release_name = form.release_name.data
if not release_name:
- return await session.redirect(root.index, error="Missing required
parameters")
+ return await session.redirect(get.root.index, error="Missing required
parameters")
project_name = form.project_name.data
if not project_name:
- return await session.redirect(root.index, error="Missing required
parameters")
+ return await session.redirect(get.root.index, error="Missing required
parameters")
version_name = form.version_name.data
if not version_name:
- return await session.redirect(root.index, error="Missing required
parameters")
+ return await session.redirect(get.root.index, error="Missing required
parameters")
await session.check_access(project_name)
@@ -91,7 +91,7 @@ async def delete(session: web.Committer) -> response.Response:
# Yet it works in preview.py
await aioshutil.rmtree(draft_dir) # type: ignore[call-arg]
- return await session.redirect(root.index, success="Candidate draft deleted
successfully")
+ return await session.redirect(get.root.index, success="Candidate draft
deleted successfully")
@post.committer("/draft/delete-file/<project_name>/<version_name>")
@@ -296,11 +296,11 @@ async def vote_preview(
session: web.Committer, project_name: str, version_name: str
) -> quart.wrappers.response.Response | response.Response | str:
"""Show the vote email preview for a release."""
- import atr.routes.root as root
+ import atr.get as get
form = await VotePreviewForm.create_form(data=await quart.request.form)
if not await form.validate_on_submit():
- return await session.redirect(root.index, error="Invalid form data")
+ return await session.redirect(get.root.index, error="Invalid form
data")
release = await session.release(project_name, version_name)
if release.committee is None:
diff --git a/atr/post/preview.py b/atr/post/preview.py
index 9e55944..7bc2f6d 100644
--- a/atr/post/preview.py
+++ b/atr/post/preview.py
@@ -23,7 +23,6 @@ import atr.construct as construct
import atr.forms as forms
import atr.log as log
import atr.models.sql as sql
-import atr.routes.root as root
import atr.storage as storage
import atr.web as web
@@ -80,6 +79,8 @@ async def announce_preview(
@post.committer("/preview/delete")
async def delete(session: web.Committer) -> response.Response:
"""Delete a preview and all its associated files."""
+ import atr.get.root as root
+
# TODO: Where does this come from? A static template?
form = await DeleteForm.create_form(data=await quart.request.form)
diff --git a/atr/post/sbom.py b/atr/post/sbom.py
new file mode 100644
index 0000000..354407a
--- /dev/null
+++ b/atr/post/sbom.py
@@ -0,0 +1,120 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+import pathlib
+from typing import TYPE_CHECKING
+
+import asfquart.base as base
+import quart
+
+import atr.blueprints.post as post
+import atr.db as db
+import atr.get as get
+import atr.log as log
+import atr.storage as storage
+import atr.util as util
+import atr.web as web
+
+if TYPE_CHECKING:
+ import werkzeug.wrappers.response as response
+
+
[email protected]("/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:
+ """Augment a CycloneDX SBOM file."""
+ await session.check_access(project_name)
+
+ await util.validate_empty_form()
+ rel_path = pathlib.Path(file_path)
+
+ # Check that the file is a .cdx.json archive before creating a revision
+ if not (file_path.endswith(".cdx.json")):
+ raise base.ASFQuartException("SBOM augmentation is only supported for
.cdx.json files", errorcode=400)
+
+ try:
+ async with db.session() as data:
+ release = await data.release(project_name=project_name,
version=version_name).demand(
+ RuntimeError("Release does not exist for new revision
creation")
+ )
+ revision_number = release.latest_revision_number
+ if revision_number is None:
+ raise RuntimeError("No revision number found for new revision
creation")
+ log.info(f"Augmenting SBOM for {project_name} {version_name}
{revision_number} {rel_path}")
+ async with storage.write_as_project_committee_member(project_name) as
wacm:
+ sbom_task = await wacm.sbom.augment_cyclonedx(project_name,
version_name, revision_number, rel_path)
+
+ except Exception as e:
+ log.exception("Error augmenting SBOM:")
+ await quart.flash(f"Error augmenting SBOM: {e!s}", "error")
+ return await session.redirect(
+ get.sbom.report,
+ project=project_name,
+ version=version_name,
+ file_path=str(rel_path),
+ )
+
+ return await session.redirect(
+ get.sbom.report,
+ success=f"SBOM augmentation task queued for {rel_path.name} (task ID:
{util.unwrap(sbom_task.id)})",
+ project=project_name,
+ version=version_name,
+ file_path=str(rel_path),
+ )
+
+
[email protected]("/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:
+ """Scan a CycloneDX SBOM file for vulnerabilities using OSV."""
+ await session.check_access(project_name)
+
+ await util.validate_empty_form()
+ rel_path = pathlib.Path(file_path)
+
+ if not (file_path.endswith(".cdx.json")):
+ raise base.ASFQuartException("OSV scanning is only supported for
.cdx.json files", errorcode=400)
+
+ try:
+ async with db.session() as data:
+ release = await data.release(project_name=project_name,
version=version_name).demand(
+ RuntimeError("Release does not exist for OSV scan")
+ )
+ revision_number = release.latest_revision_number
+ if revision_number is None:
+ raise RuntimeError("No revision number found for OSV scan")
+ log.info(f"Starting OSV scan for {project_name} {version_name}
{revision_number} {rel_path}")
+ async with storage.write_as_project_committee_member(project_name) as
wacm:
+ sbom_task = await wacm.sbom.osv_scan_cyclonedx(project_name,
version_name, revision_number, rel_path)
+
+ except Exception as e:
+ log.exception("Error starting OSV scan:")
+ await quart.flash(f"Error starting OSV scan: {e!s}", "error")
+ return await session.redirect(
+ get.sbom.report,
+ project=project_name,
+ version=version_name,
+ file_path=str(rel_path),
+ )
+
+ return await session.redirect(
+ get.sbom.report,
+ success=f"OSV vulnerability scan queued for {rel_path.name} (task ID:
{util.unwrap(sbom_task.id)})",
+ project=project_name,
+ version=version_name,
+ file_path=str(rel_path),
+ )
diff --git a/atr/post/candidate.py b/atr/post/start.py
similarity index 73%
copy from atr/post/candidate.py
copy to atr/post/start.py
index ca0b011..bc801e5 100644
--- a/atr/post/candidate.py
+++ b/atr/post/start.py
@@ -15,16 +15,15 @@
# specific language governing permissions and limitations
# 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
[email protected]("/candidate/delete")
-async def delete(session: web.Committer) -> response.Response:
- """Delete a release candidate."""
- import atr.routes.root as root
-
- # TODO: We need to never retire revisions, if allowing release deletion
- return await session.redirect(root.index, error="Not yet implemented")
[email protected]("/start/<project_name>")
+async def selected(session: web.Committer, project_name: str) ->
response.Response | 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/routes/__init__.py b/atr/routes/__init__.py
index c4dfc96..30b164b 100644
--- a/atr/routes/__init__.py
+++ b/atr/routes/__init__.py
@@ -15,18 +15,12 @@
# specific language governing permissions and limitations
# under the License.
-import atr.routes.root as root
-import atr.routes.sbom as sbom
-import atr.routes.start as start
import atr.routes.tokens as tokens
import atr.routes.upload as upload
import atr.routes.user as user
import atr.routes.voting as voting
__all__ = [
- "root",
- "sbom",
- "start",
"tokens",
"upload",
"user",
diff --git a/atr/routes/voting.py b/atr/routes/voting.py
index 6c18027..0e84a10 100644
--- a/atr/routes/voting.py
+++ b/atr/routes/voting.py
@@ -31,7 +31,6 @@ import atr.get.vote as vote
import atr.log as log
import atr.models.sql as sql
import atr.route as route
-import atr.routes.root as root
import atr.storage as storage
import atr.template as template
import atr.user as user
@@ -122,6 +121,8 @@ async def start_vote_manual(
# This verifies the state and sets the phase to RELEASE_CANDIDATE
error = await wacp.release.promote_to_candidate(release.name,
selected_revision_number, vote_manual=True)
if error:
+ import atr.get.root as root
+
return await session.redirect(root.index, error=error)
return await session.redirect(
vote.selected,
diff --git a/atr/shared/__init__.py b/atr/shared/__init__.py
index e958e06..822ed7c 100644
--- a/atr/shared/__init__.py
+++ b/atr/shared/__init__.py
@@ -33,6 +33,7 @@ import atr.shared.ignores as ignores
import atr.shared.keys as keys
import atr.shared.projects as projects
import atr.shared.resolve as resolve
+import atr.shared.start as start
import atr.shared.vote as vote
import atr.storage as storage
import atr.template as template
@@ -188,5 +189,6 @@ __all__ = [
"keys",
"projects",
"resolve",
+ "start",
"vote",
]
diff --git a/atr/shared/finish.py b/atr/shared/finish.py
index 607ce98..c197946 100644
--- a/atr/shared/finish.py
+++ b/atr/shared/finish.py
@@ -136,10 +136,10 @@ async def selected(
try:
source_files_rel, target_dirs = await
_sources_and_targets(latest_revision_dir)
except FileNotFoundError:
- import atr.routes.root as root
+ import atr.get as get
await quart.flash("Preview revision directory not found.", "error")
- return await session.redirect(root.index)
+ return await session.redirect(get.root.index)
formdata = None
if quart.request.method == "POST":
diff --git a/atr/routes/start.py b/atr/shared/start.py
similarity index 95%
rename from atr/routes/start.py
rename to atr/shared/start.py
index a1c9917..307dc2c 100644
--- a/atr/routes/start.py
+++ b/atr/shared/start.py
@@ -41,8 +41,7 @@ class StartReleaseForm(forms.Typed):
submit = forms.submit("Start new release")
[email protected]("/start/<project_name>", methods=["GET", "POST"])
-async def selected(session: route.CommitterSession, project_name: str) ->
response.Response | str:
+async def selected(session: web.Committer, project_name: str) ->
response.Response | str:
"""Allow the user to start a new release draft, or handle its
submission."""
await session.check_access(project_name)
diff --git a/atr/templates/check-selected-path-table.html
b/atr/templates/check-selected-path-table.html
index 9f5ed05..42f95d7 100644
--- a/atr/templates/check-selected-path-table.html
+++ b/atr/templates/check-selected-path-table.html
@@ -66,7 +66,7 @@
</form>
{% endif %}
{% if path.suffixes[-2:] == [".cdx", ".json"] %}
- <a href="{{ as_url(routes.sbom.report, project=project_name,
version=version_name, file_path=path) }}"
+ <a href="{{ as_url(get.sbom.report, project=project_name,
version=version_name, file_path=path) }}"
class="btn btn-sm btn-outline-secondary">Show SBOM</a>
{% endif %}
{% if has_errors %}
diff --git a/atr/templates/check-selected.html
b/atr/templates/check-selected.html
index d229cd0..1c20499 100644
--- a/atr/templates/check-selected.html
+++ b/atr/templates/check-selected.html
@@ -31,7 +31,7 @@
{% block content %}
{% set phase = release.phase.value %}
<p class="d-flex justify-content-between align-items-center">
- <a href="{{ as_url(routes.root.index) }}" class="atr-back-link">← Back to
Select a release</a>
+ <a href="{{ as_url(get.root.index) }}" class="atr-back-link">← Back to
Select a release</a>
<span>
{% if phase == "release_candidate_draft" %}
<strong class="atr-phase-one atr-phase-symbol">①</strong>
diff --git a/atr/templates/error.html b/atr/templates/error.html
index 3a76089..ba6d8ca 100644
--- a/atr/templates/error.html
+++ b/atr/templates/error.html
@@ -30,7 +30,7 @@
</div>
<div class="mt-4">
- <a href="{{ as_url(routes.root.index) }}" class="btn btn-primary">Return
to Home</a>
+ <a href="{{ as_url(get.root.index) }}" class="btn btn-primary">Return to
Home</a>
</div>
</div>
{% endblock content %}
diff --git a/atr/templates/finish-selected.html
b/atr/templates/finish-selected.html
index 34794c5..a381335 100644
--- a/atr/templates/finish-selected.html
+++ b/atr/templates/finish-selected.html
@@ -43,7 +43,7 @@
{% block content %}
<p class="d-flex justify-content-between align-items-center">
- <a href="{{ as_url(routes.root.index) }}" class="atr-back-link">← Back to
Select a release</a>
+ <a href="{{ as_url(get.root.index) }}" class="atr-back-link">← Back to
Select a release</a>
<span>
<span class="atr-phase-symbol-other">①</span>
<span class="atr-phase-arrow">→</span>
diff --git a/atr/templates/includes/sidebar.html
b/atr/templates/includes/sidebar.html
index 882bd8a..c9f860d 100644
--- a/atr/templates/includes/sidebar.html
+++ b/atr/templates/includes/sidebar.html
@@ -1,6 +1,6 @@
<aside class="sidebar">
<div class="sidebar-header">
- <a href="{{ as_url(routes.root.index) }}" class="site-title">
+ <a href="{{ as_url(get.root.index) }}" class="site-title">
<h1>
<span class="apache">A<span class="rest">pache</span></span>
<br />
@@ -33,7 +33,7 @@
<ul>
<li>
<i class="bi bi-play-circle"></i>
- <a href="{{ as_url(routes.root.index) }}">Make releases</a>
+ <a href="{{ as_url(get.root.index) }}">Make releases</a>
</li>
{% if current_user %}
<li>
@@ -88,13 +88,13 @@
{% for project_name, project_full_name in user_projects[:max_projects]
%}
<li>
<i class="bi bi-plus-circle"></i>
- <a href="{{ as_url(routes.start.selected,
project_name=project_name) }}">{{ project_full_name.removeprefix("Apache
").removesuffix(" (Incubating)") }}</a>
+ <a href="{{ as_url(get.start.selected, project_name=project_name)
}}">{{ project_full_name.removeprefix("Apache ").removesuffix(" (Incubating)")
}}</a>
</li>
{% endfor %}
{% if user_projects|length > max_projects %}
<li>
<i class="bi bi-three-dots"></i>
- (<a href="{{ as_url(routes.root.index) }}">{{ user_projects|length
- max_projects }} more</a>)
+ (<a href="{{ as_url(get.root.index) }}">{{ user_projects|length -
max_projects }} more</a>)
</li>
{% endif %}
</ul>
@@ -117,7 +117,7 @@
<ul>
<li>
<i class="bi bi-book"></i>
- <a href="{{ as_url(routes.root.tutorial) }}">Tutorial</a>
+ <a href="{{ as_url(get.root.tutorial) }}">Tutorial</a>
</li>
<li>
<i class="bi bi-book"></i>
@@ -125,7 +125,7 @@
</li>
<li>
<i class="bi bi-book"></i>
- <a href="{{ as_url(routes.root.policies) }}">ASF Policies</a>
+ <a href="{{ as_url(get.root.policies) }}">ASF Policies</a>
</li>
<li>
<i class="bi bi-file-earmark-code"></i>
diff --git a/atr/templates/index-committer.html
b/atr/templates/index-committer.html
index 74e7138..e2e1030 100644
--- a/atr/templates/index-committer.html
+++ b/atr/templates/index-committer.html
@@ -143,7 +143,7 @@
</a>
{% endfor %}
- <a href="{{ as_url(routes.start.selected, project_name=project.name)
}}"
+ <a href="{{ as_url(get.start.selected, project_name=project.name) }}"
title="Start a new {{ project.display_name }} release"
class="text-decoration-none">
<div class="card h-100 shadow-sm atr-cursor-pointer page-card">
diff --git a/atr/templates/notfound.html b/atr/templates/notfound.html
index f559638..3e1dde7 100644
--- a/atr/templates/notfound.html
+++ b/atr/templates/notfound.html
@@ -25,7 +25,7 @@
</div>
<div class="mt-4">
- <a href="{{ as_url(routes.root.index) }}" class="btn btn-primary">Return
to Home</a>
+ <a href="{{ as_url(get.root.index) }}" class="btn btn-primary">Return to
Home</a>
</div>
</div>
{% endblock content %}
diff --git a/atr/templates/project-view.html b/atr/templates/project-view.html
index a57a426..a958388 100644
--- a/atr/templates/project-view.html
+++ b/atr/templates/project-view.html
@@ -34,7 +34,7 @@
{{ forms.errors_summary(policy_form) }}
<p class="mb-4">
- <a href="{{ as_url(routes.start.selected, project_name=project.name) }}"
+ <a href="{{ as_url(get.start.selected, project_name=project.name) }}"
class="btn btn-sm btn-outline-primary">Start a new release</a>
</p>
diff --git a/atr/templates/releases-finished.html
b/atr/templates/releases-finished.html
index 6841d74..7d9d456 100644
--- a/atr/templates/releases-finished.html
+++ b/atr/templates/releases-finished.html
@@ -10,7 +10,7 @@
{% block content %}
<p>
- <a href="{{ as_url(routes.root.index) }}" class="atr-back-link">← Back to
Select a release</a>
+ <a href="{{ as_url(get.root.index) }}" class="atr-back-link">← Back to
Select a release</a>
</p>
<h1>Releases of {{ project.display_name }}</h1>
diff --git a/atr/templates/revisions-selected.html
b/atr/templates/revisions-selected.html
index 98e031a..b660939 100644
--- a/atr/templates/revisions-selected.html
+++ b/atr/templates/revisions-selected.html
@@ -33,7 +33,7 @@
<span class="atr-phase-three atr-phase-label">FINISH</span>
</span>
{% else %}
- <a href="{{ as_url(routes.root.index) }}" class="atr-back-link">← Back
to Select a release</a>
+ <a href="{{ as_url(get.root.index) }}" class="atr-back-link">← Back to
Select a release</a>
{% endif %}
</p>
diff --git a/atr/templates/start-selected.html
b/atr/templates/start-selected.html
index a755fbb..9b0d0af 100644
--- a/atr/templates/start-selected.html
+++ b/atr/templates/start-selected.html
@@ -13,7 +13,7 @@
{{ forms.errors_summary(form) }}
<form method="post"
- action="{{ as_url(routes.start.selected, project_name=project.name) }}"
+ action="{{ as_url(post.start.selected, project_name=project.name) }}"
enctype="multipart/form-data"
class="atr-canary py-4 px-5 border rounded"
novalidate>
@@ -35,7 +35,7 @@
<div class="mt-4">
{{ form.submit(class_="btn btn-primary btn-lg") }}
- <a href="{{ as_url(routes.root.index) }}" class="btn btn-link
ms-2">Cancel</a>
+ <a href="{{ as_url(get.root.index) }}" class="btn btn-link
ms-2">Cancel</a>
</div>
</form>
diff --git a/atr/templates/todo.html b/atr/templates/todo.html
deleted file mode 100644
index 6285d3c..0000000
--- a/atr/templates/todo.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends "layouts/base.html" %}
-
-{% block title %}
- TODO ~ ATR
-{% endblock title %}
-
-{% block description %}
- TODO.
-{% endblock description %}
-
-{% block content %}
- <h1>TODO</h1>
-
- <p>This page is a placeholder for development.</p>
-
- <p>
- <a href="{{ as_url(routes.root.index) }}" class="btn btn-primary">← Back
to home</a>
- </p>
-{% endblock content %}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]