
    $jd(                         d Z ddlZddlZddlmZmZ ddlmZmZ ddl	m
Z
  ej        e          Zddededz  defd	Z G d
 de          Z G d de          Z G d de          Z G d de          ZdS )uM  
Task backend for distributed task processing.

This provides an abstraction for task storage and execution:
- BrokerTaskBackend: Uses PostgreSQL as broker (production API servers)
- WorkerTaskBackend: No-op submit_task (production workers — child tasks are polled)
- SyncTaskBackend: Executes tasks immediately (testing/embedded)
    N)ABCabstractmethod)	AwaitableCallable)Anytableschemareturnc                 &    ddl m}  || |          S )z;Get fully-qualified table name with optional schema prefix.   )fq_table_explicit)r	   r   )r   r	   r   s      i/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/engine/task_backend.pyfq_tabler      s'    ))))))UF+++    c                       e Zd ZdZd Zdeeeef         ge	d         f         fdZ
ed             Zedeeef         fd            Zed	             Zdeeef         fd
ZdS )TaskBackenda  
    Abstract base class for task execution backends.

    Implementations must:
    1. Store/publish task events (as serializable dicts)
    2. Execute tasks through a provided executor callback (optional)

    The backend treats tasks as pure dictionaries that can be serialized
    and stored in the database. The executor (typically MemoryEngine.execute_task)
    receives the dict and routes it to the appropriate handler.
    c                 "    d| _         d| _        dS )zInitialize the task backend.NF)	_executor_initializedselfs    r   __init__zTaskBackend.__init__'   s    MQ!r   executorNc                     || _         dS )z
        Set the executor callback for processing tasks.

        Args:
            executor: Async function that takes a task dict and executes it
        N)r   )r   r   s     r   set_executorzTaskBackend.set_executor,   s     "r   c                 
   K   dS )zE
        Initialize the backend (e.g., connect to database).
        N r   s    r   
