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]

Reply via email to