chacc-api 1.0.0b1__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.
Files changed (48) hide show
  1. chacc_api/__init__.py +44 -0
  2. chacc_api/api/__init__.py +5 -0
  3. chacc_api/core/__init__.py +8 -0
  4. chacc_api/database/__init__.py +25 -0
  5. chacc_api/loaders/__init__.py +5 -0
  6. chacc_api/migration/__init__.py +5 -0
  7. chacc_api/server/__init__.py +3 -0
  8. chacc_api/server/main.py +219 -0
  9. chacc_api/server/start_server.py +78 -0
  10. chacc_api/server/uvicorn_config.py +64 -0
  11. chacc_api/services/__init__.py +7 -0
  12. chacc_api/utils/__init__.py +67 -0
  13. chacc_api-1.0.0b1.dist-info/METADATA +278 -0
  14. chacc_api-1.0.0b1.dist-info/RECORD +48 -0
  15. chacc_api-1.0.0b1.dist-info/WHEEL +5 -0
  16. chacc_api-1.0.0b1.dist-info/entry_points.txt +2 -0
  17. chacc_api-1.0.0b1.dist-info/licenses/LICENSE +201 -0
  18. chacc_api-1.0.0b1.dist-info/top_level.txt +3 -0
  19. chacc_cli/__init__.py +17 -0
  20. chacc_cli/__main__.py +149 -0
  21. chacc_cli/commands.py +383 -0
  22. chacc_cli/templates/README.md.template +70 -0
  23. chacc_cli/templates/context_factory.py.template +77 -0
  24. chacc_cli/templates/dev_context.py.template +102 -0
  25. chacc_cli/templates/main.py.template +36 -0
  26. chacc_cli/templates/models.py.template +18 -0
  27. chacc_cli/templates/module_meta.json.template +16 -0
  28. chacc_cli/templates/routes.py.template +23 -0
  29. chacc_cli/templates/run_tests.py.template +36 -0
  30. chacc_cli/templates/test_module.py.template +38 -0
  31. src/__init__.py +7 -0
  32. src/chacc_dependency_manager.py +80 -0
  33. src/constants.py +75 -0
  34. src/core_services.py +95 -0
  35. src/database.py +271 -0
  36. src/env_validator.py +211 -0
  37. src/health.py +85 -0
  38. src/logger.py +50 -0
  39. src/migration/__init__.py +37 -0
  40. src/migration/backup.py +293 -0
  41. src/migration/runner.py +411 -0
  42. src/migration/tracker.py +210 -0
  43. src/module_loader.py +669 -0
  44. src/modules.py +427 -0
  45. src/plugin_loader.py +265 -0
  46. src/rate_limiter.py +20 -0
  47. src/redis_service.py +96 -0
  48. src/services/__init__.py +8 -0
