
    js                     j   d Z ddlZddlZddlZddlZddlZddlZddlZddlm	Z	m
Z
 ddlmZmZmZmZ ddlmZ ddlmZ ddlmZ d	Z ej.                  e      Ze	 G d
 de
             Ze	 G d de
             Z G d de      Zdej:                  defdZ G d d      Z ejB                  jD                  ejB                  ejF                  jD                  ejF                  ejH                  jD                  ejH                  ejJ                  jD                  ejJ                  ejL                  jD                  ejL                  ejN                  jD                  ejN                  ejP                  jD                  ejP                  ejR                  jD                  ejR                  ejT                  jD                  ejT                  ejV                  jD                  ejV                  ejX                  jD                  ejX                  ejZ                  jD                  ejZ                  iZ.dedej:                  fdZ/y)u~  On-disk SQLite message store used by the replay flow to persist messages
that failed to reach the Opik server. DBManager exposes helpers for the full
message lifecycle: register_message(s), fetch_failed_messages_batched, and
replay_failed_messages (which accepts a ReplayCallback, typically Streamer.put,
to re-inject messages). The manager tracks three states — undefined, initialized, closed,
and error — and if the underlying database becomes unavailable, it marks itself
as failed (error) and logs that resiliency features are disabled. The standalone helper
db_message_to_message raises ValueError for unsupported message types.
    N)uniqueIntEnum)List
NamedTupleOptionalIterator)messages)message_serialization   )ReplayCallbackzopik_messages.dbc                       e Zd ZdZdZdZdZy)MessageStatusa#  
    Represents the status of a message.

    This enumeration defines the potential statuses that a message can
    have in a messaging system. Used for categorizing messages based on
    their lifecycle stage or outcome.

    Attributes:
        registered: Represents a message that has been successfully
            registered but not yet processed or delivered.
        delivered : Represents a message that has been successfully
            delivered to its recipient.
        failed: Represents a message for which delivery has failed.
    r         N)__name__
__module____qualname____doc__
registered	deliveredfailed     /Users/manta/Documents/Projects/TheRoad-I1/backend/.venv/lib/python3.12/site-packages/opik/message_processing/replay/db_manager.pyr   r       s     JIFr   r   c                        e Zd ZdZdZdZdZdZy)DBManagerStatuszRepresents various status values for a manager.

    Defines a set of distinct states that a manager can occupy during its lifecycle.
    This can be used to track and manage the status of a manager in an application.
    r   r   r      N)r   r   r   r   	undefinedinitializedclosederrorr   r   r   r   r   6   s     IKFEr   r   c                   :    e Zd ZU dZeed<   eed<   eed<   eed<   y)	DBMessagea  
    Represents a database message entity.

    This class is used to encapsulate information about messages stored in
    or retrieved from a database. It provides a structured format for handling
    message data, including its unique identifier, type, JSON content, and
    current status.

    Attributes:
        id: The unique identifier of the message.
        type: The type/category of the message.
        json: The JSON-encoded content of the message.
        status: The current status of the message.
    idtypejsonstatusN)r   r   r   r   int__annotations__strr   r   r   r   r#   r#   D   s     	G
I
Ir   r#   messagereturnc                 Z    | j                   t        d      t        j                  |       S )NzMessage ID expected)
message_id
ValueErrorr
   serialize_message)r+   s    r   _preprocess_registered_messager1   Z   s+    !.// 227;;r   c                   *   e Zd ZdZ	 	 	 d"dededee   deej                     dee
j                     ddfd	Zd#d
Zd#dZej                   fdej$                  deddfdZej                   fdeej$                     deddfdZdee   deddfdZdededdfdZdedefdZdedefdZdee   dedefdZdedeej$                     fdZdedee   fdZede fd       Z!ede fd       Z"ede fd       Z#dede$ee      fdZ%defd Z&deddfd!Z'y)$	DBManagerac  
    Manages message storage, batch processing, and database operations within a replay
    system.

    This class provides functionalities to handle message registrations, batch updates,
    and database schema management. It ensures thread-safe operations, cleans up
    temporary files, and handles database connections as part of its lifecycle.

    N
