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,189 @@
|
|
|
1
|
+
"""Task health monitoring service for scheduler health checks."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
7
|
+
|
|
8
|
+
from app.core.log import logger
|
|
9
|
+
from .models import SchedulerHealthMetadata, UpcomingTask
|
|
10
|
+
|
|
11
|
+
{% if cookiecutter.scheduler_backend != "memory" %}
|
|
12
|
+
from .scheduled_task_manager import ScheduledTaskManager
|
|
13
|
+
{% endif %}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TaskHealthMonitor:
|
|
17
|
+
"""
|
|
18
|
+
Service for providing rich task health data for scheduler health checks.
|
|
19
|
+
|
|
20
|
+
Works with both persistent and non-persistent scheduler modes:
|
|
21
|
+
- Persistent mode: Uses ScheduledTaskManager for detailed task info
|
|
22
|
+
- Non-persistent mode: Uses scheduler.get_jobs() directly
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
{% if cookiecutter.scheduler_backend != "memory" %}
|
|
27
|
+
self.task_manager = ScheduledTaskManager()
|
|
28
|
+
{% endif %}
|
|
29
|
+
|
|
30
|
+
async def get_health_metadata(
|
|
31
|
+
self, scheduler: AsyncIOScheduler | None
|
|
32
|
+
) -> SchedulerHealthMetadata:
|
|
33
|
+
"""
|
|
34
|
+
Get comprehensive health metadata for scheduler component.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
scheduler: The AsyncIOScheduler instance, or None if not available
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
SchedulerHealthMetadata with task statistics and upcoming task information
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# First, try to get enhanced data if persistence is available
|
|
44
|
+
{% if cookiecutter.scheduler_backend != "memory" %}
|
|
45
|
+
if await self.task_manager.has_persistence():
|
|
46
|
+
return await self._get_persistent_metadata()
|
|
47
|
+
{% endif %}
|
|
48
|
+
|
|
49
|
+
# If no persistence, we need a scheduler instance for direct inspection
|
|
50
|
+
if scheduler is None:
|
|
51
|
+
return SchedulerHealthMetadata(
|
|
52
|
+
total_tasks=0,
|
|
53
|
+
active_tasks=0,
|
|
54
|
+
paused_tasks=0,
|
|
55
|
+
upcoming_tasks=[],
|
|
56
|
+
scheduler_state="not_initialized"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Fallback to direct scheduler inspection
|
|
60
|
+
return await self._get_direct_scheduler_metadata(scheduler)
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.error("Error getting scheduler health metadata", error=str(e))
|
|
64
|
+
# Return basic fallback data - only if we have a scheduler instance
|
|
65
|
+
if scheduler is not None:
|
|
66
|
+
return await self._get_direct_scheduler_metadata(scheduler)
|
|
67
|
+
else:
|
|
68
|
+
return SchedulerHealthMetadata(
|
|
69
|
+
total_tasks=0,
|
|
70
|
+
active_tasks=0,
|
|
71
|
+
paused_tasks=0,
|
|
72
|
+
upcoming_tasks=[],
|
|
73
|
+
scheduler_state="error"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
{% if cookiecutter.scheduler_backend != "memory" %}
|
|
77
|
+
async def _get_persistent_metadata(self) -> SchedulerHealthMetadata:
|
|
78
|
+
"""Get detailed metadata using ScheduledTaskManager (persistent mode)."""
|
|
79
|
+
try:
|
|
80
|
+
# Get statistics
|
|
81
|
+
stats = await self.task_manager.get_statistics()
|
|
82
|
+
|
|
83
|
+
# Get upcoming tasks (limit to top 5)
|
|
84
|
+
tasks = await self.task_manager.list_tasks()
|
|
85
|
+
upcoming_tasks = []
|
|
86
|
+
|
|
87
|
+
for task in tasks:
|
|
88
|
+
if task.next_run_time and task.status == "active":
|
|
89
|
+
upcoming_tasks.append(UpcomingTask(
|
|
90
|
+
job_id=task.job_id,
|
|
91
|
+
name=task.name,
|
|
92
|
+
next_run=task.next_run_time.isoformat(),
|
|
93
|
+
schedule=task.schedule
|
|
94
|
+
))
|
|
95
|
+
|
|
96
|
+
# Sort by next run time and take top 5
|
|
97
|
+
upcoming_tasks.sort(key=lambda x: x.next_run)
|
|
98
|
+
upcoming_tasks = upcoming_tasks[:5]
|
|
99
|
+
|
|
100
|
+
return SchedulerHealthMetadata(
|
|
101
|
+
total_tasks=stats.total_tasks,
|
|
102
|
+
active_tasks=stats.active_tasks,
|
|
103
|
+
paused_tasks=stats.paused_tasks,
|
|
104
|
+
upcoming_tasks=upcoming_tasks,
|
|
105
|
+
scheduler_state="running_persistent"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error("Error getting persistent metadata", error=str(e))
|
|
110
|
+
raise
|
|
111
|
+
{% endif %}
|
|
112
|
+
|
|
113
|
+
async def _get_direct_scheduler_metadata(
|
|
114
|
+
self, scheduler: AsyncIOScheduler
|
|
115
|
+
) -> SchedulerHealthMetadata:
|
|
116
|
+
"""Get basic metadata by inspecting scheduler directly (non-persistent mode)."""
|
|
117
|
+
try:
|
|
118
|
+
jobs = scheduler.get_jobs()
|
|
119
|
+
total_tasks = len(jobs)
|
|
120
|
+
|
|
121
|
+
# Count active jobs (those with next_run_time)
|
|
122
|
+
active_tasks = sum(1 for job in jobs if job.next_run_time is not None)
|
|
123
|
+
paused_tasks = total_tasks - active_tasks
|
|
124
|
+
|
|
125
|
+
# Get upcoming tasks (top 5)
|
|
126
|
+
upcoming_tasks = []
|
|
127
|
+
active_jobs = [job for job in jobs if job.next_run_time is not None]
|
|
128
|
+
active_jobs.sort(key=lambda x: x.next_run_time)
|
|
129
|
+
|
|
130
|
+
for job in active_jobs[:5]:
|
|
131
|
+
upcoming_tasks.append(UpcomingTask(
|
|
132
|
+
job_id=job.id,
|
|
133
|
+
name=job.name or job.id,
|
|
134
|
+
next_run=job.next_run_time.isoformat(),
|
|
135
|
+
schedule=self._format_trigger_simple(job.trigger)
|
|
136
|
+
))
|
|
137
|
+
|
|
138
|
+
return SchedulerHealthMetadata(
|
|
139
|
+
total_tasks=total_tasks,
|
|
140
|
+
active_tasks=active_tasks,
|
|
141
|
+
paused_tasks=paused_tasks,
|
|
142
|
+
upcoming_tasks=upcoming_tasks,
|
|
143
|
+
scheduler_state="running_memory"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error("Error getting direct scheduler metadata", error=str(e))
|
|
148
|
+
return SchedulerHealthMetadata(
|
|
149
|
+
total_tasks=0,
|
|
150
|
+
active_tasks=0,
|
|
151
|
+
paused_tasks=0,
|
|
152
|
+
upcoming_tasks=[],
|
|
153
|
+
scheduler_state="error"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _format_trigger_simple(self, trigger: Any) -> str:
|
|
157
|
+
"""Simple trigger formatting for non-persistent mode."""
|
|
158
|
+
if not trigger:
|
|
159
|
+
return "Unknown"
|
|
160
|
+
|
|
161
|
+
trigger_type = type(trigger).__name__
|
|
162
|
+
|
|
163
|
+
if trigger_type == "IntervalTrigger":
|
|
164
|
+
if hasattr(trigger, "interval"):
|
|
165
|
+
seconds = trigger.interval.total_seconds()
|
|
166
|
+
if seconds < 60:
|
|
167
|
+
return f"Every {int(seconds)}s"
|
|
168
|
+
elif seconds < 3600:
|
|
169
|
+
minutes = int(seconds / 60)
|
|
170
|
+
return f"Every {minutes}m"
|
|
171
|
+
elif seconds < 86400:
|
|
172
|
+
hours = seconds / 3600
|
|
173
|
+
if hours == int(hours):
|
|
174
|
+
return f"Every {int(hours)}h"
|
|
175
|
+
else:
|
|
176
|
+
return f"Every {hours:.1f}h"
|
|
177
|
+
else:
|
|
178
|
+
days = int(seconds / 86400)
|
|
179
|
+
return f"Every {days}d"
|
|
180
|
+
|
|
181
|
+
elif trigger_type == "CronTrigger":
|
|
182
|
+
# Simple cron description
|
|
183
|
+
return "Cron schedule"
|
|
184
|
+
|
|
185
|
+
elif trigger_type == "DateTrigger":
|
|
186
|
+
if hasattr(trigger, "run_date"):
|
|
187
|
+
return f"Once at {trigger.run_date.strftime('%Y-%m-%d %H:%M')}"
|
|
188
|
+
|
|
189
|
+
return trigger_type.replace("Trigger", "")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared domain models.
|
|
3
|
+
|
|
4
|
+
Common Pydantic models used across multiple domains and services.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseResponse(BaseModel):
|
|
13
|
+
"""Base API response model."""
|
|
14
|
+
|
|
15
|
+
success: bool = Field(..., description="Whether operation was successful")
|
|
16
|
+
message: str = Field(..., description="Response message")
|
|
17
|
+
data: dict[str, Any] | list[Any] | None = Field(None, description="Response data")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ErrorResponse(BaseModel):
|
|
21
|
+
"""Standard error response model."""
|
|
22
|
+
|
|
23
|
+
error: str = Field(..., description="Error message")
|
|
24
|
+
status: str = Field(default="error", description="Status indicator")
|
|
25
|
+
details: dict[str, Any] | None = Field(None, description="Additional error details")
|
|
26
|
+
timestamp: str | None = Field(None, description="ISO timestamp when error occurred")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
System domain - health monitoring, alerts, and system management.
|
|
3
|
+
|
|
4
|
+
This domain provides functions for:
|
|
5
|
+
- System health checking and monitoring
|
|
6
|
+
- Component status validation
|
|
7
|
+
- Alert management and notifications
|
|
8
|
+
- System resource monitoring
|
|
9
|
+
|
|
10
|
+
All functions use Pydantic models for type safety and validation.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .alerts import (
|
|
14
|
+
send_alert,
|
|
15
|
+
send_critical_alert,
|
|
16
|
+
send_health_alert,
|
|
17
|
+
)
|
|
18
|
+
from .health import (
|
|
19
|
+
check_system_status,
|
|
20
|
+
get_system_status,
|
|
21
|
+
is_system_healthy,
|
|
22
|
+
register_health_check,
|
|
23
|
+
)
|
|
24
|
+
from .models import (
|
|
25
|
+
Alert,
|
|
26
|
+
AlertSeverity,
|
|
27
|
+
ComponentStatus,
|
|
28
|
+
ComponentStatusType,
|
|
29
|
+
DetailedHealthResponse,
|
|
30
|
+
HealthResponse,
|
|
31
|
+
SystemStatus,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Health functions
|
|
36
|
+
"check_system_status",
|
|
37
|
+
"get_system_status",
|
|
38
|
+
"is_system_healthy",
|
|
39
|
+
"register_health_check",
|
|
40
|
+
# Alert functions
|
|
41
|
+
"send_alert",
|
|
42
|
+
"send_critical_alert",
|
|
43
|
+
"send_health_alert",
|
|
44
|
+
# Models
|
|
45
|
+
"Alert",
|
|
46
|
+
"AlertSeverity",
|
|
47
|
+
"ComponentStatus",
|
|
48
|
+
"ComponentStatusType",
|
|
49
|
+
"SystemStatus",
|
|
50
|
+
"HealthResponse",
|
|
51
|
+
"DetailedHealthResponse",
|
|
52
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
System alert management functions.
|
|
3
|
+
|
|
4
|
+
Pure functions for sending alerts, managing notifications, and rate limiting.
|
|
5
|
+
All functions use Pydantic models for type safety and validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from app.core.log import logger
|
|
12
|
+
|
|
13
|
+
from .models import Alert, SystemStatus, alert_severity
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Global state for alert rate limiting
|
|
20
|
+
_last_alerts: dict[str, datetime] = {}
|
|
21
|
+
_rate_limit_seconds = 300 # 5 minutes between similar alerts
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _should_send_alert(alert_key: str) -> bool:
|
|
25
|
+
"""Check if we should send this alert based on rate limiting."""
|
|
26
|
+
if alert_key not in _last_alerts:
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
time_since_last = datetime.now(UTC) - _last_alerts[alert_key]
|
|
30
|
+
return time_since_last.total_seconds() > _rate_limit_seconds
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def send_alert(alert: Alert) -> None:
|
|
34
|
+
"""Send an alert through configured channels."""
|
|
35
|
+
alert_key = f"{alert.severity}:{alert.title}"
|
|
36
|
+
|
|
37
|
+
if not _should_send_alert(alert_key):
|
|
38
|
+
logger.debug(f"Rate limiting alert: {alert_key}")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
_last_alerts[alert_key] = datetime.now(UTC)
|
|
42
|
+
|
|
43
|
+
# Log-based alerting (always available)
|
|
44
|
+
log_level = {
|
|
45
|
+
alert_severity.INFO: logger.info,
|
|
46
|
+
alert_severity.WARNING: logger.warning,
|
|
47
|
+
alert_severity.ERROR: logger.error,
|
|
48
|
+
alert_severity.CRITICAL: logger.critical,
|
|
49
|
+
}.get(alert.severity, logger.info)
|
|
50
|
+
|
|
51
|
+
log_level(
|
|
52
|
+
f"🚨 ALERT [{alert.severity.upper()}]: {alert.title}",
|
|
53
|
+
extra={
|
|
54
|
+
"alert_message": alert.message,
|
|
55
|
+
"alert_metadata": alert.metadata,
|
|
56
|
+
"alert_timestamp": alert.timestamp.isoformat(),
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# TODO: Add integrations for:
|
|
61
|
+
# - Slack/Discord webhooks
|
|
62
|
+
# - Email notifications
|
|
63
|
+
# - PagerDuty/Opsgenie
|
|
64
|
+
# - Custom webhook endpoints
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def send_health_alert(status: SystemStatus) -> None:
|
|
68
|
+
"""Send health-related alerts."""
|
|
69
|
+
if not status.overall_healthy:
|
|
70
|
+
alert = Alert(
|
|
71
|
+
severity=alert_severity.WARNING,
|
|
72
|
+
title="System Health Issues Detected",
|
|
73
|
+
message=(
|
|
74
|
+
f"{len(status.unhealthy_components)} components unhealthy: "
|
|
75
|
+
f"{', '.join(status.unhealthy_components)}"
|
|
76
|
+
),
|
|
77
|
+
timestamp=status.timestamp,
|
|
78
|
+
metadata={
|
|
79
|
+
"unhealthy_components": status.unhealthy_components,
|
|
80
|
+
"health_percentage": status.health_percentage,
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
await send_alert(alert)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def send_critical_alert(title: str, message: str) -> None:
|
|
87
|
+
"""Send a critical system alert."""
|
|
88
|
+
alert = Alert(
|
|
89
|
+
severity=alert_severity.CRITICAL,
|
|
90
|
+
title=title,
|
|
91
|
+
message=message,
|
|
92
|
+
timestamp=datetime.now(UTC),
|
|
93
|
+
)
|
|
94
|
+
await send_alert(alert)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database backup service for {{ cookiecutter.project_name }}.
|
|
3
|
+
|
|
4
|
+
Provides database backup functionality for scheduled backup jobs.
|
|
5
|
+
Included when scheduler and database components are both present.
|
|
6
|
+
"""
|
|
7
|
+
{% if cookiecutter.scheduler_backend != "memory" or (cookiecutter.include_scheduler == "yes" and cookiecutter.include_database == "yes") %}
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import shutil
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from app.core.db import DATABASE_PATH
|
|
15
|
+
from app.core.log import logger
|
|
16
|
+
|
|
17
|
+
# Backup file naming pattern
|
|
18
|
+
BACKUP_FILE_PATTERN = "database_backup_*.db"
|
|
19
|
+
BACKUP_FILE_PREFIX = "database_backup_"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def backup_database_job() -> None:
|
|
23
|
+
"""
|
|
24
|
+
Scheduled database backup job.
|
|
25
|
+
|
|
26
|
+
Creates a backup of the SQLite database file with timestamp.
|
|
27
|
+
Keeps the last 7 daily backups to prevent disk space issues.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
# Ensure backup directory exists
|
|
31
|
+
backup_dir = Path("backups")
|
|
32
|
+
backup_dir.mkdir(exist_ok=True)
|
|
33
|
+
|
|
34
|
+
# Create timestamped backup filename
|
|
35
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
36
|
+
backup_filename = f"{BACKUP_FILE_PREFIX}{timestamp}.db"
|
|
37
|
+
backup_path = backup_dir / backup_filename
|
|
38
|
+
|
|
39
|
+
# Copy database file
|
|
40
|
+
if Path(DATABASE_PATH).exists():
|
|
41
|
+
shutil.copy2(DATABASE_PATH, backup_path)
|
|
42
|
+
logger.info(f"💾 Database backup created: {backup_path}")
|
|
43
|
+
else:
|
|
44
|
+
logger.warning(f"⚠️ Database file not found for backup: {DATABASE_PATH}")
|
|
45
|
+
|
|
46
|
+
# Clean up old backups (keep last 7) - always run regardless of backup success
|
|
47
|
+
await _cleanup_old_backups(backup_dir)
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"❌ Database backup failed: {e}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _cleanup_old_backups(backup_dir: Path, keep_count: int = 7) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Remove old backup files, keeping only the most recent ones.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
backup_dir: Directory containing backup files
|
|
59
|
+
keep_count: Number of recent backups to keep
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
# Get all backup files sorted by modification time (newest first)
|
|
63
|
+
backup_files = [
|
|
64
|
+
f for f in backup_dir.glob(BACKUP_FILE_PATTERN)
|
|
65
|
+
if f.is_file()
|
|
66
|
+
]
|
|
67
|
+
backup_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
|
|
68
|
+
|
|
69
|
+
# Remove old backups beyond keep_count
|
|
70
|
+
old_backups = backup_files[keep_count:]
|
|
71
|
+
for old_backup in old_backups:
|
|
72
|
+
old_backup.unlink()
|
|
73
|
+
logger.info(f"🗑️ Removed old backup: {old_backup.name}")
|
|
74
|
+
|
|
75
|
+
if old_backups:
|
|
76
|
+
kept_count = min(len(backup_files), keep_count)
|
|
77
|
+
logger.info(
|
|
78
|
+
f"✅ Cleaned up {len(old_backups)} old backups, kept {kept_count}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"❌ Backup cleanup failed: {e}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def restore_database_from_backup(backup_filename: str) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Restore database from a backup file.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
backup_filename: Name of the backup file to restore from
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if restore was successful, False otherwise
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
backup_dir = Path("backups")
|
|
97
|
+
backup_path = backup_dir / backup_filename
|
|
98
|
+
|
|
99
|
+
if not backup_path.exists():
|
|
100
|
+
logger.error(f"❌ Backup file not found: {backup_path}")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
# Create backup of current database before restore
|
|
104
|
+
current_db = Path(DATABASE_PATH)
|
|
105
|
+
if current_db.exists():
|
|
106
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
107
|
+
current_backup = backup_dir / f"pre_restore_backup_{timestamp}.db"
|
|
108
|
+
shutil.copy2(current_db, current_backup)
|
|
109
|
+
logger.info(f"💾 Created pre-restore backup: {current_backup}")
|
|
110
|
+
|
|
111
|
+
# Restore from backup
|
|
112
|
+
shutil.copy2(backup_path, DATABASE_PATH)
|
|
113
|
+
logger.info(f"✅ Database restored from backup: {backup_filename}")
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"❌ Database restore failed: {e}")
|
|
118
|
+
return False
|
|
119
|
+
{% endif %}
|