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 c8af58d  Fix class order
c8af58d is described below

commit c8af58dc1b5331096b0149e595718746e90a455c
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Apr 15 15:25:04 2025 +0100

    Fix class order
---
 atr/config.py              |  24 +++----
 atr/db/__init__.py         | 169 ++++++++++++++++++++++-----------------------
 atr/routes/draft.py        |  14 ++--
 atr/routes/keys.py         |   8 +--
 atr/ssh.py                 |   4 +-
 atr/tasks/sbom.py          |  14 ++--
 atr/tasks/vote.py          |   6 +-
 scripts/interface_order.py |  37 +++++++---
 8 files changed, 146 insertions(+), 130 deletions(-)

diff --git a/atr/config.py b/atr/config.py
index 5cb2d5b..9ec87f4 100644
--- a/atr/config.py
+++ b/atr/config.py
@@ -25,15 +25,6 @@ _MB: Final = 1024 * 1024
 _GB: Final = 1024 * _MB
 
 
-class Mode(enum.Enum):
-    Debug = "Debug"
-    Production = "Production"
-    Profiling = "Profiling"
-
-
-_global_mode: Mode | None = None
-
-
 class AppConfig:
     SSH_HOST = decouple.config("SSH_HOST", default="0.0.0.0")
     SSH_PORT = decouple.config("SSH_PORT", default=2222, cast=int)
@@ -67,15 +58,24 @@ class AppConfig:
     )
 
 
-class ProductionConfig(AppConfig): ...
-
-
 class DebugConfig(AppConfig):
     DEBUG = True
     TEMPLATES_AUTO_RELOAD = True
     USE_BLOCKBUSTER = False
 
 
+class Mode(enum.Enum):
+    Debug = "Debug"
+    Production = "Production"
+    Profiling = "Profiling"
+
+
+_global_mode: Mode | None = None
+
+
+class ProductionConfig(AppConfig): ...
+
+
 class ProfilingConfig(AppConfig):
     DEBUG = False
     TEMPLATES_AUTO_RELOAD = False
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index 4c829dc..30b317b 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -46,11 +46,8 @@ _global_atr_sessionmaker: 
sqlalchemy.ext.asyncio.async_sessionmaker | None = Non
 
 T = TypeVar("T")
 
-# TODO: The not set class should be NotSet
-# And the constant should be _NOT_SET
 
