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 6526bc7  Add a page listing manually recorded distributions
6526bc7 is described below

commit 6526bc76086900524a02a5a403b2f79f54517588
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Aug 7 17:07:49 2025 +0100

    Add a page listing manually recorded distributions
---
 atr/blueprints/api/api.py                       |  1 +
 atr/db/__init__.py                              | 21 ++++++++++++
 atr/models/sql.py                               | 13 +++----
 atr/routes/distribution.py                      | 28 ++++++++++++++-
 atr/storage/writers/distributions.py            |  2 +-
 migrations/versions/0020_2025.08.07_23999f25.py | 45 +++++++++++++++++++++++++
 6 files changed, 102 insertions(+), 8 deletions(-)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index a252770..b8c2564 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -311,6 +311,7 @@ async def ignore_delete(data: models.api.IgnoreDeleteArgs) 
-> DictResponse:
     ).model_dump(), 200
 
 
+# TODO: Rename to ignores
 @api.BLUEPRINT.route("/ignore/list/<committee_name>")
 @quart_schema.validate_response(models.api.IgnoreListResults, 200)
 async def ignore_list(committee_name: str) -> DictResponse:
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index 530a7ce..65dc3af 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -283,6 +283,27 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
 
         return Query(self, query)
 
+    def distribution(
+        self,
+        release_name: Opt[str] = NOT_SET,
+        platform: Opt[sql.DistributionPlatform] = NOT_SET,
+        owner_namespace: Opt[str] = NOT_SET,
+        package: Opt[str] = NOT_SET,
+        version: Opt[str] = NOT_SET,
+    ) -> Query[sql.Distribution]:
+        query = sqlmodel.select(sql.Distribution)
+        if is_defined(release_name):
+            query = query.where(sql.Distribution.release_name == release_name)
+        if is_defined(platform):
+            query = query.where(sql.Distribution.platform == platform)
+        if is_defined(owner_namespace):
+            query = query.where(sql.Distribution.owner_namespace == 
owner_namespace)
+        if is_defined(package):
+            query = query.where(sql.Distribution.package == package)
+        if is_defined(version):
+            query = query.where(sql.Distribution.version == version)
+        return Query(self, query)
+
     async def execute_query(self, query: sqlalchemy.sql.expression.Executable) 
-> sqlalchemy.engine.Result:
         if (self.log_queries or global_log_query) and isinstance(query, 
sqlalchemy.sql.expression.Select):
             try:
diff --git a/atr/models/sql.py b/atr/models/sql.py
index ed1750a..291ee67 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -819,13 +819,14 @@ class CheckResultIgnore(sqlmodel.SQLModel, table=True):
 
 # Distribution: Release
 class Distribution(sqlmodel.SQLModel, table=True):
-    id: int = sqlmodel.Field(default=None, primary_key=True)
-    release_name: str = sqlmodel.Field(foreign_key="release.name", 
ondelete="CASCADE")
+    release_name: str = sqlmodel.Field(primary_key=True, index=True, 
foreign_key="release.name", ondelete="CASCADE")
     release: Release = sqlmodel.Relationship(back_populates="distributions")
-    platform: DistributionPlatform = 
sqlmodel.Field(default=DistributionPlatform.ARTIFACTHUB)
-    owner_namespace: str | None = sqlmodel.Field(default=None)
-    package: str
-    version: str
+    platform: DistributionPlatform = sqlmodel.Field(
+        primary_key=True, index=True, default=DistributionPlatform.ARTIFACTHUB
+    )
+    owner_namespace: str = sqlmodel.Field(primary_key=True, index=True, 
default="")
+    package: str = sqlmodel.Field(primary_key=True, index=True)
+    version: str = sqlmodel.Field(primary_key=True, index=True)
     staging: bool = sqlmodel.Field(default=False)
     upload_date: datetime.datetime | None = sqlmodel.Field(default=None)
     api_url: str
diff --git a/atr/routes/distribution.py b/atr/routes/distribution.py
index aa7e92d..da19529 100644
--- a/atr/routes/distribution.py
+++ b/atr/routes/distribution.py
@@ -133,6 +133,32 @@ class DistributeData(schema.Lax):
         return None if v is None or (isinstance(v, str) and v.strip() == "") 
