aegis-stack 0.2.0rc2__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.
- aegis/__init__.py +5 -0
- aegis/__main__.py +51 -0
- aegis/cli/__init__.py +6 -0
- aegis/cli/callbacks.py +114 -0
- aegis/cli/interactive.py +611 -0
- aegis/cli/utils.py +70 -0
- aegis/cli/validators.py +34 -0
- aegis/commands/__init__.py +6 -0
- aegis/commands/add.py +353 -0
- aegis/commands/add_service.py +332 -0
- aegis/commands/components.py +35 -0
- aegis/commands/init.py +370 -0
- aegis/commands/remove.py +227 -0
- aegis/commands/services.py +52 -0
- aegis/commands/update.py +252 -0
- aegis/commands/version.py +12 -0
- aegis/config/__init__.py +1 -0
- aegis/config/shared_files.py +136 -0
- aegis/core/CLAUDE.md +377 -0
- aegis/core/__init__.py +6 -0
- aegis/core/component_files.py +228 -0
- aegis/core/component_utils.py +220 -0
- aegis/core/components.py +127 -0
- aegis/core/copier_manager.py +315 -0
- aegis/core/copier_updater.py +475 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/manual_updater.py +554 -0
- aegis/core/post_gen_tasks.py +547 -0
- aegis/core/service_resolver.py +261 -0
- aegis/core/services.py +157 -0
- aegis/core/template_generator.py +266 -0
- aegis/core/version_compatibility.py +259 -0
- aegis/templates/CLAUDE.md +591 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
- aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
- aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
- aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
- aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
- aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,621 @@
|
|
|
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
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
13
|
+
- **Job persistence and recovery**
|
|
14
|
+
- **Real-time job monitoring**
|
|
15
|
+
- **Task statistics tracking**
|
|
16
|
+
{% else -%}
|
|
17
|
+
- **In-memory job execution**
|
|
18
|
+
{% endif -%}
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
23
|
+
```mermaid
|
|
24
|
+
graph TB
|
|
25
|
+
subgraph "{{ cookiecutter.project_name }} Architecture"
|
|
26
|
+
API[FastAPI Backend<br/>Port 8000]
|
|
27
|
+
Scheduler[Scheduler Service<br/>Background Jobs]
|
|
28
|
+
Database[(SQLite Database<br/>Job Persistence)]
|
|
29
|
+
Jobs[(Job Queue<br/>Persistent)]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subgraph "Job Types"
|
|
33
|
+
BackupJob[Database Backup<br/>Daily at 2 AM]
|
|
34
|
+
SystemJobs[System Maintenance<br/>Configurable]
|
|
35
|
+
CustomJobs[Custom Jobs<br/>User-defined]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
API -.-> Scheduler
|
|
39
|
+
Scheduler --> Database
|
|
40
|
+
Database --> Jobs
|
|
41
|
+
Jobs --> BackupJob
|
|
42
|
+
Jobs --> SystemJobs
|
|
43
|
+
Jobs --> CustomJobs
|
|
44
|
+
|
|
45
|
+
style Scheduler fill:#f9f,stroke:#333,stroke-width:2px
|
|
46
|
+
style Database fill:#bbf,stroke:#333,stroke-width:2px
|
|
47
|
+
style BackupJob fill:#bfb,stroke:#333,stroke-width:2px
|
|
48
|
+
```
|
|
49
|
+
{% else -%}
|
|
50
|
+
```mermaid
|
|
51
|
+
graph TB
|
|
52
|
+
subgraph "{{ cookiecutter.project_name }} Architecture"
|
|
53
|
+
API[FastAPI Backend<br/>Port 8000]
|
|
54
|
+
Scheduler[Scheduler Service<br/>Background Jobs]
|
|
55
|
+
Jobs[(Job Queue<br/>In-Memory)]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
subgraph "Job Types"
|
|
59
|
+
SystemJobs[System Maintenance<br/>Every 6 hours]
|
|
60
|
+
CustomJobs[Custom Jobs<br/>User-defined]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
API -.-> Scheduler
|
|
64
|
+
Scheduler --> Jobs
|
|
65
|
+
Jobs --> SystemJobs
|
|
66
|
+
Jobs --> CustomJobs
|
|
67
|
+
|
|
68
|
+
style Scheduler fill:#f9f,stroke:#333,stroke-width:2px
|
|
69
|
+
style Jobs fill:#bbf,stroke:#333,stroke-width:2px
|
|
70
|
+
```
|
|
71
|
+
{% endif -%}
|
|
72
|
+
|
|
73
|
+
## Running the Scheduler
|
|
74
|
+
|
|
75
|
+
The scheduler runs as a Docker service. Use these commands:
|
|
76
|
+
|
|
77
|
+
### Docker Deployment
|
|
78
|
+
```bash
|
|
79
|
+
# Run only scheduler service
|
|
80
|
+
docker compose --profile dev up scheduler
|
|
81
|
+
|
|
82
|
+
# Run all services including scheduler
|
|
83
|
+
make run
|
|
84
|
+
|
|
85
|
+
# Or use docker compose directly
|
|
86
|
+
docker compose --profile dev up
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
90
|
+
## Database Persistence Features
|
|
91
|
+
|
|
92
|
+
Your scheduler includes persistence capabilities through SQLAlchemy jobstore:
|
|
93
|
+
|
|
94
|
+
### Persistence Features
|
|
95
|
+
- **Job Persistence**: Jobs survive container restarts via SQLAlchemy jobstore
|
|
96
|
+
- **Daily Database Backup**: Pre-configured backup job at 2 AM UTC
|
|
97
|
+
- **Real-Time Task Monitoring**: Track task statistics (total, active, paused)
|
|
98
|
+
- **Upcoming Task Lists**: View scheduled jobs with next execution times
|
|
99
|
+
- **Job History**: Query past executions and failures
|
|
100
|
+
|
|
101
|
+
### Task Monitoring
|
|
102
|
+
|
|
103
|
+
#### CLI Health Check
|
|
104
|
+
```bash
|
|
105
|
+
{{ cookiecutter.project_slug }} health check --detailed
|
|
106
|
+
|
|
107
|
+
# Example Output:
|
|
108
|
+
✓ scheduler Scheduler running with 3 tasks
|
|
109
|
+
└─ Tasks: 3 total, 3 active, 0 paused
|
|
110
|
+
└─ Upcoming Tasks (Next 3):
|
|
111
|
+
├─ database_backup: Daily at 2:00 AM UTC → in 5h
|
|
112
|
+
├─ cleanup_temp: Every 6 hours → in 2h
|
|
113
|
+
└─ report_gen: Weekly on Monday → in 3d
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Dashboard UI Card
|
|
117
|
+
The scheduler component card provides:
|
|
118
|
+
|
|
119
|
+
- **Task counter** with status-aware coloring (healthy/warning states)
|
|
120
|
+
- **Scrollable upcoming jobs** with next execution countdowns
|
|
121
|
+
- **Interactive controls** (pause/delete buttons appear on hover)
|
|
122
|
+
- **Job details** showing schedule patterns and names
|
|
123
|
+
|
|
124
|
+
{% endif -%}
|
|
125
|
+
## Job Configuration
|
|
126
|
+
|
|
127
|
+
The scheduler is configured in `app/components/scheduler/main.py`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
131
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
132
|
+
|
|
133
|
+
def create_scheduler() -> AsyncIOScheduler:
|
|
134
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
135
|
+
# Configured with SQLAlchemy jobstore for persistence
|
|
136
|
+
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
|
137
|
+
from app.core.db import engine, init_database
|
|
138
|
+
|
|
139
|
+
init_database()
|
|
140
|
+
jobstore = SQLAlchemyJobStore(engine=engine, tablename='apscheduler_jobs')
|
|
141
|
+
scheduler = AsyncIOScheduler(jobstores={'default': jobstore})
|
|
142
|
+
{% else -%}
|
|
143
|
+
# Memory-only scheduler (jobs reset on restart)
|
|
144
|
+
scheduler = AsyncIOScheduler()
|
|
145
|
+
{% endif -%}
|
|
146
|
+
|
|
147
|
+
# Add your jobs here
|
|
148
|
+
scheduler.add_job(
|
|
149
|
+
func=system_maintenance_job,
|
|
150
|
+
trigger=CronTrigger(hour=2), # Run daily at 2 AM
|
|
151
|
+
id="system_maintenance",
|
|
152
|
+
name="System Maintenance",
|
|
153
|
+
replace_existing=True
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return scheduler
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
160
|
+
### Pre-configured Jobs
|
|
161
|
+
|
|
162
|
+
Your scheduler includes these automatic jobs:
|
|
163
|
+
|
|
164
|
+
#### Database Backup Job
|
|
165
|
+
```python
|
|
166
|
+
# Automatically included when database component is present
|
|
167
|
+
scheduler.add_job(
|
|
168
|
+
backup_database_job,
|
|
169
|
+
trigger="cron",
|
|
170
|
+
hour=2,
|
|
171
|
+
minute=0,
|
|
172
|
+
id="database_backup",
|
|
173
|
+
name="Daily Database Backup",
|
|
174
|
+
max_instances=1,
|
|
175
|
+
coalesce=True,
|
|
176
|
+
replace_existing=True
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This job:
|
|
181
|
+
|
|
182
|
+
- Runs daily at 2:00 AM UTC
|
|
183
|
+
- Creates timestamped database backups
|
|
184
|
+
- Handles rotation of old backup files
|
|
185
|
+
- Logs backup success/failure status
|
|
186
|
+
- Prevents overlapping executions
|
|
187
|
+
|
|
188
|
+
{% endif -%}
|
|
189
|
+
## Adding Custom Jobs
|
|
190
|
+
|
|
191
|
+
### 1. Create Job Function
|
|
192
|
+
Create job functions in `app/services/`:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
# app/services/my_jobs.py
|
|
196
|
+
import asyncio
|
|
197
|
+
from app.core.log import logger
|
|
198
|
+
|
|
199
|
+
async def process_daily_reports():
|
|
200
|
+
"""Generate daily reports."""
|
|
201
|
+
logger.info("Starting daily report generation")
|
|
202
|
+
|
|
203
|
+
# Your job logic here
|
|
204
|
+
await asyncio.sleep(1) # Simulate work
|
|
205
|
+
|
|
206
|
+
logger.info("Daily reports completed")
|
|
207
|
+
|
|
208
|
+
async def cleanup_old_files():
|
|
209
|
+
"""Clean up old temporary files."""
|
|
210
|
+
logger.info("Starting file cleanup")
|
|
211
|
+
|
|
212
|
+
# Your cleanup logic here
|
|
213
|
+
|
|
214
|
+
logger.info("File cleanup completed")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 2. Register Jobs in Scheduler
|
|
218
|
+
Add jobs to the scheduler configuration:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# app/components/scheduler/main.py
|
|
222
|
+
from app.services.my_jobs import process_daily_reports, cleanup_old_files
|
|
223
|
+
|
|
224
|
+
def create_scheduler() -> AsyncIOScheduler:
|
|
225
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
226
|
+
# Configure persistent scheduler (already initialized above)
|
|
227
|
+
scheduler = AsyncIOScheduler(jobstores={'default': jobstore})
|
|
228
|
+
|
|
229
|
+
# Check for existing jobs to respect runtime modifications
|
|
230
|
+
force_update = os.getenv("SCHEDULER_FORCE_UPDATE", "false").lower() == "true"
|
|
231
|
+
|
|
232
|
+
# Daily report at 6 AM (check if exists first)
|
|
233
|
+
if not _job_exists_in_database("daily_reports") or force_update:
|
|
234
|
+
scheduler.add_job(
|
|
235
|
+
func=process_daily_reports,
|
|
236
|
+
trigger=CronTrigger(hour=6, minute=0),
|
|
237
|
+
id="daily_reports",
|
|
238
|
+
name="Daily Report Generation",
|
|
239
|
+
replace_existing=True
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Weekly cleanup on Sundays at midnight
|
|
243
|
+
if not _job_exists_in_database("weekly_cleanup") or force_update:
|
|
244
|
+
scheduler.add_job(
|
|
245
|
+
func=cleanup_old_files,
|
|
246
|
+
trigger=CronTrigger(day_of_week="sun", hour=0, minute=0),
|
|
247
|
+
id="weekly_cleanup",
|
|
248
|
+
name="Weekly File Cleanup",
|
|
249
|
+
replace_existing=True
|
|
250
|
+
)
|
|
251
|
+
{% else -%}
|
|
252
|
+
# Configure memory scheduler
|
|
253
|
+
scheduler = AsyncIOScheduler()
|
|
254
|
+
|
|
255
|
+
# Daily report at 6 AM
|
|
256
|
+
scheduler.add_job(
|
|
257
|
+
func=process_daily_reports,
|
|
258
|
+
trigger=CronTrigger(hour=6, minute=0),
|
|
259
|
+
id="daily_reports",
|
|
260
|
+
name="Daily Report Generation"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Weekly cleanup on Sundays at midnight
|
|
264
|
+
scheduler.add_job(
|
|
265
|
+
func=cleanup_old_files,
|
|
266
|
+
trigger=CronTrigger(day_of_week="sun", hour=0, minute=0),
|
|
267
|
+
id="weekly_cleanup",
|
|
268
|
+
name="Weekly File Cleanup"
|
|
269
|
+
)
|
|
270
|
+
{% endif -%}
|
|
271
|
+
|
|
272
|
+
return scheduler
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Job Scheduling Options
|
|
276
|
+
|
|
277
|
+
### Cron-Style Triggers
|
|
278
|
+
```python
|
|
279
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
280
|
+
|
|
281
|
+
# Every day at 2:30 AM
|
|
282
|
+
CronTrigger(hour=2, minute=30)
|
|
283
|
+
|
|
284
|
+
# Every Monday at 9:00 AM
|
|
285
|
+
CronTrigger(day_of_week="mon", hour=9, minute=0)
|
|
286
|
+
|
|
287
|
+
# Every 15 minutes
|
|
288
|
+
CronTrigger(minute="*/15")
|
|
289
|
+
|
|
290
|
+
# First day of every month at midnight
|
|
291
|
+
CronTrigger(day=1, hour=0, minute=0)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Interval Triggers
|
|
295
|
+
```python
|
|
296
|
+
from apscheduler.triggers.interval import IntervalTrigger
|
|
297
|
+
|
|
298
|
+
# Every 5 minutes
|
|
299
|
+
IntervalTrigger(minutes=5)
|
|
300
|
+
|
|
301
|
+
# Every 2 hours
|
|
302
|
+
IntervalTrigger(hours=2)
|
|
303
|
+
|
|
304
|
+
# Every 30 seconds
|
|
305
|
+
IntervalTrigger(seconds=30)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### One-Time Jobs
|
|
309
|
+
```python
|
|
310
|
+
from datetime import datetime, timedelta
|
|
311
|
+
|
|
312
|
+
# Run once in 1 hour
|
|
313
|
+
scheduler.add_job(
|
|
314
|
+
func=one_time_task,
|
|
315
|
+
trigger="date",
|
|
316
|
+
run_date=datetime.now() + timedelta(hours=1),
|
|
317
|
+
id="one_time_task"
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Job Management
|
|
322
|
+
|
|
323
|
+
### Listing Jobs
|
|
324
|
+
```python
|
|
325
|
+
# Get all scheduled jobs
|
|
326
|
+
jobs = scheduler.get_jobs()
|
|
327
|
+
for job in jobs:
|
|
328
|
+
print(f"Job: {job.name}, Next run: {job.next_run_time}")
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Modifying Jobs
|
|
332
|
+
```python
|
|
333
|
+
# Pause a job
|
|
334
|
+
scheduler.pause_job("daily_reports")
|
|
335
|
+
|
|
336
|
+
# Resume a job
|
|
337
|
+
scheduler.resume_job("daily_reports")
|
|
338
|
+
|
|
339
|
+
# Remove a job
|
|
340
|
+
scheduler.remove_job("old_job_id")
|
|
341
|
+
|
|
342
|
+
# Modify job schedule
|
|
343
|
+
scheduler.modify_job("daily_reports", hour=7) # Change to 7 AM
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
347
|
+
### Production Job Updates
|
|
348
|
+
|
|
349
|
+
For persistent schedulers, use environment variables to control job updates:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# Force update all jobs from code configuration (useful during deployments)
|
|
353
|
+
SCHEDULER_FORCE_UPDATE=true docker compose up -d scheduler
|
|
354
|
+
|
|
355
|
+
# Normal startup (preserves runtime job modifications)
|
|
356
|
+
docker compose up -d scheduler
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
This pattern allows:
|
|
360
|
+
- **Development**: Jobs update automatically when code changes
|
|
361
|
+
- **Production**: Runtime job modifications are preserved across restarts
|
|
362
|
+
- **Deployments**: Force updates when needed with environment flag
|
|
363
|
+
|
|
364
|
+
{% endif -%}
|
|
365
|
+
## Error Handling
|
|
366
|
+
|
|
367
|
+
### Job Error Handling
|
|
368
|
+
```python
|
|
369
|
+
async def robust_job():
|
|
370
|
+
"""A job with proper error handling."""
|
|
371
|
+
try:
|
|
372
|
+
logger.info("Starting robust job")
|
|
373
|
+
|
|
374
|
+
# Job logic here
|
|
375
|
+
result = await some_async_operation()
|
|
376
|
+
|
|
377
|
+
logger.info(f"Job completed successfully: {result}")
|
|
378
|
+
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.error(f"Job failed: {str(e)}", exc_info=True)
|
|
381
|
+
# Optionally send alerts or retry logic
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Scheduler Error Listeners
|
|
385
|
+
```python
|
|
386
|
+
def job_listener(event):
|
|
387
|
+
"""Listen for job events."""
|
|
388
|
+
if event.exception:
|
|
389
|
+
logger.error(f"Job {event.job_id} crashed: {event.exception}")
|
|
390
|
+
else:
|
|
391
|
+
logger.info(f"Job {event.job_id} executed successfully")
|
|
392
|
+
|
|
393
|
+
# Add listener to scheduler
|
|
394
|
+
scheduler.add_listener(job_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Monitoring Jobs
|
|
398
|
+
|
|
399
|
+
### Health Checks
|
|
400
|
+
The scheduler health is included in system health checks:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
{{ cookiecutter.project_slug }} health check --detailed
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
407
|
+
Shows scheduler status, task statistics, active jobs, and next execution times.
|
|
408
|
+
{% else -%}
|
|
409
|
+
Shows scheduler status and basic job information.
|
|
410
|
+
{% endif -%}
|
|
411
|
+
|
|
412
|
+
### Job Logging
|
|
413
|
+
All job executions are logged with structured logging:
|
|
414
|
+
|
|
415
|
+
```json
|
|
416
|
+
{
|
|
417
|
+
"timestamp": "2024-01-01T02:00:00Z",
|
|
418
|
+
"level": "INFO",
|
|
419
|
+
"logger": "scheduler",
|
|
420
|
+
"message": "Job executed successfully",
|
|
421
|
+
"job_id": "system_maintenance",
|
|
422
|
+
"execution_time_ms": 1250
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Custom Job Metrics
|
|
427
|
+
Add metrics to your jobs:
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
import time
|
|
431
|
+
from app.core.log import logger
|
|
432
|
+
|
|
433
|
+
async def monitored_job():
|
|
434
|
+
start_time = time.time()
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
# Job work here
|
|
438
|
+
await do_work()
|
|
439
|
+
|
|
440
|
+
execution_time = time.time() - start_time
|
|
441
|
+
logger.info(f"Job completed in {execution_time:.2f}s")
|
|
442
|
+
|
|
443
|
+
except Exception as e:
|
|
444
|
+
execution_time = time.time() - start_time
|
|
445
|
+
logger.error(f"Job failed after {execution_time:.2f}s: {e}")
|
|
446
|
+
raise
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Configuration
|
|
450
|
+
|
|
451
|
+
### Scheduler Settings
|
|
452
|
+
Configure scheduler behavior in `app/core/config.py`:
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
class Settings(BaseSettings):
|
|
456
|
+
# Scheduler configuration
|
|
457
|
+
scheduler_timezone: str = "UTC"
|
|
458
|
+
scheduler_max_workers: int = 10
|
|
459
|
+
scheduler_job_defaults: dict = {
|
|
460
|
+
"coalesce": False,
|
|
461
|
+
"max_instances": 3
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
466
|
+
### Persistence Configuration
|
|
467
|
+
The scheduler automatically uses SQLAlchemy jobstore with your database:
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
# Configured in create_scheduler()
|
|
471
|
+
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
|
472
|
+
from app.core.db import engine
|
|
473
|
+
|
|
474
|
+
jobstore = SQLAlchemyJobStore(engine=engine, tablename='apscheduler_jobs')
|
|
475
|
+
scheduler = AsyncIOScheduler(jobstores={'default': jobstore})
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Jobs are stored in the `apscheduler_jobs` table with automatic schema creation.
|
|
479
|
+
{% else -%}
|
|
480
|
+
### Job Persistence
|
|
481
|
+
For production deployments, consider upgrading to persistent job storage by adding the database component:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
# Upgrade your project to include persistence
|
|
485
|
+
aegis init {{ cookiecutter.project_slug }}-v2 --components scheduler,database
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
This adds:
|
|
489
|
+
- SQLAlchemy jobstore for job persistence
|
|
490
|
+
- Automatic database backup jobs
|
|
491
|
+
- Real-time task monitoring
|
|
492
|
+
- Job history tracking
|
|
493
|
+
{% endif -%}
|
|
494
|
+
|
|
495
|
+
## Best Practices
|
|
496
|
+
|
|
497
|
+
### 1. Keep Jobs Small and Focused
|
|
498
|
+
```python
|
|
499
|
+
# Good: Small, focused job
|
|
500
|
+
async def send_daily_summary():
|
|
501
|
+
await generate_summary()
|
|
502
|
+
await send_email()
|
|
503
|
+
|
|
504
|
+
# Better: Break into smaller jobs
|
|
505
|
+
async def generate_daily_summary():
|
|
506
|
+
return await generate_summary()
|
|
507
|
+
|
|
508
|
+
async def send_summary_email():
|
|
509
|
+
summary = await get_cached_summary()
|
|
510
|
+
await send_email(summary)
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### 2. Use Proper Async Patterns
|
|
514
|
+
```python
|
|
515
|
+
# Good: Use async/await consistently
|
|
516
|
+
async def async_job():
|
|
517
|
+
async with httpx.AsyncClient() as client:
|
|
518
|
+
response = await client.get("https://api.example.com")
|
|
519
|
+
return response.json()
|
|
520
|
+
|
|
521
|
+
# Avoid: Blocking operations
|
|
522
|
+
def bad_job():
|
|
523
|
+
response = requests.get("https://api.example.com") # Blocks event loop
|
|
524
|
+
return response.json()
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### 3. Handle Job Dependencies
|
|
528
|
+
```python
|
|
529
|
+
async def dependent_job():
|
|
530
|
+
# Check if prerequisite job completed
|
|
531
|
+
if not await check_prerequisite_completed():
|
|
532
|
+
logger.warning("Prerequisite not completed, skipping job")
|
|
533
|
+
return
|
|
534
|
+
|
|
535
|
+
await perform_dependent_work()
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### 4. Use Idempotent Jobs
|
|
539
|
+
```python
|
|
540
|
+
async def idempotent_job():
|
|
541
|
+
# Check if work already done today
|
|
542
|
+
if await is_work_already_done_today():
|
|
543
|
+
logger.info("Work already completed today")
|
|
544
|
+
return
|
|
545
|
+
|
|
546
|
+
await perform_work()
|
|
547
|
+
await mark_work_completed()
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Troubleshooting
|
|
551
|
+
|
|
552
|
+
### Common Issues
|
|
553
|
+
|
|
554
|
+
**Jobs not executing:**
|
|
555
|
+
- Check if scheduler service is running
|
|
556
|
+
- Verify job is properly registered
|
|
557
|
+
- Check timezone settings
|
|
558
|
+
- Review job logs for errors
|
|
559
|
+
|
|
560
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
561
|
+
**Database connection issues:**
|
|
562
|
+
- Verify database service is running
|
|
563
|
+
- Check SQLite file permissions
|
|
564
|
+
- Review database initialization logs
|
|
565
|
+
- Confirm jobstore table creation
|
|
566
|
+
|
|
567
|
+
**Jobs not persisting:**
|
|
568
|
+
- Check if SCHEDULER_FORCE_UPDATE is set incorrectly
|
|
569
|
+
- Verify database write permissions
|
|
570
|
+
- Review SQLAlchemy jobstore logs
|
|
571
|
+
{% endif -%}
|
|
572
|
+
|
|
573
|
+
**High memory usage:**
|
|
574
|
+
- Monitor job execution times
|
|
575
|
+
- Check for memory leaks in job functions
|
|
576
|
+
- Consider job queue limits
|
|
577
|
+
|
|
578
|
+
**Jobs running too frequently:**
|
|
579
|
+
- Review cron trigger configuration
|
|
580
|
+
- Check for overlapping job instances
|
|
581
|
+
- Consider using `max_instances=1` for singleton jobs
|
|
582
|
+
|
|
583
|
+
### Debug Commands
|
|
584
|
+
```bash
|
|
585
|
+
# Check scheduler status
|
|
586
|
+
{{ cookiecutter.project_slug }} health check
|
|
587
|
+
|
|
588
|
+
# View scheduler logs
|
|
589
|
+
docker compose logs scheduler
|
|
590
|
+
|
|
591
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
592
|
+
# Check database for jobs
|
|
593
|
+
sqlite3 app.db "SELECT id, name, next_run_time FROM apscheduler_jobs;"
|
|
594
|
+
|
|
595
|
+
{% endif -%}
|
|
596
|
+
# List all jobs (in Python shell)
|
|
597
|
+
python -c "
|
|
598
|
+
from app.components.scheduler.main import create_scheduler
|
|
599
|
+
scheduler = create_scheduler()
|
|
600
|
+
for job in scheduler.get_jobs():
|
|
601
|
+
print(f'{job.id}: {job.next_run_time}')
|
|
602
|
+
"
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## Production Considerations
|
|
606
|
+
|
|
607
|
+
{% if cookiecutter.scheduler_backend != "memory" -%}
|
|
608
|
+
1. **Database Backups**: Your automatic backup job handles database persistence
|
|
609
|
+
2. **Job Monitoring**: Use the built-in task statistics for monitoring
|
|
610
|
+
3. **Resource Limits**: Configure appropriate memory and CPU limits
|
|
611
|
+
4. **Timezone Handling**: All jobs use UTC (configured automatically)
|
|
612
|
+
5. **Error Notifications**: Implement alerting for critical job failures
|
|
613
|
+
6. **Job Updates**: Use SCHEDULER_FORCE_UPDATE for deployment job updates
|
|
614
|
+
{% else -%}
|
|
615
|
+
1. **Job Persistence**: Consider upgrading to database component for persistence
|
|
616
|
+
2. **Monitoring**: Set up alerts for job failures and long execution times
|
|
617
|
+
3. **Resource Limits**: Configure appropriate memory and CPU limits
|
|
618
|
+
4. **Timezone Handling**: Always use UTC for job scheduling
|
|
619
|
+
5. **Error Notifications**: Implement alerting for critical job failures
|
|
620
|
+
6. **Job State**: Jobs reset on restart - design accordingly
|
|
621
|
+
{% endif -%}
|