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


The following commit(s) were added to refs/heads/sbp by this push:
     new d9f42be8 Ensure that datetime fields do not use SQL NULL as values
d9f42be8 is described below

commit d9f42be8564b3977bef9d59dc5247815420c8f72
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Mar 12 15:24:52 2026 +0000

    Ensure that datetime fields do not use SQL NULL as values
---
 atr/models/sql.py                               | 21 ++++---
 migrations/versions/0058_2026.03.12_2ebee77e.py | 83 +++++++++++++++++++++++++
 2 files changed, 95 insertions(+), 9 deletions(-)

diff --git a/atr/models/sql.py b/atr/models/sql.py
index 626de8a6..02e3337a 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -366,9 +366,10 @@ class PersonalAccessToken(sqlmodel.SQLModel, table=True):
     asfuid: str = sqlmodel.Field(index=True)
     token_hash: str = sqlmodel.Field(unique=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, nullable=False),
     )
-    expires: datetime.datetime = 
sqlmodel.Field(sa_column=sqlalchemy.Column(UTCDateTime))
+    expires: datetime.datetime = 
sqlmodel.Field(sa_column=sqlalchemy.Column(UTCDateTime, nullable=False))
     last_used: datetime.datetime | None = sqlmodel.Field(default=None, 
sa_column=sqlalchemy.Column(UTCDateTime))
     label: str | None = None
 
@@ -403,7 +404,7 @@ class Task(sqlmodel.SQLModel, table=True):
     asf_uid: str
     added: datetime.datetime = sqlmodel.Field(
         default_factory=lambda: datetime.datetime.now(datetime.UTC),
-        sa_column=sqlalchemy.Column(UTCDateTime, index=True),
+        sa_column=sqlalchemy.Column(UTCDateTime, index=True, nullable=False),
     )
     scheduled: datetime.datetime | None = sqlmodel.Field(
         default=None,
@@ -598,7 +599,7 @@ class Project(sqlmodel.SQLModel, table=True):
 
     created: datetime.datetime = sqlmodel.Field(
         default_factory=lambda: datetime.datetime.now(datetime.UTC),
-        sa_column=sqlalchemy.Column(UTCDateTime),
+        sa_column=sqlalchemy.Column(UTCDateTime, nullable=False),
         **example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
     created_by: str | None = sqlmodel.Field(default=None, **example("user"))
@@ -847,7 +848,8 @@ class Release(sqlmodel.SQLModel, table=True):
     name: str = sqlmodel.Field(default="", primary_key=True, unique=True, 
**example("example-0.0.1"))
     phase: ReleasePhase = 
sqlmodel.Field(**example(ReleasePhase.RELEASE_CANDIDATE_DRAFT))
     created: datetime.datetime = sqlmodel.Field(
-        sa_column=sqlalchemy.Column(UTCDateTime), 
**example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC))
+        sa_column=sqlalchemy.Column(UTCDateTime, nullable=False),
+        **example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
     released: datetime.datetime | None = sqlmodel.Field(
         default=None,
@@ -1022,7 +1024,7 @@ class CheckResult(sqlmodel.SQLModel, table=True):
     )
     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),
+        sa_column=sqlalchemy.Column(UTCDateTime, nullable=False),
         **example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
     status: CheckResultStatus = 
