This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit 2ebee77e53803f21ed02fe089bbb4728b6aa1443
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Mar 12 15:03:28 2026 +0000

    Ensure that list fields do not use SQL NULL as values
---
 atr/models/sql.py                               | 32 +++++++----
 migrations/versions/0057_2026.03.12_cdf4ce7d.py | 72 +++++++++++++++++++++++++
 2 files changed, 94 insertions(+), 10 deletions(-)

diff --git a/atr/models/sql.py b/atr/models/sql.py
index 29eac4ac..626de8a6 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -523,13 +523,17 @@ class Committee(sqlmodel.SQLModel, table=True):
     projects: list["Project"] = 
sqlmodel.Relationship(back_populates="committee")
 
     committee_members: list[str] = sqlmodel.Field(
-        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON), 
**example(["sbp", "tn", "wave"])
+        default_factory=list,
+        sa_column=sqlalchemy.Column(sqlalchemy.JSON, nullable=False),
+        **example(["sbp", "tn", "wave"]),
     )
     committers: list[str] = sqlmodel.Field(
-        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON), 
**example(["sbp", "tn", "wave"])
+        default_factory=list,
+        sa_column=sqlalchemy.Column(sqlalchemy.JSON, nullable=False),
+        **example(["sbp", "tn", "wave"]),
     )
     release_managers: list[str] = sqlmodel.Field(
-        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON), 
**example(["wave"])
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False), **example(["wave"])
     )
 
     # M-M: Committee -> [PublicSigningKey]
@@ -860,14 +864,16 @@ class Release(sqlmodel.SQLModel, table=True):
     see_also(Project.releases)
 
     package_managers: list[str] = sqlmodel.Field(
-        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON), 
**example([])
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False), **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 = sqlmodel.Field(**example("0.0.1"))
-    sboms: list[str] = sqlmodel.Field(default_factory=list, 
sa_column=sqlalchemy.Column(sqlalchemy.JSON), **example([]))
+    sboms: list[str] = sqlmodel.Field(
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False), **example([])
+    )
 
     # 1-1: Release -C-> ReleasePolicy
     # 1-1: ReleasePolicy -> Release
@@ -877,7 +883,9 @@ 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))
+    votes: list[VoteEntry] = sqlmodel.Field(
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False)
+    )
     vote_manual: bool = sqlmodel.Field(default=False, **example(False))
     vote_started: datetime.datetime | None = sqlmodel.Field(
         default=None,
@@ -1142,7 +1150,9 @@ class PublicSigningKey(sqlmodel.SQLModel, table=True):
     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), 
**example(["User <[email protected]>"])
+        default_factory=list,
+        sa_column=sqlalchemy.Column(sqlalchemy.JSON, nullable=False),
+        **example(["User <[email protected]>"]),
     )
     # The UID used by Apache, if available
     apache_uid: str | None = sqlmodel.Field(**example("user"))
@@ -1216,7 +1226,9 @@ class Quarantined(sqlmodel.SQLModel, table=True):
 # ReleasePolicy: Project
 class ReleasePolicy(sqlmodel.SQLModel, table=True):
     id: int = sqlmodel.Field(default=None, primary_key=True)
-    mailto_addresses: list[str] = sqlmodel.Field(default_factory=list, 
sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+    mailto_addresses: list[str] = sqlmodel.Field(
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False)
+    )
     manual_vote: bool = sqlmodel.Field(default=False)
     min_hours: int | None = sqlmodel.Field(default=None)
     release_checklist: str = sqlmodel.Field(default="")
@@ -1227,10 +1239,10 @@ class ReleasePolicy(sqlmodel.SQLModel, table=True):
     announce_release_subject: str = sqlmodel.Field(default="")
     announce_release_template: str = sqlmodel.Field(default="")
     binary_artifact_paths: list[str] = sqlmodel.Field(
-        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON)
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False)
     )
     source_artifact_paths: list[str] = sqlmodel.Field(
-        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON)
+        default_factory=list, sa_column=sqlalchemy.Column(sqlalchemy.JSON, 
nullable=False)
     )
     license_check_mode: LicenseCheckMode = 
