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 a3863d6 Migrate to the storage interface code for resolving a vote
a3863d6 is described below
commit a3863d60f4ff5e14a9d5b24396f490eeafd4fe91
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Sep 5 16:57:04 2025 +0100
Migrate to the storage interface code for resolving a vote
---
atr/routes/resolve.py | 171 ++++----------------------------------------
atr/storage/writers/vote.py | 6 +-
2 files changed, 16 insertions(+), 161 deletions(-)
diff --git a/atr/routes/resolve.py b/atr/routes/resolve.py
index 0705112..95eeb5c 100644
--- a/atr/routes/resolve.py
+++ b/atr/routes/resolve.py
@@ -19,7 +19,6 @@
import quart
import werkzeug.wrappers.response as response
-import atr.construct as construct
import atr.db as db
import atr.db.interaction as interaction
import atr.forms as forms
@@ -31,7 +30,6 @@ import atr.routes.finish as finish
import atr.routes.vote as vote
import atr.storage as storage
import atr.tabulate as tabulate
-import atr.tasks.message as message
import atr.template as template
import atr.util as util
@@ -176,15 +174,20 @@ async def submit_selected(
voting_round = None
if is_podling is True:
voting_round = 1 if (podling_thread_id is None) else 2
- release, success_message = await _resolve_vote(
- session,
- project_name,
- vote_result,
- email_body,
- latest_vote_task,
- release,
- voting_round,
- )
+ if release.committee is None:
+ raise ValueError("Project has no committee")
+ async with storage.write_as_committee_member(release.committee.name) as
wacm:
+ release, success_message, error_message = await wacm.vote.resolve(
+ project_name,
+ release,
+ voting_round,
+ vote_result,
+ latest_vote_task,
+ session.fullname,
+ email_body,
+ )
+ if error_message is not None:
+ await quart.flash(error_message, "error")
if vote_result == "passed":
if voting_round == 1:
destination = vote.selected
@@ -292,149 +295,3 @@ async def _committees_check(vote_thread_url: str,
vote_result_url: str) -> None:
raise RuntimeError("Vote committee not found")
if result_committee_label is None:
raise RuntimeError("Result committee not found")
-
-
-async def _resolve_vote(
- session: routes.CommitterSession,
- project_name: str,
- vote_result: str,
- resolution_body: str,
- latest_vote_task: sql.Task,
- release: sql.Release,
- voting_round: int | None,
-) -> tuple[sql.Release, str]:
- # Check that the user has access to the project
- await session.check_access(project_name)
-
- # Update release status in the database
- async with db.session() as data:
- # Attach the existing release to the session
- release = await data.merge(release)
- # Update the release phase based on vote result
- extra_destination = None
- if (voting_round == 1) and (vote_result == "passed"):
- # This is the first podling vote, by the PPMC and not the
Incubator PMC
- # In this branch, we do not move to RELEASE_PREVIEW but keep
everything the same
- # We only set the podling_thread_id to the thread_id of the vote
thread
- # Then we automatically start the Incubator PMC vote
- # TODO: Note on the resolve vote page that resolving the Project
PPMC vote starts the Incubator PMC vote
- task_mid = interaction.task_mid_get(latest_vote_task)
- archive_url = await interaction.task_archive_url_cached(task_mid)
- if archive_url is None:
- await quart.flash("No archive URL found for podling vote",
"error")
- return release, "Failure"
- thread_id = archive_url.split("/")[-1]
- release.podling_thread_id = thread_id
- # incubator_vote_address = "[email protected]"
- incubator_vote_address = util.USER_TESTS_ADDRESS
- if not release.project.committee:
- raise ValueError("Project has no committee")
- revision_number = release.latest_revision_number
- if revision_number is None:
- raise ValueError("Release has no revision number")
- if release.committee is None:
- raise ValueError("Project has no committee")
- async with
storage.write_as_committee_member(release.committee.name) as wacm:
- await wacm.vote.start(
- email_to=incubator_vote_address,
- permitted_recipients=[incubator_vote_address],
- project_name=release.project.name,
- version_name=release.version,
- selected_revision_number=revision_number,
- asf_uid=session.uid,
- asf_fullname=session.fullname,
-
vote_duration_choice=latest_vote_task.task_args["vote_duration"],
- subject_data=f"[VOTE] Release
{release.project.display_name} {release.version}",
- body_data=await
construct.start_vote_default(release.project.name),
- release=release,
- promote=False,
- )
- success_message = "Project PPMC vote marked as passed, and
Incubator PMC vote automatically started"
- elif vote_result == "passed":
- release.phase = sql.ReleasePhase.RELEASE_PREVIEW
- success_message = "Vote marked as passed"
-
- description = "Create a preview revision from the last candidate
draft"
- async with revision.create_and_manage(
- project_name, release.version, session.uid,
description=description
- ) as _creating:
- pass
- if (voting_round == 2) and (release.podling_thread_id is not None):
- round_one_email_address, round_one_message_id = await
util.email_mid_from_thread_id(
- release.podling_thread_id
- )
- extra_destination = (round_one_email_address,
round_one_message_id)
- else:
- release.phase = sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT
- success_message = "Vote marked as failed"
- await data.commit()
-
- error_message = await _send_resolution(
- session, release, vote_result, resolution_body,
extra_destination=extra_destination
- )
- if error_message is not None:
- await quart.flash(error_message, "error")
- return release, success_message
-
-
-async def _send_resolution(
- session: routes.CommitterSession,
- release: sql.Release,
- resolution: str,
- body: str,
- extra_destination: tuple[str, str] | None = None,
-) -> str | None:
- # Get the email thread
- latest_vote_task = await interaction.release_latest_vote_task(release)
- if latest_vote_task is None:
- return "No vote task found, unable to send resolution message."
- vote_thread_mid = interaction.task_mid_get(latest_vote_task)
- if vote_thread_mid is None:
- return "No vote thread found, unable to send resolution message."
-
- # Construct the reply email
- # original_subject = latest_vote_task.task_args["subject"]
-
- # Arguments for the task to cast a vote
- email_recipient = latest_vote_task.task_args["email_to"]
- email_sender = f"{session.uid}@apache.org"
- subject = f"[VOTE] [RESULT] Release {release.project.display_name}
{release.version} {resolution.upper()}"
- body = f"{body}\n\n-- \n{session.fullname} ({session.uid})"
- in_reply_to = vote_thread_mid
-
- task = sql.Task(
- status=sql.TaskStatus.QUEUED,
- task_type=sql.TaskType.MESSAGE_SEND,
- task_args=message.Send(
- email_sender=email_sender,
- email_recipient=email_recipient,
- subject=subject,
- body=body,
- in_reply_to=in_reply_to,
- ).model_dump(),
- asf_uid=util.unwrap(session.uid),
- project_name=release.project.name,
- version_name=release.version,
- )
- tasks = [task]
- if extra_destination is not None:
- task = sql.Task(
- status=sql.TaskStatus.QUEUED,
- task_type=sql.TaskType.MESSAGE_SEND,
- task_args=message.Send(
- email_sender=email_sender,
- email_recipient=extra_destination[0],
- subject=subject,
- body=body,
- in_reply_to=extra_destination[1],
- ).model_dump(),
- asf_uid=util.unwrap(session.uid),
- project_name=release.project.name,
- version_name=release.version,
- )
- tasks.append(task)
- async with db.session() as data:
- data.add_all(tasks)
- await data.flush()
- await data.commit()
- return None
diff --git a/atr/storage/writers/vote.py b/atr/storage/writers/vote.py
index 8c293cf..aa0119e 100644
--- a/atr/storage/writers/vote.py
+++ b/atr/storage/writers/vote.py
@@ -104,7 +104,7 @@ class CommitteeMember(CommitteeParticipant):
latest_vote_task: sql.Task,
asf_fullname: str,
resolution_body: str,
- ) -> tuple[sql.Release, str]:
+ ) -> tuple[sql.Release, str, str | None]:
# Attach the existing release to the session
release = await self.__data.merge(release)
# Update the release phase based on vote result
@@ -170,9 +170,7 @@ class CommitteeMember(CommitteeParticipant):
asf_fullname=asf_fullname,
extra_destination=extra_destination,
)
- if error_message is not None:
- raise storage.AccessError(error_message)
- return release, success_message
+ return release, success_message, error_message
async def resolve_api(self, project_name: str, version_name: str,
resolution: Literal["passed", "failed"]) -> None:
release_name = sql.release_name(project_name, version_name)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]