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,92 @@
|
|
|
1
|
+
# app/core/log.py
|
|
2
|
+
"""
|
|
3
|
+
Core logging configuration for the application.
|
|
4
|
+
|
|
5
|
+
This module sets up structlog to provide structured, context-aware logging.
|
|
6
|
+
It supports both human-readable console output for development and JSON
|
|
7
|
+
output for production environments.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
import structlog
|
|
14
|
+
from app.core.config import settings
|
|
15
|
+
from structlog.types import Processor
|
|
16
|
+
|
|
17
|
+
# A global logger instance for easy access throughout the application
|
|
18
|
+
logger: structlog.stdlib.BoundLogger = structlog.get_logger()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def setup_logging() -> None:
|
|
22
|
+
"""
|
|
23
|
+
Configures logging for the entire application.
|
|
24
|
+
|
|
25
|
+
This function sets up structlog with processors for structured logging.
|
|
26
|
+
It routes all standard library logging through structlog to ensure
|
|
27
|
+
consistent log formats. The output format is determined by the APP_ENV
|
|
28
|
+
setting (dev-friendly console format or production-ready JSON format).
|
|
29
|
+
"""
|
|
30
|
+
# Type hint for the list of processors
|
|
31
|
+
shared_processors: list[Processor] = [
|
|
32
|
+
structlog.stdlib.add_logger_name,
|
|
33
|
+
structlog.stdlib.add_log_level,
|
|
34
|
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
35
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
36
|
+
structlog.processors.StackInfoRenderer(),
|
|
37
|
+
structlog.processors.format_exc_info,
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# Configure structlog
|
|
41
|
+
structlog.configure(
|
|
42
|
+
processors=shared_processors
|
|
43
|
+
+ [
|
|
44
|
+
# Prepare event dict for `ProcessorFormatter`.
|
|
45
|
+
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
|
46
|
+
],
|
|
47
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
48
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
49
|
+
cache_logger_on_first_use=True,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Define the formatter based on the environment
|
|
53
|
+
if settings.APP_ENV == "dev":
|
|
54
|
+
formatter = structlog.stdlib.ProcessorFormatter(
|
|
55
|
+
# The final processor formats the log entry for console output.
|
|
56
|
+
processor=structlog.dev.ConsoleRenderer(colors=True),
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
formatter = structlog.stdlib.ProcessorFormatter(
|
|
60
|
+
# The final processor formats the log entry as JSON.
|
|
61
|
+
processor=structlog.processors.JSONRenderer(),
|
|
62
|
+
# Remove metadata added by ProcessorFormatter
|
|
63
|
+
foreign_pre_chain=shared_processors,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Configure the root logger
|
|
67
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
68
|
+
handler.setFormatter(formatter)
|
|
69
|
+
root_logger = logging.getLogger()
|
|
70
|
+
|
|
71
|
+
# CRITICAL: Set log level BEFORE adding handler
|
|
72
|
+
# This ensures all loggers (including import-time loggers) respect the level
|
|
73
|
+
log_level = settings.LOG_LEVEL.upper()
|
|
74
|
+
root_logger.setLevel(getattr(logging, log_level))
|
|
75
|
+
|
|
76
|
+
# Add handler after level is set
|
|
77
|
+
root_logger.addHandler(handler)
|
|
78
|
+
|
|
79
|
+
# Adjust log levels for noisy third-party libraries
|
|
80
|
+
logging.getLogger("flet_core").setLevel(logging.INFO)
|
|
81
|
+
logging.getLogger("flet_runtime").setLevel(logging.INFO)
|
|
82
|
+
logging.getLogger("flet_fastapi").setLevel(logging.INFO)
|
|
83
|
+
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
|
84
|
+
|
|
85
|
+
log_format = "DEV" if settings.APP_ENV == "dev" else "JSON"
|
|
86
|
+
logger.info(
|
|
87
|
+
"Logging setup complete",
|
|
88
|
+
level=log_level,
|
|
89
|
+
log_format=log_format,
|
|
90
|
+
root_level=root_logger.level,
|
|
91
|
+
effective_level=root_logger.getEffectiveLevel(),
|
|
92
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Security utilities for authentication and authorization."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime, timedelta
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from app.core.config import settings
|
|
7
|
+
from jose import JWTError, jwt
|
|
8
|
+
from passlib.context import CryptContext
|
|
9
|
+
|
|
10
|
+
pwd_context = CryptContext(
|
|
11
|
+
schemes=["bcrypt"],
|
|
12
|
+
deprecated="auto",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _truncate_password(password: str) -> str:
|
|
17
|
+
"""Truncate password to 72 bytes for bcrypt compatibility.
|
|
18
|
+
|
|
19
|
+
bcrypt has a 72-byte limit. We explicitly truncate here rather than
|
|
20
|
+
rely on library defaults for clarity and consistency.
|
|
21
|
+
"""
|
|
22
|
+
return password[:72]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_access_token(
|
|
26
|
+
data: dict[str, Any], expires_delta: timedelta | None = None
|
|
27
|
+
) -> str:
|
|
28
|
+
"""Create a JWT access token."""
|
|
29
|
+
to_encode = data.copy()
|
|
30
|
+
if expires_delta:
|
|
31
|
+
expire = datetime.now(UTC) + expires_delta
|
|
32
|
+
else:
|
|
33
|
+
expire = datetime.now(UTC) + timedelta(
|
|
34
|
+
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
to_encode.update({"exp": expire})
|
|
38
|
+
encoded_jwt = jwt.encode(
|
|
39
|
+
to_encode, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM
|
|
40
|
+
)
|
|
41
|
+
return encoded_jwt
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def verify_token(token: str) -> dict[str, Any] | None:
|
|
45
|
+
"""Verify and decode a JWT token."""
|
|
46
|
+
try:
|
|
47
|
+
payload = jwt.decode(
|
|
48
|
+
token, settings.SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]
|
|
49
|
+
)
|
|
50
|
+
return payload
|
|
51
|
+
except JWTError:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_password_hash(password: str) -> str:
|
|
56
|
+
"""Hash a password."""
|
|
57
|
+
return pwd_context.hash(_truncate_password(password))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
61
|
+
"""Verify a password against its hash."""
|
|
62
|
+
return pwd_context.verify(_truncate_password(plain_password), hashed_password)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Entry points for different Aegis Stack execution modes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Scheduler entrypoint for {{ project_name }}.
|
|
4
|
+
|
|
5
|
+
This entrypoint starts the scheduler component.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
from app.components.scheduler.main import run_scheduler
|
|
11
|
+
from app.core.log import setup_logging
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def main() -> None:
|
|
15
|
+
"""Main scheduler entry point"""
|
|
16
|
+
setup_logging()
|
|
17
|
+
await run_scheduler()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Web server entry point for Aegis Stack.
|
|
4
|
+
Runs FastAPI + Flet only (clean separation of concerns).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uvicorn
|
|
8
|
+
from app.core.config import settings
|
|
9
|
+
from app.core.log import logger, setup_logging
|
|
10
|
+
from app.integrations.main import create_integrated_app
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
"""Main webserver entry point"""
|
|
15
|
+
setup_logging()
|
|
16
|
+
logger.info("Starting Aegis Stack Web Server...")
|
|
17
|
+
|
|
18
|
+
# Run the web server
|
|
19
|
+
if settings.AUTO_RELOAD:
|
|
20
|
+
# When reload is enabled, uvicorn requires an import string
|
|
21
|
+
uvicorn.run(
|
|
22
|
+
"app.integrations.main:create_integrated_app",
|
|
23
|
+
factory=True,
|
|
24
|
+
host="0.0.0.0",
|
|
25
|
+
port=settings.PORT,
|
|
26
|
+
reload=True,
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
# Use the integration layer (handles webserver hooks, service discovery, etc.)
|
|
30
|
+
app = create_integrated_app()
|
|
31
|
+
uvicorn.run(
|
|
32
|
+
app,
|
|
33
|
+
host="0.0.0.0",
|
|
34
|
+
port=settings.PORT,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from collections.abc import AsyncGenerator
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
|
|
4
|
+
import flet.fastapi as flet_fastapi
|
|
5
|
+
from app.components.backend.hooks import backend_hooks
|
|
6
|
+
from app.components.backend.main import create_backend_app
|
|
7
|
+
from app.components.frontend.main import create_frontend_app
|
|
8
|
+
from app.core.config import settings
|
|
9
|
+
from app.core.log import logger
|
|
10
|
+
from fastapi import FastAPI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@asynccontextmanager
|
|
14
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
15
|
+
"""
|
|
16
|
+
Application lifespan manager.
|
|
17
|
+
Handles startup/shutdown concerns using component-specific hooks.
|
|
18
|
+
"""
|
|
19
|
+
# --- STARTUP ---
|
|
20
|
+
logger.info("--- Running application startup ---")
|
|
21
|
+
|
|
22
|
+
# Discover startup and shutdown hooks
|
|
23
|
+
await backend_hooks.discover_lifespan_hooks()
|
|
24
|
+
|
|
25
|
+
# Start Flet app manager
|
|
26
|
+
await flet_fastapi.app_manager.start()
|
|
27
|
+
|
|
28
|
+
# Execute backend startup hooks
|
|
29
|
+
await backend_hooks.execute_startup_hooks()
|
|
30
|
+
|
|
31
|
+
logger.info("--- Application startup complete ---")
|
|
32
|
+
|
|
33
|
+
yield
|
|
34
|
+
|
|
35
|
+
# --- SHUTDOWN ---
|
|
36
|
+
logger.info("--- Running application shutdown ---")
|
|
37
|
+
|
|
38
|
+
# Execute backend shutdown hooks
|
|
39
|
+
await backend_hooks.execute_shutdown_hooks()
|
|
40
|
+
|
|
41
|
+
# Stop Flet app manager
|
|
42
|
+
await flet_fastapi.app_manager.shutdown()
|
|
43
|
+
|
|
44
|
+
logger.info("--- Application shutdown complete ---")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def create_integrated_app() -> FastAPI:
|
|
48
|
+
"""
|
|
49
|
+
Creates the integrated Flet+FastAPI application using the officially
|
|
50
|
+
recommended pattern and component-specific hooks.
|
|
51
|
+
"""
|
|
52
|
+
app = FastAPI(lifespan=lifespan)
|
|
53
|
+
|
|
54
|
+
create_backend_app(app)
|
|
55
|
+
# Create and mount the Flet app using the flet.fastapi module
|
|
56
|
+
# First, get the actual session handler function from the factory
|
|
57
|
+
session_handler = create_frontend_app()
|
|
58
|
+
flet_app = flet_fastapi.app(session_handler, assets_dir=settings.FLET_ASSETS_DIR)
|
|
59
|
+
# Mount Flet at /dashboard to avoid intercepting FastAPI routes like /health
|
|
60
|
+
app.mount("/dashboard", flet_app)
|
|
61
|
+
return app
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Data models for the application."""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""User data models."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import EmailStr
|
|
6
|
+
from sqlmodel import Field, SQLModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UserBase(SQLModel):
|
|
10
|
+
"""Base user model with shared fields."""
|
|
11
|
+
|
|
12
|
+
email: EmailStr = Field(unique=True, index=True)
|
|
13
|
+
full_name: str | None = None
|
|
14
|
+
is_active: bool = Field(default=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class User(UserBase, table=True):
|
|
18
|
+
"""User database model."""
|
|
19
|
+
|
|
20
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
21
|
+
hashed_password: str
|
|
22
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
23
|
+
updated_at: datetime | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UserCreate(UserBase):
|
|
27
|
+
"""User creation model."""
|
|
28
|
+
|
|
29
|
+
password: str = Field(min_length=8)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UserLogin(SQLModel):
|
|
33
|
+
"""User login model."""
|
|
34
|
+
|
|
35
|
+
email: str
|
|
36
|
+
password: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class UserResponse(UserBase):
|
|
40
|
+
"""User response model (excludes sensitive data)."""
|
|
41
|
+
|
|
42
|
+
id: int
|
|
43
|
+
created_at: datetime
|
|
44
|
+
updated_at: datetime | None = None
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Business logic services
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI service configuration models.
|
|
3
|
+
|
|
4
|
+
Configuration management for AI service providers, models, and settings.
|
|
5
|
+
Integrates with main application settings through app.core.config.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from .models import (
|
|
13
|
+
AIProvider,
|
|
14
|
+
ProviderConfig,
|
|
15
|
+
get_provider_capabilities,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AIServiceConfig(BaseModel):
|
|
20
|
+
"""
|
|
21
|
+
AI service configuration that integrates with main app settings.
|
|
22
|
+
|
|
23
|
+
This class provides convenience methods and validation for AI service
|
|
24
|
+
configuration while the actual settings live in app.core.config.Settings.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
enabled: bool = True
|
|
28
|
+
provider: AIProvider = (
|
|
29
|
+
AIProvider.PUBLIC
|
|
30
|
+
) # Default to public endpoints (no API key required)
|
|
31
|
+
model: str = "gpt-3.5-turbo" # Default to widely supported model
|
|
32
|
+
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
|
|
33
|
+
max_tokens: int = Field(default=1000, gt=0, le=8000)
|
|
34
|
+
timeout_seconds: float = Field(default=30.0, gt=0)
|
|
35
|
+
|
|
36
|
+
class Config:
|
|
37
|
+
use_enum_values = True
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_settings(cls, settings: Any) -> "AIServiceConfig":
|
|
41
|
+
"""Create configuration from main application settings."""
|
|
42
|
+
return cls(
|
|
43
|
+
enabled=getattr(settings, "AI_ENABLED", True),
|
|
44
|
+
provider=AIProvider(getattr(settings, "AI_PROVIDER", "public")),
|
|
45
|
+
model=getattr(settings, "AI_MODEL", "gpt-3.5-turbo"),
|
|
46
|
+
temperature=getattr(settings, "AI_TEMPERATURE", 0.7),
|
|
47
|
+
max_tokens=getattr(settings, "AI_MAX_TOKENS", 1000),
|
|
48
|
+
timeout_seconds=getattr(settings, "AI_TIMEOUT_SECONDS", 30.0),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def get_provider_config(self, settings: Any) -> ProviderConfig:
|
|
52
|
+
"""Get provider-specific configuration."""
|
|
53
|
+
# Get API key based on provider
|
|
54
|
+
api_key_mapping = {
|
|
55
|
+
AIProvider.OPENAI: getattr(settings, "OPENAI_API_KEY", None),
|
|
56
|
+
AIProvider.ANTHROPIC: getattr(settings, "ANTHROPIC_API_KEY", None),
|
|
57
|
+
AIProvider.GOOGLE: getattr(settings, "GOOGLE_API_KEY", None),
|
|
58
|
+
AIProvider.GROQ: getattr(settings, "GROQ_API_KEY", None),
|
|
59
|
+
AIProvider.MISTRAL: getattr(settings, "MISTRAL_API_KEY", None),
|
|
60
|
+
AIProvider.COHERE: getattr(settings, "COHERE_API_KEY", None),
|
|
61
|
+
AIProvider.PUBLIC: None, # No API key required for public endpoints
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return ProviderConfig(
|
|
65
|
+
name=self.provider,
|
|
66
|
+
api_key=api_key_mapping.get(self.provider),
|
|
67
|
+
max_tokens=self.max_tokens,
|
|
68
|
+
temperature=self.temperature,
|
|
69
|
+
timeout_seconds=self.timeout_seconds,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def validate_configuration(self, settings: Any) -> list[str]:
|
|
73
|
+
"""
|
|
74
|
+
Validate AI service configuration and return list of issues.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of validation error messages (empty if valid)
|
|
78
|
+
"""
|
|
79
|
+
errors = []
|
|
80
|
+
|
|
81
|
+
if not self.enabled:
|
|
82
|
+
return errors # Skip validation if disabled
|
|
83
|
+
|
|
84
|
+
# Check if provider is supported
|
|
85
|
+
capabilities = get_provider_capabilities(self.provider)
|
|
86
|
+
if not capabilities:
|
|
87
|
+
errors.append(f"Unsupported provider: {self.provider}")
|
|
88
|
+
|
|
89
|
+
# Check API key requirement (only PUBLIC provider requires no API key)
|
|
90
|
+
provider_config = self.get_provider_config(settings)
|
|
91
|
+
|
|
92
|
+
if self.provider != AIProvider.PUBLIC and not provider_config.api_key:
|
|
93
|
+
errors.append(
|
|
94
|
+
f"Missing API key for {self.provider} provider. "
|
|
95
|
+
f"Set {self.provider.upper()}_API_KEY environment variable."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Note: Token limits vary by model within each provider,
|
|
99
|
+
# so we don't validate them here
|
|
100
|
+
|
|
101
|
+
return errors
|
|
102
|
+
|
|
103
|
+
def is_provider_available(self, settings: Any) -> bool:
|
|
104
|
+
"""Check if the configured provider is available and properly configured."""
|
|
105
|
+
errors = self.validate_configuration(settings)
|
|
106
|
+
return len(errors) == 0
|
|
107
|
+
|
|
108
|
+
def get_available_providers(self, settings: Any) -> list[AIProvider]:
|
|
109
|
+
"""Get list of providers that are properly configured."""
|
|
110
|
+
available = []
|
|
111
|
+
|
|
112
|
+
for provider in AIProvider:
|
|
113
|
+
# Temporarily check each provider
|
|
114
|
+
temp_config = AIServiceConfig(
|
|
115
|
+
enabled=True,
|
|
116
|
+
provider=provider,
|
|
117
|
+
model=self.model,
|
|
118
|
+
temperature=self.temperature,
|
|
119
|
+
max_tokens=self.max_tokens,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if len(temp_config.validate_configuration(settings)) == 0:
|
|
123
|
+
available.append(provider)
|
|
124
|
+
|
|
125
|
+
return available
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_ai_config(settings: Any) -> AIServiceConfig:
|
|
129
|
+
"""Get AI service configuration from application settings."""
|
|
130
|
+
return AIServiceConfig.from_settings(settings)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI conversation management.
|
|
3
|
+
|
|
4
|
+
In-memory conversation storage and management for AI chat sessions.
|
|
5
|
+
This provides conversation persistence during application runtime.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import uuid
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from app.core.log import logger
|
|
13
|
+
|
|
14
|
+
from .models import AIProvider, Conversation
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConversationManager:
|
|
18
|
+
"""
|
|
19
|
+
Manages AI conversations in memory.
|
|
20
|
+
|
|
21
|
+
This is a simple in-memory implementation. In production, you might want
|
|
22
|
+
to use a database or external storage for persistence across restarts.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
"""Initialize conversation manager with empty storage."""
|
|
27
|
+
self.conversations: dict[str, Conversation] = {}
|
|
28
|
+
# logger.info("Conversation manager initialized")
|
|
29
|
+
|
|
30
|
+
def create_conversation(
|
|
31
|
+
self,
|
|
32
|
+
provider: AIProvider,
|
|
33
|
+
model: str,
|
|
34
|
+
user_id: str = "default",
|
|
35
|
+
conversation_id: str | None = None,
|
|
36
|
+
) -> Conversation:
|
|
37
|
+
"""
|
|
38
|
+
Create a new conversation.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
provider: AI provider being used
|
|
42
|
+
model: Model name
|
|
43
|
+
user_id: User identifier
|
|
44
|
+
conversation_id: Optional custom conversation ID
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Conversation: The created conversation
|
|
48
|
+
"""
|
|
49
|
+
if conversation_id is None:
|
|
50
|
+
conversation_id = str(uuid.uuid4())
|
|
51
|
+
|
|
52
|
+
conversation = Conversation(
|
|
53
|
+
id=conversation_id,
|
|
54
|
+
provider=provider,
|
|
55
|
+
model=model,
|
|
56
|
+
metadata={"user_id": user_id, "created_by": "ai_service"},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.conversations[conversation_id] = conversation
|
|
60
|
+
# logger.debug(f"Created conversation {conversation_id} for user {user_id}")
|
|
61
|
+
|
|
62
|
+
return conversation
|
|
63
|
+
|
|
64
|
+
def get_conversation(self, conversation_id: str) -> Conversation | None:
|
|
65
|
+
"""
|
|
66
|
+
Get a conversation by ID.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
conversation_id: The conversation identifier
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Conversation | None: The conversation if found, None otherwise
|
|
73
|
+
"""
|
|
74
|
+
return self.conversations.get(conversation_id)
|
|
75
|
+
|
|
76
|
+
def save_conversation(self, conversation: Conversation) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Save a conversation (update in memory storage).
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
conversation: The conversation to save
|
|
82
|
+
"""
|
|
83
|
+
conversation.updated_at = datetime.now(UTC)
|
|
84
|
+
self.conversations[conversation.id] = conversation
|
|
85
|
+
# logger.debug(f"Saved conversation {conversation.id}")
|
|
86
|
+
|
|
87
|
+
def list_conversations(self, user_id: str | None = None) -> list[Conversation]:
|
|
88
|
+
"""
|
|
89
|
+
List conversations, optionally filtered by user.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
user_id: Optional user ID to filter by
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
list[Conversation]: List of conversations
|
|
96
|
+
"""
|
|
97
|
+
conversations = list(self.conversations.values())
|
|
98
|
+
|
|
99
|
+
if user_id:
|
|
100
|
+
conversations = [
|
|
101
|
+
conv
|
|
102
|
+
for conv in conversations
|
|
103
|
+
if conv.metadata.get("user_id") == user_id
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
# Sort by most recent activity
|
|
107
|
+
conversations.sort(key=lambda c: c.updated_at, reverse=True)
|
|
108
|
+
return conversations
|
|
109
|
+
|
|
110
|
+
def delete_conversation(self, conversation_id: str) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Delete a conversation.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
conversation_id: The conversation identifier
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: True if conversation was deleted, False if not found
|
|
119
|
+
"""
|
|
120
|
+
if conversation_id in self.conversations:
|
|
121
|
+
del self.conversations[conversation_id]
|
|
122
|
+
# logger.debug(f"Deleted conversation {conversation_id}")
|
|
123
|
+
return True
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
def get_conversation_count(self, user_id: str | None = None) -> int:
|
|
127
|
+
"""
|
|
128
|
+
Get count of conversations.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
user_id: Optional user ID to filter by
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
int: Number of conversations
|
|
135
|
+
"""
|
|
136
|
+
if user_id:
|
|
137
|
+
return len(
|
|
138
|
+
[
|
|
139
|
+
conv
|
|
140
|
+
for conv in self.conversations.values()
|
|
141
|
+
if conv.metadata.get("user_id") == user_id
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
return len(self.conversations)
|
|
145
|
+
|
|
146
|
+
def get_recent_conversations(
|
|
147
|
+
self, limit: int = 10, user_id: str | None = None
|
|
148
|
+
) -> list[Conversation]:
|
|
149
|
+
"""
|
|
150
|
+
Get recent conversations.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
limit: Maximum number of conversations to return
|
|
154
|
+
user_id: Optional user ID to filter by
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
list[Conversation]: Recent conversations
|
|
158
|
+
"""
|
|
159
|
+
conversations = self.list_conversations(user_id)
|
|
160
|
+
return conversations[:limit]
|
|
161
|
+
|
|
162
|
+
def cleanup_old_conversations(self, max_age_hours: int = 24) -> int:
|
|
163
|
+
"""
|
|
164
|
+
Clean up old conversations.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
max_age_hours: Maximum age in hours before cleanup
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
int: Number of conversations cleaned up
|
|
171
|
+
"""
|
|
172
|
+
cutoff_time = datetime.now(UTC).timestamp() - (max_age_hours * 3600)
|
|
173
|
+
to_delete = []
|
|
174
|
+
|
|
175
|
+
for conv_id, conversation in self.conversations.items():
|
|
176
|
+
if conversation.updated_at.timestamp() < cutoff_time:
|
|
177
|
+
to_delete.append(conv_id)
|
|
178
|
+
|
|
179
|
+
for conv_id in to_delete:
|
|
180
|
+
del self.conversations[conv_id]
|
|
181
|
+
|
|
182
|
+
if to_delete:
|
|
183
|
+
logger.info(f"Cleaned up {len(to_delete)} old conversations")
|
|
184
|
+
|
|
185
|
+
return len(to_delete)
|
|
186
|
+
|
|
187
|
+
def get_stats(self) -> dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Get conversation manager statistics.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict: Statistics about conversations
|
|
193
|
+
"""
|
|
194
|
+
total_conversations = len(self.conversations)
|
|
195
|
+
total_messages = sum(
|
|
196
|
+
conv.get_message_count() for conv in self.conversations.values()
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Get user breakdown
|
|
200
|
+
users = set()
|
|
201
|
+
for conv in self.conversations.values():
|
|
202
|
+
user_id = conv.metadata.get("user_id")
|
|
203
|
+
if user_id:
|
|
204
|
+
users.add(user_id)
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"total_conversations": total_conversations,
|
|
208
|
+
"total_messages": total_messages,
|
|
209
|
+
"unique_users": len(users),
|
|
210
|
+
"average_messages_per_conversation": (
|
|
211
|
+
total_messages / total_conversations if total_conversations > 0 else 0
|
|
212
|
+
),
|
|
213
|
+
}
|