XjRLzdZddlZddlZddlZddlZddlmZmZddlmZm Z ddl m Z m Z m Z mZmZmZmZmZmZmZddlZddlmZddlmZddlmZdd lmZdd lmZm Z m!Z!m"Z"dd l#m$Z$dd l%m&Z&m'Z'ejPe)Z*eGd dZ+ejXZ-de+fdZ.dZ/ddZ0ddZ1de!de!fdZ2Gdde3Z4GddZ5 ddl6m7Z7e7jpe5_9e5jpe7_8eee&e fZ?ee?Z@e e?ZAeeAe@fZBGddeZCy#e:$re*jwdY?wxYw)uServer-Sent Events response for Starlette / FastAPI. Intentional divergence from ``starlette.responses.StreamingResponse`` -------------------------------------------------------------------- ``EventSourceResponse`` is modelled on Starlette's ``StreamingResponse`` and re-syncs most of its behaviour (WebSocket denial, ``collapse_excgroups()`` around the task group, ``memoryview`` chunk handling). The following points are deliberate divergences — DO NOT "fix" them without reading the rationale: 1. ASGI ``spec_version >= 2.4`` fast path is NOT adopted. Upstream short-circuits to ``await stream_response(send)`` and converts ``OSError`` into ``ClientDisconnect``, skipping ``listen_for_disconnect``. We keep ``_listen_for_disconnect`` running because it (a) invokes ``client_close_handler_callable`` on disconnect, (b) flips ``self.active = False`` so ``_ping`` and the cooperative shutdown grace loop exit promptly. Adopting the upstream fast path would regress both features. 2. ``_wrap_websocket_denial_send`` is inlined in this module rather than inherited from ``starlette.responses.Response``. The helper landed on Starlette ``main`` after our minimum pin (``starlette>=0.41.3``); inline until the floor moves past the release that contains it. 3. ``collapse_excgroups()`` is vendored in ``sse_starlette._utils`` rather than imported from ``starlette._utils`` (private module). N) dataclassfield)datetimetimezone) Any AsyncIterable AwaitableCallable CoroutineIteratorMappingOptionalSetUnion)BackgroundTask)iterate_in_threadpool)MutableHeaders)Response)ReceiveScopeSendMessage)collapse_excgroups)ServerSentEvent ensure_bytescVeZdZUdZeeZeeje d<dZ 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_factoryeventsFwatcher_startedN) __name__ __module__ __qualname____doc__rsetrranyioEvent__annotations__r boole/mnt/ssd/data/Dropbox/adrian/vault-secondbrain/venv/lib/python3.12/site-packages/sse_starlette/sse.pyrr>s+ %S9FC 9!OT!r+rreturncVttdd}|t}|t_|S)z4Get or create shutdown state for the current thread.shutdown_stateN)getattr _thread_staterr/)states r,_get_shutdown_stater3Ns+ M#3T :E } ', $ Lr+c tjtj}t|dr|j}t|dr|Sy#t $rYywxYw)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 getsignalSIGTERMhasattrr5 Exception)handlerservers r,_get_uvicorn_serverr>Ws[ ""6>>2 7J '%%Fv}-      sAA AAcjKt}t} tjrnHtjr||jr dt_nt j dd{Yt|jD]}|j d|_ y78#d|_ wxYww)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) r3r> AppStatusr6enable_automatic_graceful_drainr&sleeplistrr%r )r2uvicorn_serverevents r,_shutdown_watcherrFns !E(*N&$$99"."..(, %++c" " "%,,' E IIK !& #!&s/B3AB',B%-/B'B3%B'' B00B3ct}|js6d|_ tj}|j t yy#t $r d|_YywxYw)zDEnsure the shutdown watcher is running for this thread (event loop).TFN)r3r asyncioget_running_loop create_taskrF RuntimeError)r2loops r,$_ensure_watcher_started_on_this_looprMs^  !E  $ *++-D   .0 1 !  *$)E ! *s-AA! A!sendc$dtddffd }|S)a;Mirror of ``starlette.responses.Response._wrap_websocket_denial_send``. Divergence #2 (see module docstring): inlined because the helper landed on Starlette ``main`` (commit 9ee9519) after our minimum pin ``starlette>=0.41.3``. Drop this once the floor moves past the release that contains it. messager-NcZK|d}|dvr i|dd|zi}|d{y7w)Ntype>http.response.bodyhttp.response.startz websocket.r*)rP message_typerNs r,wrappedz,_wrap_websocket_denial_send..wrappeds<v H HFF&,*EFG7ms +)+)r)rNrVs` r,_wrap_websocket_denial_sendrWsw4 Nr+c eZdZy)SendTimeoutErrorN)r!r"r#r*r+r,rYrYsr+rYc^eZdZUdZdZdZdZeee d<e dZ e dZ e dZ y) r@z\Helper to capture a shutdown signal from Uvicorn so we can gracefully terminate SSE streams.FTNoriginal_handlercdt_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). FNr@rAr*r+r, disable_automatic_graceful_drainz*AppStatus.disable_automatic_graceful_drains 5: 1r+cdt_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(). TNr]r*r+r,$enable_automatic_graceful_drain_modez.AppStatus.enable_automatic_graceful_drain_modes 59 1r+ctjr dt_tjtj|i|yy)NT)r@rAr6r[)argskwargss r, handle_exitzAppStatus.handle_exits7  4 4$(I !  % % 1  & & 7 7 2r+)r!r"r#r$r6rAr[rr r( staticmethodr^r`rdr*r+r,r@r@sZfK&*#+/hx(/ : :9988r+r@)ServerzHUvicorn not installed. Graceful shutdown on server termination disabled.ceZdZdZdZdZ d#dededee e e fde d ee d eed ee d ee ge fd ee gedfdeedee egedfdeej&deddfdZedeeeffdZej0deeefddfdZd$deddfdZdeddfdZdeddfdZed%dZ d%dZ!deddfd Z"d!e#dededdfd"Z$y)&EventSourceResponseagStreaming response implementing the SSE (Server-Sent Events) specification. Args: content: Async iterable or sync iterator yielding SSE event data. status_code: HTTP status code. Default: 200. headers: Additional HTTP headers. media_type: Response media type. Default: "text/event-stream". background: Background task to run after response completes. ping: Ping interval in seconds (0 to disable). Default: 15. sep: Line separator for SSE messages ("\r\n", "\r", or "\n"). ping_message_factory: Callable returning custom ping ServerSentEvent. data_sender_callable: Async callable for push-based data sending. send_timeout: Timeout in seconds for individual send operations. client_close_handler_callable: Async callback on client disconnect. shutdown_event: Optional ``anyio.Event`` set by the library when server shutdown is detected. Generators can watch this event to send farewell messages and exit cooperatively instead of receiving CancelledError. shutdown_grace_period: Seconds to wait after setting ``shutdown_event`` before force-cancelling the generator. Must be >= 0. Should be less than your ASGI server's graceful shutdown timeout. Default: 0 (immediate cancel, identical to pre-v3.3.0 behavior).  Ncontent status_codeheaders media_type backgroundpingsepping_message_factorydata_sender_callable)NNN send_timeoutclient_close_handler_callableshutdown_eventshutdown_grace_periodr-cZ|dvrtd||xs |j|_t|tr||_nt ||_||_| |jn||_||_ | |_ | |_ t}||j||jddd|d<d|d<|j|| |j n||_||_| |_| d kr td | |_| |_d |_t/j0|_y) N)Nrj  z'sep must be one of: \r\n, \r, \n, got: z Cache-Controlzno-storez keep-alive ConnectionnozX-Accel-Bufferingrz"shutdown_grace_period must be >= 0T) ValueErrorDEFAULT_SEPARATORrq isinstancer body_iteratorrrlrnrorsrtrupdate setdefault init_headersDEFAULT_PING_INTERVAL ping_intervalrrru_shutdown_event_shutdown_grace_periodactiver&Lock _send_lock)selfrkrlrmrnrorprqrrrsrtrurvrw_headerss r,__init__zEventSourceResponse.__init__s3* 0 0J3%PQ Q0$00 g} -!(D !6w!?D &-7-?$//Z$$8!("#   OOG $ OZ8!-(,$% (#;?C,C>A E C0C.C0 E+ E8#EC<E,C>.C00C9 5E>EDEE+D.,E1 E<D?=EE E EEreceivecK|jrg|d{}|ddk(rBd|_tjd|jr|j|d{y|jrfyy7]7w)uqWatch for a disconnect message from the client. Divergence #1 (see module docstring): kept unconditionally instead of adopting Starlette's ASGI 2.4 ``OSError → ClientDisconnect`` fast path, because this loop drives ``client_close_handler_callable`` and flips ``self.active = False`` for ``_ping`` and the shutdown grace loop. NrRzhttp.disconnectFz+Got event: http.disconnect. Stop streaming.)rrrru)rrrPs r,_listen_for_disconnectz*EventSourceResponse._listen_for_disconnectzslkk#IoGv"33#  JK55<.cancel_on_finishs'&LL++224!s 0. 0c&jSr)rrrNsr,z.EventSourceResponse.__call__..sd.C.CD.Ir+c&jSr)rrsr,rz.EventSourceResponse.__call__..s 4@Pr+c&jSr)r)rrsr,rz.EventSourceResponse.__call__..sd.I.I'.Rr+) rWrr&create_task_groupr r start_soonrrsro)rrrrNrrs` `` @r,__call__zEventSourceResponse.__call__s2 =K '.t4D  ! ..0  J5"io:M1N5%%$&I%%&68PQ%%$d&M&M,,))$*C*CD%%$&R%   . ?? &//# # # '-       0 $soED6DD6BD! D6*D+D6/'EEED6D6!D3 'D*(D3 /D66D?;E) Nztext/event-streamNNNNNNNNr)Fr-N)%r!r"r#r$rr~ ContentStreamrrr strrr rr rrr r&r'rpropertyrrsetterr)rrrrrrerrrrrr*r+r,rhrhs&. /3-/3"!HL (, 04'(%G'G'G''#s(+, G'  G' ^, G'smG'c]G''xO0C'DEG'' R#344 5 G'uoG'(0 gY $/ 0( G'"!-#G'$ %%G'& 'G'R#uS%Z0##$5e#4$$$SSSX4XDX8G"((&+(6($E($G($4($D($r+rhr)Dr$rHloggingr7 threading dataclassesrrrrtypingrrr r r r r rrrr&starlette.backgroundrstarlette.concurrencyrstarlette.datastructuresrstarlette.responsesrstarlette.typesrrrrsse_starlette._utilsrsse_starlette.eventrr getLoggerr!rrlocalr1r3r>rFrMrW TimeoutErrorrYr@ uvicorn.mainrfrdr[ ImportErrorrrbytesdictContentSyncContentStreamAsyncContentStreamrrhr*r+r,rs]8 ('    /73(993=   8 $ "" " ! ^.!&H *dt$ | #8#8L#!'!3!3I"..F UD/36 7W%"7+(*;;< K$(K$ LLRs(D!!D:9D: