Files
controller-wisetopdataserver/python/biopro/exp_pro/protocol.py
T

478 lines
13 KiB
Python
Raw Normal View History

2021-12-20 14:52:55 +08:00
"""This module actually implement the content which declared at biopro.exp_pro.
"""
import inspect
from typing import Type, Callable
from biopro.util.json import JsonSerialize, JSON_OBJECT
from . import *
if True:
# prevent import reorder
pass
# noinspection PyUnresolvedReferences
from biopro.devlib.device import DeviceInfo
@overload
def exp_options(f) -> property:
pass
@overload
def exp_options(name: str, desp: str = None) -> Callable[[Any], property]:
pass
def exp_options(f, *,
name: str = None,
desp: str = None,
opt_type=None,
readonly=False):
"""**decorator**, mark a function as a experimental protocol parameter.
This decorator will return a property. It will hide the original function *f*
into the property getter.
This property getter will get the hidden attribute from `ExpProtocol`_. If not found,
it will set this attribute a default value got from the result of *f*. getter will
set the value to the hidden attribute. deleter will reset the value.
:param f:
:param name: parameter name
:param desp: parameter description
:param opt_type:
:param readonly: readonly property, cannot not be set or reset
:return: property
"""
def _doc(doc: Optional[str]) -> str:
if doc is None:
return ''
for line in doc.split('\n'):
if len(line) > 0:
break
else:
return ''
if '.' in line:
line = line[:line.index('.') + 1]
return line
def _inner(f):
v = opt_type
if v is None:
s = inspect.signature(f)
v = s.return_annotation
_name = name if name is not None else f.__name__
_desp = desp if desp is not None else _doc(f.__doc__)
return _gen_exp_property(f, _name, _desp, v, readonly=readonly)
if f is None:
return _inner
else:
return _inner(f)
def _gen_exp_property(f, name: str, desp: str, opt_type=int, readonly=False):
a = '__' + name
# TODO do value type checking?
def getter(self):
if not hasattr(self, a):
setattr(self, a, f(self))
return getattr(self, a)
def setter(self, value):
setattr(self, a, value)
def deleter(self):
setattr(self, a, f(self))
if readonly:
ret = property(getter)
else:
ret = property(getter, setter, deleter)
getter._exp_name = name
getter._exp_desp = desp
getter._exp_type = opt_type
getter._exp_default = f
return ret
def exp_protocol(name: str, template: str = None):
"""**decorator**, register ExpProtocol a export protocol"""
def _inner(cls):
cls._exp_name = name
cls._exp_template = template
return cls
return _inner
class ExpOption(JsonSerialize):
"""
**json format**
::
exp_option = {
'name': str
'desp': str
}
"""
__slots__ = '__p', '__t'
def __init__(self, p: property, t: 'ExpProtocol'):
self.__p = p
self.__t = t
@property
def name(self) -> str:
"""option name"""
# noinspection PyProtectedMember
return self.__p.fget._exp_name
@property
def desp(self) -> str:
"""option description"""
# noinspection PyProtectedMember
return self.__p.fget._exp_desp
@property
def value_type(self) -> Type[Any]:
"""option value type"""
# noinspection PyProtectedMember
return self.__p.fget._exp_type
def get(self) -> Any:
"""get value from this option"""
return getattr(self.__t, self.name)
def set(self, value: Any):
"""set value to this option"""
if self.__p.fset is not None:
setattr(self.__t, self.name, value)
def reset(self):
"""reset this option back to default value"""
if self.__p.fdel is not None:
delattr(self.__t, self.name)
def default(self):
"""default value of this options"""
# noinspection PyProtectedMember
return self.__p.fget._exp_default(self)
def __str__(self):
return type(self.__t).__name__ + '.' + self.name
def __repr__(self):
return type(self.__t).__name__ + '.' + self.name
def as_json(self) -> JSON_OBJECT:
raise {
'name': self.name,
'desp': self.desp,
'type': self.value_type.__name__
}
class ReadonlyExpOption(ExpOption):
__slots__ = ()
def __init__(self, o: ExpOption):
super().__init__(o.__p, o.__t)
def set(self, value: Any):
"""set value to this option"""
pass
def reset(self):
"""reset this option back to default value"""
pass
# noinspection PyMethodMayBeStatic, PyUnusedLocal
class ExpProtocol(metaclass=abc.ABCMeta):
# protocol method (class method)
@classmethod
def exp_name(cls) -> str:
return getattr(cls, '_exp_name')
@classmethod
def exp_desp(cls) -> Optional[str]:
return cls.__doc__
@classmethod
def exp_template(cls) -> Optional[str]:
return getattr(cls, '_exp_template', None)
@classmethod
def exp_filepath(cls) -> Optional[str]:
return getattr(cls, '_exp_filepath', None)
# protocol property, shortcut for protocol method
@property
def pro_name(self) -> str:
return self.exp_name() + ':' + str(self.pro_id)
@property
def pro_class(self) -> str:
return type(self).__name__
@property
def pro_template(self) -> Optional[str]:
return self.exp_template()
@property
def pro_filepath(self) -> str:
return self.exp_filepath()
@property
def pro_id(self) -> int:
return getattr(self, '_exp_id', -1)
@property
def pro_error(self) -> Optional[BaseException]:
return getattr(self, '_exp_error', None)
# predefine protocol method
def pro_ensure(self, api: ExpApi) -> bool:
return True
def pro_setup(self, api: ExpApi):
self.pro_ensure(api)
def pro_step(self, api: ExpApi) -> int:
pass
def pro_teardown(self, api: ExpApi):
pass
def pro_on_error(self, api: ExpApi, status: int) -> bool:
return False
# protocol utility methods
def pro_reset(self, *options: str):
for p in self.pro_options():
if len(options) == 0 or p.name in options:
p.reset()
def pro_options(self) -> List[ExpOption]:
ret = []
cls = type(self)
for attr_name in dir(cls):
if attr_name.startswith('_'):
continue
attr_value = getattr(cls, attr_name)
if isinstance(attr_value, property):
if hasattr(attr_value.fget, '_exp_name'):
ret.append(ExpOption(attr_value, self))
return ret
def pro_option(self, option: str) -> ExpOption:
cls = type(self)
attr_value = getattr(cls, option, None)
if isinstance(attr_value, property):
if hasattr(attr_value.fget, '_exp_name'):
return ExpOption(attr_value, self)
raise RuntimeError('options not found : ' + option)
def __str__(self):
return self.pro_name
def __repr__(self):
return self.pro_name
class ExpProtocolInfo(JsonSerialize):
"""
**json format**
::
protocol_info = {
'name': str
'desp': str
'template: str
}
"""
__slots__ = '__protocol',
def __init__(self, protocol: Type[ExpProtocol]):
self.__protocol = protocol
@property
def exp_name(self) -> str:
return self.__protocol.exp_name()
@property
def exp_class(self) -> str:
return self.__protocol.__name__
@property
def exp_desp(self) -> Optional[str]:
return self.__protocol.exp_desp()
@property
def exp_template(self) -> Optional[str]:
return self.__protocol.exp_template()
@property
def exp_filepath(self) -> Optional[str]:
return self.__protocol.exp_filepath()
def new_protocol(self) -> ExpProtocol:
return self.__protocol()
def as_json(self) -> JSON_OBJECT:
return {
'name': self.__protocol.exp_name(),
'desp': self.__protocol.exp_desp(),
'template': self.__protocol.exp_template()
}
class CompletedExpApi(ExpApi, metaclass=abc.ABCMeta):
@abc.abstractmethod
def shutdown(self):
pass
class ExpApiBaseImpl(CompletedExpApi):
def __init__(self, parent: ExpApi) -> None:
self.__parent = parent
def log_verbose(self, *message: Any):
self.__parent.log_verbose(*message)
def log_info(self, *message: Any):
self.__parent.log_info(*message)
def log_warn(self, *message: Any):
self.__parent.log_warn(*message)
@property
def user(self) -> str:
return self.__parent.user
@property
def root_directory(self) -> str:
return self.__parent.root_directory
@property
def use_directory(self) -> str:
return self.__parent.use_directory
def change_directory(self, path: str) -> str:
return self.__parent.change_directory(path)
def shutdown(self):
if isinstance(self.__parent, CompletedExpApi):
self.__parent.shutdown()
def current_timestamp(self) -> int:
return self.__parent.current_timestamp()
def current_datetime(self) -> datetime.datetime:
return self.__parent.current_datetime()
def schedule_time(self, timestamp: Union[float, datetime.datetime]):
self.__parent.schedule_time(timestamp)
def schedule_sleep(self, sleep: Union[float, datetime.timedelta]):
self.__parent.schedule_sleep(sleep)
def make_device_info(self, *, device_name: str = None, mac_address: Union[str, ADDRESS] = None) -> DeviceInfo:
return self.__parent.make_device_info(device_name=device_name, mac_address=mac_address)
def found_device(self) -> List[DeviceInfo]:
return self.__parent.found_device()
def connected_device(self) -> List[DeviceInfo]:
return self.__parent.connected_device()
def ensure_device(self, *device_list: DeviceInfo, status: int = STATUS_INIT) -> bool:
# we ensure that all of the implement of ExpApi.ensure_device has parameter status.
# noinspection PyArgumentList
return self.__parent.ensure_device(*device_list, status=status)
def all_device(self) -> List[DeviceInfo]:
return self.__parent.all_device()
def get_device(self, device: int) -> DeviceInfo:
return self.__parent.get_device(device)
def release_device(self, device_list: Optional[List[DeviceInfo]] = None, disconnect=True):
self.__parent.release_device(device_list, disconnect=disconnect)
def apply_setup(self, name: str):
self.__parent.apply_setup(name)
def device_get_parameter(self, device: Union[int, DeviceInfo], parameter: str) -> Any:
return self.__parent.device_get_parameter(device, parameter)
def device_set_parameter(self, device: Union[int, DeviceInfo], *parameter):
self.__parent.device_set_parameter(device, *parameter)
def device_to_value(self, device: Union[int, DeviceInfo], parameter: str, value: Any) -> Any:
return self.__parent.device_to_value(device, parameter, value)
def device_to_parameter(self, device: Union[int, DeviceInfo], parameter: str, value: Any) -> Any:
return self.__parent.device_to_parameter(device, parameter, value)
def device_call_instruction(self, device: Union[int, DeviceInfo], instruction: str):
self.__parent.device_call_instruction(device, instruction)
def device_use_filepath(self, device: Union[int, DeviceInfo]) -> Optional[str]:
return self.__parent.device_use_filepath(device)
def device_set_filepath(self, device: Union[int, DeviceInfo], filename: str) -> str:
return self.__parent.device_set_filepath(device, filename)
def device_unset_filepath(self, device: Union[int, DeviceInfo]) -> Optional[str]:
return self.__parent.device_unset_filepath(device)
def set_complete(self):
self.__parent.set_complete()
def set_error(self, message: str):
self.__parent.set_error(message)
def file_export(self,
ftype: str,
*filepath: Union[str, DeviceInfo],
**options) -> Promise[List[Union[str, DeviceInfo]]]:
return self.__parent.file_export(ftype, *filepath, **options)
def file_archive(self,
filename: str,
*filepath: Union[str, DeviceInfo],
ftype: Optional[str] = None) -> Promise[str]:
return self.__parent.file_archive(filename, *filepath, ftype=ftype)