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]