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 8403836 Add committee validation
8403836 is described below
commit 8403836911212fe712d825992ba139fd57e58ff1
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Jun 25 16:45:25 2025 +0100
Add committee validation
---
atr/db/__init__.py | 3 ++
atr/validate.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 84 insertions(+)
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index cf5d44d..ad4c8e2 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -204,6 +204,7 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
committers: Opt[list[str]] = NOT_SET,
release_managers: Opt[list[str]] = NOT_SET,
name_in: Opt[list[str]] = NOT_SET,
+ _child_committees: bool = False,
_projects: bool = False,
_public_signing_keys: bool = False,
) -> Query[models.Committee]:
@@ -228,6 +229,8 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
models_committee_name =
models.validate_instrumented_attribute(models.Committee.name)
query = query.where(models_committee_name.in_(name_in))
+ if _child_committees:
+ query =
query.options(select_in_load(models.Committee.child_committees))
if _projects:
query = query.options(select_in_load(models.Committee.projects))
if _public_signing_keys:
diff --git a/atr/validate.py b/atr/validate.py
index a134c41..1b1e492 100644
--- a/atr/validate.py
+++ b/atr/validate.py
@@ -41,6 +41,8 @@ class AnnotatedDivergence(NamedTuple):
Divergences = Generator[Divergence]
AnnotatedDivergences = Generator[AnnotatedDivergence]
AsyncAnnotatedDivergences = AsyncGenerator[AnnotatedDivergence]
+CommitteeDivergences = Callable[[models.Committee], Divergences]
+CommitteeAnnotatedDivergences = Callable[[models.Committee],
AnnotatedDivergences]
ProjectDivergences = Callable[[models.Project], Divergences]
ProjectAnnotatedDivergences = Callable[[models.Project], AnnotatedDivergences]
ReleaseDivergences = Callable[[models.Release], Divergences]
@@ -49,6 +51,81 @@ ReleaseAnnotatedDivergences = Callable[[models.Release],
AnnotatedDivergences]
T = TypeVar("T")
+def committee(c: models.Committee) -> AnnotatedDivergences:
+ """Check that a committee is valid."""
+
+ yield from committee_child_committees(c)
+ yield from committee_full_name(c)
+
+
+def committee_components(
+ *components: str,
+) -> Callable[[CommitteeDivergences], CommitteeAnnotatedDivergences]:
+ """Wrap a Committee divergence generator to yield annotated divergences."""
+
+ def wrap(original: CommitteeDivergences) -> CommitteeAnnotatedDivergences:
+ def replacement(c: models.Committee) -> AnnotatedDivergences:
+ yield from divergences_with_annotations(
+ components,
+ original.__name__,
+ c.name,
+ original(c),
+ )
+
+ return replacement
+
+ return wrap
+
+
+@committee_components("Committee.full_name")
+def committee_full_name(c: models.Committee) -> Divergences:
+ """Validate the Committee.full_name value."""
+
+ full_name = c.full_name
+
+ def present(fn: str | None) -> bool:
+ return bool(fn)
+
+ yield from divergences_predicate(
+ present,
+ "value to be set",
+ full_name,
+ )
+
+ def trimmed(fn: str | None) -> bool:
+ return False if fn is None else (fn == fn.strip())
+
+ yield from divergences_predicate(
+ trimmed,
+ "value not to have surrounding whitespace",
+ full_name,
+ )
+
+ def not_prefixed(fn: str | None) -> bool:
+ return False if fn is None else (not fn.startswith("Apache "))
+
+ yield from divergences_predicate(
+ not_prefixed,
+ "value not to start with 'Apache '",
+ full_name,
+ )
+
+
+@committee_components("Committee.child_committees")
+def committee_child_committees(c: models.Committee) -> Divergences:
+ """Check that a committee has no child_committees."""
+
+ expected: list[object] = []
+ actual = c.child_committees
+ yield from divergences(expected, actual)
+
+
+def committees(cs: Iterable[models.Committee]) -> AnnotatedDivergences:
+ """Validate multiple committees."""
+ for c in cs:
+ yield from committee(c)
+
+
def divergences[T](expected: T, actual: T) -> Divergences:
"""Compare two values and yield the divergence if they differ."""
if expected != actual:
@@ -74,9 +151,13 @@ def divergences_with_annotations(
async def everything(data: db.Session) -> AsyncAnnotatedDivergences:
"""Yield divergences for all projects and releases in the DB."""
+ committees_sorted = await
data.committee(_child_committees=True).order_by(models.Committee.name).all()
projects_sorted = await
data.project(_distribution_channels=True).order_by(models.Project.name).all()
releases_sorted = await data.release().order_by(models.Release.name).all()
+ for c in await asyncio.to_thread(committees, committees_sorted):
+ yield c
+
for p in await asyncio.to_thread(projects, projects_sorted):
yield p
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]