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-releases-client.git
The following commit(s) were added to refs/heads/main by this push:
new 5732bff Add a command to tabulate votes
5732bff is described below
commit 5732bff1f13ae35b3cefd892f0210778b7d9a78b
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Jul 24 19:37:13 2025 +0100
Add a command to tabulate votes
---
pyproject.toml | 4 +--
src/atrclient/client.py | 14 ++++++++-
src/atrclient/models/__init__.py | 4 +--
src/atrclient/models/api.py | 14 ++++++++-
src/atrclient/models/sql.py | 1 +
src/atrclient/models/tabulate.py | 65 ++++++++++++++++++++++++++++++++++++++++
uv.lock | 4 +--
7 files changed, 98 insertions(+), 8 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 73306cf..f7e2d3e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ build-backend = "hatchling.build"
[project]
name = "apache-trusted-releases"
-version = "0.20250724.1813"
+version = "0.20250724.1835"
description = "ATR CLI and Python API"
readme = "README.md"
requires-python = ">=3.13"
@@ -72,4 +72,4 @@ select = [
]
[tool.uv]
-exclude-newer = "2025-07-24T18:13:00Z"
+exclude-newer = "2025-07-24T18:35:00Z"
diff --git a/src/atrclient/client.py b/src/atrclient/client.py
index 3db100c..9939ebd 100755
--- a/src/atrclient/client.py
+++ b/src/atrclient/client.py
@@ -262,6 +262,12 @@ def api_vote_start(api: ApiPost, args:
models.api.VoteStartArgs) -> models.api.V
return models.api.validate_vote_start(response)
+@api_post("/vote/tabulate")
+def api_vote_tabulate(api: ApiPost, args: models.api.VoteTabulateArgs) ->
models.api.VoteTabulateResults:
+ response = api.post(args)
+ return models.api.validate_vote_tabulate(response)
+
+
@APP.command(name="announce", help="Announce a release.")
def app_announce(
project: str,
@@ -288,7 +294,6 @@ def app_announce(
@APP.command(name="api", help="Call the API directly.")
async def app_api(path: str, /, **kwargs: str) -> None:
- print("app_api")
jwt_value = config_jwt_usable()
host, verify_ssl = config_host_get()
url = f"https://{host}/api{path}"
@@ -871,6 +876,13 @@ def app_vote_start(
print(vote_start.task.model_dump_json(indent=None))
+@APP_VOTE.command(name="tabulate", help="Tabulate a vote.")
+def app_vote_tabulate(project: str, version: str, /) -> None:
+ vote_tabulate_args = models.api.VoteTabulateArgs(project=project,
version=version)
+ vote_tabulate = api_vote_tabulate(vote_tabulate_args)
+ print(vote_tabulate.model_dump_json(indent=2))
+
+
def checks_display(results: Sequence[models.sql.CheckResult], verbose: bool =
False) -> None:
if not results:
print("No check results found for this revision.")
diff --git a/src/atrclient/models/__init__.py b/src/atrclient/models/__init__.py
index 63e3704..559a7d2 100644
--- a/src/atrclient/models/__init__.py
+++ b/src/atrclient/models/__init__.py
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
-from . import api, helpers, results, schema, sql
+from . import api, helpers, results, schema, sql, tabulate
# If we use .__name__, pyright gives a warning
-__all__ = ["api", "helpers", "results", "schema", "sql"]
+__all__ = ["api", "helpers", "results", "schema", "sql", "tabulate"]
diff --git a/src/atrclient/models/api.py b/src/atrclient/models/api.py
index 127baf4..40d49c5 100644
--- a/src/atrclient/models/api.py
+++ b/src/atrclient/models/api.py
@@ -21,7 +21,7 @@ from typing import Annotated, Any, Literal, TypeVar
import pydantic
-from . import schema, sql
+from . import schema, sql, tabulate
T = TypeVar("T")
@@ -340,6 +340,16 @@ class VoteStartResults(schema.Strict):
task: sql.Task
+class VoteTabulateArgs(schema.Strict):
+ project: str
+ version: str
+
+
+class VoteTabulateResults(schema.Strict):
+ endpoint: Literal["/vote/tabulate"] = schema.Field(alias="endpoint")
+ details: tabulate.VoteDetails
+
+
class UploadArgs(schema.Strict):
project: str
version: str
@@ -389,6 +399,7 @@ Results = Annotated[
| UsersListResults
| VoteResolveResults
| VoteStartResults
+ | VoteTabulateResults
| UploadResults,
schema.Field(discriminator="endpoint"),
]
@@ -440,4 +451,5 @@ validate_tasks = validator(TasksResults)
validate_users_list = validator(UsersListResults)
validate_vote_resolve = validator(VoteResolveResults)
validate_vote_start = validator(VoteStartResults)
+validate_vote_tabulate = validator(VoteTabulateResults)
validate_upload = validator(UploadResults)
diff --git a/src/atrclient/models/sql.py b/src/atrclient/models/sql.py
index 31d2de5..20b8857 100644
--- a/src/atrclient/models/sql.py
+++ b/src/atrclient/models/sql.py
@@ -221,6 +221,7 @@ class Task(sqlmodel.SQLModel, table=True):
status: TaskStatus = sqlmodel.Field(default=TaskStatus.QUEUED, index=True)
task_type: TaskType
task_args: Any =
sqlmodel.Field(sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+ asf_uid: str
added: datetime.datetime = sqlmodel.Field(
default_factory=lambda: datetime.datetime.now(datetime.UTC),
sa_column=sqlalchemy.Column(UTCDateTime, index=True),
diff --git a/src/atrclient/models/tabulate.py b/src/atrclient/models/tabulate.py
new file mode 100644
index 0000000..e58b035
--- /dev/null
+++ b/src/atrclient/models/tabulate.py
@@ -0,0 +1,65 @@
+# 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.
+
+import enum
+
+import pydantic
+
+from . import schema
+
+
+class Vote(enum.Enum):
+ YES = "Yes"
+ NO = "No"
+ ABSTAIN = "-"
+ UNKNOWN = "?"
+
+
+class VoteStatus(enum.Enum):
+ BINDING = "Binding"
+ COMMITTER = "Committer"
+ CONTRIBUTOR = "Contributor"
+ UNKNOWN = "Unknown"
+
+
+class VoteEmail(schema.Strict):
+ asf_uid_or_email: str
+ from_email: str
+ status: VoteStatus
+ asf_eid: str
+ iso_datetime: str
+ vote: Vote
+ quotation: str
+ updated: bool
+
+ @pydantic.field_validator("status", mode="before")
+ @classmethod
+ def status_to_enum(cls, v):
+ return VoteStatus(v) if isinstance(v, str) else v
+
+ @pydantic.field_validator("vote", mode="before")
+ @classmethod
+ def vote_to_enum(cls, v):
+ return Vote(v) if isinstance(v, str) else v
+
+
+class VoteDetails(schema.Strict):
+ start_unixtime: int | None
+ votes: dict[str, VoteEmail]
+ summary: dict[str, int]
+ passed: bool
+ outcome: str
diff --git a/uv.lock b/uv.lock
index db03bb5..e15a6d7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,7 @@ version = 1
requires-python = ">=3.13"
[options]
-exclude-newer = "2025-07-24T18:13:00Z"
+exclude-newer = "2025-07-24T18:35:00Z"
[[package]]
name = "aiohappyeyeballs"
@@ -83,7 +83,7 @@ wheels = [
[[package]]
name = "apache-trusted-releases"
-version = "0.20250724.1813"
+version = "0.20250724.1835"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]