aegis-stack 0.1.0__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.

Potentially problematic release.


This version of aegis-stack might be problematic. Click here for more details.

Files changed (103) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +374 -0
  3. aegis/core/CLAUDE.md +365 -0
  4. aegis/core/__init__.py +6 -0
  5. aegis/core/components.py +115 -0
  6. aegis/core/dependency_resolver.py +119 -0
  7. aegis/core/template_generator.py +163 -0
  8. aegis/templates/CLAUDE.md +306 -0
  9. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +27 -0
  10. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +172 -0
  11. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  12. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +70 -0
  13. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +127 -0
  14. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  15. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +211 -0
  16. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  17. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  18. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  19. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py +321 -0
  20. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py +638 -0
  21. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py +41 -0
  22. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  23. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  24. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +134 -0
  25. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +247 -0
  26. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +14 -0
  27. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/tasks.py.j2 +596 -0
  28. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +133 -0
  29. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +16 -0
  30. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  31. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  32. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  33. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  34. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  35. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +190 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +0 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/__init__.py +1 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/theme.py +46 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py +687 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py +138 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md +213 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +78 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +48 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +41 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +36 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +119 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +526 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +32 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +279 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +119 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +60 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py +67 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +85 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +61 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +661 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +269 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1105 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +169 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +195 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +414 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +156 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +104 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +239 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +76 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +81 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +376 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +633 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +665 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +602 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +96 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +224 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +50 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  100. aegis_stack-0.1.0.dist-info/METADATA +114 -0
  101. aegis_stack-0.1.0.dist-info/RECORD +103 -0
  102. aegis_stack-0.1.0.dist-info/WHEEL +4 -0
  103. aegis_stack-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,139 @@
