
    	]jJ              	       <   d Z ddlmZmZ ddlmZ ddlmZmZmZm	Z	m
Z
mZ ddlmZ ddlmZmZmZ erddlmZ  ede	      Z ed
e	      Zn ed      Z ed
      Z G d dee	eef         Z G d de      Z G d deee	eef         Z G d dee	eef         Zy)a  
Declarative Pydantic-based transition system for FSM engine.

This module provides a framework for defining state transitions as first-class
Pydantic models with built-in validation, context passing, and middleware-like
functionality for enhanced declarative state management.
    )ABCabstractmethod)datetime)TYPE_CHECKINGAnyDictGenericOptionalTypeVar)Model)	BaseModel
ConfigDictField)	BaseState
EntityType)boundStateModelTypec                   8   e Zd ZU dZ ed      Z edd      Zee	d<    edd	      Z
ee   e	d
<    edd      Zee   e	d<    edd      Zee   e	d<    edd      Zee   e	d<    eej"                  d      Zee	d<    edd      Zee   e	d<    eed      Zeeef   e	d<    eed      Zeeef   e	d<    edd      Zee   e	d<    edd      Zee   e	d<    edd       Zee   e	d!<    eed"      Zeeef   e	d#<   ed$efd%       Zed$efd&       Z y)'TransitionContextz
    Context object passed to all transitions containing middleware-like information.

    This provides access to current state, entity, user, and other contextual
    information needed for transition validation and execution.
    T)arbitrary_types_allowed.zThe entity being transitioned)descriptionentityNz-User triggering the transition (request user)current_userzFull current state objectcurrent_state_objectzCurrent state as stringcurrent_statezHTarget state for this transition (None for side-effect only transitions)target_statezWhen transition was initiateddefault_factoryr   	timestampzName of the transition methodtransition_namezAdditional request/context datarequest_datazTransition-specific metadatametadataz'Organization context for the transitionorganization_idFz-Whether to skip validation for the transitiondefaultr   skip_validationzFOverride reason for this transition (takes precedence over get_reason)reasonz2Additional context data to store with state recordcontext_datareturnc                     | j                   duS )z#Check if entity has a current stateN)r   selfs    F/root/env/lib/python3.12/site-packages/label_studio/fsm/transitions.pyhas_current_statez#TransitionContext.has_current_stateJ   s     !!--    c                     | j                    S )z:Check if this is the first state transition for the entity)r.   r+   s    r-   is_initial_transitionz'TransitionContext.is_initial_transitionO   s     ))))r/   )!__name__
__module____qualname____doc__r   model_configr   r   r   __annotations__r   r
   r   r   strr   r   nowr   r    dictr!   r   r"   r#   intr&   boolr'   r(   propertyr.   r1    r/   r-   r   r      s    d;L )HIFCI"':i"jL(3-j*/B]*^(3-^#(;T#UM8C=U"'d#L(3- 
  JijIxj%*4=\%]OXc]] $)Kl#mL$sCx.m$TGefHd38nf &+4=f%gOXc]g ',EGv&wOXd^w
 "bFHSM  $)*^$L$sCx.  .4 . . *t * *r/   r   c                   >     e Zd ZdZddedeeeef      f fdZ xZ	S )TransitionValidationErrorz1Exception raised when transition validation failsmessagecontextc                 :    t         |   |       |xs i | _        y N)super__init__rB   )r,   rA   rB   	__class__s      r-   rF   z"TransitionValidationError.__init__X   s    !}"r/   rD   )
r2   r3   r4   r5   r8   r
   r   r   rF   __classcell__rG   s   @r-   r@   r@   U   s+    ;% %htCH~.F % %r/   r@   c                       e Zd ZdZ eddd      Z fdZedee	e