sqlmodel.Field(default=CheckResultStatus.SUCCESS, 
**example(CheckResultStatus.SUCCESS))
@@ -1038,7 +1040,7 @@ class CheckResultIgnore(sqlmodel.SQLModel, table=True):
     id: int = sqlmodel.Field(default=None, primary_key=True, **example(123))
     asf_uid: str = sqlmodel.Field(**example("user"))
     created: datetime.datetime = sqlmodel.Field(
-        sa_column=sqlalchemy.Column(UTCDateTime),
+        sa_column=sqlalchemy.Column(UTCDateTime, nullable=False),
         **example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
     project_name: str = sqlmodel.Field(foreign_key="project.name", 
**example("example"))
@@ -1138,7 +1140,8 @@ class PublicSigningKey(sqlmodel.SQLModel, table=True):
     length: int = sqlmodel.Field(**example(4096))
     # Creation date
     created: datetime.datetime = sqlmodel.Field(
-        sa_column=sqlalchemy.Column(UTCDateTime), 
**example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC))
+        sa_column=sqlalchemy.Column(UTCDateTime, nullable=False),
+        **example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
     # Latest self signature
     latest_self_signature: datetime.datetime | None = sqlmodel.Field(
@@ -1321,7 +1324,7 @@ class Revision(sqlmodel.SQLModel, table=True):
     asfuid: str = sqlmodel.Field(**example("user"))
     created: datetime.datetime = sqlmodel.Field(
         default_factory=lambda: datetime.datetime.now(datetime.UTC),
-        sa_column=sqlalchemy.Column(UTCDateTime),
+        sa_column=sqlalchemy.Column(UTCDateTime, nullable=False),
         **example(datetime.datetime(2025, 5, 1, 1, 2, 3, tzinfo=datetime.UTC)),
     )
     phase: ReleasePhase = 
sqlmodel.Field(**example(ReleasePhase.RELEASE_CANDIDATE_DRAFT))
diff --git a/migrations/versions/0058_2026.03.12_2ebee77e.py 
b/migrations/versions/0058_2026.03.12_2ebee77e.py
new file mode 100644
index 00000000..9437e4b9
--- /dev/null
+++ b/migrations/versions/0058_2026.03.12_2ebee77e.py
@@ -0,0 +1,83 @@
+"""Make all datetime fields non-nullable
+
+Revision ID: 0058_2026.03.12_2ebee77e
+Revises: 0057_2026.03.12_cdf4ce7d
+Create Date: 2026-03-12 15:22:42.426540+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0058_2026.03.12_2ebee77e"
+down_revision: str | None = "0057_2026.03.12_cdf4ce7d"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    # Backfill NULL datetimes before setting nullable=False
+    epoch = "1970-01-01 00:00:00"
+    op.execute(sa.text(f"UPDATE checkresult SET created = '{epoch}' WHERE 
created IS NULL"))
+    op.execute(sa.text(f"UPDATE checkresultignore SET created = '{epoch}' 
WHERE created IS NULL"))
+    op.execute(sa.text(f"UPDATE personalaccesstoken SET created = '{epoch}' 
WHERE created IS NULL"))
+    op.execute(sa.text(f"UPDATE personalaccesstoken SET expires = '{epoch}' 
WHERE expires IS NULL"))
+    op.execute(sa.text(f"UPDATE project SET created = '{epoch}' WHERE created 
IS NULL"))
+    op.execute(sa.text(f"UPDATE publicsigningkey SET created = '{epoch}' WHERE 
created IS NULL"))
+    op.execute(sa.text(f"UPDATE release SET created = '{epoch}' WHERE created 
IS NULL"))
+    op.execute(sa.text(f"UPDATE revision SET created = '{epoch}' WHERE created 
IS NULL"))
+    op.execute(sa.text(f"UPDATE task SET added = '{epoch}' WHERE added IS 
NULL"))
+
+    with op.batch_alter_table("checkresult", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+    with op.batch_alter_table("checkresultignore", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+    with op.batch_alter_table("personalaccesstoken", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=False)
+        batch_op.alter_column("expires", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+    with op.batch_alter_table("project", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+    with op.batch_alter_table("publicsigningkey", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+    with op.batch_alter_table("release", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+    with op.batch_alter_table("revision", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+    with op.batch_alter_table("task", schema=None) as batch_op:
+        batch_op.alter_column("added", existing_type=sa.TIMESTAMP(), 
nullable=False)
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("task", schema=None) as batch_op:
+        batch_op.alter_column("added", existing_type=sa.TIMESTAMP(), 
nullable=True)
+
+    with op.batch_alter_table("revision", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=True)
+
+    with op.batch_alter_table("release", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=True)
+
+    with op.batch_alter_table("publicsigningkey", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=True)
+
+    with op.batch_alter_table("project", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=True)
+
+    with op.batch_alter_table("personalaccesstoken", schema=None) as batch_op:
+        batch_op.alter_column("expires", existing_type=sa.TIMESTAMP(), 
nullable=True)
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=True)
+
+    with op.batch_alter_table("checkresultignore", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=True)
+
+    with op.batch_alter_table("checkresult", schema=None) as batch_op:
+        batch_op.alter_column("created", existing_type=sa.TIMESTAMP(), 
nullable=True)


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

Reply via email to