initializezTaskBackend.initialize5         
 	r   	task_dictc                 
   K   dS )z
        Submit a task for execution.

        Args:
            task_dict: Task as a dictionary (must be serializable)
        Nr   r   r    s     r   submit_taskzTaskBackend.submit_task<   s       	r   c                 
   K   dS )z2
        Shutdown the backend gracefully.
        Nr   r   s    r   shutdownzTaskBackend.shutdownF   r   r   c                    K   | j         5|                    dd          }t                              d|            dS |                      |           d{V  dS )z
        Execute a task through the registered executor.

        Args:
            task_dict: Task dictionary to execute

        Raises:
            Exception: Re-raised from executor on failure.
        Ntypeunknownz&No executor registered, skipping task )r   getloggerwarningr   r    	task_types      r   _execute_taskzTaskBackend._execute_taskM   sm       >!!fi88INNOIOOPPPFnnY'''''''''''r   )__name__
__module____qualname____doc__r   r   dictstrr   r   r   r   r   r#   r%   r.   r   r   r   r   r      s        
 
" " "
"XtCH~.>	$.O%P " " " "   ^ 4S>    ^   ^(T#s(^ ( ( ( ( ( (r   r   c                   :    e Zd ZdZd Zdeeef         fdZd Z	dS )SyncTaskBackendz
    Synchronous task backend that executes tasks immediately.

    This is useful for tests and embedded/CLI usage where we don't want
    background workers. Tasks are executed inline rather than being queued.
    c                 L   K   d| _         t                              d           dS )No-op for sync backend.TzSyncTaskBackend initializedNr   r*   debugr   s    r   r   zSyncTaskBackend.initializeg   s'       233333r   r    c                    K   | j         s|                                  d{V  |                     |           d{V  dS )z
        Execute the task immediately (synchronously).

        Args:
            task_dict: Task dictionary to execute
        N)r   r   r.   r"   s     r   r#   zSyncTaskBackend.submit_taskl   sa         	$//#########  +++++++++++r   c                 L   K   d| _         t                              d           dS )r8   FzSyncTaskBackend shutdownNr9   r   s    r   r%   zSyncTaskBackend.shutdownx   s'      !/00000r   N
r/   r0   r1   r2   r   r3   r4   r   r#   r%   r   r   r   r6   r6   _   s_         4 4 4

,4S> 
, 
, 
, 
,1 1 1 1 1r   r6   c                   :    e Zd ZdZd Zdeeef         fdZd Z	dS )WorkerTaskBackendu;  
    Task backend for worker processes.

    Workers execute tasks directly via the poller (claim → execute), so they
    don't need submit_task to run anything.  When engine code running *inside*
    a worker-executed task calls submit_task (e.g. retain triggers consolidation),
    the async-operation row has already been persisted (with task_payload) by
    _submit_async_operation — so submit_task is a no-op.  The new task will be
    picked up by a worker on the next poll cycle instead of being executed inline,
    which avoids blocking the parent task.
    c                 L   K   d| _         t                              d           d S )NTzWorkerTaskBackend initializedr9   r   s    r   r   zWorkerTaskBackend.initialize   s'       455555r   r    c                 r   K   |                     dd          }t                              d| d           dS )zJNo-op: the row already exists in async_operations; a worker will claim it.r'   r(   z)WorkerTaskBackend: submit_task no-op for z (will be picked up by poller)N)r)   r*   r:   r,   s      r   r#   zWorkerTaskBackend.submit_task   s=      MM&)44	jjjjkkkkkr   c                 L   K   d| _         t                              d           d S )NFzWorkerTaskBackend shutdownr9   r   s    r   r%   zWorkerTaskBackend.shutdown   s'      !122222r   Nr=   r   r   r   r?   r?   ~   sd        
 
6 6 6l4S> l l l l
3 3 3 3 3r   r?   c            
            e Zd ZdZ	 	 ddeg ef         dedz  deg edz  f         dz  f fdZd Zde	eef         fd	Z
d
 ZddefdZ xZS )BrokerTaskBackenda6  
    Task backend using PostgreSQL as broker.

    submit_task() stores task_payload in async_operations table.
    Actual polling and execution is handled separately by WorkerPoller.

    This backend is used by the API to store tasks. Workers poll
    the database separately to claim and execute tasks.
    Npool_getterr	   schema_getterc                 r    t                                                       || _        || _        || _        dS )a  
        Initialize the broker task backend.

        Args:
            pool_getter: Callable that returns the asyncpg connection pool
            schema: Database schema for multi-tenant support (optional, static)
            schema_getter: Callable that returns current schema dynamically (optional).
                          If set, takes precedence over static schema for submit_task.
        N)superr   _pool_getter_schema_schema_getter)r   rE   r	   rF   	__class__s       r   r   zBrokerTaskBackend.__init__   s8     	'+r   c                 L   K   d| _         t                              d           dS )zInitialize the backend.TzBrokerTaskBackend initializedNr   r*   infor   s    r   r   zBrokerTaskBackend.initialize   s'       344444r   r    c                   K   | j         s|                                  d{V  |                                 }|                    d          }|                    dd          }|                    d          }ddlm fd}t          j        ||	          }| j        r|                                 n| j        }t          d
|          }	ddl
m}
 |rv |
|          4 d{V }|                    d|	 d||           d{V  ddd          d{V  n# 1 d{V swxY w Y   t                              d| d           dS ddl}|                                } |
|          4 d{V }|                    d|	 d||||           d{V  ddd          d{V  n# 1 d{V swxY w Y   t                              d| d|            dS )a/  
        Store task payload in async_operations table.

        The task_dict should contain an 'operation_id' if updating an existing
        operation record, otherwise a new operation will be created.

        Args:
            task_dict: Task dictionary to store (must be JSON serializable)
        Noperation_idr'   r(   bank_idr   )datetimec                     t          |           r|                                 S t          dt          |           j         d          )NzObject of type z is not JSON serializable)
isinstance	isoformat	TypeErrorr'   r/   )objrS   s    r   datetime_encoderz7BrokerTaskBackend.submit_task.<locals>.datetime_encoder   sF    #x(( '}}&[d3ii.@[[[\\\r   )defaultasync_operationsr   acquire_with_retryz
                    UPDATE z
                    SET task_payload = $1::jsonb, updated_at = now()
                    WHERE operation_id = $2 AND task_payload IS NULL
                    z!submit_task UPDATE for operation z (no-op if payload already set)z!
                    INSERT INTO z (operation_id, bank_id, operation_type, status, task_payload)
                    VALUES ($1, $2, $3, 'pending', $4::jsonb)
                    zCreated new operation z for task type )r   r   rI   r)   rS   jsondumpsrK   rJ   r   db_utilsr]   executer*   r:   uuiduuid4)r   r    poolrQ   r-   rR   rY   payload_jsonr	   r   r]   connrb   new_idrS   s                 @r   r#   zBrokerTaskBackend.submit_task   s        	$//#########  "" }}^44MM&)44	--	** 	&%%%%%	] 	] 	] 	] 	]
 z)5EFFF*.*=O$$&&&4<+V44000000 "	V *)$// 	 	 	 	 	 	 	4ll!  
 !         	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 LLj\jjjkkkkk KKKZZ\\F))$// 
 
 
 
 
 
 
4ll!&    	 	 	 	 	 	 	 	 	
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 LLT&TTTTUUUUUs$   %"D
D#&D#3$F))
F36F3c                 L   K   d| _         t                              d           dS )zShutdown the backend.FzBrokerTaskBackend shutdownNrN   r   s    r   r%   zBrokerTaskBackend.shutdown  s'      !011111r         ^@timeoutc                   K   ddl }ddlm} |                                 }| j        r|                                 n| j        }t          d|          }|                                                                }|                                                                |z
  |k     r ||          4 d{V }|	                    d| d           d{V }	ddd          d{V  n# 1 d{V swxY w Y   |	dk    rdS |
                    d           d{V  |                                                                |z
  |k     t                              d	| d
           dS )aK  
        Wait for pending tasks to be processed.

        In the broker model, this polls the database to check if tasks
        for this process have been completed. This is useful in tests
        when worker_enabled=True (API processes its own tasks).

        Args:
            timeout: Maximum time to wait in seconds
        r   Nr   r\   r[   z*
                    SELECT COUNT(*) FROM z_
                    WHERE status = 'pending' AND task_payload IS NOT NULL
                    g      ?z(Timeout waiting for pending tasks after s)asyncior`   r]   rI   rK   rJ   r   get_event_looptimefetchvalsleepr*   r+   )
r   rj   rm   r]   rd   r	   r   
start_timerf   counts
             r   wait_for_pending_tasksz(BrokerTaskBackend.wait_for_pending_tasks  s2      	000000  ""*.*=O$$&&&4<+V44++--2244
$$&&++--
:WDD))$//       4"mm*/                                    zz--$$$$$$$$$ $$&&++--
:WDD 	L'LLLMMMMMs   7 C))
C36C3)NN)ri   )r/   r0   r1   r2   r   r   r4   r   r   r3   r#   r%   floatrt   __classcell__)rL   s   @r   rD   rD      s         "9=	, ,b#g&, d
,  C$J/$6	, , , , , ,(5 5 5
CV4S> CV CV CV CVJ2 2 2
#N #NE #N #N #N #N #N #N #N #Nr   rD   )N)r2   r^   loggingabcr   r   collections.abcr   r   typingr   	getLoggerr/   r*   r4   r   r   r6   r?   rD   r   r   r   <module>r|      sx      # # # # # # # # / / / / / / / /      		8	$	$, ,C ,t ,s , , , ,B( B( B( B( B(# B( B( B(J1 1 1 1 1k 1 1 1>3 3 3 3 3 3 3 36QN QN QN QN QN QN QN QN QN QNr   