loom-core 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
loom/web/main.py ADDED
@@ -0,0 +1,306 @@
1
+ """Loom Web Dashboard FastAPI Application
2
+
3
+ Main entry point for the web dashboard, providing REST APIs and server-sent events
4
+ for monitoring and managing Loom workflows.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from contextlib import asynccontextmanager
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from fastapi import FastAPI, Request
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import HTMLResponse
16
+ from fastapi.staticfiles import StaticFiles
17
+
18
+ from ..database.db import Database
19
+ from .api import events, graphs, logs, stats, tasks, workflows
20
+
21
+
22
+ @asynccontextmanager
23
+ async def lifespan(app: FastAPI):
24
+ """Application lifespan manager"""
25
+ # Startup: Initialize database
26
+ async with Database[Any, Any]() as db:
27
+ await db._init_db()
28
+ yield
29
+ # Shutdown: cleanup if needed
30
+
31
+
32
+ app = FastAPI(
33
+ title="Loom Workflow Dashboard",
34
+ description="""
35
+ ## Loom Workflow Orchestration API
36
+
37
+ **Loom** is a Python-based durable workflow orchestration engine inspired by Temporal and Durable Task Framework.
38
+ This API provides comprehensive monitoring and management capabilities for workflows, tasks, events, and logs.
39
+
40
+ ### Key Features
41
+ - **Event-sourced workflows** with automatic recovery and replay
42
+ - **Deterministic execution** with state reconstruction from events
43
+ - **Real-time monitoring** via Server-Sent Events (SSE)
44
+ - **Comprehensive pagination** and filtering across all endpoints
45
+ - **Task queue management** with retry policies and timeouts
46
+
47
+ ### API Structure
48
+ - **Workflows**: Manage workflow lifecycle and state
49
+ - **Tasks**: Monitor task execution and queue status
50
+ - **Events**: Audit trail with real-time streaming
51
+ - **Logs**: Application logging with structured output
52
+ - **Statistics**: System metrics and performance data
53
+
54
+ ### Authentication
55
+ Currently no authentication required (development mode).
56
+ Production deployments should implement appropriate security measures.
57
+
58
+ ### Real-time Updates
59
+ Use Server-Sent Events endpoints (`/stream/*`) for real-time monitoring.
60
+ These endpoints return `text/event-stream` with JSON payloads.
61
+
62
+ ### Rate Limiting
63
+ No rate limiting currently implemented.
64
+ Consider implementing in production environments.
65
+ """,
66
+ version="0.2.0",
67
+ docs_url="/docs",
68
+ redoc_url="/redoc",
69
+ lifespan=lifespan,
70
+ contact={
71
+ "name": "Loom Development Team",
72
+ "url": "https://github.com/yourusername/loom",
73
+ },
74
+ license_info={
75
+ "name": "MIT",
76
+ "url": "https://opensource.org/licenses/MIT",
77
+ },
78
+ servers=[
79
+ {"url": "http://localhost:8000", "description": "Development server"},
80
+ {
81
+ "url": "https://your-production-domain.com",
82
+ "description": "Production server",
83
+ },
84
+ ],
85
+ openapi_tags=[
86
+ {
87
+ "name": "Workflows",
88
+ "description": "Manage workflow definitions, execution state, and lifecycle operations.",
89
+ },
90
+ {
91
+ "name": "Tasks",
92
+ "description": "Monitor task execution, queue status, and retry policies.",
93
+ },
94
+ {
95
+ "name": "Events",
96
+ "description": "Event sourcing audit trail with real-time streaming capabilities.",
97
+ },
98
+ {
99
+ "name": "Logs",
100
+ "description": "Application logging with structured output and filtering.",
101
+ },
102
+ {
103
+ "name": "Statistics",
104
+ "description": "System metrics, performance data, and analytics.",
105
+ },
106
+ {
107
+ "name": "Health",
108
+ "description": "System health checks and status monitoring.",
109
+ },
110
+ ],
111
+ )
112
+
113
+ # CORS middleware for development
114
+ app.add_middleware(
115
+ CORSMiddleware,
116
+ allow_origins=[
117
+ "*",
118
+ ], # React dev server
119
+ allow_credentials=True,
120
+ allow_methods=["*"],
121
+ allow_headers=["*"],
122
+ )
123
+
124
+ # Import and register API routers
125
+
126
+ app.include_router(workflows.router, prefix="/api/workflows", tags=["Workflows"])
127
+ app.include_router(tasks.router, prefix="/api/tasks", tags=["Tasks"])
128
+ app.include_router(events.router, prefix="/api/events", tags=["Events"])
129
+ app.include_router(logs.router, prefix="/api/logs", tags=["Logs"])
130
+ app.include_router(stats.router, prefix="/api/stats", tags=["Statistics"])
131
+ app.include_router(graphs.router, prefix="/api/graphs", tags=["Graphs"])
132
+
133
+ # Mount static files for React UI (must be after API routes)
134
+ dist_dir = Path(__file__).parent / "dist"
135
+ if dist_dir.exists():
136
+ app.mount("/assets", StaticFiles(directory=str(dist_dir / "assets")), name="assets")
137
+
138
+
139
+ # Health check endpoints
140
+ @app.get(
141
+ "/health",
142
+ tags=["Health"],
143
+ summary="Health Check",
144
+ description="Simple health check endpoint to verify API availability.",
145
+ responses={
146
+ 200: {
147
+ "description": "API is healthy",
148
+ "content": {
149
+ "application/json": {
150
+ "example": {
151
+ "status": "healthy",
152
+ "timestamp": "2026-01-29T10:30:00Z",
153
+ }
154
+ }
155
+ },
156
+ }
157
+ },
158
+ )
159
+ async def health_check():
160
+ """Basic health check endpoint"""
161
+ from datetime import datetime, timezone
162
+
163
+ return {"status": "healthy", "timestamp": datetime.now(timezone.utc).isoformat()}
164
+
165
+
166
+ @app.get(
167
+ "/health/detailed",
168
+ tags=["Health"],
169
+ summary="Detailed Health Check",
170
+ description="""
171
+ Comprehensive health check including database connectivity and system status.
172
+
173
+ **Checks performed:**
174
+ - Database connection and schema validation
175
+ - Memory usage and performance metrics
176
+ - Active workflow and task counts
177
+ """,
178
+ responses={
179
+ 200: {
180
+ "description": "Detailed system health information",
181
+ "content": {
182
+ "application/json": {
183
+ "example": {
184
+ "status": "healthy",
185
+ "timestamp": "2026-01-29T10:30:00Z",
186
+ "database": {"status": "connected", "response_time_ms": 12},
187
+ "workflows": {"total": 150, "active": 23},
188
+ "tasks": {"pending": 5, "running": 2},
189
+ }
190
+ }
191
+ },
192
+ },
193
+ 503: {
194
+ "description": "System unhealthy",
195
+ "content": {
196
+ "application/json": {
197
+ "example": {
198
+ "status": "unhealthy",
199
+ "timestamp": "2026-01-29T10:30:00Z",
200
+ "errors": ["Database connection failed"],
201
+ }
202
+ }
203
+ },
204
+ },
205
+ },
206
+ )
207
+ async def detailed_health_check():
208
+ """Detailed health check with database connectivity"""
209
+ import time
210
+ from datetime import datetime, timezone
211
+
212
+ health_status = {
213
+ "status": "healthy",
214
+ "timestamp": datetime.now(timezone.utc).isoformat(),
215
+ "errors": [],
216
+ }
217
+
218
+ try:
219
+ # Check database connectivity
220
+ start_time = time.time()
221
+ async with Database[Any, Any]() as db:
222
+ # Simple query to test database
223
+ result = await db.fetchone("SELECT COUNT(*) as count FROM workflows")
224
+ workflow_count = result["count"] if result else 0
225
+
226
+ # Get task counts
227
+ pending_tasks = await db.fetchone(
228
+ "SELECT COUNT(*) as count FROM tasks WHERE status = 'PENDING'"
229
+ )
230
+ running_tasks = await db.fetchone(
231
+ "SELECT COUNT(*) as count FROM tasks WHERE status = 'RUNNING'"
232
+ )
233
+
234
+ response_time = (time.time() - start_time) * 1000
235
+
236
+ health_status.update(
237
+ {
238
+ "database": { # type: ignore
239
+ "status": "connected",
240
+ "response_time_ms": round(response_time, 2),
241
+ },
242
+ "workflows": { # type: ignore
243
+ "total": workflow_count,
244
+ "active": await _get_active_workflow_count(db),
245
+ },
246
+ "tasks": { # type: ignore
247
+ "pending": pending_tasks["count"] if pending_tasks else 0,
248
+ "running": running_tasks["count"] if running_tasks else 0,
249
+ },
250
+ }
251
+ )
252
+
253
+ except Exception as e:
254
+ health_status["status"] = "unhealthy"
255
+ health_status["errors"].append(f"Database connection failed: {str(e)}") # type: ignore
256
+
257
+ status_code = 200 if health_status["status"] == "healthy" else 503
258
+ from fastapi import Response
259
+
260
+ return Response(
261
+ content=json.dumps(health_status),
262
+ status_code=status_code,
263
+ media_type="application/json",
264
+ )
265
+
266
+
267
+ async def _get_active_workflow_count(db: Database[Any, Any]) -> int:
268
+ """Get count of active workflows (RUNNING or PENDING)"""
269
+ result = await db.fetchone(
270
+ "SELECT COUNT(*) as count FROM workflows WHERE status IN ('RUNNING', 'PENDING')"
271
+ )
272
+ return result["count"] if result else 0
273
+
274
+
275
+ @app.get("/", include_in_schema=False)
276
+ async def root(request: Request):
277
+ """Serve React UI with API URL configuration"""
278
+ dist_dir = Path(__file__).parent / "dist"
279
+ index_file = dist_dir / "index.html"
280
+
281
+ # Check if React build exists
282
+ if not index_file.exists():
283
+ return {"message": "Loom Dashboard API", "docs": "/docs", "note": "React UI not built"}
284
+
285
+ # Read index.html
286
+ with open(index_file, "r", encoding="utf-8") as f:
287
+ html_content = f.read()
288
+
289
+ # Inject API URL configuration
290
+ api_url = str(request.base_url).rstrip('/')
291
+ config_script = f"""
292
+ <script>
293
+ window.__API_URL__ = "{api_url}";
294
+ </script>
295
+ """
296
+
297
+ # Insert before </head> tag
298
+ html_content = html_content.replace("</head>", f"{config_script}</head>")
299
+
300
+ return HTMLResponse(content=html_content)
301
+
302
+
303
+ @app.get("/health")
304
+ async def health():
305
+ """Health check endpoint"""
306
+ return {"status": "healthy", "service": "loom-dashboard"}