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 e3e279d Fix a problem with the api command
e3e279d is described below
commit e3e279d926d86c1591fa4c0137aeace1b2c42658
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Jul 28 20:20:35 2025 +0100
Fix a problem with the api command
---
pyproject.toml | 4 +--
src/atrclient/client.py | 6 ++--
src/atrclient/models/api.py | 18 +++++------
src/atrclient/models/sql.py | 73 +++++++++++++++++++++++++++------------------
uv.lock | 4 +--
5 files changed, 60 insertions(+), 45 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 844a6c1..fbf9c23 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ build-backend = "hatchling.build"
[project]
name = "apache-trusted-releases"
-version = "0.20250728.1817"
+version = "0.20250728.1919"
description = "ATR CLI and Python API"
readme = "README.md"
requires-python = ">=3.13"
@@ -74,4 +74,4 @@ select = [
]
[tool.uv]
-exclude-newer = "2025-07-28T18:17:00Z"
+exclude-newer = "2025-07-28T19:19:00Z"
diff --git a/src/atrclient/client.py b/src/atrclient/client.py
index 3a9c860..90f505f 100755
--- a/src/atrclient/client.py
+++ b/src/atrclient/client.py
@@ -321,7 +321,7 @@ def app_announce(
@APP.command(name="api", help="Call the API directly.")
-async def app_api(path: str, /, **kwargs: str) -> None:
+def app_api(path: str, /, **kwargs: str) -> None:
jwt_value = config_jwt_usable()
host, verify_ssl = config_host_get()
url = f"https://{host}/api{path}"
@@ -329,14 +329,14 @@ async def app_api(path: str, /, **kwargs: str) -> None:
# print(url)
# print(kwargs)
if "_version" in kwargs:
- # There's a bug in Cyclopts where it does not pass --version to
**kwargs
+ # TODO: There's a bug in Cyclopts where it does not pass --version to
**kwargs
kwargs["version"] = kwargs["_version"]
del kwargs["_version"]
if not is_json(kwargs):
show_error_and_exit(f"Unexpected API response: {kwargs}")
if not is_json_dict(kwargs):
show_error_and_exit(f"Unexpected API response: {kwargs}")
- json_data = await web_post_json(url, kwargs, jwt_value, verify_ssl)
+ json_data = asyncio.run(web_post_json(url, kwargs, jwt_value, verify_ssl))
print(json.dumps(json_data, indent=None))
diff --git a/src/atrclient/models/api.py b/src/atrclient/models/api.py
index 3a565d8..820d466 100644
--- a/src/atrclient/models/api.py
+++ b/src/atrclient/models/api.py
@@ -69,8 +69,8 @@ class ChecksOngoingResults(schema.Strict):
ongoing: int
-class CommitteesResults(schema.Strict):
- endpoint: Literal["/committees"] = schema.Field(alias="endpoint")
+class CommitteesGetResults(schema.Strict):
+ endpoint: Literal["/committees/get"] = schema.Field(alias="endpoint")
committee: sql.Committee
@@ -139,9 +139,9 @@ class KeysAddResults(schema.Strict):
fingerprint: str
-class KeysCommitteeResults(schema.Strict):
- endpoint: Literal["/keys/committee"] = schema.Field(alias="endpoint")
- keys: Sequence[sql.PublicSigningKey]
+# class KeysCommitteeResults(schema.Strict):
+# endpoint: Literal["/keys/committee"] = schema.Field(alias="endpoint")
+# keys: Sequence[sql.PublicSigningKey]
class KeysDeleteArgs(schema.Strict):
@@ -416,7 +416,7 @@ Results = Annotated[
AnnounceResults
| ChecksListResults
| ChecksOngoingResults
- | CommitteesResults
+ | CommitteesGetResults
| CommitteesKeysResults
| CommitteesListResults
| CommitteesProjectsResults
@@ -426,7 +426,7 @@ Results = Annotated[
| KeysAddResults
| KeysDeleteResults
| KeysGetResults
- | KeysCommitteeResults
+ # | KeysCommitteeResults
| KeysUploadResults
| KeysUserResults
| ListResults
@@ -469,7 +469,7 @@ def validator[T](t: type[T]) -> Callable[[Any], T]:
validate_announce = validator(AnnounceResults)
validate_checks_list = validator(ChecksListResults)
validate_checks_ongoing = validator(ChecksOngoingResults)
-validate_committees = validator(CommitteesResults)
+validate_committees_get = validator(CommitteesGetResults)
validate_committees_keys = validator(CommitteesKeysResults)
validate_committees_list = validator(CommitteesListResults)
validate_committees_projects = validator(CommitteesProjectsResults)
@@ -477,7 +477,7 @@ validate_draft_delete = validator(DraftDeleteResults)
validate_jwt = validator(JwtResults)
validate_keys = validator(KeysResults)
validate_keys_add = validator(KeysAddResults)
-validate_keys_committee = validator(KeysCommitteeResults)
+# validate_keys_committee = validator(KeysCommitteeResults)
validate_keys_delete = validator(KeysDeleteResults)
validate_keys_get = validator(KeysGetResults)
validate_keys_upload = validator(KeysUploadResults)
diff --git a/src/atrclient/models/sql.py b/src/atrclient/models/sql.py
index b224a5e..9af0a0a 100644
--- a/src/atrclient/models/sql.py
+++ b/src/atrclient/models/sql.py
@@ -192,6 +192,11 @@ class ResultsJSON(sqlalchemy.types.TypeDecorator):
# SQL models
+
+def example(value: Any) -> dict[Literal["schema_extra"], dict[str, Any]]:
+ return {"schema_extra": {"json_schema_extra": {"examples": [value]}}}
+
+
# SQL models with no dependencies
@@ -309,8 +314,8 @@ class TextValue(sqlmodel.SQLModel, table=True):
class Committee(sqlmodel.SQLModel, table=True):
# TODO: Consider using key or label for primary string keys
# Then we can use simply "name" for full_name, and make it str rather than
str | None
- name: str = sqlmodel.Field(unique=True, primary_key=True)
- full_name: str | None = sqlmodel.Field(default=None)
+ name: str = sqlmodel.Field(unique=True, primary_key=True,
**example("example"))
+ full_name: str | None = sqlmodel.Field(default=None, **example("Example"))
# True only if this is an incubator podling with a PPMC
is_podling: bool = sqlmodel.Field(default=False)
@@ -331,9 +336,15 @@ class Committee(sqlmodel.SQLModel, table=True):
# M-1: Project -> Committee
projects: list["Project"] =
sqlmodel.Relationship(back_populates="committee")
- committee_members: list[str] = sqlmodel.Field(default_factory=list,
sa_column=sqlalchemy.Column(sqlalchemy.JSON))
- committers: list[str] = sqlmodel.Field(default_factory=list,
sa_column=sqlalchemy.Column(sqlalchemy.JSON))
- release_managers: list[str] = sqlmodel.Field(default_factory=list,
sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+ committee_members: list[str] = sqlmodel.Field(
+ default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON),
**example(["sbp", "tn", "wave"])
+ )
+ committers: list[str] = sqlmodel.Field(
+ default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON),
**example(["sbp", "tn", "wave"])
+ )
+ release_managers: list[str] = sqlmodel.Field(
+ default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON),
**example(["wave"])
+ )
# M-M: Committee -> [PublicSigningKey]
# M-M: PublicSigningKey -> [Committee]
@@ -356,12 +367,12 @@ def see_also(arg: Any) -> None:
class Project(sqlmodel.SQLModel, table=True):
# TODO: Consider using key or label for primary string keys
# Then we can use simply "name" for full_name, and make it str rather than
str | None
- name: str = sqlmodel.Field(unique=True, primary_key=True)
+ name: str = sqlmodel.Field(unique=True, primary_key=True,
**example("example"))
# TODO: Ideally full_name would be unique for str only, but that's complex
# We always include "Apache" in the full_name
- full_name: str | None = sqlmodel.Field(default=None)
+ full_name: str | None = sqlmodel.Field(default=None, **example("Apache
Example"))
- status: ProjectStatus = sqlmodel.Field(default=ProjectStatus.ACTIVE)
+ status: ProjectStatus = sqlmodel.Field(default=ProjectStatus.ACTIVE,
**example(ProjectStatus.ACTIVE))
# M-1: Project -> Project
# 1-M: (Project.child_project is missing, would be Project -> [Project])
@@ -369,13 +380,13 @@ class Project(sqlmodel.SQLModel, table=True):
# NOTE: Neither "Project" | None nor "Project | None" works
super_project: Optional["Project"] = sqlmodel.Relationship()
- description: str | None = sqlmodel.Field(default=None)
- category: str | None = sqlmodel.Field(default=None)
- programming_languages: str | None = sqlmodel.Field(default=None)
+ description: str | None = sqlmodel.Field(default=None, **example("Example
is a simple example project"))
+ category: str | None = sqlmodel.Field(default=None,
**example("data,storage"))
+ programming_languages: str | None = sqlmodel.Field(default=None,
**example("c,python"))
# M-1: Project -> Committee
# 1-M: Committee -> [Project]
- committee_name: str | None = sqlmodel.Field(default=None,
foreign_key="committee.name")
+ committee_name: str | None = sqlmodel.Field(default=None,
foreign_key="committee.name", **example("example"))
committee: Committee | None =
sqlmodel.Relationship(back_populates="projects")
see_also(Committee.projects)
@@ -396,9 +407,11 @@ class Project(sqlmodel.SQLModel, table=True):
)
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)),
)
- created_by: str | None = sqlmodel.Field(default=None)
+ created_by: str | None = sqlmodel.Field(default=None, **example("user"))
@property
def display_name(self) -> str:
@@ -646,10 +659,6 @@ 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):
# TODO: We have default=None here with a field typed int, not int | None
@@ -657,19 +666,19 @@ class CheckResult(sqlmodel.SQLModel, table=True):
# M-1: CheckResult -> Release
# 1-M: Release -C-> [CheckResult]
- release_name: str = sqlmodel.Field(foreign_key="release.name",
ondelete="CASCADE")
+ release_name: str = sqlmodel.Field(foreign_key="release.name",
ondelete="CASCADE", **example("example-0.0.1"))
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,
**example("00005"))
- checker: str =
sqlmodel.Field(**example("atr.tasks.checks.hashing.HashingCheck"))
+ checker: str = sqlmodel.Field(**example("atr.tasks.checks.license.files"))
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)),
+ **example(datetime.datetime(2025, 5, 1, 1, 2, 3, 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"))
@@ -698,13 +707,17 @@ class DistributionChannel(sqlmodel.SQLModel, table=True):
# PublicSigningKey: Committee
class PublicSigningKey(sqlmodel.SQLModel, table=True):
# The fingerprint must be stored as lowercase hex
- fingerprint: str = sqlmodel.Field(primary_key=True, unique=True)
+ fingerprint: str = sqlmodel.Field(
+ primary_key=True, unique=True,
**example("0123456789abcdef0123456789abcdef01234567")
+ )
# The algorithm is an RFC 4880 algorithm ID
- algorithm: int
+ algorithm: int = sqlmodel.Field(**example(1))
# Key length in bits
- length: int
+ length: int = sqlmodel.Field(**example(4096))
# Creation date
- created: datetime.datetime =
sqlmodel.Field(sa_column=sqlalchemy.Column(UTCDateTime))
+ created: datetime.datetime = sqlmodel.Field(
+ sa_column=sqlalchemy.Column(UTCDateTime),
**example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC))
+ )
# Latest self signature
latest_self_signature: datetime.datetime | None = sqlmodel.Field(
default=None, sa_column=sqlalchemy.Column(UTCDateTime)
@@ -712,15 +725,17 @@ class PublicSigningKey(sqlmodel.SQLModel, table=True):
# Expiration date
expires: datetime.datetime | None = sqlmodel.Field(default=None,
sa_column=sqlalchemy.Column(UTCDateTime))
# The primary UID declared in the key
- primary_declared_uid: str | None
+ primary_declared_uid: str | None = sqlmodel.Field(**example("User
<[email protected]>"))
# The secondary UIDs declared in the key
secondary_declared_uids: list[str] = sqlmodel.Field(
- default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON)
+ default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON),
**example(["User <[email protected]>"])
)
# The UID used by Apache, if available
- apache_uid: str | None
+ apache_uid: str | None = sqlmodel.Field(**example("user"))
# The ASCII armored key
- ascii_armored_key: str
+ ascii_armored_key: str = sqlmodel.Field(
+ **example("-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n...\n-----END PGP
PUBLIC KEY BLOCK-----\n")
+ )
# M-M: PublicSigningKey -> [Committee]
# M-M: Committee -> [PublicSigningKey]
diff --git a/uv.lock b/uv.lock
index 2bf35e5..4c1305a 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,7 @@ version = 1
requires-python = ">=3.13"
[options]
-exclude-newer = "2025-07-28T18:17:00Z"
+exclude-newer = "2025-07-28T19:19:00Z"
[[package]]
name = "aiohappyeyeballs"
@@ -83,7 +83,7 @@ wheels = [
[[package]]
name = "apache-trusted-releases"
-version = "0.20250728.1817"
+version = "0.20250728.1919"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]