batch_sizebatch_replay_delaydb_fileconn	sync_lockr,   c                    || _         || _        t        j                  | _        t        j                         | _        |.t        j                  j                  | j                  t              }|| _        |!t        j                  | j                  d      }|| _        |t!        j"                         | _        n|| _        t!        j&                         | _        | j+                          y)aX  
        Initializes the Manager class, setting up the database connection, temporary
        directory, and other necessary properties for managing batch operations.

        Args:
            batch_size: The size of batches for processing.
            batch_replay_delay: The delay (in seconds) between replaying batches of messages.
            db_file: Path to the database file. If not provided, a
                temporary file will be created in a temporary directory.
            conn: A pre-existing database connection.
                If not provided, a new connection is created.
        NF)check_same_thread)r4   r5   r   r   r'   tempfilemkdtemptmp_dirospathjoinDEFAULT_DB_FILEr6   sqlite3connectr7   	threadingRLock__lock__Lock_replay_mutex_create_db_schema)selfr4   r5   r6   r7   r8   s         r   __init__zDBManager.__init__l   s    ( %"4%//'')?ggll4<<AG<??4<<5ID	%OO-DM%DM
 '^^- r   c                 p   	 | j                   5  | j                  5  | j                  j                  d       t        j                  | _        d d d        d d d        y # 1 sw Y   xY w# 1 sw Y   y xY w# t        $ r7}d| }| j                  |       t        j                  |d       Y d }~y d }~ww xY w)NaN  CREATE TABLE IF NOT EXISTS messages
                                            (message_id INTEGER NOT NULL PRIMARY KEY,
                                            status INTEGER NOT NULL,
                                            message_type TEXT NOT NULL,
                                            message_json TEXT NOT NULL)z)Database schema creation failed, reason: Texc_info)
