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,414 @@
1
+ # Scheduler Component
2
+
3
+ The Scheduler Component provides background task processing and cron job capabilities using [APScheduler](https://apscheduler.readthedocs.io/).
4
+
5
+ ## Overview
6
+
7
+ {{ cookiecutter.project_name }} includes a scheduler component that runs as an independent service, enabling:
8
+
9
+ - **Background job processing**
10
+ - **Cron-style scheduled tasks**
11
+ - **Async job execution**
12
+ - **Job persistence and recovery**
13
+ - **Real-time job monitoring**
14
+
15
+ ## Architecture
16
+
17
+ ```mermaid
18
+ graph TB
19
+ subgraph "{{ cookiecutter.project_name }} Architecture"
20
+ API[FastAPI Backend<br/>Port 8000]
21
+ Scheduler[Scheduler Service<br/>Background Jobs]
22
+ Jobs[(Job Queue<br/>In-Memory)]
23
+ end
24
+
25
+ subgraph "Job Types"
26
+ SystemJobs[System Maintenance<br/>Every 6 hours]
27
+ CustomJobs[Custom Jobs<br/>User-defined]
28
+ end
29
+
30
+ API -.-> Scheduler
31
+ Scheduler --> Jobs
32
+ Jobs --> SystemJobs
33
+ Jobs --> CustomJobs
34
+
35
+ style Scheduler fill:#f9f,stroke:#333,stroke-width:2px
36
+ style Jobs fill:#bbf,stroke:#333,stroke-width:2px
37
+ ```
38
+
39
+ ## Running the Scheduler
40
+
41
+ The scheduler runs as a Docker service. Use these commands:
42
+
43
+ ### Docker Deployment
44
+ ```bash
45
+ # Run only scheduler service
46
+ docker compose --profile dev up scheduler
47
+
48
+ # Run all services including scheduler
49
+ make run
50
+
51
+ # Or use docker compose directly
52
+ docker compose --profile dev up
53
+ ```
54
+
55
+ ## Job Configuration
56
+
57
+ The scheduler is configured in `app/components/scheduler/main.py`:
58
+
59
+ ```python
60
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
61
+ from apscheduler.triggers.cron import CronTrigger
62
+
63
+ def create_scheduler() -> AsyncIOScheduler:
64
+ scheduler = AsyncIOScheduler()
65
+
66
+ # Add your jobs here
67
+ scheduler.add_job(
68
+ func=system_maintenance_job,
69
+ trigger=CronTrigger(hour=2), # Run daily at 2 AM
70
+ id="system_maintenance",
71
+ name="System Maintenance",
72
+ replace_existing=True
73
+ )
74
+
75
+ return scheduler
76
+ ```
77
+
78
+ ## Adding Custom Jobs
79
+
80
+ ### 1. Create Job Function
81
+ Create job functions in `app/services/`:
82
+
83
+ ```python
84
+ # app/services/my_jobs.py
85
+ import asyncio
86
+ from app.core.log import logger
87
+
88
+ async def process_daily_reports():
89
+ """Generate daily reports."""
90
+ logger.info("Starting daily report generation")
91
+
92
+ # Your job logic here
93
+ await asyncio.sleep(1) # Simulate work
94
+
95
+ logger.info("Daily reports completed")
96
+
97
+ async def cleanup_old_files():
98
+ """Clean up old temporary files."""
99
+ logger.info("Starting file cleanup")
100
+
101
+ # Your cleanup logic here
102
+
103
+ logger.info("File cleanup completed")
104
+ ```
105
+
106
+ ### 2. Register Jobs in Scheduler
107
+ Add jobs to the scheduler configuration:
108
+
109
+ ```python
110
+ # app/components/scheduler/main.py
111
+ from app.services.my_jobs import process_daily_reports, cleanup_old_files
112
+
113
+ def create_scheduler() -> AsyncIOScheduler:
114
+ scheduler = AsyncIOScheduler()
115
+
116
+ # Daily report at 6 AM
117
+ scheduler.add_job(
118
+ func=process_daily_reports,
119
+ trigger=CronTrigger(hour=6, minute=0),
120
+ id="daily_reports",
121
+ name="Daily Report Generation"
122
+ )
123
+
124
+ # Weekly cleanup on Sundays at midnight
125
+ scheduler.add_job(
126
+ func=cleanup_old_files,
127
+ trigger=CronTrigger(day_of_week="sun", hour=0, minute=0),
128
+ id="weekly_cleanup",
129
+ name="Weekly File Cleanup"
130
+ )
131
+
132
+ return scheduler
133
+ ```
134
+
135
+ ## Job Scheduling Options
136
+
137
+ ### Cron-Style Triggers
138
+ ```python
139
+ from apscheduler.triggers.cron import CronTrigger
140
+
141
+ # Every day at 2:30 AM
142
+ CronTrigger(hour=2, minute=30)
143
+
144
+ # Every Monday at 9:00 AM
145
+ CronTrigger(day_of_week="mon", hour=9, minute=0)
146
+
147
+ # Every 15 minutes
148
+ CronTrigger(minute="*/15")
149
+
150
+ # First day of every month at midnight
151
+ CronTrigger(day=1, hour=0, minute=0)
152
+ ```
153
+
154
+ ### Interval Triggers
155
+ ```python
156
+ from apscheduler.triggers.interval import IntervalTrigger
157
+
158
+ # Every 5 minutes
159
+ IntervalTrigger(minutes=5)
160
+
161
+ # Every 2 hours
162
+ IntervalTrigger(hours=2)
163
+
164
+ # Every 30 seconds
165
+ IntervalTrigger(seconds=30)
166
+ ```
167
+
168
+ ### One-Time Jobs
169
+ ```python
170
+ from datetime import datetime, timedelta
171
+
172
+ # Run once in 1 hour
173
+ scheduler.add_job(
174
+ func=one_time_task,
175
+ trigger="date",
176
+ run_date=datetime.now() + timedelta(hours=1),
177
+ id="one_time_task"
178
+ )
179
+ ```
180
+
181
+ ## Job Management
182
+
183
+ ### Listing Jobs
184
+ ```python
185
+ # Get all scheduled jobs
186
+ jobs = scheduler.get_jobs()
187
+ for job in jobs:
188
+ print(f"Job: {job.name}, Next run: {job.next_run_time}")
189
+ ```
190
+
191
+ ### Modifying Jobs
192
+ ```python
193
+ # Pause a job
194
+ scheduler.pause_job("daily_reports")
195
+
196
+ # Resume a job
197
+ scheduler.resume_job("daily_reports")
198
+
199
+ # Remove a job
200
+ scheduler.remove_job("old_job_id")
201
+
202
+ # Modify job schedule
203
+ scheduler.modify_job("daily_reports", hour=7) # Change to 7 AM
204
+ ```
205
+
206
+ ## Error Handling
207
+
208
+ ### Job Error Handling
209
+ ```python
210
+ async def robust_job():
211
+ """A job with proper error handling."""
212
+ try:
213
+ logger.info("Starting robust job")
214
+
215
+ # Job logic here
216
+ result = await some_async_operation()
217
+
218
+ logger.info(f"Job completed successfully: {result}")
219
+
220
+ except Exception as e:
221
+ logger.error(f"Job failed: {str(e)}", exc_info=True)
222
+ # Optionally send alerts or retry logic
223
+ ```
224
+
225
+ ### Scheduler Error Listeners
226
+ ```python
227
+ def job_listener(event):
228
+ """Listen for job events."""
229
+ if event.exception:
230
+ logger.error(f"Job {event.job_id} crashed: {event.exception}")
231
+ else:
232
+ logger.info(f"Job {event.job_id} executed successfully")
233
+
234
+ # Add listener to scheduler
235
+ scheduler.add_listener(job_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
236
+ ```
237
+
238
+ ## Monitoring Jobs
239
+
240
+ ### Health Checks
241
+ The scheduler health is included in system health checks:
242
+
243
+ ```bash
244
+ {{ cookiecutter.project_slug }} health check --detailed
245
+ ```
246
+
247
+ Shows scheduler status, active jobs, and next execution times.
248
+
249
+ ### Job Logging
250
+ All job executions are logged with structured logging:
251
+
252
+ ```json
253
+ {
254
+ "timestamp": "2024-01-01T02:00:00Z",
255
+ "level": "INFO",
256
+ "logger": "scheduler",
257
+ "message": "Job executed successfully",
258
+ "job_id": "system_maintenance",
259
+ "execution_time_ms": 1250
260
+ }
261
+ ```
262
+
263
+ ### Custom Job Metrics
264
+ Add metrics to your jobs:
265
+
266
+ ```python
267
+ import time
268
+ from app.core.log import logger
269
+
270
+ async def monitored_job():
271
+ start_time = time.time()
272
+
273
+ try:
274
+ # Job work here
275
+ await do_work()
276
+
277
+ execution_time = time.time() - start_time
278
+ logger.info(f"Job completed in {execution_time:.2f}s")
279
+
280
+ except Exception as e:
281
+ execution_time = time.time() - start_time
282
+ logger.error(f"Job failed after {execution_time:.2f}s: {e}")
283
+ raise
284
+ ```
285
+
286
+ ## Configuration
287
+
288
+ ### Scheduler Settings
289
+ Configure scheduler behavior in `app/core/config.py`:
290
+
291
+ ```python
292
+ class Settings(BaseSettings):
293
+ # Scheduler configuration
294
+ scheduler_timezone: str = "UTC"
295
+ scheduler_max_workers: int = 10
296
+ scheduler_job_defaults: dict = {
297
+ "coalesce": False,
298
+ "max_instances": 3
299
+ }
300
+ ```
301
+
302
+ ### Job Persistence
303
+ For production deployments, consider using persistent job storage:
304
+
305
+ ```python
306
+ from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
307
+
308
+ jobstores = {
309
+ 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
310
+ }
311
+
312
+ scheduler = AsyncIOScheduler(jobstores=jobstores)
313
+ ```
314
+
315
+ ## Best Practices
316
+
317
+ ### 1. Keep Jobs Small and Focused
318
+ ```python
319
+ # Good: Small, focused job
320
+ async def send_daily_summary():
321
+ await generate_summary()
322
+ await send_email()
323
+
324
+ # Better: Break into smaller jobs
325
+ async def generate_daily_summary():
326
+ return await generate_summary()
327
+
328
+ async def send_summary_email():
329
+ summary = await get_cached_summary()
330
+ await send_email(summary)
331
+ ```
332
+
333
+ ### 2. Use Proper Async Patterns
334
+ ```python
335
+ # Good: Use async/await consistently
336
+ async def async_job():
337
+ async with httpx.AsyncClient() as client:
338
+ response = await client.get("https://api.example.com")
339
+ return response.json()
340
+
341
+ # Avoid: Blocking operations
342
+ def bad_job():
343
+ response = requests.get("https://api.example.com") # Blocks event loop
344
+ return response.json()
345
+ ```
346
+
347
+ ### 3. Handle Job Dependencies
348
+ ```python
349
+ async def dependent_job():
350
+ # Check if prerequisite job completed
351
+ if not await check_prerequisite_completed():
352
+ logger.warning("Prerequisite not completed, skipping job")
353
+ return
354
+
355
+ await perform_dependent_work()
356
+ ```
357
+
358
+ ### 4. Use Idempotent Jobs
359
+ ```python
360
+ async def idempotent_job():
361
+ # Check if work already done today
362
+ if await is_work_already_done_today():
363
+ logger.info("Work already completed today")
364
+ return
365
+
366
+ await perform_work()
367
+ await mark_work_completed()
368
+ ```
369
+
370
+ ## Troubleshooting
371
+
372
+ ### Common Issues
373
+
374
+ **Jobs not executing:**
375
+ - Check if scheduler service is running
376
+ - Verify job is properly registered
377
+ - Check timezone settings
378
+ - Review job logs for errors
379
+
380
+ **High memory usage:**
381
+ - Monitor job execution times
382
+ - Check for memory leaks in job functions
383
+ - Consider job queue limits
384
+
385
+ **Jobs running too frequently:**
386
+ - Review cron trigger configuration
387
+ - Check for overlapping job instances
388
+ - Consider using `max_instances=1` for singleton jobs
389
+
390
+ ### Debug Commands
391
+ ```bash
392
+ # Check scheduler status
393
+ {{ cookiecutter.project_slug }} health check
394
+
395
+ # View scheduler logs
396
+ docker compose logs scheduler
397
+
398
+ # List all jobs (in Python shell)
399
+ python -c "
400
+ from app.components.scheduler.main import create_scheduler
401
+ scheduler = create_scheduler()
402
+ for job in scheduler.get_jobs():
403
+ print(f'{job.id}: {job.next_run_time}')
404
+ "
405
+ ```
406
+
407
+ ## Production Considerations
408
+
409
+ 1. **Job Persistence**: Use database job store for job persistence across restarts
410
+ 2. **Monitoring**: Set up alerts for job failures and long execution times
411
+ 3. **Resource Limits**: Configure appropriate memory and CPU limits
412
+ 4. **Timezone Handling**: Always use UTC for job scheduling
413
+ 5. **Error Notifications**: Implement alerting for critical job failures
414
+ 6. **Job Cleanup**: Regularly remove old completed jobs to prevent database bloat
@@ -0,0 +1,215 @@
1
+ # Development Guide
2
+
3
+ This guide covers how to develop and maintain {{ cookiecutter.project_name }}.
4
+
5
+ ## Getting Started
6
+
7
+ ### Prerequisites
8
+ - Python 3.11+
9
+ - UV package manager
10
+
11
+ ### Setup
12
+ ```bash
13
+ # Clone and enter the project
14
+ cd {{ cookiecutter.project_slug }}
15
+
16
+ # Install dependencies
17
+ uv sync
18
+
19
+ # Copy environment template
20
+ cp .env.example .env
21
+
22
+ # Start development server
23
+ make run
24
+ ```
25
+
26
+ ## Development Commands
27
+
28
+ ### Running the Application
29
+ ```bash
30
+ make run # Start with Docker
31
+ ```
32
+
33
+ ### Health Monitoring
34
+ ```bash
35
+ make health # Check system health
36
+ make health-detailed # Detailed health information
37
+ make health-json # JSON health output
38
+ ```
39
+
40
+ ### Code Quality
41
+ ```bash
42
+ make test # Run test suite
43
+ make lint # Check code style
44
+ make typecheck # Run type checking
45
+ make check # Run all checks
46
+ make fix # Auto-fix code issues
47
+ ```
48
+
49
+ ### Documentation
50
+ ```bash
51
+ make docs-serve # Serve documentation locally (http://localhost:8001)
52
+ make docs-build # Build static documentation
53
+ ```
54
+
55
+ ## Project Structure
56
+
57
+ ```
58
+ {{ cookiecutter.project_slug }}/
59
+ ├── app/
60
+ │ ├── components/ # Application components{% if cookiecutter.include_scheduler == "yes" %}
61
+ │ │ ├── scheduler/ # Background task scheduling{% endif %}
62
+ │ │ ├── backend/ # FastAPI web server
63
+ │ │ ├── frontend/ # Flet user interface
64
+ │ │ └── worker/ # Background task workers (arq)
65
+ │ ├── core/ # Core utilities and configuration
66
+ │ ├── services/ # Business logic services
67
+ │ └── cli/ # Command-line interface
68
+ ├── tests/ # Test suite
69
+ ├── docs/ # Project documentation
70
+ └── docker-compose.yml # Container orchestration
71
+ ```
72
+
73
+ ## Adding New Features
74
+
75
+ ### 1. Create Business Logic
76
+ Add pure business logic functions to `app/services/`:
77
+
78
+ ```python
79
+ # app/services/my_service.py
80
+ async def process_data(data: str) -> str:
81
+ """Process data and return result."""
82
+ return f"Processed: {data}"
83
+ ```
84
+
85
+ ### 2. Add API Endpoints
86
+ Create routes in `app/components/backend/api/`:
87
+
88
+ ```python
89
+ # app/components/backend/api/my_endpoints.py
90
+ from fastapi import APIRouter
91
+ from app.services.my_service import process_data
92
+
93
+ router = APIRouter()
94
+
95
+ @router.post("/process")
96
+ async def process_endpoint(data: str):
97
+ result = await process_data(data)
98
+ return {"result": result}
99
+ ```
100
+
101
+ Register in `app/components/backend/api/routing.py`:
102
+
103
+ ```python
104
+ from app.components.backend.api import my_endpoints
105
+
106
+ def include_routers(app: FastAPI) -> None:
107
+ app.include_router(my_endpoints.router, prefix="/api", tags=["processing"])
108
+ ```
109
+
110
+ ### 3. Add Background Tasks
111
+ Create worker tasks in `app/components/worker/tasks/`:
112
+
113
+ ```python
114
+ # app/components/worker/tasks/my_tasks.py
115
+ async def background_process_data(data: str) -> dict[str, str]:
116
+ """Process data in background."""
117
+ logger.info(f"Processing {data} in background")
118
+
119
+ # Your processing logic here
120
+ result = f"Processed: {data}"
121
+
122
+ return {
123
+ "status": "completed",
124
+ "result": result,
125
+ "timestamp": datetime.now(UTC).isoformat()
126
+ }
127
+ ```
128
+
129
+ Register in worker queue (`app/components/worker/queues/system.py`):
130
+
131
+ ```python
132
+ from app.components.worker.tasks.my_tasks import background_process_data
133
+
134
+ class WorkerSettings:
135
+ functions = [
136
+ system_health_check,
137
+ background_process_data, # Add your task here
138
+ ]
139
+ ```{% if cookiecutter.include_scheduler == "yes" %}
140
+
141
+ ### 4. Add Scheduled Tasks
142
+ Add jobs to the scheduler component:
143
+
144
+ ```python
145
+ # In app/components/scheduler/main.py
146
+ from app.services.my_service import process_data
147
+
148
+ # Add to create_scheduler function
149
+ scheduler.add_job(
150
+ func=lambda: process_data("scheduled"),
151
+ trigger="cron",
152
+ hour=2, # Run at 2 AM daily
153
+ id="daily_processing",
154
+ name="Daily Data Processing"
155
+ )
156
+ ```{% endif %}
157
+
158
+ ## Testing
159
+
160
+ ### Running Tests
161
+ ```bash
162
+ make test # All tests
163
+ make test-verbose # Verbose output
164
+ ```
165
+
166
+ ### Writing Tests
167
+ Create tests in the `tests/` directory:
168
+
169
+ ```python
170
+ # tests/services/test_my_service.py
171
+ import pytest
172
+ from app.services.my_service import process_data
173
+
174
+ @pytest.mark.asyncio
175
+ async def test_process_data():
176
+ result = await process_data("test")
177
+ assert result == "Processed: test"
178
+ ```
179
+
180
+ ## Deployment
181
+
182
+ ### Docker Deployment
183
+ ```bash
184
+ # Build and run
185
+ make docker-build
186
+ make docker-up
187
+
188
+ # Or use profiles for specific components
189
+ docker compose --profile dev up
190
+ ```
191
+
192
+ ### Environment Configuration
193
+ Configure `.env` file for your environment:
194
+
195
+ ```env
196
+ # API Configuration
197
+ API_HOST=0.0.0.0
198
+ API_PORT=8000
199
+
200
+ # Logging
201
+ LOG_LEVEL=INFO{% if cookiecutter.include_scheduler == "yes" %}
202
+
203
+ # Scheduler Configuration
204
+ SCHEDULER_TIMEZONE=UTC{% endif %}
205
+ ```
206
+
207
+ ## Monitoring and Health Checks
208
+
209
+ {{ cookiecutter.project_name }} includes comprehensive health monitoring:
210
+
211
+ - **Health Endpoints**: `/health/` and `/health/detailed`
212
+ - **CLI Commands**: `{{ cookiecutter.project_slug }} health check`
213
+ - **Component Monitoring**: Automatic health checks for all components
214
+
215
+ See [Health Monitoring](health.md) for complete details.