-
-class _NotSetType:
+class NotSet:
     """
     A marker class to indicate that a value is not set and thus should
     not be considered. This is different to None.
@@ -73,10 +70,8 @@ class _NotSetType:
         return NotSet
 
 
-# TODO: Technically a constant, and only used in this file
-# Should be _NOT_SET
-NotSet: Final[_NotSetType] = _NotSetType()
-type Opt[T] = T | _NotSetType
+_NOT_SET: Final[NotSet] = NotSet()
+type Opt[T] = T | NotSet
 
 
 class Query(Generic[T]):
@@ -112,14 +107,14 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def check_result(
         self,
-        id: Opt[int] = NotSet,
-        release_name: Opt[str] = NotSet,
-        checker: Opt[str] = NotSet,
-        primary_rel_path: Opt[str | None] = NotSet,
-        created: Opt[datetime.datetime] = NotSet,
-        status: Opt[models.CheckResultStatus] = NotSet,
-        message: Opt[str] = NotSet,
-        data: Opt[Any] = NotSet,
+        id: Opt[int] = _NOT_SET,
+        release_name: Opt[str] = _NOT_SET,
+        checker: Opt[str] = _NOT_SET,
+        primary_rel_path: Opt[str | None] = _NOT_SET,
+        created: Opt[datetime.datetime] = _NOT_SET,
+        status: Opt[models.CheckResultStatus] = _NOT_SET,
+        message: Opt[str] = _NOT_SET,
+        data: Opt[Any] = _NOT_SET,
         _release: bool = False,
     ) -> Query[models.CheckResult]:
         query = sqlmodel.select(models.CheckResult)
@@ -148,16 +143,16 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def committee(
         self,
-        id: Opt[int] = NotSet,
-        name: Opt[str] = NotSet,
-        full_name: Opt[str] = NotSet,
-        is_podling: Opt[bool] = NotSet,
-        parent_committee_id: Opt[int] = NotSet,
-        committee_members: Opt[list[str]] = NotSet,
-        committers: Opt[list[str]] = NotSet,
-        release_managers: Opt[list[str]] = NotSet,
-        vote_policy_id: Opt[int] = NotSet,
-        name_in: Opt[list[str]] = NotSet,
+        id: Opt[int] = _NOT_SET,
+        name: Opt[str] = _NOT_SET,
+        full_name: Opt[str] = _NOT_SET,
+        is_podling: Opt[bool] = _NOT_SET,
+        parent_committee_id: Opt[int] = _NOT_SET,
+        committee_members: Opt[list[str]] = _NOT_SET,
+        committers: Opt[list[str]] = _NOT_SET,
+        release_managers: Opt[list[str]] = _NOT_SET,
+        vote_policy_id: Opt[int] = _NOT_SET,
+        name_in: Opt[list[str]] = _NOT_SET,
         _projects: bool = False,
         _public_signing_keys: bool = False,
     ) -> Query[models.Committee]:
@@ -193,14 +188,14 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def package(
         self,
-        artifact_sha3: Opt[str] = NotSet,
-        artifact_type: Opt[str] = NotSet,
-        filename: Opt[str] = NotSet,
-        sha512: Opt[str] = NotSet,
-        signature_sha3: Opt[str | None] = NotSet,
-        uploaded: Opt[datetime.datetime] = NotSet,
-        bytes_size: Opt[int] = NotSet,
-        release_name: Opt[str] = NotSet,
+        artifact_sha3: Opt[str] = _NOT_SET,
+        artifact_type: Opt[str] = _NOT_SET,
+        filename: Opt[str] = _NOT_SET,
+        sha512: Opt[str] = _NOT_SET,
+        signature_sha3: Opt[str | None] = _NOT_SET,
+        uploaded: Opt[datetime.datetime] = _NOT_SET,
+        bytes_size: Opt[int] = _NOT_SET,
+        release_name: Opt[str] = _NOT_SET,
         _release: bool = False,
         _release_project: bool = False,
         _release_committee: bool = False,
@@ -237,12 +232,12 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def project(
         self,
-        id: Opt[int] = NotSet,
-        name: Opt[str] = NotSet,
-        full_name: Opt[str] = NotSet,
-        is_podling: Opt[bool] = NotSet,
-        committee_id: Opt[int] = NotSet,
-        vote_policy_id: Opt[int] = NotSet,
+        id: Opt[int] = _NOT_SET,
+        name: Opt[str] = _NOT_SET,
+        full_name: Opt[str] = _NOT_SET,
+        is_podling: Opt[bool] = _NOT_SET,
+        committee_id: Opt[int] = _NOT_SET,
+        vote_policy_id: Opt[int] = _NOT_SET,
         _committee: bool = False,
         _releases: bool = False,
         _distribution_channels: bool = False,
@@ -279,14 +274,14 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def public_signing_key(
         self,
-        fingerprint: Opt[str] = NotSet,
-        algorithm: Opt[str] = NotSet,
-        length: Opt[int] = NotSet,
-        created: Opt[datetime.datetime] = NotSet,
-        expires: Opt[datetime.datetime | None] = NotSet,
-        declared_uid: Opt[str | None] = NotSet,
-        apache_uid: Opt[str] = NotSet,
-        ascii_armored_key: Opt[str] = NotSet,
+        fingerprint: Opt[str] = _NOT_SET,
+        algorithm: Opt[str] = _NOT_SET,
+        length: Opt[int] = _NOT_SET,
+        created: Opt[datetime.datetime] = _NOT_SET,
+        expires: Opt[datetime.datetime | None] = _NOT_SET,
+        declared_uid: Opt[str | None] = _NOT_SET,
+        apache_uid: Opt[str] = _NOT_SET,
+        ascii_armored_key: Opt[str] = _NOT_SET,
         _committees: bool = False,
     ) -> Query[models.PublicSigningKey]:
         query = sqlmodel.select(models.PublicSigningKey)
@@ -315,16 +310,16 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def release(
         self,
-        name: Opt[str] = NotSet,
-        stage: Opt[models.ReleaseStage] = NotSet,
-        phase: Opt[models.ReleasePhase] = NotSet,
-        created: Opt[datetime.datetime] = NotSet,
-        project_id: Opt[int] = NotSet,
-        package_managers: Opt[list[str]] = NotSet,
-        version: Opt[str] = NotSet,
-        sboms: Opt[list[str]] = NotSet,
-        vote_policy_id: Opt[int] = NotSet,
-        votes: Opt[list[models.VoteEntry]] = NotSet,
+        name: Opt[str] = _NOT_SET,
+        stage: Opt[models.ReleaseStage] = _NOT_SET,
+        phase: Opt[models.ReleasePhase] = _NOT_SET,
+        created: Opt[datetime.datetime] = _NOT_SET,
+        project_id: Opt[int] = _NOT_SET,
+        package_managers: Opt[list[str]] = _NOT_SET,
+        version: Opt[str] = _NOT_SET,
+        sboms: Opt[list[str]] = _NOT_SET,
+        vote_policy_id: Opt[int] = _NOT_SET,
+        votes: Opt[list[models.VoteEntry]] = _NOT_SET,
         _project: bool = False,
         _packages: bool = False,
         _vote_policy: bool = False,
@@ -369,9 +364,9 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def ssh_key(
         self,
-        fingerprint: Opt[str] = NotSet,
-        key: Opt[str] = NotSet,
-        asf_uid: Opt[str] = NotSet,
+        fingerprint: Opt[str] = _NOT_SET,
+        key: Opt[str] = _NOT_SET,
+        asf_uid: Opt[str] = _NOT_SET,
     ) -> Query[models.SSHKey]:
         query = sqlmodel.select(models.SSHKey)
 
@@ -386,19 +381,19 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def task(
         self,
-        id: Opt[int] = NotSet,
-        status: Opt[models.TaskStatus] = NotSet,
-        task_type: Opt[str] = NotSet,
-        task_args: Opt[Any] = NotSet,
-        added: Opt[datetime.datetime] = NotSet,
-        started: Opt[datetime.datetime | None] = NotSet,
-        pid: Opt[int | None] = NotSet,
-        completed: Opt[datetime.datetime | None] = NotSet,
-        result: Opt[Any | None] = NotSet,
-        error: Opt[str | None] = NotSet,
-        release_name: Opt[str | None] = NotSet,
-        path: Opt[str | None] = NotSet,
-        modified: Opt[int | None] = NotSet,
+        id: Opt[int] = _NOT_SET,
+        status: Opt[models.TaskStatus] = _NOT_SET,
+        task_type: Opt[str] = _NOT_SET,
+        task_args: Opt[Any] = _NOT_SET,
+        added: Opt[datetime.datetime] = _NOT_SET,
+        started: Opt[datetime.datetime | None] = _NOT_SET,
+        pid: Opt[int | None] = _NOT_SET,
+        completed: Opt[datetime.datetime | None] = _NOT_SET,
+        result: Opt[Any | None] = _NOT_SET,
+        error: Opt[str | None] = _NOT_SET,
+        release_name: Opt[str | None] = _NOT_SET,
+        path: Opt[str | None] = _NOT_SET,
+        modified: Opt[int | None] = _NOT_SET,
         _release: bool = False,
     ) -> Query[models.Task]:
         query = sqlmodel.select(models.Task)
@@ -433,12 +428,12 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def vote_policy(
         self,
-        id: Opt[int] = NotSet,
-        mailto_addresses: Opt[list[str]] = NotSet,
-        manual_vote: Opt[bool] = NotSet,
-        min_hours: Opt[int] = NotSet,
-        release_checklist: Opt[str] = NotSet,
-        pause_for_rm: Opt[bool] = NotSet,
+        id: Opt[int] = _NOT_SET,
+        mailto_addresses: Opt[list[str]] = _NOT_SET,
+        manual_vote: Opt[bool] = _NOT_SET,
+        min_hours: Opt[int] = _NOT_SET,
+        release_checklist: Opt[str] = _NOT_SET,
+        pause_for_rm: Opt[bool] = _NOT_SET,
         _project: bool = False,
     ) -> Query[models.VotePolicy]:
         query = sqlmodel.select(models.VotePolicy)
@@ -463,9 +458,9 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
     def text_value(
         self,
-        ns: Opt[str] = NotSet,
-        key: Opt[str] = NotSet,
-        value: Opt[str] = NotSet,
+        ns: Opt[str] = _NOT_SET,
+        key: Opt[str] = _NOT_SET,
+        value: Opt[str] = _NOT_SET,
     ) -> Query[models.TextValue]:
         query = sqlmodel.select(models.TextValue)
 
@@ -552,12 +547,12 @@ async def init_database_for_worker() -> None:
     )
 
 
-def is_defined(v: T | _NotSetType) -> TypeGuard[T]:
-    return not isinstance(v, _NotSetType)
+def is_defined(v: T | NotSet) -> TypeGuard[T]:
+    return not isinstance(v, NotSet)
 
 
-def is_undefined(v: T | _NotSetType) -> TypeGuard[_NotSetType]:  # pyright: 
ignore [reportInvalidTypeVarUse]
-    return isinstance(v, _NotSetType)
+def is_undefined(v: T | NotSet) -> TypeGuard[NotSet]:
+    return isinstance(v, NotSet)
 
 
 # async def recent_tasks(data: Session, release_name: str, file_path: str, 
modified: int) -> dict[str, models.Task]:
diff --git a/atr/routes/draft.py b/atr/routes/draft.py
index a56e150..0ee0447 100644
--- a/atr/routes/draft.py
+++ b/atr/routes/draft.py
@@ -65,6 +65,13 @@ class AddProtocol(Protocol):
     project_name: wtforms.SelectField
 
 
+class DeleteFileForm(util.QuartFormTyped):
+    """Form for deleting a file."""
+
+    file_path = wtforms.StringField("File path", 
validators=[wtforms.validators.InputRequired("File path is required")])
+    submit = wtforms.SubmitField("Delete file")
+
+
 class DeleteForm(util.QuartFormTyped):
     """Form for deleting a candidate draft."""
 
@@ -81,13 +88,6 @@ class DeleteForm(util.QuartFormTyped):
     submit = wtforms.SubmitField("Delete candidate draft")
 
 
-class DeleteFileForm(util.QuartFormTyped):
-    """Form for deleting a file."""
-
-    file_path = wtforms.StringField("File path", 
validators=[wtforms.validators.InputRequired("File path is required")])
-    submit = wtforms.SubmitField("Delete file")
-
-
 class PromoteForm(util.QuartFormTyped):
     """Form for promoting a candidate draft."""
 
diff --git a/atr/routes/keys.py b/atr/routes/keys.py
index 64a5943..2142991 100644
--- a/atr/routes/keys.py
+++ b/atr/routes/keys.py
@@ -43,15 +43,15 @@ import atr.routes as routes
 import atr.util as util
 
 
-class DeleteKeyForm(util.QuartFormTyped):
-    submit = wtforms.SubmitField("Delete key")
-
-
 class AddSSHKeyForm(util.QuartFormTyped):
     key = wtforms.StringField("SSH key", widget=wtforms.widgets.TextArea())
     submit = wtforms.SubmitField("Add SSH key")
 
 
+class DeleteKeyForm(util.QuartFormTyped):
+    submit = wtforms.SubmitField("Delete key")
+
+
 @routes.committer("/keys/add", methods=["GET", "POST"])
 async def add(session: routes.CommitterSession) -> str:
     """Add a new public signing key to the user's account."""
