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]
