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 8ed9369 Add API endpoints to delete and list ignores
8ed9369 is described below
commit 8ed93691d03d67e32c8cdfc0fb1546c1b6729fa3
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Jul 30 15:47:08 2025 +0100
Add API endpoints to delete and list ignores
---
atr/blueprints/api/api.py | 96 ++++++++++++++++++++++++++++++-------------
atr/models/api.py | 61 +++++++++++++++++----------
atr/models/sql.py | 4 ++
atr/storage/__init__.py | 10 +++++
atr/storage/readers/checks.py | 7 ++++
atr/storage/writers/checks.py | 9 ++--
6 files changed, 134 insertions(+), 53 deletions(-)
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index ac252d1..2d91e51 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -64,35 +64,6 @@ import atr.util as util
DictResponse = tuple[dict[str, Any], int]
[email protected]("/checks/ignore/add", methods=["POST"])
[email protected]
-@quart_schema.security_scheme([{"BearerAuth": []}])
-@quart_schema.validate_request(models.api.ChecksIgnoreAddArgs)
-@quart_schema.validate_response(models.api.ChecksIgnoreAddResults, 200)
-async def checks_ignore_add(data: models.api.ChecksIgnoreAddArgs) ->
DictResponse:
- """
- Add a check ignore.
- """
- asf_uid = _jwt_asf_uid()
- if not any(data.model_dump().values()):
- raise exceptions.BadRequest("At least one field must be provided")
- async with storage.write(asf_uid) as write:
- wacm = write.as_committee_member(data.committee_name)
- await wacm.checks.ignore_add(
- data.release_glob,
- data.revision_number,
- data.checker_glob,
- data.primary_rel_path_glob,
- data.member_rel_path_glob,
- data.status,
- data.message_glob,
- )
- return models.api.ChecksIgnoreAddResults(
- endpoint="/checks/ignore/add",
- success=True,
- ).model_dump(), 200
-
-
@api.BLUEPRINT.route("/checks/list/<project>/<version>")
@quart_schema.validate_response(models.api.ChecksListResults, 200)
async def checks_list(project: str, version: str) -> DictResponse:
@@ -288,6 +259,73 @@ async def committees_list() -> DictResponse:
).model_dump(), 200
[email protected]("/ignore/add", methods=["POST"])
[email protected]
+@quart_schema.security_scheme([{"BearerAuth": []}])
+@quart_schema.validate_request(models.api.IgnoreAddArgs)
+@quart_schema.validate_response(models.api.IgnoreAddResults, 200)
+async def ignore_add(data: models.api.IgnoreAddArgs) -> DictResponse:
+ """
+ Add a check ignore.
+ """
+ asf_uid = _jwt_asf_uid()
+ if not any(data.model_dump().values()):
+ raise exceptions.BadRequest("At least one field must be provided")
+ async with storage.write(asf_uid) as write:
+ wacm = write.as_committee_member(data.committee_name)
+ await wacm.checks.ignore_add(
+ data.release_glob,
+ data.revision_number,
+ data.checker_glob,
+ data.primary_rel_path_glob,
+ data.member_rel_path_glob,
+ data.status,
+ data.message_glob,
+ )
+ return models.api.IgnoreAddResults(
+ endpoint="/ignore/add",
+ success=True,
+ ).model_dump(), 200
+
+
[email protected]("/ignore/delete", methods=["POST"])
[email protected]
+@quart_schema.security_scheme([{"BearerAuth": []}])
+@quart_schema.validate_request(models.api.IgnoreDeleteArgs)
+@quart_schema.validate_response(models.api.IgnoreDeleteResults, 200)
+async def ignore_delete(data: models.api.IgnoreDeleteArgs) -> DictResponse:
+ """
+ Delete a check ignore.
+ """
+ asf_uid = _jwt_asf_uid()
+ if not any(data.model_dump().values()):
+ raise exceptions.BadRequest("At least one field must be provided")
+ async with storage.write(asf_uid) as write:
+ wacm = write.as_committee_member(data.committee)
+ # TODO: This is more like discard
+ # Should potentially check for rowcount, and raise an error if it's 0
+ await wacm.checks.ignore_delete(data.id)
+ return models.api.IgnoreDeleteResults(
+ endpoint="/ignore/delete",
+ success=True,
+ ).model_dump(), 200
+
+
[email protected]("/ignore/list/<committee_name>")
+@quart_schema.validate_response(models.api.IgnoreListResults, 200)
+async def ignore_list(committee_name: str) -> DictResponse:
+ """
+ List ignores by committee name.
+ """
+ _simple_check(committee_name)
+ async with db.session() as data:
+ ignores = await
data.check_result_ignore(committee_name=committee_name).all()
+ return models.api.IgnoreListResults(
+ endpoint="/ignore/list",
+ ignores=ignores,
+ ).model_dump(), 200
+
+
# This is the only POST endpoint that does not require a JWT
@api.BLUEPRINT.route("/jwt/create", methods=["POST"])
@quart_schema.validate_request(models.api.JwtCreateArgs)
diff --git a/atr/models/api.py b/atr/models/api.py
index 009ae07..a0294cd 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -34,24 +34,6 @@ class ResultsTypeError(TypeError):
pass
-class ChecksIgnoreAddArgs(schema.Strict):
- committee_name: str = schema.Field(..., **example("example"))
- release_glob: str | None = schema.Field(default=None,
**example("example-0.0.*"))
- revision_number: str | None = schema.Field(default=None,
**example("00001"))
- checker_glob: str | None = schema.Field(default=None,
**example("atr.tasks.checks.license.files"))
- primary_rel_path_glob: str | None = schema.Field(default=None,
**example("apache-example-0.0.1-*.tar.gz"))
- member_rel_path_glob: str | None = schema.Field(default=None,
**example("apache-example-0.0.1/*.xml"))
- status: sql.CheckResultStatusIgnore | None = schema.Field(
- default=None, **example(sql.CheckResultStatusIgnore.FAILURE)
- )
- message_glob: str | None = schema.Field(default=None, **example("sha512
matches for apache-example-0.0.1/*.xml"))
-
-
-class ChecksIgnoreAddResults(schema.Strict):
- endpoint: Literal["/checks/ignore/add"] = schema.Field(alias="endpoint")
- success: Literal[True] = schema.Field(..., **example(True))
-
-
class ChecksListResults(schema.Strict):
endpoint: Literal["/checks/list"] = schema.Field(alias="endpoint")
checks: Sequence[sql.CheckResult]
@@ -89,6 +71,39 @@ class CommitteesListResults(schema.Strict):
committees: Sequence[sql.Committee]
+class IgnoreAddArgs(schema.Strict):
+ committee_name: str = schema.Field(..., **example("example"))
+ release_glob: str | None = schema.Field(default=None,
**example("example-0.0.*"))
+ revision_number: str | None = schema.Field(default=None,
**example("00001"))
+ checker_glob: str | None = schema.Field(default=None,
**example("atr.tasks.checks.license.files"))
+ primary_rel_path_glob: str | None = schema.Field(default=None,
**example("apache-example-0.0.1-*.tar.gz"))
+ member_rel_path_glob: str | None = schema.Field(default=None,
**example("apache-example-0.0.1/*.xml"))
+ status: sql.CheckResultStatusIgnore | None = schema.Field(
+ default=None, **example(sql.CheckResultStatusIgnore.FAILURE)
+ )
+ message_glob: str | None = schema.Field(default=None, **example("sha512
matches for apache-example-0.0.1/*.xml"))
+
+
+class IgnoreAddResults(schema.Strict):
+ endpoint: Literal["/ignore/add"] = schema.Field(alias="endpoint")
+ success: Literal[True] = schema.Field(..., **example(True))
+
+
+class IgnoreDeleteArgs(schema.Strict):
+ committee: str = schema.Field(..., **example("example"))
+ id: int = schema.Field(..., **example(1))
+
+
+class IgnoreDeleteResults(schema.Strict):
+ endpoint: Literal["/ignore/delete"] = schema.Field(alias="endpoint")
+ success: Literal[True] = schema.Field(..., **example(True))
+
+
+class IgnoreListResults(schema.Strict):
+ endpoint: Literal["/ignore/list"] = schema.Field(alias="endpoint")
+ ignores: Sequence[sql.CheckResultIgnore]
+
+
class JwtCreateArgs(schema.Strict):
asfuid: str = schema.Field(..., **example("user"))
pat: str = schema.Field(...,
**example("8M5t4GCU63EdOy4NNXgXn7o-bc-muK8TRg5W-DeBaWY"))
@@ -399,13 +414,15 @@ class VoteTabulateResults(schema.Strict):
# This is for *Results classes only
# We do NOT put *Args classes here
Results = Annotated[
- ChecksIgnoreAddResults
- | ChecksListResults
+ ChecksListResults
| ChecksOngoingResults
| CommitteeGetResults
| CommitteeKeysResults
| CommitteeProjectsResults
| CommitteesListResults
+ | IgnoreAddResults
+ | IgnoreDeleteResults
+ | IgnoreListResults
| JwtCreateResults
| KeyAddResults
| KeyDeleteResults
@@ -449,13 +466,15 @@ def validator[T](t: type[T]) -> Callable[[Any], T]:
return validate
-validate_checks_ignore_add = validator(ChecksIgnoreAddResults)
validate_checks_list = validator(ChecksListResults)
validate_checks_ongoing = validator(ChecksOngoingResults)
validate_committee_get = validator(CommitteeGetResults)
validate_committee_keys = validator(CommitteeKeysResults)
validate_committee_projects = validator(CommitteeProjectsResults)
validate_committees_list = validator(CommitteesListResults)
+validate_ignore_add = validator(IgnoreAddResults)
+validate_ignore_delete = validator(IgnoreDeleteResults)
+validate_ignore_list = validator(IgnoreListResults)
validate_jwt_create = validator(JwtCreateResults)
validate_key_add = validator(KeyAddResults)
validate_key_delete = validator(KeyDeleteResults)
diff --git a/atr/models/sql.py b/atr/models/sql.py
index d58121c..e77d213 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -737,6 +737,10 @@ class CheckResultIgnore(sqlmodel.SQLModel, table=True):
)
message_glob: str | None = sqlmodel.Field(**example("sha512 matches for
apache-example-0.0.1/*.xml"))
+ def model_post_init(self, _context):
+ if isinstance(self.created, str):
+ self.created =
datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+
# DistributionChannel: Project
class DistributionChannel(sqlmodel.SQLModel, table=True):
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index 1f44d19..edb2176 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -483,6 +483,15 @@ class ContextManagers:
member_of, participant_of = await
self.__member_and_participant(data, asf_uid)
yield Read(data, asf_uid, member_of, participant_of)
+ @contextlib.asynccontextmanager
+ async def read_and_write(self, asf_uid: str | None = None) ->
AsyncGenerator[tuple[Read, Write]]:
+ async with db.session() as data:
+ # TODO: Replace data with a DatabaseWriter instance
+ member_of, participant_of = await
self.__member_and_participant(data, asf_uid)
+ r = Read(data, asf_uid, member_of, participant_of)
+ w = Write(data, asf_uid, member_of, participant_of)
+ yield r, w
+
@contextlib.asynccontextmanager
async def write(self, asf_uid: str | None = None) -> AsyncGenerator[Write]:
async with db.session() as data:
@@ -494,4 +503,5 @@ class ContextManagers:
_MANAGERS: Final[ContextManagers] = ContextManagers()
read = _MANAGERS.read
+read_and_write = _MANAGERS.read_and_write
write = _MANAGERS.write
diff --git a/atr/storage/readers/checks.py b/atr/storage/readers/checks.py
index fbe8ed3..fb56817 100644
--- a/atr/storage/readers/checks.py
+++ b/atr/storage/readers/checks.py
@@ -87,6 +87,12 @@ class GeneralPublic:
member_results_list[member_rel_path].sort(key=lambda r: r.checker)
return types.CheckResults(primary_results_list, member_results_list,
ignored_checks)
+ async def ignores(self, committee_name: str) ->
list[sql.CheckResultIgnore]:
+ results = await self.__data.check_result_ignore(
+ committee_name=committee_name,
+ ).all()
+ return list(results)
+
async def ignores_matcher(
self,
committee_name: str,
@@ -141,4 +147,5 @@ class GeneralPublic:
pattern = re.escape(glob).replace(r"\*", ".*")
# Should also handle ^ and $
# And maybe .replace(r"\?", ".?")
+ # Could also use "!" for negation
return re.match(pattern, value) is not None
diff --git a/atr/storage/writers/checks.py b/atr/storage/writers/checks.py
index 2fcb8fb..c6566a4 100644
--- a/atr/storage/writers/checks.py
+++ b/atr/storage/writers/checks.py
@@ -20,6 +20,8 @@ from __future__ import annotations
import datetime
+import sqlmodel
+
import atr.db as db
import atr.log as log
import atr.models.sql as sql
@@ -114,6 +116,7 @@ class CommitteeMember(CommitteeParticipant):
self.__data.add(cri)
await self.__data.commit()
- # def ignore_delete(self, id: int):
- # self.__data.delete(sql.CheckResultIgnore, id=id)
- # self.__data.commit()
+ async def ignore_delete(self, id: int) -> None:
+ via = sql.validate_instrumented_attribute
+ await
self.__data.execute(sqlmodel.delete(sql.CheckResultIgnore).where(via(sql.CheckResultIgnore.id)
== id))
+ await self.__data.commit()
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]