rF   r7   executer   r   r'   	Exception_mark_as_db_failedLOGGERwarningrJ   exmsgs      r   rI   zDBManager._create_db_schema   s    	/YYII%%K #2"="=DK  Y   	/=bTBC##C(NN3N..	/sK   A5 A)1AA)A5 A&	"A))A2.A5 2A5 5	B5>-B00B5c                 P   | j                   ry| j                  5  t        j                   | _        	 t        j                  d       | j                  j                          | j                  @	 t        j                  d| j                         t        j                  | j                         ddd       y# t        $ r"}t        j                  d|d       Y d}~{d}~ww xY w# t        $ r-}t        j                  d| j                  |d       Y d}~hd}~ww xY w# 1 sw Y   yxY w)a  
        Closes the current manager, releasing all associated resources.

        This method ensures the proper cleanup of resources such as database connections
        and temporary directories. It performs the operations within a thread-safe context
        by acquiring an internal lock. If the manager is already closed, the method will
        return immediately without performing any actions.
        NzClosing messages DB connectionz*Failed to close messages DB connection: %sTrM   zCleaning temporary data dir: %rz2Failed to clean temporary data dir: %r, reason: %s)r    rF   r   r'   rR   debugr7   closerP   r=   shutilrmtree)rJ   es     r   rY   zDBManager.close   s     ;;]])00DK=>		! ||'	LL!BDLLQMM$,,/ ]  @!d    ! LLL!%	 !  ! ]sX   D/B5 D-?C#5	C >CDC  D#	D,#DDDDD%r+   r'   c                    | j                   st        j                  d       y| j                  5  | j                  rt        j                  d       	 ddd       yt        |      }|j                  ||j                  |f}	 | j                  5  | j                  j                  d|       ddd       ddd       y# 1 sw Y   xY w# t        $ r}| j                  d|         d}~ww xY w# 1 sw Y   yxY w)a  
        Registers a message into the database if the system is initialized and not closed.

        This method processes the given message, converts it into a JSON format, and
        inserts it into the database with the provided or default status. If the system
        is either not initialized or already closed, the message registration will be
        ignored.

        Args:
            message: The message object to be registered. This
                object must have attributes such as `message_id` and `message_type`.
            status: The status of the message to be registered.
                Defaults to `MessageStatus.registered`.
        z*Not initialized - register message ignoredNz)Already closed - register message ignoredINSERT INTO messages(message_id, status, message_type, message_json) VALUES (?,?,?,?) ON CONFLICT(message_id) DO UPDATE SET status = excluded.status, message_type = excluded.message_type, message_json = excluded.message_jsonz<register_message: failed to insert message into DB, reason: )r   rR   rX   rF   r    rS   r1   r.   message_typer7   rO   rP   rQ   )rJ   r+   r'   message_jsonvaluesrU   s         r   register_messagezDBManager.register_message   s    & LLEF]]{{JK ]
 :'BL ""$$	FYYII%%f  ] Y  ''RSURVW 	) ]sG   #C-%C-CB;*C;C	 C	C*C%%C**C--C6messages_batchc                 "   | j                   st        j                  d       y| j                  5  | j                  rt        j                  d       	 ddd       yg }|D ]6  }t        |      }|j                  |j                  ||j                  |f       8 	 | j                  5  | j                  j                  d|       ddd       ddd       y# 1 sw Y   xY w# t        $ r}| j                  d|         d}~ww xY w# 1 sw Y   yxY w)a  
        Registers a batch of messages in the database. If the system is not initialized or
        already closed, the operation is ignored.

        Args:
            messages_batch: A list of message objects to be
                registered in the database.
            status: The status to be associated with the registered messages.
                Defaults to MessageStatus.registered.
        z0Not initialized - register messages list ignoredNz/Already closed - register messages list ignoredr^   z>register_messages: failed to insert messages into DB, reason: )r   rR   rX   rF   r    rS   r1   appendr.   r_   r7   executemanyrP   rQ   )rJ   rc   r'   ra   r+   r`   rU   s          r   register_messageszDBManager.register_messages   s     LLKL]]{{PQ ]
 F)=gF**,,$	 *YYII))f % ]$ Y  ''TUWTXY 	1 ]sG   #D=DC%CCC	C	D(C==DDDmessage_idsc           	      4   | j                   s!t        j                  dt        |      |       y| j                  5  | j
                  r*t        j                  dt        |      |       	 ddd       y	 | j                  5  |t        j                  k(  rW|D cg c]  }|f }}| j                  j                  d|      }t        j                  d|j                  t        |             nX|D cg c]  }||f }}| j                  j                  d|      }t        j                  d|j                  t        |      |       ddd       ddd       yc c}w c c}w # 1 sw Y   xY w# t        $ r}| j                  d|         d}~ww xY w# 1 sw Y   yxY w)	a
  
        Updates the status of a batch of messages in the messages database table or deletes
        the messages if their status is 'delivered'. The function ensures thread safety
        and performs operations only if the instance is initialized and not closed.

        Args:
            message_ids: A list of message IDs to be updated or deleted.
            status: The new status to be assigned to the messages or to
                determine if messages should be deleted when the status is 'delivered'.

        zENot initialized - messages batch update ignored, size: %d, status: %rNzDAlready closed - messages batch update ignored, size: %d, status: %r)DELETE FROM messages WHERE message_id = ?z7Deleted %d DB message records for %d delivered messages3UPDATE messages SET status = ? WHERE message_id = ?z@Updated %d DB message records for %d messages to have status: %rzJupdate_messages_batch: failed to update messages batch in the DB, reason: )r   rR   rX   lenrF   r    rS   r7   r   r   rf   rowcountrP   rQ   )rJ   rh   r'   r.   db_idscdb_status_idsrU   s           r   update_messages_batchzDBManager.update_messages_batch0  s    LLWK 
 ]]{{Z$
  ]YY!8!88BM!N+J:-+!N II11G" UJJ, DO)CNZVZ0; & ) !II11Q) ^JJ,"	)  ] "O) Y4  ''`ac`de 	I ]sa   .F2E(>E
