diff --git a/core/include_internal/ten_runtime/binding/python/common/error.h b/core/include_internal/ten_runtime/binding/python/common/error.h index 307a4289e4..7102d9d2c8 100644 --- a/core/include_internal/ten_runtime/binding/python/common/error.h +++ b/core/include_internal/ten_runtime/binding/python/common/error.h @@ -11,6 +11,29 @@ #include #include "include_internal/ten_runtime/binding/python/common/python_stuff.h" +#include "ten_utils/lib/error.h" + +typedef struct ten_py_error_t { + PyObject_HEAD + + ten_error_t c_error; +} ten_py_error_t; + +TEN_RUNTIME_PRIVATE_API PyTypeObject *ten_py_error_py_type(void); + +TEN_RUNTIME_PRIVATE_API bool ten_py_error_init_for_module(PyObject *module); + +TEN_RUNTIME_PRIVATE_API ten_py_error_t *ten_py_error_wrap(ten_error_t *error); + +TEN_RUNTIME_PRIVATE_API void ten_py_error_invalidate(ten_py_error_t *error); + +TEN_RUNTIME_PRIVATE_API void ten_py_error_destroy(PyObject *self); + +TEN_RUNTIME_PRIVATE_API PyObject *ten_py_error_get_errno(PyObject *self, + PyObject *args); + +TEN_RUNTIME_PRIVATE_API PyObject *ten_py_error_get_errmsg(PyObject *self, + PyObject *args); TEN_RUNTIME_PRIVATE_API bool ten_py_check_and_clear_py_error(void); diff --git a/core/src/ten_runtime/binding/python/interface/ten/__init__.py b/core/src/ten_runtime/binding/python/interface/ten/__init__.py index 95e3d17f80..19808e5ff4 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/__init__.py +++ b/core/src/ten_runtime/binding/python/interface/ten/__init__.py @@ -18,6 +18,7 @@ from .data import Data from .log_level import LogLevel from .test import ExtensionTester, TenEnvTester +from .error import TenError # Specify what should be imported when a user imports * from the # ten_runtime_python package. @@ -41,4 +42,5 @@ "LogLevel", "ExtensionTester", "TenEnvTester", + "TenError", ] diff --git a/core/src/ten_runtime/binding/python/interface/ten/async_ten_env.py b/core/src/ten_runtime/binding/python/interface/ten/async_ten_env.py index 906262d369..3a32541f82 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/async_ten_env.py +++ b/core/src/ten_runtime/binding/python/interface/ten/async_ten_env.py @@ -4,13 +4,17 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from asyncio import AbstractEventLoop import asyncio import threading +from asyncio import AbstractEventLoop +from typing import AsyncGenerator + from .cmd import Cmd +from .data import Data +from .video_frame import VideoFrame +from .audio_frame import AudioFrame from .cmd_result import CmdResult from .ten_env import TenEnv -from typing import AsyncGenerator class AsyncTenEnv(TenEnv): @@ -30,12 +34,17 @@ async def send_cmd(self, cmd: Cmd) -> CmdResult: q = asyncio.Queue(maxsize=1) self._internal.send_cmd( cmd, - lambda _, result: asyncio.run_coroutine_threadsafe( - q.put(result), self._ten_loop - ), + lambda _, result, error: asyncio.run_coroutine_threadsafe( + q.put([result, error]), + self._ten_loop, + ), # type: ignore False, ) - result: CmdResult = await q.get() + + [result, error] = await q.get() + if error is not None: + raise Exception(error.err_msg()) + assert result.is_completed() return result @@ -43,19 +52,94 @@ async def send_cmd_ex(self, cmd: Cmd) -> AsyncGenerator[CmdResult, None]: q = asyncio.Queue(maxsize=10) self._internal.send_cmd( cmd, - lambda _, result: asyncio.run_coroutine_threadsafe( - q.put(result), self._ten_loop - ), + lambda _, result, error: asyncio.run_coroutine_threadsafe( + q.put([result, error]), + self._ten_loop, + ), # type: ignore True, ) while True: - result: CmdResult = await q.get() - if result.is_completed(): + [result, error] = await q.get() + if error is not None: + raise Exception(error.err_msg()) + else: + if result.is_completed(): + yield result + # This is the final result, so break the while loop. + break yield result - # This is the final result, so break the while loop. - break - yield result + + async def send_data(self, data: Data) -> None: + q = asyncio.Queue(maxsize=1) + self._internal.send_data( + data, + lambda _, error: asyncio.run_coroutine_threadsafe( + q.put(error), + self._ten_loop, + ), # type: ignore + ) + + error = await q.get() + if error is not None: + raise Exception(error.err_msg()) + + async def send_video_frame(self, video_frame: VideoFrame) -> None: + q = asyncio.Queue(maxsize=1) + self._internal.send_video_frame( + video_frame, + lambda _, error: asyncio.run_coroutine_threadsafe( + q.put(error), + self._ten_loop, + ), # type: ignore + ) + + error = await q.get() + if error is not None: + raise Exception(error.err_msg()) + + async def send_audio_frame(self, audio_frame: AudioFrame) -> None: + q = asyncio.Queue(maxsize=1) + self._internal.send_audio_frame( + audio_frame, + lambda _, error: asyncio.run_coroutine_threadsafe( + q.put(error), + self._ten_loop, + ), # type: ignore + ) + + error = await q.get() + if error is not None: + raise Exception(error.err_msg()) + + async def return_result(self, result: CmdResult, target_cmd: Cmd) -> None: + q = asyncio.Queue(maxsize=1) + self._internal.return_result( + result, + target_cmd, + lambda _, error: asyncio.run_coroutine_threadsafe( + q.put(error), + self._ten_loop, + ), # type: ignore + ) + + error = await q.get() + if error is not None: + raise Exception(error.err_msg()) + + async def return_result_directly(self, result: CmdResult) -> None: + q = asyncio.Queue(maxsize=1) + self._internal.return_result_directly( + result, + lambda _, error: asyncio.run_coroutine_threadsafe( + q.put(error), + self._ten_loop, + ), # type: ignore + ) + + error = await q.get() + if error is not None: + raise Exception(error.err_msg()) async def on_configure_done(self) -> None: raise NotImplementedError( diff --git a/core/src/ten_runtime/binding/python/interface/ten/error.py b/core/src/ten_runtime/binding/python/interface/ten/error.py new file mode 100644 index 0000000000..da683b0778 --- /dev/null +++ b/core/src/ten_runtime/binding/python/interface/ten/error.py @@ -0,0 +1,15 @@ +# +# Copyright © 2024 Agora +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0, with certain conditions. +# Refer to the "LICENSE" file in the root directory for more information. +# +from libten_runtime_python import _TenError + + +class TenError(_TenError): + def errno(self) -> int: + return _TenError.errno(self) + + def err_msg(self) -> str: + return _TenError.err_msg(self) diff --git a/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi b/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi index fd225ff4ef..2518ac34e3 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi +++ b/core/src/ten_runtime/binding/python/interface/ten/libten_runtime_python.pyi @@ -7,6 +7,11 @@ from .ten_env_attach_to_enum import _TenEnvAttachTo from .log_level import LogLevel from .addon import Addon +from .ten_env import ResultHandler, ErrorHandler + +class _TenError: + def errno(self) -> int: ... + def err_msg(self) -> str: ... class _Msg: def to_json(self) -> str: ... @@ -108,12 +113,22 @@ class _TenEnv: def set_property_bool(self, path: str, value: int) -> None: ... def get_property_float(self, path: str) -> float: ... def set_property_float(self, path: str, value: float) -> None: ... - def send_cmd(self, cmd: _Cmd, result_handler, is_ex: bool) -> None: ... - def send_data(self, data: _Data) -> None: ... - def send_video_frame(self, video_frame: _VideoFrame) -> None: ... - def send_audio_frame(self, audio_frame: _AudioFrame) -> None: ... - def return_result(self, result: _CmdResult, target_cmd: _Cmd) -> None: ... - def return_result_directly(self, result: _CmdResult) -> None: ... + def send_cmd( + self, cmd: _Cmd, result_handler: ResultHandler, is_ex: bool + ) -> None: ... + def send_data(self, data: _Data, error_handler: ErrorHandler) -> None: ... + def send_video_frame( + self, video_frame: _VideoFrame, error_handler: ErrorHandler + ) -> None: ... + def send_audio_frame( + self, audio_frame: _AudioFrame, error_handler: ErrorHandler + ) -> None: ... + def return_result( + self, result: _CmdResult, target_cmd: _Cmd, error_handler: ErrorHandler + ) -> None: ... + def return_result_directly( + self, result: _CmdResult, error_handler: ErrorHandler + ) -> None: ... def is_property_exist(self, path: str) -> bool: ... def init_property_from_json(self, json_str: str) -> None: ... def log( diff --git a/core/src/ten_runtime/binding/python/interface/ten/ten_env.py b/core/src/ten_runtime/binding/python/interface/ten/ten_env.py index 771c00c5b3..7f0a3fa22f 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/ten_env.py +++ b/core/src/ten_runtime/binding/python/interface/ten/ten_env.py @@ -4,9 +4,11 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from typing import Callable import inspect +from typing import Callable, Optional + from libten_runtime_python import _Extension, _TenEnv +from .error import TenError from .ten_env_attach_to_enum import _TenEnvAttachTo from .cmd_result import CmdResult from .cmd import Cmd @@ -19,7 +21,11 @@ class TenEnv: ... # type: ignore -ResultHandler = Callable[[TenEnv, CmdResult], None] | None +ResultHandler = ( + Callable[[TenEnv, Optional[CmdResult], Optional[TenError]], None] | None +) + +ErrorHandler = Callable[[TenEnv, Optional[TenError]], None] | None class TenEnv: @@ -80,20 +86,31 @@ def send_cmd(self, cmd: Cmd, result_handler: ResultHandler) -> None: def send_cmd_ex(self, cmd: Cmd, result_handler: ResultHandler) -> None: return self._internal.send_cmd(cmd, result_handler, True) - def send_data(self, data: Data) -> None: - return self._internal.send_data(data) - - def send_video_frame(self, video_frame: VideoFrame) -> None: - return self._internal.send_video_frame(video_frame) - - def send_audio_frame(self, audio_frame: AudioFrame) -> None: - return self._internal.send_audio_frame(audio_frame) - - def return_result(self, result: CmdResult, target_cmd: Cmd) -> None: - return self._internal.return_result(result, target_cmd) - - def return_result_directly(self, result: CmdResult) -> None: - return self._internal.return_result_directly(result) + def send_data(self, data: Data, error_handler: ErrorHandler = None) -> None: + return self._internal.send_data(data, error_handler) + + def send_video_frame( + self, video_frame: VideoFrame, error_handler: ErrorHandler = None + ) -> None: + return self._internal.send_video_frame(video_frame, error_handler) + + def send_audio_frame( + self, audio_frame: AudioFrame, error_handler: ErrorHandler = None + ) -> None: + return self._internal.send_audio_frame(audio_frame, error_handler) + + def return_result( + self, + result: CmdResult, + target_cmd: Cmd, + error_handler: ErrorHandler = None, + ) -> None: + return self._internal.return_result(result, target_cmd, error_handler) + + def return_result_directly( + self, result: CmdResult, error_handler: ErrorHandler = None + ) -> None: + return self._internal.return_result_directly(result, error_handler) def is_property_exist(self, path: str) -> bool: return self._internal.is_property_exist(path) diff --git a/core/src/ten_runtime/binding/python/interface/ten/test.py b/core/src/ten_runtime/binding/python/interface/ten/test.py index d73b3ab7b2..1a48de610c 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/test.py +++ b/core/src/ten_runtime/binding/python/interface/ten/test.py @@ -4,11 +4,13 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from typing import Callable, final import sys import importlib from pathlib import Path +from typing import Callable, Optional, final + from libten_runtime_python import _ExtensionTester, _TenEnvTester +from .error import TenError from .cmd import Cmd from .data import Data from .audio_frame import AudioFrame @@ -20,7 +22,10 @@ class TenEnvTester: ... # type: ignore -ResultHandler = Callable[[TenEnvTester, CmdResult], None] | None +ResultHandler = ( + Callable[[TenEnvTester, Optional[CmdResult], Optional[TenError]], None] + | None +) class TenEnvTester: diff --git a/core/src/ten_runtime/binding/python/native/common/error.c b/core/src/ten_runtime/binding/python/native/common/error.c index 01a857db12..9d1c7d24b3 100644 --- a/core/src/ten_runtime/binding/python/native/common/error.c +++ b/core/src/ten_runtime/binding/python/native/common/error.c @@ -4,11 +4,66 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // +#include "include_internal/ten_runtime/binding/python/common/error.h" + #include #include "include_internal/ten_runtime/binding/python/common/python_stuff.h" +#include "ten_utils/lib/error.h" #include "ten_utils/lib/string.h" #include "ten_utils/log/log.h" +#include "ten_utils/macro/check.h" + +ten_py_error_t *ten_py_error_wrap(ten_error_t *error) { + if (!error) { + return NULL; + } + + ten_py_error_t *py_error = (ten_py_error_t *)ten_py_error_py_type()->tp_alloc( + ten_py_error_py_type(), 0); + if (!py_error) { + return NULL; + } + + ten_error_init(&py_error->c_error); + ten_error_copy(&py_error->c_error, error); + + return py_error; +} + +void ten_py_error_invalidate(ten_py_error_t *py_error) { + TEN_ASSERT(py_error, "Invalid argument."); + Py_DECREF(py_error); +} + +void ten_py_error_destroy(PyObject *self) { + ten_py_error_t *py_error = (ten_py_error_t *)self; + if (!py_error) { + return; + } + + ten_error_deinit(&py_error->c_error); + + Py_TYPE(self)->tp_free(self); +} + +PyObject *ten_py_error_get_errno(PyObject *self, PyObject *args) { + ten_py_error_t *py_error = (ten_py_error_t *)self; + if (!py_error) { + return ten_py_raise_py_value_error_exception("Invalid argument."); + } + + return PyLong_FromLong(ten_error_errno(&py_error->c_error)); +} + +PyObject *ten_py_error_get_errmsg(PyObject *self, PyObject *args) { + ten_py_error_t *py_error = (ten_py_error_t *)self; + if (!py_error) { + return ten_py_raise_py_value_error_exception("Invalid argument."); + } + + return PyUnicode_FromString(ten_error_errmsg(&py_error->c_error)); +} static void ten_py_print_py_error(void) { PyObject *ptype = NULL; @@ -130,3 +185,24 @@ PyObject *ten_py_raise_py_not_implemented_error_exception(const char *msg) { return NULL; } + +bool ten_py_error_init_for_module(PyObject *module) { + PyTypeObject *py_type = ten_py_error_py_type(); + + if (PyType_Ready(py_type) < 0) { + ten_py_raise_py_system_error_exception("Python Error class is not ready."); + + TEN_ASSERT(0, "Should not happen."); + return false; + } + + if (PyModule_AddObjectRef(module, "_TenError", (PyObject *)py_type) < 0) { + ten_py_raise_py_import_error_exception( + "Failed to add Python type to module."); + + TEN_ASSERT(0, "Should not happen."); + return false; + } + + return true; +} diff --git a/core/src/ten_runtime/binding/python/native/init.c b/core/src/ten_runtime/binding/python/native/init.c index a9d2fa2c8c..af2d20bc2a 100644 --- a/core/src/ten_runtime/binding/python/native/init.c +++ b/core/src/ten_runtime/binding/python/native/init.c @@ -129,5 +129,10 @@ PyMODINIT_FUNC PyInit_libten_runtime_python(void) { return NULL; } + if (!ten_py_error_init_for_module(module)) { + Py_DECREF(module); + return NULL; + } + return module; } diff --git a/core/src/ten_runtime/binding/python/native/msg/type.c b/core/src/ten_runtime/binding/python/native/msg/type.c index 70b86740b0..17310b8803 100644 --- a/core/src/ten_runtime/binding/python/native/msg/type.c +++ b/core/src/ten_runtime/binding/python/native/msg/type.c @@ -5,6 +5,7 @@ // Refer to the "LICENSE" file in the root directory for more information. // #include "include_internal/ten_runtime/binding/python/common/buf.h" +#include "include_internal/ten_runtime/binding/python/common/error.h" #include "include_internal/ten_runtime/binding/python/msg/audio_frame.h" #include "include_internal/ten_runtime/binding/python/msg/cmd.h" #include "include_internal/ten_runtime/binding/python/msg/cmd_result.h" @@ -233,3 +234,27 @@ PyTypeObject *ten_py_buf_py_type(void) { return &py_type; } + +PyTypeObject *ten_py_error_py_type(void) { + static PyMethodDef py_methods[] = { + {"errno", ten_py_error_get_errno, METH_VARARGS, NULL}, + {"err_msg", ten_py_error_get_errmsg, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL}, + }; + + static PyTypeObject py_type = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = + "libten_runtime_python._TenError", + .tp_doc = PyDoc_STR("_TenError"), + .tp_basicsize = sizeof(ten_py_error_t), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_dealloc = ten_py_error_destroy, + .tp_methods = py_methods, + .tp_init = NULL, + .tp_getset = NULL, + .tp_new = NULL, + }; + + return &py_type; +} diff --git a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_return_result.c b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_return_result.c index 2bf146446b..1d9077eae7 100644 --- a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_return_result.c +++ b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_return_result.c @@ -4,6 +4,7 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // +#include "include_internal/ten_runtime/binding/python/common/common.h" #include "include_internal/ten_runtime/binding/python/common/error.h" #include "include_internal/ten_runtime/binding/python/msg/cmd.h" #include "include_internal/ten_runtime/binding/python/msg/cmd_result.h" @@ -11,16 +12,19 @@ #include "include_internal/ten_runtime/binding/python/ten_env/ten_env.h" #include "ten_runtime/msg/cmd_result/cmd_result.h" #include "ten_utils/lib/error.h" +#include "ten_utils/log/log.h" #include "ten_utils/macro/memory.h" typedef struct ten_env_notify_return_result_info_t { ten_shared_ptr_t *c_cmd; ten_shared_ptr_t *c_target_cmd; + PyObject *py_cb_func; } ten_env_notify_return_result_info_t; static ten_env_notify_return_result_info_t * ten_env_notify_return_result_info_create(ten_shared_ptr_t *c_cmd, - ten_shared_ptr_t *c_target_cmd) { + ten_shared_ptr_t *c_target_cmd, + PyObject *py_cb_func) { TEN_ASSERT(c_cmd, "Invalid argument."); ten_env_notify_return_result_info_t *info = @@ -29,6 +33,11 @@ ten_env_notify_return_result_info_create(ten_shared_ptr_t *c_cmd, info->c_cmd = c_cmd; info->c_target_cmd = c_target_cmd; + info->py_cb_func = py_cb_func; + + if (py_cb_func) { + Py_INCREF(py_cb_func); + } return info; } @@ -47,6 +56,8 @@ static void ten_env_notify_return_result_info_destroy( info->c_target_cmd = NULL; } + info->py_cb_func = NULL; + TEN_FREE(info); } @@ -57,17 +68,65 @@ static void ten_env_proxy_notify_return_result(ten_env_t *ten_env, "Should not happen."); ten_env_notify_return_result_info_t *info = user_data; + TEN_ASSERT(info, "Should not happen."); ten_error_t err; ten_error_init(&err); bool rc = false; - if (info->c_target_cmd) { - rc = ten_env_return_result(ten_env, info->c_cmd, info->c_target_cmd, &err); - TEN_ASSERT(rc, "Should not happen."); + if (info->py_cb_func == NULL) { + if (info->c_target_cmd) { + rc = + ten_env_return_result(ten_env, info->c_cmd, info->c_target_cmd, &err); + } else { + rc = ten_env_return_result_directly(ten_env, info->c_cmd, &err); + } + + if (!rc) { + TEN_LOGE( + "Failed to return result, but no callback function is provided. " + "errno: %s, err_msg: %s", + ten_error_errno(&err), ten_error_errmsg(&err)); + } } else { - rc = ten_env_return_result_directly(ten_env, info->c_cmd, &err); - TEN_ASSERT(rc, "Should not happen."); + // TODO(xilin) : Transform the return_xxx C function into an async API and + // set the callback here. Wait for the PR 357 to be merged. + if (info->c_target_cmd) { + rc = + ten_env_return_result(ten_env, info->c_cmd, info->c_target_cmd, &err); + } else { + rc = ten_env_return_result_directly(ten_env, info->c_cmd, &err); + } + + ten_py_error_t *py_err = NULL; + + // About to call the Python function, so it's necessary to ensure that the + // GIL has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + if (!rc) { + py_err = ten_py_error_wrap(&err); + } + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + + PyObject *arglist = Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, + py_err ? (PyObject *)py_err : Py_None); + + PyObject *result = PyObject_CallObject(info->py_cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. + + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + + if (py_err) { + ten_py_error_invalidate(py_err); + } + + ten_py_gil_state_release(prev_state); } ten_error_deinit(&err); @@ -80,11 +139,6 @@ PyObject *ten_py_ten_env_return_result(PyObject *self, PyObject *args) { TEN_ASSERT(py_ten_env && ten_py_ten_env_check_integrity(py_ten_env), "Invalid argument."); - if (PyTuple_GET_SIZE(args) != 2) { - return ten_py_raise_py_value_error_exception( - "Invalid argument count when ten_env.return_result."); - } - bool success = true; ten_error_t err; @@ -92,22 +146,30 @@ PyObject *ten_py_ten_env_return_result(PyObject *self, PyObject *args) { ten_py_cmd_t *py_target_cmd = NULL; ten_py_cmd_result_t *py_cmd_result = NULL; + PyObject *cb_func = NULL; - if (!PyArg_ParseTuple(args, "O!O!", ten_py_cmd_result_py_type(), - &py_cmd_result, ten_py_cmd_py_type(), &py_target_cmd)) { + if (!PyArg_ParseTuple(args, "O!O!O", ten_py_cmd_result_py_type(), + &py_cmd_result, ten_py_cmd_py_type(), &py_target_cmd, + &cb_func)) { success = false; ten_py_raise_py_type_error_exception( "Invalid argument type when return result."); goto done; } + // Check if cb_func is callable. + if (!PyCallable_Check(cb_func)) { + cb_func = NULL; + } + ten_shared_ptr_t *c_target_cmd = ten_shared_ptr_clone(py_target_cmd->msg.c_msg); ten_shared_ptr_t *c_result_cmd = ten_shared_ptr_clone(py_cmd_result->msg.c_msg); ten_env_notify_return_result_info_t *notify_info = - ten_env_notify_return_result_info_create(c_result_cmd, c_target_cmd); + ten_env_notify_return_result_info_create(c_result_cmd, c_target_cmd, + cb_func); bool rc = ten_env_proxy_notify(py_ten_env->c_ten_env_proxy, ten_env_proxy_notify_return_result, @@ -145,31 +207,32 @@ PyObject *ten_py_ten_env_return_result_directly(PyObject *self, TEN_ASSERT(py_ten_env && ten_py_ten_env_check_integrity(py_ten_env), "Invalid argument."); - if (PyTuple_GET_SIZE(args) != 1) { - return ten_py_raise_py_value_error_exception( - "Invalid argument count when ten_env.return_result_directly."); - } - bool success = true; ten_error_t err; ten_error_init(&err); ten_py_cmd_result_t *py_cmd_result = NULL; + PyObject *cb_func = NULL; - if (!PyArg_ParseTuple(args, "O!", ten_py_cmd_result_py_type(), - &py_cmd_result)) { + if (!PyArg_ParseTuple(args, "O!O", ten_py_cmd_result_py_type(), + &py_cmd_result, &cb_func)) { success = false; ten_py_raise_py_type_error_exception( "Invalid argument type when return result directly."); goto done; } + // Check if cb_func is callable. + if (!PyCallable_Check(cb_func)) { + cb_func = NULL; + } + ten_shared_ptr_t *c_result_cmd = ten_shared_ptr_clone(py_cmd_result->msg.c_msg); ten_env_notify_return_result_info_t *notify_info = - ten_env_notify_return_result_info_create(c_result_cmd, NULL); + ten_env_notify_return_result_info_create(c_result_cmd, NULL, cb_func); if (!ten_env_proxy_notify(py_ten_env->c_ten_env_proxy, ten_env_proxy_notify_return_result, notify_info, diff --git a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_audio_frame.c b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_audio_frame.c index ca0b2a2ca0..82f4365bc4 100644 --- a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_audio_frame.c +++ b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_audio_frame.c @@ -4,26 +4,34 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // +#include "include_internal/ten_runtime/binding/python/common/common.h" #include "include_internal/ten_runtime/binding/python/common/error.h" #include "include_internal/ten_runtime/binding/python/msg/audio_frame.h" +#include "include_internal/ten_runtime/binding/python/msg/msg.h" #include "include_internal/ten_runtime/binding/python/ten_env/ten_env.h" -#include "ten_runtime/extension/extension.h" -#include "ten_runtime/ten_env/internal/send.h" +#include "ten_utils/macro/mark.h" #include "ten_utils/macro/memory.h" typedef struct ten_env_notify_send_audio_frame_info_t { ten_shared_ptr_t *c_audio_frame; + PyObject *py_cb_func; } ten_env_notify_send_audio_frame_info_t; static ten_env_notify_send_audio_frame_info_t * -ten_env_notify_send_audio_frame_info_create(ten_shared_ptr_t *c_cmd) { - TEN_ASSERT(c_cmd, "Invalid argument."); +ten_env_notify_send_audio_frame_info_create(ten_shared_ptr_t *c_audio_frame, + PyObject *py_cb_func) { + TEN_ASSERT(c_audio_frame, "Invalid argument."); ten_env_notify_send_audio_frame_info_t *info = TEN_MALLOC(sizeof(ten_env_notify_send_audio_frame_info_t)); TEN_ASSERT(info, "Failed to allocate memory."); - info->c_audio_frame = c_cmd; + info->c_audio_frame = c_audio_frame; + info->py_cb_func = py_cb_func; + + if (py_cb_func != NULL) { + Py_INCREF(py_cb_func); + } return info; } @@ -37,19 +45,109 @@ static void ten_env_notify_send_audio_frame_info_destroy( info->c_audio_frame = NULL; } + info->py_cb_func = NULL; + TEN_FREE(info); } +static void proxy_send_audio_frame_callback( + ten_env_t *ten_env, TEN_UNUSED ten_shared_ptr_t *cmd_result, + void *callback_info, ten_error_t *err) { + TEN_ASSERT(ten_env && ten_env_check_integrity(ten_env, true), + "Should not happen."); + TEN_ASSERT(callback_info, "Should not happen."); + + // About to call the Python function, so it's necessary to ensure that the GIL + // has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + PyObject *cb_func = callback_info; + + PyObject *arglist = NULL; + ten_py_error_t *py_error = NULL; + + if (err) { + py_error = ten_py_error_wrap(err); + + arglist = Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, py_error); + } else { + arglist = Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, Py_None); + } + + PyObject *result = PyObject_CallObject(cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. + + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + Py_XDECREF(cb_func); + + if (py_error) { + ten_py_error_invalidate(py_error); + } + + ten_py_gil_state_release(prev_state); +} + static void ten_env_proxy_notify_send_audio_frame(ten_env_t *ten_env, - void *user_data) { - TEN_ASSERT(user_data, "Invalid argument."); + void *notify_info_) { + TEN_ASSERT(notify_info_, "Invalid argument."); TEN_ASSERT(ten_env && ten_env_check_integrity(ten_env, true), "Should not happen."); - ten_env_notify_send_audio_frame_info_t *notify_info = user_data; + ten_env_notify_send_audio_frame_info_t *notify_info = notify_info_; + TEN_ASSERT(notify_info, "Invalid argument."); + + ten_error_t err; + ten_error_init(&err); + + bool res = false; + if (notify_info->py_cb_func == NULL) { + res = ten_env_send_audio_frame(ten_env, notify_info->c_audio_frame, NULL, + NULL, &err); + if (!res) { + TEN_LOGE( + "Failed to send audio_frame, but no callback function is provided. " + "errno: " + "%s, err_msg: %s", + ten_error_errno(&err), ten_error_errmsg(&err)); + } + } else { + res = ten_env_send_audio_frame(ten_env, notify_info->c_audio_frame, + proxy_send_audio_frame_callback, + notify_info->py_cb_func, &err); + if (!res) { + // About to call the Python function, so it's necessary to ensure that the + // GIL has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + ten_py_error_t *py_err = ten_py_error_wrap(&err); + + PyObject *arglist = + Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, py_err); + + PyObject *result = PyObject_CallObject(notify_info->py_cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. - ten_env_send_audio_frame(ten_env, notify_info->c_audio_frame, NULL, NULL, - NULL); + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + + ten_py_error_invalidate(py_err); + + ten_py_gil_state_release(prev_state); + } + } + + ten_error_deinit(&err); ten_env_notify_send_audio_frame_info_destroy(notify_info); } @@ -65,25 +163,32 @@ PyObject *ten_py_ten_env_send_audio_frame(PyObject *self, PyObject *args) { ten_error_init(&err); ten_py_audio_frame_t *py_audio_frame = NULL; - if (!PyArg_ParseTuple(args, "O!", ten_py_audio_frame_py_type(), - &py_audio_frame)) { + PyObject *cb_func = NULL; + + if (!PyArg_ParseTuple(args, "O!O", ten_py_audio_frame_py_type(), + &py_audio_frame, &cb_func)) { success = false; ten_py_raise_py_type_error_exception( - "Invalid argument type when send audio frame."); + "Invalid argument type when send audio_frame."); goto done; } - ten_shared_ptr_t *cloned_frame = + // Check if cb_func is callable. + if (!PyCallable_Check(cb_func)) { + cb_func = NULL; + } + + ten_shared_ptr_t *cloned_audio_frame = ten_shared_ptr_clone(py_audio_frame->msg.c_msg); ten_env_notify_send_audio_frame_info_t *notify_info = - ten_env_notify_send_audio_frame_info_create(cloned_frame); + ten_env_notify_send_audio_frame_info_create(cloned_audio_frame, cb_func); if (!ten_env_proxy_notify(py_ten_env->c_ten_env_proxy, ten_env_proxy_notify_send_audio_frame, notify_info, false, &err)) { ten_env_notify_send_audio_frame_info_destroy(notify_info); success = false; - ten_py_raise_py_runtime_error_exception("Failed to send audio frame."); + ten_py_raise_py_runtime_error_exception("Failed to send audio_frame."); goto done; } else { // Destroy the C message from the Python message as the ownership has been diff --git a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_cmd.c b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_cmd.c index f5c86b2ec1..c395078989 100644 --- a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_cmd.c +++ b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_cmd.c @@ -14,8 +14,9 @@ #include "ten_runtime/extension/extension.h" #include "ten_runtime/msg/cmd_result/cmd_result.h" #include "ten_runtime/ten_env_proxy/ten_env_proxy.h" +#include "ten_utils/lib/error.h" +#include "ten_utils/log/log.h" #include "ten_utils/macro/check.h" -#include "ten_utils/macro/mark.h" #include "ten_utils/macro/memory.h" typedef struct ten_env_notify_send_cmd_info_t { @@ -59,11 +60,23 @@ static void proxy_send_xxx_callback(ten_env_t *ten_env, PyGILState_STATE prev_state = ten_py_gil_state_ensure(); ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); - ten_py_cmd_result_t *cmd_result_bridge = ten_py_cmd_result_wrap(cmd_result); - PyObject *cb_func = callback_info; - PyObject *arglist = - Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, cmd_result_bridge); + + PyObject *arglist = NULL; + ten_py_error_t *py_error = NULL; + ten_py_cmd_result_t *cmd_result_bridge = NULL; + + if (err) { + py_error = ten_py_error_wrap(err); + + arglist = Py_BuildValue("(OOO)", py_ten_env->actual_py_ten_env, Py_None, + py_error); + } else { + cmd_result_bridge = ten_py_cmd_result_wrap(cmd_result); + + arglist = Py_BuildValue("(OOO)", py_ten_env->actual_py_ten_env, + cmd_result_bridge, Py_None); + } PyObject *result = PyObject_CallObject(cb_func, arglist); Py_XDECREF(result); // Ensure cleanup if an error occurred. @@ -78,7 +91,13 @@ static void proxy_send_xxx_callback(ten_env_t *ten_env, Py_XDECREF(cb_func); } - ten_py_cmd_result_invalidate(cmd_result_bridge); + if (py_error) { + ten_py_error_invalidate(py_error); + } + + if (cmd_result_bridge) { + ten_py_cmd_result_invalidate(cmd_result_bridge); + } ten_py_gil_state_release(prev_state); } @@ -114,12 +133,43 @@ static void ten_env_proxy_notify_send_cmd(ten_env_t *ten_env, void *user_data) { send_cmd_func = ten_env_send_cmd; } - TEN_UNUSED bool res = false; + bool res = false; if (notify_info->py_cb_func == NULL) { - res = send_cmd_func(ten_env, notify_info->c_cmd, NULL, NULL, NULL); + res = send_cmd_func(ten_env, notify_info->c_cmd, NULL, NULL, &err); + if (!res) { + TEN_LOGE( + "Failed to send cmd, but no callback function is provided. errno: " + "%s, err_msg: %s", + ten_error_errno(&err), ten_error_errmsg(&err)); + } } else { res = send_cmd_func(ten_env, notify_info->c_cmd, proxy_send_xxx_callback, - notify_info->py_cb_func, NULL); + notify_info->py_cb_func, &err); + if (!res) { + // About to call the Python function, so it's necessary to ensure that the + // GIL has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + ten_py_error_t *py_err = ten_py_error_wrap(&err); + + PyObject *arglist = Py_BuildValue("(OOO)", py_ten_env->actual_py_ten_env, + Py_None, py_err); + + PyObject *result = PyObject_CallObject(notify_info->py_cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. + + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + + ten_py_error_invalidate(py_err); + + ten_py_gil_state_release(prev_state); + } } ten_error_deinit(&err); diff --git a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_data.c b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_data.c index e838d8bf8e..efa7cfc79d 100644 --- a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_data.c +++ b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_data.c @@ -4,18 +4,21 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // +#include "include_internal/ten_runtime/binding/python/common/common.h" #include "include_internal/ten_runtime/binding/python/common/error.h" #include "include_internal/ten_runtime/binding/python/msg/data.h" #include "include_internal/ten_runtime/binding/python/msg/msg.h" #include "include_internal/ten_runtime/binding/python/ten_env/ten_env.h" +#include "ten_utils/macro/mark.h" #include "ten_utils/macro/memory.h" typedef struct ten_env_notify_send_data_info_t { ten_shared_ptr_t *c_data; + PyObject *py_cb_func; } ten_env_notify_send_data_info_t; static ten_env_notify_send_data_info_t *ten_env_notify_send_data_info_create( - ten_shared_ptr_t *c_data) { + ten_shared_ptr_t *c_data, PyObject *py_cb_func) { TEN_ASSERT(c_data, "Invalid argument."); ten_env_notify_send_data_info_t *info = @@ -23,6 +26,11 @@ static ten_env_notify_send_data_info_t *ten_env_notify_send_data_info_create( TEN_ASSERT(info, "Failed to allocate memory."); info->c_data = c_data; + info->py_cb_func = py_cb_func; + + if (py_cb_func != NULL) { + Py_INCREF(py_cb_func); + } return info; } @@ -36,9 +44,54 @@ static void ten_env_notify_send_data_info_destroy( info->c_data = NULL; } + info->py_cb_func = NULL; + TEN_FREE(info); } +static void proxy_send_data_callback(ten_env_t *ten_env, + TEN_UNUSED ten_shared_ptr_t *cmd_result, + void *callback_info, ten_error_t *err) { + TEN_ASSERT(ten_env && ten_env_check_integrity(ten_env, true), + "Should not happen."); + TEN_ASSERT(callback_info, "Should not happen."); + + // About to call the Python function, so it's necessary to ensure that the GIL + // has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + PyObject *cb_func = callback_info; + + PyObject *arglist = NULL; + ten_py_error_t *py_error = NULL; + + if (err) { + py_error = ten_py_error_wrap(err); + + arglist = Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, py_error); + } else { + arglist = Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, Py_None); + } + + PyObject *result = PyObject_CallObject(cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. + + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + Py_XDECREF(cb_func); + + if (py_error) { + ten_py_error_invalidate(py_error); + } + + ten_py_gil_state_release(prev_state); +} + static void ten_env_proxy_notify_send_data(ten_env_t *ten_env, void *user_data) { TEN_ASSERT(user_data, "Invalid argument."); @@ -46,8 +99,53 @@ static void ten_env_proxy_notify_send_data(ten_env_t *ten_env, "Should not happen."); ten_env_notify_send_data_info_t *notify_info = user_data; + TEN_ASSERT(notify_info, "Invalid argument."); + + ten_error_t err; + ten_error_init(&err); + + bool res = false; + if (notify_info->py_cb_func == NULL) { + res = ten_env_send_data(ten_env, notify_info->c_data, NULL, NULL, &err); + if (!res) { + TEN_LOGE( + "Failed to send data, but no callback function is provided. errno: " + "%s, err_msg: %s", + ten_error_errno(&err), ten_error_errmsg(&err)); + } + } else { + res = ten_env_send_data(ten_env, notify_info->c_data, + proxy_send_data_callback, notify_info->py_cb_func, + &err); + if (!res) { + // About to call the Python function, so it's necessary to ensure that the + // GIL + // has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + ten_py_error_t *py_err = ten_py_error_wrap(&err); + + PyObject *arglist = + Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, py_err); + + PyObject *result = PyObject_CallObject(notify_info->py_cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. - ten_env_send_data(ten_env, notify_info->c_data, NULL, NULL, NULL); + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + + ten_py_error_invalidate(py_err); + + ten_py_gil_state_release(prev_state); + } + } + + ten_error_deinit(&err); ten_env_notify_send_data_info_destroy(notify_info); } @@ -63,16 +161,24 @@ PyObject *ten_py_ten_env_send_data(PyObject *self, PyObject *args) { ten_error_init(&err); ten_py_data_t *py_data = NULL; - if (!PyArg_ParseTuple(args, "O!", ten_py_data_py_type(), &py_data)) { + PyObject *cb_func = NULL; + + if (!PyArg_ParseTuple(args, "O!O", ten_py_data_py_type(), &py_data, + &cb_func)) { success = false; ten_py_raise_py_type_error_exception( "Invalid argument type when send data."); goto done; } + // Check if cb_func is callable. + if (!PyCallable_Check(cb_func)) { + cb_func = NULL; + } + ten_shared_ptr_t *cloned_data = ten_shared_ptr_clone(py_data->msg.c_msg); ten_env_notify_send_data_info_t *notify_info = - ten_env_notify_send_data_info_create(cloned_data); + ten_env_notify_send_data_info_create(cloned_data, cb_func); if (!ten_env_proxy_notify(py_ten_env->c_ten_env_proxy, ten_env_proxy_notify_send_data, notify_info, false, diff --git a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_video_frame.c b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_video_frame.c index c034daf763..97d6764079 100644 --- a/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_video_frame.c +++ b/core/src/ten_runtime/binding/python/native/ten_env/ten_env_send_video_frame.c @@ -4,24 +4,34 @@ // Licensed under the Apache License, Version 2.0, with certain conditions. // Refer to the "LICENSE" file in the root directory for more information. // +#include "include_internal/ten_runtime/binding/python/common/common.h" #include "include_internal/ten_runtime/binding/python/common/error.h" +#include "include_internal/ten_runtime/binding/python/msg/msg.h" #include "include_internal/ten_runtime/binding/python/msg/video_frame.h" #include "include_internal/ten_runtime/binding/python/ten_env/ten_env.h" +#include "ten_utils/macro/mark.h" #include "ten_utils/macro/memory.h" typedef struct ten_env_notify_send_video_frame_info_t { - ten_shared_ptr_t *c_audio_frame; + ten_shared_ptr_t *c_video_frame; + PyObject *py_cb_func; } ten_env_notify_send_video_frame_info_t; static ten_env_notify_send_video_frame_info_t * -ten_env_notify_send_video_frame_info_create(ten_shared_ptr_t *c_cmd) { - TEN_ASSERT(c_cmd, "Invalid argument."); +ten_env_notify_send_video_frame_info_create(ten_shared_ptr_t *c_video_frame, + PyObject *py_cb_func) { + TEN_ASSERT(c_video_frame, "Invalid argument."); ten_env_notify_send_video_frame_info_t *info = TEN_MALLOC(sizeof(ten_env_notify_send_video_frame_info_t)); TEN_ASSERT(info, "Failed to allocate memory."); - info->c_audio_frame = c_cmd; + info->c_video_frame = c_video_frame; + info->py_cb_func = py_cb_func; + + if (py_cb_func != NULL) { + Py_INCREF(py_cb_func); + } return info; } @@ -30,24 +40,114 @@ static void ten_env_notify_send_video_frame_info_destroy( ten_env_notify_send_video_frame_info_t *info) { TEN_ASSERT(info, "Invalid argument."); - if (info->c_audio_frame) { - ten_shared_ptr_destroy(info->c_audio_frame); - info->c_audio_frame = NULL; + if (info->c_video_frame) { + ten_shared_ptr_destroy(info->c_video_frame); + info->c_video_frame = NULL; } + info->py_cb_func = NULL; + TEN_FREE(info); } +static void proxy_send_video_frame_callback( + ten_env_t *ten_env, TEN_UNUSED ten_shared_ptr_t *cmd_result, + void *callback_info, ten_error_t *err) { + TEN_ASSERT(ten_env && ten_env_check_integrity(ten_env, true), + "Should not happen."); + TEN_ASSERT(callback_info, "Should not happen."); + + // About to call the Python function, so it's necessary to ensure that the GIL + // has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + PyObject *cb_func = callback_info; + + PyObject *arglist = NULL; + ten_py_error_t *py_error = NULL; + + if (err) { + py_error = ten_py_error_wrap(err); + + arglist = Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, py_error); + } else { + arglist = Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, Py_None); + } + + PyObject *result = PyObject_CallObject(cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. + + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + Py_XDECREF(cb_func); + + if (py_error) { + ten_py_error_invalidate(py_error); + } + + ten_py_gil_state_release(prev_state); +} + static void ten_env_proxy_notify_send_video_frame(ten_env_t *ten_env, - void *user_data) { - TEN_ASSERT(user_data, "Invalid argument."); + void *notify_info_) { + TEN_ASSERT(notify_info_, "Invalid argument."); TEN_ASSERT(ten_env && ten_env_check_integrity(ten_env, true), "Should not happen."); - ten_env_notify_send_video_frame_info_t *notify_info = user_data; + ten_env_notify_send_video_frame_info_t *notify_info = notify_info_; + TEN_ASSERT(notify_info, "Invalid argument."); + + ten_error_t err; + ten_error_init(&err); + + bool res = false; + if (notify_info->py_cb_func == NULL) { + res = ten_env_send_video_frame(ten_env, notify_info->c_video_frame, NULL, + NULL, &err); + if (!res) { + TEN_LOGE( + "Failed to send video_frame, but no callback function is provided. " + "errno: " + "%s, err_msg: %s", + ten_error_errno(&err), ten_error_errmsg(&err)); + } + } else { + res = ten_env_send_video_frame(ten_env, notify_info->c_video_frame, + proxy_send_video_frame_callback, + notify_info->py_cb_func, &err); + if (!res) { + // About to call the Python function, so it's necessary to ensure that the + // GIL has been acquired. + // + // Allows C codes to work safely with Python objects. + PyGILState_STATE prev_state = ten_py_gil_state_ensure(); + + ten_py_ten_env_t *py_ten_env = ten_py_ten_env_wrap(ten_env); + ten_py_error_t *py_err = ten_py_error_wrap(&err); + + PyObject *arglist = + Py_BuildValue("(OO)", py_ten_env->actual_py_ten_env, py_err); + + PyObject *result = PyObject_CallObject(notify_info->py_cb_func, arglist); + Py_XDECREF(result); // Ensure cleanup if an error occurred. - ten_env_send_video_frame(ten_env, notify_info->c_audio_frame, NULL, NULL, - NULL); + bool err_occurred = ten_py_check_and_clear_py_error(); + TEN_ASSERT(!err_occurred, "Should not happen."); + + Py_XDECREF(arglist); + + ten_py_error_invalidate(py_err); + + ten_py_gil_state_release(prev_state); + } + } + + ten_error_deinit(&err); ten_env_notify_send_video_frame_info_destroy(notify_info); } @@ -63,25 +163,32 @@ PyObject *ten_py_ten_env_send_video_frame(PyObject *self, PyObject *args) { ten_error_init(&err); ten_py_video_frame_t *py_video_frame = NULL; - if (!PyArg_ParseTuple(args, "O!", ten_py_video_frame_py_type(), - &py_video_frame)) { + PyObject *cb_func = NULL; + + if (!PyArg_ParseTuple(args, "O!O", ten_py_video_frame_py_type(), + &py_video_frame, &cb_func)) { success = false; ten_py_raise_py_type_error_exception( - "Invalid argument type when send video frame."); + "Invalid argument type when send video_frame."); goto done; } - ten_shared_ptr_t *cloned_frame = + // Check if cb_func is callable. + if (!PyCallable_Check(cb_func)) { + cb_func = NULL; + } + + ten_shared_ptr_t *cloned_video_frame = ten_shared_ptr_clone(py_video_frame->msg.c_msg); ten_env_notify_send_video_frame_info_t *notify_info = - ten_env_notify_send_video_frame_info_create(cloned_frame); + ten_env_notify_send_video_frame_info_create(cloned_video_frame, cb_func); if (!ten_env_proxy_notify(py_ten_env->c_ten_env_proxy, ten_env_proxy_notify_send_video_frame, notify_info, false, &err)) { ten_env_notify_send_video_frame_info_destroy(notify_info); success = false; - ten_py_raise_py_runtime_error_exception("Failed to send video frame."); + ten_py_raise_py_runtime_error_exception("Failed to send video_frame."); goto done; } else { // Destroy the C message from the Python message as the ownership has been diff --git a/core/src/ten_runtime/binding/python/native/test/env_tester/ten_env_send_cmd.c b/core/src/ten_runtime/binding/python/native/test/env_tester/ten_env_send_cmd.c index 7017d53f38..6fdc0bae4e 100644 --- a/core/src/ten_runtime/binding/python/native/test/env_tester/ten_env_send_cmd.c +++ b/core/src/ten_runtime/binding/python/native/test/env_tester/ten_env_send_cmd.c @@ -14,10 +14,12 @@ #include "ten_runtime/msg/cmd_result/cmd_result.h" #include "ten_runtime/test/env_tester.h" #include "ten_utils/macro/check.h" +#include "ten_utils/macro/mark.h" static void proxy_send_xxx_callback(ten_env_tester_t *ten_env_tester, ten_shared_ptr_t *cmd_result, - void *callback_info, ten_error_t *error) { + void *callback_info, + TEN_UNUSED ten_error_t *error) { TEN_ASSERT(ten_env_tester && ten_env_tester_check_integrity(ten_env_tester), "Should not happen."); TEN_ASSERT(cmd_result && ten_cmd_base_check_integrity(cmd_result), @@ -35,8 +37,9 @@ static void proxy_send_xxx_callback(ten_env_tester_t *ten_env_tester, ten_py_cmd_result_t *cmd_result_bridge = ten_py_cmd_result_wrap(cmd_result); PyObject *cb_func = callback_info; - PyObject *arglist = Py_BuildValue( - "(OO)", py_ten_env_tester->actual_py_ten_env_tester, cmd_result_bridge); + PyObject *arglist = + Py_BuildValue("(OOO)", py_ten_env_tester->actual_py_ten_env_tester, + cmd_result_bridge, Py_None); PyObject *result = PyObject_CallObject(cb_func, arglist); Py_XDECREF(result); // Ensure cleanup if an error occurred. diff --git a/core/src/ten_runtime/ten_env/internal/send.c b/core/src/ten_runtime/ten_env/internal/send.c index 5772cfe8ee..d60c917d22 100644 --- a/core/src/ten_runtime/ten_env/internal/send.c +++ b/core/src/ten_runtime/ten_env/internal/send.c @@ -184,17 +184,23 @@ bool ten_env_send_cmd(ten_env_t *self, ten_shared_ptr_t *cmd, self); TEN_ASSERT(cmd, "Should not happen."); + bool rc = false; + if (result_handler) { ten_cmd_result_handler_for_send_cmd_ctx_t *ctx = ten_cmd_result_handler_for_send_cmd_ctx_create( result_handler, result_handler_user_data); - return ten_send_msg_internal(self, cmd, cmd_result_handler_for_send_cmd, - ctx, err); + rc = ten_send_msg_internal(self, cmd, cmd_result_handler_for_send_cmd, ctx, + err); + if (!rc) { + ten_cmd_result_handler_for_send_cmd_ctx_destroy(ctx); + } } else { - return ten_send_msg_internal(self, cmd, NULL, result_handler_user_data, - err); + rc = ten_send_msg_internal(self, cmd, NULL, result_handler_user_data, err); } + + return rc; } bool ten_env_send_cmd_ex(ten_env_t *self, ten_shared_ptr_t *cmd, diff --git a/packages/core_extensions/default_async_extension_python/extension.py b/packages/core_extensions/default_async_extension_python/extension.py index 5e3b99dc6e..d22c00df98 100644 --- a/packages/core_extensions/default_async_extension_python/extension.py +++ b/packages/core_extensions/default_async_extension_python/extension.py @@ -39,7 +39,7 @@ async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None: # TODO: process cmd cmd_result = CmdResult.create(StatusCode.OK) - ten_env.return_result(cmd_result, cmd) + await ten_env.return_result(cmd_result, cmd) async def on_data(self, ten_env: AsyncTenEnv, data: Data) -> None: data_name = data.get_name() diff --git a/packages/core_extensions/default_async_extension_python/tests/test_basic.py b/packages/core_extensions/default_async_extension_python/tests/test_basic.py index c3755f449a..a7e17790ea 100644 --- a/packages/core_extensions/default_async_extension_python/tests/test_basic.py +++ b/packages/core_extensions/default_async_extension_python/tests/test_basic.py @@ -5,11 +5,29 @@ # Refer to the "LICENSE" file in the root directory for more information. # from pathlib import Path -from ten import ExtensionTester, TenEnvTester, Cmd, CmdResult, StatusCode +from typing import Optional +from ten import ( + ExtensionTester, + TenEnvTester, + Cmd, + CmdResult, + StatusCode, + TenError, +) class ExtensionTesterBasic(ExtensionTester): - def check_hello(self, ten_env: TenEnvTester, result: CmdResult): + def check_hello( + self, + ten_env: TenEnvTester, + result: Optional[CmdResult], + error: Optional[TenError], + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + statusCode = result.get_status_code() print("receive hello_world, status:" + str(statusCode)) @@ -22,7 +40,9 @@ def on_start(self, ten_env: TenEnvTester) -> None: print("send hello_world") ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error + ), ) print("tester on_start_done") diff --git a/packages/core_extensions/default_extension_python/tests/test_basic.py b/packages/core_extensions/default_extension_python/tests/test_basic.py index 019740a3da..bb004f999e 100644 --- a/packages/core_extensions/default_extension_python/tests/test_basic.py +++ b/packages/core_extensions/default_extension_python/tests/test_basic.py @@ -5,11 +5,29 @@ # Refer to the "LICENSE" file in the root directory for more information. # from pathlib import Path -from ten import ExtensionTester, TenEnvTester, Cmd, CmdResult, StatusCode +from typing import Optional +from ten import ( + ExtensionTester, + TenEnvTester, + Cmd, + CmdResult, + StatusCode, + TenError, +) class ExtensionTesterBasic(ExtensionTester): - def check_hello(self, ten_env: TenEnvTester, result: CmdResult): + def check_hello( + self, + ten_env: TenEnvTester, + result: Optional[CmdResult], + error: Optional[TenError], + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + statusCode = result.get_status_code() print("receive hello_world, status:" + str(statusCode)) @@ -22,7 +40,9 @@ def on_start(self, ten_env: TenEnvTester) -> None: print("send hello_world") ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error + ), ) print("tester on_start_done") diff --git a/packages/example_extensions/aio_http_server_python/main.py b/packages/example_extensions/aio_http_server_python/main.py index a3275d1410..eb1ecd9b04 100644 --- a/packages/example_extensions/aio_http_server_python/main.py +++ b/packages/example_extensions/aio_http_server_python/main.py @@ -125,7 +125,7 @@ async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None: ten_env.log_debug("on_cmd") # Not supported command. - ten_env.return_result(CmdResult.create(StatusCode.ERROR), cmd) + await ten_env.return_result(CmdResult.create(StatusCode.ERROR), cmd) async def on_stop(self, ten_env: AsyncTenEnv) -> None: ten_env.log_debug("on_stop") diff --git a/tests/ten_runtime/integration/pytest.ini b/tests/ten_runtime/integration/pytest.ini index 745d473094..91c450893e 100644 --- a/tests/ten_runtime/integration/pytest.ini +++ b/tests/ten_runtime/integration/pytest.ini @@ -31,3 +31,5 @@ addopts = --ignore=tests/ten_runtime/integration/python/two_async_exts_one_group_python/two_async_exts_one_group_python_app/ten_packages/extension/default_extension_python/tests/ --ignore=tests/ten_runtime/integration/python/two_async_exts_python/two_async_exts_python_app/ten_packages/extension/default_extension_python/tests/ --ignore=tests/ten_runtime/integration/python/unused_addon_python/unused_addon_python_app/ten_packages/extension/default_extension_python/tests/ + --ignore=tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/ten_packages/extension/default_extension_python/tests/ + --ignore=tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/ten_packages/extension/default_async_extension_python/tests/ diff --git a/tests/ten_runtime/integration/python/BUILD.gn b/tests/ten_runtime/integration/python/BUILD.gn index 8974f47c45..f9c89624ff 100644 --- a/tests/ten_runtime/integration/python/BUILD.gn +++ b/tests/ten_runtime/integration/python/BUILD.gn @@ -29,6 +29,8 @@ group("python") { "multiple_results_python_1", "multiple_results_python_2", "multiple_results_python_3", + "no_dest_async_python", + "no_dest_python", "resp_handler_yield_python", "send_cmd_discard_result_python", "send_cmd_python", diff --git a/tests/ten_runtime/integration/python/async_extension_basic_python/async_extension_basic_python_app/ten_packages/extension/default_async_extension_python/extension.py b/tests/ten_runtime/integration/python/async_extension_basic_python/async_extension_basic_python_app/ten_packages/extension/default_async_extension_python/extension.py index 9d1bb21f4a..5cf7505459 100644 --- a/tests/ten_runtime/integration/python/async_extension_basic_python/async_extension_basic_python_app/ten_packages/extension/default_async_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/async_extension_basic_python/async_extension_basic_python_app/ten_packages/extension/default_async_extension_python/extension.py @@ -42,7 +42,7 @@ async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None: # result will be returned to the original sender. new_cmd = Cmd.create("hello") cmd_result = await ten_env.send_cmd(new_cmd) - ten_env.return_result(cmd_result, cmd) + await ten_env.return_result(cmd_result, cmd) async def on_stop(self, ten_env: AsyncTenEnv) -> None: ten_env.log_debug("on_stop") diff --git a/tests/ten_runtime/integration/python/async_io_basic_python/async_io_basic_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/async_io_basic_python/async_io_basic_python_app/ten_packages/extension/default_extension_python/extension.py index 731062862b..4a049b15cd 100644 --- a/tests/ten_runtime/integration/python/async_io_basic_python/async_io_basic_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/async_io_basic_python/async_io_basic_python_app/ten_packages/extension/default_extension_python/extension.py @@ -16,7 +16,7 @@ class DefaultExtension(Extension): async def __thread_routine(self, ten_env: TenEnv): - print("DefaultExtension __thread_routine start") + ten_env.log_info("__thread_routine start") self.loop = asyncio.get_running_loop() @@ -32,15 +32,19 @@ async def stop_thread(self): self.stopEvent.set() async def send_cmd_async(self, ten_env: TenEnv, cmd: Cmd) -> CmdResult: - print("DefaultExtension send_cmd_async") + ten_env.log_info("send_cmd_async") q = asyncio.Queue(maxsize=10) ten_env.send_cmd( cmd, - lambda ten_env, result: asyncio.run_coroutine_threadsafe( - q.put(result), self.loop + lambda ten_env, result, error: asyncio.run_coroutine_threadsafe( + q.put([result, error]), self.loop ), # type: ignore ) - return await q.get() + + [result, error] = await q.get() + if error is not None: + raise Exception(error.err_msg()) + return result def __init__(self, name: str) -> None: super().__init__(name) @@ -61,14 +65,14 @@ def on_start(self, ten_env: TenEnv) -> None: self.thread.start() def on_deinit(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_deinit") + ten_env.log_info("on_deinit") ten_env.on_deinit_done() async def on_cmd_async(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd_async") + ten_env.log_info("on_cmd_async") cmd_json = cmd.to_json() - print("DefaultExtension on_cmd_async json: " + cmd_json) + ten_env.log_info("on_cmd_async json: " + cmd_json) # Mock async operation, e.g. network, file I/O await asyncio.sleep(1) @@ -80,14 +84,14 @@ async def on_cmd_async(self, ten_env: TenEnv, cmd: Cmd) -> None: ten_env.return_result(cmd_result, cmd) def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd") + ten_env.log_info("on_cmd") asyncio.run_coroutine_threadsafe( self.on_cmd_async(ten_env, cmd), self.loop ) def on_stop(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_stop") + ten_env.log_info("on_stop") if self.thread.is_alive(): asyncio.run_coroutine_threadsafe(self.stop_thread(), self.loop) diff --git a/tests/ten_runtime/integration/python/cpp_app_multi_process_python/cpp_app_multi_process_python_app_source/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/cpp_app_multi_process_python/cpp_app_multi_process_python_app_source/ten_packages/extension/default_extension_python/extension.py index 1de457a9fb..3361388679 100644 --- a/tests/ten_runtime/integration/python/cpp_app_multi_process_python/cpp_app_multi_process_python_app_source/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/cpp_app_multi_process_python/cpp_app_multi_process_python_app_source/ten_packages/extension/default_extension_python/extension.py @@ -7,13 +7,8 @@ import multiprocessing as mp import os import time -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -38,7 +33,7 @@ def on_start(self, ten_env: TenEnv) -> None: ten_env.set_property_from_json("testKey2", '"testValue2"') testValue = ten_env.get_property_to_json("testKey") testValue2 = ten_env.get_property_to_json("testKey2") - print("testValue: ", testValue, " testValue2: ", testValue2) + ten_env.log_info(f"testValue: {testValue}, testValue2: {testValue2}") ten_env.on_start_done() @@ -50,19 +45,27 @@ def on_deinit(self, ten_env: TenEnv) -> None: ten_env.log_debug("on_deinit") ten_env.on_deinit_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + statusCode = result.get_status_code() detail = result.get_property_string("detail") - print( - "DefaultExtension check_hello: status:" - + str(statusCode) - + " detail:" - + detail + ten_env.log_info( + "check_hello: status:" + str(statusCode) + " detail:" + detail ) respCmd = CmdResult.create(StatusCode.OK) respCmd.set_property_string("detail", detail + " nbnb") - print("DefaultExtension create respCmd") + ten_env.log_info("create respCmd") ten_env.return_result(respCmd, receivedCmd) @@ -97,7 +100,9 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/cpp_app_python/cpp_app_python_app_source/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/cpp_app_python/cpp_app_python_app_source/ten_packages/extension/default_extension_python/extension.py index b34bad81e0..aed39e41d0 100644 --- a/tests/ten_runtime/integration/python/cpp_app_python/cpp_app_python_app_source/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/cpp_app_python/cpp_app_python_app_source/ten_packages/extension/default_extension_python/extension.py @@ -4,13 +4,8 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -27,27 +22,38 @@ def on_start(self, ten_env: TenEnv) -> None: # If the io encoding is not utf-8, the following print will cause an # error. Ex: UnicodeEncodeError: 'ascii' codec can't encode characters # in position 0-1: ordinal not in range(128). - print("中文") + ten_env.log_info("中文") ten_env.set_property_from_json("testKey2", '"testValue2"') testValue = ten_env.get_property_to_json("testKey") testValue2 = ten_env.get_property_to_json("testKey2") - print("testValue: ", testValue, " testValue2: ", testValue2) + ten_env.log_info(f"testValue: {testValue}, testValue2: {testValue2}") ten_env.on_start_done() def on_stop(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_stop") + ten_env.log_info("on_stop") ten_env.on_stop_done() def on_deinit(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_deinit") + ten_env.log_info("on_deinit") ten_env.on_deinit_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + statusCode = result.get_status_code() detail = result.get_property_string("detail") - print( + ten_env.log_info( "DefaultExtension check_hello: status:" + str(statusCode) + " detail:" @@ -56,22 +62,24 @@ def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): respCmd = CmdResult.create(StatusCode.OK) respCmd.set_property_string("detail", detail + " nbnb") - print("DefaultExtension create respCmd") + ten_env.log_info("create respCmd") ten_env.return_result(respCmd, receivedCmd) def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd") + ten_env.log_info("on_cmd") cmd_json = cmd.to_json() - print("DefaultExtension on_cmd json: " + cmd_json) + ten_env.log_info("on_cmd json: " + cmd_json) new_cmd = Cmd.create("hello") new_cmd.set_property_from_json("test", '"testValue2"') test_value = new_cmd.get_property_to_json("test") - print("DefaultExtension on_cmd test_value: " + test_value) + ten_env.log_info("on_cmd test_value: " + test_value) ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/get_set_prop_python/get_set_prop_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/get_set_prop_python/get_set_prop_python_app/ten_packages/extension/default_extension_python/extension.py index b05825ae26..64fc93cfbe 100644 --- a/tests/ten_runtime/integration/python/get_set_prop_python/get_set_prop_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/get_set_prop_python/get_set_prop_python_app/ten_packages/extension/default_extension_python/extension.py @@ -5,13 +5,8 @@ # Refer to the "LICENSE" file in the root directory for more information. # import threading -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -34,7 +29,7 @@ def __test_thread_routine(self, ten_env: TenEnv): assert throw_exception is True assert i == 10000 - print("DefaultExtension __test_thread_routine done") + ten_env.log_info("__test_thread_routine done") def on_start(self, ten_env: TenEnv) -> None: ten_env.log_debug("on_start") @@ -45,7 +40,7 @@ def on_start(self, ten_env: TenEnv) -> None: env_value = ten_env.get_property_string("env_not_set_has_default") assert env_value == "" except Exception as e: - print(e) + ten_env.log_info(str(e)) assert False assert ten_env.is_property_exist("undefined_key") is False @@ -97,24 +92,32 @@ def __join_thread(self, ten_env: TenEnv): ten_env.on_stop_done() def on_stop(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_stop") + ten_env.log_info("on_stop") # Start a new thread to join the previous thread to avoid blocking the # TEN extension thread. threading.Thread(target=self.__join_thread, args=(ten_env,)).start() def on_deinit(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_deinit") + ten_env.log_info("on_deinit") ten_env.on_deinit_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error + + assert result is not None + statusCode = result.get_status_code() detail = result.get_property_string("detail") - print( - "DefaultExtension check_hello: status:" - + str(statusCode) - + " detail:" - + detail + ten_env.log_info( + "check_hello: status:" + str(statusCode) + " detail:" + detail ) for i in range(0, 10000): @@ -128,14 +131,14 @@ def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): respCmd = CmdResult.create(StatusCode.OK) respCmd.set_property_string("detail", detail + " nbnb") - print("DefaultExtension create respCmd") + ten_env.log_info("create respCmd") ten_env.return_result(respCmd, receivedCmd) def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd") + ten_env.log_info("on_cmd") cmd_json = cmd.to_json() - print("DefaultExtension on_cmd json: " + cmd_json) + ten_env.log_info("on_cmd json: " + cmd_json) new_cmd = Cmd.create("hello") new_cmd.set_property_from_json("test", '"testValue2"') @@ -162,12 +165,14 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: try: _ = new_cmd.get_property_string("undefinedKey") except Exception as e: - print( + ten_env.log_info( "DefaultExtension on_cmd get_property_string exception: " + str(e) ) ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/go_app_async_extension_python/go_app_async_extension_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/go_app_async_extension_python/go_app_async_extension_python_app/ten_packages/extension/default_extension_python/extension.py index 52e84169c8..0950d24834 100644 --- a/tests/ten_runtime/integration/python/go_app_async_extension_python/go_app_async_extension_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/go_app_async_extension_python/go_app_async_extension_python_app/ten_packages/extension/default_extension_python/extension.py @@ -85,4 +85,4 @@ async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None: respCmd.set_property_string("detail", "received response") ten_env.log_info("create respCmd") - ten_env.return_result(respCmd, cmd) + await ten_env.return_result(respCmd, cmd) diff --git a/tests/ten_runtime/integration/python/go_app_cythonize/go_app_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx b/tests/ten_runtime/integration/python/go_app_cythonize/go_app_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx index a95e1e6b65..192eda9775 100644 --- a/tests/ten_runtime/integration/python/go_app_cythonize/go_app_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx +++ b/tests/ten_runtime/integration/python/go_app_cythonize/go_app_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx @@ -4,13 +4,13 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) + +# import debugpy +# debugpy.listen(5678) +# debugpy.wait_for_client() + +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -26,63 +26,78 @@ class DefaultExtension(Extension): ten_env.set_property_from_json("testKey2", '"testValue2"') testValue = ten_env.get_property_to_json("testKey") testValue2 = ten_env.get_property_to_json("testKey2") - print("testValue: ", testValue, " testValue2: ", testValue2) + ten_env.log_info(f"testValue: {testValue}, testValue2: {testValue2}") ten_env.on_start_done() def on_stop(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_stop") + ten_env.log_debug("on_stop") ten_env.on_stop_done() def on_deinit(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_deinit") + ten_env.log_debug("on_deinit") ten_env.on_deinit_done() def check_greeting( - self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + statusCode = result.get_status_code() - print( - "DefaultExtension check_greeting: status:" - + str(statusCode) - ) + ten_env.log_info(f"check_greeting: status: {str(statusCode)}") respCmd = CmdResult.create(StatusCode.OK) respCmd.set_property_string("detail", "received response") - print("DefaultExtension create respCmd") + ten_env.log_info("create respCmd") ten_env.return_result(respCmd, receivedCmd) - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None statusCode = result.get_status_code() detail = result.get_property_string("detail") - print( - "DefaultExtension check_hello: status:" - + str(statusCode) - + " detail:" - + detail + ten_env.log_info( + f"check_hello: status: {str(statusCode)}, detail: {detail}" ) # Send a command to go extension. new_cmd = Cmd.create("greeting") ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_greeting(ten_env, result, receivedCmd), + lambda ten_env, result, error: self.check_greeting( + ten_env, result, error, receivedCmd + ), ) def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd") - cmd_json = cmd.to_json() - print("DefaultExtension on_cmd json: " + cmd_json) + ten_env.log_debug("on_cmd: " + cmd_json) new_cmd = Cmd.create("hello") new_cmd.set_property_from_json("test", '"testValue2"') test_value = new_cmd.get_property_to_json("test") - print("DefaultExtension on_cmd test_value: " + test_value) + ten_env.log_info(f"on_cmd test_value: {test_value}") # Send command to a cpp extension. ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd) + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/go_app_partially_cythonize/go_app_partially_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx b/tests/ten_runtime/integration/python/go_app_partially_cythonize/go_app_partially_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx index 61443ce859..192eda9775 100644 --- a/tests/ten_runtime/integration/python/go_app_partially_cythonize/go_app_partially_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx +++ b/tests/ten_runtime/integration/python/go_app_partially_cythonize/go_app_partially_cythonize_app/ten_packages/extension/default_extension_python/extension.pyx @@ -4,13 +4,13 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) + +# import debugpy +# debugpy.listen(5678) +# debugpy.wait_for_client() + +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -26,62 +26,78 @@ class DefaultExtension(Extension): ten_env.set_property_from_json("testKey2", '"testValue2"') testValue = ten_env.get_property_to_json("testKey") testValue2 = ten_env.get_property_to_json("testKey2") - print("testValue: ", testValue, " testValue2: ", testValue2) + ten_env.log_info(f"testValue: {testValue}, testValue2: {testValue2}") ten_env.on_start_done() def on_stop(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_stop") + ten_env.log_debug("on_stop") ten_env.on_stop_done() def on_deinit(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_deinit") + ten_env.log_debug("on_deinit") ten_env.on_deinit_done() def check_greeting( - self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + statusCode = result.get_status_code() - print( - "DefaultExtension check_greeting: status:" - + str(statusCode) - ) + ten_env.log_info(f"check_greeting: status: {str(statusCode)}") respCmd = CmdResult.create(StatusCode.OK) respCmd.set_property_string("detail", "received response") - print("DefaultExtension create respCmd") + ten_env.log_info("create respCmd") ten_env.return_result(respCmd, receivedCmd) - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None statusCode = result.get_status_code() detail = result.get_property_string("detail") - print( - "DefaultExtension check_hello: status:" - + str(statusCode) - + " detail:" - + detail + ten_env.log_info( + f"check_hello: status: {str(statusCode)}, detail: {detail}" ) # Send a command to go extension. new_cmd = Cmd.create("greeting") ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_greeting(ten_env, result, receivedCmd), + lambda ten_env, result, error: self.check_greeting( + ten_env, result, error, receivedCmd + ), ) def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd") - cmd_json = cmd.to_json() - print("DefaultExtension on_cmd json: " + cmd_json) + ten_env.log_debug("on_cmd: " + cmd_json) new_cmd = Cmd.create("hello") new_cmd.set_property_from_json("test", '"testValue2"') test_value = new_cmd.get_property_to_json("test") - print("DefaultExtension on_cmd test_value: " + test_value) + ten_env.log_info(f"on_cmd test_value: {test_value}") # Send command to a cpp extension. ten_env.send_cmd( - new_cmd, lambda ten_env, result: self.check_hello(ten_env, result, cmd) + new_cmd, + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/go_app_python/go_app_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/go_app_python/go_app_python_app/ten_packages/extension/default_extension_python/extension.py index 1df155b5c4..192eda9775 100644 --- a/tests/ten_runtime/integration/python/go_app_python/go_app_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/go_app_python/go_app_python_app/ten_packages/extension/default_extension_python/extension.py @@ -9,13 +9,8 @@ # debugpy.listen(5678) # debugpy.wait_for_client() -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -44,12 +39,19 @@ def on_deinit(self, ten_env: TenEnv) -> None: ten_env.on_deinit_done() def check_greeting( - self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + statusCode = result.get_status_code() - ten_env.log_info( - f"check_greeting: status: {str(statusCode)}" - ) + ten_env.log_info(f"check_greeting: status: {str(statusCode)}") respCmd = CmdResult.create(StatusCode.OK) respCmd.set_property_string("detail", "received response") @@ -57,7 +59,17 @@ def check_greeting( ten_env.return_result(respCmd, receivedCmd) - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None statusCode = result.get_status_code() detail = result.get_property_string("detail") ten_env.log_info( @@ -68,8 +80,8 @@ def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): new_cmd = Cmd.create("greeting") ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_greeting( - ten_env, result, receivedCmd + lambda ten_env, result, error: self.check_greeting( + ten_env, result, error, receivedCmd ), ) @@ -85,5 +97,7 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: # Send command to a cpp extension. ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/multi_process_python/multi_process_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/multi_process_python/multi_process_python_app/ten_packages/extension/default_extension_python/extension.py index bb7e21b086..235a718289 100644 --- a/tests/ten_runtime/integration/python/multi_process_python/multi_process_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/multi_process_python/multi_process_python_app/ten_packages/extension/default_extension_python/extension.py @@ -6,13 +6,8 @@ # from multiprocessing import Process import os -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -40,7 +35,18 @@ def on_deinit(self, ten_env: TenEnv) -> None: ten_env.log_info("on_deinit") ten_env.on_deinit_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error + + assert result is not None + statusCode = result.get_status_code() detail = result.get_property_string("detail") ten_env.log_info( @@ -70,7 +76,9 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/multiple_results_python_1/multiple_results_python_1_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/multiple_results_python_1/multiple_results_python_1_app/ten_packages/extension/default_extension_python/extension.py index 447ae8e719..2bfb77878e 100644 --- a/tests/ten_runtime/integration/python/multiple_results_python_1/multiple_results_python_1_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/multiple_results_python_1/multiple_results_python_1_app/ten_packages/extension/default_extension_python/extension.py @@ -4,13 +4,8 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -23,7 +18,18 @@ def on_init(self, ten_env: TenEnv) -> None: ten_env.log_debug("on_init") ten_env.on_init_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error + + assert result is not None + self.__counter += 1 if self.__counter == 1: @@ -45,7 +51,9 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: new_cmd = Cmd.create("hello") ten_env.send_cmd_ex( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) elif self.name == "default_extension_python_2": ten_env.log_info("create respCmd 1") diff --git a/tests/ten_runtime/integration/python/multiple_results_python_2/multiple_results_python_2_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/multiple_results_python_2/multiple_results_python_2_app/ten_packages/extension/default_extension_python/extension.py index 65506b3828..6a181a01eb 100644 --- a/tests/ten_runtime/integration/python/multiple_results_python_2/multiple_results_python_2_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/multiple_results_python_2/multiple_results_python_2_app/ten_packages/extension/default_extension_python/extension.py @@ -4,13 +4,8 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -24,7 +19,18 @@ def on_init(self, ten_env: TenEnv) -> None: ten_env.log_debug("on_init") ten_env.on_init_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error + + assert result is not None + self.__counter += 1 if self.__counter == 1: @@ -45,7 +51,9 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: new_cmd = Cmd.create("hello") ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) elif self.name == "default_extension_python_2": ten_env.log_info("create respCmd") diff --git a/tests/ten_runtime/integration/python/multiple_results_python_3/multiple_results_python_3_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/multiple_results_python_3/multiple_results_python_3_app/ten_packages/extension/default_extension_python/extension.py index 7fa743899b..2724365031 100644 --- a/tests/ten_runtime/integration/python/multiple_results_python_3/multiple_results_python_3_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/multiple_results_python_3/multiple_results_python_3_app/ten_packages/extension/default_extension_python/extension.py @@ -4,13 +4,8 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from ten import ( - Extension, - TenEnv, - Cmd, - StatusCode, - CmdResult, -) +from typing import Optional +from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, TenError class DefaultExtension(Extension): @@ -24,7 +19,18 @@ def on_init(self, ten_env: TenEnv) -> None: ten_env.log_debug("on_init") ten_env.on_init_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error + + assert result is not None + self.__counter += 1 if self.__counter == 1: @@ -48,7 +54,9 @@ def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: new_cmd = Cmd.create("hello") ten_env.send_cmd_ex( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) elif self.name == "default_extension_python_2": ten_env.log_info("create respCmd") diff --git a/tests/ten_runtime/integration/python/no_dest_async_python/BUILD.gn b/tests/ten_runtime/integration/python/no_dest_async_python/BUILD.gn new file mode 100644 index 0000000000..c0ecef1ead --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_async_python/BUILD.gn @@ -0,0 +1,50 @@ +# +# Copyright © 2024 Agora +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0, with certain conditions. +# Refer to the "LICENSE" file in the root directory for more information. +# +import("//build/ten_runtime/feature/test.gni") +import("//build/ten_runtime/ten.gni") + +ten_package_test_prepare_app("no_dest_async_python_app") { + src_app = "default_app_python" + src_app_language = "python" + generated_app_src_root_dir_name = "no_dest_async_python_app" + + replace_files_after_install_app = [ + "no_dest_async_python_app/manifest.json", + "no_dest_async_python_app/property.json", + ] + + replace_files_after_install_all = [ "no_dest_async_python_app/ten_packages/extension/default_async_extension_python/extension.py" ] + + if (ten_enable_package_manager) { + deps = [ + "//core/src/ten_manager", + "//packages/core_apps/default_app_python:upload_default_app_python_to_server", + "//packages/core_extensions/default_async_extension_python:upload_default_async_extension_python_to_server", + "//packages/example_extensions/simple_echo_cpp:upload_simple_echo_cpp_to_server", + "//packages/example_extensions/simple_http_server_cpp:upload_simple_http_server_cpp_to_server", + ] + } +} + +ten_package_test_prepare_auxiliary_resources( + "no_dest_async_python_test_files") { + resources = [ + "//tests/ten_runtime/integration/common=>common", + "__init__.py", + "test_case.py", + ] + if (enable_sanitizer) { + resources += [ "//tests/ten_runtime/integration/tools/use_asan_lib_marker=>use_asan_lib_marker" ] + } +} + +group("no_dest_async_python") { + deps = [ + ":no_dest_async_python_app", + ":no_dest_async_python_test_files", + ] +} diff --git a/tests/ten_runtime/integration/python/no_dest_async_python/__init__.py b/tests/ten_runtime/integration/python/no_dest_async_python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/manifest.json b/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/manifest.json new file mode 100644 index 0000000000..42e715454d --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/manifest.json @@ -0,0 +1,24 @@ +{ + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.4.2" + }, + { + "type": "extension", + "name": "simple_http_server_cpp", + "version": "0.1.0" + }, + { + "type": "extension", + "name": "default_async_extension_python", + "version": "0.4.2" + }, + { + "type": "extension", + "name": "simple_echo_cpp", + "version": "0.1.0" + } + ] +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/property.json b/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/property.json new file mode 100644 index 0000000000..f154d32dd2 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/property.json @@ -0,0 +1,66 @@ +{ + "_ten": { + "log_level": 2, + "predefined_graphs": [ + { + "name": "default", + "auto_start": true, + "nodes": [ + { + "type": "extension", + "name": "simple_http_server_cpp", + "addon": "simple_http_server_cpp", + "extension_group": "default_extension_group", + "property": { + "server_port": 8002 + } + }, + { + "type": "extension", + "name": "default_async_extension_python", + "addon": "default_async_extension_python", + "extension_group": "test" + }, + { + "type": "extension", + "name": "simple_echo_cpp", + "addon": "simple_echo_cpp", + "extension_group": "default_extension_group" + } + ], + "connections": [ + { + "extension_group": "default_extension_group", + "extension": "simple_http_server_cpp", + "cmd": [ + { + "name": "test", + "dest": [ + { + "extension_group": "test", + "extension": "default_async_extension_python" + } + ] + } + ] + }, + { + "extension_group": "test", + "extension": "default_async_extension_python", + "cmd": [ + { + "name": "hello", + "dest": [ + { + "extension_group": "default_extension_group", + "extension": "simple_echo_cpp" + } + ] + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/ten_packages/extension/default_async_extension_python/extension.py b/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/ten_packages/extension/default_async_extension_python/extension.py new file mode 100644 index 0000000000..29f0f3e3be --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_async_python/no_dest_async_python_app/ten_packages/extension/default_async_extension_python/extension.py @@ -0,0 +1,89 @@ +# +# Copyright © 2024 Agora +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0, with certain conditions. +# Refer to the "LICENSE" file in the root directory for more information. +# +import asyncio +from ten import AsyncExtension, AsyncTenEnv, Cmd, Data, AudioFrame, VideoFrame + + +class DefaultAsyncExtension(AsyncExtension): + async def on_configure(self, ten_env: AsyncTenEnv) -> None: + await asyncio.sleep(0.5) + + async def on_init(self, ten_env: AsyncTenEnv) -> None: + await asyncio.sleep(0.5) + + async def on_start(self, ten_env: AsyncTenEnv) -> None: + await asyncio.sleep(0.5) + ten_env.log_debug("on_start") + + assert ten_env.is_property_exist("unknown_field") is False + + ten_env.set_property_string("string_field", "hello") + assert ten_env.is_property_exist("string_field") is True + + exception_caught = False + try: + result = await ten_env.send_cmd(Cmd.create("unknown_cmd")) + except Exception as e: + ten_env.log_error(f"Error: {e}") + exception_caught = True + + assert exception_caught is True + exception_caught = False + + try: + result = await ten_env.send_data(Data.create("unknown_data")) + except Exception as e: + ten_env.log_error(f"Error: {e}") + exception_caught = True + + assert exception_caught is True + exception_caught = False + + try: + result = await ten_env.send_audio_frame( + AudioFrame.create("unknown_audio_frame") + ) + except Exception as e: + ten_env.log_error(f"Error: {e}") + exception_caught = True + + assert exception_caught is True + exception_caught = False + + try: + result = await ten_env.send_video_frame( + VideoFrame.create("unknown_video_frame") + ) + except Exception as e: + ten_env.log_error(f"Error: {e}") + exception_caught = True + + assert exception_caught is True + + async def on_deinit(self, ten_env: AsyncTenEnv) -> None: + await asyncio.sleep(0.5) + + async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None: + cmd_json = cmd.to_json() + ten_env.log_debug(f"on_cmd: {cmd_json}") + + # Mock async operation, e.g. network, file I/O. + await asyncio.sleep(0.5) + + assert ten_env.is_cmd_connected("hello") is True + assert ten_env.is_cmd_connected("unknown_cmd") is False + + # Send a new command to other extensions and wait for the result. The + # result will be returned to the original sender. + new_cmd = Cmd.create("hello") + cmd_result = await ten_env.send_cmd(new_cmd) + await ten_env.return_result(cmd_result, cmd) + + async def on_stop(self, ten_env: AsyncTenEnv) -> None: + ten_env.log_debug("on_stop") + + await asyncio.sleep(0.5) diff --git a/tests/ten_runtime/integration/python/no_dest_async_python/test_case.py b/tests/ten_runtime/integration/python/no_dest_async_python/test_case.py new file mode 100644 index 0000000000..896ba6c7d7 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_async_python/test_case.py @@ -0,0 +1,136 @@ +""" +Test no_dest_async_python. +""" + +import subprocess +import os +import sys +from sys import stdout +from .common import http + + +def http_request(): + return http.post( + "http://127.0.0.1:8002/", + { + "_ten": { + "name": "test", + }, + }, + ) + + +def test_no_dest_async_python(): + """Test client and app server.""" + base_path = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(base_path, "../../../../../") + + # Create virtual environment. + venv_dir = os.path.join(base_path, "venv") + subprocess.run([sys.executable, "-m", "venv", venv_dir]) + + my_env = os.environ.copy() + + # Set the required environment variables for the test. + my_env["PYTHONMALLOC"] = "malloc" + my_env["PYTHONDEVMODE"] = "1" + + # Launch virtual environment. + my_env["VIRTUAL_ENV"] = venv_dir + my_env["PATH"] = os.path.join(venv_dir, "bin") + os.pathsep + my_env["PATH"] + + if sys.platform == "win32": + print("test_no_dest_async_python doesn't support win32") + assert False + elif sys.platform == "darwin": + # client depends on some libraries in the TEN app. + my_env["DYLD_LIBRARY_PATH"] = os.path.join( + base_path, "no_dest_async_python_app/lib" + ) + else: + # client depends on some libraries in the TEN app. + my_env["LD_LIBRARY_PATH"] = os.path.join( + base_path, "no_dest_async_python_app/lib" + ) + + app_root_path = os.path.join(base_path, "no_dest_async_python_app") + + tman_install_cmd = [ + os.path.join(root_dir, "ten_manager/bin/tman"), + "--config-file", + os.path.join(root_dir, "tests/local_registry/config.json"), + "install", + ] + + tman_install_process = subprocess.Popen( + tman_install_cmd, + stdout=stdout, + stderr=subprocess.STDOUT, + env=my_env, + cwd=app_root_path, + ) + tman_install_process.wait() + + bootstrap_cmd = os.path.join( + base_path, "no_dest_async_python_app/bin/bootstrap" + ) + + bootstrap_process = subprocess.Popen( + bootstrap_cmd, stdout=stdout, stderr=subprocess.STDOUT, env=my_env + ) + bootstrap_process.wait() + + if sys.platform == "linux": + if os.path.exists(os.path.join(base_path, "use_asan_lib_marker")): + libasan_path = os.path.join( + base_path, + "no_dest_async_python_app/ten_packages/system/ten_runtime/lib/libasan.so", + ) + + if os.path.exists(libasan_path): + my_env["LD_PRELOAD"] = libasan_path + + server_cmd = os.path.join( + base_path, "no_dest_async_python_app/bin/start" + ) + + server = subprocess.Popen( + server_cmd, + stdout=stdout, + stderr=subprocess.STDOUT, + env=my_env, + cwd=app_root_path, + ) + + is_started = http.is_app_started("127.0.0.1", 8002, 30) + if not is_started: + print( + "The no_dest_async_python is not started after 30 seconds." + ) + + server.kill() + exit_code = server.wait() + print("The exit code of no_dest_async_python: ", exit_code) + + assert exit_code == 0 + assert 0 + + return + + try: + resp = http_request() + assert resp != 500 + print(resp) + + finally: + is_stopped = http.stop_app("127.0.0.1", 8002, 30) + if not is_stopped: + print( + "The no_dest_async_python can not stop after 30 seconds." + ) + server.kill() + + exit_code = server.wait() + print("The exit code of no_dest_async_python: ", exit_code) + + assert exit_code == 0 diff --git a/tests/ten_runtime/integration/python/no_dest_python/BUILD.gn b/tests/ten_runtime/integration/python/no_dest_python/BUILD.gn new file mode 100644 index 0000000000..98eac65164 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_python/BUILD.gn @@ -0,0 +1,50 @@ +# +# Copyright © 2024 Agora +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0, with certain conditions. +# Refer to the "LICENSE" file in the root directory for more information. +# +import("//build/ten_runtime/feature/test.gni") +import("//build/ten_runtime/ten.gni") + +ten_package_test_prepare_app("no_dest_python_app") { + src_app = "default_app_python" + src_app_language = "python" + generated_app_src_root_dir_name = "no_dest_python_app" + + replace_files_after_install_app = [ + "no_dest_python_app/manifest.json", + "no_dest_python_app/property.json", + ] + + replace_files_after_install_all = [ "no_dest_python_app/ten_packages/extension/default_extension_python/extension.py" ] + + if (ten_enable_package_manager) { + deps = [ + "//core/src/ten_manager", + "//packages/core_apps/default_app_python:upload_default_app_python_to_server", + "//packages/core_extensions/default_extension_python:upload_default_extension_python_to_server", + "//packages/example_extensions/simple_echo_cpp:upload_simple_echo_cpp_to_server", + "//packages/example_extensions/simple_http_server_cpp:upload_simple_http_server_cpp_to_server", + ] + } +} + +ten_package_test_prepare_auxiliary_resources("no_dest_python_test_files") { + resources = [ + "//tests/ten_runtime/integration/common=>common", + "__init__.py", + "test_case.py", + ] + if (enable_sanitizer) { + resources += [ "//tests/ten_runtime/integration/tools/use_asan_lib_marker=>use_asan_lib_marker" ] + } +} + +group("no_dest_python") { + deps = [ + ":no_dest_python_app", + ":no_dest_python_test_files", + "//tests/ten_runtime/integration/python:copy_pytest_ini_for_ten_runtime_python_integration_test", + ] +} diff --git a/tests/ten_runtime/integration/python/no_dest_python/__init__.py b/tests/ten_runtime/integration/python/no_dest_python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/manifest.json b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/manifest.json new file mode 100644 index 0000000000..fa745ae225 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/manifest.json @@ -0,0 +1,24 @@ +{ + "dependencies": [ + { + "type": "system", + "name": "ten_runtime", + "version": "0.4.2" + }, + { + "type": "extension", + "name": "simple_http_server_cpp", + "version": "0.1.0" + }, + { + "type": "extension", + "name": "default_extension_python", + "version": "0.4.2" + }, + { + "type": "extension", + "name": "simple_echo_cpp", + "version": "0.1.0" + } + ] +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/property.json b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/property.json new file mode 100644 index 0000000000..3a8f23fac0 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/property.json @@ -0,0 +1,66 @@ +{ + "_ten": { + "log_level": 2, + "predefined_graphs": [ + { + "name": "default", + "auto_start": true, + "nodes": [ + { + "type": "extension", + "name": "simple_http_server_cpp", + "addon": "simple_http_server_cpp", + "extension_group": "default_extension_group", + "property": { + "server_port": 8002 + } + }, + { + "type": "extension", + "name": "default_extension_python", + "addon": "default_extension_python", + "extension_group": "test" + }, + { + "type": "extension", + "name": "simple_echo_cpp", + "addon": "simple_echo_cpp", + "extension_group": "default_extension_group" + } + ], + "connections": [ + { + "extension_group": "default_extension_group", + "extension": "simple_http_server_cpp", + "cmd": [ + { + "name": "test", + "dest": [ + { + "extension_group": "test", + "extension": "default_extension_python" + } + ] + } + ] + }, + { + "extension_group": "test", + "extension": "default_extension_python", + "cmd": [ + { + "name": "hello", + "dest": [ + { + "extension_group": "default_extension_group", + "extension": "simple_echo_cpp" + } + ] + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/ten_packages/extension/default_extension_python/addon.py b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/ten_packages/extension/default_extension_python/addon.py new file mode 100644 index 0000000000..699fa5a885 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/ten_packages/extension/default_extension_python/addon.py @@ -0,0 +1,19 @@ +# +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0. +# See the LICENSE file for more information. +# +from ten import ( + Addon, + register_addon_as_extension, + TenEnv, +) +from .extension import DefaultExtension + + +@register_addon_as_extension("default_extension_python") +class DefaultExtensionAddon(Addon): + + def on_create_instance(self, ten_env: TenEnv, name: str, context) -> None: + ten_env.log_info("DefaultExtensionAddon on_create_instance") + ten_env.on_create_instance_done(DefaultExtension(name), context) diff --git a/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/ten_packages/extension/default_extension_python/extension.py new file mode 100644 index 0000000000..bc364755e9 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_python/no_dest_python_app/ten_packages/extension/default_extension_python/extension.py @@ -0,0 +1,133 @@ +# +# Copyright © 2024 Agora +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0, with certain conditions. +# Refer to the "LICENSE" file in the root directory for more information. +# +from typing import Optional +from ten import ( + Extension, + TenEnv, + Cmd, + Data, + AudioFrame, + VideoFrame, + StatusCode, + CmdResult, + LogLevel, + TenError, +) + + +class DefaultExtension(Extension): + def __init__(self, name: str) -> None: + super().__init__(name) + self.name = name + + def on_configure(self, ten_env: TenEnv) -> None: + ten_env.log( + LogLevel.DEBUG, f"DefaultExtension on_init, name: {self.name}" + ) + assert self.name == "default_extension_python" + + ten_env.init_property_from_json('{"testKey": "testValue"}') + ten_env.on_configure_done() + + def handle_error(self, ten_env: TenEnv, error: Optional[TenError]) -> None: + assert error is not None + ten_env.log_error("DefaultExtension handle_error: " + error.err_msg()) + + self.no_dest_error_recv_count += 1 + if self.no_dest_error_recv_count == 4: + ten_env.on_start_done() + + def on_start(self, ten_env: TenEnv) -> None: + ten_env.log_debug("on_start") + + self.no_dest_error_recv_count = 0 + + ten_env.set_property_from_json("testKey2", '"testValue2"') + testValue = ten_env.get_property_to_json("testKey") + testValue2 = ten_env.get_property_to_json("testKey2") + print("testValue: ", testValue, " testValue2: ", testValue2) + + # Send an unconnected command + cmd = Cmd.create("unconnected_cmd") + ten_env.send_cmd( + cmd, + lambda ten_env, result, error: self.handle_error(ten_env, error), + ) + + # Send an unconnected data + data = Data.create("unconnected_data") + ten_env.send_data( + data, + lambda ten_env, error: self.handle_error(ten_env, error), + ) + + # Send an unconnected video frame + video_frame = VideoFrame.create("unconnected_video_frame") + ten_env.send_video_frame( + video_frame, + lambda ten_env, error: self.handle_error(ten_env, error), + ) + + # Send an unconnected audio frame + audio_frame = AudioFrame.create("unconnected_audio_frame") + ten_env.send_audio_frame( + audio_frame, + lambda ten_env, error: self.handle_error(ten_env, error), + ) + + def on_stop(self, ten_env: TenEnv) -> None: + print("DefaultExtension on_stop") + ten_env.on_stop_done() + + def on_deinit(self, ten_env: TenEnv) -> None: + print("DefaultExtension on_deinit") + ten_env.on_deinit_done() + + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error.err_msg() + + assert result is not None + + statusCode = result.get_status_code() + detail = result.get_property_string("detail") + print( + "DefaultExtension check_hello: status:" + + str(statusCode) + + " detail:" + + detail + ) + + respCmd = CmdResult.create(StatusCode.OK) + respCmd.set_property_string("detail", detail + " nbnb") + print("DefaultExtension create respCmd") + + ten_env.return_result(respCmd, receivedCmd) + + def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: + print("DefaultExtension on_cmd") + + cmd_json = cmd.to_json() + print("DefaultExtension on_cmd json: " + cmd_json) + + new_cmd = Cmd.create("hello") + new_cmd.set_property_from_json("test", '"testValue2"') + test_value = new_cmd.get_property_to_json("test") + print("DefaultExtension on_cmd test_value: " + test_value) + + ten_env.send_cmd( + new_cmd, + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), + ) diff --git a/tests/ten_runtime/integration/python/no_dest_python/test_case.py b/tests/ten_runtime/integration/python/no_dest_python/test_case.py new file mode 100644 index 0000000000..e3d62e2816 --- /dev/null +++ b/tests/ten_runtime/integration/python/no_dest_python/test_case.py @@ -0,0 +1,128 @@ +""" +Test no_dest_python. +""" + +import subprocess +import os +import sys +from sys import stdout +from .common import http + + +def http_request(): + return http.post( + "http://127.0.0.1:8002/", + { + "_ten": { + "name": "test", + }, + }, + ) + + +def test_no_dest_python(): + """Test client and app server.""" + base_path = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(base_path, "../../../../../") + + # Create virtual environment. + venv_dir = os.path.join(base_path, "venv") + subprocess.run([sys.executable, "-m", "venv", venv_dir]) + + my_env = os.environ.copy() + + # Set the required environment variables for the test. + my_env["PYTHONMALLOC"] = "malloc" + my_env["PYTHONDEVMODE"] = "1" + + # Launch virtual environment. + my_env["VIRTUAL_ENV"] = venv_dir + my_env["PATH"] = os.path.join(venv_dir, "bin") + os.pathsep + my_env["PATH"] + + if sys.platform == "win32": + print("test_no_dest_python doesn't support win32") + assert False + elif sys.platform == "darwin": + # client depends on some libraries in the TEN app. + my_env["DYLD_LIBRARY_PATH"] = os.path.join( + base_path, "no_dest_python_app/lib" + ) + else: + # client depends on some libraries in the TEN app. + my_env["LD_LIBRARY_PATH"] = os.path.join( + base_path, "no_dest_python_app/lib" + ) + + app_root_path = os.path.join(base_path, "no_dest_python_app") + + tman_install_cmd = [ + os.path.join(root_dir, "ten_manager/bin/tman"), + "--config-file", + os.path.join(root_dir, "tests/local_registry/config.json"), + "install", + ] + + tman_install_process = subprocess.Popen( + tman_install_cmd, + stdout=stdout, + stderr=subprocess.STDOUT, + env=my_env, + cwd=app_root_path, + ) + tman_install_process.wait() + + bootstrap_cmd = os.path.join(base_path, "no_dest_python_app/bin/bootstrap") + + bootstrap_process = subprocess.Popen( + bootstrap_cmd, stdout=stdout, stderr=subprocess.STDOUT, env=my_env + ) + bootstrap_process.wait() + + if sys.platform == "linux": + if os.path.exists(os.path.join(base_path, "use_asan_lib_marker")): + libasan_path = os.path.join( + base_path, + "no_dest_python_app/ten_packages/system/ten_runtime/lib/libasan.so", + ) + + if os.path.exists(libasan_path): + my_env["LD_PRELOAD"] = libasan_path + + server_cmd = os.path.join(base_path, "no_dest_python_app/bin/start") + + server = subprocess.Popen( + server_cmd, + stdout=stdout, + stderr=subprocess.STDOUT, + env=my_env, + cwd=app_root_path, + ) + + is_started = http.is_app_started("127.0.0.1", 8002, 30) + if not is_started: + print("The no_dest_python is not started after 30 seconds.") + + server.kill() + exit_code = server.wait() + print("The exit code of no_dest_python: ", exit_code) + + assert exit_code == 0 + assert 0 + + return + + try: + resp = http_request() + assert resp != 500 + print(resp) + + finally: + is_stopped = http.stop_app("127.0.0.1", 8002, 30) + if not is_stopped: + print("The no_dest_python can not stop after 30 seconds.") + server.kill() + + exit_code = server.wait() + print("The exit code of no_dest_python: ", exit_code) + + assert exit_code == 0 diff --git a/tests/ten_runtime/integration/python/resp_handler_yield_python/resp_handler_yield_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/resp_handler_yield_python/resp_handler_yield_python_app/ten_packages/extension/default_extension_python/extension.py index 94c412e0d1..f38dc30fce 100644 --- a/tests/ten_runtime/integration/python/resp_handler_yield_python/resp_handler_yield_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/resp_handler_yield_python/resp_handler_yield_python_app/ten_packages/extension/default_extension_python/extension.py @@ -19,7 +19,7 @@ def __init__(self, name: str) -> None: self.name = name def on_configure(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_init, name", self.name) + ten_env.log_info(f"on_init, name: {self.name}") assert self.name == "default_extension_python" ten_env.init_property_from_json('{"testKey": "testValue"}') @@ -31,25 +31,30 @@ def on_start(self, ten_env: TenEnv) -> None: ten_env.set_property_from_json("testKey2", '"testValue2"') testValue = ten_env.get_property_to_json("testKey") testValue2 = ten_env.get_property_to_json("testKey2") - print("testValue: ", testValue, " testValue2: ", testValue2) + ten_env.log_info(f"testValue: {testValue}, testValue2: {testValue2}") ten_env.on_start_done() def on_stop(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_stop") + ten_env.log_info("on_stop") ten_env.on_stop_done() def on_deinit(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_deinit") + ten_env.log_info("on_deinit") ten_env.on_deinit_done() def echo_cmd_result_generator(self, ten_env: TenEnv, cmd: Cmd): - print("DefaultExtension send_cmd_yeild") + ten_env.log_info("send_cmd_yeild") q = queue.Queue(maxsize=1) def task(): - ten_env.send_cmd(cmd, lambda ten_env, result: q.put(result)) + ten_env.send_cmd( + cmd, + lambda ten_env, result, error: q.put( + error if error is not None else result + ), + ) t = threading.Thread(target=task) t.start() @@ -57,7 +62,7 @@ def task(): yield q.get() def __handle_cmd(self, ten_env: TenEnv, cmd: Cmd): - print("DefaultExtension __handle_cmd") + ten_env.log_info("__handle_cmd") cmd_hello = Cmd.create("hello") @@ -65,13 +70,16 @@ def __handle_cmd(self, ten_env: TenEnv, cmd: Cmd): result = next(generator) + if isinstance(result, Exception): + raise result + ten_env.return_result(result, cmd) def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd") + ten_env.log_info("on_cmd") cmd_json = cmd.to_json() - print("DefaultExtension on_cmd json: " + cmd_json) + ten_env.log_info("on_cmd json: " + cmd_json) self.thread = threading.Thread( target=self.__handle_cmd, args=(ten_env, cmd) diff --git a/tests/ten_runtime/integration/python/send_cmd_python/send_cmd_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/send_cmd_python/send_cmd_python_app/ten_packages/extension/default_extension_python/extension.py index 92916cdad6..aa7e796298 100644 --- a/tests/ten_runtime/integration/python/send_cmd_python/send_cmd_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/send_cmd_python/send_cmd_python_app/ten_packages/extension/default_extension_python/extension.py @@ -4,7 +4,16 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -from ten import Extension, TenEnv, Cmd, StatusCode, CmdResult, LogLevel +from typing import Optional +from ten import ( + Extension, + TenEnv, + Cmd, + StatusCode, + CmdResult, + LogLevel, + TenError, +) class DefaultExtension(Extension): @@ -27,46 +36,56 @@ def on_start(self, ten_env: TenEnv) -> None: ten_env.set_property_from_json("testKey2", '"testValue2"') testValue = ten_env.get_property_to_json("testKey") testValue2 = ten_env.get_property_to_json("testKey2") - print("testValue: ", testValue, " testValue2: ", testValue2) + ten_env.log_info(f"testValue: {testValue}, testValue2: {testValue2}") ten_env.on_start_done() def on_stop(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_stop") + ten_env.log_info("on_stop") ten_env.on_stop_done() def on_deinit(self, ten_env: TenEnv) -> None: - print("DefaultExtension on_deinit") + ten_env.log_info("on_deinit") ten_env.on_deinit_done() - def check_hello(self, ten_env: TenEnv, result: CmdResult, receivedCmd: Cmd): + def check_hello( + self, + ten_env: TenEnv, + result: Optional[CmdResult], + error: Optional[TenError], + receivedCmd: Cmd, + ): + if error is not None: + assert False, error + + assert result is not None + statusCode = result.get_status_code() detail = result.get_property_string("detail") - print( - "DefaultExtension check_hello: status:" - + str(statusCode) - + " detail:" - + detail + ten_env.log_info( + "check_hello: status:" + str(statusCode) + " detail:" + detail ) respCmd = CmdResult.create(StatusCode.OK) respCmd.set_property_string("detail", detail + " nbnb") - print("DefaultExtension create respCmd") + ten_env.log_info("create respCmd") ten_env.return_result(respCmd, receivedCmd) def on_cmd(self, ten_env: TenEnv, cmd: Cmd) -> None: - print("DefaultExtension on_cmd") + ten_env.log_info("on_cmd") cmd_json = cmd.to_json() - print("DefaultExtension on_cmd json: " + cmd_json) + ten_env.log_info("on_cmd json: " + cmd_json) new_cmd = Cmd.create("hello") new_cmd.set_property_from_json("test", '"testValue2"') test_value = new_cmd.get_property_to_json("test") - print("DefaultExtension on_cmd test_value: " + test_value) + ten_env.log_info("on_cmd test_value: " + test_value) ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result, cmd), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error, cmd + ), ) diff --git a/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_basic.py b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_basic.py index 7217ca166a..7e5d893d57 100644 --- a/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_basic.py +++ b/tests/ten_runtime/integration/python/standalone_test_python/default_extension_python/tests/test_basic.py @@ -5,11 +5,29 @@ # Refer to the "LICENSE" file in the root directory for more information. # from pathlib import Path -from ten import ExtensionTester, TenEnvTester, Cmd, CmdResult, StatusCode +from typing import Optional +from ten import ( + ExtensionTester, + TenEnvTester, + Cmd, + CmdResult, + StatusCode, + TenError, +) class ExtensionTesterBasic(ExtensionTester): - def check_hello(self, ten_env: TenEnvTester, result: CmdResult): + def check_hello( + self, + ten_env: TenEnvTester, + result: Optional[CmdResult], + error: Optional[TenError], + ): + if error is not None: + assert False, error + + assert result is not None + statusCode = result.get_status_code() print("receive hello_world, status:" + str(statusCode)) @@ -22,7 +40,9 @@ def on_start(self, ten_env: TenEnvTester) -> None: print("send hello_world") ten_env.send_cmd( new_cmd, - lambda ten_env, result: self.check_hello(ten_env, result), + lambda ten_env, result, error: self.check_hello( + ten_env, result, error + ), ) print("tester on_start_done") diff --git a/tests/ten_runtime/integration/python/two_async_exts_one_group_python/two_async_exts_one_group_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/two_async_exts_one_group_python/two_async_exts_one_group_python_app/ten_packages/extension/default_extension_python/extension.py index b1c8e767cb..6b6779b097 100644 --- a/tests/ten_runtime/integration/python/two_async_exts_one_group_python/two_async_exts_one_group_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/two_async_exts_one_group_python/two_async_exts_one_group_python_app/ten_packages/extension/default_extension_python/extension.py @@ -42,7 +42,7 @@ async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None: # result will be returned to the original sender. new_cmd = Cmd.create("hello") cmd_result = await ten_env.send_cmd(new_cmd) - ten_env.return_result(cmd_result, cmd) + await ten_env.return_result(cmd_result, cmd) async def on_stop(self, ten_env: AsyncTenEnv) -> None: ten_env.log_debug("on_stop") diff --git a/tests/ten_runtime/integration/python/two_async_exts_python/two_async_exts_python_app/ten_packages/extension/default_extension_python/extension.py b/tests/ten_runtime/integration/python/two_async_exts_python/two_async_exts_python_app/ten_packages/extension/default_extension_python/extension.py index 84ce80c070..3d01d25001 100644 --- a/tests/ten_runtime/integration/python/two_async_exts_python/two_async_exts_python_app/ten_packages/extension/default_extension_python/extension.py +++ b/tests/ten_runtime/integration/python/two_async_exts_python/two_async_exts_python_app/ten_packages/extension/default_extension_python/extension.py @@ -43,7 +43,7 @@ async def on_cmd(self, ten_env: AsyncTenEnv, cmd: Cmd) -> None: # result will be returned to the original sender. new_cmd = Cmd.create("hello") cmd_result = await ten_env.send_cmd(new_cmd) - ten_env.return_result(cmd_result, cmd) + await ten_env.return_result(cmd_result, cmd) async def on_stop(self, ten_env: AsyncTenEnv) -> None: ten_env.log_debug("on_stop")