Skip to content

Commit

Permalink
feat: add error handler for send_msg in Python binding (#355)
Browse files Browse the repository at this point in the history
* feat: add TenError in result handler in Python binding

* feat: add error handler in send_data/audio/video in Python

* fix: refine codes

* feat: add more test cases

* feat: add error handler for return_xxx in python

* fix: refine codes

---------

Co-authored-by: Hu Yueh-Wei <[email protected]>
  • Loading branch information
sunxilin and halajohn authored Dec 2, 2024
1 parent fa55e28 commit 9a526dc
Show file tree
Hide file tree
Showing 54 changed files with 2,008 additions and 326 deletions.
23 changes: 23 additions & 0 deletions core/include_internal/ten_runtime/binding/python/common/error.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@
#include <stdbool.h>

#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);

Expand Down
2 changes: 2 additions & 0 deletions core/src/ten_runtime/binding/python/interface/ten/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -41,4 +42,5 @@
"LogLevel",
"ExtensionTester",
"TenEnvTester",
"TenError",
]
112 changes: 98 additions & 14 deletions core/src/ten_runtime/binding/python/interface/ten/async_ten_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -30,32 +34,112 @@ 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

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(
Expand Down
15 changes: 15 additions & 0 deletions core/src/ten_runtime/binding/python/interface/ten/error.py
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down Expand Up @@ -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(
Expand Down
49 changes: 33 additions & 16 deletions core/src/ten_runtime/binding/python/interface/ten/ten_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 7 additions & 2 deletions core/src/ten_runtime/binding/python/interface/ten/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
Loading

0 comments on commit 9a526dc

Please sign in to comment.