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 36fb3e3  Add quiet modes to the interface linting scripts and fix 
detected problems
36fb3e3 is described below

commit 36fb3e3237ebc03d7eba1964bb5a4666ab3bc8fb
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Apr 15 15:58:27 2025 +0100

    Add quiet modes to the interface linting scripts and fix detected problems
---
 Makefile                     |  4 ++
 atr/datasources/apache.py    | 39 +++++--------------
 atr/util.py                  | 17 +++++++-
 scripts/interface_order.py   | 93 ++++++++++++++++++++++++++------------------
 scripts/interface_privacy.py | 16 ++++++--
 5 files changed, 98 insertions(+), 71 deletions(-)

diff --git a/Makefile b/Makefile
index 8e87f0e..f9bd81d 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,10 @@ certs:
 check:
        $(SCRIPTS)/run pre-commit run --all-files
 
+check-extra:
+       @find atr -name '*.py' -exec python3 scripts/interface_order.py {} 
--quiet \;
+       @find atr -name '*.py' -exec python3 scripts/interface_privacy.py {} 
--quiet \;
+
 docs:
        for fn in docs/*.md; \
        do cmark "$$fn" > "$${fn%.md}.html"; \
diff --git a/atr/datasources/apache.py b/atr/datasources/apache.py
index 06fe97b..51259da 100644
--- a/atr/datasources/apache.py
+++ b/atr/datasources/apache.py
@@ -20,24 +20,19 @@
 from __future__ import annotations
 
 import datetime
-from typing import TYPE_CHECKING, Annotated, TypeVar
+from typing import Annotated, Final
 
 import httpx
 import pydantic
 
 import atr.util as util
 
-if TYPE_CHECKING:
-    from collections.abc import Generator, ItemsView
-
-_WHIMSY_COMMITTEE_INFO_URL = 
"https://whimsy.apache.org/public/committee-info.json";
-_WHIMSY_COMMITTEE_RETIRED_URL = 
"https://whimsy.apache.org/public/committee-retired.json";
-_WHIMSY_PROJECTS_URL = 
"https://whimsy.apache.org/public/public_ldap_projects.json";
-_PROJECTS_PROJECTS_URL = 
"https://projects.apache.org/json/foundation/projects.json";
-_PROJECTS_PODLINGS_URL = 
"https://projects.apache.org/json/foundation/podlings.json";
-_PROJECTS_GROUPS_URL = 
"https://projects.apache.org/json/foundation/groups.json";
-
-VT = TypeVar("VT")
+_WHIMSY_COMMITTEE_INFO_URL: Final[str] = 
"https://whimsy.apache.org/public/committee-info.json";
+_WHIMSY_COMMITTEE_RETIRED_URL: Final[str] = 
"https://whimsy.apache.org/public/committee-retired.json";
+_WHIMSY_PROJECTS_URL: Final[str] = 
"https://whimsy.apache.org/public/public_ldap_projects.json";
+_PROJECTS_PROJECTS_URL: Final[str] = 
"https://projects.apache.org/json/foundation/projects.json";
+_PROJECTS_PODLINGS_URL: Final[str] = 
"https://projects.apache.org/json/foundation/podlings.json";
+_PROJECTS_GROUPS_URL: Final[str] = 
"https://projects.apache.org/json/foundation/groups.json";
 
 
 class LDAPProjectsData(pydantic.BaseModel):
@@ -114,25 +109,11 @@ class PodlingStatus(pydantic.BaseModel):
     resolution: str | None = None
 
 
-class _DictRootModel(pydantic.RootModel[dict[str, VT]]):
-    def __iter__(self) -> Generator[tuple[str, VT]]:
-        yield from self.root.items()
-
-    def items(self) -> ItemsView[str, VT]:
-        return self.root.items()
-
-    def get(self, key: str) -> VT | None:
-        return self.root.get(key)
-
-    def __len__(self) -> int:
-        return len(self.root)
-
-
-class PodlingsData(_DictRootModel[PodlingStatus]):
+class PodlingsData(util.DictRootModel[PodlingStatus]):
     pass
 
 
-class GroupsData(_DictRootModel[list[str]]):
+class GroupsData(util.DictRootModel[list[str]]):
     pass
 
 
@@ -156,7 +137,7 @@ class ProjectStatus(pydantic.BaseModel):
     release: list[Release] = pydantic.Field(default_factory=list)
 
 
-class ProjectsData(_DictRootModel[ProjectStatus]):
+class ProjectsData(util.DictRootModel[ProjectStatus]):
     pass
 
 
diff --git a/atr/util.py b/atr/util.py
index 6f5b178..81e0c8b 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -28,7 +28,7 @@ import tarfile
 import tempfile
 import uuid
 import zipfile
-from collections.abc import AsyncGenerator, Callable, Mapping, Sequence
+from collections.abc import AsyncGenerator, Callable, Generator, ItemsView, 
Mapping, Sequence
 from typing import Annotated, Any, TypeVar
 
 import aiofiles.os
@@ -50,10 +50,25 @@ import atr.user as user
 
 F = TypeVar("F", bound="QuartFormTyped")
 T = TypeVar("T")
+VT = TypeVar("VT")
 
 _LOGGER = logging.getLogger(__name__)
 
 
+class DictRootModel(pydantic.RootModel[dict[str, VT]]):
+    def __iter__(self) -> Generator[tuple[str, VT]]:
+        yield from self.root.items()
+
+    def items(self) -> ItemsView[str, VT]:
+        return self.root.items()
+
+    def get(self, key: str) -> VT | None:
+        return self.root.get(key)
+
+    def __len__(self) -> int:
+        return len(self.root)
+
+
 # from 
https://github.com/pydantic/pydantic/discussions/8755#discussioncomment-8417979
 @dataclasses.dataclass
 class DictToList:
diff --git a/scripts/interface_order.py b/scripts/interface_order.py
index e95b6e7..0df9707 100644
--- a/scripts/interface_order.py
+++ b/scripts/interface_order.py
@@ -5,6 +5,12 @@ import enum
 import pathlib
 import sys
 from collections.abc import Sequence
+from typing import Final
+
+_CLASS_EXEMPTIONS: Final[set[str]] = {
+    "atr/datasources/apache.py",
+    "atr/db/models.py",
+}
 
 
 class ExitCode(enum.IntEnum):
@@ -13,6 +19,56 @@ class ExitCode(enum.IntEnum):
     USAGE_ERROR = 2
 
 
+def check_order(file_path: pathlib.Path, quiet: bool) -> bool:
+    content = _read_file_content(file_path)
+    if content is None:
+        sys.exit(ExitCode.FAILURE)
+
+    tree = _parse_python_code(content, str(file_path))
+    if tree is None:
+        sys.exit(ExitCode.FAILURE)
+
+    class_names = _extract_top_level_class_names(tree)
+    function_names = _extract_top_level_function_names(tree)
+
+    all_ok = True
+    if not _verify_names_are_sorted(function_names, str(file_path), 
"function"):
+        all_ok = False
+
+    for class_name in class_names:
+        if class_name.startswith("_"):
+            print(f"!! {file_path} - class '{class_name}' is private", 
file=sys.stderr)
+            all_ok = False
+
+    if (not quiet) or (str(file_path) not in _CLASS_EXEMPTIONS):
+        if not _verify_names_are_sorted(class_names, str(file_path), "class"):
+            all_ok = False
+
+    return all_ok
+
+
+def main() -> None:
+    quiet = sys.argv[2:3] == ["--quiet"]
+    argc = len(sys.argv)
+    match (argc, quiet):
+        case (2, False):
+            ...
+        case (3, True):
+            ...
+        case _:
+            print("Usage: python interface_order.py <filename> [ --quiet ]", 
file=sys.stderr)
+            sys.exit(ExitCode.USAGE_ERROR)
+
+    file_path = pathlib.Path(sys.argv[1])
+    all_ok = check_order(file_path, quiet)
+    if all_ok:
+        if not quiet:
+            print(f"ok {file_path}")
+        sys.exit(ExitCode.SUCCESS)
+    else:
+        sys.exit(ExitCode.FAILURE)
+
+
 def _extract_top_level_function_names(tree: ast.Module) -> list[str]:
     function_names: list[str] = []
     for node in tree.body:
@@ -75,42 +131,5 @@ def _verify_names_are_sorted(names: Sequence[str], 
filename: str, interface_type
     return False
 
 
-def main() -> None:
-    if len(sys.argv) != 2:
-        print("Usage: python interface_order.py <filename>", file=sys.stderr)
-        sys.exit(ExitCode.USAGE_ERROR)
-
-    file_path = pathlib.Path(sys.argv[1])
-
-    content = _read_file_content(file_path)
-    if content is None:
-        sys.exit(ExitCode.FAILURE)
-
-    tree = _parse_python_code(content, str(file_path))
-    if tree is None:
-        sys.exit(ExitCode.FAILURE)
-
-    class_names = _extract_top_level_class_names(tree)
-    function_names = _extract_top_level_function_names(tree)
-
-    all_ok = True
-    if not _verify_names_are_sorted(function_names, str(file_path), 
"function"):
-        all_ok = False
-
-    for class_name in class_names:
-        if class_name.startswith("_"):
-            print(f"!! {file_path} - class '{class_name}' is private", 
file=sys.stderr)
-            all_ok = False
-
-    if not _verify_names_are_sorted(class_names, str(file_path), "class"):
-        all_ok = False
-
-    if all_ok:
-        print(f"ok {file_path}")
-        sys.exit(ExitCode.SUCCESS)
-    else:
-        sys.exit(ExitCode.FAILURE)
-
-
 if __name__ == "__main__":
     main()
diff --git a/scripts/interface_privacy.py b/scripts/interface_privacy.py
index 3211d4f..efc50a3 100644
--- a/scripts/interface_privacy.py
+++ b/scripts/interface_privacy.py
@@ -59,9 +59,16 @@ def _read_file_content(file_path: pathlib.Path) -> str | 
None:
 
 def main() -> None:
     """Main entry point for the script."""
-    if len(sys.argv) != 2:
-        print(f"Usage: {sys.argv[0]} <filename.py>", file=sys.stderr)
-        sys.exit(ExitCode.USAGE_ERROR)
+    quiet = sys.argv[2:3] == ["--quiet"]
+    argc = len(sys.argv)
+    match (argc, quiet):
+        case (2, False):
+            ...
+        case (3, True):
+            ...
+        case _:
+            print(f"Usage: {sys.argv[0]} <filename.py> [ --quiet ]", 
file=sys.stderr)
+            sys.exit(ExitCode.USAGE_ERROR)
 
     file_path = pathlib.Path(sys.argv[1])
     filename = str(file_path)
@@ -87,7 +94,8 @@ def main() -> None:
             print(f"!! {filename}:{lineno}:{col} - access to {name}")
         sys.exit(ExitCode.FAILURE)
     else:
-        print(f"ok {filename}")
+        if not quiet:
+            print(f"ok {filename}")
         sys.exit(ExitCode.SUCCESS)
 
 


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

Reply via email to