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-releases-client.git


The following commit(s) were added to refs/heads/main by this push:
     new aa301a9  Ensure that the latest revision is detected correctly
aa301a9 is described below

commit aa301a9d5ee7a93789a728d02a3ec7918f3ee561
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Jul 28 19:18:31 2025 +0100

    Ensure that the latest revision is detected correctly
---
 pyproject.toml              |  4 ++--
 src/atrclient/client.py     | 10 ++++++++--
 src/atrclient/models/api.py | 43 +++++++++++++++++++++++++++++++++++--------
 src/atrclient/models/sql.py | 40 ++++++++++++++++++++++++++++++----------
 tests/cli_workflow.t        |  4 ++++
 tests/test_all.py           |  2 ++
 uv.lock                     |  4 ++--
 7 files changed, 83 insertions(+), 24 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 5690345..844a6c1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ build-backend = "hatchling.build"
 
 [project]
 name            = "apache-trusted-releases"
-version         = "0.20250728.1433"
+version         = "0.20250728.1817"
 description     = "ATR CLI and Python API"
 readme          = "README.md"
 requires-python = ">=3.13"
@@ -74,4 +74,4 @@ select = [
 ]
 
 [tool.uv]
-exclude-newer = "2025-07-28T14:33:00Z"
+exclude-newer = "2025-07-28T18:17:00Z"
diff --git a/src/atrclient/client.py b/src/atrclient/client.py
index 575a42b..3a9c860 100755
--- a/src/atrclient/client.py
+++ b/src/atrclient/client.py
@@ -26,6 +26,7 @@ import base64
 import contextlib
 import copy
 import datetime
+import functools
 import hashlib
 import importlib.metadata as metadata
 import io
@@ -122,13 +123,18 @@ R = TypeVar("R", bound=models.api.Results)
 
 def api_get(path: str) -> Callable[[Callable[..., R]], Callable[..., R]]:
     def decorator(func: Callable[..., R]) -> Callable[..., R]:
+        @functools.wraps(func)
         def wrapper(*args: str, **kwargs: str | None) -> R:
             api_instance = ApiGet(path)
             try:
                 response = func(api_instance, *args, **kwargs)
