Keep the engine's state in SQLite, not Postgres
One process owns this database — the image has run a single uvicorn worker for that reason since the four-engines bug — so a file beside the flows is the honest shape for it, and it is what lets `fluksio serve` need no infrastructure at all. Live values, node execution and the work queue never came here anyway; what does is a rollup a minute at a time, a row per cascade and the run history, and WAL keeps the readers going while that one writer works. DATA_DIR is now the one setting that moves everything an installation keeps; the rest derive from it and the images still spell theirs out. The schema is prepared in-process at startup, so the prestart service is gone, and the ten Postgres-only revisions collapse into one portable baseline. Three things only worked because psycopg was casting for us: a token's subject arriving as a string where the column is a UUID, `greatest`, and `date_bin`. The timestamps needed a column type of their own — SQLite stores no offset, and a naive datetime read back either raises against an aware `now` or serialises as local time. Postgres stays in the stack only for Umami, behind the analytics profile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+26
-18
@@ -3,9 +3,11 @@ from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from pydantic import EmailStr
|
||||
from sqlalchemy import JSON, Column, DateTime
|
||||
from sqlalchemy import JSON, Column
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
from fluksio.core.types import UTCDateTime
|
||||
|
||||
|
||||
def get_datetime_utc() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
@@ -57,7 +59,7 @@ class User(UserBase, table=True):
|
||||
portal_sub: str | None = Field(default=None, max_length=64, unique=True, index=True)
|
||||
created_at: datetime | None = Field(
|
||||
default_factory=get_datetime_utc,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
|
||||
|
||||
@@ -118,7 +120,9 @@ class OAuthClient(SQLModel, table=True):
|
||||
client_name: str = Field(max_length=128)
|
||||
redirect_uris: list[str] = Field(sa_column=Column(JSON), default_factory=list)
|
||||
created_at: datetime = Field(
|
||||
default_factory=lambda: datetime.now(timezone.utc), nullable=False
|
||||
default_factory=lambda: datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
|
||||
|
||||
@@ -138,12 +142,14 @@ class OAuthAuthorizationCode(SQLModel, table=True):
|
||||
redirect_uri: str = Field(max_length=2048)
|
||||
code_challenge: str = Field(max_length=128)
|
||||
resource: str | None = Field(default=None, max_length=2048)
|
||||
expires_at: datetime
|
||||
used_at: datetime | None = None
|
||||
expires_at: datetime = Field(sa_type=UTCDateTime)
|
||||
used_at: datetime | None = Field(default=None, sa_type=UTCDateTime)
|
||||
#: The refresh token this code produced, so replaying the code can revoke it.
|
||||
refresh_token_id: uuid.UUID | None = None
|
||||
created_at: datetime = Field(
|
||||
default_factory=lambda: datetime.now(timezone.utc), nullable=False
|
||||
default_factory=lambda: datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
|
||||
|
||||
@@ -163,10 +169,12 @@ class OAuthRefreshToken(SQLModel, table=True):
|
||||
#: Every token rotated out of one authorization shares this, so reusing an
|
||||
#: old one can revoke the whole line rather than just itself.
|
||||
family_id: uuid.UUID
|
||||
expires_at: datetime
|
||||
expires_at: datetime = Field(sa_type=UTCDateTime)
|
||||
revoked: bool = False
|
||||
created_at: datetime = Field(
|
||||
default_factory=lambda: datetime.now(timezone.utc), nullable=False
|
||||
default_factory=lambda: datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
|
||||
|
||||
@@ -231,7 +239,7 @@ class MetricBucket(SQLModel, table=True):
|
||||
bucket: datetime = Field(
|
||||
primary_key=True,
|
||||
index=True,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
executions: int = 0
|
||||
errors: int = 0
|
||||
@@ -255,7 +263,7 @@ class EngineEvent(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
ts: datetime = Field(
|
||||
index=True,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
#: node_error, flow_quarantined, engine_degraded, …, or audit.
|
||||
type: str = Field(max_length=32, index=True)
|
||||
@@ -279,11 +287,11 @@ class FlowRun(SQLModel, table=True):
|
||||
source: str = ""
|
||||
started_at: datetime = Field(
|
||||
index=True,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
finished_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
#: running, ok, error or abandoned.
|
||||
status: str = "running"
|
||||
@@ -337,15 +345,15 @@ class Run(SQLModel, table=True):
|
||||
labels: list[str] = Field(sa_column=Column(JSON), default_factory=list)
|
||||
created_at: datetime = Field(
|
||||
index=True,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
started_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
finished_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
duration_ms: float = 0.0
|
||||
#: The flow's declared outputs once it finished.
|
||||
@@ -355,7 +363,7 @@ class Run(SQLModel, table=True):
|
||||
engine: str = Field(default="", max_length=64)
|
||||
lease_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
actor: str = Field(default="", max_length=255)
|
||||
|
||||
@@ -372,7 +380,7 @@ class RunNode(SQLModel, table=True):
|
||||
attempt: int = 1
|
||||
started_at: datetime | None = Field(
|
||||
default=None,
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
duration_ms: float = 0.0
|
||||
#: Which worker ran it: "local", or a remote worker's name.
|
||||
@@ -417,5 +425,5 @@ class RunArtifact(SQLModel, table=True):
|
||||
media_type: str = Field(default="application/octet-stream", max_length=128)
|
||||
created_at: datetime = Field(
|
||||
default_factory=lambda: datetime.now(timezone.utc),
|
||||
sa_type=DateTime(timezone=True), # type: ignore
|
||||
sa_type=UTCDateTime,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user