
    ci4                     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 d dl	m
Z
mZmZmZmZmZmZmZmZmZ d dlZd dlmZ d dlmZ d dlmZ d dlmZ d d	lmZmZm Z m!Z! d d
l"m#Z#m$Z$  ejJ                  e&      Z'e G d d             Z( ejR                         Z*de(fdZ+d Z,ddZ-ddZ. G d de/      Z0 G d d      Z1	 d dl2m3Z3 e3jh                  e1_5        e1jh                  e3_4        ee8e9e:e#e
f   Z;ee;   Z<ee;   Z=ee=e<f   Z> G d de      Z?y# e6$ r e'jo                  d       Y ?w xY w)    N)	dataclassfield)datetimetimezone)
AnyAsyncIterable	AwaitableCallable	CoroutineIteratorMappingOptionalSetUnion)BackgroundTask)iterate_in_threadpool)MutableHeaders)Response)ReceiveScopeSendMessage)ServerSentEventensure_bytesc                   V    e Zd ZU dZ ee      Zeej                     e
d<   dZee
d<   y)_ShutdownStatezPer-thread state for shutdown coordination.

    Issue #152 fix: Uses threading.local() instead of ContextVar to ensure
    one watcher per thread rather than one per async context.
    )default_factoryeventsFwatcher_startedN)__name__
