@@ -562,8 +562,119 @@ static PyObject *py_loader_port_invoke(PyObject *self, PyObject *var_args)
562562 return result ;
563563}
564564
565- // TODO
566- #if 0
565+ /* Context passed to resolve/reject callbacks for await */
566+ typedef struct py_loader_port_await_context_type
567+ {
568+ loader_impl_py py_impl ;
569+ PyObject * future ;
570+ } py_loader_port_await_context ;
571+
572+ static void * py_loader_port_await_resolve (void * result , void * data )
573+ {
574+ py_loader_port_await_context * ctx = (py_loader_port_await_context * )data ;
575+ loader_impl impl = loader_get_impl (py_loader_tag );
576+
577+ py_loader_thread_acquire ();
578+
579+ /* Convert metacall value to Python object */
580+ PyObject * py_result = NULL ;
581+
582+ if (result != NULL )
583+ {
584+ py_result = py_loader_impl_value_to_capi (impl , value_type_id (result ), result );
585+ }
586+
587+ if (py_result == NULL )
588+ {
589+ Py_IncRef (Py_None );
590+ py_result = Py_None ;
591+ }
592+
593+ /* Get thread background module and asyncio loop */
594+ PyObject * thread_bg_module = py_loader_impl_get_thread_background_module (ctx -> py_impl );
595+ PyObject * asyncio_loop = py_loader_impl_get_asyncio_loop (ctx -> py_impl );
596+
597+ /* Call future_resolve(tl, future, value) */
598+ PyObject * future_resolve_func = PyObject_GetAttrString (thread_bg_module , "future_resolve" );
599+
600+ if (future_resolve_func != NULL && PyCallable_Check (future_resolve_func ))
601+ {
602+ PyObject * args = PyTuple_Pack (3 , asyncio_loop , ctx -> future , py_result );
603+ PyObject * call_result = PyObject_Call (future_resolve_func , args , NULL );
604+
605+ Py_XDecRef (call_result );
606+ Py_DecRef (args );
607+ Py_DecRef (future_resolve_func );
608+ }
609+
610+ Py_DecRef (py_result );
611+ Py_DecRef (ctx -> future );
612+
613+ py_loader_thread_release ();
614+
615+ free (ctx );
616+
617+ return NULL ;
618+ }
619+
620+ static void * py_loader_port_await_reject (void * result , void * data )
621+ {
622+ py_loader_port_await_context * ctx = (py_loader_port_await_context * )data ;
623+ loader_impl impl = loader_get_impl (py_loader_tag );
624+
625+ py_loader_thread_acquire ();
626+
627+ /* Convert to Python exception object */
628+ PyObject * py_exception = NULL ;
629+
630+ if (result != NULL )
631+ {
632+ py_exception = py_loader_impl_value_to_capi (impl , value_type_id (result ), result );
633+ }
634+
635+ if (py_exception == NULL )
636+ {
637+ py_exception = PyExc_RuntimeErrorPtr ();
638+ Py_IncRef (py_exception );
639+ }
640+
641+ /* Create an Exception instance if we got a string or other value */
642+ if (!PyExceptionInstance_Check (py_exception ) && !PyExceptionClass_Check (py_exception ))
643+ {
644+ PyObject * exc_args = PyTuple_Pack (1 , py_exception );
645+ PyObject * new_exc = PyObject_Call (PyExc_RuntimeErrorPtr (), exc_args , NULL );
646+ Py_DecRef (exc_args );
647+ Py_DecRef (py_exception );
648+ py_exception = new_exc ;
649+ }
650+
651+ /* Get thread background module and asyncio loop */
652+ PyObject * thread_bg_module = py_loader_impl_get_thread_background_module (ctx -> py_impl );
653+ PyObject * asyncio_loop = py_loader_impl_get_asyncio_loop (ctx -> py_impl );
654+
655+ /* Call future_reject(tl, future, exception) */
656+ PyObject * future_reject_func = PyObject_GetAttrString (thread_bg_module , "future_reject" );
657+
658+ if (future_reject_func != NULL && PyCallable_Check (future_reject_func ))
659+ {
660+ PyObject * args = PyTuple_Pack (3 , asyncio_loop , ctx -> future , py_exception );
661+ PyObject * call_result = PyObject_Call (future_reject_func , args , NULL );
662+
663+ Py_XDecRef (call_result );
664+ Py_DecRef (args );
665+ Py_DecRef (future_reject_func );
666+ }
667+
668+ Py_DecRef (py_exception );
669+ Py_DecRef (ctx -> future );
670+
671+ py_loader_thread_release ();
672+
673+ free (ctx );
674+
675+ return NULL ;
676+ }
677+
567678static PyObject * py_loader_port_await (PyObject * self , PyObject * var_args )
568679{
569680 PyObject * name , * result = NULL ;
@@ -573,38 +684,50 @@ static PyObject *py_loader_port_await(PyObject *self, PyObject *var_args)
573684 size_t args_size = 0 , args_count ;
574685 Py_ssize_t var_args_size ;
575686 loader_impl impl ;
687+ loader_impl_py py_impl ;
576688
577689 (void )self ;
578690
579691 /* Obtain Python loader implementation */
580692 impl = loader_get_impl (py_loader_tag );
693+ py_impl = loader_impl_get (impl );
694+
695+ /* Check if asyncio is initialized */
696+ PyObject * asyncio_loop = py_loader_impl_get_asyncio_loop (py_impl );
697+
698+ if (asyncio_loop == NULL )
699+ {
700+ PyErr_SetString (PyExc_RuntimeErrorPtr (), "Asyncio loop not initialized. Cannot use metacall_await." );
701+ return Py_ReturnNone ();
702+ }
581703
582704 var_args_size = PyTuple_Size (var_args );
583705
584706 if (var_args_size == 0 )
585707 {
586- PyErr_SetString (PyExc_TypeErrorPtr (), "Invalid number of arguments, use it like: metacall ('function_name', 'asd', 123, [7, 4] );" );
708+ PyErr_SetString (PyExc_TypeErrorPtr (), "Invalid number of arguments, use it like: metacall_await ('function_name', arg1, arg2, ... );" );
587709 return Py_ReturnNone ();
588710 }
589711
712+ /* Get function name */
590713 name = PyTuple_GetItem (var_args , 0 );
591714
592- #if PY_MAJOR_VERSION == 2
715+ #if PY_MAJOR_VERSION == 2
593716 {
594717 if (!(PyString_Check (name ) && PyString_AsStringAndSize (name , & name_str , & name_length ) != -1 ))
595718 {
596719 name_str = NULL ;
597720 }
598721 }
599- #elif PY_MAJOR_VERSION == 3
722+ #elif PY_MAJOR_VERSION == 3
600723 {
601724 name_str = PyUnicode_Check (name ) ? (char * )PyUnicode_AsUTF8AndSize (name , & name_length ) : NULL ;
602725 }
603- #endif
726+ #endif
604727
605728 if (name_str == NULL )
606729 {
607- PyErr_SetString (PyExc_TypeErrorPtr (), "Invalid function name string conversion, first parameter must be a string" );
730+ PyErr_SetString (PyExc_TypeErrorPtr (), "First parameter must be a string (function name) " );
608731 return Py_ReturnNone ();
609732 }
610733
@@ -618,7 +741,7 @@ static PyObject *py_loader_port_await(PyObject *self, PyObject *var_args)
618741
619742 if (value_args == NULL )
620743 {
621- PyErr_SetString (PyExc_ValueErrorPtr (), "Invalid argument allocation " );
744+ PyErr_SetString (PyExc_MemoryErrorPtr (), "Failed to allocate arguments " );
622745 return Py_ReturnNone ();
623746 }
624747
@@ -631,57 +754,103 @@ static PyObject *py_loader_port_await(PyObject *self, PyObject *var_args)
631754 }
632755 }
633756
634- /* Execute the await */
757+ /* Create Python Future */
758+ PyObject * thread_bg_module = py_loader_impl_get_thread_background_module (py_impl );
759+ PyObject * future_create_func = PyObject_GetAttrString (thread_bg_module , "future_create" );
760+
761+ if (future_create_func == NULL || !PyCallable_Check (future_create_func ))
635762 {
636- void * ret ;
763+ PyErr_SetString (PyExc_RuntimeErrorPtr (), "Failed to get future_create function" );
764+ Py_XDecRef (future_create_func );
765+ goto cleanup_args ;
766+ }
637767
638- py_loader_thread_release ();
768+ PyObject * future_args = PyTuple_Pack (1 , asyncio_loop );
769+ PyObject * future = PyObject_Call (future_create_func , future_args , NULL );
770+ Py_DecRef (future_args );
771+ Py_DecRef (future_create_func );
639772
640- /* TODO: */
641- /*
642- if (value_args != NULL)
643- {
644- ret = metacallv_s(name_str, value_args, args_size);
645- }
646- else
773+ if (future == NULL )
774+ {
775+ if (PyErr_Occurred () == NULL )
647776 {
648- ret = metacallv_s(name_str, metacall_null_args, 0 );
777+ PyErr_SetString ( PyExc_RuntimeErrorPtr (), "Failed to create Future" );
649778 }
650- */
779+ goto cleanup_args ;
780+ }
651781
652- py_loader_thread_acquire ();
782+ /* Create callback context */
783+ py_loader_port_await_context * ctx = (py_loader_port_await_context * )malloc (sizeof (py_loader_port_await_context ));
653784
654- if (ret == NULL )
655- {
656- result = Py_ReturnNone ();
657- goto clear ;
658- }
785+ if (ctx == NULL )
786+ {
787+ Py_DecRef (future );
788+ PyErr_SetString (PyExc_MemoryErrorPtr (), "Failed to allocate context" );
789+ goto cleanup_args ;
790+ }
659791
660- result = py_loader_impl_value_to_capi (impl , value_type_id (ret ), ret );
792+ ctx -> py_impl = py_impl ;
793+ ctx -> future = future ;
794+ Py_IncRef (future ); /* Keep reference for callback */
661795
662- value_type_destroy (ret );
796+ /* Execute the await call */
797+ py_loader_thread_release ();
663798
664- if (result == NULL )
799+ void * ret = metacall_await_s (
800+ name_str ,
801+ value_args != NULL ? value_args : metacall_null_args ,
802+ args_size ,
803+ py_loader_port_await_resolve ,
804+ py_loader_port_await_reject ,
805+ ctx );
806+
807+ py_loader_thread_acquire ();
808+
809+ /* Check for immediate errors (e.g., function not found) */
810+ if (ret != NULL && value_type_id (ret ) == TYPE_THROWABLE )
811+ {
812+ PyObject * error = py_loader_impl_value_to_capi (impl , TYPE_THROWABLE , ret );
813+ if (error != NULL )
665814 {
666- result = Py_ReturnNone ( );
667- goto clear ;
815+ PyErr_SetObject ( PyExc_RuntimeErrorPtr (), error );
816+ Py_DecRef ( error ) ;
668817 }
818+ else
819+ {
820+ PyErr_SetString (PyExc_RuntimeErrorPtr (), "Async call failed" );
821+ }
822+ Py_DecRef (future );
823+ Py_DecRef (ctx -> future );
824+ free (ctx );
825+ value_type_destroy (ret );
826+ result = Py_ReturnNone ();
827+ goto cleanup_args ;
669828 }
670829
671- clear :
830+ if (ret != NULL )
831+ {
832+ value_type_destroy (ret );
833+ }
834+
835+ result = future ;
836+
837+ cleanup_args :
672838 if (value_args != NULL )
673839 {
840+ py_loader_thread_release ();
841+
674842 for (args_count = 0 ; args_count < args_size ; ++ args_count )
675843 {
676844 value_type_destroy (value_args [args_count ]);
677845 }
678846
847+ py_loader_thread_acquire ();
848+
679849 free (value_args );
680850 }
681851
682852 return result ;
683853}
684- #endif
685854
686855static PyObject * py_loader_port_inspect (PyObject * self , PyObject * args )
687856{
@@ -925,6 +1094,8 @@ static PyMethodDef metacall_methods[] = {
9251094 "Get information about all loaded objects." },
9261095 { "metacall" , py_loader_port_invoke , METH_VARARGS ,
9271096 "Call a function anonymously." },
1097+ { "metacall_await" , py_loader_port_await , METH_VARARGS ,
1098+ "Call an async function and return a Future." },
9281099 { "metacall_value_create_ptr" , py_loader_port_value_create_ptr , METH_VARARGS ,
9291100 "Create a new value of type Pointer." },
9301101 { "metacall_value_reference" , py_loader_port_value_reference , METH_VARARGS ,
0 commit comments