
    	]j?U                     (   d Z ddlZddlmZ ddlmZmZmZmZmZ ddl	m
Z
 ddlmZ ddlmZ ddlmZmZ dd	lmZmZ dd
lmZ ddlmZ ddlmZ  ej6                  e      Zdad Z G d de       Z! G d de!      Z" G d d      Z#e#Z$dZ%dee#   fdZ&y)z
Core state management functionality for Label Studio.

Provides high-performance state management with caching and batch operations
that can be extended by Label Studio Enterprise with additional features.
    N)datetime)AnyDictListOptionalType)CurrentContext)flag_set)settings)cachecaches)ModelQuerySet)get_state_model_for_entity)	BaseState%execute_transition_with_state_managerc                      t         t         S t        t        dd      } | r!| t        j                  v rt        |    a t         S t
        a t         S )aG  
    Get the cache backend for FSM operations.

    Uses REDIS_CACHE_ALIAS when available (LSE) to ensure FSM caching works
    even when DUMMY_CACHE is enabled. Falls back to default cache for LSO.

    The result is memoized so the cache lookup only happens once per process.

    Returns:
        Cache backend instance
    NREDIS_CACHE_ALIAS)
_fsm_cachegetattrr   CACHESr   r   )redis_cache_aliass    H/root/env/lib/python3.12/site-packages/label_studio/fsm/state_manager.pyget_fsm_cacher      sO     *=tD.(//A-.
  
    c                       e Zd ZdZy)StateManagerErrorz*Base exception for StateManager operationsN__name__
__module____qualname____doc__ r   r   r   r   3   s    4r   r   c                       e Zd ZdZy)InvalidTransitionErrorz4Raised when an invalid state transition is attemptedNr   r$   r   r   r&   r&   9   s    >r   r&   c                      e Zd ZdZ eedd      ZdZed        Z	edde
fd       Zededefd	       Zededee   fd
       Zededefd       Ze	 	 	 	 	 	 ddedededeeef   dede
de
fd       Zededee   fd       Ze	 ddededee   dee   fd       Zedefd       Zedee   fd       Ze	 ddededeeef   defd       Zy) StateManagera-  
    Core state management system for Label Studio.

    Provides the foundation for state management that can be extended
    by Label Studio Enterprise with additional features like:
    - Advanced caching strategies
    - Bulk operations optimization
    - Complex transition validation
    - Enterprise-specific state models

    Features:
    - INSERT-only architecture with UUID7 for maximum performance
    - Basic caching for current state lookups
    - Simple state transitions with audit trails
    - Extensible design for enterprise features
    FSM_CACHE_TTLi,  zfsm:currentc                     t               }| j                   d}t        |d      r|j                  |       yt        j                  d       y)z
        Clear all FSM-related cache keys.

        Uses delete_pattern if available (django-redis), otherwise logs a warning.
        This is primarily used for test isolation.
        z:*delete_patternzhFSM cache clear requested but cache backend does not support delete_pattern. FSM cache keys may persist.N)r   CACHE_PREFIXhasattrr+   loggerwarning)cls	fsm_cachepatterns      r   clear_fsm_cachezStateManager.clear_fsm_cacheT   sH     "O	%%&b)9./$$W-NN.r   returnc                 R    ||dk(  rt        j                         S t        d|      S )z1Check if FSM feature is enabled via feature flag.auto*fflag_feat_fit_568_finite_state_managementuser)r	   is_fsm_enabledr
   )r0   r9   s     r   _is_fsm_enabledzStateManager._is_fsm_enabledf   s,     <46>!0022D4PPr   entityc                 f    | j                    d|j                  j                   d|j                   S )z-Generate cache key for entity's current state:)r,   _metalabel_lowerpk)r0   r<   s     r   get_cache_keyzStateManager.get_cache_keym   s1     ""#1V\\%=%=$>a		{KKr   c                 j   | j                         sy| j                  |      }t               }|j                  |      }|Ot        j                  dd|j                  j                  |j                  t        j                         |d       |S t        |      }|s#t        d|j                  j                   d      	 |j                  |      }|i|j                  ||| j                          t        j                  dd	|j                  j                  |j                  t        j                         d
       |S # t"        $ rk}t        j%                  dd|j                  j                  |j                  t        j                         t'        |      dd       t        d|       |d}~ww xY w)a  
        Get current state with basic caching.

        Args:
            entity: The entity to get current state for

        Returns:
            Current state string
        Raises:
            StateManagerError: If no state model found

        Example:
            task = Task.objects.get(id=123)
            current_state = StateManager.get_current_state_value(task)
            if current_state == 'COMPLETED':
                # Task is finished
                pass
        NzFSM: Cache hitzfsm.cache_hit)evententity_type	entity_idorganization_idstateextraNo state model found for z when getting current statezFSM: Cache misszfsm.cache_miss)rD   rE   rF   rG   z FSM: Error getting current statezfsm.get_state_error)rD   rE   rF   rG   errorTrJ   exc_infozError getting current state: )r;   rB   r   getr.   infor?   r@   rA   r	   get_organization_idr   r   
