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-releases.git


The following commit(s) were added to refs/heads/main by this push:
     new af926e3  Add a lint to check imports in the models
af926e3 is described below

commit af926e3f0114acb07bd0d6bdae04bc8c9e357887
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Jan 20 15:15:27 2026 +0000

    Add a lint to check imports in the models
---
 .pre-commit-config.yaml         |  8 ++++
 atr/models/attestable.py        |  2 +-
 scripts/check_models_imports.py | 82 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 91 insertions(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0c823fc..4a71a40 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -185,3 +185,11 @@ repos:
       language: system
       pass_filenames: false
       always_run: true
+
+    - id: check-models-imports
+      name: check models imports
+      description: Ensure that the models only import from approved packages
+      entry: uv run --frozen python scripts/check_models_imports.py
+      language: system
+      pass_filenames: false
+      files: ^atr/models/.*\.py$
diff --git a/atr/models/attestable.py b/atr/models/attestable.py
index f49cc1f..3cff655 100644
--- a/atr/models/attestable.py
+++ b/atr/models/attestable.py
@@ -19,7 +19,7 @@ from typing import Annotated, Literal
 
 import pydantic
 
-import atr.models.schema as schema
+from . import schema
 
 
 class HashEntry(schema.Strict):
diff --git a/scripts/check_models_imports.py b/scripts/check_models_imports.py
new file mode 100755
index 0000000..80ae8e3
--- /dev/null
+++ b/scripts/check_models_imports.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+# 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.
+
+import ast
+import pathlib
+import sys
+from typing import Final
+
+_ALLOWED_PACKAGES: Final = frozenset(
+    {
+        "pydantic",
+        "pydantic_core",
+        "sqlalchemy",
+        "sqlmodel",
+    }
+)
+
+
+def _check_file(path: pathlib.Path) -> list[str]:
+    errors = []
+    tree = ast.parse(path.read_text(), filename=str(path))
+
+    for node in ast.walk(tree):
+        if isinstance(node, ast.Import):
+            for alias in node.names:
+                root = alias.name.split(".")[0]
+                if not _is_stdlib(alias.name) and (root not in 
_ALLOWED_PACKAGES):
+                    errors.append(f"{path}:{node.lineno}: disallowed import 
'{alias.name}'")
+
+        elif isinstance(node, ast.ImportFrom):
+            if node.level > 0:
+                # This uses "from ." or "from .name" syntax
+                continue
+            if node.module is None:
+                # This should be unreachable
+                continue
+            root = node.module.split(".")[0]
+            if not _is_stdlib(node.module) and (root not in _ALLOWED_PACKAGES):
+                errors.append(f"{path}:{node.lineno}: disallowed import from 
'{node.module}'")
+
+    return errors
+
+
+def _is_stdlib(module: str) -> bool:
+    root = module.split(".")[0]
+    return root in sys.stdlib_module_names
+
+
+def _run() -> int:
+    models_dir = pathlib.Path(__file__).parent.parent / "atr" / "models"
+    errors = []
+
+    for path in models_dir.glob("*.py"):
+        errors.extend(_check_file(path))
+
+    for error in errors:
+        print(error, file=sys.stderr)
+
+    return 1 if errors else 0
+
+
+def main() -> None:
+    sys.exit(_run())
+
+
+if __name__ == "__main__":
+    main()


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

Reply via email to