__module____qualname____doc__r   setr   r   anyioEvent__annotations__r   bool     8/tmp/pip-target-z3e9_cxr/lib/python/sse_starlette/sse.pyr   r   !   s+      %S9FC9!OT!r*   r   returnc                  V    t        t        dd      } | t               } | t        _        | S )z4Get or create shutdown state for the current thread.shutdown_stateN)getattr_thread_stater   r.   )states    r+   _get_shutdown_stater2   1   s+    M#3T:E} ',$Lr*   c                      	 t        j                  t         j                        } t        | d      r| j                  }t        |d      r|S y# t
        $ r Y yw xY w)am  
    Try to get uvicorn Server instance via signal handler introspection.

    When uvicorn registers signal handlers, they're bound methods on the Server instance.
    We can retrieve the Server from the handler's __self__ attribute.

    Returns None if:
    - Not running under uvicorn
    - Signal handler isn't a bound method
    - Any introspection fails
    __self__should_exitN)signal	getsignalSIGTERMhasattrr4   	Exception)handlerservers     r+   _get_uvicorn_serverr=   :   s[    ""6>>27J'%%Fv}-   s   AA 	AAc                  j  K   t               } t               }	 	 t        j                  rnHt        j                  r||j                  rdt        _        nt        j                  d       d{    Yt        | j                        D ]  }|j                           	 d| _
        y7 8# d| _
        w xY ww)ag  
    Poll for shutdown and broadcast to all events in this context.

    One watcher runs per thread (event loop). Checks two shutdown sources:
    1. AppStatus.should_exit - set when our monkey-patch works
    2. uvicorn Server.should_exit - via signal handler introspection (Issue #132 fix)

    When either becomes True, signals all registered events.
    TNg      ?F)r2   r=   	AppStatusr5   enable_automatic_graceful_drainr%   sleeplistr   r$   r   )r1   uvicorn_serverevents      r+   _shutdown_watcherrE   Q   s       !E(*N&$$ 99"."..(,	%++c"""  %,,' 	EIIK	 !& # !&s/   B3AB' ,B%-/B' B3%B' '	B00B3c                      t               } | j                  s6d| _        	 t        j                         }|j	                  t                      yy# t        $ r
 d| _        Y yw xY w)zDEnsure the shutdown watcher is running for this thread (event loop).TFN)r2   r   asyncioget_running_loopcreate_taskrE   RuntimeError)r1   loops     r+   $_ensure_watcher_started_on_this_looprL   u   s^    !E   $	*++-D.01	 !
  	*$)E!	*s   -A A! A!c                       e Zd Zy)SendTimeoutErrorN)r    r!   r"   r)   r*   r+   rN   rN      s    r*   rN   c                   ^    e Zd ZU dZdZdZdZee   e	d<   e
d        Ze
d        Ze
d        Zy)	r?   z\Helper to capture a shutdown signal from Uvicorn so we can gracefully terminate SSE streams.FTNoriginal_handlerc                      dt         _        y)aJ  
        Prevent automatic SSE stream termination on server shutdown.

        WARNING: When disabled, you MUST set AppStatus.should_exit = True
        at some point during shutdown, or streams will never close and the
        server will hang indefinitely (or until uvicorn's graceful shutdown
        timeout expires).
        FNr?   r@   r)   r*   r+    disable_automatic_graceful_drainz*AppStatus.disable_automatic_graceful_drain   s     5:	1r*   c                      dt         _        y)a  
        Re-enable automatic SSE stream termination on server shutdown.

        This restores the default behavior where SIGTERM triggers immediate
        stream draining. Call this to undo a previous call to
        disable_automatic_graceful_drain().
        TNrR   r)   r*   r+   $enable_automatic_graceful_drain_modez.AppStatus.enable_automatic_graceful_drain_mode   s     59	1r*   c                      t         j                  rdt         _        t         j                  t        j                  | i | y y )NT)r?   r@   r5   rP   )argskwargss     r+   handle_exitzAppStatus.handle_exit   s7    44$(I!%%1&&77 2r*   )r    r!   r"   r#   r5   r@   rP   r   r
   r'   staticmethodrS   rU   rY   r)   r*   r+   r?   r?      sZ    fK&*#+/hx(/	: 	: 9 9 8 8r*   r?   )ServerzHUvicorn not installed. Graceful shutdown on server termination disabled.c                      e Zd ZdZdZdZ	 	 	 	 	 	 	 	 	 	 d dededee	e
e
f      de
d	ee   d
ee   dee
   deeg ef      deeg ed   f      dee   deeeged   f      ddfdZedeeef   fd       Zej,                  deeef   ddfd       Zd!deddfdZdeddfdZdeddfdZed"d       ZdeddfdZde dededdfdZ!y)#EventSourceResponsezf
    Streaming response that sends data conforming to the SSE (Server-Sent Events) specification.
       
Ncontentstatus_codeheaders
media_type
backgroundpingsepping_message_factorydata_sender_callable)NNNsend_timeoutclient_close_handler_callabler,   c                    |dvrt        d|       |xs | j                  | _        t        |t              r|| _        nt        |      | _        || _        || j                  n|| _        || _	        |	| _
        |
| _        t               }||j                  |       |j                  dd       d|d<   d|d<   | j                  |       || j                   n|| _        || _        || _        d	| _        t+        j,                         | _        y )
N)Nr_   
z'sep must be one of: \r\n, \r, \n, got: zCache-Controlzno-storez
keep-alive
ConnectionnozX-Accel-BufferingT)
ValueErrorDEFAULT_SEPARATORrf   
isinstancer   body_iteratorr   ra   rc   rd   rh   ri   r   update
setdefaultinit_headersDEFAULT_PING_INTERVALping_intervalrg   rj   activer%   Lock
_send_lock)selfr`   ra   rb   rc   rd   re   rf   rg   rh   ri   rj   _headerss                r+   __init__zEventSourceResponse.__init__   s   & 00J3%PQQ0$00 g}-!(D!6w!?D&-7-?$//Z$$8!( "#OOG$ 	OZ8!-(,$%(#;?<T77T$8!-J***,r*   c                     | j                   S N)_ping_interval)r|   s    r+   rx   z!EventSourceResponse.ping_interval  s    """r*   valuec                 t    t        |t        t        f      st        d      |dk  rt	        d      || _        y )Nzping interval must be intr   z$ping interval must be greater than 0)rr   intfloat	TypeErrorrp   r   )r|   r   s     r+   rx   z!EventSourceResponse.ping_interval  s7    %#u.78819CDD#r*   forcec                     t        d      )Nz-Compression is not supported for SSE streams.)NotImplementedError)r|   r   s     r+   enable_compressionz&EventSourceResponse.enable_compression  s    !"QRRr*   sendc                   K    |d| j                   | j                  d       d{    | j                  2 3 d{   }t        || j                        }t
        j                  d|       t        j                  | j                        5 } |d|dd       d{    ddd       st|j                  st        | j                  d      r"| j                  j                          d{    t               7 7 7 b# 1 sw Y   axY w7  6 | j                  4 d{  7   d	| _         |dd
d	d       d{  7   ddd      d{  7   y# 1 d{  7  sw Y   yxY ww)zHSend out SSE data to the client as it becomes available in the iterator.zhttp.response.start)typestatusrb   Nz	chunk: %shttp.response.bodyTr   body	more_bodyacloseFr*   )ra   raw_headersrs   r   rf   loggerdebugr%   move_on_afterri   cancel_calledr9   r   rN   r{   ry   )r|   r   datachunkcancel_scopes        r+   _stream_responsez$EventSourceResponse._stream_response  sZ    -**++
 	
 	
 ,, 	) 	)$ txx0ELLe,$$T%6%67 <15tT  
  : :4--x8,,33555&(('	
	)  6 - ?? 	X 	XDK 4cPUVWWW	X 	X 	X 	X 	Xs   $E(C:E(DC<DAE(D C>D  
E(+E(83E(+D,E(<D>D  D		E(E(D" E($E;D><EE(EE(E%EE%!E(receivec                    K   | j                   rg |        d{   }|d   dk(  rBd| _         t        j                  d       | j                  r| j                  |       d{    y| j                   rfyy7 ]7 w)z/Watch for a disconnect message from the client.Nr   zhttp.disconnectFz+Got event: http.disconnect. Stop streaming.)ry   r   r   rj   )r|   r   messages      r+   _listen_for_disconnectz*EventSourceResponse._listen_for_disconnect+  sj     kk#IoGv"33#JK55<<WEEE kk%
 Fs(   A;A7AA;"A9#A;5A;9A;c                    K   t         j                  ryt                t               } t	        j
                         }| j                  j                  |       	 t         j                  r	 | j                  j                  |       y|j                          d{    | j                  j                  |       y7  # | j                  j                  |       w xY ww)z0Wait for shutdown signal via the shared watcher.N)
r?   r5   rL   r2   r%   r&   r   adddiscardwait)r1   rD   s     r+   _listen_for_exit_signalz+EventSourceResponse._listen_for_exit_signal6  s        ,.#%	($$ LL  ' **,LL  ' LL  's<   ACB; )CB; B9B; C9B; ;CCc                 X  K   | j                   rt        j                  | j                         d{    | j                  r| j	                         n:t        dt        j                  t        j                         | j                        }t        || j                        }t        j                  d|       | j                  4 d{    | j                   r |d|dd       d{    ddd      d{    | j                   ryy7 7 E7 '7 # 1 d{  7  sw Y   )xY ww)zPeriodically send ping messages to keep the connection alive on proxies.
        - frequenccy ca every 15 seconds.
        - Alternatively one can send periodically a comment line (one starting with a ':' character)
        Nzping - )commentrf   zping: %sr   Tr   )ry   r%   rA   r   rg   r   r   nowr   utcrf   r   r   r   r{   )r|   r   sse_ping
ping_bytess       r+   _pingzEventSourceResponse._pingJ  s     
 kk++d11222 ,, ))+$%hll8<<&@%AB  &h9JLLZ0  ;;$8$.)-    kk2   sp   /D*DBD*	D
D*D)D*D.D*9D:D*D*D*DD*D'DD'#D*scopec                 (   K   t        j                         4 d{   dt        g t        d   f   ffd}j	                  | fd       j	                  | fd       j	                  | j
                          j                  rj	                   j                         j	                  | fd       ddd      d{     j                   j                          d{    yy7 7 ,# 1 d{  7  sw Y   <xY w7 w)a  Entrypoint for Starlette's ASGI contract. We spin up tasks:
        - _stream_response to push events
        - _ping to keep the connection alive
        - _listen_for_exit_signal to respond to server shutdown
        - _listen_for_disconnect to respond to client disconnect
        Ncoroc                 d   K    |         d {    j                   j                          y 7 wr   )r   cancel)r   
task_groups    r+   cancel_on_finishz6EventSourceResponse.__call__.<locals>.cancel_on_finishn  s'     f''..0 s   0. 0c                  &     j                        S r   )r   r|   r   s   r+   <lambda>z.EventSourceResponse.__call__.<locals>.<lambda>r  s    D<Q<QRV<W r*   c                  &     j                        S r   )r   r   s   r+   r   z.EventSourceResponse.__call__.<locals>.<lambda>s  s    DJJt<L r*   c                  &    j                         S r   )r   )r   r|   s   r+   r   z.EventSourceResponse.__call__.<locals>.<lambda>{  s    $*E*Eg*N r*   )r%   create_task_groupr
   r	   
start_soonr   rh   rd   )r|   r   r   r   r   r   s   ` `` @r+   __call__zEventSourceResponse.__call__e  s      **, 	 	
1Xb)D/6I-J 1 !!"24WX!!"24LM!!"2D4P4PQ((%%d&?&?@ !! "N	 	$ ??&//### '%	 	 	 	 	& $sQ   DC7DBC;DC9#D0D1D9D;DDD	D)
   Nztext/event-streamNNNNNNN)Fr,   N)"r    r!   r"   r#   rw   rq   ContentStreamr   r   r   strr   r
   r   r   r   r   r	   r~   propertyr   rx   setterr(   r   r   r   r   r   rZ   r   r   r   r   r)   r*   r+   r]   r]      s    
 /3-/3"!HL (, !;';' ;' '#s(+,	;'
 ;' ^,;' sm;' c];' 'xO0C'DE;' 'R#3445
;' uo;' (0gY	$/0(
;'" 
#;'z #uS%Z0 # # $5e#4 $ $ $S S SX4 XD X6	G 	 	 ( (&  6$E $G $4 $D $r*   r]   r   )@rG   loggingr6   	threadingdataclassesr   r   r   r   typingr   r   r	   r
   r   r   r   r   r   r   r%   starlette.backgroundr   starlette.concurrencyr   starlette.datastructuresr   starlette.responsesr   starlette.typesr   r   r   r   sse_starlette.eventr   r   	getLoggerr    r   r   localr0   r2   r=   rE   rL   TimeoutErrorrN   r?   uvicorn.mainr[   rY   rP   ImportErrorr   r   bytesdictContentSyncContentStreamAsyncContentStreamr   r]   r)   r*   r+   <module>r      s@       ( '    / 7 3 ( 9 9 = 
		8	$ " " "  	!^ .!&H
*	| 	#8 #8L#!'!3!3I"..F UD/36
7W% "7+ (*;;<C$( C$  
LLRs   (D D*)D*