np-services 0.1.69__py3-none-any.whl → 0.1.70__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. np_services/__init__.py +8 -8
  2. np_services/open_ephys.py +377 -378
  3. np_services/protocols.py +185 -185
  4. np_services/proxies.py +1489 -1489
  5. np_services/resources/mvr_connector.py +260 -260
  6. np_services/resources/zro.py +325 -325
  7. np_services/scripts/pretest.py +412 -389
  8. np_services/stim_computer_theme_changer.py +41 -41
  9. np_services/utils.py +167 -167
  10. {np_services-0.1.69.dist-info → np_services-0.1.70.dist-info}/METADATA +5 -5
  11. np_services-0.1.70.dist-info/RECORD +15 -0
  12. {np_services-0.1.69.dist-info → np_services-0.1.70.dist-info}/WHEEL +2 -1
  13. {np_services-0.1.69.dist-info → np_services-0.1.70.dist-info}/entry_points.txt +1 -1
  14. np_services-0.1.70.dist-info/top_level.txt +1 -0
  15. np_services/.mypy_cache/.gitignore +0 -2
  16. np_services/.mypy_cache/3.9/@plugins_snapshot.json +0 -1
  17. np_services/.mypy_cache/3.9/__future__.data.json +0 -1
  18. np_services/.mypy_cache/3.9/__future__.meta.json +0 -1
  19. np_services/.mypy_cache/3.9/_ast.data.json +0 -1
  20. np_services/.mypy_cache/3.9/_ast.meta.json +0 -1
  21. np_services/.mypy_cache/3.9/_codecs.data.json +0 -1
  22. np_services/.mypy_cache/3.9/_codecs.meta.json +0 -1
  23. np_services/.mypy_cache/3.9/_collections_abc.data.json +0 -1
  24. np_services/.mypy_cache/3.9/_collections_abc.meta.json +0 -1
  25. np_services/.mypy_cache/3.9/_ctypes.data.json +0 -1
  26. np_services/.mypy_cache/3.9/_ctypes.meta.json +0 -1
  27. np_services/.mypy_cache/3.9/_decimal.data.json +0 -1
  28. np_services/.mypy_cache/3.9/_decimal.meta.json +0 -1
  29. np_services/.mypy_cache/3.9/_random.data.json +0 -1
  30. np_services/.mypy_cache/3.9/_random.meta.json +0 -1
  31. np_services/.mypy_cache/3.9/_socket.data.json +0 -1
  32. np_services/.mypy_cache/3.9/_socket.meta.json +0 -1
  33. np_services/.mypy_cache/3.9/_thread.data.json +0 -1
  34. np_services/.mypy_cache/3.9/_thread.meta.json +0 -1
  35. np_services/.mypy_cache/3.9/_typeshed/__init__.data.json +0 -1
  36. np_services/.mypy_cache/3.9/_typeshed/__init__.meta.json +0 -1
  37. np_services/.mypy_cache/3.9/_warnings.data.json +0 -1
  38. np_services/.mypy_cache/3.9/_warnings.meta.json +0 -1
  39. np_services/.mypy_cache/3.9/_weakref.data.json +0 -1
  40. np_services/.mypy_cache/3.9/_weakref.meta.json +0 -1
  41. np_services/.mypy_cache/3.9/_weakrefset.data.json +0 -1
  42. np_services/.mypy_cache/3.9/_weakrefset.meta.json +0 -1
  43. np_services/.mypy_cache/3.9/_winapi.data.json +0 -1
  44. np_services/.mypy_cache/3.9/_winapi.meta.json +0 -1
  45. np_services/.mypy_cache/3.9/abc.data.json +0 -1
  46. np_services/.mypy_cache/3.9/abc.meta.json +0 -1
  47. np_services/.mypy_cache/3.9/array.data.json +0 -1
  48. np_services/.mypy_cache/3.9/array.meta.json +0 -1
  49. np_services/.mypy_cache/3.9/atexit.data.json +0 -1
  50. np_services/.mypy_cache/3.9/atexit.meta.json +0 -1
  51. np_services/.mypy_cache/3.9/builtins.data.json +0 -1
  52. np_services/.mypy_cache/3.9/builtins.meta.json +0 -1
  53. np_services/.mypy_cache/3.9/codecs.data.json +0 -1
  54. np_services/.mypy_cache/3.9/codecs.meta.json +0 -1
  55. np_services/.mypy_cache/3.9/collections/__init__.data.json +0 -1
  56. np_services/.mypy_cache/3.9/collections/__init__.meta.json +0 -1
  57. np_services/.mypy_cache/3.9/collections/abc.data.json +0 -1
  58. np_services/.mypy_cache/3.9/collections/abc.meta.json +0 -1
  59. np_services/.mypy_cache/3.9/contextlib.data.json +0 -1
  60. np_services/.mypy_cache/3.9/contextlib.meta.json +0 -1
  61. np_services/.mypy_cache/3.9/ctypes/__init__.data.json +0 -1
  62. np_services/.mypy_cache/3.9/ctypes/__init__.meta.json +0 -1
  63. np_services/.mypy_cache/3.9/datetime.data.json +0 -1
  64. np_services/.mypy_cache/3.9/datetime.meta.json +0 -1
  65. np_services/.mypy_cache/3.9/decimal.data.json +0 -1
  66. np_services/.mypy_cache/3.9/decimal.meta.json +0 -1
  67. np_services/.mypy_cache/3.9/email/__init__.data.json +0 -1
  68. np_services/.mypy_cache/3.9/email/__init__.meta.json +0 -1
  69. np_services/.mypy_cache/3.9/email/charset.data.json +0 -1
  70. np_services/.mypy_cache/3.9/email/charset.meta.json +0 -1
  71. np_services/.mypy_cache/3.9/email/contentmanager.data.json +0 -1
  72. np_services/.mypy_cache/3.9/email/contentmanager.meta.json +0 -1
  73. np_services/.mypy_cache/3.9/email/errors.data.json +0 -1
  74. np_services/.mypy_cache/3.9/email/errors.meta.json +0 -1
  75. np_services/.mypy_cache/3.9/email/header.data.json +0 -1
  76. np_services/.mypy_cache/3.9/email/header.meta.json +0 -1
  77. np_services/.mypy_cache/3.9/email/message.data.json +0 -1
  78. np_services/.mypy_cache/3.9/email/message.meta.json +0 -1
  79. np_services/.mypy_cache/3.9/email/policy.data.json +0 -1
  80. np_services/.mypy_cache/3.9/email/policy.meta.json +0 -1
  81. np_services/.mypy_cache/3.9/enum.data.json +0 -1
  82. np_services/.mypy_cache/3.9/enum.meta.json +0 -1
  83. np_services/.mypy_cache/3.9/errno.data.json +0 -1
  84. np_services/.mypy_cache/3.9/errno.meta.json +0 -1
  85. np_services/.mypy_cache/3.9/fractions.data.json +0 -1
  86. np_services/.mypy_cache/3.9/fractions.meta.json +0 -1
  87. np_services/.mypy_cache/3.9/genericpath.data.json +0 -1
  88. np_services/.mypy_cache/3.9/genericpath.meta.json +0 -1
  89. np_services/.mypy_cache/3.9/importlib/__init__.data.json +0 -1
  90. np_services/.mypy_cache/3.9/importlib/__init__.meta.json +0 -1
  91. np_services/.mypy_cache/3.9/importlib/abc.data.json +0 -1
  92. np_services/.mypy_cache/3.9/importlib/abc.meta.json +0 -1
  93. np_services/.mypy_cache/3.9/importlib/machinery.data.json +0 -1
  94. np_services/.mypy_cache/3.9/importlib/machinery.meta.json +0 -1
  95. np_services/.mypy_cache/3.9/importlib/metadata/__init__.data.json +0 -1
  96. np_services/.mypy_cache/3.9/importlib/metadata/__init__.meta.json +0 -1
  97. np_services/.mypy_cache/3.9/io.data.json +0 -1
  98. np_services/.mypy_cache/3.9/io.meta.json +0 -1
  99. np_services/.mypy_cache/3.9/json/__init__.data.json +0 -1
  100. np_services/.mypy_cache/3.9/json/__init__.meta.json +0 -1
  101. np_services/.mypy_cache/3.9/json/decoder.data.json +0 -1
  102. np_services/.mypy_cache/3.9/json/decoder.meta.json +0 -1
  103. np_services/.mypy_cache/3.9/json/encoder.data.json +0 -1
  104. np_services/.mypy_cache/3.9/json/encoder.meta.json +0 -1
  105. np_services/.mypy_cache/3.9/logging/__init__.data.json +0 -1
  106. np_services/.mypy_cache/3.9/logging/__init__.meta.json +0 -1
  107. np_services/.mypy_cache/3.9/math.data.json +0 -1
  108. np_services/.mypy_cache/3.9/math.meta.json +0 -1
  109. np_services/.mypy_cache/3.9/mmap.data.json +0 -1
  110. np_services/.mypy_cache/3.9/mmap.meta.json +0 -1
  111. np_services/.mypy_cache/3.9/np_services/__init__.data.json +0 -1
  112. np_services/.mypy_cache/3.9/np_services/__init__.meta.json +0 -1
  113. np_services/.mypy_cache/3.9/np_services/config.data.json +0 -1
  114. np_services/.mypy_cache/3.9/np_services/config.meta.json +0 -1
  115. np_services/.mypy_cache/3.9/np_services/protocols.data.json +0 -1
  116. np_services/.mypy_cache/3.9/np_services/protocols.meta.json +0 -1
  117. np_services/.mypy_cache/3.9/np_services/zro.data.json +0 -1
  118. np_services/.mypy_cache/3.9/np_services/zro.meta.json +0 -1
  119. np_services/.mypy_cache/3.9/ntpath.data.json +0 -1
  120. np_services/.mypy_cache/3.9/ntpath.meta.json +0 -1
  121. np_services/.mypy_cache/3.9/numbers.data.json +0 -1
  122. np_services/.mypy_cache/3.9/numbers.meta.json +0 -1
  123. np_services/.mypy_cache/3.9/os/__init__.data.json +0 -1
  124. np_services/.mypy_cache/3.9/os/__init__.meta.json +0 -1
  125. np_services/.mypy_cache/3.9/os/path.data.json +0 -1
  126. np_services/.mypy_cache/3.9/os/path.meta.json +0 -1
  127. np_services/.mypy_cache/3.9/pathlib.data.json +0 -1
  128. np_services/.mypy_cache/3.9/pathlib.meta.json +0 -1
  129. np_services/.mypy_cache/3.9/pickle.data.json +0 -1
  130. np_services/.mypy_cache/3.9/pickle.meta.json +0 -1
  131. np_services/.mypy_cache/3.9/platform.data.json +0 -1
  132. np_services/.mypy_cache/3.9/platform.meta.json +0 -1
  133. np_services/.mypy_cache/3.9/posixpath.data.json +0 -1
  134. np_services/.mypy_cache/3.9/posixpath.meta.json +0 -1
  135. np_services/.mypy_cache/3.9/random.data.json +0 -1
  136. np_services/.mypy_cache/3.9/random.meta.json +0 -1
  137. np_services/.mypy_cache/3.9/re.data.json +0 -1
  138. np_services/.mypy_cache/3.9/re.meta.json +0 -1
  139. np_services/.mypy_cache/3.9/shutil.data.json +0 -1
  140. np_services/.mypy_cache/3.9/shutil.meta.json +0 -1
  141. np_services/.mypy_cache/3.9/socket.data.json +0 -1
  142. np_services/.mypy_cache/3.9/socket.meta.json +0 -1
  143. np_services/.mypy_cache/3.9/sre_compile.data.json +0 -1
  144. np_services/.mypy_cache/3.9/sre_compile.meta.json +0 -1
  145. np_services/.mypy_cache/3.9/sre_constants.data.json +0 -1
  146. np_services/.mypy_cache/3.9/sre_constants.meta.json +0 -1
  147. np_services/.mypy_cache/3.9/sre_parse.data.json +0 -1
  148. np_services/.mypy_cache/3.9/sre_parse.meta.json +0 -1
  149. np_services/.mypy_cache/3.9/string.data.json +0 -1
  150. np_services/.mypy_cache/3.9/string.meta.json +0 -1
  151. np_services/.mypy_cache/3.9/subprocess.data.json +0 -1
  152. np_services/.mypy_cache/3.9/subprocess.meta.json +0 -1
  153. np_services/.mypy_cache/3.9/sys.data.json +0 -1
  154. np_services/.mypy_cache/3.9/sys.meta.json +0 -1
  155. np_services/.mypy_cache/3.9/threading.data.json +0 -1
  156. np_services/.mypy_cache/3.9/threading.meta.json +0 -1
  157. np_services/.mypy_cache/3.9/time.data.json +0 -1
  158. np_services/.mypy_cache/3.9/time.meta.json +0 -1
  159. np_services/.mypy_cache/3.9/types.data.json +0 -1
  160. np_services/.mypy_cache/3.9/types.meta.json +0 -1
  161. np_services/.mypy_cache/3.9/typing.data.json +0 -1
  162. np_services/.mypy_cache/3.9/typing.meta.json +0 -1
  163. np_services/.mypy_cache/3.9/typing_extensions.data.json +0 -1
  164. np_services/.mypy_cache/3.9/typing_extensions.meta.json +0 -1
  165. np_services/.mypy_cache/3.9/warnings.data.json +0 -1
  166. np_services/.mypy_cache/3.9/warnings.meta.json +0 -1
  167. np_services/.mypy_cache/3.9/weakref.data.json +0 -1
  168. np_services/.mypy_cache/3.9/weakref.meta.json +0 -1
  169. np_services/.mypy_cache/3.9/zmq/__init__.data.json +0 -1
  170. np_services/.mypy_cache/3.9/zmq/__init__.meta.json +0 -1
  171. np_services/.mypy_cache/3.9/zmq/_typing.data.json +0 -1
  172. np_services/.mypy_cache/3.9/zmq/_typing.meta.json +0 -1
  173. np_services/.mypy_cache/3.9/zmq/backend/__init__.data.json +0 -1
  174. np_services/.mypy_cache/3.9/zmq/backend/__init__.meta.json +0 -1
  175. np_services/.mypy_cache/3.9/zmq/backend/select.data.json +0 -1
  176. np_services/.mypy_cache/3.9/zmq/backend/select.meta.json +0 -1
  177. np_services/.mypy_cache/3.9/zmq/constants.data.json +0 -1
  178. np_services/.mypy_cache/3.9/zmq/constants.meta.json +0 -1
  179. np_services/.mypy_cache/3.9/zmq/error.data.json +0 -1
  180. np_services/.mypy_cache/3.9/zmq/error.meta.json +0 -1
  181. np_services/.mypy_cache/3.9/zmq/sugar/__init__.data.json +0 -1
  182. np_services/.mypy_cache/3.9/zmq/sugar/__init__.meta.json +0 -1
  183. np_services/.mypy_cache/3.9/zmq/sugar/attrsettr.data.json +0 -1
  184. np_services/.mypy_cache/3.9/zmq/sugar/attrsettr.meta.json +0 -1
  185. np_services/.mypy_cache/3.9/zmq/sugar/context.data.json +0 -1
  186. np_services/.mypy_cache/3.9/zmq/sugar/context.meta.json +0 -1
  187. np_services/.mypy_cache/3.9/zmq/sugar/frame.data.json +0 -1
  188. np_services/.mypy_cache/3.9/zmq/sugar/frame.meta.json +0 -1
  189. np_services/.mypy_cache/3.9/zmq/sugar/poll.data.json +0 -1
  190. np_services/.mypy_cache/3.9/zmq/sugar/poll.meta.json +0 -1
  191. np_services/.mypy_cache/3.9/zmq/sugar/socket.data.json +0 -1
  192. np_services/.mypy_cache/3.9/zmq/sugar/socket.meta.json +0 -1
  193. np_services/.mypy_cache/3.9/zmq/sugar/tracker.data.json +0 -1
  194. np_services/.mypy_cache/3.9/zmq/sugar/tracker.meta.json +0 -1
  195. np_services/.mypy_cache/3.9/zmq/sugar/version.data.json +0 -1
  196. np_services/.mypy_cache/3.9/zmq/sugar/version.meta.json +0 -1
  197. np_services/.mypy_cache/3.9/zmq/utils/__init__.data.json +0 -1
  198. np_services/.mypy_cache/3.9/zmq/utils/__init__.meta.json +0 -1
  199. np_services/.mypy_cache/3.9/zmq/utils/interop.data.json +0 -1
  200. np_services/.mypy_cache/3.9/zmq/utils/interop.meta.json +0 -1
  201. np_services/.mypy_cache/3.9/zmq/utils/jsonapi.data.json +0 -1
  202. np_services/.mypy_cache/3.9/zmq/utils/jsonapi.meta.json +0 -1
  203. np_services/.mypy_cache/CACHEDIR.TAG +0 -3
  204. np_services/resources/black_desktop.ps1 +0 -66
  205. np_services/resources/grey_desktop.ps1 +0 -66
  206. np_services/resources/reset_desktop.ps1 +0 -66
  207. np_services-0.1.69.dist-info/RECORD +0 -206