E AE-E8A	EE(
EE%	!E((	F1FFFFr.   c                    | j                   st        j                  d||       y| j                  5  | j                  r!t        j                  d||       	 ddd       y	 | j                  5  |t        j                  k(  r| j                  j                  d|f       n| j                  j                  d||f       ddd       ddd       y# 1 sw Y   xY w# t        $ r}| j                  d|         d}~ww xY w# 1 sw Y   yxY w)aE  
        Updates the status of a message in the database or removes it if delivered.

        This method is responsible for updating the status of a message in the database
        or removing delivered messages along with their associated data. The operation
        is ignored if the instance is not initialized or already closed.

        Args:
            message_id: The unique identifier of the message to be updated.
            status: The new status to set for the message. If the status
                is `MessageStatus.delivered`, the message will be removed.
        z<Not initialized - message update ignored, id: %d, status: %rNz;Already closed - message update ignored, id: %d, status: %rrj   rk   z<update_message: failed to update message in the DB, reason: )r   rR   rX   rF   r    rS   r7   r   r   rO   rP   rQ   )rJ   r.   r'   rU   s       r   update_messagezDBManager.update_messagep  s     LLN
 ]]{{Q
  ]YY!8!88		))G* 		))Q#Z0  ] Y  ''RSURVW 	) ]sB   %C? C,AC<CC	C	C<"C77C<<C??Dreplay_callbackc                    | j                   rt        j                  d       y| j                  st        j                  d       y| j                  j                  d      st        j                  d       y	 | j                  |      | j                  j                          S # | j                  j                          w xY w)aA  
        Replays previously failed messages by fetching them in batches, marking them as
        "in progress," and invoking the provided callback for processing.

        This method processes messages marked as failed in the database by updating
        their status and invoking the replay callback. It supports batch processing to
        limit memory usage. If the system is not initialized or already closed, the
        replay process is skipped. Errors encountered during the replay or while
        updating the database are logged, and the replay process halts.

        The lock is held only for the minimal critical sections (closed check and the
        DB status update that marks each batch as in-progress). It is released before
        calling the replay callback and before sleeping between batches so that
        producers (register_message / update_message) are never blocked during replay.

        Concurrent calls are serialized by a non-blocking mutex: if a replay is
        already in progress, the second caller returns 0 immediately, preventing
        duplicate re-fetching of the same failed messages.

        Args:
            replay_callback: A callback function that processes the
                replayed messages.

        Returns:
            int: The total count of successfully replayed messages.
        zDBManager already closedr   z)Not initialized - messages replay ignoredF)blockingz5Replay already in progress - skipping concurrent call)r    rR   rX   r   rH   acquire_replay_failed_messages_implrelease)rJ   rt   s     r   replay_failed_messagesz DBManager.replay_failed_messages  s    6 ;;LL34LLDE !!))5)9LLPQ	)44_E&&(D&&(s   8B# #B?c           
         | j                   5  | j                  rt        j                  d       	 d d d        y	 d d d        d}| j	                  | j
                        D ]  }|D cg c]'  }t        t        j                        |j                  f) }}| j                   5  | j                  r"t        j                  d|       	 d d d         |S 	 | j                  5  | j                  j                  d|      }t        j                  d|j                  t        |             d d d        d d d        | j#                  ||      }||z  }|| j
                  k\  st%        j&                  | j(                          |S # 1 sw Y   HxY wc c}w # 1 sw Y   oxY w# t        $ r}| j!                  d|         d }~ww xY w# 1 sw Y   xY w)Nz(Already closed - messages replay ignoredr   z1Closed during replay - stopping after %d replayedrk   z4Updated %d DB message records for %d failed messageszEreplay_failed_messages: failed to update messages in the DB, reason: )db_messagesrt   )rF   r    rR   rS   fetch_failed_messages_batchedr4   r(   r   r   r$   rX   r7   rf   rm   rl   rP   rQ   _replay_messagestimesleepr5   )	rJ   rt   total_replayedr|   r+   paramsro   rU   replayeds	            r   rx   z&DBManager._replay_failed_messages_impl  s   ]]{{IJ ] 
 ==dooNKKVKV]--.

;;  
 ;;LLK&  J = II11Q" RJJ, # 4 ,,' - H h&N
 4??*

4223S OV c ] # ! ++_`b_cd 	% sT   #F ',F!$GFAF$F F
FF	G'F<<GGG	r|   c           
      z   t         j                  dt        |             d}|D ]  }	 t        |      } ||       |dz  } |S # t        $ rm}t         j                  d|j                  |j                  |j                  |d       | j                  |j                  t        j                         Y d }~d }~ww xY w)Nz(Replaying %d failed messages to streamerr   r   zCFailed to replay message with id=%r, type=%r, status=%r, reason: %sTrM   )rR   rX   rl   db_message_to_messagerP   r!   r$   r%   r'   rs   r   r   )rJ   r|   rt   r   r+   base_messager\   s          r   r~   zDBManager._replay_messages  s     	?[AQR"GF4W=-A	 #"   
