moved code for coherence

This commit is contained in:
2026-02-12 22:21:56 +01:00
parent 04ee42eaa7
commit 210c2d6231
12 changed files with 8 additions and 11 deletions

46
src/py_func/__init__.py Executable file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python
import multiprocessing
from py_func.tm import _init_api, _remote_print
from testium.interpreter.utils.stdout_redirect import stdio_redir
class TcpStdOut:
def __init__(self):
pass
def write(self, s: str) -> None:
_remote_print(s)
def flush(self):
pass
def main():
# This line sets the method for the "Process" function. It is required for Linux
# support of the test dialogs.
multiprocessing.set_start_method('spawn')
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--ip", type=str, help="Ip address or hostname to listen to",
default="localhost")
parser.add_argument("-p", "--port", type=int, help="port to listen to",
default=9000)
parser.add_argument("-t", "--timeout", type=float, help="Timeout waiting for connection",
default=10)
parser.add_argument("-v", "--verbose", action='store_true', help="port to listen to")
args = parser.parse_args()
thrd_api = _init_api(args.ip, args.port, args.timeout)
# redirect I/O
outstream = TcpStdOut()
stdio_redir.redirect(outstream)
# debug the server
if args.verbose:
thrd_api.dbg_out = stdio_redir.ini_stdout
thrd_api.start()
try:
while thrd_api.is_alive():
thrd_api.join(1)
finally:
stdio_redir.restore()

23
src/py_func/__main__.py Normal file
View File

@@ -0,0 +1,23 @@
from pathlib import Path
import sys
import traceback
def exception_handler(typ_exc, value, trbk):
"""Testium Exception handling"""
print("An unmanaged exception occured", exc_info=(typ_exc, value, trbk))
print(f"Critical failure : '{value}'.")
tb = traceback.format_exception(typ_exc, value, trbk)
print("".join(tb))
sys.excepthook = exception_handler
p = Path(__file__)
p = p.parent / ".."
p = p.resolve()
sys.path.append(p)
from py_func import main
if __name__ == '__main__':
main()

73
src/py_func/func_call.py Normal file
View File

