-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathscan.py
More file actions
103 lines (73 loc) · 3.34 KB
/
scan.py
File metadata and controls
103 lines (73 loc) · 3.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
from collections.abc import Callable, Coroutine
from types import MethodType
from fastcs.controllers import BaseController
from fastcs.logging import bind_logger
from fastcs.methods.method import Controller_T, Method
logger = bind_logger(logger_name=__name__)
UnboundScanCallback = Callable[[Controller_T], Coroutine[None, None, None]]
"""A Scan callback that is unbound and must be called with a `Controller` instance"""
ScanCallback = Callable[[], Coroutine[None, None, None]]
"""A Scan callback that is bound and can be called without `self`"""
class Scan(Method[BaseController]):
"""A `Controller` `Method` that will be called periodically in the background.
This class contains a function that is bound to a specific `Controller` instance and
is callable outside of the class context, without an explicit `self` parameter.
Calling an instance of this class will call the bound `Controller` method.
"""
def __init__(self, fn: ScanCallback, period: float):
super().__init__(fn)
self._period = period
@property
def period(self):
return self._period
def _validate(self, fn: ScanCallback) -> None:
super()._validate(fn)
if not len(self.parameters) == 0:
raise TypeError("Scan method cannot have arguments")
async def __call__(self):
return await self._fn()
@property
def fn(self):
async def scan():
try:
return await self._fn()
except Exception:
logger.error("Scan update loop stopped", fn=self._fn)
raise
return scan
class UnboundScan(Method[Controller_T]):
"""A wrapper of an unbound `Controller` method to be bound into a `Scan`.
This generic class stores an unbound `Controller` method - effectively a function
that takes an instance of a specific `Controller` type (`Controller_T`). Instances
of this class can be added at `Controller` definition, either manually or with use
of the `scan` wrapper, to register the method to be included in the API of the
`Controller`. When the `Controller` is instantiated, these instances will be bound
to the instance, creating a `Scan` instance.
"""
def __init__(self, fn: UnboundScanCallback[Controller_T], period: float) -> None:
super().__init__(fn)
self._period = period
@property
def period(self):
return self._period
def _validate(self, fn: UnboundScanCallback[Controller_T]) -> None:
super()._validate(fn)
if not len(self.parameters) == 1:
raise TypeError("Scan method cannot have arguments")
def bind(self, controller: Controller_T) -> Scan:
return Scan(MethodType(self.fn, controller), self._period)
def __call__(self):
raise NotImplementedError(
"Method must be bound to a controller instance to be callable"
)
def scan(
period: float,
) -> Callable[[UnboundScanCallback[Controller_T]], UnboundScan[Controller_T]]:
"""Decorator to register a `Controller` method as a `Scan`
The `Scan` method will be called periodically in the background.
"""
if period <= 0:
raise ValueError("Scan method must have a positive scan period")
def wrapper(fn: UnboundScanCallback[Controller_T]) -> UnboundScan[Controller_T]:
return UnboundScan(fn, period)
return wrapper