aegis-stack 0.1.0__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.

Potentially problematic release.


This version of aegis-stack might be problematic. Click here for more details.

Files changed (103) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +374 -0
  3. aegis/core/CLAUDE.md +365 -0
  4. aegis/core/__init__.py +6 -0
  5. aegis/core/components.py +115 -0
  6. aegis/core/dependency_resolver.py +119 -0
  7. aegis/core/template_generator.py +163 -0
  8. aegis/templates/CLAUDE.md +306 -0
  9. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +27 -0
  10. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +172 -0
  11. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  12. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +70 -0
  13. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +127 -0
  14. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  15. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +211 -0
  16. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  17. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  18. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  19. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py +321 -0
  20. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py +638 -0
  21. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py +41 -0
  22. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  23. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  24. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +134 -0
  25. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +247 -0
  26. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +14 -0
  27. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/tasks.py.j2 +596 -0
  28. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +133 -0
  29. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +16 -0
  30. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  31. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  32. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  33. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  34. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  35. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +190 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +0 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/__init__.py +1 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/theme.py +46 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py +687 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py +138 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md +213 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +78 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +48 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +41 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +36 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +119 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +526 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +32 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +279 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +119 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +60 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py +67 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +85 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +61 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +661 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +269 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1105 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +169 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +195 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +414 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +156 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +104 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +239 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +76 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +81 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +376 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +633 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +665 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +602 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +96 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +224 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +50 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  100. aegis_stack-0.1.0.dist-info/METADATA +114 -0
  101. aegis_stack-0.1.0.dist-info/RECORD +103 -0
  102. aegis_stack-0.1.0.dist-info/WHEEL +4 -0
  103. aegis_stack-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,138 @@
