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]