-            except (pydantic.ValidationError, models.api.ResultsTypeError) as 
e:
+            except pydantic.ValidationError as e:
+                error_summary = "\n".join([f"  - {err['loc'][1]}: 
{err['msg']}" for err in e.errors()])
+                show_error_and_exit(f"API response failed 
validation:\n{error_summary}")
+            except (aiohttp.ClientError, models.api.ResultsTypeError) as e:
                 show_error_and_exit(f"Unexpected API GET response: {e}")
-            return response
+            else:
+                return response
 
         return wrapper
 
diff --git a/src/atrclient/models/api.py b/src/atrclient/models/api.py
index eaa9a2f..3a565d8 100644
--- a/src/atrclient/models/api.py
+++ b/src/atrclient/models/api.py
@@ -26,28 +26,42 @@ from . import schema, sql, tabulate
 T = TypeVar("T")
 
 
+def example(value: Any) -> dict[Literal["json_schema_extra"], dict[str, Any]]:
+    return {"json_schema_extra": {"example": value}}
+
+
 class ResultsTypeError(TypeError):
     pass
 
 
 class AnnounceArgs(schema.Strict):
-    project: str
-    version: str
-    revision: str
-    email_to: str
-    subject: str
-    body: str
-    path_suffix: str
+    project: str = schema.Field(..., **example("example"))
+    version: str = schema.Field(..., **example("1.0.0"))
+    revision: str = schema.Field(..., **example("00005"))
+    email_to: str = schema.Field(..., **example("[email protected]"))
+    subject: str = schema.Field(..., **example("[ANNOUNCE] Apache Example 
1.0.0 release"))
+    body: str = schema.Field(
+        ...,
+        **example("The Apache Example team is pleased to announce the release 
of Example 1.0.0..."),
+    )
+    path_suffix: str = schema.Field(..., **example("example/1.0.0"))
 
 
 class AnnounceResults(schema.Strict):
     endpoint: Literal["/announce"] = schema.Field(alias="endpoint")
-    success: str
+    success: str = schema.Field(..., **example("Announcement sent"))
 
 
 class ChecksListResults(schema.Strict):
     endpoint: Literal["/checks/list"] = schema.Field(alias="endpoint")
     checks: Sequence[sql.CheckResult]
+    checks_revision: str = schema.Field(..., **example("00005"))
+    current_phase: sql.ReleasePhase = schema.Field(..., 
**example(sql.ReleasePhase.RELEASE_CANDIDATE))
+
+    @pydantic.field_validator("current_phase", mode="before")
+    @classmethod
+    def current_phase_to_enum(cls, v):
+        return sql.ReleasePhase(v) if isinstance(v, str) else v
 
 
 class ChecksOngoingResults(schema.Strict):
@@ -255,6 +269,19 @@ class ReleasesVersionResults(schema.Strict):
     endpoint: Literal["/releases/version"] = schema.Field(alias="endpoint")
     release: sql.Release
 
+    @pydantic.field_validator("release", mode="before")
+    @classmethod
+    def _preserve_latest_revision_number(cls, v):
+        if isinstance(v, dict):
+            data = dict(v)
+            lrn = data.pop("latest_revision_number", None)
+            allowed = {k: data[k] for k in data if k in 
sql.Release.model_fields}
+            obj = sql.Release(**allowed)
+            if lrn is not None:
+                setattr(obj, "_latest_revision_number", lrn)
+            return obj
+        return v
+
 
 class ReleasesRevisionsResults(schema.Strict):
     endpoint: Literal["/releases/revisions"] = schema.Field(alias="endpoint")
diff --git a/src/atrclient/models/sql.py b/src/atrclient/models/sql.py
index 20b8857..b224a5e 100644
--- a/src/atrclient/models/sql.py
+++ b/src/atrclient/models/sql.py
@@ -24,7 +24,7 @@
 
 import datetime
 import enum
-from typing import Any, Final, Optional
+from typing import Any, Final, Literal, Optional, TypeVar
 
 import pydantic
 import sqlalchemy
@@ -35,6 +35,8 @@ import sqlmodel
 
 from . import results, schema
 
+T = TypeVar("T")
+
 sqlmodel.SQLModel.metadata = sqlalchemy.MetaData(
     naming_convention={
         "ix": "ix_%(table_name)s_%(column_0_N_name)s",
@@ -64,6 +66,12 @@ class ProjectStatus(str, enum.Enum):
 
 
 class ReleasePhase(str, enum.Enum):
+    # TODO: Rename these to the UI names?
+    # COMPOSE, VOTE, FINISH, "DISTRIBUTE"
+    # Compose a draft
+    # Vote on a candidate
+    # Finish a preview
+    # Distribute a (finished) release
     # Step 1: The candidate files are added from external sources and checked 
by ATR
     RELEASE_CANDIDATE_DRAFT = "release_candidate_draft"
     # Step 2: The project members are voting on the candidate release
@@ -638,9 +646,14 @@ class Release(sqlmodel.SQLModel, table=True):
 # SQL models referencing Committee, Project, or Release
 
 
+def example(value: Any) -> dict[Literal["schema_extra"], dict[str, Any]]:
+    return {"schema_extra": {"json_schema_extra": {"examples": [value]}}}
+
+
 # CheckResult: Release
 class CheckResult(sqlmodel.SQLModel, table=True):
-    id: int = sqlmodel.Field(default=None, primary_key=True)
+    # TODO: We have default=None here with a field typed int, not int | None
+    id: int = sqlmodel.Field(default=None, primary_key=True, **example(123))
 
     # M-1: CheckResult -> Release
     # 1-M: Release -C-> [CheckResult]
@@ -648,14 +661,21 @@ class CheckResult(sqlmodel.SQLModel, table=True):
     release: Release = sqlmodel.Relationship(back_populates="check_results")
 
     # We don't call this latest_revision_number, because it might not be the 
latest
-    revision_number: str | None = sqlmodel.Field(default=None, index=True)
-    checker: str
-    primary_rel_path: str | None = sqlmodel.Field(default=None, index=True)
-    member_rel_path: str | None = sqlmodel.Field(default=None, index=True)
-    created: datetime.datetime = 
sqlmodel.Field(sa_column=sqlalchemy.Column(UTCDateTime))
-    status: CheckResultStatus
-    message: str
-    data: Any = sqlmodel.Field(sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+    revision_number: str | None = sqlmodel.Field(default=None, index=True, 
**example("00005"))
+    checker: str = 
sqlmodel.Field(**example("atr.tasks.checks.hashing.HashingCheck"))
+    primary_rel_path: str | None = sqlmodel.Field(
+        default=None, index=True, 
**example("apache-example-0.0.1-source.tar.gz")
+    )
+    member_rel_path: str | None = sqlmodel.Field(default=None, index=True, 
**example("apache-example-0.0.1/pom.xml"))
+    created: datetime.datetime = sqlmodel.Field(
+        sa_column=sqlalchemy.Column(UTCDateTime),
+        **example(datetime.datetime(2025, 1, 1, 12, 0, 0, 
tzinfo=datetime.UTC)),
+    )
+    status: CheckResultStatus = 
sqlmodel.Field(default=CheckResultStatus.SUCCESS, 
**example(CheckResultStatus.SUCCESS))
+    message: str = sqlmodel.Field(**example("sha512 matches for 
apache-example-0.0.1/pom.xml"))
+    data: Any = sqlmodel.Field(
+        sa_column=sqlalchemy.Column(sqlalchemy.JSON), **example({"expected": 
"...", "found": "..."})
+    )
 
 
 # DistributionChannel: Project
diff --git a/tests/cli_workflow.t b/tests/cli_workflow.t
index bce9b7a..4ad2500 100644
--- a/tests/cli_workflow.t
+++ b/tests/cli_workflow.t
@@ -35,6 +35,10 @@ $ atr upload tooling-test-example 0.3+cli atr-client.conf 
<!config_rel_path!>
 $ atr checks wait tooling-test-example 0.3+cli -i 25
 Checks completed.
 
+$ atr checks status tooling-test-example 0.3+cli
+Total checks: 1
+  warning: 1
+
 $ atr checks status tooling-test-example 0.3+cli 00002
 Total checks: 1
   warning: 1
diff --git a/tests/test_all.py b/tests/test_all.py
index 2268b06..9af300d 100755
--- a/tests/test_all.py
+++ b/tests/test_all.py
@@ -107,6 +107,8 @@ def test_app_checks_status_verbose(capsys: 
pytest.CaptureFixture[str], fixture_c
 
     checks_payload = {
         "endpoint": "/checks/list",
+        "checks_revision": "00003",
+        "current_phase": "release_candidate_draft",
         "checks": [
             {
                 "release_name": "test-project-2.3.1",
diff --git a/uv.lock b/uv.lock
index 14311ba..2bf35e5 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,7 @@ version = 1
 requires-python = ">=3.13"
 
 [options]
-exclude-newer = "2025-07-28T14:33:00Z"
+exclude-newer = "2025-07-28T18:17:00Z"
 
 [[package]]
 name = "aiohappyeyeballs"
@@ -83,7 +83,7 @@ wheels = [
 
 [[package]]
 name = "apache-trusted-releases"
-version = "0.20250728.1433"
+version = "0.20250728.1817"
 source = { editable = "." }
 dependencies = [
     { name = "aiohttp" },


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

Reply via email to