diff --git a/atr/ssh.py b/atr/ssh.py
index 8f7f8bd..ec74bc4 100644
--- a/atr/ssh.py
+++ b/atr/ssh.py
@@ -42,7 +42,7 @@ _CONFIG: Final = config.get()
 T = TypeVar("T")
 
 
-class _SSHServer(asyncssh.SSHServer):
+class SSHServer(asyncssh.SSHServer):
     """Simple SSH server that handles connections."""
 
     def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
@@ -113,7 +113,7 @@ async def server_start() -> asyncssh.SSHAcceptor:
         _LOGGER.info(f"Generated SSH host key at {key_path}")
 
     server = await asyncssh.create_server(
-        _SSHServer,
+        SSHServer,
         server_host_keys=[key_path],
         process_factory=_handle_client,
         host=_CONFIG.SSH_HOST,
diff --git a/atr/tasks/sbom.py b/atr/tasks/sbom.py
index c3d0c91..75a871a 100644
--- a/atr/tasks/sbom.py
+++ b/atr/tasks/sbom.py
@@ -34,6 +34,13 @@ _CONFIG: Final = config.get()
 _LOGGER: Final = logging.getLogger(__name__)
 
 
+class GenerateCycloneDX(pydantic.BaseModel):
+    """Arguments for the task to generate a CycloneDX SBOM."""
+
+    artifact_path: str = pydantic.Field(..., description="Absolute path to the 
artifact")
+    output_path: str = pydantic.Field(..., description="Absolute path where 
the generated SBOM JSON should be written")
+
+
 class SBOMGenerationError(Exception):
     """Custom exception for SBOM generation failures."""
 
@@ -42,13 +49,6 @@ class SBOMGenerationError(Exception):
         self.details = details or {}
 
 
-class GenerateCycloneDX(pydantic.BaseModel):
-    """Arguments for the task to generate a CycloneDX SBOM."""
-
-    artifact_path: str = pydantic.Field(..., description="Absolute path to the 
artifact")
-    output_path: str = pydantic.Field(..., description="Absolute path where 
the generated SBOM JSON should be written")
-
-
 def archive_extract_safe(
     archive_path: str,
     extract_dir: str,
diff --git a/atr/tasks/vote.py b/atr/tasks/vote.py
index ef1bd56..d38e276 100644
--- a/atr/tasks/vote.py
+++ b/atr/tasks/vote.py
@@ -33,9 +33,6 @@ _LOGGER: Final = logging.getLogger(__name__)
 _LOGGER.setLevel(logging.DEBUG)
 
 
-class VoteInitiationError(Exception): ...
-
-
 class Initiate(pydantic.BaseModel):
     """Arguments for the task to start a vote."""
 
@@ -50,6 +47,9 @@ class Initiate(pydantic.BaseModel):
     body: str = pydantic.Field(..., description="Body content for the vote 
email")
 
 
+class VoteInitiationError(Exception): ...
+
+
 @checks.with_model(Initiate)
 async def initiate(args: Initiate) -> str | None:
     """Initiate a vote for a release."""
diff --git a/scripts/interface_order.py b/scripts/interface_order.py
index e7f571d..e95b6e7 100644
--- a/scripts/interface_order.py
+++ b/scripts/interface_order.py
@@ -21,6 +21,14 @@ def _extract_top_level_function_names(tree: ast.Module) -> 
list[str]:
     return function_names
 
 
+def _extract_top_level_class_names(tree: ast.Module) -> list[str]:
+    class_names: list[str] = []
+    for node in tree.body:
+        if isinstance(node, ast.ClassDef):
+            class_names.append(node.name)
+    return class_names
+
+
 def _parse_python_code(code: str, filename: str) -> ast.Module | None:
     try:
         return ast.parse(code, filename=filename)
@@ -47,17 +55,21 @@ def _toggle_sortability(name: str) -> str:
         return "_" + name
 
 
-def _verify_names_are_sorted(names: Sequence[str], filename: str) -> bool:
+def _verify_names_are_sorted(names: Sequence[str], filename: str, 
interface_type: str) -> bool:
     is_sorted = all(names[i] <= names[i + 1] for i in range(len(names) - 1))
     if is_sorted:
         return True
 
     for i in range(len(names) - 1):
         if names[i] > names[i + 1]:
-            a = _toggle_sortability(names[i])
-            b = _toggle_sortability(names[i + 1])
+            if interface_type == "class":
+                a = names[i]
+                b = names[i + 1]
+            else:
+                a = _toggle_sortability(names[i])
+                b = _toggle_sortability(names[i + 1])
             print(
-                f"!! {filename} - function '{b}' is misordered relative to 
'{a}'",
+                f"!! {filename} - {interface_type} '{b}' is misordered 
relative to '{a}'",
                 file=sys.stderr,
             )
     return False
@@ -78,13 +90,22 @@ def main() -> None:
     if tree is None:
         sys.exit(ExitCode.FAILURE)
 
+    class_names = _extract_top_level_class_names(tree)
     function_names = _extract_top_level_function_names(tree)
 
-    if not function_names:
-        print(f"ok {file_path} - no top level functions")
-        sys.exit(ExitCode.SUCCESS)
+    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 _verify_names_are_sorted(function_names, str(file_path)):
+    if all_ok:
         print(f"ok {file_path}")
         sys.exit(ExitCode.SUCCESS)
     else:


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

Reply via email to