feat: flow engine data layer with tests
- SQLModel tables: flow_definitions/versions/runs, approval_tasks - Alembic setup with autogenerate (sqlmodel types fixed via script.py.mako) - Settings extended (shared root .env), db engine/session helpers - pytest suite: conftest with isolated test DB, 5 model tests green
This commit is contained in:
parent
2ba062c4c5
commit
e178ae406d
12 changed files with 686 additions and 3 deletions
3
backend/app/models/__init__.py
Normal file
3
backend/app/models/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .flow import ApprovalTask, FlowDefinition, FlowRun, FlowVersion
|
||||
|
||||
__all__ = ["ApprovalTask", "FlowDefinition", "FlowRun", "FlowVersion"]
|
||||
90
backend/app/models/flow.py
Normal file
90
backend/app/models/flow.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Flow engine domain models (SQLModel tables)."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlalchemy import JSON, Column, DateTime, UniqueConstraint
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
def utcnow() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def _ts_column() -> Column:
|
||||
return Column(DateTime(timezone=True))
|
||||
|
||||
|
||||
def _json_column() -> Column:
|
||||
# Generic JSON: JSONB on Postgres, JSON/TEXT elsewhere.
|
||||
return Column(JSON)
|
||||
|
||||
|
||||
class FlowDefinition(SQLModel, table=True):
|
||||
"""A named decision flow (e.g. 'Bauvorhaben pruefen'). Versions live separately."""
|
||||
|
||||
__tablename__ = "flow_definitions"
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
name: str = Field(index=True, unique=True, max_length=200)
|
||||
description: str = Field(default="", max_length=2000)
|
||||
status: str = Field(default="active", max_length=20) # active | archived
|
||||
# Flow to run after a human approval on this flow's runs (nullable).
|
||||
continuation_flow_id: UUID | None = Field(
|
||||
default=None, foreign_key="flow_definitions.id"
|
||||
)
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
|
||||
|
||||
class FlowVersion(SQLModel, table=True):
|
||||
"""An immutable version of a flow: the exact Langflow JSON that runs."""
|
||||
|
||||
__tablename__ = "flow_versions"
|
||||
__table_args__ = (UniqueConstraint("flow_id", "version_no"),)
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
flow_id: UUID = Field(foreign_key="flow_definitions.id", index=True)
|
||||
version_no: int = Field(index=True)
|
||||
langflow_json: dict = Field(default_factory=dict, sa_column=_json_column())
|
||||
# Flow id under which LFX serves this version (set on deploy).
|
||||
lfx_flow_id: str | None = Field(default=None, max_length=200)
|
||||
deployed_at: datetime | None = Field(default=None, sa_column=_ts_column())
|
||||
author: str = Field(default="", max_length=200)
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
|
||||
|
||||
class FlowRun(SQLModel, table=True):
|
||||
"""One execution of a flow version."""
|
||||
|
||||
__tablename__ = "flow_runs"
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
version_id: UUID = Field(foreign_key="flow_versions.id", index=True)
|
||||
tenant_id: str = Field(default="default", index=True, max_length=200)
|
||||
# running | completed | pending_approval | failed
|
||||
status: str = Field(default="running", index=True, max_length=30)
|
||||
input: dict | None = Field(default=None, sa_column=_json_column())
|
||||
output: dict | None = Field(default=None, sa_column=_json_column())
|
||||
session_id: str | None = Field(default=None, max_length=200)
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
finished_at: datetime | None = Field(default=None, sa_column=_ts_column())
|
||||
|
||||
|
||||
class ApprovalTask(SQLModel, table=True):
|
||||
"""Human task created by a flow that reached a terminal approval state."""
|
||||
|
||||
__tablename__ = "approval_tasks"
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
run_id: UUID = Field(foreign_key="flow_runs.id", index=True)
|
||||
version_id: UUID = Field(foreign_key="flow_versions.id", index=True)
|
||||
tenant_id: str = Field(default="default", index=True, max_length=200)
|
||||
title: str = Field(default="", max_length=500)
|
||||
payload: dict = Field(default_factory=dict, sa_column=_json_column())
|
||||
continuation_flow_id: UUID | None = Field(default=None)
|
||||
approver: str | None = Field(default=None, max_length=200)
|
||||
# open | approved | rejected
|
||||
status: str = Field(default="open", index=True, max_length=20)
|
||||
decision_note: str = Field(default="", max_length=2000)
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
decided_at: datetime | None = Field(default=None, sa_column=_ts_column())
|
||||
Loading…
Add table
Add a link
Reference in a new issue