model_nameget_current_state_valueset	CACHE_TTL	ExceptionrL   str)r0   r<   	cache_keyr1   cached_statestate_modelcurrent_statees           r   rS   z$StateManager.get_current_state_valuer   s   ( ""$%%f-	!O	 !}}Y/#KK ,#)<<#;#;!''5'I'I'K)  	   18#&?@W@W?XXs$tuu	P'??GM (iF%!1'-||'?'?%+YY+9+M+M+O	   !  	PLL22#)<<#;#;!''5'I'I'K V   
 $&CA3$GHaO	Ps    A=D> >	F2A&F--F2c                     t        |      }|s#t        d|j                  j                   d      |j	                  |      S )a  
        Get current state object with full audit information.

        Args:
            entity: The entity to get current state object for

        Returns:
            Latest BaseState instance

        Raises:
            StateManagerError: If no state model found
        rK   z" when getting current state object)r   r   r?   rR   get_current_stater0   r<   rZ   s      r   get_current_state_objectz%StateManager.get_current_state_object   sI     18#+FLL,C,C+DDfg  ,,V44r   N	new_statetransition_namecontextreasonforce_state_recordc	                 0   | j                  |      syt        j                         ryt        |      }	|	s#t	        d|j
                  j                   d      | j                  |      }
|
|k(  rE|sC |	j                  j                  d"i |j
                  j                  |ij                         }|ry| j                  |      }| d}t               }|t        j                         }	 |j                  |dd	      }|s<t        j!                  d
d|j
                  j"                  |j$                  ||d       y	 |	j'                  |      }|/t)        |dt)        |dd            }|t        j*                  |       |sG|rEt-        |d      r9|j.                  r-|j.                  j0                  }|t        j*                  |       t        j!                  dd|j
                  j"                  |j$                  |
||d|r|j0                  nd|r|ndd       |	j3                         } |	j                  j4                  d"i ||i||
|t)        |dd      r|nd|xs i ||d|}|j7                  ||| j8                         t        j!                  dd|j
                  j"                  |j$                  |d|r|j0                  nd|r|ndd       t        j!                  dd|j
                  j"                  |j$                  |t;        |j0                        d|r|j0                  nd|r|ndd       	 |j=                  |       y# |j=                  |       w xY w# t>        $ r}|j=                  |       |j=                  |       t        j                         }t        jA                  dd|j
                  j"                  |j$                  |