FYJJLLNN!   ##GJJ0D0DEE
Fs   A	B:A#B55B:c                 @    | j                  |      }|t        |      S y)a
  
        Fetches a message by its unique identifier.

        This method retrieves a message from the database using the provided message ID. If a corresponding
        database message is found, it converts the database message to a message object and returns it.
        If no message is found for the given ID, the method returns None.

        Args:
            message_id: The unique identifier of the message to retrieve.

        Returns:
            The converted message object if found, otherwise None.
        N)get_db_messager   )rJ   r.   
db_messages      r   get_messagezDBManager.get_message  s(     ((4
!(44r   c           
          | j                   5  | j                   j                  d|f      }|j                         }|-t        |d   |d   |d   t	        |d               cddd       S 	 ddd       y# 1 sw Y   yxY w)aH  
        Retrieves a database message based on the provided message ID.

        This method queries the database for a message record that corresponds to the
        given message ID. If a matching message record is found, it constructs and
        returns a `DBMessage` object containing the message's details. If no record is
        found, it returns `None`.

        Args:
            message_id: The ID of the message to retrieve.

        Returns:
            A `DBMessage` object containing the details of the
            retrieved message if found, otherwise `None`.
        zXSELECT message_id, message_type, message_json, status FROM messages WHERE message_id = ?Nr   r   r   r   r$   r%   r&   r'   )r7   rO   fetchoner#   r   )rJ   r.   ro   rows       r   r   zDBManager.get_db_message1  sv      YY		!!jA **,C 1vCFQcRSf@U Y  YYs   AA4*A44A=c                 <    | j                   t        j                  k(  S )am  
        Determines if the manager is currently closed.

        This property evaluates whether the manager's `status` is equivalent to
        `DBManagerStatus.closed`. If so, it indicates that the manager is not
        active or operational.

        Returns:
            bool: True if the manager's status is `DBManagerStatus.closed`, False otherwise.
        )r'   r   r    rJ   s    r   r    zDBManager.closedN  s     {{o4444r   c                 <    | j                   t        j                  k(  S )aR  
        Indicates whether the manager's status is currently set to 'initialized'.

        This property provides a lookup to determine if the manager is currently in the
        initialized state, based on its status attribute.

        Returns:
            bool: True if the manager's status is 'initialized', False otherwise.
        )r'   r   r   r   s    r   r   zDBManager.initialized\  s     {{o9999r   c                 <    | j                   t        j                  k(  S )a{  
        Checks if the current status indicates a failure.

        The `failed` property evaluates whether the `status` attribute of the
        manager is equal to `ManagerStatus.error`. This serves as an indicator
        of whether an error state has been encountered.

        Returns:
            bool: True if the status is `ManagerStatus.error`, otherwise False.
        )r'   r   r!   r   s    r   r   zDBManager.failedi  s     {{o3333r   c           
   #     K   d}	 g }| j                   5  	 | j                  j                  dt        j                  ||f      }|D ]7  }|j                  t        |d   |d   |d   t        j                               9 	 ddd       |sy|d   j                  }| # t
        $ r}| j                  d|         d}~ww xY w# 1 sw Y   FxY ww)	zFetch failed messages from DB in bounded batches to avoid OOM.

        Uses cursor-based pagination with message_id for efficient iteration.
        Yields batches of DBMessage objects until no more failed messages remain.
        z{SELECT message_id, message_type, message_json FROM messages WHERE status = ? AND message_id > ? ORDER BY message_id LIMIT ?zTfetch_failed_messages_batched: failed to fetch failed messages from the DB, reason: Nr   r   r   r   )
