This is an automated email from the ASF dual-hosted git repository.

arm pushed a commit to branch check_version_database
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit 905e9234c9de39ce8158ce608bae468cb1eeb344
Author: Alastair McFarlane <[email protected]>
AuthorDate: Wed Mar 18 13:53:16 2026 +0000

    #892 - record check version in database (and fix a couple of type issues in 
tests)
---
 atr/models/sql.py                               |  2 ++
 atr/tasks/checks/__init__.py                    | 11 ++++++++++
 atr/tasks/checks/compare.py                     |  1 +
 atr/tasks/checks/hashing.py                     |  1 +
 atr/tasks/checks/license.py                     |  2 ++
 atr/tasks/checks/paths.py                       |  4 ++++
 atr/tasks/checks/rat.py                         |  1 +
 atr/tasks/checks/signature.py                   |  1 +
 atr/tasks/checks/targz.py                       |  1 +
 atr/tasks/checks/zipformat.py                   |  1 +
 migrations/versions/0061_2026.03.18_7838cfcc.py | 27 +++++++++++++++++++++++++
 tests/unit/recorders.py                         | 11 ++++++----
 tests/unit/test_checks_compare.py               | 10 ++++++---
 13 files changed, 66 insertions(+), 7 deletions(-)

diff --git a/atr/models/sql.py b/atr/models/sql.py
index 7f677449..d0a1a817 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -1015,6 +1015,8 @@ class CheckResult(sqlmodel.SQLModel, table=True):
     # We don't call this latest_revision_number, because it might not be the 
latest
     revision_number: str | None = sqlmodel.Field(default=None, index=True, 
**example("00005"))
     checker: str = sqlmodel.Field(**example("atr.tasks.checks.license.files"))
+    checker_version: str | None = sqlmodel.Field(default=None, **example("2"))
+
     primary_rel_path: str | None = sqlmodel.Field(
         default=None, index=True, 
**example("apache-example-0.0.1-source.tar.gz")
     )
diff --git a/atr/tasks/checks/__init__.py b/atr/tasks/checks/__init__.py
index 0b8beaff..acefedfa 100644
--- a/atr/tasks/checks/__init__.py
+++ b/atr/tasks/checks/__init__.py
@@ -61,6 +61,7 @@ class FunctionArguments:
 
 class Recorder:
     checker: str
+    checker_version: str | None
     release_key: safe.ReleaseKey
     project_key: safe.ProjectKey
     version_key: safe.VersionKey
@@ -74,6 +75,7 @@ class Recorder:
     def __init__(
         self,
         checker: str | Callable[..., Any],
+        checker_version: str | None,
         inputs_hash: str | None,
         project_key: safe.ProjectKey,
         version_key: safe.VersionKey,
@@ -83,6 +85,7 @@ class Recorder:
         afresh: bool = True,
     ) -> None:
         self.checker = function_key(checker)
+        self.checker_version = checker_version
         self.release_key = sql.release_key(project_key, version_key)
         self.revision_number = revision_number
         self.primary_rel_path = primary_rel_path
@@ -100,6 +103,7 @@ class Recorder:
     async def create(
         cls,
         checker: str | Callable[..., Any],
+        checker_version: str | None,
         inputs_hash: str,
         project_key: safe.ProjectKey,
         version_key: safe.VersionKey,
@@ -110,6 +114,7 @@ class Recorder:
     ) -> Recorder:
         recorder = cls(
             checker,
+            checker_version,
             inputs_hash,
             project_key,
             version_key,
@@ -134,6 +139,8 @@ class Recorder:
     ) -> sql.CheckResult:
         if self.constructed is False:
             raise RuntimeError("Cannot add check result to a recorder that has 
not been constructed")
+        if self.checker_version is None:
+            raise RuntimeError("checker_version must be set before recording 
results")
         if primary_rel_path is not None:
             if self.primary_rel_path is not None:
                 raise ValueError("Cannot specify path twice")
@@ -149,6 +156,7 @@ class Recorder:
             release_key=str(self.release_key),
             revision_number=str(self.revision_number),
             checker=self.checker,
+            checker_version=self.checker_version,
             primary_rel_path=primary_rel_path or self.primary_rel_path,
             member_rel_path=member_rel_path,
             created=datetime.datetime.now(datetime.UTC),
@@ -243,6 +251,9 @@ class Recorder:
     def input_hash(self) -> str | None:
         return self.__input_hash
 
+    def set_checker_version(self, version: str) -> None:
+        self.checker_version = version
+
     async def blocker(
         self,
         message: str,
diff --git a/atr/tasks/checks/compare.py b/atr/tasks/checks/compare.py
index 73264b64..1c6fe0c3 100644
--- a/atr/tasks/checks/compare.py
+++ b/atr/tasks/checks/compare.py
@@ -84,6 +84,7 @@ class TreeComparisonResult:
 
 async def source_trees(args: checks.FunctionArguments) -> results.Results | 
None:  # noqa: C901
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION)
     is_source = await recorder.primary_path_is_source()
     if not is_source:
         log.info(
diff --git a/atr/tasks/checks/hashing.py b/atr/tasks/checks/hashing.py
index dd11a427..063f2422 100644
--- a/atr/tasks/checks/hashing.py
+++ b/atr/tasks/checks/hashing.py
@@ -34,6 +34,7 @@ CHECK_VERSION: Final[str] = "1"
 async def check(args: checks.FunctionArguments) -> results.Results | None:
     """Check the hash of a file."""
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION)
     if not (hash_abs_path := await recorder.abs_path()):
         return None
 
diff --git a/atr/tasks/checks/license.py b/atr/tasks/checks/license.py
index 722e160f..a39a715b 100644
--- a/atr/tasks/checks/license.py
+++ b/atr/tasks/checks/license.py
@@ -132,6 +132,7 @@ type Result = ArtifactResult | MemberResult | 
MemberSkippedResult
 async def files(args: checks.FunctionArguments) -> results.Results | None:
     """Check that the LICENSE and NOTICE files exist and are valid."""
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION_FILES)
     if not (artifact_abs_path := await recorder.abs_path()):
         return None
 
@@ -173,6 +174,7 @@ async def files(args: checks.FunctionArguments) -> 
results.Results | None:
 async def headers(args: checks.FunctionArguments) -> results.Results | None:
     """Check that all source files have valid license headers."""
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION_HEADERS)
     if not (artifact_abs_path := await recorder.abs_path()):
         return None
 