sqlmodel.Field(default=LicenseCheckMode.BOTH)
     source_excludes_lightweight: list[str] = sqlmodel.Field(
diff --git a/migrations/versions/0057_2026.03.12_cdf4ce7d.py 
b/migrations/versions/0057_2026.03.12_cdf4ce7d.py
new file mode 100644
index 00000000..5f153ba9
--- /dev/null
+++ b/migrations/versions/0057_2026.03.12_cdf4ce7d.py
@@ -0,0 +1,72 @@
+"""Make all list fields non-nullable
+
+Revision ID: 0057_2026.03.12_cdf4ce7d
+Revises: 0056_2026.03.08_427a333e
+Create Date: 2026-03-12 14:50:05.930579+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.dialects import sqlite
+
+# Revision identifiers, used by Alembic
+revision: str = "0057_2026.03.12_cdf4ce7d"
+down_revision: str | None = "0056_2026.03.08_427a333e"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    # Backfill NULL -> [] before setting nullable=False
+    op.execute(sa.text("UPDATE committee SET committee_members = '[]' WHERE 
committee_members IS NULL"))
+    op.execute(sa.text("UPDATE committee SET committers = '[]' WHERE 
committers IS NULL"))
+    op.execute(sa.text("UPDATE committee SET release_managers = '[]' WHERE 
release_managers IS NULL"))
+    op.execute(
+        sa.text("UPDATE publicsigningkey SET secondary_declared_uids = '[]' 
WHERE secondary_declared_uids IS NULL")
+    )
+    op.execute(sa.text("UPDATE release SET package_managers = '[]' WHERE 
package_managers IS NULL"))
+    op.execute(sa.text("UPDATE release SET sboms = '[]' WHERE sboms IS NULL"))
+    op.execute(sa.text("UPDATE release SET votes = '[]' WHERE votes IS NULL"))
+    op.execute(sa.text("UPDATE releasepolicy SET mailto_addresses = '[]' WHERE 
mailto_addresses IS NULL"))
+    op.execute(sa.text("UPDATE releasepolicy SET binary_artifact_paths = '[]' 
WHERE binary_artifact_paths IS NULL"))
+    op.execute(sa.text("UPDATE releasepolicy SET source_artifact_paths = '[]' 
WHERE source_artifact_paths IS NULL"))
+
+    with op.batch_alter_table("committee", schema=None) as batch_op:
+        batch_op.alter_column("committee_members", 
existing_type=sqlite.JSON(), nullable=False)
+        batch_op.alter_column("committers", existing_type=sqlite.JSON(), 
nullable=False)
+        batch_op.alter_column("release_managers", existing_type=sqlite.JSON(), 
nullable=False)
+
+    with op.batch_alter_table("publicsigningkey", schema=None) as batch_op:
+        batch_op.alter_column("secondary_declared_uids", 
existing_type=sqlite.JSON(), nullable=False)
+
+    with op.batch_alter_table("release", schema=None) as batch_op:
+        batch_op.alter_column("package_managers", existing_type=sqlite.JSON(), 
nullable=False)
+        batch_op.alter_column("sboms", existing_type=sqlite.JSON(), 
nullable=False)
+        batch_op.alter_column("votes", existing_type=sqlite.JSON(), 
nullable=False)
+
+    with op.batch_alter_table("releasepolicy", schema=None) as batch_op:
+        batch_op.alter_column("mailto_addresses", existing_type=sqlite.JSON(), 
nullable=False)
+        batch_op.alter_column("binary_artifact_paths", 
existing_type=sqlite.JSON(), nullable=False)
+        batch_op.alter_column("source_artifact_paths", 
existing_type=sqlite.JSON(), nullable=False)
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("releasepolicy", schema=None) as batch_op:
+        batch_op.alter_column("source_artifact_paths", 
existing_type=sqlite.JSON(), nullable=True)
+        batch_op.alter_column("binary_artifact_paths", 
existing_type=sqlite.JSON(), nullable=True)
+        batch_op.alter_column("mailto_addresses", existing_type=sqlite.JSON(), 
nullable=True)
+
+    with op.batch_alter_table("release", schema=None) as batch_op:
+        batch_op.alter_column("votes", existing_type=sqlite.JSON(), 
nullable=True)
+        batch_op.alter_column("sboms", existing_type=sqlite.JSON(), 
nullable=True)
+        batch_op.alter_column("package_managers", existing_type=sqlite.JSON(), 
nullable=True)
+
+    with op.batch_alter_table("publicsigningkey", schema=None) as batch_op:
+        batch_op.alter_column("secondary_declared_uids", 
existing_type=sqlite.JSON(), nullable=True)
+
+    with op.batch_alter_table("committee", schema=None) as batch_op:
+        batch_op.alter_column("release_managers", existing_type=sqlite.JSON(), 
nullable=True)
+        batch_op.alter_column("committers", existing_type=sqlite.JSON(), 
nullable=True)
+        batch_op.alter_column("committee_members", 
existing_type=sqlite.JSON(), nullable=True)


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

Reply via email to