You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/04/15 10:21:53 UTC
(tooling-trusted-releases) 01/01: #1053 - Move tasks/list endpoint to /admin/tasks/list, shares permissions with UI view of tasks.
This is an automated email from the ASF dual-hosted git repository.
arm pushed a commit to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit dfd1d72288abb6336a7ac750e7c90831ddf0feb8
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Wed Apr 15 11:21:32 2026 +0100
#1053 - Move tasks/list endpoint to /admin/tasks/list, shares permissions with UI view of tasks.
---
atr/admin/__init__.py | 38 ++++++++++++++++++++++++++++++
atr/api/__init__.py | 61 +++++++-----------------------------------------
atr/models/api.py | 2 +-
atr/models/validation.py | 21 ++++++++++++++++-
4 files changed, 68 insertions(+), 54 deletions(-)
diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 9064e30c..5b33e6b7 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -36,6 +36,7 @@ import pydantic
import quart
import sqlalchemy
import sqlmodel
+import werkzeug.exceptions as exceptions
import atr.blueprints.admin as admin
import atr.config as config
@@ -48,9 +49,11 @@ import atr.jwtoken as jwtoken
import atr.ldap as ldap
import atr.log as log
import atr.mapping as mapping
+import atr.models.api as api
import atr.models.safe as safe
import atr.models.sql as sql
import atr.models.unsafe as unsafe
+import atr.models.validation as validation
import atr.paths as paths
import atr.principal as principal
import atr.storage as storage
@@ -896,6 +899,41 @@ async def rotate_jwt_key_post(
return await session.redirect(rotate_jwt_key_get)
+@admin.typed
+async def tasks_list(
+ _session: web.Committer,
+ _tasks_list: Literal["tasks/list"],
+ query_args: api.TasksListQuery,
+) -> tuple[dict[str, Any], int]:
+ """
+ URL: GET /tasks/list
+
+ List tasks.
+ """
+ try:
+ validation.pagination_args_validate(query_args)
+ except ValueError as e:
+ raise exceptions.BadRequest(str(e))
+ via = sql.validate_instrumented_attribute
+ async with db.session() as data:
+ statement = sqlmodel.select(sql.Task).limit(query_args.limit).offset(query_args.offset)
+ if query_args.status:
+ if query_args.status not in sql.TaskStatus:
+ raise exceptions.BadRequest(f"Invalid status: {query_args.status}")
+ statement = statement.where(sql.Task.status == query_args.status)
+ statement = statement.order_by(via(sql.Task.id).desc())
+ paged_tasks = (await data.execute(statement)).scalars().all()
+ count_statement = sqlalchemy.select(sqlalchemy.func.count(via(sql.Task.id)))
+ if query_args.status:
+ count_statement = count_statement.where(via(sql.Task.status) == query_args.status)
+ count = (await data.execute(count_statement)).scalar_one()
+ return api.TasksListResults(
+ endpoint="/admin/tasks/list",
+ data=paged_tasks,
+ count=count,
+ ).model_dump(mode="json"), 200
+
+
@admin.typed
async def task_times(
_session: web.Committer,
diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index c83b445f..afcfa182 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -39,6 +39,7 @@ import atr.models as models
import atr.models.safe as safe
import atr.models.sql as sql
import atr.models.unsafe as unsafe
+import atr.models.validation as validation
import atr.paths as paths
import atr.principal as principal
import atr.storage as storage
@@ -1218,7 +1219,10 @@ async def releases_list(
The list of releases is paged and can be filtered by phase.
"""
- _pagination_args_validate(query_args)
+ try:
+ validation.pagination_args_validate(query_args)
+ except ValueError as e:
+ raise exceptions.BadRequest(str(e))
via = sql.validate_instrumented_attribute
async with db.session() as data:
statement = sqlmodel.select(sql.Release)
@@ -1394,7 +1398,10 @@ async def ssh_keys_list(
List SSH keys by ASF UID.
"""
- _pagination_args_validate(query_args)
+ try:
+ validation.pagination_args_validate(query_args)
+ except ValueError as e:
+ raise exceptions.BadRequest(str(e))
via = sql.validate_instrumented_attribute
async with db.session() as data:
statement = (
@@ -1416,37 +1423,6 @@ async def ssh_keys_list(
).model_dump(mode="json"), 200
-@api.typed
-async def tasks_list(
- _tasks_list: Literal["tasks/list"],
- query_args: models.api.TasksListQuery,
-) -> DictResponse:
- """
- URL: GET /tasks/list
-
- List tasks.
- """
- _pagination_args_validate(query_args)
- via = sql.validate_instrumented_attribute
- async with db.session() as data:
- statement = sqlmodel.select(sql.Task).limit(query_args.limit).offset(query_args.offset)
- if query_args.status:
- if query_args.status not in sql.TaskStatus:
- raise exceptions.BadRequest(f"Invalid status: {query_args.status}")
- statement = statement.where(sql.Task.status == query_args.status)
- statement = statement.order_by(via(sql.Task.id).desc())
- paged_tasks = (await data.execute(statement)).scalars().all()
- count_statement = sqlalchemy.select(sqlalchemy.func.count(via(sql.Task.id)))
- if query_args.status:
- count_statement = count_statement.where(via(sql.Task.status) == query_args.status)
- count = (await data.execute(count_statement)).scalar_one()
- return models.api.TasksListResults(
- endpoint="/tasks/list",
- data=paged_tasks,
- count=count,
- ).model_dump(mode="json"), 200
-
-
@api.typed
async def update_distribution_task_status(
_distribute_task_status: Literal["distribute/task/status"],
@@ -1783,22 +1759,3 @@ async def _match_release(release_directory: safe.StatePath, data: models.api.Sig
if rel_path_sha3_256 == data.signature_sha3_256:
return True
return False
-
-
-def _pagination_args_validate(query_args: Any) -> None:
- # Users could request any amount using limit=N with arbitrarily high N
- # We therefore limit the maximum limit to 1000
- if hasattr(query_args, "limit"):
- limit = query_args.limit
- if limit > 1000:
- raise exceptions.BadRequest("Maximum limit of 1000 exceeded")
- elif limit < 1:
- raise exceptions.BadRequest("Minimum limit less than 1 is nonsense")
- # Users could request any amount using offset=N with arbitrarily high N
- # We therefore limit the maximum offset to 1000000
- if hasattr(query_args, "offset"):
- offset = query_args.offset
- if offset > 1000000:
- raise exceptions.BadRequest("Maximum offset of 1000000 exceeded")
- elif offset < 0:
- raise exceptions.BadRequest("Minimum offset less than 0 is nonsense")
diff --git a/atr/models/api.py b/atr/models/api.py
index a623b2ca..0752c958 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -581,7 +581,7 @@ class TasksListQuery:
class TasksListResults(schema.Strict):
- endpoint: Literal["/tasks/list"] = schema.alias("endpoint")
+ endpoint: Literal["/admin/tasks/list"] = schema.alias("endpoint")
data: Sequence[sql.Task]
count: int = schema.example(10)
diff --git a/atr/models/validation.py b/atr/models/validation.py
index 00ff352c..6c6c511f 100644
--- a/atr/models/validation.py
+++ b/atr/models/validation.py
@@ -16,7 +16,7 @@
# under the License.
import re
-from typing import Final
+from typing import Any, Final
import hyperscan
@@ -67,6 +67,25 @@ def compile_ignore_pattern(pattern: str):
return HyperscanPattern(db)
+def pagination_args_validate(query_args: Any) -> None:
+ # Users could request any amount using limit=N with arbitrarily high N
+ # We therefore limit the maximum limit to 1000
+ if hasattr(query_args, "limit"):
+ limit = query_args.limit
+ if limit > 1000:
+ raise ValueError("Maximum limit of 1000 exceeded")
+ elif limit < 1:
+ raise ValueError("Minimum limit less than 1 is nonsense")
+ # Users could request any amount using offset=N with arbitrarily high N
+ # We therefore limit the maximum offset to 1000000
+ if hasattr(query_args, "offset"):
+ offset = query_args.offset
+ if offset > 1000000:
+ raise ValueError("Maximum offset of 1000000 exceeded")
+ elif offset < 0:
+ raise ValueError("Minimum offset less than 0 is nonsense")
+
+
def validate_ignore_pattern(pattern: str) -> None:
"""Raise an exception if the pattern is invalid."""
if pattern == "!":
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org