diff --git a/atr/tasks/checks/paths.py b/atr/tasks/checks/paths.py
index d127403f..9c33abf6 100644
--- a/atr/tasks/checks/paths.py
+++ b/atr/tasks/checks/paths.py
@@ -53,9 +53,11 @@ async def check(args: checks.FunctionArguments) -> 
results.Results | None:
     # - Incubation Policy (IP)
     # https://incubator.apache.org/policy/incubation.html
     base_recorder = await args.recorder()
+    base_recorder.set_checker_version(CHECK_VERSION)
 
     recorder_errors = await checks.Recorder.create(
         checker=checks.function_key(check) + "_errors",
+        checker_version=CHECK_VERSION,
         inputs_hash=base_recorder.input_hash or "",
         project_key=args.project_key,
         version_key=args.version_key,
@@ -65,6 +67,7 @@ async def check(args: checks.FunctionArguments) -> 
results.Results | None:
     )
     recorder_warnings = await checks.Recorder.create(
         checker=checks.function_key(check) + "_warnings",
+        checker_version=CHECK_VERSION,
         inputs_hash=base_recorder.input_hash or "",
         project_key=args.project_key,
         version_key=args.version_key,
@@ -74,6 +77,7 @@ async def check(args: checks.FunctionArguments) -> 
results.Results | None:
     )
     recorder_success = await checks.Recorder.create(
         checker=checks.function_key(check) + "_success",
+        checker_version=CHECK_VERSION,
         inputs_hash=base_recorder.input_hash or "",
         project_key=args.project_key,
         version_key=args.version_key,
diff --git a/atr/tasks/checks/rat.py b/atr/tasks/checks/rat.py
index acb1d474..1ea8a6e0 100644
--- a/atr/tasks/checks/rat.py
+++ b/atr/tasks/checks/rat.py
@@ -78,6 +78,7 @@ class RatError(RuntimeError):
 async def check(args: checks.FunctionArguments) -> results.Results | None:
     """Use Apache RAT to check the licenses of the files in the artifact."""
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION)
     if not (artifact_abs_path := await recorder.abs_path()):
         return None
     if await recorder.primary_path_is_binary():
diff --git a/atr/tasks/checks/signature.py b/atr/tasks/checks/signature.py
index 050f5ae5..92af5b2a 100644
--- a/atr/tasks/checks/signature.py
+++ b/atr/tasks/checks/signature.py
@@ -39,6 +39,7 @@ CHECK_VERSION: Final[str] = "1"
 async def check(args: checks.FunctionArguments) -> results.Results | None:
     """Check a signature file."""
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION)
     if not (primary_abs_path := await recorder.abs_path()):
         return None
 
diff --git a/atr/tasks/checks/targz.py b/atr/tasks/checks/targz.py
index dee2988d..61824592 100644
--- a/atr/tasks/checks/targz.py
+++ b/atr/tasks/checks/targz.py
@@ -76,6 +76,7 @@ def root_directory(archive_dir: pathlib.Path) -> tuple[str, 
bytes | None]:
 async def structure(args: checks.FunctionArguments) -> results.Results | None: 
 # noqa: C901
     """Check the structure of a .tar.gz file using the extracted tree."""
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION_STRUCTURE)
     if not (artifact_abs_path := await recorder.abs_path()):
         return None
     if await recorder.primary_path_is_binary():