ef      fd       Zej                  de	e
ef   fd       Ze	 dd
ee	e
ef      dee   fd       Zedefd       Zed
e	e
ef   defd       Zd
e	e
ef   defdZd
e	e
ef   dd	fdZed
e	e
ef   deeef   fd       Zd
e	e
ef   dedd	fdZd
e	e
ef   defdZd
e	e
ef   deeef   fdZd
e	e
ef   dedd	fdZ xZS )BaseTransitiona  
    Abstract base class for all declarative state transitions.

    This provides the framework for implementing transitions as first-class Pydantic
    models with built-in validation, context handling, and execution logic.

    Example usage:
        class StartTaskTransition(BaseTransition[Task, TaskState]):
            assigned_user_id: int = Field(..., description="User assigned to start the task")
            estimated_duration: Optional[int] = Field(None, description="Estimated completion time in hours")

            def get_target_state(self, context: Optional[TransitionContext[Task, TaskState]]) -> str:
                return TaskStateChoices.IN_PROGRESS

            def validate_transition(self, context: TransitionContext[Task, TaskState]) -> bool:
                if context.current_state == TaskStateChoices.COMPLETED:
                    raise TransitionValidationError("Cannot start an already completed task")
                return True

            def transition(self, context: TransitionContext[Task, TaskState]) -> Dict[str, Any]:
                return {
                    "assigned_user_id": self.assigned_user_id,
                    "estimated_duration": self.estimated_duration,
                    "started_at": context.timestamp.isoformat()
                }
    T)r   validate_assignmentuse_enum_valuesc                 2    t        |   di | d | _        y )Nr>   )rE   rF   _BaseTransition__context)r,   datarG   s     r-   rF   zBaseTransition.__init__{   s     4 RVr/   r)   c                     t        | dd      S )z%Access the current transition contextrO   N)getattrr+   s    r-   rB   zBaseTransition.context   s     t7>>r/   valuec                     || _         y)zSet the transition contextN)rO   )r,   rS   s     r-   rB   zBaseTransition.context   s     r/   NrB   c                      y)a  
        Get the target state this transition leads to.

        Can optionally use context to compute target_state dynamically.
        If context is None, should return a static target_state or None.

        Args:
            context: Optional transition context (may be minimal with just entity)

        Returns:
            String representation of the target state, or None for side-effect only transitions
            that don't create state records (e.g., audit-only or notification-only transitions)
        Nr>   r,   rB   s     r-   get_target_statezBaseTransition.get_target_state       " 	r/   c                    t        | j                  d      r| j                  j                  S | j                  j                  }d}t	        |      D ]2  \  }}|j                         r
|dkD  r|dz  }||j                         z  }4 |S )z
        Name of this transition for audit purposes.

        Returns the registered transition name from the decorator, or falls back
        to the class name in snake_case.
        _transition_name r   _)hasattrrG   rZ   r2   	enumerateisupperlower)r,   
class_nameresultichars        r-   r    zBaseTransition.transition_name   s     4>>#56>>222 ^^,,
 , 	#GAt||~!a%#djjl"F	# r/   c                      y)a  
        Class-level validation for whether this transition type is allowed from the current state.

        This method checks if the transition is structurally valid (e.g., allowed state transitions)
        without needing the actual transition data. Override this to implement state-based rules.

        Args:
            context: The transition context containing entity, user, and state information

        Returns:
            True if transition type is allowed from current state, False otherwise
        Tr>   )clsrB   s     r-   can_transition_from_statez(BaseTransition.can_transition_from_state   s     r/   c                 (    | j                  |      syy)a#  
        Validate whether this specific transition instance can be performed.

        This method validates both the transition type (via can_transition_from_state)
        and the specific transition data. Override to add data-specific validation.

        Args:
            context: The transition context containing entity, user, and state information

        Returns:
            True if transition is valid, False otherwise

        Raises:
            TransitionValidationError: If transition validation fails with specific reason
        FT)rg   rV   s     r-   validate_transitionz"BaseTransition.validate_transition   s    " --g6 r/   c                      y)z
        Hook called before the transition is executed.

        Use this for any setup or preparation needed before state change.
        Override in subclasses as needed.

        Args:
            context: The transition context
        Nr>   rV   s     r-   pre_transition_hookz"BaseTransition.pre_transition_hook   s     	r/   c                      y)a  
        Execute the transition and return context data for the state record.

        This is the core method that implements the transition logic.
        Must be implemented by all concrete transition classes.

        Args:
            context: The transition context containing all necessary information

        Returns:
            Dictionary of context data to be stored with the state record

        Raises:
            TransitionValidationError: If transition cannot be completed
        Nr>   rV   s     r-   
