11import asyncio
2+ import contextvars
23import hashlib
34import inspect
45import json
1415from typing_extensions import deprecated
1516from unstructured_ingest .data_types .file_data import BatchFileData , FileData , file_data_from_dict
1617from unstructured_ingest .error import UnstructuredIngestError
18+ from utic_invocation_settings import add_metadata_route , install_invocation_envelope
1719from uvicorn .config import LOG_LEVELS
1820from uvicorn .importer import import_from_string
1921
@@ -67,7 +69,13 @@ async def invoke_func(func: Callable, kwargs: Optional[dict[str, Any]] = None) -
6769 if inspect .iscoroutinefunction (func ):
6870 return await func (** kwargs )
6971 else :
70- return await asyncio .get_event_loop ().run_in_executor (None , partial (func , ** kwargs ))
72+ # run_in_executor does not propagate contextvars, so without copying the context a sync
73+ # plugin would observe request-scoped bindings (current_invocation_settings and friends)
74+ # as absent and could take an unintended fallback path.
75+ ctx = contextvars .copy_context ()
76+ return await asyncio .get_event_loop ().run_in_executor (
77+ None , ctx .run , partial (func , ** kwargs )
78+ )
7179
7280
7381def check_precheck_func (precheck_func : Callable ):
@@ -117,9 +125,15 @@ def wrap_in_fastapi(
117125 func : Callable ,
118126 plugin_id : str ,
119127 precheck_func : Optional [Callable ] = None ,
128+ invoke_with_sealed_dag_node_settings : bool = False ,
120129) -> FastAPI :
121130 try :
122- return _wrap_in_fastapi (func = func , plugin_id = plugin_id , precheck_func = precheck_func )
131+ return _wrap_in_fastapi (
132+ func = func ,
133+ plugin_id = plugin_id ,
134+ precheck_func = precheck_func ,
135+ invoke_with_sealed_dag_node_settings = invoke_with_sealed_dag_node_settings ,
136+ )
123137 except Exception as e :
124138 logger .error (f"failed to wrap function in FastAPI: { e } " , exc_info = True )
125139 raise EtlApiException (e ) from e
@@ -129,6 +143,7 @@ def _wrap_in_fastapi(
129143 func : Callable ,
130144 plugin_id : str ,
131145 precheck_func : Optional [Callable ] = None ,
146+ invoke_with_sealed_dag_node_settings : bool = False ,
132147) -> FastAPI :
133148 if precheck_func is not None :
134149 check_precheck_func (precheck_func = precheck_func )
@@ -347,6 +362,19 @@ async def get_id() -> str:
347362 except TypeError as e :
348363 raise TypeError (f"failed to validate function schema: { e } " ) from e
349364
365+ # The middleware handles the reserved /invoke fields (invocation_settings and
366+ # invocation_context) outside the generated handler schema. It resolves sealed settings with
367+ # the configured private key and exposes both values through request-scoped accessors. The
368+ # sealed-settings capability remains opt-in because it asserts that the wrapped function
369+ # consumes current_invocation_settings(), not merely that the host can resolve it. Repeated
370+ # installation is safe: the middleware installs once and the last /metadata registration wins.
371+ add_metadata_route (
372+ fastapi_app ,
373+ identifier = plugin_id ,
374+ invoke_with_sealed_dag_node_settings = invoke_with_sealed_dag_node_settings ,
375+ )
376+ install_invocation_envelope (fastapi_app )
377+
350378 FastAPIInstrumentor .instrument_app (
351379 fastapi_app , tracer_provider = get_trace_provider (), meter_provider = get_metric_provider ()
352380 )
@@ -361,6 +389,7 @@ def generate_fast_api(
361389 id_method : Optional [str ] = None ,
362390 precheck_str : Optional [str ] = None ,
363391 precheck_method : Optional [str ] = None ,
392+ invoke_with_sealed_dag_node_settings : bool = False ,
364393) -> FastAPI :
365394 instance = import_from_string (app )
366395 func = get_func (instance , method_name )
@@ -379,4 +408,9 @@ def generate_fast_api(
379408 elif precheck_method :
380409 precheck_func = get_func (instance , precheck_method )
381410
382- return wrap_in_fastapi (func = func , plugin_id = plugin_id , precheck_func = precheck_func )
411+ return wrap_in_fastapi (
412+ func = func ,
413+ plugin_id = plugin_id ,
414+ precheck_func = precheck_func ,
415+ invoke_with_sealed_dag_node_settings = invoke_with_sealed_dag_node_settings ,
416+ )
0 commit comments