import inspect
from types import UnionType
from typing import Any, Union, get_args, get_origin
from apparser.core import BaseUi
from apparser.instructions import BaseInstruction
from apparser.instructions.debuggers import BaseDebugger, Debugger
from apparser.instructions.ui.algorithms.base import BaseAlgorithm
from apparser.instructions.utils import get_instruction_by_id
def _check_instruction(instruction: tuple[int, list[Any]]) -> tuple[int, list[Any]]:
if not isinstance(instruction, tuple):
raise TypeError(f"instruction must be tuple")
if len(instruction) < 2:
raise TypeError(f"instruction must have at least 2 arguments")
instruction_id, instruction_args = instruction
if not isinstance(instruction_id, int):
raise TypeError(f"instruction_id must be int")
if not isinstance(instruction_args, list):
raise TypeError(f"instruction_args must be list")
return instruction_id, instruction_args
def _is_annotation_matches(annotation: Any, value: Any) -> bool:
if annotation is inspect.Parameter.empty:
return False
if annotation is Any:
return True
annotation_origin = get_origin(annotation)
annotation_args = get_args(annotation)
if annotation_origin in (Union, UnionType):
return any(_is_annotation_matches(i, value) for i in annotation_args)
if isinstance(annotation_origin, type):
return isinstance(value, annotation_origin)
if isinstance(annotation, type):
return isinstance(value, annotation)
return annotation is type(value)
[docs]
class IdsAlgorithm(BaseAlgorithm):
"""Resolve and execute instructions by their numeric identifiers."""
def __init__(self,
instructions: list[tuple[int, list[Any]]],
attributes: list[Any],
debugger: BaseDebugger | bool = True):
"""Initialize an identifier-based instruction algorithm.
:param instructions: Sequence of instruction identifiers with their arguments.
:type instructions: list[tuple[int, list[Any]]]
:param attributes: Attribute values matched to instruction parameters by type.
:type attributes: list[Any]
:param debugger: Debugger used to wrap instruction execution. If True, use Debugger. If False do not wrap instruction execution.
:type debugger: BaseDebugger | bool
:raises TypeError: If ``debugger`` has an invalid type.
"""
if not isinstance(attributes, list):
raise TypeError("attributes must be list")
if not isinstance(instructions, list):
raise TypeError("instructions must be list")
if not isinstance(debugger, BaseDebugger) and not isinstance(debugger, bool):
raise TypeError(f"debugger must be a bool or BaseDebugger")
if debugger == True:
debugger = Debugger()
elif debugger == False:
debugger = None
self.__debugger = debugger
self.__instructions = instructions
self.__attributes = attributes
@property
def id(self) -> int:
return 1501
def __form_args(self, instruction: BaseInstruction, *additional_args) -> dict[str, Any]:
result = {}
function_signature = inspect.signature(instruction.perform)
for arg in function_signature.parameters.values():
for a in self.__attributes + list(additional_args):
if _is_annotation_matches(arg.annotation, a):
result[arg.name] = a
return result
[docs]
def add_instruction(self, instruction: tuple[int, list[Any]]):
_check_instruction(instruction)
self.__instructions.append(instruction)
@property
def instructions(self) -> list[tuple[int, list[Any]]]:
return self.__instructions