else v
 
 
[email protected]("/distributions/list/<project>/<version>", methods=["GET"])
+async def list_get(session: routes.CommitterSession, project: str, version: 
str) -> str:
+    async with db.session() as data:
+        distributions = await data.distribution(
+            release_name=sql.release_name(project, version),
+        ).all()
+
+    block = htm.Block()
+    block.h1["Distribution list"]
+    for distribution in distributions:
+        block.h2[f"{distribution.platform.name} {distribution.package} 
{distribution.version}"]
+        tbody = htpy.tbody[
+            _tr("Release name", distribution.release_name),
+            _tr("Platform", distribution.platform.value.name),
+            _tr("Owner or Namespace", distribution.owner_namespace or "-"),
+            _tr("Package", distribution.package),
+            _tr("Version", distribution.version),
+            _tr("Staging", "Yes" if distribution.staging else "No"),
+            _tr("Upload date", str(distribution.upload_date)),
+            _tr("API URL", distribution.api_url),
+        ]
+        block.table(".table.table-striped.table-bordered")[tbody]
+    title = f"Distribution list for {project} {version}"
+    return await template.blank(title, content=block.collect())
+
+
 @routes.committer("/distribution/record/<project>/<version>", methods=["GET"])
 async def record(session: routes.CommitterSession, project: str, version: str) 
-> str:
     form = await DistributeForm.create_form(data={"package": project, 
"version": version})
@@ -259,7 +285,7 @@ async def _distribute_post_validated(fpv: 
FormProjectVersion, /) -> str:
             _tr("Owner or Namespace", distribution.owner_namespace or "-"),
             _tr("Package", distribution.package),
             _tr("Version", distribution.version),
-            _tr("Staging", "No" if distribution.staging else "Yes"),
+            _tr("Staging", "Yes" if distribution.staging else "No"),
             _tr("Upload date", str(distribution.upload_date)),
             _tr("API URL", distribution.api_url),
         ]
diff --git a/atr/storage/writers/distributions.py 
b/atr/storage/writers/distributions.py
index 8786f6f..de1a301 100644
--- a/atr/storage/writers/distributions.py
+++ b/atr/storage/writers/distributions.py
@@ -101,7 +101,7 @@ class CommitteeMember(CommitteeParticipant):
         distribution = sql.Distribution(
             platform=platform,
             release_name=release_name,
-            owner_namespace=owner_namespace,
+            owner_namespace=owner_namespace or "",
             package=package,
             version=version,
             staging=staging,
diff --git a/migrations/versions/0020_2025.08.07_23999f25.py 
b/migrations/versions/0020_2025.08.07_23999f25.py
new file mode 100644
index 0000000..9767358
--- /dev/null
+++ b/migrations/versions/0020_2025.08.07_23999f25.py
@@ -0,0 +1,45 @@
+"""Add a compound primary key to Distribution
+
+Revision ID: 0020_2025.08.07_23999f25
+Revises: 0019_2025.08.07_279ca4a9
+Create Date: 2025-08-07 15:53:27.260830+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0020_2025.08.07_23999f25"
+down_revision: str | None = "0019_2025.08.07_279ca4a9"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    op.execute("UPDATE distribution SET owner_namespace = '' WHERE 
owner_namespace IS NULL")
+    with op.batch_alter_table("distribution", schema=None) as batch_op:
+        batch_op.drop_column("id")
+        batch_op.alter_column("owner_namespace", existing_type=sa.VARCHAR(), 
nullable=False)
+        batch_op.create_primary_key(
+            "pk_distribution", ["release_name", "platform", "owner_namespace", 
"package", "version"]
+        )
+        batch_op.create_index(batch_op.f("ix_distribution_owner_namespace"), 
["owner_namespace"], unique=False)
+        batch_op.create_index(batch_op.f("ix_distribution_package"), 
["package"], unique=False)
+        batch_op.create_index(batch_op.f("ix_distribution_platform"), 
["platform"], unique=False)
+        batch_op.create_index(batch_op.f("ix_distribution_release_name"), 
["release_name"], unique=False)
+        batch_op.create_index(batch_op.f("ix_distribution_version"), 
["version"], unique=False)
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("distribution", schema=None) as batch_op:
+        batch_op.drop_index(batch_op.f("ix_distribution_version"))
+        batch_op.drop_index(batch_op.f("ix_distribution_release_name"))
+        batch_op.drop_index(batch_op.f("ix_distribution_platform"))
+        batch_op.drop_index(batch_op.f("ix_distribution_package"))
+        batch_op.drop_index(batch_op.f("ix_distribution_owner_namespace"))
+        batch_op.drop_constraint("pk_distribution", type_="primary")
+        batch_op.alter_column("owner_namespace", existing_type=sa.VARCHAR(), 
nullable=True)
+        batch_op.add_column(sa.Column("id", sa.INTEGER(), nullable=False))
+        batch_op.create_primary_key("pk_distribution_id", ["id"])


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

Reply via email to