feat(C1+C2): workflow system — WorkflowDefinition + Celery Canvas builder
Migrations 037 (workflow tables + 3 seed definitions) + 038 (output_types.workflow_definition_id). WorkflowDefinition/Run/NodeResult SQLAlchemy models in domains/rendering/models.py. workflow_builder.py: dispatch_workflow() with Celery Canvas for still/turntable/multi_angle. workflow_router.py: CRUD endpoints at /api/workflows (admin/PM guards). dispatch_service.py: dispatch_render_with_workflow() prefers workflow path when OutputType.workflow_definition_id is set, falls back to legacy dispatch otherwise. main.py: registers workflows_router. models/__init__.py: re-exports WorkflowDefinition, WorkflowRun, WorkflowNodeResult. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""Add workflow_definitions, workflow_runs, workflow_node_results tables.
|
||||
|
||||
Revision ID: 037
|
||||
Revises: 036
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
revision = '037'
|
||||
down_revision = '036'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('workflow_definitions',
|
||||
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text('gen_random_uuid()')),
|
||||
sa.Column('name', sa.String(200), nullable=False),
|
||||
sa.Column('output_type_id', UUID(as_uuid=True), sa.ForeignKey('output_types.id', ondelete='SET NULL'), nullable=True),
|
||||
sa.Column('config', JSONB, nullable=False, server_default='{}'),
|
||||
sa.Column('is_active', sa.Boolean, nullable=False, server_default='true'),
|
||||
sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.text('NOW()')),
|
||||
)
|
||||
|
||||
op.create_table('workflow_runs',
|
||||
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text('gen_random_uuid()')),
|
||||
sa.Column('workflow_def_id', UUID(as_uuid=True), sa.ForeignKey('workflow_definitions.id', ondelete='SET NULL'), nullable=True),
|
||||
sa.Column('order_line_id', UUID(as_uuid=True), sa.ForeignKey('order_lines.id', ondelete='CASCADE'), nullable=True),
|
||||
sa.Column('celery_task_id', sa.String(500), nullable=True),
|
||||
sa.Column('status', sa.String(50), nullable=False, server_default='pending'),
|
||||
sa.Column('started_at', sa.DateTime, nullable=True),
|
||||
sa.Column('completed_at', sa.DateTime, nullable=True),
|
||||
sa.Column('error_message', sa.Text, nullable=True),
|
||||
sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.text('NOW()')),
|
||||
)
|
||||
op.create_index('ix_workflow_runs_order_line', 'workflow_runs', ['order_line_id'])
|
||||
op.create_index('ix_workflow_runs_status', 'workflow_runs', ['status'])
|
||||
|
||||
op.create_table('workflow_node_results',
|
||||
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text('gen_random_uuid()')),
|
||||
sa.Column('run_id', UUID(as_uuid=True), sa.ForeignKey('workflow_runs.id', ondelete='CASCADE'), nullable=False),
|
||||
sa.Column('node_name', sa.String(200), nullable=False),
|
||||
sa.Column('status', sa.String(50), nullable=False, server_default='pending'),
|
||||
sa.Column('output', JSONB, nullable=True),
|
||||
sa.Column('log', sa.Text, nullable=True),
|
||||
sa.Column('duration_s', sa.Float, nullable=True),
|
||||
sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.text('NOW()')),
|
||||
)
|
||||
op.create_index('ix_workflow_node_results_run', 'workflow_node_results', ['run_id'])
|
||||
|
||||
# Seed standard workflow definitions
|
||||
op.execute("""
|
||||
INSERT INTO workflow_definitions (name, config, is_active) VALUES
|
||||
('Still-Render', '{"type": "still", "params": {"render_engine": "cycles", "samples": 256, "resolution": [2048, 2048]}}', true),
|
||||
('Turntable-Animation', '{"type": "turntable", "params": {"render_engine": "cycles", "samples": 64, "fps": 24, "duration_s": 5}}', true),
|
||||
('Multi-Angle', '{"type": "multi_angle", "params": {"render_engine": "cycles", "samples": 128, "angles": [0, 45, 90, 135, 180]}}', true)
|
||||
""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('workflow_node_results')
|
||||
op.drop_table('workflow_runs')
|
||||
op.drop_table('workflow_definitions')
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Add workflow_definition_id FK to output_types.
|
||||
|
||||
Revision ID: 038
|
||||
Revises: 037
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
revision = '038'
|
||||
down_revision = '037'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('output_types',
|
||||
sa.Column('workflow_definition_id', UUID(as_uuid=True),
|
||||
sa.ForeignKey('workflow_definitions.id', ondelete='SET NULL'),
|
||||
nullable=True)
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('output_types', 'workflow_definition_id')
|
||||
Reference in New Issue
Block a user