|
import json
|
|
import sqlite3
|
|
import time
|
|
from typing import List, Optional
|
|
|
|
from .workflow_definition import Workflow
|
|
|
|
|
|
class WorkflowStorage:
|
|
def __init__(self, db_path: str):
|
|
self.db_path = db_path
|
|
self._initialize_storage()
|
|
|
|
def _initialize_storage(self):
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workflows (
|
|
workflow_id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
workflow_data TEXT NOT NULL,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
execution_count INTEGER DEFAULT 0,
|
|
last_execution_at INTEGER,
|
|
tags TEXT
|
|
)
|
|
"""
|
|
)
|
|
|
|
cursor.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workflow_executions (
|
|
execution_id TEXT PRIMARY KEY,
|
|
workflow_id TEXT NOT NULL,
|
|
started_at INTEGER NOT NULL,
|
|
completed_at INTEGER,
|
|
status TEXT NOT NULL,
|
|
execution_log TEXT,
|
|
variables TEXT,
|
|
step_results TEXT,
|
|
FOREIGN KEY (workflow_id) REFERENCES workflows(workflow_id)
|
|
)
|
|
"""
|
|
)
|
|
|
|
cursor.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_name ON workflows(name)
|
|
"""
|
|
)
|
|
cursor.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_execution_workflow ON workflow_executions(workflow_id)
|
|
"""
|
|
)
|
|
cursor.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_execution_started ON workflow_executions(started_at)
|
|
"""
|
|
)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
def save_workflow(self, workflow: Workflow) -> str:
|
|
import hashlib
|
|
|
|
workflow_data = json.dumps(workflow.to_dict())
|
|
workflow_id = hashlib.sha256(workflow.name.encode()).hexdigest()[:16]
|
|
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
current_time = int(time.time())
|
|
tags_json = json.dumps(workflow.tags)
|
|
|
|
cursor.execute(
|
|
"""
|
|
INSERT OR REPLACE INTO workflows
|
|
(workflow_id, name, description, workflow_data, created_at, updated_at, tags)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
workflow_id,
|
|
workflow.name,
|
|
workflow.description,
|
|
workflow_data,
|
|
current_time,
|
|
current_time,
|
|
tags_json,
|
|
),
|
|
)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return workflow_id
|
|
|
|
def load_workflow(self, workflow_id: str) -> Optional[Workflow]:
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT workflow_data FROM workflows WHERE workflow_id = ?", (workflow_id,))
|
|
row = cursor.fetchone()
|
|
conn.close()
|
|
|
|
if row:
|
|
workflow_dict = json.loads(row[0])
|
|
return Workflow.from_dict(workflow_dict)
|
|
return None
|
|
|
|
def load_workflow_by_name(self, name: str) -> Optional[Workflow]:
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT workflow_data FROM workflows WHERE name = ?", (name,))
|
|
row = cursor.fetchone()
|
|
conn.close()
|
|
|
|
if row:
|
|
workflow_dict = json.loads(row[0])
|
|
return Workflow.from_dict(workflow_dict)
|
|
return None
|
|
|
|
def list_workflows(self, tag: Optional[str] = None) -> List[dict]:
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
if tag:
|
|
cursor.execute(
|
|
"""
|
|
SELECT workflow_id, name, description, execution_count, last_execution_at, tags
|
|
FROM workflows
|
|
WHERE tags LIKE ?
|
|
ORDER BY name
|
|
""",
|
|
(f'%"{tag}"%',),
|
|
)
|
|
else:
|
|
cursor.execute(
|
|
"""
|
|
SELECT workflow_id, name, description, execution_count, last_execution_at, tags
|
|
FROM workflows
|
|
ORDER BY name
|
|
"""
|
|
)
|
|
|
|
workflows = []
|
|
for row in cursor.fetchall():
|
|
workflows.append(
|
|
{
|
|
"workflow_id": row[0],
|
|
"name": row[1],
|
|
"description": row[2],
|
|
"execution_count": row[3],
|
|
"last_execution_at": row[4],
|
|
"tags": json.loads(row[5]) if row[5] else [],
|
|
}
|
|
)
|
|
|
|
conn.close()
|
|
return workflows
|
|
|
|
def delete_workflow(self, workflow_id: str) -> bool:
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("DELETE FROM workflows WHERE workflow_id = ?", (workflow_id,))
|
|
deleted = cursor.rowcount > 0
|
|
|
|
cursor.execute("DELETE FROM workflow_executions WHERE workflow_id = ?", (workflow_id,))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return deleted
|
|
|
|
def save_execution(
|
|
self, workflow_id: str, execution_context: "WorkflowExecutionContext"
|
|
) -> str:
|
|
import uuid
|
|
|
|
execution_id = str(uuid.uuid4())[:16]
|
|
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
started_at = (
|
|
int(execution_context.execution_log[0]["timestamp"])
|
|
if execution_context.execution_log
|
|
else int(time.time())
|
|
)
|
|
completed_at = int(time.time())
|
|
|
|
cursor.execute(
|
|
"""
|
|
INSERT INTO workflow_executions
|
|
(execution_id, workflow_id, started_at, completed_at, status, execution_log, variables, step_results)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
execution_id,
|
|
workflow_id,
|
|
started_at,
|
|
completed_at,
|
|
"completed",
|
|
json.dumps(execution_context.execution_log),
|
|
json.dumps(execution_context.variables),
|
|
json.dumps(execution_context.step_results),
|
|
),
|
|
)
|
|
|
|
cursor.execute(
|
|
"""
|
|
UPDATE workflows
|
|
SET execution_count = execution_count + 1,
|
|
last_execution_at = ?
|
|
WHERE workflow_id = ?
|
|
""",
|
|
(completed_at, workflow_id),
|
|
)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return execution_id
|
|
|
|
def get_execution_history(self, workflow_id: str, limit: int = 10) -> List[dict]:
|
|
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(
|
|
"""
|
|
SELECT execution_id, started_at, completed_at, status
|
|
FROM workflow_executions
|
|
WHERE workflow_id = ?
|
|
ORDER BY started_at DESC
|
|
LIMIT ?
|
|
""",
|
|
(workflow_id, limit),
|
|
)
|
|
|
|
executions = []
|
|
for row in cursor.fetchall():
|
|
executions.append(
|
|
{
|
|
"execution_id": row[0],
|
|
"started_at": row[1],
|
|
"completed_at": row[2],
|
|
"status": row[3],
|
|
}
|
|
)
|
|
|
|
conn.close()
|
|
return executions
|