rF   r7   rO   r   r   rP   rQ   re   r#   r$   )rJ   r4   last_seen_idbatchrowsrU   r   s          r   r}   z'DBManager.fetch_failed_messages_batchedw  s      %'E
99,,Z&--|ZHD  CLL!"1v!$Q!$Q#0#7#7	   .  9<<LK;  ! ++noqnrs 	 s?   CC-B<C C	C(B==CCC
Cc                    | j                   st        j                  d       y| j                  5  | j                  rt        j                  d       	 ddd       y	 | j                  j                  dt        j                  f      j                         d   cddd       S # t        $ r=}d| }t        j                  |       | j                  |       Y d}~ddd       yd}~ww xY w# 1 sw Y   yxY w)z\Returns the number of failed messages in the DB or -1 if not initialized, closed, or failed.z/Not initialized - failed messages count ignoredr   z.Already closed - failed messages count ignoredNz.SELECT COUNT(*) FROM messages WHERE status = ?r   zDfailed_messages_count: failed to get failed messages count, reason: )r   rR   rX   rF   r    rS   r7   rO   r   r   r   rP   r!   rQ   rT   s      r   failed_messages_countzDBManager.failed_messages_count  s    LLJK]]{{OP ]
	yy((D"))+ (*Q  ]  \]_\`aS!'', ] ]s/   #C*;B!!	C'*+C"C*"C''C**C3c                 Z    t         j                  | _        t        j                  d|       y )NzDue to an internal error, some network resiliency features were disabled which could lead to data loss. Contact us at support@comet.com. Error details: %r)r   r!   r'   rR   )rJ   r+   s     r   rQ   zDBManager._mark_as_db_failed  s$    %++`	
r   )NNN)r,   N)(r   r   r   r   r(   floatr   r*   rB   
ConnectionrD   rE   rK   rI   rY   r   r   r	   BaseMessagerb   r   rg   rq   rs   r   rz   rx   r#   r~   r   r   propertyboolr    r   r   r   r}   r   rQ   r   r   r   r3   r3   a   s+    "&-1/3+!+! "+! #	+!
 w))*+! IOO,+! 
+!Z/""N !. 8 8/%%/ / 
	/h !. 8 8/X112/ / 
	/b>9>.;>	>@- -m - -^/)n /) /)b3N 3s 3j	?=K	0c hx7K7K.L ( )1D : 5 5 5 
:T 
: 
: 4 4 4&&	$y/	"&Ps ,
# 
$ 
r   r3   r   c                     t         j                  | j                        }|t        d| j                         t	        j
                  || j                        S )a  
    Converts a database message object to a corresponding application message object.

    This function maps a database message type to its associated application message
    class using a predefined dictionary of supported message types. If the type is not
    supported, an exception is raised. The message is then deserialized from its JSON
    representation into the appropriate application message class.

    Args:
        db_message: The database message object containing the type and serialized JSON
            data to be deserialized.

    Returns:
        The deserialized application message object corresponding to the provided
        database message type.

    Raises:
        ValueError: If the database message contains an unsupported or unrecognized
        message type.
    zUnsupported message type: )json_str)SUPPORTED_MESSAGE_TYPESgetr%   r/   r
   deserialize_messager&   )r   message_classs     r   r   r     sR    * ,//
@M5joo5FGHH 44
 r   )0r   loggingr>   rZ   rB   r;   rD   r   enumr   r   typingr   r   r   r   opik.message_processingr	   opik.message_processing.replayr
   typesr   rA   	getLoggerr   rR   r   r   r#   r   r*   r1   r3   CreateTraceMessager_   UpdateTraceMessageCreateSpanMessageUpdateSpanMessage"AddTraceFeedbackScoresBatchMessage!AddSpanFeedbackScoresBatchMessage$AddThreadsFeedbackScoresBatchMessageCreateSpansBatchMessageCreateTraceBatchMessageGuardrailBatchMessage!CreateExperimentItemsBatchMessageCreateAttachmentMessager   r   r   r   r   <module>r      s    	        7 7 , @ ! %			8	$ G  * 
g 
 

 ,<H,@,@ <S <Z	
 Z	
| ,,h.I.I,,h.I.I++X-G-G++X-G-G//<<h>i>i..;;X=g=g11>>@m@m$$1183S3S$$1183S3S""//1O1O..;;X=g=g$$1183S3S  i H4H4H r   