This contains the initial DeriveUnit implementation, including signature validation, this initial implentation only supports ValueDescriptors in the signature alongside the SignalDescriptor and returns a single ValueDescriptor The worker thread still needs to be defined and started but the offline processing has already been validate to work as a first prototype
108 lines
3.6 KiB
Python
108 lines
3.6 KiB
Python
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
|
|
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from collections.abc import Callable
|
|
from enum import IntEnum
|
|
import inspect
|
|
from queue import Empty, Queue
|
|
from threading import Thread
|
|
import threading
|
|
from time import sleep
|
|
from typing import Literal
|
|
from uuid import UUID
|
|
|
|
from dynalab_core.errors import (
|
|
DeriveUnitArgsMismatchError,
|
|
DeriveUnitInvalidSignatureError,
|
|
)
|
|
from dynalab_core.protocols.packets.data import ValueDescriptor
|
|
from dynalab_core.protocols.packets.handshake import SignalDescriptor
|
|
|
|
|
|
class ParserState(IntEnum):
|
|
INIT = 0
|
|
VALUES_DONE = 1
|
|
SIGNAL_DONE = 2
|
|
|
|
|
|
class DeriveUnit:
|
|
def __init__(
|
|
self,
|
|
unit_uuid: UUID,
|
|
processing_function: Callable[..., ValueDescriptor],
|
|
input_signals: list[SignalDescriptor],
|
|
return_signal: SignalDescriptor,
|
|
) -> None:
|
|
self._id: UUID = unit_uuid
|
|
self._worker_thread: Thread | None = None
|
|
self._process_function: Callable = processing_function
|
|
self._return_signal: SignalDescriptor = return_signal
|
|
self._stop_event: threading.Event = threading.Event()
|
|
self._input_queue: Queue[list[ValueDescriptor]] = []
|
|
self._output_queue: Queue[ValueDescriptor] = []
|
|
|
|
self._num_input_args: int = 0
|
|
self._parser_state: int = ParserState.INIT
|
|
sig = inspect.signature(self._process_function)
|
|
|
|
for name, param in sig.parameters.items():
|
|
if self._parser_state == ParserState.INIT:
|
|
if param.annotation is ValueDescriptor:
|
|
self._num_input_args += 1
|
|
else:
|
|
self._parser_state = ParserState.VALUES_DONE
|
|
|
|
if self._parser_state == ParserState.VALUES_DONE:
|
|
if param.annotation is SignalDescriptor:
|
|
self._parser_state = ParserState.SIGNAL_DONE
|
|
continue
|
|
else:
|
|
raise DeriveUnitInvalidSignatureError
|
|
|
|
if self._parser_state == ParserState.SIGNAL_DONE:
|
|
raise DeriveUnitInvalidSignatureError
|
|
|
|
if sig.return_annotation is not ValueDescriptor:
|
|
raise DeriveUnitInvalidSignatureError
|
|
|
|
print(f"Signature validated with {self._num_input_args} input values")
|
|
# TODO: Define and start worker thread
|
|
|
|
def worker_function(self) -> None:
|
|
while not self._stop_event.is_set():
|
|
try:
|
|
input_args = self._input_queue.get(timeout=0.1)
|
|
except Empty:
|
|
continue
|
|
|
|
if len(input_args) != self._num_input_args:
|
|
continue
|
|
|
|
self._output_queue.put(
|
|
self._process_function(*input_args, self._return_signal)
|
|
)
|
|
|
|
def put_data(self, input: list[ValueDescriptor]) -> None:
|
|
self._input_queue.put(input)
|
|
|
|
def get_output(self) -> ValueDescriptor | None:
|
|
try:
|
|
return self._output_queue.get_nowait()
|
|
except Empty:
|
|
return None
|
|
|
|
def get_all_output(self) -> list[ValueDescriptor]:
|
|
output: list[ValueDescriptor] = []
|
|
|
|
while True:
|
|
try:
|
|
output.append(self._output_queue.get_nowait())
|
|
except Empty:
|
|
return output
|
|
|
|
def process_offline(self, input_args: list[ValueDescriptor]) -> ValueDescriptor:
|
|
if len(input_args) == self._num_input_args:
|
|
return self._process_function(*input_args, self._return_signal)
|
|
raise DeriveUnitArgsMismatchError
|