|t;        |      d|r|j0                  nd|r|nddd        t	        d!|       |d}~ww xY w)#aE  
        Perform state transition with audit trail.

        Uses INSERT-only approach for maximum performance:
        - No UPDATE operations or row locks
        - Complete audit trail by design
        - Basic cache update for consistency

        Args:
            entity: The entity to transition
            new_state: Target state
            transition_name: Name of transition method (for audit)
            user: User triggering the transition
            organization_id: Organization ID
            context: Additional context data
            reason: Human-readable reason for transition
            force_state_record: If True, creates state record even if state doesn't change (for audit trails)

        Returns:
            True if transition succeeded, False otherwise

        Raises:
            InvalidTransitionError: If transition is not valid
            StateManagerError: If transition fails

        Example:
            success = StateManager.transition_state(
                entity=task,
                new_state='IN_PROGRESS',
                transition_name='start_annotation',
                user=request.user,
                organization_id=request.user.active_organization_id,
                context={'assignment_id': assignment.id},
                reason='User started annotation work'
            )
        r8   TrK   z when transitioning statez:lockNlocked   )timeoutz-FSM: Concurrent transition detected, skippingz!fsm.concurrent_transition_skipped)rD   rE   rF   target_staterG   rI   rG   active_organizationzFSM: State transition startingzfsm.transition_state_start)rD   rE   rF   
from_stateto_staterb   )user_idrG   is_authenticatedF)rH   previous_staterb   triggered_bycontext_datard   rG   z'FSM: Cache updated for transition statez"fsm.transition_state_cache_updated)rD   rE   rF   rH   z FSM: State transition successfulzfsm.transition_state_success)rD   rE   rF   rH   state_record_idzFSM: State transition failedzfsm.transition_state_failed)rD   rE   rF   rl   rm   rL   rM   zFailed to transition state: r$   )!r;   r	   is_fsm_disabledr   r   r?   rR   rS   objectsfilterexistsrB   r   rQ   addr.   rP   r@   rA   get_denormalized_fieldsr   set_organization_idr-   rk   id_get_entity_field_namecreaterT   rU   rW   deleterV   rL   )r0   r<   ra   rb   r9   rG   rc   rd   re   rZ   r[   state_record_existsrX   lock_keyr1   lock_acquireddenormalized_fieldsentity_field_namenew_state_recordr\   s                       r   transition_statezStateManager.transition_state   s@   ` """- ))+08#&?@W@W?XXq$rss33F; I%.@"<+"5"5"<"<"a@W@WY_?`"a"h"h"j" %%f-	[&!O	",@@BOD	O &MM(HaMHM C!D'-||'?'?%+YY(1+:  	 W+ '2&I&I&&Q# #*&- 17;NPacg3h'O '2&::?K&4GDBW4X]a]u]u&*&>&>&A&AO&2&::?K4!='-||'?'?%+YY&3$-+: 37twwDBQW[  & %0$F$F$H!#=;#6#6#=#= 
$(&1
$##0$3)07I5)QW[!(B!$3
$ *
$  iCMMB=!E'-||'?'?%+YY!*		 37twwDBQW[	   6!?'-||'?'?%+YY!*+./?/B/B+C
 37twwDBQW[
      *	  * 	OX&Y' -@@BOLL.:#)<<#;#;!'"/ ) V /3477>M?SW     $&B1#$FGQN1	Os3   +AM% >G>M =M% M""M% %	P.B"PPc                     t        |      }|s#t        d|j                  j                   d      |j	                  |      S )z
        Get complete state history for an entity.

        Args:
            entity: Entity to get history for

        Returns:
            QuerySet of state records ordered by most recent first
        No state model registered for z when getting state history)r   r   r?   rR   get_state_historyr_   s      r   r   zStateManager.get_state_history  sI     18#01H1H0IIde  ,,V44r   
start_timeend_timec                     t        |      }|s#t        d|j                  j                   d      |j	                  |||xs t        j                               S )a5  
        Get states within a time range using UUID7 time-based queries.

        Args:
            entity: Entity to get states for
            start_time: Start of time range
            end_time: End of time range (defaults to now)

        Returns:
            List of states within the time range
        r   z" when getting states in time range)r   r   r?   rR   get_states_in_ranger   now)r0   r<   r   r   rZ   s        r   get_states_in_time_rangez%StateManager.get_states_in_time_range  sX     18#01H1H0IIkl  ..vz8C]x||~^^r   c                    | j                  |      }t               }|j                  |       t        j                         }t
        j                  dd|j                  j                  |j                  dd|r|ndi       y)z%Invalidate cached state for an entityzFSM: Cache invalidatedzfsm.cache_invalidated)rD   rE   rF   rG   NrI   )
