semantic update
This commit is contained in:
@@ -6,9 +6,11 @@
|
||||
# @TIER: CRITICAL
|
||||
# @INVARIANT: Each TaskContext is bound to a single task execution.
|
||||
|
||||
# [SECTION: IMPORTS]
|
||||
# [SECTION: IMPORTS]
|
||||
from typing import Dict, Any, Callable
|
||||
from .task_logger import TaskLogger
|
||||
from ..logger import belief_scope
|
||||
# [/SECTION]
|
||||
|
||||
# [DEF:TaskContext:Class]
|
||||
@@ -44,13 +46,14 @@ class TaskContext:
|
||||
params: Dict[str, Any],
|
||||
default_source: str = "plugin"
|
||||
):
|
||||
self._task_id = task_id
|
||||
self._params = params
|
||||
self._logger = TaskLogger(
|
||||
task_id=task_id,
|
||||
add_log_fn=add_log_fn,
|
||||
source=default_source
|
||||
)
|
||||
with belief_scope("__init__"):
|
||||
self._task_id = task_id
|
||||
self._params = params
|
||||
self._logger = TaskLogger(
|
||||
task_id=task_id,
|
||||
add_log_fn=add_log_fn,
|
||||
source=default_source
|
||||
)
|
||||
# [/DEF:__init__:Function]
|
||||
|
||||
# [DEF:task_id:Function]
|
||||
@@ -60,7 +63,8 @@ class TaskContext:
|
||||
# @RETURN: str - The task ID.
|
||||
@property
|
||||
def task_id(self) -> str:
|
||||
return self._task_id
|
||||
with belief_scope("task_id"):
|
||||
return self._task_id
|
||||
# [/DEF:task_id:Function]
|
||||
|
||||
# [DEF:logger:Function]
|
||||
@@ -70,7 +74,8 @@ class TaskContext:
|
||||
# @RETURN: TaskLogger - The logger instance.
|
||||
@property
|
||||
def logger(self) -> TaskLogger:
|
||||
return self._logger
|
||||
with belief_scope("logger"):
|
||||
return self._logger
|
||||
# [/DEF:logger:Function]
|
||||
|
||||
# [DEF:params:Function]
|
||||
@@ -80,7 +85,8 @@ class TaskContext:
|
||||
# @RETURN: Dict[str, Any] - The task parameters.
|
||||
@property
|
||||
def params(self) -> Dict[str, Any]:
|
||||
return self._params
|
||||
with belief_scope("params"):
|
||||
return self._params
|
||||
# [/DEF:params:Function]
|
||||
|
||||
# [DEF:get_param:Function]
|
||||
@@ -91,7 +97,8 @@ class TaskContext:
|
||||
# @PARAM: default (Any) - Default value if key not found.
|
||||
# @RETURN: Any - Parameter value or default.
|
||||
def get_param(self, key: str, default: Any = None) -> Any:
|
||||
return self._params.get(key, default)
|
||||
with belief_scope("get_param"):
|
||||
return self._params.get(key, default)
|
||||
# [/DEF:get_param:Function]
|
||||
|
||||
# [DEF:create_sub_context:Function]
|
||||
@@ -102,12 +109,13 @@ class TaskContext:
|
||||
# @RETURN: TaskContext - New context with different source.
|
||||
def create_sub_context(self, source: str) -> "TaskContext":
|
||||
"""Create a sub-context with a different default source for logging."""
|
||||
return TaskContext(
|
||||
task_id=self._task_id,
|
||||
add_log_fn=self._logger._add_log,
|
||||
params=self._params,
|
||||
default_source=source
|
||||
)
|
||||
with belief_scope("create_sub_context"):
|
||||
return TaskContext(
|
||||
task_id=self._task_id,
|
||||
add_log_fn=self._logger._add_log,
|
||||
params=self._params,
|
||||
default_source=source
|
||||
)
|
||||
# [/DEF:create_sub_context:Function]
|
||||
|
||||
# [/DEF:TaskContext:Class]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# [DEF:TaskManagerModule:Module]
|
||||
# @TIER: CRITICAL
|
||||
# @SEMANTICS: task, manager, lifecycle, execution, state
|
||||
# @PURPOSE: Manages the lifecycle of tasks, including their creation, execution, and state tracking. It uses a thread pool to run plugins asynchronously.
|
||||
# @LAYER: Core
|
||||
@@ -74,9 +75,10 @@ class TaskManager:
|
||||
# @POST: Logs are batch-written to database every LOG_FLUSH_INTERVAL seconds.
|
||||
def _flusher_loop(self):
|
||||
"""Background thread that flushes log buffer to database."""
|
||||
while not self._flusher_stop_event.is_set():
|
||||
self._flush_logs()
|
||||
self._flusher_stop_event.wait(self.LOG_FLUSH_INTERVAL)
|
||||
with belief_scope("_flusher_loop"):
|
||||
while not self._flusher_stop_event.is_set():
|
||||
self._flush_logs()
|
||||
self._flusher_stop_event.wait(self.LOG_FLUSH_INTERVAL)
|
||||
# [/DEF:_flusher_loop:Function]
|
||||
|
||||
# [DEF:_flush_logs:Function]
|
||||
@@ -85,23 +87,24 @@ class TaskManager:
|
||||
# @POST: All buffered logs are written to task_logs table.
|
||||
def _flush_logs(self):
|
||||
"""Flush all buffered logs to the database."""
|
||||
with self._log_buffer_lock:
|
||||
task_ids = list(self._log_buffer.keys())
|
||||
|
||||
for task_id in task_ids:
|
||||
with belief_scope("_flush_logs"):
|
||||
with self._log_buffer_lock:
|
||||
logs = self._log_buffer.pop(task_id, [])
|
||||
task_ids = list(self._log_buffer.keys())
|
||||
|
||||
if logs:
|
||||
try:
|
||||
self.log_persistence_service.add_logs(task_id, logs)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to flush logs for task {task_id}: {e}")
|
||||
# Re-add logs to buffer on failure
|
||||
with self._log_buffer_lock:
|
||||
if task_id not in self._log_buffer:
|
||||
self._log_buffer[task_id] = []
|
||||
self._log_buffer[task_id].extend(logs)
|
||||
for task_id in task_ids:
|
||||
with self._log_buffer_lock:
|
||||
logs = self._log_buffer.pop(task_id, [])
|
||||
|
||||
if logs:
|
||||
try:
|
||||
self.log_persistence_service.add_logs(task_id, logs)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to flush logs for task {task_id}: {e}")
|
||||
# Re-add logs to buffer on failure
|
||||
with self._log_buffer_lock:
|
||||
if task_id not in self._log_buffer:
|
||||
self._log_buffer[task_id] = []
|
||||
self._log_buffer[task_id].extend(logs)
|
||||
# [/DEF:_flush_logs:Function]
|
||||
|
||||
# [DEF:_flush_task_logs:Function]
|
||||
@@ -111,14 +114,15 @@ class TaskManager:
|
||||
# @PARAM: task_id (str) - The task ID.
|
||||
def _flush_task_logs(self, task_id: str):
|
||||
"""Flush logs for a specific task immediately."""
|
||||
with self._log_buffer_lock:
|
||||
logs = self._log_buffer.pop(task_id, [])
|
||||
|
||||
if logs:
|
||||
try:
|
||||
self.log_persistence_service.add_logs(task_id, logs)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to flush logs for task {task_id}: {e}")
|
||||
with belief_scope("_flush_task_logs"):
|
||||
with self._log_buffer_lock:
|
||||
logs = self._log_buffer.pop(task_id, [])
|
||||
|
||||
if logs:
|
||||
try:
|
||||
self.log_persistence_service.add_logs(task_id, logs)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to flush logs for task {task_id}: {e}")
|
||||
# [/DEF:_flush_task_logs:Function]
|
||||
|
||||
# [DEF:create_task:Function]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# [DEF:TaskPersistenceModule:Module]
|
||||
# @TIER: CRITICAL
|
||||
# @SEMANTICS: persistence, sqlite, sqlalchemy, task, storage
|
||||
# @PURPOSE: Handles the persistence of tasks using SQLAlchemy and the tasks.db database.
|
||||
# @LAYER: Core
|
||||
@@ -19,42 +20,65 @@ from ..logger import logger, belief_scope
|
||||
# [/SECTION]
|
||||
|
||||
# [DEF:TaskPersistenceService:Class]
|
||||
# @TIER: CRITICAL
|
||||
# @SEMANTICS: persistence, service, database, sqlalchemy
|
||||
# @PURPOSE: Provides methods to save and load tasks from the tasks.db database using SQLAlchemy.
|
||||
# @INVARIANT: Persistence must handle potentially missing task fields natively.
|
||||
class TaskPersistenceService:
|
||||
# [DEF:_json_load_if_needed:Function]
|
||||
# @PURPOSE: Safely load JSON strings from DB if necessary
|
||||
# @PRE: value is an arbitrary database value
|
||||
# @POST: Returns parsed JSON object, list, string, or primitive
|
||||
@staticmethod
|
||||
def _json_load_if_needed(value):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (dict, list)):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
stripped = value.strip()
|
||||
if stripped == "" or stripped.lower() == "null":
|
||||
with belief_scope("TaskPersistenceService._json_load_if_needed"):
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return json.loads(stripped)
|
||||
except json.JSONDecodeError:
|
||||
if isinstance(value, (dict, list)):
|
||||
return value
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
stripped = value.strip()
|
||||
if stripped == "" or stripped.lower() == "null":
|
||||
return None
|
||||
try:
|
||||
return json.loads(stripped)
|
||||
except json.JSONDecodeError:
|
||||
return value
|
||||
return value
|
||||
# [/DEF:_json_load_if_needed:Function]
|
||||
|
||||
# [DEF:_parse_datetime:Function]
|
||||
# @PURPOSE: Safely parse a datetime string from the database
|
||||
# @PRE: value is an ISO string or datetime object
|
||||
# @POST: Returns datetime object or None
|
||||
@staticmethod
|
||||
def _parse_datetime(value):
|
||||
if value is None or isinstance(value, datetime):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return datetime.fromisoformat(value)
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_environment_id(session: Session, env_id: Optional[str]) -> Optional[str]:
|
||||
if not env_id:
|
||||
with belief_scope("TaskPersistenceService._parse_datetime"):
|
||||
if value is None or isinstance(value, datetime):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return datetime.fromisoformat(value)
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
exists = session.query(Environment.id).filter(Environment.id == env_id).first()
|
||||
return env_id if exists else None
|
||||
# [/DEF:_parse_datetime:Function]
|
||||
|
||||
# [DEF:_resolve_environment_id:Function]
|
||||
# @TIER: STANDARD
|
||||
# @PURPOSE: Resolve environment id based on provided value or fallback to default
|
||||
# @PRE: Session is active
|
||||
# @POST: Environment ID is returned
|
||||
@staticmethod
|
||||
def _resolve_environment_id(session: Session, env_id: Optional[str]) -> str:
|
||||
with belief_scope("_resolve_environment_id"):
|
||||
if env_id:
|
||||
return env_id
|
||||
repo_env = session.query(Environment).filter_by(name="default").first()
|
||||
if repo_env:
|
||||
return str(repo_env.id)
|
||||
return "default"
|
||||
# [/DEF:_resolve_environment_id:Function]
|
||||
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the persistence service.
|
||||
@@ -90,13 +114,14 @@ class TaskPersistenceService:
|
||||
|
||||
# Ensure params and result are JSON serializable
|
||||
def json_serializable(obj):
|
||||
if isinstance(obj, dict):
|
||||
return {k: json_serializable(v) for k, v in obj.items()}
|
||||
elif isinstance(obj, list):
|
||||
return [json_serializable(v) for v in obj]
|
||||
elif isinstance(obj, datetime):
|
||||
return obj.isoformat()
|
||||
return obj
|
||||
with belief_scope("TaskPersistenceService.json_serializable"):
|
||||
if isinstance(obj, dict):
|
||||
return {k: json_serializable(v) for k, v in obj.items()}
|
||||
elif isinstance(obj, list):
|
||||
return [json_serializable(v) for v in obj]
|
||||
elif isinstance(obj, datetime):
|
||||
return obj.isoformat()
|
||||
return obj
|
||||
|
||||
record.params = json_serializable(task.params)
|
||||
record.result = json_serializable(task.result)
|
||||
@@ -227,9 +252,11 @@ class TaskLogPersistenceService:
|
||||
"""
|
||||
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initialize the log persistence service.
|
||||
# @POST: Service is ready.
|
||||
def __init__(self):
|
||||
# @TIER: STANDARD
|
||||
# @PURPOSE: Initializes the TaskLogPersistenceService
|
||||
# @PRE: config is provided or defaults are used
|
||||
# @POST: Service is ready for log persistence
|
||||
def __init__(self, config=None):
|
||||
pass
|
||||
# [/DEF:__init__:Function]
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from typing import Dict, Any, Optional, Callable
|
||||
# @PURPOSE: A wrapper around TaskManager._add_log that carries task_id and source context.
|
||||
# @TIER: CRITICAL
|
||||
# @INVARIANT: All log calls include the task_id and source.
|
||||
# @TEST_DATA: task_logger -> {"task_id": "test_123", "source": "test_plugin"}
|
||||
# @UX_STATE: Idle -> Logging -> (system records log)
|
||||
class TaskLogger:
|
||||
"""
|
||||
@@ -71,6 +72,7 @@ class TaskLogger:
|
||||
# @PARAM: message (str) - Log message.
|
||||
# @PARAM: source (Optional[str]) - Override source for this log entry.
|
||||
# @PARAM: metadata (Optional[Dict]) - Additional structured data.
|
||||
# @UX_STATE: Logging -> (writing internal log)
|
||||
def _log(
|
||||
self,
|
||||
level: str,
|
||||
@@ -90,6 +92,8 @@ class TaskLogger:
|
||||
|
||||
# [DEF:debug:Function]
|
||||
# @PURPOSE: Log a DEBUG level message.
|
||||
# @PRE: message is a string.
|
||||
# @POST: Log entry added via internally with DEBUG level.
|
||||
# @PARAM: message (str) - Log message.
|
||||
# @PARAM: source (Optional[str]) - Override source.
|
||||
# @PARAM: metadata (Optional[Dict]) - Additional data.
|
||||
@@ -104,6 +108,8 @@ class TaskLogger:
|
||||
|
||||
# [DEF:info:Function]
|
||||
# @PURPOSE: Log an INFO level message.
|
||||
# @PRE: message is a string.
|
||||
# @POST: Log entry added internally with INFO level.
|
||||
# @PARAM: message (str) - Log message.
|
||||
# @PARAM: source (Optional[str]) - Override source.
|
||||
# @PARAM: metadata (Optional[Dict]) - Additional data.
|
||||
@@ -118,6 +124,8 @@ class TaskLogger:
|
||||
|
||||
# [DEF:warning:Function]
|
||||
# @PURPOSE: Log a WARNING level message.
|
||||
# @PRE: message is a string.
|
||||
# @POST: Log entry added internally with WARNING level.
|
||||
# @PARAM: message (str) - Log message.
|
||||
# @PARAM: source (Optional[str]) - Override source.
|
||||
# @PARAM: metadata (Optional[Dict]) - Additional data.
|
||||
@@ -132,6 +140,8 @@ class TaskLogger:
|
||||
|
||||
# [DEF:error:Function]
|
||||
# @PURPOSE: Log an ERROR level message.
|
||||
# @PRE: message is a string.
|
||||
# @POST: Log entry added internally with ERROR level.
|
||||
# @PARAM: message (str) - Log message.
|
||||
# @PARAM: source (Optional[str]) - Override source.
|
||||
# @PARAM: metadata (Optional[Dict]) - Additional data.
|
||||
|
||||
Reference in New Issue
Block a user