Files
activity-core/src/activity_core/orm.py
Bernd Worsch cb7cf3bc8c feat(db): ORM models + Alembic migrations 0001–0003 — T09/T10/T11
SQLAlchemy ORM (src/activity_core/orm.py):
  - ActivityDefinition, ActivityRun, TaskInstance mapped to Base.metadata
  - Wired into migrations/env.py for autogenerate support

Migrations (chained 0001 → 0002 → 0003):
  - 0001: activity_definitions (id, name, enabled, trigger_type,
          trigger_config JSONB, context_sources JSONB, task_templates JSONB,
          dedupe_key_strategy, version, created_at, updated_at)
  - 0002: activity_runs (run_id, activity_id FK→activity_definitions,
          scheduled_for, fired_at, context_snapshot JSONB, tasks_spawned,
          version_used) + index on activity_id
  - 0003: task_instances (id, run_id FK→activity_runs CASCADE,
          type, params JSONB, status, created_at) + index on run_id

Apply with: ACTCORE_DB_URL=... alembic upgrade head

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 21:51:01 +00:00

96 lines
3.2 KiB
Python

"""SQLAlchemy ORM table definitions for activity-core.
These are the persistence-layer counterparts to the Pydantic domain models in
models.py. Alembic reads Base.metadata (imported via db.py) for autogenerate.
"""
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import (
Boolean,
DateTime,
ForeignKey,
Integer,
Text,
func,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column
from activity_core.db import Base
class ActivityDefinition(Base):
__tablename__ = "activity_definitions"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
name: Mapped[str] = mapped_column(Text, nullable=False)
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
trigger_type: Mapped[str] = mapped_column(Text, nullable=False)
trigger_config: Mapped[dict] = mapped_column(JSONB, nullable=False)
context_sources: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
task_templates: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
dedupe_key_strategy: Mapped[str] = mapped_column(
Text, nullable=False, default="skip"
)
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now(),
onupdate=func.now(),
)
class ActivityRun(Base):
__tablename__ = "activity_runs"
run_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
activity_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("activity_definitions.id", ondelete="RESTRICT"),
nullable=False,
index=True,
)
scheduled_for: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
fired_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
context_snapshot: Mapped[dict] = mapped_column(
JSONB, nullable=False, default=dict
)
tasks_spawned: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
version_used: Mapped[int] = mapped_column(Integer, nullable=False)
class TaskInstance(Base):
__tablename__ = "task_instances"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
run_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("activity_runs.run_id", ondelete="CASCADE"),
nullable=False,
index=True,
)
type: Mapped[str] = mapped_column(Text, nullable=False)
params: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
status: Mapped[str] = mapped_column(Text, nullable=False, default="pending")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)