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,328 @@
|
|
|
1
|
+
{%- if cookiecutter.scheduler_backend != "memory" -%}
|
|
2
|
+
"""
|
|
3
|
+
Scheduled task CLI commands.
|
|
4
|
+
|
|
5
|
+
Provides command-line interface for managing scheduled jobs,
|
|
6
|
+
viewing job statistics, and manually triggering tasks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from rich import print as rprint
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
import typer
|
|
19
|
+
|
|
20
|
+
from app.core.log import logger
|
|
21
|
+
from app.services.scheduler import ScheduledTaskManager
|
|
22
|
+
from app.services.scheduler.models import ScheduledTask
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(
|
|
25
|
+
name="tasks",
|
|
26
|
+
help="Scheduled task management commands",
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command("list")
|
|
34
|
+
def list_jobs() -> None:
|
|
35
|
+
"""
|
|
36
|
+
List all scheduled jobs with their current status.
|
|
37
|
+
|
|
38
|
+
Shows job details including next run time, status, and configuration.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
rprint("📋 [bold blue]Listing Scheduled Jobs[/bold blue]")
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
tasks = asyncio.run(_get_scheduled_tasks())
|
|
45
|
+
|
|
46
|
+
if not tasks:
|
|
47
|
+
rprint("ℹ️ [yellow]No scheduled jobs found[/yellow]")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
table = Table(
|
|
51
|
+
title="🕒 Scheduled Jobs",
|
|
52
|
+
show_header=True,
|
|
53
|
+
header_style="bold magenta",
|
|
54
|
+
)
|
|
55
|
+
table.add_column("Job ID", style="cyan", no_wrap=True)
|
|
56
|
+
table.add_column("Name", style="green")
|
|
57
|
+
table.add_column("Status", style="bold")
|
|
58
|
+
table.add_column("Next Run", style="yellow")
|
|
59
|
+
table.add_column("Trigger", style="dim")
|
|
60
|
+
|
|
61
|
+
for task in tasks:
|
|
62
|
+
status_color = "green" if task.is_active else "red"
|
|
63
|
+
next_run = (
|
|
64
|
+
task.next_run_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
65
|
+
if task.next_run_time
|
|
66
|
+
else "Not scheduled"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
table.add_row(
|
|
70
|
+
task.job_id,
|
|
71
|
+
task.name or task.job_id,
|
|
72
|
+
(
|
|
73
|
+
f"[{status_color}]{'Active' if task.is_active else 'Paused'}"
|
|
74
|
+
f"[/{status_color}]"
|
|
75
|
+
),
|
|
76
|
+
next_run,
|
|
77
|
+
task.trigger_type or "Unknown"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
console.print(table)
|
|
81
|
+
rprint(f"\n📊 [cyan]Total jobs:[/cyan] {len(tasks)}")
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
rprint(f"❌ [red]Failed to list jobs:[/red] {e}")
|
|
85
|
+
raise typer.Exit(1)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command("stats")
|
|
89
|
+
def job_statistics(
|
|
90
|
+
job_id: str | None = typer.Argument(
|
|
91
|
+
None, help="Specific job ID to show stats for"
|
|
92
|
+
),
|
|
93
|
+
json_output: bool = typer.Option(
|
|
94
|
+
False, "--json", help="Output stats in JSON format"
|
|
95
|
+
),
|
|
96
|
+
) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Show job execution statistics and performance metrics.
|
|
99
|
+
|
|
100
|
+
Without job_id: Shows overall scheduler statistics
|
|
101
|
+
With job_id: Shows detailed statistics for specific job
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
if job_id:
|
|
106
|
+
rprint(f"📊 [bold blue]Job Statistics for: {job_id}[/bold blue]")
|
|
107
|
+
stats = asyncio.run(_get_job_statistics(job_id))
|
|
108
|
+
else:
|
|
109
|
+
rprint("📊 [bold blue]Overall Scheduler Statistics[/bold blue]")
|
|
110
|
+
stats = asyncio.run(_get_overall_statistics())
|
|
111
|
+
|
|
112
|
+
if json_output:
|
|
113
|
+
print(json.dumps(stats, indent=2, default=str))
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
_display_statistics(stats, job_id)
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
rprint(f"❌ [red]Failed to get statistics:[/red] {e}")
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command("history")
|
|
124
|
+
def job_history(
|
|
125
|
+
limit: int = typer.Option(
|
|
126
|
+
20, "--limit", "-l", help="Number of recent executions to show", min=1, max=100
|
|
127
|
+
),
|
|
128
|
+
job_id: str | None = typer.Option(
|
|
129
|
+
None, "--job-id", "-j", help="Filter by specific job ID"
|
|
130
|
+
),
|
|
131
|
+
json_output: bool = typer.Option(
|
|
132
|
+
False, "--json", help="Output history in JSON format"
|
|
133
|
+
),
|
|
134
|
+
) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Show recent job execution history.
|
|
137
|
+
|
|
138
|
+
Displays execution times, durations, and results for recent job runs.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
if job_id:
|
|
143
|
+
rprint(
|
|
144
|
+
f"📚 [bold blue]Execution History for: {job_id}[/bold blue] "
|
|
145
|
+
f"(last {limit})"
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
rprint(
|
|
149
|
+
f"📚 [bold blue]Recent Job Execution History[/bold blue] "
|
|
150
|
+
f"(last {limit})"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
history = asyncio.run(_get_job_history(limit, job_id))
|
|
154
|
+
|
|
155
|
+
if json_output:
|
|
156
|
+
print(json.dumps(history, indent=2, default=str))
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
if not history:
|
|
160
|
+
rprint("ℹ️ [yellow]No execution history found[/yellow]")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
table = Table(
|
|
164
|
+
title="🕰️ Job Execution History",
|
|
165
|
+
show_header=True,
|
|
166
|
+
header_style="bold magenta",
|
|
167
|
+
)
|
|
168
|
+
table.add_column("Timestamp", style="cyan")
|
|
169
|
+
table.add_column("Job ID", style="green")
|
|
170
|
+
table.add_column("Duration", style="yellow")
|
|
171
|
+
table.add_column("Status", style="bold")
|
|
172
|
+
table.add_column("Details", style="dim")
|
|
173
|
+
|
|
174
|
+
for execution in history:
|
|
175
|
+
status_color = "green" if execution.get("status") == "success" else "red"
|
|
176
|
+
duration = f"{execution.get('duration_ms', 0):.1f}ms"
|
|
177
|
+
|
|
178
|
+
status_value = execution.get('status', 'Unknown')
|
|
179
|
+
status_display = f"[{status_color}]{status_value}[/{status_color}]"
|
|
180
|
+
details = execution.get("details", "")[:50]
|
|
181
|
+
if len(execution.get("details", "")) > 50:
|
|
182
|
+
details += "..."
|
|
183
|
+
|
|
184
|
+
table.add_row(
|
|
185
|
+
execution.get("timestamp", "Unknown"),
|
|
186
|
+
execution.get("job_id", "Unknown"),
|
|
187
|
+
duration,
|
|
188
|
+
status_display,
|
|
189
|
+
details,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
console.print(table)
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
rprint(f"❌ [red]Failed to get job history:[/red] {e}")
|
|
196
|
+
raise typer.Exit(1)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@app.command("trigger")
|
|
200
|
+
def trigger_job(
|
|
201
|
+
job_id: str = typer.Argument(..., help="Job ID to trigger manually"),
|
|
202
|
+
wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for job completion"),
|
|
203
|
+
timeout: int = typer.Option(
|
|
204
|
+
30, "--timeout", help="Timeout for waiting (seconds)", min=1, max=300
|
|
205
|
+
),
|
|
206
|
+
) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Manually trigger a scheduled job.
|
|
209
|
+
|
|
210
|
+
Executes the specified job immediately, bypassing the normal schedule.
|
|
211
|
+
Useful for testing job functionality or running jobs on-demand.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
rprint(f"🚀 [bold blue]Triggering job:[/bold blue] {job_id}")
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
# First verify the job exists
|
|
218
|
+
tasks = asyncio.run(_get_scheduled_tasks())
|
|
219
|
+
job_exists = any(task.job_id == job_id for task in tasks)
|
|
220
|
+
|
|
221
|
+
if not job_exists:
|
|
222
|
+
rprint(f"❌ [red]Job not found:[/red] {job_id}")
|
|
223
|
+
rprint("💡 [yellow]Use 'tasks list' to see available jobs[/yellow]")
|
|
224
|
+
raise typer.Exit(1)
|
|
225
|
+
|
|
226
|
+
# Trigger the job
|
|
227
|
+
result = asyncio.run(_trigger_job_execution(job_id, wait, timeout))
|
|
228
|
+
|
|
229
|
+
if result.get("status") == "success":
|
|
230
|
+
rprint(f"✅ [green]Job triggered successfully![/green]")
|
|
231
|
+
if wait and result.get("execution_details"):
|
|
232
|
+
details = result["execution_details"]
|
|
233
|
+
duration_ms = details.get('duration_ms', 0)
|
|
234
|
+
rprint(f"⏱️ [cyan]Duration:[/cyan] {duration_ms:.1f}ms")
|
|
235
|
+
rprint(f"📋 [cyan]Result:[/cyan] {details.get('result', 'No result')}")
|
|
236
|
+
else:
|
|
237
|
+
error_msg = result.get('error', 'Unknown error')
|
|
238
|
+
rprint(f"❌ [red]Job execution failed:[/red] {error_msg}")
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
rprint(f"❌ [red]Failed to trigger job:[/red] {e}")
|
|
242
|
+
raise typer.Exit(1)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async def _get_scheduled_tasks() -> list[ScheduledTask]:
|
|
246
|
+
"""Get list of scheduled tasks from the scheduler."""
|
|
247
|
+
manager = ScheduledTaskManager()
|
|
248
|
+
return await manager.list_tasks()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
async def _get_job_statistics(job_id: str) -> dict[str, Any]:
|
|
252
|
+
"""Get statistics for a specific job."""
|
|
253
|
+
manager = ScheduledTaskManager()
|
|
254
|
+
return await manager.get_job_statistics(job_id)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
async def _get_overall_statistics() -> dict[str, Any]:
|
|
258
|
+
"""Get overall scheduler statistics."""
|
|
259
|
+
manager = ScheduledTaskManager()
|
|
260
|
+
return await manager.get_overall_statistics()
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
async def _get_job_history(limit: int, job_id: str | None) -> list[dict[str, Any]]:
|
|
264
|
+
"""Get recent job execution history."""
|
|
265
|
+
manager = ScheduledTaskManager()
|
|
266
|
+
return await manager.get_job_history(limit, job_id)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
async def _trigger_job_execution(
|
|
270
|
+
job_id: str, wait: bool, timeout: int
|
|
271
|
+
) -> dict[str, Any]:
|
|
272
|
+
"""Trigger manual job execution."""
|
|
273
|
+
manager = ScheduledTaskManager()
|
|
274
|
+
return await manager.trigger_job(job_id, wait, timeout)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _display_statistics(stats: dict[str, Any], job_id: str | None) -> None:
|
|
278
|
+
"""Display formatted statistics."""
|
|
279
|
+
|
|
280
|
+
if job_id:
|
|
281
|
+
# Job-specific statistics
|
|
282
|
+
info_text = f"""
|
|
283
|
+
[bold cyan]Job Performance Metrics[/bold cyan]
|
|
284
|
+
|
|
285
|
+
🏃 [yellow]Executions:[/yellow] {stats.get('total_executions', 0)}
|
|
286
|
+
✅ [yellow]Successful:[/yellow] {stats.get('successful_executions', 0)}
|
|
287
|
+
❌ [yellow]Failed:[/yellow] {stats.get('failed_executions', 0)}
|
|
288
|
+
📊 [yellow]Success Rate:[/yellow] {stats.get('success_rate_percent', 0):.1f}%
|
|
289
|
+
|
|
290
|
+
⏱️ [yellow]Avg Duration:[/yellow] {stats.get('avg_duration_ms', 0):.1f}ms
|
|
291
|
+
🚀 [yellow]Min Duration:[/yellow] {stats.get('min_duration_ms', 0):.1f}ms
|
|
292
|
+
🐌 [yellow]Max Duration:[/yellow] {stats.get('max_duration_ms', 0):.1f}ms
|
|
293
|
+
|
|
294
|
+
🕐 [yellow]Last Execution:[/yellow] {stats.get('last_execution', 'Never')}
|
|
295
|
+
🕑 [yellow]Next Scheduled:[/yellow] {stats.get('next_run', 'Not scheduled')}
|
|
296
|
+
""".strip()
|
|
297
|
+
|
|
298
|
+
title = f"📊 Statistics for {job_id}"
|
|
299
|
+
console.print(Panel(info_text, title=title, border_style="blue"))
|
|
300
|
+
|
|
301
|
+
else:
|
|
302
|
+
# Overall scheduler statistics
|
|
303
|
+
info_text = f"""
|
|
304
|
+
[bold cyan]Scheduler Overview[/bold cyan]
|
|
305
|
+
|
|
306
|
+
📋 [yellow]Total Jobs:[/yellow] {stats.get('total_jobs', 0)}
|
|
307
|
+
🟢 [yellow]Active Jobs:[/yellow] {stats.get('active_jobs', 0)}
|
|
308
|
+
🔴 [yellow]Paused Jobs:[/yellow] {stats.get('paused_jobs', 0)}
|
|
309
|
+
|
|
310
|
+
🏃 [yellow]Total Executions:[/yellow] {stats.get('total_executions', 0)}
|
|
311
|
+
✅ [yellow]Successful:[/yellow] {stats.get('successful_executions', 0)}
|
|
312
|
+
❌ [yellow]Failed:[/yellow] {stats.get('failed_executions', 0)}
|
|
313
|
+
|
|
314
|
+
📊 [yellow]Overall Success Rate:[/yellow] {stats.get('success_rate_percent', 0):.1f}%
|
|
315
|
+
⏱️ [yellow]Avg Execution Time:[/yellow] {stats.get('avg_duration_ms', 0):.1f}ms
|
|
316
|
+
|
|
317
|
+
🕐 [yellow]Scheduler Uptime:[/yellow] {stats.get('uptime', 'Unknown')}
|
|
318
|
+
🔄 [yellow]Last Activity:[/yellow] {stats.get('last_activity', 'No recent activity')}
|
|
319
|
+
""".strip()
|
|
320
|
+
|
|
321
|
+
console.print(
|
|
322
|
+
Panel(info_text, title="📊 Scheduler Statistics", border_style="green")
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if __name__ == "__main__":
|
|
327
|
+
app()
|
|
328
|
+
{%- endif -%}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduled task CLI commands.
|
|
3
|
+
|
|
4
|
+
Provides command-line interface for managing scheduled jobs,
|
|
5
|
+
viewing job statistics, and manually triggering tasks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from rich import print as rprint
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
import typer
|
|
17
|
+
|
|
18
|
+
from app.services.scheduler import ScheduledTaskManager
|
|
19
|
+
|
|
20
|
+
app = typer.Typer(
|
|
21
|
+
name="tasks",
|
|
22
|
+
help="Scheduled task management commands",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("list")
|
|
30
|
+
def list_jobs() -> None:
|
|
31
|
+
"""
|
|
32
|
+
List all scheduled jobs with their current status.
|
|
33
|
+
|
|
34
|
+
Shows job details including next run time, status, and configuration.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
rprint("📋 [bold blue]Listing Scheduled Jobs[/bold blue]")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
tasks = asyncio.run(_get_scheduled_tasks())
|
|
41
|
+
|
|
42
|
+
if not tasks:
|
|
43
|
+
rprint("ℹ️ [yellow]No scheduled jobs found[/yellow]")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
table = Table(
|
|
47
|
+
title="🕒 Scheduled Jobs",
|
|
48
|
+
show_header=True,
|
|
49
|
+
header_style="bold magenta",
|
|
50
|
+
)
|
|
51
|
+
table.add_column("Job ID", style="cyan", no_wrap=True)
|
|
52
|
+
table.add_column("Name", style="green")
|
|
53
|
+
table.add_column("Status", style="bold")
|
|
54
|
+
table.add_column("Next Run", style="yellow")
|
|
55
|
+
table.add_column("Trigger", style="dim")
|
|
56
|
+
|
|
57
|
+
for task in tasks:
|
|
58
|
+
status_color = "green" if task.is_active else "red"
|
|
59
|
+
next_run = (
|
|
60
|
+
task.next_run_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
61
|
+
if task.next_run_time
|
|
62
|
+
else "Not scheduled"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
table.add_row(
|
|
66
|
+
task.job_id,
|
|
67
|
+
task.name or task.job_id,
|
|
68
|
+
(
|
|
69
|
+
f"[{status_color}]{'Active' if task.is_active else 'Paused'}"
|
|
70
|
+
f"[/{status_color}]"
|
|
71
|
+
),
|
|
72
|
+
next_run,
|
|
73
|
+
task.trigger_type or "Unknown"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
console.print(table)
|
|
77
|
+
rprint(f"\n📊 [cyan]Total jobs:[/cyan] {len(tasks)}")
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
rprint(f"❌ [red]Failed to list jobs:[/red] {e}")
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command("stats")
|
|
85
|
+
def job_statistics(
|
|
86
|
+
job_id: str | None = typer.Argument(None, help="Specific job ID to show stats for"),
|
|
87
|
+
json_output: bool = typer.Option(
|
|
88
|
+
False, "--json", help="Output stats in JSON format"
|
|
89
|
+
),
|
|
90
|
+
) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Show job execution statistics and performance metrics.
|
|
93
|
+
|
|
94
|
+
Without job_id: Shows overall scheduler statistics
|
|
95
|
+
With job_id: Shows detailed statistics for specific job
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
if job_id:
|
|
100
|
+
rprint(f"📊 [bold blue]Job Statistics for: {job_id}[/bold blue]")
|
|
101
|
+
stats = asyncio.run(_get_job_statistics(job_id))
|
|
102
|
+
else:
|
|
103
|
+
rprint("📊 [bold blue]Overall Scheduler Statistics[/bold blue]")
|
|
104
|
+
stats = asyncio.run(_get_overall_statistics())
|
|
105
|
+
|
|
106
|
+
if json_output:
|
|
107
|
+
print(json.dumps(stats, indent=2, default=str))
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
_display_statistics(stats, job_id)
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
rprint(f"❌ [red]Failed to get statistics:[/red] {e}")
|
|
114
|
+
raise typer.Exit(1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command("history")
|
|
118
|
+
def job_history(
|
|
119
|
+
limit: int = typer.Option(
|
|
120
|
+
20,
|
|
121
|
+
"--limit",
|
|
122
|
+
"-l",
|
|
123
|
+
help="Number of recent executions to show",
|
|
124
|
+
min=1,
|
|
125
|
+
max=100,
|
|
126
|
+
),
|
|
127
|
+
job_id: str | None = typer.Option(
|
|
128
|
+
None, "--job-id", "-j", help="Filter by specific job ID"
|
|
129
|
+
),
|
|
130
|
+
json_output: bool = typer.Option(
|
|
131
|
+
False, "--json", help="Output history in JSON format"
|
|
132
|
+
),
|
|
133
|
+
) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Show recent job execution history.
|
|
136
|
+
|
|
137
|
+
Displays execution times, durations, and results for recent job runs.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
if job_id:
|
|
142
|
+
rprint(
|
|
143
|
+
f"📚 [bold blue]Execution History for: {job_id}[/bold blue] "
|
|
144
|
+
f"(last {limit})"
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
rprint(
|
|
148
|
+
f"📚 [bold blue]Recent Job Execution History[/bold blue] "
|
|
149
|
+
f"(last {limit})"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
history = asyncio.run(_get_job_history(limit, job_id))
|
|
153
|
+
|
|
154
|
+
if json_output:
|
|
155
|
+
print(json.dumps(history, indent=2, default=str))
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
if not history:
|
|
159
|
+
rprint("ℹ️ [yellow]No execution history found[/yellow]")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
table = Table(
|
|
163
|
+
title="🕰️ Job Execution History",
|
|
164
|
+
show_header=True,
|
|
165
|
+
header_style="bold magenta",
|
|
166
|
+
)
|
|
167
|
+
table.add_column("Timestamp", style="cyan")
|
|
168
|
+
table.add_column("Job ID", style="green")
|
|
169
|
+
table.add_column("Duration", style="yellow")
|
|
170
|
+
table.add_column("Status", style="bold")
|
|
171
|
+
table.add_column("Details", style="dim")
|
|
172
|
+
|
|
173
|
+
for execution in history:
|
|
174
|
+
status_color = "green" if execution.get("status") == "success" else "red"
|
|
175
|
+
duration = f"{execution.get('duration_ms', 0):.1f}ms"
|
|
176
|
+
|
|
177
|
+
status_value = execution.get('status', 'Unknown')
|
|
178
|
+
status_display = f"[{status_color}]{status_value}[/{status_color}]"
|
|
179
|
+
details = execution.get("details", "")[:50]
|
|
180
|
+
if len(execution.get("details", "")) > 50:
|
|
181
|
+
details += "..."
|
|
182
|
+
|
|
183
|
+
table.add_row(
|
|
184
|
+
execution.get("timestamp", "Unknown"),
|
|
185
|
+
execution.get("job_id", "Unknown"),
|
|
186
|
+
duration,
|
|
187
|
+
status_display,
|
|
188
|
+
details,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
console.print(table)
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
rprint(f"❌ [red]Failed to get job history:[/red] {e}")
|
|
195
|
+
raise typer.Exit(1)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@app.command("trigger")
|
|
199
|
+
def trigger_job(
|
|
200
|
+
job_id: str = typer.Argument(..., help="Job ID to trigger manually"),
|
|
201
|
+
wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for job completion"),
|
|
202
|
+
timeout: int = typer.Option(
|
|
203
|
+
30, "--timeout", help="Timeout for waiting (seconds)", min=1, max=300
|
|
204
|
+
),
|
|
205
|
+
) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Manually trigger a scheduled job.
|
|
208
|
+
|
|
209
|
+
Executes the specified job immediately, bypassing the normal schedule.
|
|
210
|
+
Useful for testing job functionality or running jobs on-demand.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
rprint(f"🚀 [bold blue]Triggering job:[/bold blue] {job_id}")
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
# First verify the job exists
|
|
217
|
+
tasks = asyncio.run(_get_scheduled_tasks())
|
|
218
|
+
job_exists = any(task.job_id == job_id for task in tasks)
|
|
219
|
+
|
|
220
|
+
if not job_exists:
|
|
221
|
+
rprint(f"❌ [red]Job not found:[/red] {job_id}")
|
|
222
|
+
rprint("💡 [yellow]Use 'tasks list' to see available jobs[/yellow]")
|
|
223
|
+
raise typer.Exit(1)
|
|
224
|
+
|
|
225
|
+
# Trigger the job
|
|
226
|
+
result = asyncio.run(_trigger_job_execution(job_id, wait, timeout))
|
|
227
|
+
|
|
228
|
+
if result.get("status") == "success":
|
|
229
|
+
rprint("✅ [green]Job triggered successfully![/green]")
|
|
230
|
+
if wait and result.get("execution_details"):
|
|
231
|
+
details = result["execution_details"]
|
|
232
|
+
rprint(
|
|
233
|
+
f"⏱️ [cyan]Duration:[/cyan] {details.get('duration_ms', 0):.1f}ms"
|
|
234
|
+
)
|
|
235
|
+
rprint(f"📋 [cyan]Result:[/cyan] {details.get('result', 'No result')}")
|
|
236
|
+
else:
|
|
237
|
+
rprint(
|
|
238
|
+
f"❌ [red]Job execution failed:[/red] "
|
|
239
|
+
f"{result.get('error', 'Unknown error')}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
rprint(f"❌ [red]Failed to trigger job:[/red] {e}")
|
|
244
|
+
raise typer.Exit(1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
async def _get_scheduled_tasks() -> list[Any]:
|
|
248
|
+
"""Get list of scheduled tasks from the scheduler."""
|
|
249
|
+
manager = ScheduledTaskManager()
|
|
250
|
+
return await manager.list_tasks()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
async def _get_job_statistics(job_id: str) -> dict[str, Any]:
|
|
254
|
+
"""Get statistics for a specific job."""
|
|
255
|
+
manager = ScheduledTaskManager()
|
|
256
|
+
return await manager.get_job_statistics(job_id)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
async def _get_overall_statistics() -> dict[str, Any]:
|
|
260
|
+
"""Get overall scheduler statistics."""
|
|
261
|
+
manager = ScheduledTaskManager()
|
|
262
|
+
return await manager.get_overall_statistics()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
async def _get_job_history(
|
|
266
|
+
limit: int, job_id: str | None
|
|
267
|
+
) -> list[dict[str, Any]]:
|
|
268
|
+
"""Get recent job execution history."""
|
|
269
|
+
manager = ScheduledTaskManager()
|
|
270
|
+
return await manager.get_job_history(limit, job_id)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
async def _trigger_job_execution(
|
|
274
|
+
job_id: str, wait: bool, timeout: int
|
|
275
|
+
) -> dict[str, Any]:
|
|
276
|
+
"""Trigger manual job execution."""
|
|
277
|
+
manager = ScheduledTaskManager()
|
|
278
|
+
return await manager.trigger_job(job_id, wait, timeout)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _display_statistics(stats: dict[str, Any], job_id: str | None) -> None:
|
|
282
|
+
"""Display formatted statistics."""
|
|
283
|
+
|
|
284
|
+
if job_id:
|
|
285
|
+
# Job-specific statistics
|
|
286
|
+
info_text = f"""
|
|
287
|
+
[bold cyan]Job Performance Metrics[/bold cyan]
|
|
288
|
+
|
|
289
|
+
🏃 [yellow]Executions:[/yellow] {stats.get('total_executions', 0)}
|
|
290
|
+
✅ [yellow]Successful:[/yellow] {stats.get('successful_executions', 0)}
|
|
291
|
+
❌ [yellow]Failed:[/yellow] {stats.get('failed_executions', 0)}
|
|
292
|
+
📊 [yellow]Success Rate:[/yellow] {stats.get('success_rate_percent', 0):.1f}%
|
|
293
|
+
|
|
294
|
+
⏱️ [yellow]Avg Duration:[/yellow] {stats.get('avg_duration_ms', 0):.1f}ms
|
|
295
|
+
🚀 [yellow]Min Duration:[/yellow] {stats.get('min_duration_ms', 0):.1f}ms
|
|
296
|
+
🐌 [yellow]Max Duration:[/yellow] {stats.get('max_duration_ms', 0):.1f}ms
|
|
297
|
+
|
|
298
|
+
🕐 [yellow]Last Execution:[/yellow] {stats.get('last_execution', 'Never')}
|
|
299
|
+
🕑 [yellow]Next Scheduled:[/yellow] {stats.get('next_run', 'Not scheduled')}
|
|
300
|
+
""".strip()
|
|
301
|
+
|
|
302
|
+
console.print(
|
|
303
|
+
Panel(
|
|
304
|
+
info_text,
|
|
305
|
+
title=f"📊 Statistics for {job_id}",
|
|
306
|
+
border_style="blue",
|
|
307
|
+
)
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
else:
|
|
311
|
+
# Overall scheduler statistics
|
|
312
|
+
info_text = f"""
|
|
313
|
+
[bold cyan]Scheduler Overview[/bold cyan]
|
|
314
|
+
|
|
315
|
+
📋 [yellow]Total Jobs:[/yellow] {stats.get('total_jobs', 0)}
|
|
316
|
+
🟢 [yellow]Active Jobs:[/yellow] {stats.get('active_jobs', 0)}
|
|
317
|
+
🔴 [yellow]Paused Jobs:[/yellow] {stats.get('paused_jobs', 0)}
|
|
318
|
+
|
|
319
|
+
🏃 [yellow]Total Executions:[/yellow] {stats.get('total_executions', 0)}
|
|
320
|
+
✅ [yellow]Successful:[/yellow] {stats.get('successful_executions', 0)}
|
|
321
|
+
❌ [yellow]Failed:[/yellow] {stats.get('failed_executions', 0)}
|
|
322
|
+
|
|
323
|
+
📊 [yellow]Overall Success Rate:[/yellow] {stats.get('success_rate_percent', 0):.1f}%
|
|
324
|
+
⏱️ [yellow]Avg Execution Time:[/yellow] {stats.get('avg_duration_ms', 0):.1f}ms
|
|
325
|
+
|
|
326
|
+
🕐 [yellow]Scheduler Uptime:[/yellow] {stats.get('uptime', 'Unknown')}
|
|
327
|
+
🔄 [yellow]Last Activity:[/yellow] {stats.get('last_activity', 'No recent activity')}
|
|
328
|
+
""".strip()
|
|
329
|
+
|
|
330
|
+
console.print(
|
|
331
|
+
Panel(
|
|
332
|
+
info_text,
|
|
333
|
+
title="📊 Scheduler Statistics",
|
|
334
|
+
border_style="green",
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == "__main__":
|
|
340
|
+
app()
|
|
File without changes
|
|
File without changes
|