- Convert legacy [DEF🆔Type] anchors to #region/#endregion across 329 files
- Reinstate _normalize_timestamp_value in sql_generator.py
- Fix MarkerLogger→logger migration in events.py (molecular CoT markers)
- Fix dataset_review orchestrator dependencies (_build_execution_snapshot)
- Fix config_manager stale-record deletion (moved to save path only)
- Add 77 missing [/DEF:] closers in 5 unbalanced test files
- Update assistant_chat.integration.test.js for #region format
- Apply molecular-cot-logging markers (REASON/REFLECT/EXPLORE) via logger.* methods
278 lines
9.6 KiB
Python
Executable File
278 lines
9.6 KiB
Python
Executable File
# #region AppDependencies [C:3] [TYPE Module] [SEMANTICS dependency, injection, singleton, factory, auth, jwt]
|
|
# @BRIEF Manages creation and provision of shared application dependencies, such as PluginLoader and TaskManager, to avoid circular imports.
|
|
# @LAYER: Core
|
|
# @RELATION Used by main app and API routers to get access to shared instances.
|
|
# @RELATION CALLS -> [CleanReleaseRepository]
|
|
# @RELATION CALLS -> [ConfigManager]
|
|
# @RELATION CALLS -> [PluginLoader]
|
|
# @RELATION CALLS -> [SchedulerService]
|
|
# @RELATION CALLS -> [TaskManager]
|
|
# @RELATION CALLS -> [get_all_plugin_configs]
|
|
# @RELATION CALLS -> [get_db]
|
|
# @RELATION CALLS -> [info]
|
|
# @RELATION CALLS -> [init_db]
|
|
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from fastapi import Depends, HTTPException, status
|
|
from fastapi.security import OAuth2PasswordBearer
|
|
from jose import JWTError
|
|
from .core.plugin_loader import PluginLoader
|
|
from .core.task_manager import TaskManager
|
|
from .core.config_manager import ConfigManager
|
|
from .core.scheduler import SchedulerService
|
|
from .services.resource_service import ResourceService
|
|
from .services.mapping_service import MappingService
|
|
from .services.clean_release.repositories import (
|
|
CandidateRepository,
|
|
ArtifactRepository,
|
|
ManifestRepository,
|
|
PolicyRepository,
|
|
ComplianceRepository,
|
|
ReportRepository,
|
|
ApprovalRepository,
|
|
PublicationRepository,
|
|
AuditRepository,
|
|
CleanReleaseAuditLog,
|
|
)
|
|
from .services.clean_release.repository import CleanReleaseRepository
|
|
from .services.clean_release.facade import CleanReleaseFacade
|
|
from .services.reports.report_service import ReportsService
|
|
from .core.database import init_db, get_auth_db, get_db
|
|
from .core.logger import logger
|
|
from .core.auth.jwt import decode_token
|
|
from .core.auth.repository import AuthRepository
|
|
from .models.auth import User
|
|
|
|
# Initialize singletons lazily to avoid import-time DB side effects during test collection.
|
|
# Use absolute path relative to this file to ensure plugins are found regardless of CWD
|
|
project_root = Path(__file__).parent.parent.parent
|
|
config_path = project_root / "config.json"
|
|
|
|
config_manager: Optional[ConfigManager] = None
|
|
plugin_loader: Optional[PluginLoader] = None
|
|
task_manager: Optional[TaskManager] = None
|
|
scheduler_service: Optional[SchedulerService] = None
|
|
resource_service: Optional[ResourceService] = None
|
|
|
|
|
|
# #region get_config_manager [C:1] [TYPE Function]
|
|
# @BRIEF Dependency injector for ConfigManager.
|
|
# @PRE: Global config_manager must be initialized.
|
|
# @POST: Returns shared ConfigManager instance.
|
|
def get_config_manager() -> ConfigManager:
|
|
"""Dependency injector for ConfigManager."""
|
|
global config_manager
|
|
if config_manager is None:
|
|
init_db()
|
|
config_manager = ConfigManager(config_path=str(config_path))
|
|
return config_manager
|
|
|
|
|
|
# #endregion get_config_manager
|
|
|
|
plugin_dir = Path(__file__).parent / "plugins"
|
|
|
|
# Clean Release Redesign Singletons
|
|
# Note: These use get_db() which is a generator, so we need a way to provide a session.
|
|
# For singletons in dependencies.py, we might need a different approach or
|
|
# initialize them inside the dependency functions.
|
|
|
|
|
|
# #region get_plugin_loader [C:1] [TYPE Function]
|
|
# @BRIEF Dependency injector for PluginLoader.
|
|
# @PRE: Global plugin_loader must be initialized.
|
|
# @POST: Returns shared PluginLoader instance.
|
|
def get_plugin_loader() -> PluginLoader:
|
|
"""Dependency injector for PluginLoader."""
|
|
global plugin_loader
|
|
if plugin_loader is None:
|
|
plugin_loader = PluginLoader(plugin_dir=str(plugin_dir))
|
|
logger.info(f"PluginLoader initialized with directory: {plugin_dir}")
|
|
logger.info(
|
|
f"Available plugins: {[config.name for config in plugin_loader.get_all_plugin_configs()]}"
|
|
)
|
|
return plugin_loader
|
|
|
|
|
|
# #endregion get_plugin_loader
|
|
|
|
|
|
# #region get_task_manager [C:1] [TYPE Function]
|
|
# @BRIEF Dependency injector for TaskManager.
|
|
# @PRE: Global task_manager must be initialized.
|
|
# @POST: Returns shared TaskManager instance.
|
|
def get_task_manager() -> TaskManager:
|
|
"""Dependency injector for TaskManager."""
|
|
global task_manager
|
|
if task_manager is None:
|
|
task_manager = TaskManager(get_plugin_loader())
|
|
logger.info("TaskManager initialized")
|
|
return task_manager
|
|
|
|
|
|
# #endregion get_task_manager
|
|
|
|
|
|
# #region get_scheduler_service [C:1] [TYPE Function]
|
|
# @BRIEF Dependency injector for SchedulerService.
|
|
# @PRE: Global scheduler_service must be initialized.
|
|
# @POST: Returns shared SchedulerService instance.
|
|
def get_scheduler_service() -> SchedulerService:
|
|
"""Dependency injector for SchedulerService."""
|
|
global scheduler_service
|
|
if scheduler_service is None:
|
|
scheduler_service = SchedulerService(get_task_manager(), get_config_manager())
|
|
logger.info("SchedulerService initialized")
|
|
return scheduler_service
|
|
|
|
|
|
# #endregion get_scheduler_service
|
|
|
|
|
|
# #region get_resource_service [C:1] [TYPE Function]
|
|
# @BRIEF Dependency injector for ResourceService.
|
|
# @PRE: Global resource_service must be initialized.
|
|
# @POST: Returns shared ResourceService instance.
|
|
def get_resource_service() -> ResourceService:
|
|
"""Dependency injector for ResourceService."""
|
|
global resource_service
|
|
if resource_service is None:
|
|
resource_service = ResourceService()
|
|
logger.info("ResourceService initialized")
|
|
return resource_service
|
|
|
|
|
|
# #endregion get_resource_service
|
|
|
|
|
|
# #region get_mapping_service [C:1] [TYPE Function]
|
|
# @BRIEF Dependency injector for MappingService.
|
|
# @PRE: Global config_manager must be initialized.
|
|
# @POST: Returns new MappingService instance.
|
|
def get_mapping_service() -> MappingService:
|
|
"""Dependency injector for MappingService."""
|
|
return MappingService(get_config_manager())
|
|
|
|
|
|
# #endregion get_mapping_service
|
|
|
|
|
|
_clean_release_repository = CleanReleaseRepository()
|
|
|
|
|
|
# #region get_clean_release_repository [C:1] [TYPE Function]
|
|
# @BRIEF Legacy compatibility shim for CleanReleaseRepository.
|
|
# @POST: Returns a shared CleanReleaseRepository instance.
|
|
def get_clean_release_repository() -> CleanReleaseRepository:
|
|
"""Legacy compatibility shim for CleanReleaseRepository."""
|
|
return _clean_release_repository
|
|
|
|
|
|
# #endregion get_clean_release_repository
|
|
|
|
|
|
# #region get_clean_release_facade [C:1] [TYPE Function]
|
|
# @BRIEF Dependency injector for CleanReleaseFacade.
|
|
# @POST: Returns a facade instance with a fresh DB session.
|
|
def get_clean_release_facade(db=Depends(get_db)) -> CleanReleaseFacade:
|
|
candidate_repo = CandidateRepository(db)
|
|
artifact_repo = ArtifactRepository(db)
|
|
manifest_repo = ManifestRepository(db)
|
|
policy_repo = PolicyRepository(db)
|
|
compliance_repo = ComplianceRepository(db)
|
|
report_repo = ReportRepository(db)
|
|
approval_repo = ApprovalRepository(db)
|
|
publication_repo = PublicationRepository(db)
|
|
audit_repo = AuditRepository(db)
|
|
|
|
return CleanReleaseFacade(
|
|
candidate_repo=candidate_repo,
|
|
artifact_repo=artifact_repo,
|
|
manifest_repo=manifest_repo,
|
|
policy_repo=policy_repo,
|
|
compliance_repo=compliance_repo,
|
|
report_repo=report_repo,
|
|
approval_repo=approval_repo,
|
|
publication_repo=publication_repo,
|
|
audit_repo=audit_repo,
|
|
config_manager=get_config_manager(),
|
|
)
|
|
|
|
|
|
# #endregion get_clean_release_facade
|
|
|
|
# #region oauth2_scheme [C:1] [TYPE Variable]
|
|
# @RELATION DEPENDS_ON -> OAuth2PasswordBearer
|
|
# @BRIEF OAuth2 password bearer scheme for token extraction.
|
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
|
|
# #endregion oauth2_scheme
|
|
|
|
|
|
# #region get_current_user [C:3] [TYPE Function]
|
|
# @RELATION CALLS -> AuthRepository
|
|
# @BRIEF Dependency for retrieving currently authenticated user from a JWT.
|
|
# @PRE: JWT token provided in Authorization header.
|
|
# @POST: Returns User object if token is valid.
|
|
def get_current_user(token: str = Depends(oauth2_scheme), db=Depends(get_auth_db)):
|
|
credentials_exception = HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Could not validate credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
try:
|
|
payload = decode_token(token)
|
|
username_value = payload.get("sub")
|
|
if not isinstance(username_value, str) or not username_value:
|
|
raise credentials_exception
|
|
username = username_value
|
|
except JWTError:
|
|
raise credentials_exception
|
|
|
|
repo = AuthRepository(db)
|
|
user = repo.get_user_by_username(username)
|
|
if user is None:
|
|
raise credentials_exception
|
|
return user
|
|
|
|
|
|
# #endregion get_current_user
|
|
|
|
|
|
# #region has_permission [C:3] [TYPE Function]
|
|
# @RELATION CALLS -> AuthRepository
|
|
# @BRIEF Dependency for checking if the current user has a specific permission.
|
|
# @PRE: User is authenticated.
|
|
# @POST: Returns True if user has permission.
|
|
def has_permission(resource: str, action: str):
|
|
def permission_checker(current_user: User = Depends(get_current_user)):
|
|
# Union of all permissions across all roles
|
|
for role in current_user.roles:
|
|
for perm in role.permissions:
|
|
if perm.resource == resource and perm.action == action:
|
|
return current_user
|
|
|
|
# Special case for Admin role (full access)
|
|
if any(role.name == "Admin" for role in current_user.roles):
|
|
return current_user
|
|
|
|
from .core.auth.logger import log_security_event
|
|
|
|
log_security_event(
|
|
"PERMISSION_DENIED",
|
|
str(getattr(current_user, "username", "unknown")),
|
|
{"resource": resource, "action": action},
|
|
)
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail=f"Permission denied for {resource}:{action}",
|
|
)
|
|
|
|
return permission_checker
|
|
|
|
|
|
# #endregion has_permission
|
|
|
|
# #endregion AppDependencies
|