rB   r   r~   r	   rQ   r.   rP   r?   r@   rA   )r0   r<   rX   r1   rG   s        r   invalidate_cachezStateManager.invalidate_cache  sz     %%f-	!O	#(<<>$0%||77#YY %odS	 	 	
r   entitiesc                 :   i }t        j                         }|D ],  }| j                  |      }|s| j                  |      }|||<   . |rRt	               }|j                  || j                         t        j                  ddt        |      dd|r|ndi       yy)z
        Warm cache with current states for a list of entities.

        Basic implementation that can be optimized by Enterprise with
        bulk queries and advanced caching strategies.
        zFSM: Cache warmedzfsm.cache_warmed)rD   entity_countrG   NrI   )
r	   rQ   rS   rB   r   set_manyrU   r.   rP   len)r0   r   cache_updatesrG   r<   r[   rX   r1   s           r   
warm_cachezStateManager.warm_cache  s     (<<> 	9F77?M--f5	+8i(		9 %I}cmm<KK#/$'$6 )_/RVW   r   transition_datac           	      $    t        d||||| d|S )a  
        Execute a registered transition by name.

        This is the main entry point for all state transitions using the declarative system.
        Enterprise implementations can override this method to add additional behavior.

        Args:
            entity: The entity to transition
            transition_name: Name of the registered transition
            transition_data: Data for the transition (validated by Pydantic)
            user: User executing the transition
            **context_kwargs: Additional context data

        Returns:
            The newly created state record

        Raises:
            ValueError: If transition is not found
            TransitionValidationError: If transition validation fails
        )r<   rb   r   r9   state_manager_classr$   r   )r0   r<   rb   r   r9   context_kwargss         r   execute_transitionzStateManager.execute_transition  s/    2 5 
++ #
 
 	
r   )r6   )NNNN F)N)NN)r    r!   r"   r#   r   r   rU   r,   classmethodr3   boolr;   r   rW   rB   r   rS   r   r`   r   r   r   r   r   r   r   r   r   r   r   r$   r   r   r(   r(   ?   s0   " /37I L " QT Q Q L5 LS L L KPU KPx} KP KPZ 5e 5	 5 5* 
  $"&#(TOTO TO 	TO c3hTO TO !TO 
TO TOl 5u 5)1D 5 5$ QU__(0_<DX<N_	i_ _, 
e 
 
  $u+  6 _c

-0
CGS>
	
 
r   r(   r4   c                      t         t         S t        t        d      r?t        j                  } | j	                  dd      \  }}t        ||g      }t        ||      S t        S )z
    Get the configured state manager class.

    Returns the StateManager class to use. Enterprise can override
    this by setting a different class in their configuration.
    FSM_STATE_MANAGER_CLASS.   )fromlist)RESOLVED_STATE_MANAGERr-   r   r   rsplit
__import__r   DEFAULT_STATE_MANAGER)manager_pathmodule_name
class_namemodules       r   get_state_managerr   -  s`     )%% x2377"."5"5c1"=ZK:,?vz**  r   )'r#   loggingr   typingr   r   r   r   r   core.current_requestr	   core.feature_flagsr
   django.confr   django.core.cacher   r   django.db.modelsr   r   fsm.registryr   fsm.state_modelsr   fsm.transition_executorr   	getLoggerr    r.   r   r   rV   r   r&   r(   r   r   r   r$   r   r   <module>r      s      2 2 / '   + , 3 & I			8	$ 
0		 		. 	e
 e
T %  !4- !r   