transitionzBaseTransition.transition   rX   r/   state_recordc                      y)aF  
        Hook called after the transition has been successfully executed.

        Use this for any cleanup, notifications, or side effects after state change.
        Override in subclasses as needed.

        Args:
            context: The transition context
            state_record: The newly created state record
        Nr>   r,   rB   rn   s      r-   post_transition_hookz#BaseTransition.post_transition_hook   s     	r/   c                 r    |j                   rd|j                    nd}| j                  j                   d| S )a  
        Get a human-readable reason for this transition.

        Override in subclasses to provide more specific reasons.

        Note: If `context.reason` is set, it takes precedence over this method.
        This allows callers to provide context-specific reasons when executing
        transitions (e.g., "Project moved from Sandbox to shared workspace").

        Args:
            context: The transition context

        Returns:
            Human-readable reason string
        zby automaticallyz
 executed )r   rG   r2   )r,   rB   	user_infos      r-   
get_reasonzBaseTransition.get_reason  s=      5<4H4Hc'../0o	..))**YK@@r/   c                 @   || _         | j                  |_        	 |j                  sA| j                  |      s0t	        d| j                   |j
                  |j                  d      | j                  |       | j                  |      }|S # t        $ r	 d| _          w xY w)a<  
        Prepare and validate the transition, returning the transition data.

        This method handles the preparation phase of the transition:
        1. Set context on the transition instance
        2. Validate the transition if not skipped
        3. Execute pre-transition hooks
        4. Perform the actual transition logic

        Args:
            context: The transition context

        Returns:
            Dictionary of transition data to be stored with the state record

        Raises:
            TransitionValidationError: If validation fails
        z!Transition validation failed for )r   r   N)
rB   r    r&   ri   r@   r   r   rk   rm   	Exception)r,   rB   transition_datas      r-   prepare_and_validatez#BaseTransition.prepare_and_validate  s    (  #'"6"6	**43K3KG3T/78L8L7MN&-&;&;WMaMab  $$W- #oog6O"" 	DL	s   A0B Bc                 P    	 | j                  ||       d| _        y# d| _        w xY w)aE  
        Finalize the transition after the state record has been created.

        This method handles post-transition activities:
        1. Execute post-transition hooks
        2. Clear the context

        Args:
            context: The transition context
            state_record: The newly created state record
        N)rq   rB   rp   s      r-   finalizezBaseTransition.finalizeG  s(    	 %%g|<  DL4DLs    	%rD   )r2   r3   r4   r5   r   r6   rF   r=   r
   r   r   r   rB   setterr   r8   rW   r    classmethodr<   rg   ri   rk   r   r   rm   rq   ru   ry   r{   rH   rI   s   @r-   rK   rK   ]   s   6 dPTfjkLW ?"3J4N"OP ? ? ^^.z>/IJ   QU 1*n2L MN	# $   ( 0A*nB\0] bf  +<Z=W+X ]a .
+<Z=W+X 
]a 
 "3J4N"O TXY\^aYaTb  $(^)CDTb	A"3J4N"O ATW A&,,=j.>X,Y ,^bcfhkck^l ,\  1*n2L M  ]k  pt  r/   rK   c                       e Zd ZU dZ eed      Zeeeee	f   f   e