@@ -0,0 +1,73 @@
import sys
import importlib.util
import inspect
from pathlib import Path
import importlib
import traceback
from testium.interpreter.utils.tum_except import ETUMRuntimeError, ETUMSyntaxError
from py_func import tm
def abs_path_from_file(file):
abs_file_path = Path(file)
if not abs_file_path.is_absolute():
tdir = tm.gd("test_directory")
abs_file_path = Path(tdir) / abs_file_path
abs_file_path = abs_file_path.resolve()
return abs_file_path
def func_module(file):
abs_file_path = abs_path_from_file(file)
if not abs_file_path.is_file():
raise ETUMSyntaxError(f'"{abs_file_path}" file could not be found')
try:
sys.path.append(str(abs_file_path.parent))
spec = importlib.util.spec_from_file_location(
abs_file_path.stem,
abs_file_path
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
except:
tb = traceback.format_exc()
raise ETUMRuntimeError("Error importing file.\n" + "\n".join(tb.splitlines()))
return module
def func_exec(file: str, func_name: str, params: list, verbose: bool=True):
"""Executes a python function and returns its result and reported values
"""
reported_values = {}
mod = func_module(file)
if verbose:
print("Function executed from '{}'".format(
inspect.getabsfile(mod)))
# check of the FunctionItem descendants
fitems = []
for name, cls in inspect.getmembers(mod):
if inspect.isclass(cls):
if issubclass(cls, tm.FunctionItem):
fitems.append(cls)
oldstyle = True
if len(fitems) > 0:
for fitem in fitems:
if fitem.__name__ == func_name:
oldstyle = False
o = fitem()
res = o.exec(*params)
reported_values = o.reportedValues()
if oldstyle:
res = getattr(mod, func_name)(*params)
reported_values.update({'returned': res})
return res, reported_values

68
src/py_func/handle.py Normal file
View File

@@ -0,0 +1,68 @@
import random
import os
import sys
import time
import platform
import math
import json
import traceback
from testium.interpreter.utils.jrpc import JsonRpcSrv
from testium.interpreter.utils.tum_except import ETUMRuntimeError, print_exception
from py_func.func_call import func_exec
class FuncHandler(JsonRpcSrv):
def handle_request(self, method, params):
try:
if method == "func_call":
try:
file = params["file"]
fname = params["fname"]
args = params["params"]
verb = params["verbose"]
try:
res, reported_values = func_exec(file, fname, args, verb)
return {
"result": {
"returned_value": res,
"reported_values": reported_values,
}
}
except TypeError as e:
return {
"error": f'In file "{file}",\ncalling function "{fname}" with bad arguments ({args}).\nMessage is "{str(e)}"'
}
except Exception as e:
tb = traceback.format_exc()
return {"error": "\n".join(tb.splitlines())}
except Exception as e:
tb = traceback.format_exc()
return {
"error": f"bad jrpc req handler 'func_call' arguments ({"\n".join(tb.splitlines())}). To be reported to testium support team."
}
if method == "eval":
try:
value = params["value"]
try:
res = eval(value)
return {"result": res}
except Exception as e:
# eval can crash
return {
"error": f"Evaluation of '{value}' failed with message:\n "+str(e)
}
except Exception as e:
tb = traceback.format_exc()
return {
"error": f"bad jrpc req handler 'eval' arguments ({"\n".join(tb.splitlines())}). To be reported to testium support team."
}
else:
return {
"error": f"unknown RPC request ({method}). To be reported to testium support team."
}
except:
print_exception(str(*sys.exc_info()))
raise ETUMRuntimeError(
"python Function item execution error. To be reported to testium support team."
)

101
src/py_func/tm.py Normal file
View File

@@ -0,0 +1,101 @@
"""tm — proxy module exposing remote-callable API functions.
This module dynamically exposes functions listed in
``interpreter.utils.api.SUPPORTED_API``. Each exposed function is a
thin wrapper that forwards the call to a running ``FuncHandler``
instance (stored in ``_func_call_thread``).
Typical usage:
>>> from testium.py_func import tm
>>> handler = tm._init_api(port)
>>> tm.some_api_function(args)
Only after ``_init_api`` has been called will API functions be able to
send requests to the remote handler; otherwise an ``ETUMRuntimeError``
is raised.
"""
import sys
from py_func.handle import FuncHandler
from testium.interpreter.utils.tum_except import ETUMRuntimeError
from testium.interpreter.utils.api import SUPPORTED_API
thismodule = sys.modules[__name__]
# Shared FuncHandler instance used to forward API calls. Remains None
# until `_init_api` is invoked.
_func_call_thread = None
###############################################################################
# Dynamically create module-level functions for each supported API name.
# Each generated function shares the implementation of `api_call` but
# has a distinct name used as the remote action identifier.
def _make_api(name):
def _wrapper(*params):
if _func_call_thread is not None:
res = _func_call_thread.call(name, params)
if "result" in res:
ret_val = res["result"]
elif "error" in res:
raise ETUMRuntimeError(f"api call to 'tm.{name}' failed with error '{res["error"]}'")
else:
raise ETUMRuntimeError("api call failure in jrpc client to be reported to testium support team.")
return ret_val
else:
raise ETUMRuntimeError("api not initialized")
_wrapper.__name__ = name
return _wrapper
for k in SUPPORTED_API:
setattr(thismodule, k, _make_api(k))
def _init_api(host, port, timeout):
"""Start and initialize the remote function handler.
Starts a ``FuncHandler`` bound to ``port``, runs it and blocks until
it signals readiness.
Args:
port: port number or identifier passed to ``FuncHandler``.
Returns:
The initialized ``FuncHandler`` instance assigned to
``_func_call_thread``.
"""
global _func_call_thread
_func_call_thread = FuncHandler(host, port, timeout=timeout)
return _func_call_thread
###############################################################################
def _remote_print(*values):
"""Forward print-like output to the remote handler.
If a ``_func_call_thread`` is available, this function calls the
handler with action name ``"print"`` and the provided values. Errors
during forwarding are ignored because printing is best-effort.
"""
if _func_call_thread is not None:
try:
_func_call_thread.call("print", values)
except:
# Best-effort: ignore forwarding failures
pass
###############################################################################
class FunctionItem():
"""Class allowing extended capabilities of function."""
module_count = 0
def __init__(self):
self._reported_value = {}
def reportValue(self, key, value):
self._reported_value[key] = value
def reportedValues(self):
return self._reported_value
def exec(self):
pass