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 05a6128 Use more consistent API names
05a6128 is described below
commit 05a61284681c69ffd88ae62ab30bb070e0d3adcf
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Jul 29 14:58:17 2025 +0100
Use more consistent API names
---
pyproject.toml | 9 +-
src/atrclient/client.py | 166 ++++++++++++------------
src/atrclient/models/api.py | 308 ++++++++++++++++++++------------------------
tests/cli_ssh.t | 2 +-
tests/test_all.py | 17 ++-
uv.lock | 15 ++-
6 files changed, 253 insertions(+), 264 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index b240c74..6831840 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ build-backend = "hatchling.build"
[project]
name = "apache-trusted-releases"
-version = "0.20250729.1318"
+version = "0.20250729.1358"
description = "ATR CLI and Python API"
readme = "README.md"
requires-python = ">=3.13"
@@ -73,5 +73,10 @@ select = [
"W"
]
+[tool.pytest.ini_options]
+filterwarnings = [
+ "ignore:imghdr was removed in Python
3.13:DeprecationWarning:pgpy.constants",
+]
+
[tool.uv]
-exclude-newer = "2025-07-29T13:18:00Z"
+exclude-newer = "2025-07-29T13:58:00Z"
diff --git a/src/atrclient/client.py b/src/atrclient/client.py
index 55cb8dd..eb30934 100755
--- a/src/atrclient/client.py
+++ b/src/atrclient/client.py
@@ -170,28 +170,22 @@ def api_checks_ongoing(
return models.api.validate_checks_ongoing(response)
-@api_post("/draft/delete")
-def api_draft_delete(api: ApiPost, args: models.api.DraftDeleteArgs) ->
models.api.DraftDeleteResults:
+@api_post("/key/add")
+def api_key_add(api: ApiPost, args: models.api.KeyAddArgs) ->
models.api.KeyAddResults:
response = api.post(args)
- return models.api.validate_draft_delete(response)
+ return models.api.validate_key_add(response)
-@api_post("/keys/add")
-def api_keys_add(api: ApiPost, args: models.api.KeysAddArgs) ->
models.api.KeysAddResults:
+@api_post("/key/delete")
+def api_key_delete(api: ApiPost, args: models.api.KeyDeleteArgs) ->
models.api.KeyDeleteResults:
response = api.post(args)
- return models.api.validate_keys_add(response)
+ return models.api.validate_key_delete(response)
-@api_post("/keys/delete")
-def api_keys_delete(api: ApiPost, args: models.api.KeysDeleteArgs) ->
models.api.KeysDeleteResults:
- response = api.post(args)
- return models.api.validate_keys_delete(response)
-
-
-@api_get("/keys/get")
-def api_keys_get(api: ApiGet, fingerprint: str) -> models.api.KeysGetResults:
+@api_get("/key/get")
+def api_key_get(api: ApiGet, fingerprint: str) -> models.api.KeyGetResults:
response = api.get(fingerprint)
- return models.api.validate_keys_get(response)
+ return models.api.validate_key_get(response)
@api_post("/keys/upload")
@@ -206,78 +200,88 @@ def api_keys_user(api: ApiGet, asf_uid: str) ->
models.api.KeysUserResults:
return models.api.validate_keys_user(response)
+@api_get("/project/releases")
+def api_project_releases(api: ApiGet, project: str) ->
models.api.ProjectReleasesResults:
+ response = api.get(project)
+ return models.api.validate_project_releases(response)
+
+
@api_post("/release/announce")
def api_release_announce(api: ApiPost, args: models.api.ReleaseAnnounceArgs)
-> models.api.ReleaseAnnounceResults:
response = api.post(args)
return models.api.validate_release_announce(response)
-@api_post("/releases/create")
-def api_releases_create(api: ApiPost, args: models.api.ReleasesCreateArgs) ->
models.api.ReleasesCreateResults:
+@api_post("/release/create")
+def api_release_create(api: ApiPost, args: models.api.ReleaseCreateArgs) ->
models.api.ReleaseCreateResults:
response = api.post(args)
- return models.api.validate_releases_create(response)
+ return models.api.validate_release_create(response)
-@api_post("/releases/delete")
-def api_releases_delete(api: ApiPost, args: models.api.ReleasesDeleteArgs) ->
models.api.ReleasesDeleteResults:
+@api_post("/release/delete")
+def api_release_delete(api: ApiPost, args: models.api.ReleaseDeleteArgs) ->
models.api.ReleaseDeleteResults:
response = api.post(args)
- return models.api.validate_releases_delete(response)
+ return models.api.validate_release_delete(response)
-@api_get("/releases/paths")
-def api_releases_paths(
- api: ApiGet, project: str, version: str, revision: str | None = None
-) -> models.api.ReleasesPathsResults:
- response = api.get(project, version, revision=revision)
- return models.api.validate_releases_paths(response)
+@api_post("/release/draft/delete")
+def api_release_draft_delete(
+ api: ApiPost, args: models.api.ReleaseDraftDeleteArgs
+) -> models.api.ReleaseDraftDeleteResults:
+ response = api.post(args)
+ return models.api.validate_release_draft_delete(response)
-@api_get("/releases/project")
-def api_releases_project(api: ApiGet, project: str) ->
models.api.ReleasesProjectResults:
- response = api.get(project)
- return models.api.validate_releases_project(response)
+@api_get("/release/paths")
+def api_release_paths(
+ api: ApiGet, project: str, version: str, revision: str | None = None
+) -> models.api.ReleasePathsResults:
+ response = api.get(project, version, revision=revision)
+ return models.api.validate_release_paths(response)
-@api_get("/releases/version")
-def api_releases_version(api: ApiGet, project: str, version: str) ->
models.api.ReleasesVersionResults:
+@api_get("/release/get")
+def api_release_get(api: ApiGet, project: str, version: str) ->
models.api.ReleaseGetResults:
response = api.get(project, version)
- return models.api.validate_releases_version(response)
+ return models.api.validate_release_get(response)
-@api_get("/revisions")
-def api_revisions(api: ApiGet, project: str, version: str) ->
models.api.RevisionsResults:
+@api_get("/release/revisions")
+def api_release_revisions(api: ApiGet, project: str, version: str) ->
models.api.ReleaseRevisionsResults:
response = api.get(project, version)
- return models.api.validate_revisions(response)
+ return models.api.validate_release_revisions(response)
-@api_post("/ssh/add")
-def api_ssh_add(api: ApiPost, args: models.api.SshAddArgs) ->
models.api.SshAddResults:
+@api_post("/release/upload")
+def api_release_upload(api: ApiPost, args: models.api.ReleaseUploadArgs) ->
models.api.ReleaseUploadResults:
response = api.post(args)
- return models.api.validate_ssh_add(response)
+ return models.api.validate_release_upload(response)
-@api_post("/ssh/delete")
-def api_ssh_delete(api: ApiPost, args: models.api.SshDeleteArgs) ->
models.api.SshDeleteResults:
+@api_post("/signature/provenance")
+def api_signature_provenance(
+ api: ApiPost, args: models.api.SignatureProvenanceArgs
+) -> models.api.SignatureProvenanceResults:
response = api.post(args)
- return models.api.validate_ssh_delete(response)
+ return models.api.validate_signature_provenance(response)
-@api_get("/ssh/list")
-def api_ssh_list(api: ApiGet, asf_uid: str) -> models.api.SshListResults:
- response = api.get(asf_uid)
- return models.api.validate_ssh_list(response)
+@api_post("/ssh-key/add")
+def api_ssh_key_add(api: ApiPost, args: models.api.SshKeyAddArgs) ->
models.api.SshKeyAddResults:
+ response = api.post(args)
+ return models.api.validate_ssh_key_add(response)
-@api_post("/upload")
-def api_upload(api: ApiPost, args: models.api.UploadArgs) ->
models.api.UploadResults:
+@api_post("/ssh-key/delete")
+def api_ssh_key_delete(api: ApiPost, args: models.api.SshKeyDeleteArgs) ->
models.api.SshKeyDeleteResults:
response = api.post(args)
- return models.api.validate_upload(response)
+ return models.api.validate_ssh_key_delete(response)
-@api_post("/verify/provenance")
-def api_verify_provenance(api: ApiPost, args: models.api.VerifyProvenanceArgs)
-> models.api.VerifyProvenanceResults:
- response = api.post(args)
- return models.api.validate_verify_provenance(response)
+@api_get("/ssh-keys/list")
+def api_ssh_keys_list(api: ApiGet, asf_uid: str) ->
models.api.SshKeysListResults:
+ response = api.get(asf_uid)
+ return models.api.validate_ssh_keys_list(response)
@api_post("/vote/resolve")
@@ -376,7 +380,7 @@ def app_checks_status(
revision: str | None = None,
verbose: Annotated[bool, cyclopts.Parameter(alias="-v", name="--verbose")]
= False,
) -> None:
- releases_version = api_releases_version(project, version)
+ releases_version = api_release_get(project, version)
release = releases_version.release
# TODO: Handle the not found case better
if release.phase != "release_candidate_draft":
@@ -452,8 +456,8 @@ def app_config_path() -> None:
@APP_DEV.command(name="delete", help="Delete a release.")
def app_dev_delete(project: str, version: str, /) -> None:
- releases_delete_args = models.api.ReleasesDeleteArgs(project=project,
version=version)
- release_delete = api_releases_delete(releases_delete_args)
+ releases_delete_args = models.api.ReleaseDeleteArgs(project=project,
version=version)
+ release_delete = api_release_delete(releases_delete_args)
print(release_delete.deleted)
@@ -608,8 +612,8 @@ def app_dev_user() -> None:
@APP_DRAFT.command(name="delete", help="Delete a draft release.")
def app_draft_delete(project: str, version: str, /) -> None:
- draft_delete_args = models.api.DraftDeleteArgs(project=project,
version=version)
- draft_delete = api_draft_delete(draft_delete_args)
+ draft_delete_args = models.api.ReleaseDraftDeleteArgs(project=project,
version=version)
+ draft_delete = api_release_draft_delete(draft_delete_args)
print(draft_delete.success)
@@ -690,21 +694,21 @@ def app_keys_add(path: str, committees: str = "", /) ->
None:
asf_uid = config_get(config, ["asf", "uid"])
if asf_uid is None:
show_error_and_exit("Please configure asf.uid before adding a key.")
- keys_add_args = models.api.KeysAddArgs(asfuid=asf_uid, key=key,
committees=selected_committee_names)
- keys_add = api_keys_add(keys_add_args)
+ keys_add_args = models.api.KeyAddArgs(asfuid=asf_uid, key=key,
committees=selected_committee_names)
+ keys_add = api_key_add(keys_add_args)
print(keys_add.fingerprint)
@APP_KEYS.command(name="delete", help="Delete an OpenPGP key.")
def app_keys_delete(fingerprint: str, /) -> None:
- keys_delete_args = models.api.KeysDeleteArgs(fingerprint=fingerprint)
- keys_delete = api_keys_delete(keys_delete_args)
+ keys_delete_args = models.api.KeyDeleteArgs(fingerprint=fingerprint)
+ keys_delete = api_key_delete(keys_delete_args)
print(keys_delete.success)
@APP_KEYS.command(name="get", help="Get an OpenPGP key.")
def app_keys_get(fingerprint: str, /) -> None:
- keys_get = api_keys_get(fingerprint)
+ keys_get = api_key_get(fingerprint)
print(keys_get.key.model_dump_json(indent=None))
@@ -736,34 +740,34 @@ def app_keys_user(asf_uid: str | None = None) -> None:
@APP.command(name="list", help="List all files within a release.")
def app_list(project: str, version: str, revision: str | None = None, /) ->
None:
- releases_paths = api_releases_paths(project, version, revision)
+ releases_paths = api_release_paths(project, version, revision)
for rel_path in releases_paths.rel_paths:
print(rel_path)
@APP_RELEASE.command(name="info", help="Show information about a release.")
def app_release_info(project: str, version: str, /) -> None:
- releases_version = api_releases_version(project, version)
+ releases_version = api_release_get(project, version)
print(releases_version.release.model_dump_json(indent=None))
@APP_RELEASE.command(name="list", help="List releases for a project.")
def app_release_list(project: str, /) -> None:
# TODO: Support showing all of a user's releases if no project is provided
- releases_project = api_releases_project(project)
- releases_display(releases_project.data)
+ releases_project = api_project_releases(project)
+ releases_display(releases_project.releases)
@APP_RELEASE.command(name="start", help="Start a release.")
def app_release_start(project: str, version: str, /) -> None:
- releases_create_args = models.api.ReleasesCreateArgs(project=project,
version=version)
- releases_create = api_releases_create(releases_create_args)
+ releases_create_args = models.api.ReleaseCreateArgs(project=project,
version=version)
+ releases_create = api_release_create(releases_create_args)
print(releases_create.release.model_dump_json(indent=None))
@APP.command(name="revisions", help="List all revisions for a release.")
def app_revisions(project: str, version: str, /) -> None:
- revisions = api_revisions(project, version)
+ revisions = api_release_revisions(project, version)
for revision in revisions.revisions:
print(revision)
@@ -825,15 +829,15 @@ def app_show(path: str, /) -> None:
@APP_SSH.command(name="add", help="Add an SSH key.")
def app_ssh_add(text: str, /) -> None:
- ssh_add_args = models.api.SshAddArgs(text=text)
- ssh_add = api_ssh_add(ssh_add_args)
+ ssh_add_args = models.api.SshKeyAddArgs(text=text)
+ ssh_add = api_ssh_key_add(ssh_add_args)
print(ssh_add.fingerprint)
@APP_SSH.command(name="delete", help="Delete an SSH key.")
def app_ssh_delete(fingerprint: str, /) -> None:
- ssh_delete_args = models.api.SshDeleteArgs(fingerprint=fingerprint)
- ssh_delete = api_ssh_delete(ssh_delete_args)
+ ssh_delete_args = models.api.SshKeyDeleteArgs(fingerprint=fingerprint)
+ ssh_delete = api_ssh_key_delete(ssh_delete_args)
print(ssh_delete.success)
@@ -844,7 +848,7 @@ def app_ssh_list(asf_uid: str | None = None) -> None:
asf_uid = config_get(config, ["asf", "uid"])
if asf_uid is None:
show_error_and_exit("No ASF UID provided and asf.uid not configured.")
- ssh_list = api_ssh_list(asf_uid)
+ ssh_list = api_ssh_keys_list(asf_uid)
print(ssh_list.data)
@@ -853,14 +857,14 @@ def app_upload(project: str, version: str, path: str,
filepath: str, /) -> None:
with open(filepath, "rb") as fh:
content = fh.read()
- upload_args = models.api.UploadArgs(
+ upload_args = models.api.ReleaseUploadArgs(
project=project,
version=version,
relpath=path,
content=base64.b64encode(content).decode("utf-8"),
)
- upload = api_upload(upload_args)
+ upload = api_release_upload(upload_args)
print(upload.revision.model_dump_json(indent=None))
@@ -904,7 +908,7 @@ def app_verify(url: str, /, verbose: bool = False) -> None:
signature_file_name = signature_url.split("/")[-1]
print_if_verbose("To verify the signature, we need the OpenPGP signing key
from the ATR.\n")
- verify_provenance_args = models.api.VerifyProvenanceArgs(
+ verify_provenance_args = models.api.SignatureProvenanceArgs(
artifact_file_name=artifact_file_name,
artifact_sha3_256=artifact_hash,
signature_file_name=signature_file_name,
@@ -916,7 +920,7 @@ def app_verify(url: str, /, verbose: bool = False) -> None:
dumped_json["signature_asc_text"] = dumped_json["signature_asc_text"][:32]
+ "..."
print_if_verbose(json.dumps(dumped_json, indent=2))
print_if_verbose("")
- verify_provenance = api_verify_provenance(verify_provenance_args)
+ verify_provenance = api_signature_provenance(verify_provenance_args)
print_if_verbose("The ATR found a matching OpenPGP key with the following
fingerprint:\n")
print_if_verbose(verify_provenance.fingerprint.upper() + "\n")
print_if_verbose("This key is associated with these committees with a
project containing the artifact:\n")
@@ -1396,7 +1400,7 @@ def timestamp_format(ts: int | str | None) -> str | None:
def verify_summary(
- verify_provenance: models.api.VerifyProvenanceResults,
+ verify_provenance: models.api.SignatureProvenanceResults,
signature_data: bytes,
artifact_data: bytes,
verbose: bool = False,
diff --git a/src/atrclient/models/api.py b/src/atrclient/models/api.py
index f6d0906..8a0b0a9 100644
--- a/src/atrclient/models/api.py
+++ b/src/atrclient/models/api.py
@@ -51,34 +51,24 @@ class ChecksOngoingResults(schema.Strict):
ongoing: int = schema.Field(..., **example(10))
-class CommitteesGetResults(schema.Strict):
- endpoint: Literal["/committees/get"] = schema.Field(alias="endpoint")
+class CommitteeGetResults(schema.Strict):
+ endpoint: Literal["/committee/get"] = schema.Field(alias="endpoint")
committee: sql.Committee
-class CommitteesKeysResults(schema.Strict):
- endpoint: Literal["/committees/keys"] = schema.Field(alias="endpoint")
+class CommitteeKeysResults(schema.Strict):
+ endpoint: Literal["/committee/keys"] = schema.Field(alias="endpoint")
keys: Sequence[sql.PublicSigningKey]
-class CommitteesListResults(schema.Strict):
- endpoint: Literal["/committees/list"] = schema.Field(alias="endpoint")
- committees: Sequence[sql.Committee]
-
-
-class CommitteesProjectsResults(schema.Strict):
- endpoint: Literal["/committees/projects"] = schema.Field(alias="endpoint")
+class CommitteeProjectsResults(schema.Strict):
+ endpoint: Literal["/committee/projects"] = schema.Field(alias="endpoint")
projects: Sequence[sql.Project]
-class DraftDeleteArgs(schema.Strict):
- project: str = schema.Field(..., **example("example"))
- version: str = schema.Field(..., **example("0.0.1"))
-
-
-class DraftDeleteResults(schema.Strict):
- endpoint: Literal["/draft/delete"] = schema.Field(alias="endpoint")
- success: str = schema.Field(..., **example("Draft 'example-0.0.1'
deleted"))
+class CommitteesListResults(schema.Strict):
+ endpoint: Literal["/committees/list"] = schema.Field(alias="endpoint")
+ committees: Sequence[sql.Committee]
class JwtCreateArgs(schema.Strict):
@@ -92,7 +82,7 @@ class JwtCreateResults(schema.Strict):
jwt: str = schema.Field(...,
**example("eyJhbGciOiJIUzI1[...]mMjLiuyu5CSpyHI="))
-class KeysAddArgs(schema.Strict):
+class KeyAddArgs(schema.Strict):
asfuid: str = schema.Field(..., **example("user"))
key: str = schema.Field(
..., **example("-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n...\n-----END
PGP PUBLIC KEY BLOCK-----\n")
@@ -100,23 +90,23 @@ class KeysAddArgs(schema.Strict):
committees: list[str] = schema.Field(..., **example(["example"]))
-class KeysAddResults(schema.Strict):
- endpoint: Literal["/keys/add"] = schema.Field(alias="endpoint")
+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"))
-class KeysDeleteArgs(schema.Strict):
+class KeyDeleteArgs(schema.Strict):
fingerprint: str = schema.Field(...,
**example("0123456789abcdef0123456789abcdef01234567"))
-class KeysDeleteResults(schema.Strict):
- endpoint: Literal["/keys/delete"] = schema.Field(alias="endpoint")
+class KeyDeleteResults(schema.Strict):
+ endpoint: Literal["/key/delete"] = schema.Field(alias="endpoint")
success: str = schema.Field(..., **example("Key deleted"))
-class KeysGetResults(schema.Strict):
- endpoint: Literal["/keys/get"] = schema.Field(alias="endpoint")
+class KeyGetResults(schema.Strict):
+ endpoint: Literal["/key/get"] = schema.Field(alias="endpoint")
key: sql.PublicSigningKey
@@ -147,13 +137,6 @@ KeysUploadOutcome = Annotated[
KeysUploadOutcomeAdapter = pydantic.TypeAdapter(KeysUploadOutcome)
-# def validate_keys_upload_outcome(value: Any) -> KeysUploadOutcome:
-# obj = KeysUploadOutcomeAdapter.validate_python(value)
-# if not isinstance(obj, KeysUploadOutcome):
-# raise ResultsTypeError(f"Invalid API response: {value}")
-# return obj
-
-
class KeysUploadResults(schema.Strict):
endpoint: Literal["/keys/upload"] = schema.Field(alias="endpoint")
results: Sequence[KeysUploadResult | KeysUploadException]
@@ -167,21 +150,21 @@ class KeysUserResults(schema.Strict):
keys: Sequence[sql.PublicSigningKey]
-class ProjectsGetResults(schema.Strict):
- endpoint: Literal["/projects/get"] = schema.Field(alias="endpoint")
+class ProjectGetResults(schema.Strict):
+ endpoint: Literal["/project/get"] = schema.Field(alias="endpoint")
project: sql.Project
+class ProjectReleasesResults(schema.Strict):
+ endpoint: Literal["/project/releases"] = schema.Field(alias="endpoint")
+ releases: Sequence[sql.Release]
+
+
class ProjectsListResults(schema.Strict):
endpoint: Literal["/projects/list"] = schema.Field(alias="endpoint")
projects: Sequence[sql.Project]
-class ProjectsReleasesResults(schema.Strict):
- endpoint: Literal["/projects/releases"] = schema.Field(alias="endpoint")
- releases: Sequence[sql.Release]
-
-
class ReleaseAnnounceArgs(schema.Strict):
project: str = schema.Field(..., **example("example"))
version: str = schema.Field(..., **example("1.0.0"))
@@ -200,60 +183,38 @@ class ReleaseAnnounceResults(schema.Strict):
success: bool = schema.Field(..., **example(True))
[email protected]
-class ReleasesQuery:
- offset: int = 0
- limit: int = 20
- phase: str | None = None
+class ReleaseDraftDeleteArgs(schema.Strict):
+ project: str = schema.Field(..., **example("example"))
+ version: str = schema.Field(..., **example("0.0.1"))
-class ReleasesResults(schema.Strict):
- endpoint: Literal["/releases"] = schema.Field(alias="endpoint")
- data: Sequence[sql.Release]
- count: int
+class ReleaseDraftDeleteResults(schema.Strict):
+ endpoint: Literal["/release/draft/delete"] = schema.Field(alias="endpoint")
+ success: str = schema.Field(..., **example("Draft 'example-0.0.1'
deleted"))
-class ReleasesCreateArgs(schema.Strict):
+class ReleaseCreateArgs(schema.Strict):
project: str
version: str
-class ReleasesCreateResults(schema.Strict):
- endpoint: Literal["/releases/create"] = schema.Field(alias="endpoint")
+class ReleaseCreateResults(schema.Strict):
+ endpoint: Literal["/release/create"] = schema.Field(alias="endpoint")
release: sql.Release
-class ReleasesDeleteArgs(schema.Strict):
+class ReleaseDeleteArgs(schema.Strict):
project: str
version: str
-class ReleasesDeleteResults(schema.Strict):
- endpoint: Literal["/releases/delete"] = schema.Field(alias="endpoint")
+class ReleaseDeleteResults(schema.Strict):
+ endpoint: Literal["/release/delete"] = schema.Field(alias="endpoint")
deleted: str
-class ReleasesPathsResults(schema.Strict):
- endpoint: Literal["/releases/paths"] = schema.Field(alias="endpoint")
- rel_paths: Sequence[str]
-
-
[email protected]
-class ReleasesProjectQuery:
- limit: int = 20
- offset: int = 0
- # project: str
- # version: str
-
-
-class ReleasesProjectResults(schema.Strict):
- endpoint: Literal["/releases/project"] = schema.Field(alias="endpoint")
- data: Sequence[sql.Release]
- count: int
-
-
-class ReleasesVersionResults(schema.Strict):
- endpoint: Literal["/releases/version"] = schema.Field(alias="endpoint")
+class ReleaseGetResults(schema.Strict):
+ endpoint: Literal["/release/get"] = schema.Field(alias="endpoint")
release: sql.Release
@pydantic.field_validator("release", mode="before")
@@ -270,42 +231,88 @@ class ReleasesVersionResults(schema.Strict):
return v
-class ReleasesRevisionsResults(schema.Strict):
- endpoint: Literal["/releases/revisions"] = schema.Field(alias="endpoint")
- revisions: Sequence[sql.Revision]
+class ReleasePathsResults(schema.Strict):
+ endpoint: Literal["/release/paths"] = schema.Field(alias="endpoint")
+ rel_paths: Sequence[str]
-class RevisionsResults(schema.Strict):
- endpoint: Literal["/revisions"] = schema.Field(alias="endpoint")
+class ReleaseRevisionsResults(schema.Strict):
+ endpoint: Literal["/release/revisions"] = schema.Field(alias="endpoint")
revisions: Sequence[sql.Revision]
-class SshAddArgs(schema.Strict):
+class ReleaseUploadArgs(schema.Strict):
+ project: str
+ version: str
+ relpath: str
+ content: str
+
+
+class ReleaseUploadResults(schema.Strict):
+ endpoint: Literal["/release/upload"] = schema.Field(alias="endpoint")
+ revision: sql.Revision
+
+
[email protected]
+class ReleasesListQuery:
+ offset: int = 0
+ limit: int = 20
+ phase: str | None = None
+
+
+class ReleasesListResults(schema.Strict):
+ endpoint: Literal["/releases/list"] = schema.Field(alias="endpoint")
+ data: Sequence[sql.Release]
+ count: int
+
+
+class SignatureProvenanceArgs(schema.Strict):
+ artifact_file_name: str
+ artifact_sha3_256: str
+ signature_file_name: str
+ signature_asc_text: str
+ signature_sha3_256: str
+
+
+class SignatureProvenanceKey(schema.Strict):
+ committee: str
+ keys_file_url: str
+ keys_file_sha3_256: str
+
+
+class SignatureProvenanceResults(schema.Strict):
+ endpoint: Literal["/signature/provenance"] = schema.Field(alias="endpoint")
+ fingerprint: str
+ key_asc_text: str
+ committees_with_artifact: list[SignatureProvenanceKey]
+
+
+class SshKeyAddArgs(schema.Strict):
text: str
-class SshAddResults(schema.Strict):
- endpoint: Literal["/ssh/add"] = schema.Field(alias="endpoint")
+class SshKeyAddResults(schema.Strict):
+ endpoint: Literal["/ssh-key/add"] = schema.Field(alias="endpoint")
fingerprint: str
-class SshDeleteArgs(schema.Strict):
+class SshKeyDeleteArgs(schema.Strict):
fingerprint: str
-class SshDeleteResults(schema.Strict):
- endpoint: Literal["/ssh/delete"] = schema.Field(alias="endpoint")
+class SshKeyDeleteResults(schema.Strict):
+ endpoint: Literal["/ssh-key/delete"] = schema.Field(alias="endpoint")
success: str
@dataclasses.dataclass
-class SshListQuery:
+class SshKeysListQuery:
offset: int = 0
limit: int = 20
-class SshListResults(schema.Strict):
- endpoint: Literal["/ssh/list"] = schema.Field(alias="endpoint")
+class SshKeysListResults(schema.Strict):
+ endpoint: Literal["/ssh-keys/list"] = schema.Field(alias="endpoint")
data: Sequence[sql.SSHKey]
count: int
@@ -364,77 +371,42 @@ class VoteTabulateResults(schema.Strict):
details: tabulate.VoteDetails
-class UploadArgs(schema.Strict):
- project: str
- version: str
- relpath: str
- content: str
-
-
-class UploadResults(schema.Strict):
- endpoint: Literal["/upload"] = schema.Field(alias="endpoint")
- revision: sql.Revision
-
-
-class VerifyProvenanceArgs(schema.Strict):
- artifact_file_name: str
- artifact_sha3_256: str
- signature_file_name: str
- signature_asc_text: str
- signature_sha3_256: str
-
-
-class VerifyProvenanceKey(schema.Strict):
- committee: str
- keys_file_url: str
- keys_file_sha3_256: str
-
-
-class VerifyProvenanceResults(schema.Strict):
- endpoint: Literal["/verify/provenance"] = schema.Field(alias="endpoint")
- fingerprint: str
- key_asc_text: str
- committees_with_artifact: list[VerifyProvenanceKey]
-
-
# This is for *Results classes only
# We do NOT put *Args classes here
Results = Annotated[
ChecksListResults
| ChecksOngoingResults
- | CommitteesGetResults
- | CommitteesKeysResults
+ | CommitteeGetResults
+ | CommitteeKeysResults
+ | CommitteeProjectsResults
| CommitteesListResults
- | CommitteesProjectsResults
- | DraftDeleteResults
| JwtCreateResults
- | KeysAddResults
- | KeysDeleteResults
- | KeysGetResults
+ | KeyAddResults
+ | KeyDeleteResults
+ | KeyGetResults
| KeysUploadResults
| KeysUserResults
- | ProjectsGetResults
+ | ProjectGetResults
+ | ProjectReleasesResults
| ProjectsListResults
- | ProjectsReleasesResults
| ReleaseAnnounceResults
- | ReleasesResults
- | ReleasesCreateResults
- | ReleasesDeleteResults
- | ReleasesPathsResults
- | ReleasesProjectResults
- | ReleasesVersionResults
- | ReleasesRevisionsResults
- | RevisionsResults
- | SshAddResults
- | SshDeleteResults
- | SshListResults
+ | ReleaseCreateResults
+ | ReleaseDeleteResults
+ | ReleaseDraftDeleteResults
+ | ReleaseGetResults
+ | ReleasePathsResults
+ | ReleaseRevisionsResults
+ | ReleaseUploadResults
+ | ReleasesListResults
+ | SignatureProvenanceResults
+ | SshKeyAddResults
+ | SshKeyDeleteResults
+ | SshKeysListResults
| TasksResults
| UsersListResults
- | VerifyProvenanceResults
| VoteResolveResults
| VoteStartResults
- | VoteTabulateResults
- | UploadResults,
+ | VoteTabulateResults,
schema.Field(discriminator="endpoint"),
]
@@ -453,36 +425,34 @@ def validator[T](t: type[T]) -> Callable[[Any], T]:
validate_checks_list = validator(ChecksListResults)
validate_checks_ongoing = validator(ChecksOngoingResults)
-validate_committees_get = validator(CommitteesGetResults)
-validate_committees_keys = validator(CommitteesKeysResults)
+validate_committee_get = validator(CommitteeGetResults)
+validate_committee_keys = validator(CommitteeKeysResults)
+validate_committee_projects = validator(CommitteeProjectsResults)
validate_committees_list = validator(CommitteesListResults)
-validate_committees_projects = validator(CommitteesProjectsResults)
-validate_draft_delete = validator(DraftDeleteResults)
validate_jwt_create = validator(JwtCreateResults)
-validate_keys_add = validator(KeysAddResults)
-validate_keys_delete = validator(KeysDeleteResults)
-validate_keys_get = validator(KeysGetResults)
+validate_key_add = validator(KeyAddResults)
+validate_key_delete = validator(KeyDeleteResults)
+validate_key_get = validator(KeyGetResults)
validate_keys_upload = validator(KeysUploadResults)
validate_keys_user = validator(KeysUserResults)
-validate_projects_get = validator(ProjectsGetResults)
+validate_project_get = validator(ProjectGetResults)
+validate_project_releases = validator(ProjectReleasesResults)
validate_projects_list = validator(ProjectsListResults)
-validate_projects_releases = validator(ProjectsReleasesResults)
validate_release_announce = validator(ReleaseAnnounceResults)
-validate_releases = validator(ReleasesResults)
-validate_releases_create = validator(ReleasesCreateResults)
-validate_releases_delete = validator(ReleasesDeleteResults)
-validate_releases_paths = validator(ReleasesPathsResults)
-validate_releases_project = validator(ReleasesProjectResults)
-validate_releases_version = validator(ReleasesVersionResults)
-validate_releases_revisions = validator(ReleasesRevisionsResults)
-validate_revisions = validator(RevisionsResults)
-validate_ssh_add = validator(SshAddResults)
-validate_ssh_delete = validator(SshDeleteResults)
-validate_ssh_list = validator(SshListResults)
+validate_release_create = validator(ReleaseCreateResults)
+validate_release_delete = validator(ReleaseDeleteResults)
+validate_release_draft_delete = validator(ReleaseDraftDeleteResults)
+validate_release_get = validator(ReleaseGetResults)
+validate_release_paths = validator(ReleasePathsResults)
+validate_release_revisions = validator(ReleaseRevisionsResults)
+validate_release_upload = validator(ReleaseUploadResults)
+validate_releases_list = validator(ReleasesListResults)
+validate_signature_provenance = validator(SignatureProvenanceResults)
+validate_ssh_key_add = validator(SshKeyAddResults)
+validate_ssh_key_delete = validator(SshKeyDeleteResults)
+validate_ssh_keys_list = validator(SshKeysListResults)
validate_tasks = validator(TasksResults)
validate_users_list = validator(UsersListResults)
-validate_verify_provenance = validator(VerifyProvenanceResults)
validate_vote_resolve = validator(VoteResolveResults)
validate_vote_start = validator(VoteStartResults)
validate_vote_tabulate = validator(VoteTabulateResults)
-validate_upload = validator(UploadResults)
diff --git a/tests/cli_ssh.t b/tests/cli_ssh.t
index aec05a1..b68aa9a 100644
--- a/tests/cli_ssh.t
+++ b/tests/cli_ssh.t
@@ -19,7 +19,7 @@ $ atr ssh list
! atr ssh add invalid-key
<.stderr.>
atr: error: Error message from the API:
-500 https://localhost.apache.org:8080/api/ssh/add
+500 https://localhost.apache.org:8080/api/ssh-key/add
{
"error": "Invalid SSH key format"
}
diff --git a/tests/test_all.py b/tests/test_all.py
index 9af300d..a42bca4 100755
--- a/tests/test_all.py
+++ b/tests/test_all.py
@@ -52,14 +52,14 @@ def test_app_checks_status_non_draft_phase(
client.app_set("atr.host", "example.invalid")
client.app_set("tokens.jwt", "dummy_jwt_token")
- releases_url =
"https://example.invalid/api/releases/version/test-project/2.3.0"
+ releases_url = "https://example.invalid/api/release/get/test-project/2.3.0"
with aioresponses.aioresponses() as mock:
mock.get(
releases_url,
status=200,
payload={
- "endpoint": "/releases/version",
+ "endpoint": "/release/get",
"release": {
"name": "test-project-2.3.0",
"project_name": "test-project",
@@ -86,11 +86,11 @@ def test_app_checks_status_verbose(capsys:
pytest.CaptureFixture[str], fixture_c
client.app_set("atr.host", "example.invalid")
client.app_set("tokens.jwt", "dummy_jwt_token")
- release_url =
"https://example.invalid/api/releases/version/test-project/2.3.1"
+ release_url = "https://example.invalid/api/release/get/test-project/2.3.1"
checks_url =
"https://example.invalid/api/checks/list/test-project/2.3.1/00003"
release_payload = {
- "endpoint": "/releases/version",
+ "endpoint": "/release/get",
"release": {
"name": "test-project-2.3.1",
"project_name": "test-project",
@@ -161,7 +161,7 @@ def test_app_checks_status_verbose(capsys:
pytest.CaptureFixture[str], fixture_c
def test_app_release_list_not_found(capsys: pytest.CaptureFixture[str],
fixture_config_env: pathlib.Path) -> None:
client.app_set("atr.host", "example.invalid")
- releases_url =
"https://example.invalid/api/releases/project/nonexistent-project"
+ releases_url =
"https://example.invalid/api/project/releases/nonexistent-project"
with aioresponses.aioresponses() as mock:
mock.get(releases_url, status=404, body="Not Found")
@@ -173,11 +173,11 @@ def test_app_release_list_not_found(capsys:
pytest.CaptureFixture[str], fixture_
def test_app_release_list_success(capsys: pytest.CaptureFixture[str],
fixture_config_env: pathlib.Path) -> None:
client.app_set("atr.host", "example.invalid")
- releases_url = "https://example.invalid/api/releases/project/test-project"
+ releases_url = "https://example.invalid/api/project/releases/test-project"
payload = {
- "endpoint": "/releases/project",
- "data": [
+ "endpoint": "/project/releases",
+ "releases": [
{
"name": "test-project-2.3.1",
"project_name": "test-project",
@@ -203,7 +203,6 @@ def test_app_release_list_success(capsys:
pytest.CaptureFixture[str], fixture_co
"vote_manual": False,
},
],
- "count": 2,
}
with aioresponses.aioresponses() as mock:
diff --git a/uv.lock b/uv.lock
index 47e769f..8ecf92a 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,7 @@ version = 1
requires-python = ">=3.13"
[options]
-exclude-newer = "2025-07-29T13:18:00Z"
+exclude-newer = "2025-07-29T13:58:00Z"
[[package]]
name = "aiohappyeyeballs"
@@ -83,7 +83,7 @@ wheels = [
[[package]]
name = "apache-trusted-releases"
-version = "0.20250729.1318"
+version = "0.20250729.1358"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@@ -722,6 +722,17 @@ dependencies = [
{ name = "typing-extensions" },
]
sdist = { url =
"https://files.pythonhosted.org/packages/5a/03/a0af991e3a43174d6b83fca4fb399745abceddd1171bdabae48ce877ff47/sqlalchemy-2.0.42.tar.gz",
hash =
"sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f", size
= 9749972 }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/e9/7e/25d8c28b86730c9fb0e09156f601d7a96d1c634043bf8ba36513eb78887b/sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl",
hash =
"sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c", size
= 2127905 },
+ { url =
"https://files.pythonhosted.org/packages/e5/a1/9d8c93434d1d983880d976400fcb7895a79576bd94dca61c3b7b90b1ed0d/sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl",
hash =
"sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01", size
= 2115726 },
+ { url =
"https://files.pythonhosted.org/packages/a2/cc/d33646fcc24c87cc4e30a03556b611a4e7bcfa69a4c935bffb923e3c89f4/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
hash =
"sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9", size
= 3246007 },
+ { url =
"https://files.pythonhosted.org/packages/67/08/4e6c533d4c7f5e7c4cbb6fe8a2c4e813202a40f05700d4009a44ec6e236d/sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
hash =
"sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04", size
= 3250919 },
+ { url =
"https://files.pythonhosted.org/packages/5c/82/f680e9a636d217aece1b9a8030d18ad2b59b5e216e0c94e03ad86b344af3/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl",
hash =
"sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894", size
= 3180546 },
+ { url =
"https://files.pythonhosted.org/packages/7d/a2/8c8f6325f153894afa3775584c429cc936353fb1db26eddb60a549d0ff4b/sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl",
hash =
"sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350", size
= 3216683 },
+ { url =
"https://files.pythonhosted.org/packages/39/44/3a451d7fa4482a8ffdf364e803ddc2cfcafc1c4635fb366f169ecc2c3b11/sqlalchemy-2.0.42-cp313-cp313-win32.whl",
hash =
"sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f", size
= 2093990 },
+ { url =
"https://files.pythonhosted.org/packages/4b/9e/9bce34f67aea0251c8ac104f7bdb2229d58fb2e86a4ad8807999c4bee34b/sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl",
hash =
"sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577", size
= 2120473 },
+ { url =
"https://files.pythonhosted.org/packages/ee/55/ba2546ab09a6adebc521bf3974440dc1d8c06ed342cceb30ed62a8858835/sqlalchemy-2.0.42-py3-none-any.whl",
hash =
"sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835", size
= 1922072 },
+]
[[package]]
name = "sqlmodel"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]