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 386fe98  Add more examples to model documentation
386fe98 is described below

commit 386fe98b99fe1486acf41bd966b59ce1be3f3108
Author: Sean B. Palmer <s...@miscoranda.com>
AuthorDate: Tue Jul 29 15:46:36 2025 +0100

    Add more examples to model documentation
---
 atr/blueprints/api/api.py | 13 +++----
 atr/models/api.py         | 95 +++++++++++++++++++++++++----------------------
 atr/models/sql.py         | 83 +++++++++++++++++++++++++++--------------
 atr/models/tabulate.py    | 47 ++++++++++++++++-------
 4 files changed, 146 insertions(+), 92 deletions(-)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 0cbec73..80313a7 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -314,7 +314,6 @@ async def key_add(data: models.api.KeyAddArgs) -> 
DictResponse:
 
     return models.api.KeyAddResults(
         endpoint="/key/add",
-        success="Key added",
         fingerprint=key.key_model.fingerprint.upper(),
     ).model_dump(), 200
 
@@ -348,7 +347,7 @@ async def key_delete(data: models.api.KeyDeleteArgs) -> 
DictResponse:
 
     return models.api.KeyDeleteResults(
         endpoint="/key/delete",
-        success="Key deleted",
+        success=True,
     ).model_dump(), 200
 
 
@@ -574,7 +573,7 @@ async def release_delete(data: 
models.api.ReleaseDeleteArgs) -> DictResponse:
         await db_data.commit()
     return models.api.ReleaseDeleteResults(
         endpoint="/release/delete",
-        deleted=release_name,
+        deleted=True,
     ).model_dump(), 200
 
 
@@ -615,7 +614,7 @@ async def release_draft_delete(data: 
models.api.ReleaseDraftDeleteArgs) -> DictR
         await db_data.commit()
     return models.api.ReleaseDraftDeleteResults(
         endpoint="/release/draft/delete",
-        success=f"Draft {release_name} deleted",
+        success=True,
     ).model_dump(), 200
 
 
@@ -843,7 +842,7 @@ async def ssh_key_delete(data: models.api.SshKeyDeleteArgs) 
-> DictResponse:
     await keys.ssh_key_delete(data.fingerprint, asf_uid)
     return models.api.SshKeyDeleteResults(
         endpoint="/ssh-key/delete",
-        success="SSH key deleted",
+        success=True,
     ).model_dump(), 201
 
 
@@ -964,7 +963,6 @@ async def vote_resolve(data: models.api.VoteResolveArgs) -> 
DictResponse:
         match data.resolution:
             case "passed":
                 release.phase = sql.ReleasePhase.RELEASE_PREVIEW
-                success_message = "Vote marked as passed"
                 description = "Create a preview revision from the last 