1
+ """
2
+ Worker queue registry with dynamic discovery.
3
+
4
+ Pure arq implementation - WorkerSettings classes are the single source of truth.
5
+ No configuration files, no abstractions, just arq as intended.
6
+ """
7
+
8
+ import importlib
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from app.core.log import logger
13
+
14
+
15
+ def get_worker_settings(queue_name: str) -> Any:
16
+ """Import and return WorkerSettings class for a queue.
17
+
18
+ Args:
19
+ queue_name: Name of the queue (e.g., 'system', 'load_test')
20
+
21
+ Returns:
22
+ WorkerSettings class from the queue module
23
+
24
+ Raises:
25
+ ImportError: If queue module doesn't exist
26
+ AttributeError: If WorkerSettings class not found
27
+ """
28
+ try:
29
+ module = importlib.import_module(f"app.components.worker.queues.{queue_name}")
30
+ return module.WorkerSettings
31
+ except ImportError as e:
32
+ logger.error(f"Failed to import worker queue '{queue_name}': {e}")
33
+ raise
34
+ except AttributeError as e:
35
+ logger.error(f"WorkerSettings class not found in '{queue_name}' queue: {e}")
36
+ raise
37
+
38
+
39
+ def discover_worker_queues() -> list[str]:
40
+ """Discover all worker queues from the queues directory.
41
+
42
+ Scans app/components/worker/queues/ for Python files and treats each
43
+ file as a potential queue. Excludes __init__.py and other non-queue files.
44
+
45
+ Returns:
46
+ Sorted list of queue names
47
+ """
48
+ queues_dir = Path(__file__).parent / "queues"
49
+
50
+ if not queues_dir.exists():
51
+ logger.warning(f"Worker queues directory not found: {queues_dir}")
52
+ return []
53
+
54
+ queue_files = queues_dir.glob("*.py")
55
+ queues = []
56
+
57
+ for file in queue_files:
58
+ # Skip __init__.py and other special files
59
+ if file.stem not in ["__init__", "__pycache__"]:
60
+ # Verify the file has a WorkerSettings class
61
+ try:
62
+ get_worker_settings(file.stem)
63
+ queues.append(file.stem)
64
+ except (ImportError, AttributeError):
65
+ logger.debug(f"Skipping '{file.stem}' - no valid WorkerSettings class")
66
+ continue
67
+
68
+ return sorted(queues)
69
+
70
+
71
+ def get_queue_metadata(queue_name: str) -> dict[str, Any]:
72
+ """Get metadata for a queue from its WorkerSettings class.
73
+
74
+ Args:
75
+ queue_name: Name of the queue
76
+
77
+ Returns:
78
+ Dictionary with queue metadata:
79
+ - queue_name: Redis queue name
80
+ - max_jobs: Maximum concurrent jobs
81
+ - timeout: Job timeout in seconds
82
+ - functions: List of function names in this queue
83
+ - description: Human-readable description (if available)
84
+ """
85
+ try:
86
+ settings_class = get_worker_settings(queue_name)
87
+
88
+ metadata = {
89
+ "queue_name": getattr(
90
+ settings_class, "queue_name", f"arq:queue:{queue_name}"
91
+ ),
92
+ "max_jobs": getattr(settings_class, "max_jobs", 10),
93
+ "timeout": getattr(settings_class, "job_timeout", 300),
94
+ "functions": [f.__name__ for f in getattr(settings_class, "functions", [])],
95
+ }
96
+
97
+ # Add description if available
98
+ if hasattr(settings_class, "description"):
99
+ metadata["description"] = settings_class.description
100
+ elif hasattr(settings_class, "__doc__") and settings_class.__doc__:
101
+ metadata["description"] = settings_class.__doc__.strip()
102
+ else:
103
+ metadata["description"] = f"{queue_name.title()} worker queue"
104
+
105
+ return metadata
106
+
107
+ except (ImportError, AttributeError) as e:
108
+ logger.error(f"Failed to get metadata for queue '{queue_name}': {e}")
109
+ return {
110
+ "queue_name": f"arq:queue:{queue_name}",
111
+ "max_jobs": 10,
112
+ "timeout": 300,
113
+ "functions": [],
114
+ "description": f"Unknown queue: {queue_name}",
115
+ }
116
+
117
+
118
+ def get_all_queue_metadata() -> dict[str, dict[str, Any]]:
119
+ """Get metadata for all discovered worker queues.
120
+
121
+ Returns:
122
+ Dictionary mapping queue names to their metadata
123
+ """
124
+ metadata = {}
125
+ for queue_name in discover_worker_queues():
126
+ metadata[queue_name] = get_queue_metadata(queue_name)
127
+ return metadata
128
+
129
+
130
+ def validate_queue_name(queue_name: str) -> bool:
131
+ """Check if a queue name is valid (has a corresponding WorkerSettings).
132
+
133
+ Args:
134
+ queue_name: Name to validate
135
+
136
+ Returns:
137
+ True if queue exists and has valid WorkerSettings
138
+ """
139
+ return queue_name in discover_worker_queues()
@@ -0,0 +1,119 @@
1
+ """
2
+ Worker tasks registry.
3
+
4
+ This module collects all available worker tasks and exports them for the arq worker.
5
+ Only includes production-ready, actually useful tasks.
6
+ """
7
+
8
+ from collections.abc import Callable
9
+ from typing import Any
10
+
11
+ from app.core.config import get_default_queue
12
+
13
+ from .load_tasks import (
14
+ cpu_intensive_task,
15
+ failure_testing_task,
16
+ io_simulation_task,
17
+ memory_operations_task,
18
+ )
19
+ from .system_tasks import (
20
+ load_test_orchestrator,
21
+ )
22
+
23
+ # All task functions available to arq workers
24
+ TASK_FUNCTIONS: list[Callable[..., Any]] = [
25
+ # Load testing orchestrator
26
+ load_test_orchestrator,
27
+ # Load testing tasks
28
+ cpu_intensive_task,
29
+ io_simulation_task,
30
+ memory_operations_task,
31
+ failure_testing_task,
32
+ ]
33
+
34
+
35
+ def get_task_by_name(task_name: str) -> Callable[..., Any] | None:
36
+ """
37
+ Get task function by name.
38
+
39
+ Args:
40
+ task_name: Name of the task function
41
+
42
+ Returns:
43
+ Task function or None if not found
44
+ """
45
+ for task_func in TASK_FUNCTIONS:
46
+ if task_func.__name__ == task_name:
47
+ return task_func
48
+ return None
49
+
50
+
51
+ def list_available_tasks() -> list[str]:
52
+ """
53
+ Get list of all available task names.
54
+
55
+ Returns:
56
+ List of task function names
57
+ """
58
+ return [task_func.__name__ for task_func in TASK_FUNCTIONS]
59
+
60
+
61
+ def get_queue_functions(queue_type: str) -> list[Callable[..., Any]]:
62
+ """
63
+ Get task functions specific to a queue type.
64
+
65
+ Args:
66
+ queue_type: The functional queue type ("media", "system", "load_test")
67
+
68
+ Returns:
69
+ List of task functions appropriate for this queue
70
+ """
71
+ # Function distribution by queue type
72
+ queue_function_map = {
73
+ "system": [
74
+ # System queue is for actual system tasks when needed
75
+ # Currently empty - add real system tasks here when required
76
+ ],
77
+ "media": [
78
+ # Future: Image processing, video encoding, file operations
79
+ # Currently empty - real media tasks will be added here
80
+ ],
81
+ "load_test": [
82
+ # Load testing orchestrator
83
+ load_test_orchestrator,
84
+ # Load testing tasks (synthetic workloads)
85
+ cpu_intensive_task,
86
+ io_simulation_task,
87
+ memory_operations_task,
88
+ failure_testing_task,
89
+ ],
90
+ }
91
+
92
+ from typing import cast
93
+ return cast(list[Callable[..., Any]], queue_function_map.get(queue_type, []))
94
+
95
+
96
+ def get_queue_for_task(task_name: str) -> str:
97
+ """
98
+ Get the appropriate queue type for a given task.
99
+
100
+ Args:
101
+ task_name: Name of the task function
102
+
103
+ Returns:
104
+ Queue type that should handle this task
105
+ """
106
+ # Task to queue mapping
107
+ task_queue_map = {
108
+ # Load test tasks
109
+ "load_test_orchestrator": "load_test",
110
+ "cpu_intensive_task": "load_test",
111
+ "io_simulation_task": "load_test",
112
+ "memory_operations_task": "load_test",
113
+ "failure_testing_task": "load_test",
114
+ # Future system and media tasks would go here
115
+ }
116
+
117
+ return task_queue_map.get(
118
+ task_name, get_default_queue()
119
+ ) # Default to configured default queue