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]