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,131 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{{ project_slug }}"
|
|
3
|
+
version = "{{ version }}"
|
|
4
|
+
description = "{{ project_description }}"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">={{ python_version }}"
|
|
7
|
+
authors = [
|
|
8
|
+
{name = "{{ author_name }}", email = "{{ author_email }}"}
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
"fastapi==0.116.1",
|
|
13
|
+
"flet[all]==0.28.3",
|
|
14
|
+
"uvicorn==0.35.0",
|
|
15
|
+
"structlog==25.4.0",
|
|
16
|
+
"pydantic-settings==2.10.1",
|
|
17
|
+
"typer==0.16.0",
|
|
18
|
+
# Pin click to avoid yanked versions (typer dependency)
|
|
19
|
+
"click==8.2.1",
|
|
20
|
+
# Rich CLI formatting
|
|
21
|
+
"rich==14.1.0",
|
|
22
|
+
# HTTP client for CLI API calls
|
|
23
|
+
"httpx==0.28.1",
|
|
24
|
+
# System monitoring
|
|
25
|
+
"psutil==7.0.0",
|
|
26
|
+
{%- if include_scheduler %}
|
|
27
|
+
"apscheduler==3.10.4",
|
|
28
|
+
{%- endif %}
|
|
29
|
+
{%- if include_redis %}
|
|
30
|
+
"redis==5.0.8",
|
|
31
|
+
{%- endif %}
|
|
32
|
+
{%- if include_worker %}
|
|
33
|
+
"arq==0.25.0",
|
|
34
|
+
{%- endif %}
|
|
35
|
+
{%- if include_database %}
|
|
36
|
+
"sqlmodel>=0.0.14",
|
|
37
|
+
"sqlalchemy>=2.0.0",
|
|
38
|
+
"aiosqlite>=0.19.0",
|
|
39
|
+
"greenlet>=1.1.0",
|
|
40
|
+
{%- endif %}
|
|
41
|
+
{%- if include_cache %}
|
|
42
|
+
"redis[hiredis]==5.0.8",
|
|
43
|
+
{%- endif %}
|
|
44
|
+
{%- if include_auth %}
|
|
45
|
+
"python-jose[cryptography]==3.3.0",
|
|
46
|
+
"passlib[bcrypt]==1.7.4",
|
|
47
|
+
"bcrypt<5.0.0", # Pin to 4.x due to passlib compatibility
|
|
48
|
+
"python-multipart==0.0.9",
|
|
49
|
+
"email-validator==2.2.0",
|
|
50
|
+
"alembic==1.16.5",
|
|
51
|
+
{%- endif %}
|
|
52
|
+
{%- if include_ai %}
|
|
53
|
+
"pydantic-ai-slim[{{ ai_providers }}]==1.0.10",
|
|
54
|
+
"marko==2.1.3",
|
|
55
|
+
{%- endif %}
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[project.scripts]
|
|
59
|
+
{{ project_slug }} = "app.cli.main:main"
|
|
60
|
+
|
|
61
|
+
[project.optional-dependencies]
|
|
62
|
+
dev = [
|
|
63
|
+
"pytest==8.4.1",
|
|
64
|
+
"pytest-asyncio==1.1.0",
|
|
65
|
+
"ruff==0.12.7",
|
|
66
|
+
"ty",
|
|
67
|
+
"types-psutil==7.0.0.20250801",
|
|
68
|
+
"pre-commit==4.2.0",
|
|
69
|
+
"pip-audit==2.9.0",
|
|
70
|
+
{%- if include_worker %}
|
|
71
|
+
# Development auto-reload for workers
|
|
72
|
+
"watchdog==4.0.2",
|
|
73
|
+
{%- endif %}
|
|
74
|
+
]
|
|
75
|
+
docs = [
|
|
76
|
+
"mkdocs==1.6.1",
|
|
77
|
+
"mkdocs-material==9.6.16",
|
|
78
|
+
"mkdocstrings[python]==0.30.0",
|
|
79
|
+
"mkdocs-gen-files==0.5.0",
|
|
80
|
+
"pymdown-extensions==10.16.1",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[build-system]
|
|
84
|
+
requires = ["hatchling"]
|
|
85
|
+
build-backend = "hatchling.build"
|
|
86
|
+
|
|
87
|
+
[tool.hatch.build.targets.wheel]
|
|
88
|
+
packages = ["app"]
|
|
89
|
+
|
|
90
|
+
[tool.ruff]
|
|
91
|
+
line-length = 88
|
|
92
|
+
target-version = "py{{ python_version | string | replace('.', '') }}"
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint]
|
|
95
|
+
select = ["E", "F", "I", "N", "W", "UP"] # Added UP for pyupgrade (modern Python syntax)
|
|
96
|
+
ignore = []
|
|
97
|
+
fixable = ["ALL"]
|
|
98
|
+
unfixable = []
|
|
99
|
+
|
|
100
|
+
[tool.ruff.format]
|
|
101
|
+
quote-style = "double"
|
|
102
|
+
indent-style = "space"
|
|
103
|
+
skip-magic-trailing-comma = false
|
|
104
|
+
line-ending = "auto"
|
|
105
|
+
|
|
106
|
+
[tool.ruff.lint.isort]
|
|
107
|
+
force-single-line = false
|
|
108
|
+
force-sort-within-sections = true
|
|
109
|
+
known-first-party = ["app"]
|
|
110
|
+
|
|
111
|
+
[tool.ty.rules]
|
|
112
|
+
# Configure ty to ignore only genuine false positives or unavoidable issues
|
|
113
|
+
# Real type issues should be fixed, not ignored
|
|
114
|
+
unresolved-import = "ignore" # Missing third-party type stubs (unavoidable)
|
|
115
|
+
unresolved-attribute = "ignore" # Some dynamic attribute access in Flet is unavoidable
|
|
116
|
+
invalid-argument-type = "ignore" # Flet dynamic attributes and colors
|
|
117
|
+
non-subscriptable = "ignore" # Flet page.controls access patterns
|
|
118
|
+
unsupported-operator = "ignore" # Flet value comparisons with None
|
|
119
|
+
possibly-unbound-attribute = "ignore" # Flet dynamic component properties
|
|
120
|
+
call-non-callable = "ignore" # Dynamic middleware registration patterns
|
|
121
|
+
invalid-return-type = "ignore" # Flet component return type flexibility
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
[tool.pytest.ini_options]
|
|
125
|
+
asyncio_mode = "auto"
|
|
126
|
+
testpaths = ["tests"]
|
|
127
|
+
|
|
128
|
+
[dependency-groups]
|
|
129
|
+
dev = [
|
|
130
|
+
"httpx>=0.28.1",
|
|
131
|
+
]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
# More comprehensive venv cleanup to prevent Docker container conflicts
|
|
6
|
+
if [ -d ".venv" ]; then
|
|
7
|
+
echo "🧹 Found existing .venv directory, checking compatibility..."
|
|
8
|
+
|
|
9
|
+
# Check if .venv has issues (broken symlinks, wrong Python version, etc.)
|
|
10
|
+
if [ -L ".venv/bin/python3" ] && [ ! -e ".venv/bin/python3" ]; then
|
|
11
|
+
echo "🧹 Cleaning up broken venv symlinks..."
|
|
12
|
+
rm -rf .venv
|
|
13
|
+
elif [ -f ".venv/bin/python3" ]; then
|
|
14
|
+
# Check if the Python executable is compatible and accessible
|
|
15
|
+
if ! .venv/bin/python3 --version > /dev/null 2>&1; then
|
|
16
|
+
echo "🧹 Cleaning up incompatible venv..."
|
|
17
|
+
rm -rf .venv
|
|
18
|
+
fi
|
|
19
|
+
elif [ ! -w ".venv" ] || [ ! -x ".venv" ]; then
|
|
20
|
+
# Check for permission issues in Docker containers
|
|
21
|
+
echo "🧹 Cleaning up venv with permission issues..."
|
|
22
|
+
rm -rf .venv
|
|
23
|
+
else
|
|
24
|
+
# If directory exists but has no python executable, clean it up
|
|
25
|
+
if [ ! -f ".venv/bin/python3" ] && [ ! -f ".venv/bin/python" ]; then
|
|
26
|
+
echo "🧹 Cleaning up incomplete venv..."
|
|
27
|
+
rm -rf .venv
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Configure UV environment based on execution context
|
|
33
|
+
if [ -n "$DOCKER_CONTAINER" ] || [ "$USER" = "root" ]; then
|
|
34
|
+
echo "🐳 Running in Docker container, configuring UV for containerized environment..."
|
|
35
|
+
|
|
36
|
+
# Set Docker-specific UV configuration
|
|
37
|
+
export UV_PROJECT_ENVIRONMENT=/code/.venv
|
|
38
|
+
export UV_LINK_MODE=copy
|
|
39
|
+
export VIRTUAL_ENV=/code/.venv
|
|
40
|
+
|
|
41
|
+
# Ensure .venv path is in PATH for CLI commands
|
|
42
|
+
export PATH="/code/.venv/bin:$PATH"
|
|
43
|
+
|
|
44
|
+
echo "✅ UV configured for Docker: UV_PROJECT_ENVIRONMENT=/code/.venv"
|
|
45
|
+
else
|
|
46
|
+
echo "🖥️ Running in local environment, UV will use project defaults"
|
|
47
|
+
|
|
48
|
+
# Ensure we don't inherit Docker environment variables
|
|
49
|
+
unset UV_PROJECT_ENVIRONMENT
|
|
50
|
+
unset UV_SYSTEM_PYTHON
|
|
51
|
+
|
|
52
|
+
# Let UV auto-detect local .venv
|
|
53
|
+
echo "✅ UV configured for local development"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Pop run_command from arguments
|
|
57
|
+
run_command="$1"
|
|
58
|
+
shift
|
|
59
|
+
|
|
60
|
+
if [ "$run_command" = "webserver" ]; then
|
|
61
|
+
# Web server (FastAPI + Flet)
|
|
62
|
+
uv run python -m app.entrypoints.webserver
|
|
63
|
+
elif [ "$run_command" = "scheduler" ]; then
|
|
64
|
+
# Scheduler component
|
|
65
|
+
uv run python -m app.entrypoints.scheduler
|
|
66
|
+
elif [ "$run_command" = "lint" ]; then
|
|
67
|
+
uv run ruff check .
|
|
68
|
+
elif [ "$run_command" = "typecheck" ]; then
|
|
69
|
+
uv run mypy .
|
|
70
|
+
elif [ "$run_command" = "test" ]; then
|
|
71
|
+
uv run pytest "$@"
|
|
72
|
+
elif [ "$run_command" = "health" ]; then
|
|
73
|
+
uv run python -m app.cli.health check "$@"
|
|
74
|
+
elif [ "$run_command" = "help" ]; then
|
|
75
|
+
echo "Available commands:"
|
|
76
|
+
echo " webserver - Run FastAPI + Flet web server"
|
|
77
|
+
echo " scheduler - Run scheduler component"
|
|
78
|
+
echo " health - Check system health status"
|
|
79
|
+
echo " lint - Run ruff linting"
|
|
80
|
+
echo " typecheck - Run mypy type checking"
|
|
81
|
+
echo " test - Run pytest test suite"
|
|
82
|
+
echo " help - Show this help message"
|
|
83
|
+
else
|
|
84
|
+
echo "Unknown command: $run_command"
|
|
85
|
+
echo "Available commands: webserver, scheduler, health, lint, typecheck, test, help"
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
# More comprehensive venv cleanup to prevent Docker container conflicts
|
|
6
|
+
if [ -d ".venv" ]; then
|
|
7
|
+
echo "🧹 Found existing .venv directory, checking compatibility..."
|
|
8
|
+
|
|
9
|
+
# Check if .venv has issues (broken symlinks, wrong Python version, etc.)
|
|
10
|
+
if [ -L ".venv/bin/python3" ] && [ ! -e ".venv/bin/python3" ]; then
|
|
11
|
+
echo "🧹 Cleaning up broken venv symlinks..."
|
|
12
|
+
rm -rf .venv
|
|
13
|
+
elif [ -f ".venv/bin/python3" ]; then
|
|
14
|
+
# Check if the Python executable is compatible and accessible
|
|
15
|
+
if ! .venv/bin/python3 --version > /dev/null 2>&1; then
|
|
16
|
+
echo "🧹 Cleaning up incompatible venv..."
|
|
17
|
+
rm -rf .venv
|
|
18
|
+
fi
|
|
19
|
+
elif [ ! -w ".venv" ] || [ ! -x ".venv" ]; then
|
|
20
|
+
# Check for permission issues in Docker containers
|
|
21
|
+
echo "🧹 Cleaning up venv with permission issues..."
|
|
22
|
+
rm -rf .venv
|
|
23
|
+
else
|
|
24
|
+
# If directory exists but has no python executable, clean it up
|
|
25
|
+
if [ ! -f ".venv/bin/python3" ] && [ ! -f ".venv/bin/python" ]; then
|
|
26
|
+
echo "🧹 Cleaning up incomplete venv..."
|
|
27
|
+
rm -rf .venv
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Configure UV for Docker container (this is always a Docker entrypoint)
|
|
33
|
+
echo "🐳 Configuring UV for Docker container..."
|
|
34
|
+
|
|
35
|
+
# Set Docker-specific UV configuration
|
|
36
|
+
export UV_PROJECT_ENVIRONMENT=/code/.venv
|
|
37
|
+
export UV_LINK_MODE=copy
|
|
38
|
+
export VIRTUAL_ENV=/code/.venv
|
|
39
|
+
|
|
40
|
+
# Ensure .venv path is in PATH for CLI commands
|
|
41
|
+
export PATH="/code/.venv/bin:$PATH"
|
|
42
|
+
|
|
43
|
+
echo "✅ UV configured for Docker: UV_PROJECT_ENVIRONMENT=/code/.venv"
|
|
44
|
+
|
|
45
|
+
# Pop run_command from arguments
|
|
46
|
+
run_command="$1"
|
|
47
|
+
shift
|
|
48
|
+
|
|
49
|
+
if [ "$run_command" = "webserver" ]; then
|
|
50
|
+
# Web server (FastAPI + Flet)
|
|
51
|
+
uv run python -m app.entrypoints.webserver
|
|
52
|
+
elif [ "$run_command" = "scheduler" ]; then
|
|
53
|
+
# Scheduler component
|
|
54
|
+
uv run python -m app.entrypoints.scheduler
|
|
55
|
+
elif [ "$run_command" = "worker" ]; then
|
|
56
|
+
# Worker component using STANDARD arq CLI
|
|
57
|
+
queue_type="${1:-system}" # Default to system queue if not specified
|
|
58
|
+
shift
|
|
59
|
+
|
|
60
|
+
# Build the module path for the queue
|
|
61
|
+
worker_module="app.components.worker.queues.${queue_type}.WorkerSettings"
|
|
62
|
+
|
|
63
|
+
# Development mode auto-reload (APP_ENV from .env or WORKER_WATCH override)
|
|
64
|
+
if [ "$APP_ENV" = "dev" ] || [ "$WORKER_WATCH" = "true" ]; then
|
|
65
|
+
echo "🔄 Starting ${queue_type} worker with auto-reload..."
|
|
66
|
+
exec uv run python -m arq "${worker_module}" --watch /code/app "$@"
|
|
67
|
+
else
|
|
68
|
+
echo "🚀 Starting ${queue_type} worker..."
|
|
69
|
+
exec uv run python -m arq "${worker_module}" "$@"
|
|
70
|
+
fi
|
|
71
|
+
elif [ "$run_command" = "lint" ]; then
|
|
72
|
+
uv run ruff check .
|
|
73
|
+
elif [ "$run_command" = "typecheck" ]; then
|
|
74
|
+
uv run mypy .
|
|
75
|
+
elif [ "$run_command" = "test" ]; then
|
|
76
|
+
uv run pytest "$@"
|
|
77
|
+
elif [ "$run_command" = "health" ]; then
|
|
78
|
+
uv run python -m app.cli.health check "$@"
|
|
79
|
+
elif [ "$run_command" = "help" ]; then
|
|
80
|
+
echo "Available commands:"
|
|
81
|
+
echo " webserver - Run FastAPI + Flet web server"
|
|
82
|
+
echo " scheduler - Run scheduler component"
|
|
83
|
+
echo " worker - Run arq worker (standard arq CLI patterns)"
|
|
84
|
+
echo " health - Check system health status"
|
|
85
|
+
echo " lint - Run ruff linting"
|
|
86
|
+
echo " typecheck - Run mypy type checking"
|
|
87
|
+
echo " test - Run pytest test suite"
|
|
88
|
+
echo " help - Show this help message"
|
|
89
|
+
else
|
|
90
|
+
echo "Unknown command: $run_command"
|
|
91
|
+
echo "Available commands: webserver, scheduler, worker, health, lint, typecheck, test, help"
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# scripts/gen_docs.py
|
|
2
|
+
"""
|
|
3
|
+
A script to dynamically generate documentation files for MkDocs.
|
|
4
|
+
This is run automatically by the mkdocs-gen-files plugin.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import mkdocs_gen_files # noqa: F401
|
|
8
|
+
|
|
9
|
+
print("--- Running gen_docs.py ---")
|
|
10
|
+
|
|
11
|
+
# Copy the root README.md to be the documentation's index page.
|
|
12
|
+
# This allows us to maintain a single source of truth for the project's
|
|
13
|
+
# main landing page, which is visible on both GitHub and the docs site.
|
|
14
|
+
with open("README.md") as readme, open("docs/index.md", "w") as index:
|
|
15
|
+
index.write(readme.read())
|
|
16
|
+
print("✓ Copied README.md to docs/index.md")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# API tests package
|
aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for authentication API endpoints.
|
|
3
|
+
|
|
4
|
+
This module tests the auth endpoints including user registration,
|
|
5
|
+
login, token validation, and protected endpoint access.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from app.core.security import create_access_token
|
|
10
|
+
from app.models.user import UserCreate
|
|
11
|
+
from app.services.auth.user_service import UserService
|
|
12
|
+
from fastapi import status
|
|
13
|
+
from fastapi.testclient import TestClient
|
|
14
|
+
from sqlmodel import Session
|
|
15
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestAuthEndpoints:
|
|
19
|
+
"""Test auth API endpoints."""
|
|
20
|
+
|
|
21
|
+
@pytest.mark.asyncio
|
|
22
|
+
async def test_register_new_user(
|
|
23
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
24
|
+
):
|
|
25
|
+
"""Test user registration with valid data."""
|
|
26
|
+
user_data = {
|
|
27
|
+
"email": "test@example.com",
|
|
28
|
+
"full_name": "Test User",
|
|
29
|
+
"password": "testpassword123"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
response = async_client_with_db.post("/api/v1/auth/register", json=user_data)
|
|
33
|
+
|
|
34
|
+
assert response.status_code == status.HTTP_200_OK
|
|
35
|
+
data = response.json()
|
|
36
|
+
assert data["email"] == user_data["email"]
|
|
37
|
+
assert data["full_name"] == user_data["full_name"]
|
|
38
|
+
assert data["is_active"] is True
|
|
39
|
+
assert "id" in data
|
|
40
|
+
assert "created_at" in data
|
|
41
|
+
# Ensure password is not returned
|
|
42
|
+
assert "password" not in data
|
|
43
|
+
assert "hashed_password" not in data
|
|
44
|
+
|
|
45
|
+
@pytest.mark.asyncio
|
|
46
|
+
async def test_register_duplicate_email(
|
|
47
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
48
|
+
):
|
|
49
|
+
"""Test registration with already existing email."""
|
|
50
|
+
# Create a user first
|
|
51
|
+
user_service = UserService(async_db_session)
|
|
52
|
+
existing_user = UserCreate(
|
|
53
|
+
email="existing@example.com",
|
|
54
|
+
full_name="Existing User",
|
|
55
|
+
password="password123"
|
|
56
|
+
)
|
|
57
|
+
await user_service.create_user(existing_user)
|
|
58
|
+
|
|
59
|
+
# Try to register with same email
|
|
60
|
+
duplicate_user_data = {
|
|
61
|
+
"email": "existing@example.com",
|
|
62
|
+
"full_name": "Another User",
|
|
63
|
+
"password": "differentpassword"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
response = async_client_with_db.post(
|
|
67
|
+
"/api/v1/auth/register", json=duplicate_user_data
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
71
|
+
assert "already registered" in response.json()["detail"].lower()
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_register_invalid_data(self, async_client_with_db: TestClient):
|
|
75
|
+
"""Test registration with invalid data."""
|
|
76
|
+
invalid_user_data = {
|
|
77
|
+
"email": "not-an-email", # Invalid email format
|
|
78
|
+
"password": "123" # Too short
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
response = async_client_with_db.post(
|
|
82
|
+
"/api/v1/auth/register", json=invalid_user_data
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_login_valid_credentials(
|
|
89
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
90
|
+
):
|
|
91
|
+
"""Test login with valid credentials."""
|
|
92
|
+
# Create a user first
|
|
93
|
+
user_service = UserService(async_db_session)
|
|
94
|
+
user_data = UserCreate(
|
|
95
|
+
email="login@example.com",
|
|
96
|
+
full_name="Login User",
|
|
97
|
+
password="loginpassword123"
|
|
98
|
+
)
|
|
99
|
+
await user_service.create_user(user_data)
|
|
100
|
+
|
|
101
|
+
# Login with valid credentials
|
|
102
|
+
login_data = {
|
|
103
|
+
"username": "login@example.com", # OAuth2 uses username field
|
|
104
|
+
"password": "loginpassword123"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
response = async_client_with_db.post("/api/v1/auth/token", data=login_data)
|
|
108
|
+
|
|
109
|
+
assert response.status_code == status.HTTP_200_OK
|
|
110
|
+
data = response.json()
|
|
111
|
+
assert "access_token" in data
|
|
112
|
+
assert data["token_type"] == "bearer"
|
|
113
|
+
assert len(data["access_token"]) > 0
|
|
114
|
+
|
|
115
|
+
@pytest.mark.asyncio
|
|
116
|
+
async def test_login_invalid_email(self, async_client_with_db: TestClient):
|
|
117
|
+
"""Test login with non-existent email."""
|
|
118
|
+
login_data = {
|
|
119
|
+
"username": "nonexistent@example.com",
|
|
120
|
+
"password": "somepassword"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
response = async_client_with_db.post("/api/v1/auth/token", data=login_data)
|
|
124
|
+
|
|
125
|
+
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
126
|
+
assert "incorrect" in response.json()["detail"].lower()
|
|
127
|
+
|
|
128
|
+
@pytest.mark.asyncio
|
|
129
|
+
async def test_login_invalid_password(
|
|
130
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
131
|
+
):
|
|
132
|
+
"""Test login with wrong password."""
|
|
133
|
+
# Create a user first
|
|
134
|
+
user_service = UserService(async_db_session)
|
|
135
|
+
user_data = UserCreate(
|
|
136
|
+
email="wrongpass@example.com",
|
|
137
|
+
full_name="Wrong Pass User",
|
|
138
|
+
password="correctpassword"
|
|
139
|
+
)
|
|
140
|
+
await user_service.create_user(user_data)
|
|
141
|
+
|
|
142
|
+
# Login with wrong password
|
|
143
|
+
login_data = {
|
|
144
|
+
"username": "wrongpass@example.com",
|
|
145
|
+
"password": "wrongpassword"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
response = async_client_with_db.post("/api/v1/auth/token", data=login_data)
|
|
149
|
+
|
|
150
|
+
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
151
|
+
assert "incorrect" in response.json()["detail"].lower()
|
|
152
|
+
|
|
153
|
+
@pytest.mark.asyncio
|
|
154
|
+
async def test_get_current_user_valid_token(
|
|
155
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
156
|
+
):
|
|
157
|
+
"""Test getting current user with valid token."""
|
|
158
|
+
# Create a user first
|
|
159
|
+
user_service = UserService(async_db_session)
|
|
160
|
+
user_data = UserCreate(
|
|
161
|
+
email="currentuser@example.com",
|
|
162
|
+
full_name="Current User",
|
|
163
|
+
password="password123"
|
|
164
|
+
)
|
|
165
|
+
created_user = await user_service.create_user(user_data)
|
|
166
|
+
|
|
167
|
+
# Create a valid token
|
|
168
|
+
token = create_access_token(data={"sub": created_user.email})
|
|
169
|
+
|
|
170
|
+
# Get current user
|
|
171
|
+
response = async_client_with_db.get(
|
|
172
|
+
"/api/v1/auth/me",
|
|
173
|
+
headers={"Authorization": f"Bearer {token}"}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
assert response.status_code == status.HTTP_200_OK
|
|
177
|
+
data = response.json()
|
|
178
|
+
assert data["email"] == created_user.email
|
|
179
|
+
assert data["full_name"] == created_user.full_name
|
|
180
|
+
assert data["id"] == created_user.id
|
|
181
|
+
|
|
182
|
+
@pytest.mark.asyncio
|
|
183
|
+
async def test_get_current_user_invalid_token(
|
|
184
|
+
self, async_client_with_db: TestClient
|
|
185
|
+
):
|
|
186
|
+
"""Test getting current user with invalid token."""
|
|
187
|
+
response = async_client_with_db.get(
|
|
188
|
+
"/api/v1/auth/me",
|
|
189
|
+
headers={"Authorization": "Bearer invalid_token"}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
193
|
+
|
|
194
|
+
@pytest.mark.asyncio
|
|
195
|
+
async def test_get_current_user_no_token(self, async_client_with_db: TestClient):
|
|
196
|
+
"""Test getting current user without token."""
|
|
197
|
+
response = async_client_with_db.get("/api/v1/auth/me")
|
|
198
|
+
|
|
199
|
+
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
200
|
+
|
|
201
|
+
@pytest.mark.asyncio
|
|
202
|
+
async def test_get_current_user_expired_token(
|
|
203
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
204
|
+
):
|
|
205
|
+
"""Test getting current user with expired token."""
|
|
206
|
+
# Create a user first
|
|
207
|
+
user_service = UserService(async_db_session)
|
|
208
|
+
user_data = UserCreate(
|
|
209
|
+
email="expiredtoken@example.com",
|
|
210
|
+
full_name="Expired Token User",
|
|
211
|
+
password="password123"
|
|
212
|
+
)
|
|
213
|
+
created_user = await user_service.create_user(user_data)
|
|
214
|
+
|
|
215
|
+
# Create an expired token (negative expiry)
|
|
216
|
+
from datetime import timedelta
|
|
217
|
+
expired_token = create_access_token(
|
|
218
|
+
data={"sub": created_user.email},
|
|
219
|
+
expires_delta=timedelta(seconds=-1)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Try to get current user with expired token
|
|
223
|
+
response = async_client_with_db.get(
|
|
224
|
+
"/api/v1/auth/me",
|
|
225
|
+
headers={"Authorization": f"Bearer {expired_token}"}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestAuthIntegration:
|
|
232
|
+
"""Integration tests for auth flow."""
|
|
233
|
+
|
|
234
|
+
@pytest.mark.asyncio
|
|
235
|
+
async def test_full_auth_flow(
|
|
236
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
237
|
+
):
|
|
238
|
+
"""Test complete auth flow: register -> login -> protected endpoint."""
|
|
239
|
+
# Step 1: Register new user
|
|
240
|
+
user_data = {
|
|
241
|
+
"email": "fullflow@example.com",
|
|
242
|
+
"full_name": "Full Flow User",
|
|
243
|
+
"password": "fullflowpassword123"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
register_response = async_client_with_db.post(
|
|
247
|
+
"/api/v1/auth/register", json=user_data
|
|
248
|
+
)
|
|
249
|
+
assert register_response.status_code == status.HTTP_200_OK
|
|
250
|
+
|
|
251
|
+
# Step 2: Login with credentials
|
|
252
|
+
login_data = {
|
|
253
|
+
"username": user_data["email"],
|
|
254
|
+
"password": user_data["password"]
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
login_response = async_client_with_db.post(
|
|
258
|
+
"/api/v1/auth/token", data=login_data
|
|
259
|
+
)
|
|
260
|
+
assert login_response.status_code == status.HTTP_200_OK
|
|
261
|
+
|
|
262
|
+
token = login_response.json()["access_token"]
|
|
263
|
+
|
|
264
|
+
# Step 3: Access protected endpoint
|
|
265
|
+
me_response = async_client_with_db.get(
|
|
266
|
+
"/api/v1/auth/me",
|
|
267
|
+
headers={"Authorization": f"Bearer {token}"}
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
assert me_response.status_code == status.HTTP_200_OK
|
|
271
|
+
user_info = me_response.json()
|
|
272
|
+
assert user_info["email"] == user_data["email"]
|
|
273
|
+
assert user_info["full_name"] == user_data["full_name"]
|
|
274
|
+
|
|
275
|
+
@pytest.mark.asyncio
|
|
276
|
+
async def test_user_persistence_across_requests(
|
|
277
|
+
self, async_client_with_db: TestClient, async_db_session: AsyncSession
|
|
278
|
+
):
|
|
279
|
+
"""Test that users are properly persisted in database."""
|
|
280
|
+
# Register user
|
|
281
|
+
user_data = {
|
|
282
|
+
"email": "persistent@example.com",
|
|
283
|
+
"full_name": "Persistent User",
|
|
284
|
+
"password": "persistentpassword"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async_client_with_db.post("/api/v1/auth/register", json=user_data)
|
|
288
|
+
|
|
289
|
+
# Login in separate request
|
|
290
|
+
login_data = {
|
|
291
|
+
"username": user_data["email"],
|
|
292
|
+
"password": user_data["password"]
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
login_response = async_client_with_db.post(
|
|
296
|
+
"/api/v1/auth/token", data=login_data
|
|
297
|
+
)
|
|
298
|
+
assert login_response.status_code == status.HTTP_200_OK
|
|
299
|
+
|
|
300
|
+
# Verify user exists in database
|
|
301
|
+
user_service = UserService(async_db_session)
|
|
302
|
+
db_user = await user_service.get_user_by_email(user_data["email"])
|
|
303
|
+
|
|
304
|
+
assert db_user is not None
|
|
305
|
+
assert db_user.email == user_data["email"]
|
|
306
|
+
assert db_user.full_name == user_data["full_name"]
|
|
307
|
+
assert db_user.is_active is True
|