np_services/protocols.py CHANGED
@@ -1,185 +1,185 @@
1
- import pathlib
2
- import time
3
- import typing
4
- from typing import Any, Optional, Protocol, Union
5
-
6
-
7
- class TestError(AssertionError):
8
- ...
9
-
10
-
11
- # verb-based protocols define functions called at specific points in an experiment workflow
12
- # so that their order is fixed: initialize() | start() | verify() | stop() | finalize()
13
-
14
-
15
- @typing.runtime_checkable
16
- class Initializable(Protocol):
17
- "Supports `initialize()`: runs setup or configuration to effectively reset the service for fresh use."
18
-
19
- def initialize(self) -> None:
20
- ...
21
-
22
-
23
- @typing.runtime_checkable
24
- class Configurable(Initializable, Protocol):
25
- "Supports `config()`: ensures all required parameters are set before use. Called in `initialize()`."
26
-
27
- def configure(self) -> None:
28
- ...
29
-
30
- # def ensure_config(self) -> None: ...
31
-
32
-
33
- @typing.runtime_checkable
34
- class Testable(Protocol):
35
- "Supports `test()`: without creating new data, quickly confirms readiness for use, or raises `TestError`. Always called before first use. See `PreTestable` for comprehensive test."
36
-
37
- def test(self) -> None:
38
- ...
39
-
40
-
41
- @typing.runtime_checkable
42
- class Pretestable(Protocol):
43
- "Supports `pretest()`: comprehensively tests service functionality and code by calling every class method critical for use. Should be expected to fail."
44
-
45
- def pretest(self) -> None:
46
- ...
47
-
48
-
49
- @typing.runtime_checkable
50
- class Startable(Protocol):
51
- "Supports `start()`, `latest_start`"
52
-
53
- def start(self) -> None:
54
- "Starts stimulus/recording and records current time in `latest_start`."
55
- self.latest_start = time.time()
56
-
57
- # def is_ready_to_start(self) -> bool:
58
- # "The body of start() will not execute unless `is_ready_to_start()` returns `True`"
59
-
60
- # def is_started(self) -> bool:
61
- # "Prevents service from being re-started once started"
62
-
63
- latest_start: float
64
- "Store `time.time()` in each `start()` so we can find files created afterward."
65
-
66
-
67
- @typing.runtime_checkable
68
- class Primeable(Startable, Protocol): # ? PreStartable
69
- "Supports `prime()`: makes ready for imminent `start()` by re-arming, running checks, etc. Called before `start()`."
70
-
71
- def prime(self) -> None:
72
- ...
73
-
74
- # ? auto-run at beginning of `start()`?
75
-
76
-
77
- @typing.runtime_checkable
78
- class Verifiable(Startable, Protocol): # ? PostStartable
79
- "Supports `verify()`: asserts service has started, e.g. stimulus is running, data file is increasing in size etc., or raises `AssertionError`. Called after `start()` and checking `self.is_started()`."
80
-
81
- def verify(self) -> None:
82
- ...
83
-
84
- # ? auto-run at end of `start()`?
85
-
86
-
87
- @typing.runtime_checkable
88
- class Stoppable(Protocol):
89
- "Supports `stop()`: stops or pauses stimulus/recording. Called after `start()`."
90
-
91
- def stop(self) -> None:
92
- ...
93
-
94
-
95
- @typing.runtime_checkable
96
- class Finalizable(Protocol):
97
- "Supports `finalize()`: handle results of most-recent `start()` or `stop()`. Cleanup, file management etc."
98
-
99
- def finalize(self) -> None:
100
- ...
101
-
102
- # ? if multiple start-stop loops: finalize altogether or individually?
103
- # ? auto-run `finalize()` at end of `stop()`?
104
-
105
-
106
- @typing.runtime_checkable
107
- class Validatable(Protocol):
108
- "Supports `validate()`: asserts most-recent data are valid, or raises `AssertionError`. Called after checking `self.is_started() is not True`."
109
-
110
- def validate(self, data: Optional[pathlib.Path] = None) -> None:
111
- ...
112
-
113
-
114
- @typing.runtime_checkable
115
- class Shutdownable(Protocol):
116
- "Supports `shutdown()`: gracefully closes service. Called after `finalize()`."
117
-
118
- def shutdown(self) -> None:
119
- ...
120
-
121
-
122
- PreExperimentProtocols = Union[
123
- Initializable, Testable, Pretestable, Primeable, Startable
124
- ]
125
- PostExperimentProtocols = Union[Stoppable, Finalizable, Validatable, Shutdownable]
126
- Service = Union[PreExperimentProtocols, PostExperimentProtocols]
127
-
128
- # special methods - should be the only :
129
-
130
-
131
- @typing.runtime_checkable
132
- class Gettable(Protocol):
133
- def get(self, property: str) -> Any:
134
- return getattr(self, property)
135
-
136
-
137
- @typing.runtime_checkable
138
- class Settable(Protocol):
139
- def set(self, property: str, value: Any) -> Any:
140
- if not hasattr(self, property):
141
- raise AttributeError(f"Service {self} has no property {property}")
142
- setattr(self, property, value)
143
-
144
-
145
- # noun-based protocols are for more-specific functions/properties than verb-based protocols
146
- # and may be checked at any time.. for example 'Recorder' captures data and therefore
147
- # has a data path in the filesystem
148
-
149
-
150
- @typing.runtime_checkable
151
- class Recorder(Startable, Protocol):
152
- data_root: pathlib.Path
153
- raw_suffix: str # include leading dot
154
-
155
-
156
- if __name__ == "__main__":
157
-
158
- class Test_1:
159
- _is_started: bool
160
-
161
- def initialize(self):
162
- self._is_started = False
163
- print(__class__, "initialized")
164
-
165
- def is_started(self):
166
- return self._is_started
167
-
168
- def is_ready_to_start(self):
169
- return not self.is_started()
170
-
171
- def start():
172
- last_started = time.time()
173
- started = True
174
- print(__class__, "started")
175
-
176
- class Test_2:
177
- def start():
178
- print(__class__, "started")
179
-
180
- services = [Test_1, Test_2]
181
-
182
- def initialize_all():
183
- for service in services:
184
- if isinstance(service, Initializable):
185
- service.initialize()
1
+ import pathlib
2
+ import time
3
+ import typing
4
+ from typing import Any, Optional, Protocol, Union
5
+
6
+
7
+ class TestError(AssertionError):
8
+ ...
9
+
10
+
11
+ # verb-based protocols define functions called at specific points in an experiment workflow
12
+ # so that their order is fixed: initialize() | start() | verify() | stop() | finalize()
13
+
14
+
15
+ @typing.runtime_checkable
16
+ class Initializable(Protocol):
17
+ "Supports `initialize()`: runs setup or configuration to effectively reset the service for fresh use."
18
+
19
+ def initialize(self) -> None:
20
+ ...
21
+
22
+
23
+ @typing.runtime_checkable
24
+ class Configurable(Initializable, Protocol):
25
+ "Supports `config()`: ensures all required parameters are set before use. Called in `initialize()`."
26
+
27
+ def configure(self) -> None:
28
+ ...
29
+
30
+ # def ensure_config(self) -> None: ...
31
+
32
+
33
+ @typing.runtime_checkable
34
+ class Testable(Protocol):
35
+ "Supports `test()`: without creating new data, quickly confirms readiness for use, or raises `TestError`. Always called before first use. See `PreTestable` for comprehensive test."
36
+
37
+ def test(self) -> None:
38
+ ...
39
+
40
+
41
+ @typing.runtime_checkable
42
+ class Pretestable(Protocol):
43
+ "Supports `pretest()`: comprehensively tests service functionality and code by calling every class method critical for use. Should be expected to fail."
44
+
45
+ def pretest(self) -> None:
46
+ ...
47
+
48
+
49
+ @typing.runtime_checkable
50
+ class Startable(Protocol):
51
+ "Supports `start()`, `latest_start`"
52
+
53
+ def start(self) -> None:
54
+ "Starts stimulus/recording and records current time in `latest_start`."
55
+ self.latest_start = time.time()
56
+
57
+ # def is_ready_to_start(self) -> bool:
58
+ # "The body of start() will not execute unless `is_ready_to_start()` returns `True`"
59
+
60
+ # def is_started(self) -> bool:
61
+ # "Prevents service from being re-started once started"
62
+
63
+ latest_start: float
64
+ "Store `time.time()` in each `start()` so we can find files created afterward."
65
+
66
+
67
+ @typing.runtime_checkable
68
+ class Primeable(Startable, Protocol): # ? PreStartable
69
+ "Supports `prime()`: makes ready for imminent `start()` by re-arming, running checks, etc. Called before `start()`."
70
+
71
+ def prime(self) -> None:
72
+ ...
73
+
74
+ # ? auto-run at beginning of `start()`?
75
+
76
+
77
+ @typing.runtime_checkable
78
+ class Verifiable(Startable, Protocol): # ? PostStartable
79
+ "Supports `verify()`: asserts service has started, e.g. stimulus is running, data file is increasing in size etc., or raises `AssertionError`. Called after `start()` and checking `self.is_started()`."
80
+
81
+ def verify(self) -> None:
82
+ ...
83
+
84
+ # ? auto-run at end of `start()`?
85
+
86
+
87
+ @typing.runtime_checkable
88
+ class Stoppable(Protocol):
89
+ "Supports `stop()`: stops or pauses stimulus/recording. Called after `start()`."
90
+
91
+ def stop(self) -> None:
92
+ ...
93
+
94
+
95
+ @typing.runtime_checkable
96
+ class Finalizable(Protocol):
97
+ "Supports `finalize()`: handle results of most-recent `start()` or `stop()`. Cleanup, file management etc."
98
+
99
+ def finalize(self) -> None:
100
+ ...
101
+
102
+ # ? if multiple start-stop loops: finalize altogether or individually?
103
+ # ? auto-run `finalize()` at end of `stop()`?
104
+
105
+
106
+ @typing.runtime_checkable
107
+ class Validatable(Protocol):
108
+ "Supports `validate()`: asserts most-recent data are valid, or raises `AssertionError`. Called after checking `self.is_started() is not True`."
109
+
110
+ def validate(self, data: Optional[pathlib.Path] = None) -> None:
111
+ ...
112
+
113
+
114
+ @typing.runtime_checkable
115
+ class Shutdownable(Protocol):
116
+ "Supports `shutdown()`: gracefully closes service. Called after `finalize()`."
117
+
118
+ def shutdown(self) -> None:
119
+ ...
120
+
121
+
122
+ PreExperimentProtocols = Union[
123
+ Initializable, Testable, Pretestable, Primeable, Startable
124
+ ]
125
+ PostExperimentProtocols = Union[Stoppable, Finalizable, Validatable, Shutdownable]
126
+ Service = Union[PreExperimentProtocols, PostExperimentProtocols]
127
+
128
+ # special methods - should be the only :
129
+
130
+
131
+ @typing.runtime_checkable
132
+ class Gettable(Protocol):
133
+ def get(self, property: str) -> Any:
134
+ return getattr(self, property)
135
+
136
+
137
+ @typing.runtime_checkable
138
+ class Settable(Protocol):
139
+ def set(self, property: str, value: Any) -> Any:
140
+ if not hasattr(self, property):
141
+ raise AttributeError(f"Service {self} has no property {property}")
142
+ setattr(self, property, value)
143
+
144
+
145
+ # noun-based protocols are for more-specific functions/properties than verb-based protocols
146
+ # and may be checked at any time.. for example 'Recorder' captures data and therefore
147
+ # has a data path in the filesystem
148
+
149
+
150
+ @typing.runtime_checkable
151
+ class Recorder(Startable, Protocol):
152
+ data_root: pathlib.Path
153
+ raw_suffix: str # include leading dot
154
+
155
+
156
+ if __name__ == "__main__":
157
+
158
+ class Test_1:
159
+ _is_started: bool
160
+
161
+ def initialize(self):
162
+ self._is_started = False
163
+ print(__class__, "initialized")
164
+
165
+ def is_started(self):
166
+ return self._is_started
167
+
168
+ def is_ready_to_start(self):
169
+ return not self.is_started()
170
+
171
+ def start():
172
+ last_started = time.time()
173
+ started = True
174
+ print(__class__, "started")
175
+
176
+ class Test_2:
177
+ def start():
178
+ print(__class__, "started")
179
+
180
+ services = [Test_1, Test_2]
181
+
182
+ def initialize_all():
183
+ for service in services:
184
+ if isinstance(service, Initializable):
185
+ service.initialize()