d<    edd      Zee
d<   dZee
d	<   d
Zee
d<   g Zee
d<   deeef   defdZdeeef   def fdZededeeef   dd fd       Zdeeef   defdZ xZS )ModelChangeTransitionaQ  
    Specialized transition class for model-triggered state changes.

    This class extends BaseTransition with additional context about model changes,
    making it ideal for transitions triggered by FsmHistoryStateModel.save() operations.

    Features:
    - Access to changed fields (old vs new values)
    - Knowledge of whether entity is being created or updated
    - Automatic integration with FsmHistoryStateModel lifecycle
    - Declarative trigger field specification

    Example usage:
        @register_state_transition('task', 'task_created', triggers_on_create=True)
        class TaskCreatedTransition(ModelChangeTransition[Task, TaskState]):
            def get_target_state(self, context: Optional[TransitionContext] = None) -> str:
                return 'CREATED'

            def transition(self, context: TransitionContext) -> Dict[str, Any]:
                return {'reason': 'Task created'}

        @register_state_transition('task', 'task_labeled', triggers_on=['is_labeled'])
        class TaskLabeledTransition(ModelChangeTransition[Task, TaskState]):
            def get_target_state(self, context: Optional[TransitionContext] = None) -> str:
                return 'ANNOTATION_COMPLETE'

            def transition(self, context: TransitionContext) -> Dict[str, Any]:
                return {'reason': 'Task became labeled'}
    z?Fields that changed: {field_name: {'old': value, 'new': value}}r   changed_fieldsFz%Whether this is a new entity creationr$   is_creating_triggers_on_createT_triggers_on_update_trigger_fieldsrB   r)   c                      y)a  
        Determine if this transition should execute based on model changes.

        Override in subclasses to provide specific logic based on:
        - Whether entity is being created (is_creating)
        - Which fields changed (changed_fields)
        - Current and target states

        Default implementation always returns True.

        Args:
            context: The transition context with entity and state information

        Returns:
            True if transition should execute, False to skip

        Example:
            def should_execute(self, context: TransitionContext) -> bool:
                # Only execute if is_labeled changed to True
                if 'is_labeled' in self.changed_fields:
                    old_val = self.changed_fields['is_labeled']['old']
                    new_val = self.changed_fields['is_labeled']['new']
                    return not old_val and new_val
                return False
        Tr>   rV   s     r-   should_executez$ModelChangeTransition.should_execute  s    4 r/   c                 J    t         |   |      sy| j                  |      syy)aV  
        Validate whether this transition should execute.

        Extends parent validation with should_execute() check.

        Args:
            context: The transition context

        Returns:
            True if transition is valid and should execute

        Raises:
            TransitionValidationError: If validation fails
        FT)rE   ri   r   )r,   rB   rG   s     r-   ri   z)ModelChangeTransition.validate_transition  s+      w*73 ""7+r/   c           	      |    |j                         D ci c]  \  }\  }}|||d }}}} | d||d|S c c}}}w )a  
        Factory method to create a transition from model change data.

        This is called by FsmHistoryStateModel when a transition needs to be executed.

        Args:
            is_creating: Whether the model is being created
            changed_fields: Dict of changed fields (field_name -> (old, new))
            **extra_data: Additional data to pass to the transition

        Returns:
            Configured transition instance
        )oldnew)r   r   r>   )items)rf   r   r   
extra_data
field_nameold_valnew_valconverted_fieldss           r-   from_model_changez'ModelChangeTransition.from_model_change  s`    & `n_s_s_u
 
=[ZI['SZJ88
 
 Z{;KZzZZ	
s   7c                 \   | j                   r#|j                  j                  j                   dS | j                  rOdj                  | j                  j                               }|j                  j                  j                   d| dS |j                  j                  j                   dS )a  
        Get a human-readable reason for this model change transition.

        Override to provide more specific reasons based on model changes.

        Args:
            context: The transition context

        Returns:
            Human-readable reason string
        z createdz, z
 updated (z	 changed)z	 modified)r   r   rG   r2   r   joinkeys)r,   rB   fieldss      r-   ru   z ModelChangeTransition.get_reason  s     nn..778AAYYt22779:Fnn..778
6()TT..**334I>>r/   )r2   r3   r4   r5   r   r:   r   r   r8   r   r7   r   r<   r   r   r   listr   r   r   r   ri   r}   tupler   ru   rH   rI   s   @r-   r   r   [  s    > 16*k1NDd38n,-  e9`aKa !&% $$OT&7
N8R&S X\ 8+<Z=W+X ]a 2 [[04S%Z0@[	 [ [.?"3J4N"O ?TW ?r/   r   N)r5   abcr   r   r   typingr   r   r   r	   r
   r   django.db.modelsr   pydanticr   r   r   fsm.state_modelsr   r   r   r   rw   r@   rK   r   r>   r/   r-   <module>r      s    $  G G " 1 1* U3J-Y?N&J-.N7*	7:~+E#F 7*t%	 %{ YWZ-G%H { |J?NGJ4N,O J?r/   