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 a9b3b57  Add an API endpoint to get the number of ongoing checks
a9b3b57 is described below

commit a9b3b570863f584cda702228cd032ec71767b904
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Jul 14 15:13:58 2025 +0100

    Add an API endpoint to get the number of ongoing checks
---
 atr/blueprints/api/api.py | 36 ++++++++++++++++++++++++++++++++----
 atr/db/interaction.py     | 20 ++++++++------------
 atr/models/api.py         |  4 ++++
 atr/models/sql.py         |  8 +++++---
 4 files changed, 49 insertions(+), 19 deletions(-)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 6ced3ba..88309c1 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -21,6 +21,7 @@ import datetime
 import hashlib
 import pathlib
 from collections.abc import Mapping
+from typing import Any
 
 import aiofiles.os
 import asfquart.base as base
@@ -53,19 +54,20 @@ import atr.util as util
 # We implicitly have /api/openapi.json
 
 
[email protected]("/checks/<project>/<version>")
[email protected]("/checks/list/<project>/<version>")
 @quart_schema.validate_response(list[sql.CheckResult], 200)
-async def checks_project_version(project: str, version: str) -> 
tuple[list[Mapping], int]:
+async def checks_list_project_version(project: str, version: str) -> 
tuple[list[Mapping], int]:
     """List all check results for a given release."""
+    # TODO: Merge with checks_list_project_version_revision
     async with db.session() as data:
         release_name = sql.release_name(project, version)
         check_results = await 
data.check_result(release_name=release_name).all()
         return [cr.model_dump() for cr in check_results], 200
 
 
[email protected]("/checks/<project>/<version>/<revision>")
[email protected]("/checks/list/<project>/<version>/<revision>")
 @quart_schema.validate_response(list[sql.CheckResult], 200)
-async def checks_project_version_revision(project: str, version: str, 
revision: str) -> tuple[list[Mapping], int]:
+async def checks_list_project_version_revision(project: str, version: str, 
revision: str) -> tuple[list[Mapping], int]:
     """List all check results for a specific revision of a release."""
     async with db.session() as data:
         project_result = await data.project(name=project).get()
@@ -85,6 +87,32 @@ async def checks_project_version_revision(project: str, 
version: str, revision:
         return [cr.model_dump() for cr in check_results], 200
 
 
[email protected]("/checks/ongoing/<project>/<version>")
[email protected]("/checks/ongoing/<project>/<version>/<revision>")
+@quart_schema.validate_response(models.api.ResultCount, 200)
+async def checks_ongoing_project_version(
+    project: str,
+    version: str,
+    revision: str | None = None,
+) -> tuple[Mapping[str, Any], int]:
+    """Return a count of all unfinished check results for a given release."""
+    ongoing_tasks_count = await interaction.tasks_ongoing(project, version, 
revision)
+    # TODO: Is there a way to return just an int?
+    # The ResponseReturnValue type in quart does not allow int
+    # And if we use quart.jsonify, we must return quart.Response which 
quart_schema tries to validate
+    # ResponseValue = Union[
+    #     "Response",
+    #     "WerkzeugResponse",
+    #     bytes,
+    #     str,
+    #     Mapping[str, Any],  # any jsonify-able dict
+    #     list[Any],  # any jsonify-able list
+    #     Iterator[bytes],
+    #     Iterator[str],
+    # ]
+    return models.api.ResultCount(count=ongoing_tasks_count).model_dump(), 200
+
+
 @api.BLUEPRINT.route("/committees")
 @quart_schema.validate_response(list[sql.Committee], 200)
 async def committees() -> tuple[list[Mapping], int]:
diff --git a/atr/db/interaction.py b/atr/db/interaction.py
index c5cafe5..33bae0d 100644
--- a/atr/db/interaction.py
+++ b/atr/db/interaction.py
@@ -314,19 +314,15 @@ async def release_delete(
     await _delete_release_data_filesystem(release_dir, release_name)
 
 
-async def tasks_ongoing(project_name: str, version_name: str, revision_number: 
str) -> int:
+async def tasks_ongoing(project_name: str, version_name: str, revision_number: 
str | None = None) -> int:
+    tasks = sqlmodel.select(sqlalchemy.func.count()).select_from(sql.Task)
     async with db.session() as data:
-        query = (
-            sqlmodel.select(sqlalchemy.func.count())
-            .select_from(sql.Task)
-            .where(
-                sql.Task.project_name == project_name,
-                sql.Task.version_name == version_name,
-                sql.Task.revision_number == revision_number,
-                sql.validate_instrumented_attribute(sql.Task.status).in_(
-                    [sql.TaskStatus.QUEUED, sql.TaskStatus.ACTIVE]
-                ),
-            )
+        query = tasks.where(
+            sql.Task.project_name == project_name,
+            sql.Task.version_name == version_name,
+            sql.Task.revision_number
+            == (sql.RELEASE_LATEST_REVISION_NUMBER if (revision_number is 
None) else revision_number),
+            
sql.validate_instrumented_attribute(sql.Task.status).in_([sql.TaskStatus.QUEUED,
 sql.TaskStatus.ACTIVE]),
         )
         result = await data.execute(query)
         return result.scalar_one()
diff --git a/atr/models/api.py b/atr/models/api.py
index 66be28c..f88f552 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -60,6 +60,10 @@ class ReleaseCreateRequest(schema.Strict):
     version: str
 
 
+class ResultCount(schema.Strict):
+    count: int
+
+
 class VoteStartRequest(schema.Strict):
     project_name: str
     version: str
diff --git a/atr/models/sql.py b/atr/models/sql.py
index 03fb6ad..e7caab7 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -24,7 +24,7 @@
 
 import datetime
 import enum
-from typing import Any, Optional
+from typing import Any, Final, Optional
 
 import pydantic
 import sqlalchemy
@@ -834,8 +834,7 @@ def validate_instrumented_attribute(obj: Any) -> 
orm.InstrumentedAttribute:
     return obj
 
 
-# https://github.com/fastapi/sqlmodel/issues/240#issuecomment-2074161775
-Release._latest_revision_number = orm.column_property(
+RELEASE_LATEST_REVISION_NUMBER: Final = orm.column_property(
     sqlalchemy.select(validate_instrumented_attribute(Revision.number))
     .where(validate_instrumented_attribute(Revision.release_name) == 
Release.name)
     .order_by(validate_instrumented_attribute(Revision.seq).desc())
@@ -843,3 +842,6 @@ Release._latest_revision_number = orm.column_property(
     .correlate_except(Revision)
     .scalar_subquery(),
 )
+
+# https://github.com/fastapi/sqlmodel/issues/240#issuecomment-2074161775
+Release._latest_revision_number = RELEASE_LATEST_REVISION_NUMBER


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to