chacc_api/__init__.py ADDED
@@ -0,0 +1,44 @@
1
+ """
2
+ ChaCC API - Python SDK for ChaCC API development.
3
+
4
+ This package provides the core APIs that developers should import from
5
+ when building modules for the ChaCC API backbone.
6
+
7
+ Usage:
8
+ from chacc_api import BackboneContext, ChaCCBaseModel, RedisService
9
+ from chacc_api.database import register_model, get_db
10
+ """
11
+
12
+ # Re-export core services
13
+ from src.core_services import BackboneContext
14
+
15
+ # Re-export database models and utilities
16
+ from src.database import (
17
+ ChaCCBaseModel,
18
+ register_model,
19
+ get_db,
20
+ ModuleRecord,
21
+ initialize_database_models,
22
+ run_automatic_migration,
23
+ metadata_obj,
24
+ engine,
25
+ )
26
+
27
+ # Re-export services
28
+ from src.redis_service import RedisService
29
+
30
+ __all__ = [
31
+ # Core
32
+ "BackboneContext",
33
+ # Database
34
+ "ChaCCBaseModel",
35
+ "register_model",
36
+ "get_db",
37
+ "ModuleRecord",
38
+ "initialize_database_models",
39
+ "run_automatic_migration",
40
+ "metadata_obj",
41
+ "engine",
42
+ # Services
43
+ "RedisService",
44
+ ]
@@ -0,0 +1,5 @@
1
+ """
2
+ API routes for ChaCC API.
3
+ """
4
+
5
+ __all__ = []
@@ -0,0 +1,8 @@
1
+ """
2
+ Core services for ChaCC API.
3
+ """
4
+
5
+ from ...src.core_services import BackboneContext
6
+ from ...src.redis_service import RedisService
7
+
8
+ __all__ = ["BackboneContext", "RedisService"]
@@ -0,0 +1,25 @@
1
+ """
2
+ Database models and utilities for ChaCC API.
3
+ """
4
+
5
+ from ...src.database import (
6
+ ChaCCBaseModel,
7
+ register_model,
8
+ get_db,
9
+ ModuleRecord,
10
+ initialize_database_models,
11
+ run_automatic_migration,
12
+ metadata_obj,
13
+ engine,
14
+ )
15
+
16
+ __all__ = [
17
+ "ChaCCBaseModel",
18
+ "register_model",
19
+ "get_db",
20
+ "ModuleRecord",
21
+ "initialize_database_models",
22
+ "run_automatic_migration",
23
+ "metadata_obj",
24
+ "engine",
25
+ ]
@@ -0,0 +1,5 @@
1
+ """
2
+ Loaders for ChaCC API modules and plugins.
3
+ """
4
+
5
+ __all__ = []
@@ -0,0 +1,5 @@
1
+ """
2
+ Migration utilities for ChaCC API.
3
+ """
4
+
5
+ __all__ = []
@@ -0,0 +1,3 @@
1
+ """
2
+ Server configuration and startup files for ChaCC API.
3
+ """
@@ -0,0 +1,219 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ from fastapi import FastAPI
5
+ from fastapi.concurrency import asynccontextmanager
6
+ from slowapi.errors import RateLimitExceeded
7
+ from src.rate_limiter import limiter, rate_limit_exceeded_handler
8
+ from src.modules import modules_router
9
+ from src.health import health_router
10
+ from src.database import ModuleRecord, initialize_database_models, get_db
11
+ from src.logger import configure_logging, LogLevels
12
+ from src.core_services import BackboneContext
13
+ from src.constants import DEVELOPMENT_MODE, MODULES_LOADED_DIR, PLUGINS_DIR, BASE_DIR
14
+ from src.env_validator import validate_environment, ValidationError
15
+
16
+ from src.migration.runner import run_migration
17
+ from src.redis_service import RedisService
18
+
19
+ chacc_logger = configure_logging(log_level=LogLevels.DEBUG)
20
+
21
+
22
+ async def run_backbone_tests():
23
+ """
24
+ Run backbone unit tests on startup.
25
+ Raises RuntimeError if tests fail to prevent app startup.
26
+ Only runs if tests directory exists in CWD.
27
+ """
28
+ tests_path = os.path.join(BASE_DIR, "tests", "test_backbone.py")
29
+ if not os.path.exists(tests_path):
30
+ chacc_logger.info(
31
+ "No backbone tests found in CWD. Skipping tests (not a development install)."
32
+ )
33
+ return
34
+
35
+ chacc_logger.info("Running backbone unit tests...")
36
+ try:
37
+ proc = await asyncio.create_subprocess_exec(
38
+ sys.executable,
39
+ "-m",
40
+ "pytest",
41
+ f"{BASE_DIR}/tests/test_backbone.py",
42
+ "-v",
43
+ "--tb=short",
44
+ "--no-header",
45
+ stdout=asyncio.subprocess.PIPE,
46
+ stderr=asyncio.subprocess.PIPE,
47
+ )
48
+ stdout, stderr = await proc.communicate()
49
+ result_stdout = stdout.decode() if stdout else ""
50
+ result_stderr = stderr.decode() if stderr else ""
51
+
52
+ passed_tests = []
53
+ failed_tests = []
54
+
55
+ if result_stdout:
56
+ lines = result_stdout.strip().split("\n")
57
+ for line in lines:
58
+ line = line.strip()
59
+ if "PASSED" in line:
60
+ passed_tests.append(line)
61
+ elif "FAILED" in line or "ERROR" in line:
62
+ failed_tests.append(line)
63
+
64
+ if proc.returncode == 0:
65
+ chacc_logger.info(f"All backbone tests passed successfully ({len(passed_tests)} tests)")
66
+ if passed_tests:
67
+ chacc_logger.info("Passed tests:")
68
+ for test in passed_tests:
69
+ chacc_logger.info(f" ✓ {test}")
70
+ else:
71
+ chacc_logger.error(f"Backbone tests failed with return code {proc.returncode}")
72
+
73
+ if passed_tests:
74
+ chacc_logger.info(f"Passed tests ({len(passed_tests)}):")
75
+ for test in passed_tests:
76
+ chacc_logger.info(f" ✓ {test}")
77
+
78
+ if failed_tests:
79
+ chacc_logger.error(f"Failed tests ({len(failed_tests)}):")
80
+ for test in failed_tests:
81
+ chacc_logger.error(f" ✗ {test}")
82
+ else:
83
+ chacc_logger.error("Test output:")
84
+ if result_stdout:
85
+ chacc_logger.error(result_stdout)
86
+
87
+ if result_stderr:
88
+ chacc_logger.error(f"Test stderr: {result_stderr}")
89
+
90
+ raise RuntimeError(
91
+ f"Backbone tests failed ({len(failed_tests)} failed, {len(passed_tests)} passed). Application startup aborted."
92
+ )
93
+
94
+ except Exception as e:
95
+ chacc_logger.error(f"Unexpected error running backbone tests: {e}")
96
+ raise RuntimeError(f"Backbone tests failed due to unexpected error: {e}")
97
+
98
+
99
+ @asynccontextmanager
100
+ async def onStartupLifespan(app: FastAPI):
101
+ """
102
+ FastAPI lifespan context manager for startup and shutdown events.
103
+ """
104
+ chacc_logger.info("Application startup initiated...")
105
+
106
+ redis_service = RedisService()
107
+
108
+ try:
109
+ validate_environment()
110
+ except ValidationError as e:
111
+ chacc_logger.critical(f"Environment validation failed: {e}")
112
+ raise RuntimeError(f"Cannot start application: {e}")
113
+
114
+ backbone_context = BackboneContext(
115
+ app=app, limiter=app.state.limiter, logger=chacc_logger, db_session_factory=get_db
116
+ )
117
+
118
+ try:
119
+ redis_client = await redis_service.get_client()
120
+ if redis_client:
121
+ backbone_context.register_service("redis", redis_service)
122
+ chacc_logger.info("Redis service registered in backbone context.")
123
+ elif redis_service.connection_error:
124
+ chacc_logger.warning(
125
+ f"Redis connection failed: {redis_service.connection_error}. Continuing without Redis."
126
+ )
127
+ else:
128
+ chacc_logger.info("Redis is disabled. Continuing without Redis.")
129
+ except Exception as e:
130
+ chacc_logger.warning(f"Failed to initialize Redis service: {e}. Continuing without Redis.")
131
+
132
+ app.state.backbone_context = backbone_context
133
+
134
+ initialize_database_models(backbone_context)
135
+
136
+ session = await anext(get_db())
137
+ modules_table_exists = False
138
+ try:
139
+ session.query(ModuleRecord).first()
140
+ modules_table_exists = True
141
+ chacc_logger.info("Modules table exists. Proceeding with regular startup sequence.")
142
+ except Exception:
143
+ chacc_logger.warning("Modules table does not exist. Running initial migration.")
144
+ pass
145
+
146
+ if not modules_table_exists:
147
+ await run_migration()
148
+
149
+ print("First migration completed. Running backbone tests before loading modules...")
150
+
151
+ await run_backbone_tests()
152
+
153
+ if DEVELOPMENT_MODE:
154
+ chacc_logger.info("=" * 65)
155
+ chacc_logger.info(f"DEVELOPMENT MODE: Loading plugins from {PLUGINS_DIR} directory")
156
+ chacc_logger.info("=" * 65)
157
+ from src.plugin_loader import load_dev_modules
158
+
159
+ await load_dev_modules(app, backbone_context)
160
+ else:
161
+ from src.module_loader import load_modules
162
+
163
+ chacc_logger.info("=" * 65)
164
+ chacc_logger.info(f"PRODUCTION MODE: Loading modules from {MODULES_LOADED_DIR} directory")
165
+ chacc_logger.info("=" * 65)
166
+ await load_modules(app, backbone_context)
167
+
168
+ await run_migration()
169
+
170
+ yield
171
+
172
+ try:
173
+ redis_service = backbone_context.get_service("redis")
174
+ if redis_service and redis_service.is_connected:
175
+ await redis_service.close()
176
+ chacc_logger.info("Redis connection closed gracefully.")
177
+ else:
178
+ chacc_logger.debug("Redis service not available or not connected. Skipping cleanup.")
179
+ except Exception as e:
180
+ chacc_logger.warning(f"Error during Redis shutdown: {e}")
181
+
182
+ chacc_logger.info("Application shutting down.")
183
+
184
+
185
+ app = FastAPI(
186
+ title="ChaCC API Backbone",
187
+ description="A modular FastAPI application for extensible APIs.",
188
+ version="1.0.0",
189
+ docs_url="/docs",
190
+ redoc_url="/redoc",
191
+ lifespan=onStartupLifespan,
192
+ )
193
+
194
+ app.state.limiter = limiter
195
+ app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
196
+
197
+ app.state.loaded_modules = {}
198
+ app.state.mounted_routers = {}
199
+
200
+ app.state.backbone_context = None
201
+
202
+
203
+ @app.get(
204
+ "/",
205
+ summary="Root Endpoint",
206
+ description="Welcome endpoint for the ChaCC API Backbone",
207
+ response_description="Welcome message with documentation link",
208
+ tags=["Core"],
209
+ )
210
+ async def read_root():
211
+ """
212
+ Root endpoint of the ChaCC API backbone.
213
+ Returns a welcome message and directs users to the API documentation.
214
+ """
215
+ return {"message": "Welcome to the ChaCC API Backbone! Check /docs for API modules."}
216
+
217
+
218
+ app.include_router(health_router)
219
+ app.include_router(modules_router)
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Safe server startup script that prevents auto-reloader loops.
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ import subprocess
9
+
10
+ from chacc_api.utils import LogLevels, configure_logging, BASE_DIR
11
+
12
+ logger = configure_logging(log_level=LogLevels.INFO)
13
+
14
+
15
+ def run_tests_safely():
16
+ """Run tests in a way that doesn't trigger auto-reloader."""
17
+ tests_path = os.path.join(BASE_DIR, "tests", "test_backbone.py")
18
+ if not os.path.exists(tests_path):
19
+ logger.info("No backbone tests found in CWD. Skipping tests (not a development install).")
20
+ return True
21
+
22
+ logger.info("Running backbone tests safely...")
23
+
24
+ try:
25
+ result = subprocess.run(
26
+ [sys.executable, "-m", "pytest", tests_path, "-v", "--tb=short", "--no-header"],
27
+ capture_output=True,
28
+ text=True,
29
+ cwd=BASE_DIR,
30
+ )
31
+
32
+ if result.returncode == 0:
33
+ logger.info("✅ All backbone tests passed!")
34
+ return True
35
+ else:
36
+ logger.error("❌ Backbone tests failed!")
37
+ if result.stdout:
38
+ logger.error("Test output:")
39
+ logger.error(result.stdout)
40
+ if result.stderr:
41
+ logger.error("Test errors:")
42
+ logger.error(result.stderr)
43
+ return False
44
+
45
+ except Exception as e:
46
+ logger.error(f"❌ Error running tests: {e}")
47
+ return False
48
+
49
+
50
+ def start_server():
51
+ """Start the server without auto-reload."""
52
+ logger.info("Starting server without auto-reload...")
53
+
54
+ try:
55
+ import uvicorn
56
+
57
+ uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=False)
58
+ except Exception as e:
59
+ logger.error(f"❌ Error starting server: {e}")
60
+ sys.exit(1)
61
+
62
+
63
+ def main():
64
+ """Main startup sequence."""
65
+ logger.info("=" * 60)
66
+ logger.info("Starting ChaCC API Server (Safe Mode)")
67
+ logger.info("=" * 60)
68
+
69
+ if not run_tests_safely():
70
+ logger.error("🔴 Tests failed. Server startup aborted.")
71
+ sys.exit(1)
72
+
73
+ logger.info("🟢 Tests passed. Starting server...")
74
+ start_server()
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -0,0 +1,64 @@
1
+ """
2
+ Uvicorn configuration to prevent auto-reloader loops.
3
+ """
4
+
5
+ import os
6
+ from chacc_api.utils import (
7
+ PLUGINS_DIR,
8
+ MODULES_LOADED_DIR,
9
+ MODULES_UPLOAD_DIR,
10
+ MODULES_INSTALLED_DIR,
11
+ DEVELOPMENT_MODE,
12
+ DEPENDENCY_CACHE_DIR,
13
+ BACKBONE_REQUIREMENTS_LOCK_FILE,
14
+ DEPENDENCY_CACHE_FILE,
15
+ ENABLE_PLUGIN_HOT_RELOAD,
16
+ BASE_DIR,
17
+ )
18
+
19
+
20
+ # Get relative paths for uvicorn (it needs paths relative to cwd)
21
+ def get_relative_path(abs_path):
22
+ """Convert absolute path to relative path from base directory."""
23
+ if abs_path.startswith(BASE_DIR):
24
+ return os.path.relpath(abs_path, BASE_DIR)
25
+ return abs_path
26
+
27
+
28
+ config = {
29
+ "app": "main:app",
30
+ "host": "0.0.0.0",
31
+ "port": 8080,
32
+ "reload": True,
33
+ "reload_dirs": ["src", "main.py"],
34
+ "reload_excludes": [
35
+ f"{get_relative_path(MODULES_LOADED_DIR)}/",
36
+ f"{get_relative_path(MODULES_UPLOAD_DIR)}/",
37
+ f"{get_relative_path(DEPENDENCY_CACHE_DIR)}/",
38
+ get_relative_path(BACKBONE_REQUIREMENTS_LOCK_FILE),
39
+ f"{get_relative_path(MODULES_INSTALLED_DIR)}/",
40
+ get_relative_path(DEPENDENCY_CACHE_FILE),
41
+ "*.chacc",
42
+ "__pycache__",
43
+ ".pytest_cache",
44
+ "tests/",
45
+ "*.pyc",
46
+ "*.db",
47
+ "*.log",
48
+ ".env",
49
+ (
50
+ f"{get_relative_path(PLUGINS_DIR)}/"
51
+ if "/" in get_relative_path(PLUGINS_DIR)
52
+ else (
53
+ get_relative_path(PLUGINS_DIR)
54
+ if not DEVELOPMENT_MODE and ENABLE_PLUGIN_HOT_RELOAD
55
+ else ""
56
+ )
57
+ ),
58
+ ],
59
+ }
60
+
61
+ if __name__ == "__main__":
62
+ import uvicorn
63
+
64
+ uvicorn.run(**config)
@@ -0,0 +1,7 @@
1
+ """
2
+ Services for ChaCC API.
3
+ """
4
+
5
+ from src.redis_service import RedisService
6
+
7
+ __all__ = ["RedisService"]
@@ -0,0 +1,67 @@
1
+ """
2
+ Utilities for ChaCC API.
3
+ """
4
+
5
+ from src.logger import logging, LogLevels, configure_logging
6
+ from src.constants import (
7
+ BASE_DIR,
8
+ MODULES_INSTALLED_DIR,
9
+ MODULES_LOADED_DIR,
10
+ MODULES_UPLOAD_DIR,
11
+ PLUGINS_DIR,
12
+ DEPENDENCY_CACHE_DIR,
13
+ MIGRATION_MODE,
14
+ MIGRATION_BACKUP,
15
+ MIGRATION_BACKUP_DIR,
16
+ MIGRATION_AUTO_DROP,
17
+ DEVELOPMENT_MODE,
18
+ ENABLE_PLUGIN_HOT_RELOAD,
19
+ ENABLE_PLUGIN_DEPENDENCY_RESOLUTION,
20
+ PLUGIN_AUTO_DISCOVERY,
21
+ REDIS_ENABLED,
22
+ REDIS_HOST,
23
+ REDIS_PORT,
24
+ REDIS_DB,
25
+ REDIS_PASSWORD,
26
+ DATABASE_ENGINE,
27
+ DATABASE_NAME,
28
+ DATABASE_USER,
29
+ DATABASE_PASSWORD,
30
+ DATABASE_HOST,
31
+ DATABASE_PORT,
32
+ DATABASE_URL,
33
+ LOGGER_NAME,
34
+ )
35
+
36
+ __all__ = [
37
+ "logging",
38
+ "LogLevels",
39
+ "configure_logging",
40
+ "BASE_DIR",
41
+ "MODULES_INSTALLED_DIR",
42
+ "MODULES_LOADED_DIR",
43
+ "MODULES_UPLOAD_DIR",
44
+ "PLUGINS_DIR",
45
+ "DEPENDENCY_CACHE_DIR",
46
+ "MIGRATION_MODE",
47
+ "MIGRATION_BACKUP",
48
+ "MIGRATION_BACKUP_DIR",
49
+ "MIGRATION_AUTO_DROP",
50
+ "DEVELOPMENT_MODE",
51
+ "ENABLE_PLUGIN_HOT_RELOAD",
52
+ "ENABLE_PLUGIN_DEPENDENCY_RESOLUTION",
53
+ "PLUGIN_AUTO_DISCOVERY",
54
+ "REDIS_ENABLED",
55
+ "REDIS_HOST",
56
+ "REDIS_PORT",
57
+ "REDIS_DB",
58
+ "REDIS_PASSWORD",
59
+ "DATABASE_ENGINE",
60
+ "DATABASE_NAME",
61
+ "DATABASE_USER",
62
+ "DATABASE_PASSWORD",
63
+ "DATABASE_HOST",
64
+ "DATABASE_PORT",
65
+ "DATABASE_URL",
66
+ "LOGGER_NAME",
67
+ ]