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 551722e  Add some validation to the tasks API endpoint
551722e is described below

commit 551722eb2ed9d34a5c5feeae6fda7aa2d7220c7b
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Jul 2 17:17:31 2025 +0100

    Add some validation to the tasks API endpoint
---
 atr/blueprints/api/api.py | 51 +++++++++++++++++++++++++++++------------------
 1 file changed, 32 insertions(+), 19 deletions(-)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 51563be..b874b89 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -41,34 +41,47 @@ class Pagination:
     limit: int = 20
 
 
+# We implicitly have /api/openapi.json
+
+
[email protected]("/projects")
+@quart_schema.validate_response(list[models.Committee], 200)
+async def projects() -> tuple[list[Mapping], int]:
+    """List all projects in the database."""
+    async with db.session() as data:
+        committees = await data.committee().all()
+        return [committee.model_dump() for committee in committees], 200
+
+
[email protected]("/projects/<name>")
+@quart_schema.validate_response(models.Committee, 200)
+async def projects_name(name: str) -> tuple[Mapping, int]:
+    async with db.session() as data:
+        committee = await 
data.committee(name=name).demand(exceptions.NotFound())
+        return committee.model_dump(), 200
+
+
 @api.BLUEPRINT.route("/tasks")
 @quart_schema.validate_querystring(Pagination)
-async def api_tasks(query_args: Pagination) -> quart.Response:
+async def tasks(query_args: Pagination) -> quart.Response:
+    _pagination_args_validate(query_args)
+    via = models.validate_instrumented_attribute
     async with db.session() as data:
         statement = (
             sqlmodel.select(models.Task)
             .limit(query_args.limit)
             .offset(query_args.offset)
-            .order_by(models.Task.id.desc())  # type: ignore
+            .order_by(via(models.Task.id).desc())
         )
         paged_tasks = (await data.execute(statement)).scalars().all()
-        count = (await 
data.execute(sqlalchemy.select(sqlalchemy.func.count(models.Task.id)))).scalar_one()
  # type: ignore
-        result = {"data": [x.model_dump(exclude={"result"}) for x in 
paged_tasks], "count": count}
+        count = (await 
data.execute(sqlalchemy.select(sqlalchemy.func.count(via(models.Task.id))))).scalar_one()
+        result = {"data": [paged_task.model_dump(exclude={"result"}) for 
paged_task in paged_tasks], "count": count}
         return quart.jsonify(result)
 
 
[email protected]("/projects/<name>")
-@quart_schema.validate_response(models.Committee, 200)
-async def project_by_name(name: str) -> tuple[Mapping, int]:
-    async with db.session() as data:
-        committee = await 
data.committee(name=name).demand(exceptions.NotFound())
-        return committee.model_dump(), 200
-
-
[email protected]("/projects")
-@quart_schema.validate_response(list[models.Committee], 200)
-async def projects() -> tuple[list[Mapping], int]:
-    """List all projects in the database."""
-    async with db.session() as data:
-        committees = await data.committee().all()
-        return [committee.model_dump() for committee in committees], 200
+def _pagination_args_validate(query_args: Pagination) -> None:
+    # Users could request any amount using limit=N with arbitrarily high N
+    # We therefore limit the maximum limit to 1000
+    if query_args.limit > 1000:
+        # quart.abort(400, "Limit is too high")
+        raise exceptions.BadRequest("Maximum limit of 1000 exceeded")


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

Reply via email to