candidate draft"
                 async with revision.create_and_manage(
                     data.project, release.version, asf_uid, 
description=description
@@ -972,11 +970,10 @@ async def vote_resolve(data: models.api.VoteResolveArgs) 
-> DictResponse:
                     pass
             case "failed":
                 release.phase = sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT
-                success_message = "Vote marked as failed"
         await db_data.commit()
     return models.api.VoteResolveResults(
         endpoint="/vote/resolve",
-        success=success_message,
+        success=True,
     ).model_dump(), 200
 
 
diff --git a/atr/models/api.py b/atr/models/api.py
index e50de9c..414a11d 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -92,7 +92,6 @@ class KeyAddArgs(schema.Strict):
 
 class KeyAddResults(schema.Strict):
     endpoint: Literal["/key/add"] = schema.Field(alias="endpoint")
-    success: str = schema.Field(..., **example("Key added"))
     fingerprint: str = schema.Field(..., 
**example("0123456789abcdef0123456789abcdef01234567"))
 
 
@@ -102,7 +101,7 @@ class KeyDeleteArgs(schema.Strict):
 
 class KeyDeleteResults(schema.Strict):
     endpoint: Literal["/key/delete"] = schema.Field(alias="endpoint")
-    success: str = schema.Field(..., **example("Key deleted"))
+    success: Literal[True] = schema.Field(..., **example(True))
 
 
 class KeyGetResults(schema.Strict):
@@ -180,7 +179,7 @@ class ReleaseAnnounceArgs(schema.Strict):
 
 class ReleaseAnnounceResults(schema.Strict):
     endpoint: Literal["/release/announce"] = schema.Field(alias="endpoint")
-    success: bool = schema.Field(..., **example(True))
+    success: Literal[True] = schema.Field(..., **example(True))
 
 
 class ReleaseDraftDeleteArgs(schema.Strict):
@@ -190,12 +189,12 @@ class ReleaseDraftDeleteArgs(schema.Strict):
 
 class ReleaseDraftDeleteResults(schema.Strict):
     endpoint: Literal["/release/draft/delete"] = schema.Field(alias="endpoint")
-    success: str = schema.Field(..., **example("Draft 'example-0.0.1' 
deleted"))
+    success: Literal[True] = schema.Field(..., **example(True))
 
 
 class ReleaseCreateArgs(schema.Strict):
-    project: str
-    version: str
+    project: str = schema.Field(..., **example("example"))
+    version: str = schema.Field(..., **example("0.0.1"))
 
 
 class ReleaseCreateResults(schema.Strict):
@@ -204,13 +203,13 @@ class ReleaseCreateResults(schema.Strict):
 
 
 class ReleaseDeleteArgs(schema.Strict):
-    project: str
-    version: str
+    project: str = schema.Field(..., **example("example"))
+    version: str = schema.Field(..., **example("0.0.1"))
 
 
 class ReleaseDeleteResults(schema.Strict):
     endpoint: Literal["/release/delete"] = schema.Field(alias="endpoint")
-    deleted: str
+    deleted: Literal[True] = schema.Field(..., **example(True))
 
 
 class ReleaseGetResults(schema.Strict):
@@ -233,7 +232,7 @@ class ReleaseGetResults(schema.Strict):
 
 class ReleasePathsResults(schema.Strict):
     endpoint: Literal["/release/paths"] = schema.Field(alias="endpoint")
-    rel_paths: Sequence[str]
+    rel_paths: Sequence[str] = schema.Field(..., 
**example(["example/0.0.1/example-0.0.1-bin.tar.gz"]))
 
 
 class ReleaseRevisionsResults(schema.Strict):
@@ -242,10 +241,10 @@ class ReleaseRevisionsResults(schema.Strict):
 
 
 class ReleaseUploadArgs(schema.Strict):
-    project: str
-    version: str
-    relpath: str
-    content: str
+    project: str = schema.Field(..., **example("example"))
+    version: str = schema.Field(..., **example("0.0.1"))
+    relpath: str = schema.Field(..., 
**example("example/0.0.1/example-0.0.1-bin.tar.gz"))
+    content: str = schema.Field(..., **example("This is the content of the 
file."))
 
 
 class ReleaseUploadResults(schema.Strict):
@@ -267,42 +266,48 @@ class ReleasesListResults(schema.Strict):
 
 
 class SignatureProvenanceArgs(schema.Strict):
-    artifact_file_name: str
-    artifact_sha3_256: str
-    signature_file_name: str
-    signature_asc_text: str
-    signature_sha3_256: str
+    artifact_file_name: str = schema.Field(..., 
**example("example-0.0.1-bin.tar.gz"))
+    artifact_sha3_256: str = schema.Field(..., 
**example("0123456789abcdef0123456789abcdef01234567"))
+    signature_file_name: str = schema.Field(..., 
**example("example-0.0.1-bin.tar.gz.asc"))
+    signature_asc_text: str = schema.Field(
+        ..., **example("-----BEGIN PGP SIGNATURE-----\n\n...\n-----END PGP 
SIGNATURE-----\n")
+    )
+    signature_sha3_256: str = schema.Field(..., 
**example("0123456789abcdef0123456789abcdef01234567"))
 
 
 class SignatureProvenanceKey(schema.Strict):
-    committee: str
-    keys_file_url: str
-    keys_file_sha3_256: str
+    committee: str = schema.Field(..., **example("example"))
+    keys_file_url: str = schema.Field(..., 
**example("https://example.apache.org/example/KEYS";))
+    keys_file_sha3_256: str = schema.Field(..., 
**example("0123456789abcdef0123456789abcdef01234567"))
 
 
 class SignatureProvenanceResults(schema.Strict):
     endpoint: Literal["/signature/provenance"] = schema.Field(alias="endpoint")
-    fingerprint: str
-    key_asc_text: str
+    fingerprint: str = schema.Field(..., 
**example("0123456789abcdef0123456789abcdef01234567"))
+    key_asc_text: str = schema.Field(
+        ..., **example("-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n...\n-----END 
PGP PUBLIC KEY BLOCK-----\n")
+    )
     committees_with_artifact: list[SignatureProvenanceKey]
 
 
 class SshKeyAddArgs(schema.Strict):
-    text: str
+    text: str = schema.Field(
+        ..., **example("ssh-ed25519 
AAAAC3NzaC1lZDI1NTEgH5C9okWi0dh25AAAAIOMqqnkVzrm0SdG6UOoqKLsabl9GKJl")
+    )
 
 
 class SshKeyAddResults(schema.Strict):
     endpoint: Literal["/ssh-key/add"] = schema.Field(alias="endpoint")
-    fingerprint: str
+    fingerprint: str = schema.Field(..., 
**example("0123456789abcdef0123456789abcdef01234567"))
 
 
 class SshKeyDeleteArgs(schema.Strict):
-    fingerprint: str
+    fingerprint: str = schema.Field(..., 
**example("0123456789abcdef0123456789abcdef01234567"))
 
 
 class SshKeyDeleteResults(schema.Strict):
     endpoint: Literal["/ssh-key/delete"] = schema.Field(alias="endpoint")
-    success: str
+    success: Literal[True] = schema.Field(..., **example(True))
 
 
 @dataclasses.dataclass
@@ -314,7 +319,7 @@ class SshKeysListQuery:
 class SshKeysListResults(schema.Strict):
     endpoint: Literal["/ssh-keys/list"] = schema.Field(alias="endpoint")
     data: Sequence[sql.SSHKey]
-    count: int
+    count: int = schema.Field(..., **example(10))
 
 
 @dataclasses.dataclass
@@ -327,33 +332,35 @@ class TasksListQuery:
 class TasksListResults(schema.Strict):
     endpoint: Literal["/tasks/list"] = schema.Field(alias="endpoint")
     data: Sequence[sql.Task]
-    count: int
+    count: int = schema.Field(..., **example(10))
 
 
 class UsersListResults(schema.Strict):
     endpoint: Literal["/users/list"] = schema.Field(alias="endpoint")
-    users: Sequence[str]
+    users: Sequence[str] = schema.Field(..., **example(["user1", "user2"]))
 
 
 class VoteResolveArgs(schema.Strict):
-    project: str
-    version: str
-    resolution: Literal["passed", "failed"]
+    project: str = schema.Field(..., **example("example"))
+    version: str = schema.Field(..., **example("0.0.1"))
+    resolution: Literal["passed", "failed"] = schema.Field(..., 
**example("passed"))
 
 
 class VoteResolveResults(schema.Strict):
     endpoint: Literal["/vote/resolve"] = schema.Field(alias="endpoint")
-    success: str
+    success: Literal[True] = schema.Field(..., **example(True))
 
 
 class VoteStartArgs(schema.Strict):
-    project: str
-    version: str
-    revision: str
-    email_to: str
-    vote_duration: int
-    subject: str
-    body: str
+    project: str = schema.Field(..., **example("example"))
+    version: str = schema.Field(..., **example("0.0.1"))
+    revision: str = schema.Field(..., **example("00005"))
+    email_to: str = schema.Field(..., **example("d...@example.apache.org"))
+    vote_duration: int = schema.Field(..., **example(10))
+    subject: str = schema.Field(..., **example("[VOTE] Apache Example 0.0.1 
release"))
+    body: str = schema.Field(
+        ..., **example("The Apache Example team is pleased to announce the 
release of Example 0.0.1...")
+    )
 
 
 class VoteStartResults(schema.Strict):
@@ -362,8 +369,8 @@ class VoteStartResults(schema.Strict):
 
 
 class VoteTabulateArgs(schema.Strict):
-    project: str
-    version: str
+    project: str = schema.Field(..., **example("example"))
+    version: str = schema.Field(..., **example("0.0.1"))
 
 
 class VoteTabulateResults(schema.Strict):
diff --git a/atr/models/sql.py b/atr/models/sql.py
index 9af0a0a..4a1ccc8 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -121,13 +121,21 @@ class UserRole(str, enum.Enum):
 # Pydantic models
 
 
+def pydantic_example(value: Any) -> dict[Literal["json_schema_extra"], 
dict[str, Any]]:
+    return {"json_schema_extra": {"example": value}}
+
+
 class VoteEntry(schema.Strict):
-    result: bool
-    summary: str
-    binding_votes: int
-    community_votes: int
-    start: datetime.datetime
-    end: datetime.datetime
+    result: bool = schema.Field(alias="result", **pydantic_example(True))
+    summary: str = schema.Field(alias="summary", **pydantic_example("This is a 
summary"))
+    binding_votes: int = schema.Field(alias="binding_votes", 
**pydantic_example(10))
+    community_votes: int = schema.Field(alias="community_votes", 
**pydantic_example(10))
+    start: datetime.datetime = schema.Field(
+        alias="start", **pydantic_example(datetime.datetime(2025, 5, 5, 1, 2, 
3, tzinfo=datetime.UTC))
+    )
+    end: datetime.datetime = schema.Field(
+        alias="end", **pydantic_example(datetime.datetime(2025, 5, 7, 1, 2, 3, 
tzinfo=datetime.UTC))
+    )
 
 
 # Type decorators
@@ -551,24 +559,32 @@ class Release(sqlmodel.SQLModel, table=True):
 
     # We guarantee that "{project.name}-{version}" is unique
     # Therefore we can use that for the name
-    name: str = sqlmodel.Field(default="", primary_key=True, unique=True)
-    phase: ReleasePhase
-    created: datetime.datetime = 
sqlmodel.Field(sa_column=sqlalchemy.Column(UTCDateTime))
-    released: datetime.datetime | None = sqlmodel.Field(default=None, 
sa_column=sqlalchemy.Column(UTCDateTime))
+    name: str = sqlmodel.Field(default="", primary_key=True, unique=True, 
**example("example-0.0.1"))
+    phase: ReleasePhase = 
sqlmodel.Field(**example(ReleasePhase.RELEASE_CANDIDATE_DRAFT))
+    created: datetime.datetime = sqlmodel.Field(
+        sa_column=sqlalchemy.Column(UTCDateTime), 
**example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC))
+    )
+    released: datetime.datetime | None = sqlmodel.Field(
+        default=None,
+        sa_column=sqlalchemy.Column(UTCDateTime),
+        **example(datetime.datetime(2025, 6, 1, 1, 2, 3, tzinfo=datetime.UTC)),
+    )
 
     # M-1: Release -> Project
     # 1-M: Project -> [Release]
-    project_name: str = sqlmodel.Field(foreign_key="project.name")
+    project_name: str = sqlmodel.Field(foreign_key="project.name", 
**example("example"))
     project: Project = sqlmodel.Relationship(back_populates="releases")
     see_also(Project.releases)
 
-    package_managers: list[str] = sqlmodel.Field(default_factory=list, 
sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+    package_managers: list[str] = sqlmodel.Field(
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON), 
**example([])
+    )
     # TODO: Not all releases have a version
     # We could either make this str | None, or we could require version to be 
set on packages only
     # For example, Apache Airflow Providers do not have an overall version
     # They have one version per package, i.e. per provider
-    version: str
-    sboms: list[str] = sqlmodel.Field(default_factory=list, 
sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+    version: str = sqlmodel.Field(**example("0.0.1"))
+    sboms: list[str] = sqlmodel.Field(default_factory=list, 
sa_column=sqlalchemy.Column(sqlalchemy.JSON), **example([]))
 
     # 1-1: Release -C-> ReleasePolicy
     # 1-1: ReleasePolicy -> Release
@@ -579,10 +595,18 @@ class Release(sqlmodel.SQLModel, table=True):
 
     # VoteEntry is a Pydantic model, not a SQL model
     votes: list[VoteEntry] = sqlmodel.Field(default_factory=list, 
sa_column=sqlalchemy.Column(sqlalchemy.JSON))
-    vote_manual: bool = sqlmodel.Field(default=False)
-    vote_started: datetime.datetime | None = sqlmodel.Field(default=None, 
sa_column=sqlalchemy.Column(UTCDateTime))
-    vote_resolved: datetime.datetime | None = sqlmodel.Field(default=None, 
sa_column=sqlalchemy.Column(UTCDateTime))
-    podling_thread_id: str | None = sqlmodel.Field(default=None)
+    vote_manual: bool = sqlmodel.Field(default=False, **example(False))
+    vote_started: datetime.datetime | None = sqlmodel.Field(
+        default=None,
+        sa_column=sqlalchemy.Column(UTCDateTime),
+        **example(datetime.datetime(2025, 5, 5, 1, 2, 3, tzinfo=datetime.UTC)),
+    )
+    vote_resolved: datetime.datetime | None = sqlmodel.Field(
+        default=None,
+        sa_column=sqlalchemy.Column(UTCDateTime),
+        **example(datetime.datetime(2025, 5, 7, 1, 2, 3, tzinfo=datetime.UTC)),
+    )
+    podling_thread_id: str | None = sqlmodel.Field(default=None, 
**example("hmk1lpwnnxn5zsbp8gwh7115h2qm7jrh"))
 
     # 1-M: Release -C-> [Revision]
     # M-1: Revision -> Release
@@ -625,6 +649,7 @@ class Release(sqlmodel.SQLModel, table=True):
             raise ValueError("Release has no revisions")
         return number
 
+    # TODO: How do we give an example for this?
     @pydantic.computed_field  # type: ignore[prop-decorator]
     @property
     def latest_revision_number(self) -> str | None:
@@ -777,11 +802,11 @@ class ReleasePolicy(sqlmodel.SQLModel, table=True):
 
 # Revision: Release
 class Revision(sqlmodel.SQLModel, table=True):
-    name: str = sqlmodel.Field(default="", primary_key=True, unique=True)
+    name: str = sqlmodel.Field(default="", primary_key=True, unique=True, 
**example("example-0.0.1 00002"))
 
     # M-1: Revision -> Release
     # 1-M: Release -C-> [Revision]
-    release_name: str | None = sqlmodel.Field(default=None, 
foreign_key="release.name")
+    release_name: str | None = sqlmodel.Field(default=None, 
foreign_key="release.name", **example("example-0.0.1"))
     release: Release = sqlmodel.Relationship(
         back_populates="revisions",
         sa_relationship_kwargs={
@@ -789,19 +814,23 @@ class Revision(sqlmodel.SQLModel, table=True):
         },
     )
 
-    seq: int = sqlmodel.Field(default=0)
+    seq: int = sqlmodel.Field(default=0, **example(1))
     # This was designed as a property, but it's better for it to be a column
     # That way, we can do dynamic Release.latest_revision_number construction 
easier
-    number: str = sqlmodel.Field(default="")
-    asfuid: str
+    number: str = sqlmodel.Field(default="", **example("00002"))
+    asfuid: str = sqlmodel.Field(**example("user"))
     created: datetime.datetime = sqlmodel.Field(
-        default_factory=lambda: datetime.datetime.now(datetime.UTC), 
sa_column=sqlalchemy.Column(UTCDateTime)
+        default_factory=lambda: datetime.datetime.now(datetime.UTC),
+        sa_column=sqlalchemy.Column(UTCDateTime),
+        **example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
-    phase: ReleasePhase
+    phase: ReleasePhase = 
sqlmodel.Field(**example(ReleasePhase.RELEASE_CANDIDATE_DRAFT))
 
     # 1-1: Revision -> Revision
     # 1-1: Revision -> Revision
-    parent_name: str | None = sqlmodel.Field(default=None, 
foreign_key="revision.name")
+    parent_name: str | None = sqlmodel.Field(
+        default=None, foreign_key="revision.name", **example("example-0.0.1 
00001")
+    )
     parent: Optional["Revision"] = sqlmodel.Relationship(
         sa_relationship_kwargs=dict(
             remote_side=lambda: Revision.name,
@@ -815,7 +844,7 @@ class Revision(sqlmodel.SQLModel, table=True):
     # 1-1: Revision -> Revision
     child: Optional["Revision"] = 
sqlmodel.Relationship(back_populates="parent")
 
-    description: str | None = sqlmodel.Field(default=None)
+    description: str | None = sqlmodel.Field(default=None, **example("This is 
a description"))
 
     def model_post_init(self, _context):
         if isinstance(self.created, str):
diff --git a/atr/models/tabulate.py b/atr/models/tabulate.py
index e58b035..fdd0ff1 100644
--- a/atr/models/tabulate.py
+++ b/atr/models/tabulate.py
@@ -16,6 +16,7 @@
 # under the License.
 
 import enum
+from typing import Any, Literal
 
 import pydantic
 
@@ -36,15 +37,19 @@ class VoteStatus(enum.Enum):
     UNKNOWN = "Unknown"
 
 
+def example(value: Any) -> dict[Literal["json_schema_extra"], dict[str, Any]]:
+    return {"json_schema_extra": {"example": value}}
+
+
 class VoteEmail(schema.Strict):
-    asf_uid_or_email: str
-    from_email: str
-    status: VoteStatus
-    asf_eid: str
-    iso_datetime: str
-    vote: Vote
-    quotation: str
-    updated: bool
+    asf_uid_or_email: str = schema.Field(..., **example("user"))
+    from_email: str = schema.Field(..., **example("u...@example.org"))
+    status: VoteStatus = schema.Field(..., **example(VoteStatus.BINDING))
+    asf_eid: str = schema.Field(..., 
**example("102ed8a-503db792-79bc789-b8ca8...@apache.org"))
+    iso_datetime: str = schema.Field(..., **example("2025-05-01T12:00:00Z"))
+    vote: Vote = schema.Field(..., **example(Vote.YES))
+    quotation: str = schema.Field(..., **example("+1 (Binding)"))
+    updated: bool = schema.Field(..., **example(True))
 
     @pydantic.field_validator("status", mode="before")
     @classmethod
@@ -58,8 +63,24 @@ class VoteEmail(schema.Strict):
 
 
 class VoteDetails(schema.Strict):
-    start_unixtime: int | None
-    votes: dict[str, VoteEmail]
-    summary: dict[str, int]
-    passed: bool
-    outcome: str
+    start_unixtime: int | None = schema.Field(..., **example(1714435200))
+    votes: dict[str, VoteEmail] = schema.Field(
+        ...,
+        **example(
+            {
+                "user": VoteEmail(
+                    asf_uid_or_email="user",
+                    from_email="u...@example.org",
+                    status=VoteStatus.BINDING,
+                    asf_eid="102ed8a-503db792-79bc789-b8ca8...@apache.org",
+                    iso_datetime="2025-05-01T12:00:00Z",
+                    vote=Vote.YES,
+                    quotation="+1 (Binding)",
+                    updated=True,
+                )
+            }
+        ),
+    )
+    summary: dict[str, int] = schema.Field(..., **example({"user": 1}))
+    passed: bool = schema.Field(..., **example(True))
+    outcome: str = schema.Field(..., **example("The vote passed."))


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@tooling.apache.org
For additional commands, e-mail: commits-h...@tooling.apache.org

Reply via email to