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,48 @@
|
|
|
1
|
+
"""Simple theme management for the dashboard."""
|
|
2
|
+
|
|
3
|
+
import flet as ft
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ThemeManager:
|
|
7
|
+
"""Manages light/dark theme switching for the Flet page."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, page: ft.Page) -> None:
|
|
10
|
+
self.page = page
|
|
11
|
+
self.is_dark_mode = True # Default to dark
|
|
12
|
+
|
|
13
|
+
async def initialize_themes(self) -> None:
|
|
14
|
+
"""Initialize theme system with dark mode as default."""
|
|
15
|
+
self.page.theme_mode = ft.ThemeMode.DARK
|
|
16
|
+
self.is_dark_mode = True
|
|
17
|
+
self.page.update()
|
|
18
|
+
|
|
19
|
+
async def toggle_theme(self) -> None:
|
|
20
|
+
"""Toggle between light and dark themes."""
|
|
21
|
+
if self.is_dark_mode:
|
|
22
|
+
self.page.theme_mode = ft.ThemeMode.LIGHT
|
|
23
|
+
self.is_dark_mode = False
|
|
24
|
+
else:
|
|
25
|
+
self.page.theme_mode = ft.ThemeMode.DARK
|
|
26
|
+
self.is_dark_mode = True
|
|
27
|
+
|
|
28
|
+
self.page.update()
|
|
29
|
+
|
|
30
|
+
def get_status_colors(self, is_healthy: bool) -> tuple[str, str, str]:
|
|
31
|
+
"""Get (background, text, border) colors for status indicators."""
|
|
32
|
+
if is_healthy:
|
|
33
|
+
if self.is_dark_mode:
|
|
34
|
+
return (ft.Colors.GREEN_900, ft.Colors.GREEN_100, ft.Colors.GREEN)
|
|
35
|
+
else:
|
|
36
|
+
return (ft.Colors.GREEN_100, ft.Colors.GREEN_800, ft.Colors.GREEN)
|
|
37
|
+
else:
|
|
38
|
+
if self.is_dark_mode:
|
|
39
|
+
return (ft.Colors.RED_900, ft.Colors.RED_100, ft.Colors.ERROR)
|
|
40
|
+
else:
|
|
41
|
+
return (ft.Colors.RED_100, ft.Colors.RED_800, ft.Colors.ERROR)
|
|
42
|
+
|
|
43
|
+
def get_info_colors(self) -> tuple[str, str, str]:
|
|
44
|
+
"""Get (background, text, border) colors for info cards."""
|
|
45
|
+
if self.is_dark_mode:
|
|
46
|
+
return (ft.Colors.BLUE_900, ft.Colors.BLUE_100, ft.Colors.PRIMARY)
|
|
47
|
+
else:
|
|
48
|
+
return (ft.Colors.BLUE_100, ft.Colors.BLUE_800, ft.Colors.PRIMARY)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Scheduler component
|
aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduler component for {{ project_name }}.
|
|
3
|
+
|
|
4
|
+
Simple, explicit job scheduling - just import functions and schedule them.
|
|
5
|
+
Add your own jobs by importing service functions and calling scheduler.add_job().
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
12
|
+
{% if scheduler_backend != "memory" %}
|
|
13
|
+
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
|
14
|
+
from app.core.db import engine, init_database, db_session
|
|
15
|
+
from app.services.scheduler.models import APSchedulerJob
|
|
16
|
+
from app.services.system.backup import backup_database_job
|
|
17
|
+
{% elif include_scheduler and include_database %}
|
|
18
|
+
from app.services.system.backup import backup_database_job
|
|
19
|
+
{% endif %}
|
|
20
|
+
|
|
21
|
+
from app.core.log import logger
|
|
22
|
+
|
|
23
|
+
{% if scheduler_backend != "memory" %}
|
|
24
|
+
|
|
25
|
+
def _job_exists_in_database(job_id: str) -> bool:
|
|
26
|
+
"""Check if a job already exists in the persistence database using SQLModel."""
|
|
27
|
+
try:
|
|
28
|
+
from sqlmodel import select
|
|
29
|
+
|
|
30
|
+
with db_session() as session:
|
|
31
|
+
# Query for job by ID using modern SQLModel pattern
|
|
32
|
+
query = select(APSchedulerJob).where(APSchedulerJob.id == job_id)
|
|
33
|
+
result = session.exec(query)
|
|
34
|
+
job = result.first()
|
|
35
|
+
return job is not None
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.warning(f"Could not check for existing job {job_id}: {e}")
|
|
38
|
+
# If we can't check, assume it doesn't exist (safer to add than skip)
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
{% endif %}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create_scheduler() -> AsyncIOScheduler:
|
|
47
|
+
"""Create and configure the scheduler with all jobs."""
|
|
48
|
+
{% if scheduler_backend != "memory" %}
|
|
49
|
+
# Ensure database is initialized before creating jobstore
|
|
50
|
+
init_database()
|
|
51
|
+
|
|
52
|
+
# Configure SQLAlchemy jobstore for persistence
|
|
53
|
+
jobstore = SQLAlchemyJobStore(engine=engine, tablename='apscheduler_jobs')
|
|
54
|
+
jobstores = {'default': jobstore}
|
|
55
|
+
scheduler = AsyncIOScheduler(jobstores=jobstores)
|
|
56
|
+
logger.info("📊 Scheduler using SQLite database for job persistence")
|
|
57
|
+
{% else %}
|
|
58
|
+
# Use in-memory jobstore (default)
|
|
59
|
+
scheduler = AsyncIOScheduler()
|
|
60
|
+
logger.info(
|
|
61
|
+
"🕒 Scheduler running in memory mode (jobs won't persist across restarts)"
|
|
62
|
+
)
|
|
63
|
+
{% endif %}
|
|
64
|
+
|
|
65
|
+
# ============================================================================
|
|
66
|
+
# JOB SCHEDULE CONFIGURATION
|
|
67
|
+
#
|
|
68
|
+
# PERSISTENCE BEHAVIOR:
|
|
69
|
+
# - Jobs are checked against the database first
|
|
70
|
+
# - Existing jobs are preserved (respects runtime modifications)
|
|
71
|
+
# - New jobs are added from code configuration
|
|
72
|
+
# - Set SCHEDULER_FORCE_UPDATE=true to override all jobs from code
|
|
73
|
+
#
|
|
74
|
+
# To update schedules during deployment:
|
|
75
|
+
# SCHEDULER_FORCE_UPDATE=true docker-compose up -d scheduler
|
|
76
|
+
# ============================================================================
|
|
77
|
+
|
|
78
|
+
{% if scheduler_backend != "memory" or (include_scheduler and include_database) %}
|
|
79
|
+
# Check environment flag for force updates (useful during deployments)
|
|
80
|
+
force_update = os.getenv("SCHEDULER_FORCE_UPDATE", "false").lower() == "true"
|
|
81
|
+
# Database backup job (runs daily at 2 AM when database is available)
|
|
82
|
+
job_id = "database_backup"
|
|
83
|
+
{% if scheduler_backend != "memory" %}
|
|
84
|
+
# For persistent schedulers, check database directly since
|
|
85
|
+
# scheduler hasn't loaded jobs yet
|
|
86
|
+
job_exists = _job_exists_in_database(job_id)
|
|
87
|
+
{% else %}
|
|
88
|
+
# For memory schedulers, scheduler.get_job() works fine
|
|
89
|
+
existing_job = scheduler.get_job(job_id)
|
|
90
|
+
job_exists = existing_job is not None
|
|
91
|
+
{% endif %}
|
|
92
|
+
|
|
93
|
+
if not job_exists or force_update:
|
|
94
|
+
if job_exists and force_update:
|
|
95
|
+
logger.info(f"🔄 Force updating job '{job_id}' from code configuration")
|
|
96
|
+
else:
|
|
97
|
+
logger.info(f"➕ Adding new job '{job_id}'")
|
|
98
|
+
|
|
99
|
+
scheduler.add_job(
|
|
100
|
+
backup_database_job,
|
|
101
|
+
trigger="cron",
|
|
102
|
+
hour=2,
|
|
103
|
+
minute=0,
|
|
104
|
+
id=job_id,
|
|
105
|
+
name="Daily Database Backup",
|
|
106
|
+
max_instances=1,
|
|
107
|
+
coalesce=True,
|
|
108
|
+
replace_existing=True # Safe to use since we check first
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
{% if scheduler_backend != "memory" %}
|
|
112
|
+
msg = f"✅ Job '{job_id}' exists in database, preserving current configuration"
|
|
113
|
+
logger.info(msg)
|
|
114
|
+
{% else %}
|
|
115
|
+
logger.info(
|
|
116
|
+
f"✅ Job '{job_id}' exists, preserving current configuration: "
|
|
117
|
+
f"{existing_job.trigger}"
|
|
118
|
+
)
|
|
119
|
+
{% endif %}
|
|
120
|
+
{% endif %}
|
|
121
|
+
|
|
122
|
+
# Add your own scheduled jobs here by importing service functions
|
|
123
|
+
# and calling scheduler.add_job() with your custom business logic
|
|
124
|
+
|
|
125
|
+
return scheduler
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def run_scheduler() -> None:
|
|
129
|
+
"""Main scheduler runner with lifecycle management."""
|
|
130
|
+
|
|
131
|
+
logger.info("🕒 Starting {{ project_name }} Scheduler")
|
|
132
|
+
|
|
133
|
+
scheduler = create_scheduler()
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
scheduler.start()
|
|
137
|
+
logger.info("✅ Scheduler started successfully")
|
|
138
|
+
logger.info(f"📋 {len(scheduler.get_jobs())} jobs scheduled:")
|
|
139
|
+
|
|
140
|
+
for job in scheduler.get_jobs():
|
|
141
|
+
logger.info(f" • {job.name} - {job.trigger}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Keep the scheduler running
|
|
145
|
+
while True:
|
|
146
|
+
await asyncio.sleep(1)
|
|
147
|
+
|
|
148
|
+
except KeyboardInterrupt:
|
|
149
|
+
logger.info("🛑 Received shutdown signal")
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"❌ Scheduler error: {e}")
|
|
152
|
+
raise
|
|
153
|
+
finally:
|
|
154
|
+
if scheduler.running:
|
|
155
|
+
scheduler.shutdown()
|
|
156
|
+
logger.info("✅ Scheduler stopped gracefully")
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Worker Component Development Guide
|
|
2
|
+
|
|
3
|
+
This guide covers arq worker architecture patterns and development for the Aegis Stack worker component.
|
|
4
|
+
|
|
5
|
+
## Worker Architecture (arq)
|
|
6
|
+
|
|
7
|
+
Aegis Stack uses pure **arq patterns** without custom wrappers, following native arq CLI and configuration patterns.
|
|
8
|
+
|
|
9
|
+
### Worker Configuration Structure
|
|
10
|
+
|
|
11
|
+
Each worker queue has its own `WorkerSettings` class:
|
|
12
|
+
- `app/components/worker/queues/system.py` - System maintenance worker
|
|
13
|
+
- `app/components/worker/queues/load_test.py` - Load testing worker
|
|
14
|
+
- `app/components/worker/queues/media.py` - Media processing worker
|
|
15
|
+
|
|
16
|
+
### Worker Services in Docker
|
|
17
|
+
|
|
18
|
+
Workers run as separate Docker services with specific names:
|
|
19
|
+
- **`worker-system`** - System maintenance tasks (low concurrency, high reliability)
|
|
20
|
+
- **`worker-load-test`** - High-concurrency load testing (up to 50 concurrent jobs)
|
|
21
|
+
- **`worker-media`** - File/media processing (commented out by default)
|
|
22
|
+
|
|
23
|
+
## Adding Worker Tasks
|
|
24
|
+
|
|
25
|
+
### 1. Create Task Functions
|
|
26
|
+
Tasks are pure async functions in `app/components/worker/tasks/`:
|
|
27
|
+
```python
|
|
28
|
+
# app/components/worker/tasks/my_tasks.py
|
|
29
|
+
async def my_background_task() -> dict[str, str]:
|
|
30
|
+
"""My custom background task."""
|
|
31
|
+
logger.info("Running my background task")
|
|
32
|
+
|
|
33
|
+
# Your task logic here
|
|
34
|
+
await asyncio.sleep(1) # Simulate work
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"status": "completed",
|
|
38
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
39
|
+
"task": "my_background_task"
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Register with Worker Queue
|
|
44
|
+
Import and add to the appropriate `WorkerSettings`:
|
|
45
|
+
```python
|
|
46
|
+
# app/components/worker/queues/system.py
|
|
47
|
+
from app.components.worker.tasks.my_tasks import my_background_task
|
|
48
|
+
|
|
49
|
+
class WorkerSettings:
|
|
50
|
+
functions = [
|
|
51
|
+
system_health_check,
|
|
52
|
+
cleanup_temp_files,
|
|
53
|
+
my_background_task, # Add your task here
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# Standard arq configuration
|
|
57
|
+
redis_settings = RedisSettings.from_dsn(settings.REDIS_URL)
|
|
58
|
+
queue_name = "arq:queue:system"
|
|
59
|
+
max_jobs = 15
|
|
60
|
+
job_timeout = 300
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Native arq CLI Usage
|
|
64
|
+
|
|
65
|
+
### Worker Health Checks
|
|
66
|
+
```bash
|
|
67
|
+
# Check if workers can connect to Redis and validate configuration
|
|
68
|
+
uv run python -m arq app.components.worker.queues.system.WorkerSettings --check
|
|
69
|
+
uv run python -m arq app.components.worker.queues.load_test.WorkerSettings --check
|
|
70
|
+
uv run python -m arq app.components.worker.queues.media.WorkerSettings --check
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Local Worker Development
|
|
74
|
+
```bash
|
|
75
|
+
# Run worker locally with auto-reload for development
|
|
76
|
+
uv run python -m arq app.components.worker.queues.system.WorkerSettings --watch app/
|
|
77
|
+
|
|
78
|
+
# Run worker in burst mode (process all jobs and exit)
|
|
79
|
+
uv run python -m arq app.components.worker.queues.system.WorkerSettings --burst
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Worker Configuration in Health Checks
|
|
83
|
+
|
|
84
|
+
The health system reads worker configuration from `app/core/config.py` but workers themselves use their own `WorkerSettings` classes:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# Health system reads this for monitoring
|
|
88
|
+
WORKER_QUEUES: dict[str, dict[str, Any]] = {
|
|
89
|
+
"system": {
|
|
90
|
+
"description": "System maintenance and monitoring tasks",
|
|
91
|
+
"max_jobs": 15,
|
|
92
|
+
"timeout_seconds": 300,
|
|
93
|
+
"queue_name": "arq:queue:system",
|
|
94
|
+
},
|
|
95
|
+
"load_test": {
|
|
96
|
+
"description": "Load testing and performance testing",
|
|
97
|
+
"max_jobs": 50,
|
|
98
|
+
"timeout_seconds": 60,
|
|
99
|
+
"queue_name": "arq:queue:load_test",
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# But workers use their own WorkerSettings classes for actual configuration
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Key Differences from Custom Worker Systems
|
|
107
|
+
|
|
108
|
+
### ✅ What We Do (Pure arq):
|
|
109
|
+
- Use native arq CLI: `python -m arq WorkerSettings`
|
|
110
|
+
- Standard `WorkerSettings` classes with `functions` list
|
|
111
|
+
- Direct task imports into worker configurations
|
|
112
|
+
- Native arq health checking and monitoring
|
|
113
|
+
|
|
114
|
+
### ❌ What We Don't Do (Avoided custom patterns):
|
|
115
|
+
- Custom worker wrapper classes
|
|
116
|
+
- Central worker registry systems
|
|
117
|
+
- Custom CLI commands for workers
|
|
118
|
+
- Configuration-driven task discovery
|
|
119
|
+
|
|
120
|
+
This approach keeps workers transparent and lets developers use arq exactly as documented in the official arq documentation.
|
|
121
|
+
|
|
122
|
+
## Docker Worker Debugging Commands
|
|
123
|
+
|
|
124
|
+
### View Worker Logs
|
|
125
|
+
```bash
|
|
126
|
+
# View specific worker logs
|
|
127
|
+
docker compose logs worker-system # System worker logs
|
|
128
|
+
docker compose logs worker-load-test # Load test worker logs
|
|
129
|
+
docker compose logs -f worker-system # Follow system worker in real-time
|
|
130
|
+
docker compose logs -f worker-load-test # Follow load test worker in real-time
|
|
131
|
+
|
|
132
|
+
# View all workers at once
|
|
133
|
+
docker compose logs -f worker-system worker-load-test
|
|
134
|
+
|
|
135
|
+
# Filter for errors in specific workers
|
|
136
|
+
docker compose logs worker-load-test | grep "ERROR\|failed\|TypeError"
|
|
137
|
+
|
|
138
|
+
# Monitor worker processes and resources
|
|
139
|
+
docker compose exec worker-system ps aux # Check system worker processes
|
|
140
|
+
docker compose exec worker-load-test ps aux # Check load test worker processes
|
|
141
|
+
docker stats worker-system worker-load-test # Monitor resource usage
|
|
142
|
+
docker compose restart worker-system # Restart specific worker
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Essential Docker Log Monitoring
|
|
146
|
+
|
|
147
|
+
**Check Worker Logs for Load Test Issues:**
|
|
148
|
+
```bash
|
|
149
|
+
# View real-time worker logs
|
|
150
|
+
docker compose logs -f worker
|
|
151
|
+
|
|
152
|
+
# Check specific container logs
|
|
153
|
+
docker logs <container-id>
|
|
154
|
+
|
|
155
|
+
# View logs with timestamps
|
|
156
|
+
docker compose logs --timestamps worker
|
|
157
|
+
|
|
158
|
+
# Search logs for specific errors
|
|
159
|
+
docker compose logs worker | grep "TypeError\|failed"
|
|
160
|
+
|
|
161
|
+
# Check all service logs
|
|
162
|
+
docker compose logs -f
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Load Test Debugging Workflow:**
|
|
166
|
+
1. **Run Load Test**: `uv run {{ project_slug }} load-test run --type io_simulation --tasks 10`
|
|
167
|
+
2. **Monitor Worker Logs**: `docker compose logs -f worker-load-test` (in separate terminal)
|
|
168
|
+
3. **Look for**:
|
|
169
|
+
- `TypeError` messages indicating parameter mismatches
|
|
170
|
+
- Task completion vs failure counts (`j_complete=X j_failed=Y`)
|
|
171
|
+
- Task execution times and errors
|
|
172
|
+
- Redis connection issues
|
|
173
|
+
|
|
174
|
+
**Common Error Patterns:**
|
|
175
|
+
- `TypeError: function() got an unexpected keyword argument 'param'` - Parameter mismatch between orchestrator and task function
|
|
176
|
+
- `j_failed=X` increasing rapidly - Worker tasks failing due to code issues
|
|
177
|
+
- `Redis connection failed` - Infrastructure connectivity problems
|
|
178
|
+
- `delayed=X.XXs` - Queue saturation or worker overload
|
|
179
|
+
|
|
180
|
+
**System Health Verification:**
|
|
181
|
+
```bash
|
|
182
|
+
# Check all containers
|
|
183
|
+
docker compose ps
|
|
184
|
+
|
|
185
|
+
# Check system health via API
|
|
186
|
+
uv run {{ project_slug }} health status --detailed
|
|
187
|
+
|
|
188
|
+
# Monitor Redis connection
|
|
189
|
+
docker compose logs redis
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Worker Development Best Practices
|
|
193
|
+
|
|
194
|
+
### Task Design Patterns
|
|
195
|
+
1. **Pure Functions** - Tasks should be self-contained with minimal dependencies
|
|
196
|
+
2. **Error Handling** - Always include try/catch with proper logging
|
|
197
|
+
3. **Return Values** - Return structured data for monitoring and debugging
|
|
198
|
+
4. **Timeouts** - Set appropriate timeouts for different task types
|
|
199
|
+
5. **Retry Logic** - Use arq's built-in retry mechanisms
|
|
200
|
+
|
|
201
|
+
### Queue Management
|
|
202
|
+
1. **Separate Concerns** - Use different queues for different types of work
|
|
203
|
+
2. **Concurrency Limits** - Set appropriate max_jobs for each queue type
|
|
204
|
+
3. **Priority Queues** - Use different queues for different priorities
|
|
205
|
+
4. **Dead Letter Queues** - Monitor failed jobs and implement recovery
|
|
206
|
+
|
|
207
|
+
### Monitoring and Observability
|
|
208
|
+
1. **Structured Logging** - Use structured logs for easy parsing
|
|
209
|
+
2. **Metrics Collection** - Track task execution times and success rates
|
|
210
|
+
3. **Health Checks** - Implement health checks for worker availability
|
|
211
|
+
4. **Alerting** - Set up alerts for queue depth and failure rates
|
|
212
|
+
|
|
213
|
+
This approach ensures workers are maintainable, debuggable, and follow established patterns.
|
aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Worker component constants.
|
|
3
|
+
|
|
4
|
+
This module contains constants specific to the worker component,
|
|
5
|
+
keeping them separate from global application constants.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TaskNames:
|
|
12
|
+
"""Worker task function names - must match actual function names in code."""
|
|
13
|
+
|
|
14
|
+
# Orchestrator
|
|
15
|
+
LOAD_TEST_ORCHESTRATOR = "load_test_orchestrator"
|
|
16
|
+
|
|
17
|
+
# Load test tasks
|
|
18
|
+
CPU_INTENSIVE_TASK = "cpu_intensive_task"
|
|
19
|
+
IO_SIMULATION_TASK = "io_simulation_task"
|
|
20
|
+
MEMORY_OPERATIONS_TASK = "memory_operations_task"
|
|
21
|
+
FAILURE_TESTING_TASK = "failure_testing_task"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LoadTestTypes(Enum):
|
|
25
|
+
"""Load test type identifiers for task selection."""
|
|
26
|
+
|
|
27
|
+
CPU_INTENSIVE = "cpu_intensive"
|
|
28
|
+
IO_SIMULATION = "io_simulation"
|
|
29
|
+
MEMORY_OPERATIONS = "memory_operations"
|
|
30
|
+
FAILURE_TESTING = "failure_testing"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Worker pool management for client-side task enqueueing.
|
|
3
|
+
|
|
4
|
+
This module provides Redis connection pooling and caching for enqueueing tasks
|
|
5
|
+
to worker queues. Separated from worker management to allow clean architectural
|
|
6
|
+
separation between client-side enqueueing and worker-side processing.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from app.core.config import get_default_queue, settings
|
|
10
|
+
from app.core.log import logger
|
|
11
|
+
from arq import create_pool
|
|
12
|
+
from arq.connections import ArqRedis, RedisSettings
|
|
13
|
+
|
|
14
|
+
# Global pool cache to avoid creating new Redis connections repeatedly
|
|
15
|
+
_pool_cache: dict[str, ArqRedis] = {}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def get_queue_pool(queue_type: str | None = None) -> tuple[ArqRedis, str]:
|
|
19
|
+
"""
|
|
20
|
+
Get Redis pool for enqueuing tasks to specific functional queue.
|
|
21
|
+
|
|
22
|
+
Uses connection pooling to avoid creating new Redis connections repeatedly.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
queue_type: Functional queue type (defaults to configured default queue)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Tuple of (pool, queue_name) for enqueueing tasks
|
|
29
|
+
"""
|
|
30
|
+
# Use configured default queue if not specified
|
|
31
|
+
if queue_type is None:
|
|
32
|
+
queue_type = get_default_queue()
|
|
33
|
+
|
|
34
|
+
from app.core.config import get_available_queues, is_valid_queue
|
|
35
|
+
|
|
36
|
+
if not is_valid_queue(queue_type):
|
|
37
|
+
available = get_available_queues()
|
|
38
|
+
raise ValueError(f"Invalid queue type '{queue_type}'. Available: {available}")
|
|
39
|
+
|
|
40
|
+
from app.components.worker.registry import get_queue_metadata
|
|
41
|
+
|
|
42
|
+
queue_name = get_queue_metadata(queue_type)["queue_name"]
|
|
43
|
+
|
|
44
|
+
# Check cache first to avoid creating new Redis connections
|
|
45
|
+
redis_url = (
|
|
46
|
+
settings.redis_url_effective
|
|
47
|
+
if hasattr(settings, "redis_url_effective")
|
|
48
|
+
else settings.REDIS_URL
|
|
49
|
+
)
|
|
50
|
+
cache_key = f"{queue_type}_{redis_url}"
|
|
51
|
+
|
|
52
|
+
if cache_key in _pool_cache:
|
|
53
|
+
# Reuse existing pool
|
|
54
|
+
cached_pool = _pool_cache[cache_key]
|
|
55
|
+
try:
|
|
56
|
+
# Test if pool is still valid by doing a quick ping
|
|
57
|
+
await cached_pool.ping()
|
|
58
|
+
return cached_pool, queue_name
|
|
59
|
+
except Exception:
|
|
60
|
+
# Pool is stale, remove from cache and create new one
|
|
61
|
+
logger.debug(f"Removing stale pool from cache: {cache_key}")
|
|
62
|
+
del _pool_cache[cache_key]
|
|
63
|
+
|
|
64
|
+
# Create new Redis pool and cache it with improved connection settings
|
|
65
|
+
redis_url = (
|
|
66
|
+
settings.redis_url_effective
|
|
67
|
+
if hasattr(settings, "redis_url_effective")
|
|
68
|
+
else settings.REDIS_URL
|
|
69
|
+
)
|
|
70
|
+
base_settings = RedisSettings.from_dsn(redis_url)
|
|
71
|
+
redis_settings = RedisSettings(
|
|
72
|
+
host=base_settings.host,
|
|
73
|
+
port=base_settings.port,
|
|
74
|
+
database=base_settings.database,
|
|
75
|
+
password=base_settings.password,
|
|
76
|
+
conn_timeout=settings.REDIS_CONN_TIMEOUT,
|
|
77
|
+
conn_retries=settings.REDIS_CONN_RETRIES,
|
|
78
|
+
conn_retry_delay=settings.REDIS_CONN_RETRY_DELAY,
|
|
79
|
+
)
|
|
80
|
+
pool = await create_pool(redis_settings)
|
|
81
|
+
_pool_cache[cache_key] = pool
|
|
82
|
+
|
|
83
|
+
logger.debug(f"Created and cached new Redis pool: {cache_key}")
|
|
84
|
+
return pool, queue_name
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def clear_pool_cache() -> None:
|
|
88
|
+
"""Clear all cached pools. Use during shutdown or for testing."""
|
|
89
|
+
for cache_key, pool in _pool_cache.items():
|
|
90
|
+
try:
|
|
91
|
+
await pool.aclose()
|
|
92
|
+
logger.debug(f"Closed cached pool: {cache_key}")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.warning(f"Error closing cached pool {cache_key}: {e}")
|
|
95
|
+
|
|
96
|
+
_pool_cache.clear()
|
|
97
|
+
logger.info("Pool cache cleared")
|
aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Worker queue configurations using native arq WorkerSettings."""
|
aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Load test worker queue configuration.
|
|
3
|
+
|
|
4
|
+
Handles load testing orchestration and synthetic workload tasks using native arq
|
|
5
|
+
patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import load test tasks
|
|
9
|
+
from app.components.worker.tasks.load_tasks import (
|
|
10
|
+
cpu_intensive_task,
|
|
11
|
+
failure_testing_task,
|
|
12
|
+
io_simulation_task,
|
|
13
|
+
memory_operations_task,
|
|
14
|
+
)
|
|
15
|
+
from app.components.worker.tasks.system_tasks import (
|
|
16
|
+
load_test_orchestrator,
|
|
17
|
+
)
|
|
18
|
+
from app.core.config import settings
|
|
19
|
+
from arq.connections import RedisSettings
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkerSettings:
|
|
23
|
+
"""Load testing worker configuration."""
|
|
24
|
+
|
|
25
|
+
# Human-readable description
|
|
26
|
+
description = "Load testing and performance testing"
|
|
27
|
+
|
|
28
|
+
# Task functions for this queue
|
|
29
|
+
functions = [
|
|
30
|
+
# Load test orchestrator
|
|
31
|
+
load_test_orchestrator,
|
|
32
|
+
# Synthetic workload tasks
|
|
33
|
+
cpu_intensive_task,
|
|
34
|
+
io_simulation_task,
|
|
35
|
+
memory_operations_task,
|
|
36
|
+
failure_testing_task,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# arq configuration with improved connection settings
|
|
40
|
+
base_settings = RedisSettings.from_dsn(settings.REDIS_URL)
|
|
41
|
+
redis_settings = RedisSettings(
|
|
42
|
+
host=base_settings.host,
|
|
43
|
+
port=base_settings.port,
|
|
44
|
+
database=base_settings.database,
|
|
45
|
+
password=base_settings.password,
|
|
46
|
+
conn_timeout=settings.REDIS_CONN_TIMEOUT,
|
|
47
|
+
conn_retries=settings.REDIS_CONN_RETRIES,
|
|
48
|
+
conn_retry_delay=settings.REDIS_CONN_RETRY_DELAY,
|
|
49
|
+
)
|
|
50
|
+
queue_name = "arq:queue:load_test"
|
|
51
|
+
max_jobs = 50 # High concurrency for load testing
|
|
52
|
+
job_timeout = 60 # Quick tasks
|
|
53
|
+
keep_result = settings.WORKER_KEEP_RESULT_SECONDS
|
|
54
|
+
max_tries = settings.WORKER_MAX_TRIES
|
|
55
|
+
health_check_interval = settings.WORKER_HEALTH_CHECK_INTERVAL
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Media worker queue configuration.
|
|
3
|
+
|
|
4
|
+
Handles image processing, file operations, and media transformations using native
|
|
5
|
+
arq patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from app.core.config import settings
|
|
11
|
+
from arq.connections import RedisSettings
|
|
12
|
+
|
|
13
|
+
# Import media tasks (when available)
|
|
14
|
+
# from app.components.worker.tasks.media_tasks import (
|
|
15
|
+
# image_resize,
|
|
16
|
+
# video_encode,
|
|
17
|
+
# file_convert,
|
|
18
|
+
# )
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WorkerSettings:
|
|
22
|
+
"""Media processing worker configuration."""
|
|
23
|
+
|
|
24
|
+
# Human-readable description
|
|
25
|
+
description = "Image and file processing"
|
|
26
|
+
|
|
27
|
+
# Task functions for this queue
|
|
28
|
+
functions: list[Any] = [
|
|
29
|
+
# Media processing tasks will be added here
|
|
30
|
+
# Example: image_resize, video_encode, file_convert
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# arq configuration with improved connection settings
|
|
34
|
+
base_settings = RedisSettings.from_dsn(settings.REDIS_URL)
|
|
35
|
+
redis_settings = RedisSettings(
|
|
36
|
+
host=base_settings.host,
|
|
37
|
+
port=base_settings.port,
|
|
38
|
+
database=base_settings.database,
|
|
39
|
+
password=base_settings.password,
|
|
40
|
+
conn_timeout=settings.REDIS_CONN_TIMEOUT,
|
|
41
|
+
conn_retries=settings.REDIS_CONN_RETRIES,
|
|
42
|
+
conn_retry_delay=settings.REDIS_CONN_RETRY_DELAY,
|
|
43
|
+
)
|
|
44
|
+
queue_name = "arq:queue:media"
|
|
45
|
+
max_jobs = 10 # I/O-bound file operations
|
|
46
|
+
job_timeout = 600 # 10 minutes - file processing can take time
|
|
47
|
+
keep_result = settings.WORKER_KEEP_RESULT_SECONDS
|
|
48
|
+
max_tries = settings.WORKER_MAX_TRIES
|
|
49
|
+
health_check_interval = settings.WORKER_HEALTH_CHECK_INTERVAL
|