diff --git a/atr/tasks/checks/zipformat.py b/atr/tasks/checks/zipformat.py
index defe2dbc..fe029e27 100644
--- a/atr/tasks/checks/zipformat.py
+++ b/atr/tasks/checks/zipformat.py
@@ -39,6 +39,7 @@ async def structure(args: checks.FunctionArguments) -> 
results.Results | None:
     # For simplicity, they've been updated separately for now
     # (There are several small differences to resolve between the two)
     recorder = await args.recorder()
+    recorder.set_checker_version(CHECK_VERSION_STRUCTURE)
     if not (artifact_abs_path := await recorder.abs_path()):
         return None
     if await recorder.primary_path_is_binary():
diff --git a/migrations/versions/0061_2026.03.18_7838cfcc.py 
b/migrations/versions/0061_2026.03.18_7838cfcc.py
new file mode 100644
index 00000000..e05c5ff9
--- /dev/null
+++ b/migrations/versions/0061_2026.03.18_7838cfcc.py
@@ -0,0 +1,27 @@
+"""check version into database
+
+Revision ID: 0061_2026.03.18_7838cfcc
+Revises: 0060_2026.03.16_2c8e4716
+Create Date: 2026-03-18 13:51:12.776504+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0061_2026.03.18_7838cfcc"
+down_revision: str | None = "0060_2026.03.16_2c8e4716"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    with op.batch_alter_table("checkresult", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("checker_version", sa.String(), 
nullable=True))
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("checkresult", schema=None) as batch_op:
+        batch_op.drop_column("checker_version")
diff --git a/tests/unit/recorders.py b/tests/unit/recorders.py
index 96b5c09b..2dcb67be 100644
--- a/tests/unit/recorders.py
+++ b/tests/unit/recorders.py
@@ -20,18 +20,20 @@ import pathlib
 from collections.abc import Awaitable, Callable
 from typing import Any
 
+import atr.models.safe as safe
 import atr.models.sql as sql
 import atr.tasks.checks as checks
 
 
 class RecorderStub(checks.Recorder):
-    def __init__(self, path: pathlib.Path, checker: str) -> None:
+    def __init__(self, path: pathlib.Path, checker: str, checker_version: str 
| None = None) -> None:
         super().__init__(
             checker=checker,
+            checker_version=checker_version,
             inputs_hash=None,
-            project_key="test",
-            version_key="test",
-            revision_number="00001",
+            project_key=safe.ProjectKey("test"),
+            version_key=safe.VersionKey("test"),
+            revision_number=safe.RevisionNumber("00001"),
             primary_rel_path=None,
             member_rel_path=None,
             afresh=False,
@@ -59,6 +61,7 @@ class RecorderStub(checks.Recorder):
             release_key=self.release_key,
             revision_number=self.revision_number,
             checker=self.checker,
+            checker_version=self.checker_version,
             primary_rel_path=primary_rel_path,
             member_rel_path=member_rel_path,
             created=datetime.datetime.now(datetime.UTC),
diff --git a/tests/unit/test_checks_compare.py 
b/tests/unit/test_checks_compare.py
index 229f3928..f87984b8 100644
--- a/tests/unit/test_checks_compare.py
+++ b/tests/unit/test_checks_compare.py
@@ -27,6 +27,7 @@ import dulwich.refs
 import pytest
 
 import atr.models.github
+import atr.models.safe
 import atr.models.sql
 import atr.tasks.checks
 import atr.tasks.checks.compare
@@ -201,10 +202,11 @@ class RecorderStub(atr.tasks.checks.Recorder):
     def __init__(self, is_source: bool) -> None:
         super().__init__(
             checker="compare.source_trees",
+            checker_version="1",
             inputs_hash=None,
-            project_key="project",
-            version_key="version",
-            revision_number="00001",
+            project_key=atr.models.safe.ProjectKey("project"),
+            version_key=atr.models.safe.VersionKey("version"),
+            revision_number=atr.models.safe.RevisionNumber("00001"),
             primary_rel_path="artifact.tar.gz",
             member_rel_path=None,
             afresh=False,
@@ -224,6 +226,7 @@ class RecorderStub(atr.tasks.checks.Recorder):
             release_key=self.release_key,
             revision_number=self.revision_number,
             checker=self.checker,
+            checker_version=self.checker_version,
             primary_rel_path=primary_rel_path or self.primary_rel_path,
             member_rel_path=member_rel_path,
             created=datetime.datetime.now(datetime.UTC),
@@ -241,6 +244,7 @@ class RecorderStub(atr.tasks.checks.Recorder):
             release_key=None,
             revision_number=None,
             checker=self.checker,
+            checker_version=self.checker_version,
             primary_rel_path=primary_rel_path or self.primary_rel_path,
             member_rel_path=member_rel_path,
             created=datetime.datetime.now(datetime.UTC),


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

Reply via email to