advanced-python-homework/equipment/turtle_device.py

87 lines
3.8 KiB
Python
Raw Permalink Normal View History

2023-11-15 23:55:37 +03:00
from turtle import Turtle, bye
2023-10-25 20:56:57 +03:00
from typing import Any, Collection, Optional
2023-11-06 21:18:57 +03:00
from types import FunctionType, MethodType
2023-10-25 20:56:57 +03:00
from inspect import getfullargspec
2023-10-13 22:33:07 +03:00
from controls.device import SynchronyDevice
2023-10-25 20:56:57 +03:00
from controls.device import ActionDescriptor, TraitDescriptor
from controls.device import DeviceLifecycleState, DeviceError
2023-10-13 22:33:07 +03:00
class TurtleDevice(SynchronyDevice):
2023-10-25 20:56:57 +03:00
def __init__(self) -> None:
super().__init__()
self._device: Optional[Turtle] = None
self._logical_traits: dict[str, Optional[Any]] = {}
self._actions: list[str] = []
def open(self) -> None:
"""Opens a new device and initializes all logical traits with `None`"""
if self.state == DeviceLifecycleState.OPEN:
return
super().open()
self._device = Turtle()
self._logical_traits = {k.name: None for k in self.trait_descriptors()}
self._actions = [a.name for a in self.action_descriptors()]
def close(self) -> None:
"""Closes the device and invalidates all logial traits"""
if self.state == DeviceLifecycleState.CLOSE:
return
self._logical_traits = {k: None for k in self._logical_traits.keys()}
2023-11-15 23:55:37 +03:00
bye()
2023-10-25 20:56:57 +03:00
super().close()
def action_descriptors(self) -> Collection[ActionDescriptor]:
2023-11-06 21:18:57 +03:00
if self.state != DeviceLifecycleState.OPEN:
raise DeviceError("Device is not opened")
def get_args(name: str) -> dict[str, type]:
spec = getfullargspec(getattr(self._device, name))
2023-10-25 20:56:57 +03:00
return {arg: spec.annotations.get(arg, object) for arg in spec.args}
2023-11-06 21:18:57 +03:00
actions = (filter(lambda name: not name.startswith("_"),
filter(lambda name: isinstance(getattr(self._device, name), MethodType),
dir(self._device)))) # lisp :3
return [ActionDescriptor(name=action,arguments=get_args(action)) for action in actions]
2023-10-25 20:56:57 +03:00
def trait_descriptors(self) -> Collection[TraitDescriptor]:
2023-11-06 21:18:57 +03:00
if self.state != DeviceLifecycleState.OPEN:
raise DeviceError("Device is not opened")
2023-10-25 20:56:57 +03:00
traits = dict(filter(lambda i: not i[0].startswith('_'),
filter(lambda i: not isinstance(i[1], FunctionType),
self._device.__dict__.items())))
return [TraitDescriptor(name=trait) for trait in traits.keys()]
def read(self, trait_name: str) -> Any:
if self.state != DeviceLifecycleState.OPEN:
raise DeviceError("Device is not opened")
self._logical_traits[trait_name] = self._device.__dict__[trait_name]
return self._device.__dict__[trait_name]
def write(self, trait_name: str, value: Any) -> bool:
if self.state != DeviceLifecycleState.OPEN:
raise DeviceError("Device is not opened")
if trait_name not in self._logical_traits.keys():
raise KeyError(f"\"{trait_name}\" is not a trait of {self.__class__}")
return super().write(trait_name, value)
2023-10-13 22:33:07 +03:00
2023-10-25 20:56:57 +03:00
def execute(self, action_name: str, *args, **kwargs):
if self.state != DeviceLifecycleState.OPEN:
raise DeviceError("Device is not opened")
if action_name not in self._actions:
raise KeyError(f"\"{action_name}\" is not an action of {self.__class__}")
2023-11-06 21:18:57 +03:00
return getattr(self._device, action_name)(*args, **kwargs)
2023-10-13 22:33:07 +03:00
2023-10-25 20:56:57 +03:00
def invalidate(self, trait_name: str):
if trait_name in self._logical_traits:
self._logical_traits[trait_name] = None
else:
raise KeyError(f"\"{trait_name}\" is not a trait of {self.__class__}")
2023-10-13 22:33:07 +03:00
2023-10-25 20:56:57 +03:00
def __getitem__(self, trait_name: str) -> Optional[Any]:
if trait_name in self._logical_traits:
return self._logical_traits[trait_name]
else:
raise KeyError(f"\"{trait_name}\" is not a trait of {self.__class__}")