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 0ef8a0e Fix a bug in getting the number of ongoing check tasks
0ef8a0e is described below
commit 0ef8a0e4a08651d35f9481daea8769a463f1232a
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Jul 14 17:27:06 2025 +0100
Fix a bug in getting the number of ongoing check tasks
---
atr/blueprints/api/api.py | 25 +++++++++++++++++++++++--
atr/db/interaction.py | 38 ++++++++++++++++++++++++++++++++++++++
atr/models/sql.py | 7 ++++---
3 files changed, 65 insertions(+), 5 deletions(-)
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 88309c1..80c59ca 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -58,6 +58,7 @@ import atr.util as util
@quart_schema.validate_response(list[sql.CheckResult], 200)
async def checks_list_project_version(project: str, version: str) ->
tuple[list[Mapping], int]:
"""List all check results for a given release."""
+ _simple_check(project, version)
# TODO: Merge with checks_list_project_version_revision
async with db.session() as data:
release_name = sql.release_name(project, version)
@@ -69,6 +70,7 @@ async def checks_list_project_version(project: str, version:
str) -> tuple[list[
@quart_schema.validate_response(list[sql.CheckResult], 200)
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."""
+ _simple_check(project, version, revision)
async with db.session() as data:
project_result = await data.project(name=project).get()
if project_result is None:
@@ -96,7 +98,8 @@ async def checks_ongoing_project_version(
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)
+ _simple_check(project, version, revision)
+ ongoing_tasks_count, latest_revision = await
interaction.tasks_ongoing_revision(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
@@ -126,6 +129,7 @@ async def committees() -> tuple[list[Mapping], int]:
@quart_schema.validate_response(sql.Committee, 200)
async def committees_name(name: str) -> tuple[Mapping, int]:
"""Get a specific committee by name."""
+ _simple_check(name)
async with db.session() as data:
committee = await
data.committee(name=name).demand(exceptions.NotFound())
return committee.model_dump(), 200
@@ -135,6 +139,7 @@ async def committees_name(name: str) -> tuple[Mapping, int]:
@quart_schema.validate_response(list[sql.PublicSigningKey], 200)
async def committees_name_keys(name: str) -> tuple[list[Mapping], int]:
"""List all public signing keys associated with a specific committee."""
+ _simple_check(name)
async with db.session() as data:
committee = await data.committee(name=name,
_public_signing_keys=True).demand(exceptions.NotFound())
return [key.model_dump() for key in committee.public_signing_keys], 200
@@ -144,6 +149,7 @@ async def committees_name_keys(name: str) ->
tuple[list[Mapping], int]:
@quart_schema.validate_response(list[sql.Project], 200)
async def committees_name_projects(name: str) -> tuple[list[Mapping], int]:
"""List all projects for a specific committee."""
+ _simple_check(name)
async with db.session() as data:
committee = await data.committee(name=name,
_projects=True).demand(exceptions.NotFound())
return [project.model_dump() for project in committee.projects], 200
@@ -184,6 +190,7 @@ async def draft_delete_project_version() -> tuple[dict[str,
str], int]:
async def list_project_version(
project: str, version: str, revision: str | None = None
) -> tuple[dict[str, list[str]], int]:
+ _simple_check(project, version, revision)
async with db.session() as data:
release_name = sql.release_name(project, version)
release = await
data.release(name=release_name).demand(exceptions.NotFound())
@@ -243,6 +250,7 @@ async def public_keys(query_args: models.api.Pagination) ->
quart.Response:
@quart_schema.validate_response(sql.PublicSigningKey, 200)
async def public_keys_fingerprint(fingerprint: str) -> tuple[Mapping, int]:
"""Return a single public signing key by fingerprint."""
+ _simple_check(fingerprint)
async with db.session() as data:
key = await
data.public_signing_key(fingerprint=fingerprint.lower()).demand(exceptions.NotFound())
return key.model_dump(), 200
@@ -260,6 +268,7 @@ async def projects() -> tuple[list[Mapping], int]:
@api.BLUEPRINT.route("/projects/<name>")
@quart_schema.validate_response(sql.Committee, 200)
async def projects_name(name: str) -> tuple[Mapping, int]:
+ _simple_check(name)
async with db.session() as data:
committee = await
data.committee(name=name).demand(exceptions.NotFound())
return committee.model_dump(), 200
@@ -269,6 +278,7 @@ async def projects_name(name: str) -> tuple[Mapping, int]:
@quart_schema.validate_response(list[sql.Release], 200)
async def projects_name_releases(name: str) -> tuple[list[Mapping], int]:
"""List all releases for a specific project."""
+ _simple_check(name)
async with db.session() as data:
releases = await data.release(project_name=name).all()
return [release.model_dump() for release in releases], 200
@@ -335,6 +345,7 @@ async def releases_create() -> tuple[Mapping, int]:
@quart_schema.validate_querystring(models.api.Pagination)
async def releases_project(project: str, query_args: models.api.Pagination) ->
quart.Response:
"""List all releases for a specific project with pagination."""
+ _simple_check(project)
_pagination_args_validate(query_args)
async with db.session() as data:
project_result = await data.project(name=project).get()
@@ -362,9 +373,11 @@ async def releases_project(project: str, query_args:
models.api.Pagination) -> q
@api.BLUEPRINT.route("/releases/<project>/<version>")
-@quart_schema.validate_response(sql.Release, 200)
+# TODO: If we validate as sql.Release, quart_schema silently corrupts
latest_revision_number to None
+# @quart_schema.validate_response(sql.Release, 200)
async def releases_project_version(project: str, version: str) ->
tuple[Mapping, int]:
"""Return a single release by project and version."""
+ _simple_check(project, version)
async with db.session() as data:
release_name = sql.release_name(project, version)
release = await
data.release(name=release_name).demand(exceptions.NotFound())
@@ -376,6 +389,7 @@ async def releases_project_version(project: str, version:
str) -> tuple[Mapping,
@quart_schema.validate_response(list[sql.Revision], 200)
async def releases_project_version_revisions(project: str, version: str) ->
tuple[list[Mapping], int]:
"""List all revisions for a given release."""
+ _simple_check(project, version)
async with db.session() as data:
release_name = sql.release_name(project, version)
revisions = await data.revision(release_name=release_name).all()
@@ -385,6 +399,7 @@ async def releases_project_version_revisions(project: str,
version: str) -> tupl
@api.BLUEPRINT.route("/revisions/<project>/<version>")
@quart_schema.validate_response(dict[str, list[sql.Revision]], 200)
async def revisions_project_version(project: str, version: str) ->
tuple[dict[str, list[sql.Revision]], int]:
+ _simple_check(project, version)
async with db.session() as data:
release_name = sql.release_name(project, version)
await data.release(name=release_name).demand(exceptions.NotFound())
@@ -548,6 +563,12 @@ async def _payload_get() -> dict:
return payload
+def _simple_check(*args: str | None) -> None:
+ for arg in args:
+ if arg == "None":
+ raise exceptions.BadRequest("Argument cannot be the string 'None'")
+
+
async def _upload_process_file(req: models.api.FileUploadRequest, asf_uid:
str) -> sql.Revision:
file_bytes = base64.b64decode(req.content, validate=True)
file_path = req.rel_path.lstrip("/")
diff --git a/atr/db/interaction.py b/atr/db/interaction.py
index 33bae0d..9797f67 100644
--- a/atr/db/interaction.py
+++ b/atr/db/interaction.py
@@ -328,6 +328,44 @@ async def tasks_ongoing(project_name: str, version_name:
str, revision_number: s
return result.scalar_one()
+async def tasks_ongoing_revision(
+ project_name: str,
+ version_name: str,
+ revision_number: str | None = None,
+) -> tuple[int, str]:
+ via = sql.validate_instrumented_attribute
+ subquery = (
+ sqlalchemy.select(via(sql.Revision.number))
+ .where(
+ via(sql.Revision.release_name) == sql.release_name(project_name,
version_name),
+ )
+ .order_by(via(sql.Revision.seq).desc())
+ .limit(1)
+ .scalar_subquery()
+ .label("latest_revision")
+ )
+
+ query = (
+ sqlmodel.select(
+ sqlalchemy.func.count().label("task_count"),
+ subquery,
+ )
+ .select_from(sql.Task)
+ .where(
+ sql.Task.project_name == project_name,
+ sql.Task.version_name == version_name,
+ sql.Task.revision_number == (subquery if revision_number is None
else revision_number),
+ sql.validate_instrumented_attribute(sql.Task.status).in_(
+ [sql.TaskStatus.QUEUED, sql.TaskStatus.ACTIVE],
+ ),
+ )
+ )
+
+ async with db.session() as session:
+ task_count, latest_revision = (await session.execute(query)).one()
+ return task_count, latest_revision
+
+
async def unfinished_releases(asfuid: str) -> dict[str, list[sql.Release]]:
releases: dict[str, list[sql.Release]] = {}
async with db.session() as data:
diff --git a/atr/models/sql.py b/atr/models/sql.py
index e7caab7..dea5604 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -834,14 +834,15 @@ def validate_instrumented_attribute(obj: Any) ->
orm.InstrumentedAttribute:
return obj
-RELEASE_LATEST_REVISION_NUMBER: Final = orm.column_property(
+RELEASE_LATEST_REVISION_NUMBER: Final = (
sqlalchemy.select(validate_instrumented_attribute(Revision.number))
.where(validate_instrumented_attribute(Revision.release_name) ==
Release.name)
.order_by(validate_instrumented_attribute(Revision.seq).desc())
.limit(1)
.correlate_except(Revision)
- .scalar_subquery(),
+ .scalar_subquery()
)
+
# https://github.com/fastapi/sqlmodel/issues/240#issuecomment-2074161775
-Release._latest_revision_number = RELEASE_LATEST_REVISION_NUMBER
+Release._latest_revision_number =
orm.column_property(RELEASE_LATEST_REVISION_NUMBER)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]