1
+ """
2
+ Scheduler component for {{ cookiecutter.project_name }}.
3
+
4
+ Simple, explicit job scheduling - just import functions and schedule them.
5
+ Add your own jobs by importing service functions and calling scheduler.add_job().
6
+ """
7
+
8
+ import asyncio
9
+
10
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
11
+
12
+ from app.core.log import logger
13
+ from app.services.system.health import check_system_status, register_health_check
14
+ from app.services.system.models import ComponentStatus, ComponentStatusType
15
+
16
+ # Global scheduler instance for health checking
17
+ _scheduler: AsyncIOScheduler | None = None
18
+
19
+
20
+ async def _check_scheduler_health() -> ComponentStatus:
21
+ """Health check for the scheduler component."""
22
+ global _scheduler
23
+
24
+ if _scheduler is None:
25
+ return ComponentStatus(
26
+ name="scheduler",
27
+ status=ComponentStatusType.UNHEALTHY,
28
+ message="Scheduler not initialized",
29
+ response_time_ms=None,
30
+ )
31
+
32
+ if not _scheduler.running:
33
+ return ComponentStatus(
34
+ name="scheduler",
35
+ status=ComponentStatusType.UNHEALTHY,
36
+ message="Scheduler is not running",
37
+ response_time_ms=None,
38
+ )
39
+
40
+ # Get scheduler statistics
41
+ jobs = _scheduler.get_jobs()
42
+ job_count = len(jobs)
43
+
44
+ # Check if scheduler is responsive
45
+ try:
46
+ state = _scheduler.state
47
+ healthy = state == 1 # STATE_RUNNING = 1
48
+
49
+ status = (
50
+ ComponentStatusType.HEALTHY
51
+ if healthy
52
+ else ComponentStatusType.UNHEALTHY
53
+ )
54
+ return ComponentStatus(
55
+ name="scheduler",
56
+ status=status,
57
+ message=f"Scheduler running with {job_count} jobs",
58
+ response_time_ms=None,
59
+ metadata={
60
+ "job_count": job_count,
61
+ "state": state,
62
+ "jobs": [{"id": job.id, "name": job.name} for job in jobs[:5]],
63
+ },
64
+ )
65
+ except Exception as e:
66
+ return ComponentStatus(
67
+ name="scheduler",
68
+ status=ComponentStatusType.UNHEALTHY,
69
+ message=f"Scheduler health check failed: {str(e)}",
70
+ response_time_ms=None,
71
+ )
72
+
73
+
74
+ def create_scheduler() -> AsyncIOScheduler:
75
+ """Create and configure the scheduler with all jobs."""
76
+ scheduler = AsyncIOScheduler()
77
+
78
+ # ============================================================================
79
+ # JOB SCHEDULE CONFIGURATION
80
+ # Add your scheduled jobs below - import service functions and schedule them!
81
+ # ============================================================================
82
+
83
+ # System health check every 5 minutes
84
+ # Adjust this interval based on your monitoring needs:
85
+ # - Production systems: 1-5 minutes
86
+ # - Development: 10-15 minutes
87
+ # - High-availability: 30 seconds - 1 minute
88
+ scheduler.add_job(
89
+ check_system_status,
90
+ trigger="interval",
91
+ minutes=1,
92
+ id="system_status_check",
93
+ name="System Health Check",
94
+ max_instances=1, # Prevent overlapping health checks
95
+ coalesce=True, # Coalesce missed executions
96
+ )
97
+
98
+ # Add your own scheduled jobs here by importing service functions
99
+ # and calling scheduler.add_job() with your custom business logic
100
+
101
+ return scheduler
102
+
103
+
104
+ async def run_scheduler() -> None:
105
+ """Main scheduler runner with lifecycle management."""
106
+ global _scheduler
107
+
108
+ logger.info("🕒 Starting {{ cookiecutter.project_name }} Scheduler")
109
+
110
+ scheduler = create_scheduler()
111
+ _scheduler = scheduler # Store for health checking
112
+
113
+ try:
114
+ scheduler.start()
115
+ logger.info("✅ Scheduler started successfully")
116
+ logger.info(f"📋 {len(scheduler.get_jobs())} jobs scheduled:")
117
+
118
+ for job in scheduler.get_jobs():
119
+ logger.info(f" • {job.name} - {job.trigger}")
120
+
121
+ # Register scheduler health check with the system health service
122
+ register_health_check("scheduler", _check_scheduler_health)
123
+ logger.info("🩺 Scheduler health check registered")
124
+
125
+ # Keep the scheduler running
126
+ while True:
127
+ await asyncio.sleep(1)
128
+
129
+ except KeyboardInterrupt:
130
+ logger.info("🛑 Received shutdown signal")
131
+ except Exception as e:
132
+ logger.error(f"❌ Scheduler error: {e}")
133
+ raise
134
+ finally:
135
+ if scheduler.running:
136
+ scheduler.shutdown()
137
+ logger.info("✅ Scheduler stopped gracefully")
138
+ _scheduler = None # Clear global reference
@@ -0,0 +1,213 @@
1
+ # Worker Component Development Guide
2
+
3
+ This guide covers arq worker architecture patterns and development for the Aegis Stack worker component.
4
+
5
+ ## Worker Architecture (arq)
6
+
7
+ Aegis Stack uses pure **arq patterns** without custom wrappers, following native arq CLI and configuration patterns.
8
+
9
+ ### Worker Configuration Structure
10
+
11
+ Each worker queue has its own `WorkerSettings` class:
12
+ - `app/components/worker/queues/system.py` - System maintenance worker
13
+ - `app/components/worker/queues/load_test.py` - Load testing worker
14
+ - `app/components/worker/queues/media.py` - Media processing worker
15
+
16
+ ### Worker Services in Docker
17
+
18
+ Workers run as separate Docker services with specific names:
19
+ - **`worker-system`** - System maintenance tasks (low concurrency, high reliability)
20
+ - **`worker-load-test`** - High-concurrency load testing (up to 50 concurrent jobs)
21
+ - **`worker-media`** - File/media processing (commented out by default)
22
+
23
+ ## Adding Worker Tasks
24
+
25
+ ### 1. Create Task Functions
26
+ Tasks are pure async functions in `app/components/worker/tasks/`:
27
+ ```python
28
+ # app/components/worker/tasks/my_tasks.py
29
+ async def my_background_task() -> dict[str, str]:
30
+ """My custom background task."""
31
+ logger.info("Running my background task")
32
+
33
+ # Your task logic here
34
+ await asyncio.sleep(1) # Simulate work
35
+
36
+ return {
37
+ "status": "completed",
38
+ "timestamp": datetime.now(UTC).isoformat(),
39
+ "task": "my_background_task"
40
+ }
41
+ ```
42
+
43
+ ### 2. Register with Worker Queue
44
+ Import and add to the appropriate `WorkerSettings`:
45
+ ```python
46
+ # app/components/worker/queues/system.py
47
+ from app.components.worker.tasks.my_tasks import my_background_task
48
+
49
+ class WorkerSettings:
50
+ functions = [
51
+ system_health_check,
52
+ cleanup_temp_files,
53
+ my_background_task, # Add your task here
54
+ ]
55
+
56
+ # Standard arq configuration
57
+ redis_settings = RedisSettings.from_dsn(settings.REDIS_URL)
58
+ queue_name = "arq:queue:system"
59
+ max_jobs = 15
60
+ job_timeout = 300
61
+ ```
62
+
63
+ ## Native arq CLI Usage
64
+
65
+ ### Worker Health Checks
66
+ ```bash
67
+ # Check if workers can connect to Redis and validate configuration
68
+ uv run python -m arq app.components.worker.queues.system.WorkerSettings --check
69
+ uv run python -m arq app.components.worker.queues.load_test.WorkerSettings --check
70
+ uv run python -m arq app.components.worker.queues.media.WorkerSettings --check
71
+ ```
72
+
73
+ ### Local Worker Development
74
+ ```bash
75
+ # Run worker locally with auto-reload for development
76
+ uv run python -m arq app.components.worker.queues.system.WorkerSettings --watch app/
77
+
78
+ # Run worker in burst mode (process all jobs and exit)
79
+ uv run python -m arq app.components.worker.queues.system.WorkerSettings --burst
80
+ ```
81
+
82
+ ### Worker Configuration in Health Checks
83
+
84
+ The health system reads worker configuration from `app/core/config.py` but workers themselves use their own `WorkerSettings` classes:
85
+
86
+ ```python
87
+ # Health system reads this for monitoring
88
+ WORKER_QUEUES: dict[str, dict[str, Any]] = {
89
+ "system": {
90
+ "description": "System maintenance and monitoring tasks",
91
+ "max_jobs": 15,
92
+ "timeout_seconds": 300,
93
+ "queue_name": "arq:queue:system",
94
+ },
95
+ "load_test": {
96
+ "description": "Load testing and performance testing",
97
+ "max_jobs": 50,
98
+ "timeout_seconds": 60,
99
+ "queue_name": "arq:queue:load_test",
100
+ }
101
+ }
102
+
103
+ # But workers use their own WorkerSettings classes for actual configuration
104
+ ```
105
+
106
+ ## Key Differences from Custom Worker Systems
107
+
108
+ ### ✅ What We Do (Pure arq):
109
+ - Use native arq CLI: `python -m arq WorkerSettings`
110
+ - Standard `WorkerSettings` classes with `functions` list
111
+ - Direct task imports into worker configurations
112
+ - Native arq health checking and monitoring
113
+
114
+ ### ❌ What We Don't Do (Avoided custom patterns):
115
+ - Custom worker wrapper classes
116
+ - Central worker registry systems
117
+ - Custom CLI commands for workers
118
+ - Configuration-driven task discovery
119
+
120
+ This approach keeps workers transparent and lets developers use arq exactly as documented in the official arq documentation.
121
+
122
+ ## Docker Worker Debugging Commands
123
+
124
+ ### View Worker Logs
125
+ ```bash
126
+ # View specific worker logs
127
+ docker compose logs worker-system # System worker logs
128
+ docker compose logs worker-load-test # Load test worker logs
129
+ docker compose logs -f worker-system # Follow system worker in real-time
130
+ docker compose logs -f worker-load-test # Follow load test worker in real-time
131
+
132
+ # View all workers at once
133
+ docker compose logs -f worker-system worker-load-test
134
+
135
+ # Filter for errors in specific workers
136
+ docker compose logs worker-load-test | grep "ERROR\|failed\|TypeError"
137
+
138
+ # Monitor worker processes and resources
139
+ docker compose exec worker-system ps aux # Check system worker processes
140
+ docker compose exec worker-load-test ps aux # Check load test worker processes
141
+ docker stats worker-system worker-load-test # Monitor resource usage
142
+ docker compose restart worker-system # Restart specific worker
143
+ ```
144
+
145
+ ### Essential Docker Log Monitoring
146
+
147
+ **Check Worker Logs for Load Test Issues:**
148
+ ```bash
149
+ # View real-time worker logs
150
+ docker compose logs -f worker
151
+
152
+ # Check specific container logs
153
+ docker logs <container-id>
154
+
155
+ # View logs with timestamps
156
+ docker compose logs --timestamps worker
157
+
158
+ # Search logs for specific errors
159
+ docker compose logs worker | grep "TypeError\|failed"
160
+
161
+ # Check all service logs
162
+ docker compose logs -f
163
+ ```
164
+
165
+ **Load Test Debugging Workflow:**
166
+ 1. **Run Load Test**: `uv run full-stack load-test run --type io_simulation --tasks 10`
167
+ 2. **Monitor Worker Logs**: `docker compose logs -f worker-load-test` (in separate terminal)
168
+ 3. **Look for**:
169
+ - `TypeError` messages indicating parameter mismatches
170
+ - Task completion vs failure counts (`j_complete=X j_failed=Y`)
171
+ - Task execution times and errors
172
+ - Redis connection issues
173
+
174
+ **Common Error Patterns:**
175
+ - `TypeError: function() got an unexpected keyword argument 'param'` - Parameter mismatch between orchestrator and task function
176
+ - `j_failed=X` increasing rapidly - Worker tasks failing due to code issues
177
+ - `Redis connection failed` - Infrastructure connectivity problems
178
+ - `delayed=X.XXs` - Queue saturation or worker overload
179
+
180
+ **System Health Verification:**
181
+ ```bash
182
+ # Check all containers
183
+ docker compose ps
184
+
185
+ # Check system health via API
186
+ uv run full-stack health status --detailed
187
+
188
+ # Monitor Redis connection
189
+ docker compose logs redis
190
+ ```
191
+
192
+ ## Worker Development Best Practices
193
+
194
+ ### Task Design Patterns
195
+ 1. **Pure Functions** - Tasks should be self-contained with minimal dependencies
196
+ 2. **Error Handling** - Always include try/catch with proper logging
197
+ 3. **Return Values** - Return structured data for monitoring and debugging
198
+ 4. **Timeouts** - Set appropriate timeouts for different task types
199
+ 5. **Retry Logic** - Use arq's built-in retry mechanisms
200
+
201
+ ### Queue Management
202
+ 1. **Separate Concerns** - Use different queues for different types of work
203
+ 2. **Concurrency Limits** - Set appropriate max_jobs for each queue type
204
+ 3. **Priority Queues** - Use different queues for different priorities
205
+ 4. **Dead Letter Queues** - Monitor failed jobs and implement recovery
206
+
207
+ ### Monitoring and Observability
208
+ 1. **Structured Logging** - Use structured logs for easy parsing
209
+ 2. **Metrics Collection** - Track task execution times and success rates
210
+ 3. **Health Checks** - Implement health checks for worker availability
211
+ 4. **Alerting** - Set up alerts for queue depth and failure rates
212
+
213
+ This approach ensures workers are maintainable, debuggable, and follow established patterns.
@@ -0,0 +1,6 @@
1
+ """
2
+ Worker component for background task processing.
3
+
4
+ This component handles asynchronous background tasks using arq (Redis-based queues).
5
+ Tasks are organized into priority queues (high, medium, low) and by functional area.
6
+ """
@@ -0,0 +1,30 @@
1
+ """
2
+ Worker component constants.
3
+
4
+ This module contains constants specific to the worker component,
5
+ keeping them separate from global application constants.
6
+ """
7
+
8
+ from enum import Enum
9
+
10
+
11
+ class TaskNames:
12
+ """Worker task function names - must match actual function names in code."""
13
+
14
+ # Orchestrator
15
+ LOAD_TEST_ORCHESTRATOR = "load_test_orchestrator"
16
+
17
+ # Load test tasks
18
+ CPU_INTENSIVE_TASK = "cpu_intensive_task"
19
+ IO_SIMULATION_TASK = "io_simulation_task"
20
+ MEMORY_OPERATIONS_TASK = "memory_operations_task"
21
+ FAILURE_TESTING_TASK = "failure_testing_task"
22
+
23
+
24
+ class LoadTestTypes(Enum):
25
+ """Load test type identifiers for task selection."""
26
+
27
+ CPU_INTENSIVE = "cpu_intensive"
28
+ IO_SIMULATION = "io_simulation"
29
+ MEMORY_OPERATIONS = "memory_operations"
30
+ FAILURE_TESTING = "failure_testing"
@@ -0,0 +1,78 @@
1
+ """
2
+ Worker pool management for client-side task enqueueing.
3
+
4
+ This module provides Redis connection pooling and caching for enqueueing tasks
5
+ to worker queues. Separated from worker management to allow clean architectural
6
+ separation between client-side enqueueing and worker-side processing.
7
+ """
8
+
9
+ from arq import create_pool
10
+ from arq.connections import ArqRedis, RedisSettings
11
+
12
+ from app.core.config import get_default_queue, settings
13
+ from app.core.log import logger
14
+
15
+ # Global pool cache to avoid creating new Redis connections repeatedly
16
+ _pool_cache: dict[str, ArqRedis] = {}
17
+
18
+
19
+ async def get_queue_pool(queue_type: str | None = None) -> tuple[ArqRedis, str]:
20
+ """
21
+ Get Redis pool for enqueuing tasks to specific functional queue.
22
+
23
+ Uses connection pooling to avoid creating new Redis connections repeatedly.
24
+
25
+ Args:
26
+ queue_type: Functional queue type (defaults to configured default queue)
27
+
28
+ Returns:
29
+ Tuple of (pool, queue_name) for enqueueing tasks
30
+ """
31
+ # Use configured default queue if not specified
32
+ if queue_type is None:
33
+ queue_type = get_default_queue()
34
+
35
+ from app.core.config import is_valid_queue, get_available_queues
36
+
37
+ if not is_valid_queue(queue_type):
38
+ available = get_available_queues()
39
+ raise ValueError(f"Invalid queue type '{queue_type}'. Available: {available}")
40
+
41
+ from app.components.worker.registry import get_queue_metadata
42
+ queue_name = get_queue_metadata(queue_type)["queue_name"]
43
+
44
+ # Check cache first to avoid creating new Redis connections
45
+ cache_key = f"{queue_type}_{settings.REDIS_URL}"
46
+
47
+ if cache_key in _pool_cache:
48
+ # Reuse existing pool
49
+ cached_pool = _pool_cache[cache_key]
50
+ try:
51
+ # Test if pool is still valid by doing a quick ping
52
+ await cached_pool.ping()
53
+ return cached_pool, queue_name
54
+ except Exception:
55
+ # Pool is stale, remove from cache and create new one
56
+ logger.debug(f"Removing stale pool from cache: {cache_key}")
57
+ del _pool_cache[cache_key]
58
+
59
+ # Create new Redis pool and cache it
60
+ redis_settings = RedisSettings.from_dsn(settings.REDIS_URL)
61
+ pool = await create_pool(redis_settings)
62
+ _pool_cache[cache_key] = pool
63
+
64
+ logger.debug(f"Created and cached new Redis pool: {cache_key}")
65
+ return pool, queue_name
66
+
67
+
68
+ async def clear_pool_cache() -> None:
69
+ """Clear all cached pools. Use during shutdown or for testing."""
70
+ for cache_key, pool in _pool_cache.items():
71
+ try:
72
+ await pool.aclose()
73
+ logger.debug(f"Closed cached pool: {cache_key}")
74
+ except Exception as e:
75
+ logger.warning(f"Error closing cached pool {cache_key}: {e}")
76
+
77
+ _pool_cache.clear()
78
+ logger.info("Pool cache cleared")
@@ -0,0 +1 @@
1
+ """Worker queue configurations using native arq WorkerSettings."""
@@ -0,0 +1,48 @@
1
+ """
2
+ Load test worker queue configuration.
3
+
4
+ Handles load testing orchestration and synthetic workload tasks using native arq
5
+ patterns.
6
+ """
7
+
8
+ from arq.connections import RedisSettings
9
+
10
+ from app.core.config import settings
11
+
12
+ # Import load test tasks
13
+ from app.components.worker.tasks.load_tasks import (
14
+ cpu_intensive_task,
15
+ failure_testing_task,
16
+ io_simulation_task,
17
+ memory_operations_task,
18
+ )
19
+ from app.components.worker.tasks.system_tasks import (
20
+ load_test_orchestrator,
21
+ )
22
+
23
+
24
+ class WorkerSettings:
25
+ """Load testing worker configuration."""
26
+
27
+ # Human-readable description
28
+ description = "Load testing and performance testing"
29
+
30
+ # Task functions for this queue
31
+ functions = [
32
+ # Load test orchestrator
33
+ load_test_orchestrator,
34
+ # Synthetic workload tasks
35
+ cpu_intensive_task,
36
+ io_simulation_task,
37
+ memory_operations_task,
38
+ failure_testing_task,
39
+ ]
40
+
41
+ # arq configuration
42
+ redis_settings = RedisSettings.from_dsn(settings.REDIS_URL)
43
+ queue_name = "arq:queue:load_test"
44
+ max_jobs = 50 # High concurrency for load testing
45
+ job_timeout = 60 # Quick tasks
46
+ keep_result = settings.WORKER_KEEP_RESULT_SECONDS
47
+ max_tries = settings.WORKER_MAX_TRIES
48
+ health_check_interval = 30
@@ -0,0 +1,41 @@
1
+ """
2
+ Media worker queue configuration.
3
+
4
+ Handles image processing, file operations, and media transformations using native
5
+ arq patterns.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from arq.connections import RedisSettings
11
+
12
+ from app.core.config import settings
13
+
14
+ # Import media tasks (when available)
15
+ # from app.components.worker.tasks.media_tasks import (
16
+ # image_resize,
17
+ # video_encode,
18
+ # file_convert,
19
+ # )
20
+
21
+
22
+ class WorkerSettings:
23
+ """Media processing worker configuration."""
24
+
25
+ # Human-readable description
26
+ description = "Image and file processing"
27
+
28
+ # Task functions for this queue
29
+ functions: list[Any] = [
30
+ # Media processing tasks will be added here
31
+ # Example: image_resize, video_encode, file_convert
32
+ ]
33
+
34
+ # arq configuration
35
+ redis_settings = RedisSettings.from_dsn(settings.REDIS_URL)
36
+ queue_name = "arq:queue:media"
37
+ max_jobs = 10 # I/O-bound file operations
38
+ job_timeout = 600 # 10 minutes - file processing can take time
39
+ keep_result = settings.WORKER_KEEP_RESULT_SECONDS
40
+ max_tries = settings.WORKER_MAX_TRIES
41
+ health_check_interval = 30
@@ -0,0 +1,36 @@
1
+ """
2
+ System worker queue configuration.
3
+
4
+ Handles system maintenance and monitoring tasks using native arq patterns.
5
+ """
6
+
7
+ from arq.connections import RedisSettings
8
+
9
+ from app.core.config import settings
10
+
11
+ # Import system tasks
12
+ from app.components.worker.tasks.simple_system_tasks import (
13
+ system_health_check,
14
+ cleanup_temp_files,
15
+ )
16
+
17
+ class WorkerSettings:
18
+ """System maintenance worker configuration."""
19
+
20
+ # Human-readable description
21
+ description = "System maintenance and monitoring tasks"
22
+
23
+ # Task functions for this queue
24
+ functions = [
25
+ system_health_check,
26
+ cleanup_temp_files,
27
+ ]
28
+
29
+ # arq configuration
30
+ redis_settings = RedisSettings.from_dsn(settings.REDIS_URL)
31
+ queue_name = "arq:queue:system"
32
+ max_jobs = 15 # Moderate concurrency for administrative operations
33
+ job_timeout = 300 # 5 minutes
34
+ keep_result = settings.WORKER_KEEP_RESULT_SECONDS
35
+ max_tries = settings.WORKER_MAX_TRIES
36
+ health_check_interval = 30