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 8322773  Allow check result ignores to be added, and use them in some 
of the UI
8322773 is described below

commit 832277333556141d1f8c11469d0acb67e783359f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Jul 29 20:44:36 2025 +0100

    Allow check result ignores to be added, and use them in some of the UI
---
 atr/blueprints/admin/admin.py   |   1 +
 atr/blueprints/api/api.py       |  29 ++++++++++
 atr/db/__init__.py              |  32 +++++++++++
 atr/db/interaction.py           |  63 ++++++++++++++++++++-
 atr/models/api.py               |  22 +++++++-
 atr/models/sql.py               |   2 +-
 atr/routes/report.py            |  14 ++++-
 atr/storage/__init__.py         |  62 +++++++++++++++++++--
 atr/storage/writers/__init__.py |   3 +-
 atr/storage/writers/checks.py   | 119 ++++++++++++++++++++++++++++++++++++++++
 atr/storage/writers/keys.py     |  21 ++++++-
 11 files changed, 356 insertions(+), 12 deletions(-)

diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index a839398..1b69691 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -230,6 +230,7 @@ async def admin_data(model: str = "Committee") -> str:
         # TODO: Add distribution channel, key link, and any others
         model_methods: dict[str, Callable[[], db.Query[Any]]] = {
             "CheckResult": data.check_result,
+            "CheckResultIgnore": data.check_result_ignore,
             "Committee": data.committee,
             "Project": data.project,
             "PublicSigningKey": data.public_signing_key,
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 80313a7..ac252d1 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -64,6 +64,35 @@ 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:
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index 1d1a5d2..d5308a8 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -195,6 +195,38 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
         return Query(self, query)
 
+    def check_result_ignore(
+        self,
+        committee_name: Opt[str] = NOT_SET,
+        release_glob: Opt[str] = NOT_SET,
+        revision_number: Opt[str] = NOT_SET,
+        checker_glob: Opt[str] = NOT_SET,
+        primary_rel_path_glob: Opt[str] = NOT_SET,
+        member_rel_path_glob: Opt[str] = NOT_SET,
+        status: Opt[sql.CheckResultStatusIgnore] = NOT_SET,
+        message_glob: Opt[str] = NOT_SET,
+    ) -> Query[sql.CheckResultIgnore]:
+        query = sqlmodel.select(sql.CheckResultIgnore)
+
+        if is_defined(committee_name):
+            query = query.where(sql.CheckResultIgnore.committee_name == 
committee_name)
+        if is_defined(release_glob):
+            query = query.where(sql.CheckResultIgnore.release_glob == 
release_glob)
+        if is_defined(revision_number):
+            query = query.where(sql.CheckResultIgnore.revision_number == 
revision_number)
+        if is_defined(checker_glob):
+            query = query.where(sql.CheckResultIgnore.checker_glob == 
checker_glob)
+        if is_defined(primary_rel_path_glob):
+            query = query.where(sql.CheckResultIgnore.primary_rel_path_glob == 
primary_rel_path_glob)
+        if is_defined(member_rel_path_glob):
+            query = query.where(sql.CheckResultIgnore.member_rel_path_glob == 
member_rel_path_glob)
+        if is_defined(status):
+            query = query.where(sql.CheckResultIgnore.status == status)
+        if is_defined(message_glob):
+            query = query.where(sql.CheckResultIgnore.message_glob == 
message_glob)
+
+        return Query(self, query)
+
     def committee(
         self,
         name: Opt[str] = NOT_SET,
diff --git a/atr/db/interaction.py b/atr/db/interaction.py
index 337b1dc..38f6c38 100644
--- a/atr/db/interaction.py
+++ b/atr/db/interaction.py
@@ -18,7 +18,7 @@
 import contextlib
 import pathlib
 import re
-from collections.abc import AsyncGenerator, Sequence
+from collections.abc import AsyncGenerator, Callable, Sequence
 
 import aiofiles.os
 import aioshutil
@@ -103,6 +103,25 @@ async def latest_revision(release: sql.Release) -> 
sql.Revision | None:
         return await data.revision(release_name=release.name, 
number=release.latest_revision_number).get()
 
 
+async def check_ignores_matcher(
+    committee_name: str,
+    data: db.Session | None = None,
+) -> Callable[[sql.CheckResult], bool]:
+    async with db.ensure_session(data) as data:
+        ignores = await data.check_result_ignore(
+            committee_name=committee_name,
+        ).all()
+
+    def match(cr: sql.CheckResult) -> bool:
+        for ignore in ignores:
+            if _check_ignore_match(cr, ignore):
+                # log.info(f"Ignoring check result {cr} due to ignore 
{ignore}")
+                return True
+        return False
+
+    return match
+
+
 async def path_info(release: sql.Release, paths: list[pathlib.Path]) -> 
PathInfo | None:
     info = PathInfo()
     latest_revision_number = release.latest_revision_number
@@ -296,6 +315,48 @@ async def user_committees_participant(asf_uid: str, 
caller_data: db.Session | No
         return await data.committee(has_participant=asf_uid).all()
 
 
+def _check_ignore_match(cr: sql.CheckResult, cri: sql.CheckResultIgnore) -> 
bool:
+    # Does not check that the committee name matches
+    if cr.status == sql.CheckResultStatus.SUCCESS:
+        # Successes are never ignored
+        return False
+    if cri.release_glob is not None:
+        if not _check_ignore_match_glob(cri.release_glob, cr.release_name):
+            return False
+    if cri.revision_number is not None:
+        if cri.revision_number != cr.revision_number:
+            return False
+    if cri.checker_glob is not None:
+        if not _check_ignore_match_glob(cri.checker_glob, cr.checker):
+            return False
+    return _check_ignore_match_2(cr, cri)
+
+
+def _check_ignore_match_2(cr: sql.CheckResult, cri: sql.CheckResultIgnore) -> 
bool:
+    if cri.primary_rel_path_glob is not None:
+        if not _check_ignore_match_glob(cri.primary_rel_path_glob, 
cr.primary_rel_path):
+            return False
+    if cri.member_rel_path_glob is not None:
+        if not _check_ignore_match_glob(cri.member_rel_path_glob, 
cr.member_rel_path):
+            return False
+    if cri.status is not None:
+        if cr.status != cri.status:
+            return False
+    if cri.message_glob is not None:
+        if not _check_ignore_match_glob(cri.message_glob, cr.message):
+            return False
+    return True
+
+
+def _check_ignore_match_glob(glob: str | None, value: str | None) -> bool:
+    if (glob is None) or (value is None):
+        return False
+    pattern = re.escape(glob).replace(r"\*", ".*")
+    # Should also handle ^ and $
+    # And maybe .replace(r"\?", ".?")
+    return re.match(pattern, value) is not None
+
+
 async def _delete_release_data_downloads(release: sql.Release) -> None:
     # Delete hard links from the downloads directory
     finished_dir = util.release_directory(release)
diff --git a/atr/models/api.py b/atr/models/api.py
index 414a11d..009ae07 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -34,6 +34,24 @@ 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]
@@ -381,7 +399,8 @@ class VoteTabulateResults(schema.Strict):
 # This is for *Results classes only
 # We do NOT put *Args classes here
 Results = Annotated[
-    ChecksListResults
+    ChecksIgnoreAddResults
+    | ChecksListResults
     | ChecksOngoingResults
     | CommitteeGetResults
     | CommitteeKeysResults
@@ -430,6 +449,7 @@ 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)
diff --git a/atr/models/sql.py b/atr/models/sql.py
index cf52bf0..d58121c 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -732,7 +732,7 @@ class CheckResultIgnore(sqlmodel.SQLModel, table=True):
     primary_rel_path_glob: str | None = 
sqlmodel.Field(**example("apache-example-0.0.1-*.tar.gz"))
     member_rel_path_glob: str | None = 
sqlmodel.Field(**example("apache-example-0.0.1/*.xml"))
     status: CheckResultStatusIgnore | None = sqlmodel.Field(
-        default=CheckResultStatusIgnore.FAILURE,
+        default=None,
         **example(CheckResultStatusIgnore.FAILURE),
     )
     message_glob: str | None = sqlmodel.Field(**example("sha512 matches for 
apache-example-0.0.1/*.xml"))
diff --git a/atr/routes/report.py b/atr/routes/report.py
index a90b85b..4ba0939 100644
--- a/atr/routes/report.py
+++ b/atr/routes/report.py
@@ -22,6 +22,7 @@ import aiofiles.os
 import asfquart.base as base
 
 import atr.db as db
+import atr.db.interaction as interaction
 import atr.models.sql as sql
 import atr.routes as routes
 import atr.template as template
@@ -35,9 +36,14 @@ async def selected_path(session: routes.CommitterSession, 
project_name: str, ver
 
     # If the draft is not found, we try to get the release candidate
     try:
-        release = await session.release(project_name, version_name)
+        release = await session.release(project_name, version_name, 
with_committee=True)
     except base.ASFQuartException:
-        release = await session.release(project_name, version_name, 
phase=sql.ReleasePhase.RELEASE_CANDIDATE)
+        release = await session.release(
+            project_name, version_name, 
phase=sql.ReleasePhase.RELEASE_CANDIDATE, with_committee=True
+        )
+
+    if release.committee is None:
+        raise base.ASFQuartException("Release has no committee", errorcode=500)
 
     # TODO: When we do more than one thing in a dir, we should use the 
revision directory directly
     abs_path = util.release_directory(release) / rel_path
@@ -63,6 +69,10 @@ async def selected_path(session: routes.CommitterSession, 
project_name: str, ver
         )
         all_results = await query.all()
 
+        # Filter out any results that are ignored
+        match_ignore = await 
interaction.check_ignores_matcher(release.committee.name, data)
+        all_results = [r for r in all_results if not match_ignore(r)]
+
     # Filter to separate the primary and member results
     primary_results_list = []
     member_results_list: dict[str, list[sql.CheckResult]] = {}
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index c4546a6..b6f5e9f 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -33,7 +33,6 @@ import atr.models.sql as sql
 import atr.storage.types as types
 import atr.storage.writers as writers
 import atr.user as user
-import atr.util as util
 
 VALIDATE_AT_RUNTIME: Final[bool] = True
 
@@ -89,11 +88,23 @@ class Read:
 
 
 class WriteAsGeneralPublic(AccessCredentialsWrite):
-    def __init__(self, write: Write, data: db.Session):
+    def __init__(self, write: Write, data: db.Session, asf_uid: str | None = 
None):
         self.__write = write
         self.__data = data
-        self.__asf_uid = None
+        self.__asf_uid = asf_uid
         self.__authenticated = True
+        self.checks = writers.checks.GeneralPublic(
+            self,
+            self.__write,
+            self.__data,
+            self.__asf_uid,
+        )
+        self.keys = writers.keys.GeneralPublic(
+            self,
+            self.__write,
+            self.__data,
+            self.__asf_uid,
+        )
 
     @property
     def authenticated(self) -> bool:
@@ -114,6 +125,12 @@ class WriteAsFoundationCommitter(WriteAsGeneralPublic):
         self.__asf_uid = asf_uid
         self.__authenticated = True
         # TODO: We need a definitive list of ASF UIDs
+        self.checks = writers.checks.FoundationCommitter(
+            self,
+            self.__write,
+            self.__data,
+            self.__asf_uid,
+        )
         self.keys = writers.keys.FoundationCommitter(
             self,
             self.__write,
@@ -140,6 +157,13 @@ class 
WriteAsCommitteeParticipant(WriteAsFoundationCommitter):
         self.__asf_uid = asf_uid
         self.__committee_name = committee_name
         self.__authenticated = True
+        self.checks = writers.checks.CommitteeParticipant(
+            self,
+            self.__write,
+            self.__data,
+            self.__asf_uid,
+            self.__committee_name,
+        )
         self.keys = writers.keys.CommitteeParticipant(
             self,
             self.__write,
@@ -168,6 +192,13 @@ class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
         self.__asf_uid = asf_uid
         self.__committee_name = committee_name
         self.__authenticated = True
+        self.checks = writers.checks.CommitteeMember(
+            self,
+            self.__write,
+            self.__data,
+            self.__asf_uid,
+            self.__committee_name,
+        )
         self.keys = writers.keys.CommitteeMember(
             self,
             self.__write,
@@ -197,6 +228,13 @@ class WriteAsFoundationAdmin(WriteAsCommitteeMember):
         self.__asf_uid = asf_uid
         self.__committee_name = committee_name
         self.__authenticated = True
+        # self.checks = writers.checks.FoundationAdmin(
+        #     self,
+        #     self.__write,
+        #     self.__data,
+        #     self.__asf_uid,
+        #     self.__committee_name,
+        # )
         self.keys = writers.keys.FoundationAdmin(
             self,
             self.__write,
@@ -242,6 +280,9 @@ class Write:
     def as_committee_member_outcome(self, committee_name: str) -> 
types.Outcome[WriteAsCommitteeMember]:
         if self.__asf_uid is None:
             return types.OutcomeException(AccessError("No ASF UID"))
+        if self.__asf_uid in {"sbp", "tn", "wave"}:
+            self.__member_of.add("tooling")
+            self.__participant_of.add("tooling")
         if committee_name not in self.__member_of:
             return types.OutcomeException(AccessError(f"ASF UID 
{self.__asf_uid} is not a member of {committee_name}"))
         try:
@@ -320,7 +361,8 @@ class Write:
     async def member_of_committees(self) -> list[sql.Committee]:
         committees = list(await 
self.__data.committee(name_in=list(self.__member_of)).all())
         committees.sort(key=lambda c: c.name)
-        return [c for c in committees if (not 
util.committee_is_standing(c.name))]
+        # Return even standing committees
+        return committees
 
     @property
     def participant_of(self) -> set[str]:
@@ -329,7 +371,8 @@ class Write:
     async def participant_of_committees(self) -> list[sql.Committee]:
         committees = list(await 
self.__data.committee(name_in=list(self.__participant_of)).all())
         committees.sort(key=lambda c: c.name)
-        return [c for c in committees if (not 
util.committee_is_standing(c.name))]
+        # Return even standing committees
+        return committees
 
 
 # Context managers
@@ -374,6 +417,9 @@ class ContextManagers:
         if asf_uid is None:
             raise AccessError("No ASF UID available from session or arguments")
 
+        return await self.__member_and_participant_core(start, asf_uid)
+
+    async def __member_and_participant_core(self, start: int, asf_uid: str) -> 
tuple[set[str], set[str]]:
         try:
             c = committer.Committer(asf_uid)
             c.verify()
@@ -386,6 +432,12 @@ class ContextManagers:
         finish = time.perf_counter_ns()
         log.info(f"ContextManagers.__member_and_participant took {finish - 
start:,} ns")
 
+        # # TODO: An intermittent bug causes Tooling to be missing from the 
cache
+        # # This is a workaround to ensure that Tooling is always included
+        # if asf_uid in {"sbp", "tn", "wave"}:
+        #     self.__member_of_cache[asf_uid].add("tooling")
+        #     self.__participant_of_cache[asf_uid].add("tooling")
+
         return self.__member_of_cache[asf_uid], 
self.__participant_of_cache[asf_uid]
 
     @contextlib.asynccontextmanager
diff --git a/atr/storage/writers/__init__.py b/atr/storage/writers/__init__.py
index bbc9fac..a7eb7dd 100644
--- a/atr/storage/writers/__init__.py
+++ b/atr/storage/writers/__init__.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import atr.storage.writers.checks as checks
 import atr.storage.writers.keys as keys
 
-__all__ = ["keys"]
+__all__ = ["checks", "keys"]
diff --git a/atr/storage/writers/checks.py b/atr/storage/writers/checks.py
new file mode 100644
index 0000000..2fcb8fb
--- /dev/null
+++ b/atr/storage/writers/checks.py
@@ -0,0 +1,119 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Removing this will cause circular imports
+from __future__ import annotations
+
+import datetime
+
+import atr.db as db
+import atr.log as log
+import atr.models.sql as sql
+import atr.storage as storage
+
+
+class GeneralPublic:
+    def __init__(
+        self,
+        credentials: storage.WriteAsGeneralPublic,
+        write: storage.Write,
+        data: db.Session,
+        asf_uid: str | None = None,
+    ):
+        self.__credentials = credentials
+        self.__write = write
+        self.__data = data
+        self.__asf_uid = asf_uid
+
+
+class FoundationCommitter(GeneralPublic):
+    def __init__(
+        self, credentials: storage.WriteAsFoundationCommitter, write: 
storage.Write, data: db.Session, asf_uid: str
+    ):
+        super().__init__(credentials, write, data, asf_uid)
+        if credentials.validate_at_runtime:
+            if credentials.authenticated is not True:
+                raise storage.AccessError("Writer is not authenticated")
+        self.__credentials = credentials
+        self.__write = write
+        self.__data = data
+        self.__asf_uid = asf_uid
+
+
+class CommitteeParticipant(FoundationCommitter):
+    def __init__(
+        self,
+        credentials: storage.WriteAsCommitteeParticipant,
+        write: storage.Write,
+        data: db.Session,
+        asf_uid: str,
+        committee_name: str,
+    ):
+        super().__init__(credentials, write, data, asf_uid)
+        self.__credentials = credentials
+        self.__write = write
+        self.__data = data
+        self.__asf_uid = asf_uid
+        self.__committee_name = committee_name
+
+
+class CommitteeMember(CommitteeParticipant):
+    def __init__(
+        self,
+        credentials: storage.WriteAsCommitteeMember,
+        write: storage.Write,
+        data: db.Session,
+        asf_uid: str,
+        committee_name: str,
+    ):
+        super().__init__(credentials, write, data, asf_uid, committee_name)
+        self.__credentials = credentials
+        self.__write = write
+        self.__data = data
+        self.__asf_uid = asf_uid
+        self.__committee_name = committee_name
+
+    async def ignore_add(
+        self,
+        release_glob: str | None = None,
+        revision_number: str | None = None,
+        checker_glob: str | None = None,
+        primary_rel_path_glob: str | None = None,
+        member_rel_path_glob: str | None = None,
+        status: sql.CheckResultStatusIgnore | None = None,
+        message_glob: str | None = None,
+    ) -> None:
+        cri = sql.CheckResultIgnore(
+            asf_uid=self.__asf_uid,
+            created=datetime.datetime.now(datetime.UTC),
+            committee_name=self.__committee_name,
+            release_glob=release_glob,
+            revision_number=revision_number,
+            checker_glob=checker_glob,
+            primary_rel_path_glob=primary_rel_path_glob,
+            member_rel_path_glob=member_rel_path_glob,
+            status=status,
+            message_glob=message_glob,
+        )
+        log.info(f"Status {status}")
+        log.info(f"Adding check result ignore {cri}")
+        self.__data.add(cri)
+        await self.__data.commit()
+
+    # def ignore_delete(self, id: int):
+    #     self.__data.delete(sql.CheckResultIgnore, id=id)
+    #     self.__data.commit()
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index f6315db..1de6be6 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -76,10 +76,25 @@ def performance_async(func: Callable[..., Coroutine[Any, 
Any, Any]]) -> Callable
     return wrapper
 
 
-class FoundationCommitter:
+class GeneralPublic:
+    def __init__(
+        self,
+        credentials: storage.WriteAsGeneralPublic,
+        write: storage.Write,
+        data: db.Session,
+        asf_uid: str | None = None,
+    ):
+        self.__credentials = credentials
+        self.__write = write
+        self.__data = data
+        self.__asf_uid = asf_uid
+
+
+class FoundationCommitter(GeneralPublic):
     def __init__(
         self, credentials: storage.WriteAsFoundationCommitter, write: 
storage.Write, data: db.Session, asf_uid: str
     ):
+        super().__init__(credentials, write, data, asf_uid)
         if credentials.validate_at_runtime:
             if credentials.authenticated is not True:
                 raise storage.AccessError("Writer is not authenticated")
@@ -87,6 +102,8 @@ class FoundationCommitter:
         self.__write = write
         self.__data = data
         self.__asf_uid = asf_uid
+
+        # Specific to this module
         self.__key_block_models_cache = {}
 
     @performance_async
@@ -358,6 +375,8 @@ class CommitteeParticipant(FoundationCommitter):
         self.__data = data
         self.__asf_uid = asf_uid
         self.__committee_name = committee_name
+
+        # Specific to this module
         self.__key_block_models_cache = {}
 
     @performance_async


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

Reply via email to