@@ -3,10 +3,13 @@ use crate::prelude::*;
33
44use crate :: base:: schema:: { FieldSchema , ValueType } ;
55use crate :: base:: spec:: { NamedSpec , OutputMode , ReactiveOpSpec , SpecFormatter } ;
6- use crate :: lib_context:: { clear_lib_context, get_auth_registry, init_lib_context} ;
6+ use crate :: lib_context:: {
7+ QueryHandlerContext , clear_lib_context, get_auth_registry, init_lib_context,
8+ } ;
79use crate :: ops:: py_factory:: { PyExportTargetFactory , PyOpArgSchema } ;
810use crate :: ops:: { interface:: ExecutorFactory , py_factory:: PyFunctionFactory , register_factory} ;
911use crate :: server:: { self , ServerSettings } ;
12+ use crate :: service:: query_handler:: QueryHandlerInfo ;
1013use crate :: settings:: Settings ;
1114use crate :: setup:: { self } ;
1215use pyo3:: IntoPyObjectExt ;
@@ -430,6 +433,62 @@ impl Flow {
430433 } ;
431434 SetupChangeBundle ( Arc :: new ( bundle) )
432435 }
436+
437+ pub fn add_query_handler ( & self , name : String , handler : Py < PyAny > ) -> PyResult < ( ) > {
438+ struct PyQueryHandler {
439+ handler : Py < PyAny > ,
440+ }
441+
442+ #[ async_trait]
443+ impl crate :: service:: query_handler:: QueryHandler for PyQueryHandler {
444+ async fn query (
445+ & self ,
446+ input : crate :: service:: query_handler:: QueryInput ,
447+ flow_ctx : & interface:: FlowInstanceContext ,
448+ ) -> Result < crate :: service:: query_handler:: QueryOutput > {
449+ // Call the Python async function on the flow's event loop
450+ let result_fut = Python :: with_gil ( |py| -> Result < _ > {
451+ let handler = self . handler . clone_ref ( py) ;
452+ // Build args: pass a dict with the query input
453+ let args = pyo3:: types:: PyTuple :: new ( py, [ input. query ] ) ?;
454+ let result_coro = handler. call ( py, args, None ) . to_result_with_py_trace ( py) ?;
455+
456+ let py_exec_ctx = flow_ctx
457+ . py_exec_ctx
458+ . as_ref ( )
459+ . ok_or_else ( || anyhow ! ( "Python execution context is missing" ) ) ?;
460+ let task_locals = pyo3_async_runtimes:: TaskLocals :: new (
461+ py_exec_ctx. event_loop . bind ( py) . clone ( ) ,
462+ ) ;
463+ Ok ( pyo3_async_runtimes:: into_future_with_locals (
464+ & task_locals,
465+ result_coro. into_bound ( py) ,
466+ ) ?)
467+ } ) ?;
468+
469+ let py_obj = result_fut. await ;
470+ // Convert Python result to Rust type with proper traceback handling
471+ let output = Python :: with_gil ( |py| -> Result < _ > {
472+ let output_any = py_obj. to_result_with_py_trace ( py) ?;
473+ let output: crate :: py:: Pythonized < crate :: service:: query_handler:: QueryOutput > =
474+ output_any. extract ( py) ?;
475+ Ok ( output. into_inner ( ) )
476+ } ) ?;
477+
478+ Ok ( output)
479+ }
480+ }
481+
482+ let mut handlers = self . 0 . query_handlers . write ( ) . unwrap ( ) ;
483+ handlers. insert (
484+ name,
485+ QueryHandlerContext {
486+ info : Arc :: new ( QueryHandlerInfo { } ) ,
487+ handler : Arc :: new ( PyQueryHandler { handler } ) ,
488+ } ,
489+ ) ;
490+ Ok ( ( ) )
491+ }
433492}
434493
435494#[ pyclass]
0 commit comments