np-services 0.1.59__py3-none-any.whl → 0.1.73__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.
- np_services/__init__.py +8 -8
- np_services/open_ephys.py +377 -378
- np_services/protocols.py +185 -185
- np_services/proxies.py +1489 -1488
- np_services/resources/mvr_connector.py +260 -260
- np_services/resources/zro.py +325 -325
- np_services/scripts/pretest.py +170 -73
- np_services/stim_computer_theme_changer.py +41 -41
- np_services/utils.py +167 -167
- {np_services-0.1.59.dist-info → np_services-0.1.73.dist-info}/METADATA +7 -8
- np_services-0.1.73.dist-info/RECORD +15 -0
- {np_services-0.1.59.dist-info → np_services-0.1.73.dist-info}/WHEEL +2 -1
- {np_services-0.1.59.dist-info → np_services-0.1.73.dist-info}/entry_points.txt +1 -1
- np_services-0.1.73.dist-info/top_level.txt +1 -0
- np_services/.mypy_cache/.gitignore +0 -2
- np_services/.mypy_cache/3.9/@plugins_snapshot.json +0 -1
- np_services/.mypy_cache/3.9/__future__.data.json +0 -1
- np_services/.mypy_cache/3.9/__future__.meta.json +0 -1
- np_services/.mypy_cache/3.9/_ast.data.json +0 -1
- np_services/.mypy_cache/3.9/_ast.meta.json +0 -1
- np_services/.mypy_cache/3.9/_codecs.data.json +0 -1
- np_services/.mypy_cache/3.9/_codecs.meta.json +0 -1
- np_services/.mypy_cache/3.9/_collections_abc.data.json +0 -1
- np_services/.mypy_cache/3.9/_collections_abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/_ctypes.data.json +0 -1
- np_services/.mypy_cache/3.9/_ctypes.meta.json +0 -1
- np_services/.mypy_cache/3.9/_decimal.data.json +0 -1
- np_services/.mypy_cache/3.9/_decimal.meta.json +0 -1
- np_services/.mypy_cache/3.9/_random.data.json +0 -1
- np_services/.mypy_cache/3.9/_random.meta.json +0 -1
- np_services/.mypy_cache/3.9/_socket.data.json +0 -1
- np_services/.mypy_cache/3.9/_socket.meta.json +0 -1
- np_services/.mypy_cache/3.9/_thread.data.json +0 -1
- np_services/.mypy_cache/3.9/_thread.meta.json +0 -1
- np_services/.mypy_cache/3.9/_typeshed/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/_typeshed/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/_warnings.data.json +0 -1
- np_services/.mypy_cache/3.9/_warnings.meta.json +0 -1
- np_services/.mypy_cache/3.9/_weakref.data.json +0 -1
- np_services/.mypy_cache/3.9/_weakref.meta.json +0 -1
- np_services/.mypy_cache/3.9/_weakrefset.data.json +0 -1
- np_services/.mypy_cache/3.9/_weakrefset.meta.json +0 -1
- np_services/.mypy_cache/3.9/_winapi.data.json +0 -1
- np_services/.mypy_cache/3.9/_winapi.meta.json +0 -1
- np_services/.mypy_cache/3.9/abc.data.json +0 -1
- np_services/.mypy_cache/3.9/abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/array.data.json +0 -1
- np_services/.mypy_cache/3.9/array.meta.json +0 -1
- np_services/.mypy_cache/3.9/atexit.data.json +0 -1
- np_services/.mypy_cache/3.9/atexit.meta.json +0 -1
- np_services/.mypy_cache/3.9/builtins.data.json +0 -1
- np_services/.mypy_cache/3.9/builtins.meta.json +0 -1
- np_services/.mypy_cache/3.9/codecs.data.json +0 -1
- np_services/.mypy_cache/3.9/codecs.meta.json +0 -1
- np_services/.mypy_cache/3.9/collections/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/collections/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/collections/abc.data.json +0 -1
- np_services/.mypy_cache/3.9/collections/abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/contextlib.data.json +0 -1
- np_services/.mypy_cache/3.9/contextlib.meta.json +0 -1
- np_services/.mypy_cache/3.9/ctypes/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/ctypes/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/datetime.data.json +0 -1
- np_services/.mypy_cache/3.9/datetime.meta.json +0 -1
- np_services/.mypy_cache/3.9/decimal.data.json +0 -1
- np_services/.mypy_cache/3.9/decimal.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/email/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/charset.data.json +0 -1
- np_services/.mypy_cache/3.9/email/charset.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/contentmanager.data.json +0 -1
- np_services/.mypy_cache/3.9/email/contentmanager.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/errors.data.json +0 -1
- np_services/.mypy_cache/3.9/email/errors.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/header.data.json +0 -1
- np_services/.mypy_cache/3.9/email/header.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/message.data.json +0 -1
- np_services/.mypy_cache/3.9/email/message.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/policy.data.json +0 -1
- np_services/.mypy_cache/3.9/email/policy.meta.json +0 -1
- np_services/.mypy_cache/3.9/enum.data.json +0 -1
- np_services/.mypy_cache/3.9/enum.meta.json +0 -1
- np_services/.mypy_cache/3.9/errno.data.json +0 -1
- np_services/.mypy_cache/3.9/errno.meta.json +0 -1
- np_services/.mypy_cache/3.9/fractions.data.json +0 -1
- np_services/.mypy_cache/3.9/fractions.meta.json +0 -1
- np_services/.mypy_cache/3.9/genericpath.data.json +0 -1
- np_services/.mypy_cache/3.9/genericpath.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/abc.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/machinery.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/machinery.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/metadata/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/metadata/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/io.data.json +0 -1
- np_services/.mypy_cache/3.9/io.meta.json +0 -1
- np_services/.mypy_cache/3.9/json/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/json/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/json/decoder.data.json +0 -1
- np_services/.mypy_cache/3.9/json/decoder.meta.json +0 -1
- np_services/.mypy_cache/3.9/json/encoder.data.json +0 -1
- np_services/.mypy_cache/3.9/json/encoder.meta.json +0 -1
- np_services/.mypy_cache/3.9/logging/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/logging/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/math.data.json +0 -1
- np_services/.mypy_cache/3.9/math.meta.json +0 -1
- np_services/.mypy_cache/3.9/mmap.data.json +0 -1
- np_services/.mypy_cache/3.9/mmap.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/config.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/config.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/protocols.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/protocols.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/zro.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/zro.meta.json +0 -1
- np_services/.mypy_cache/3.9/ntpath.data.json +0 -1
- np_services/.mypy_cache/3.9/ntpath.meta.json +0 -1
- np_services/.mypy_cache/3.9/numbers.data.json +0 -1
- np_services/.mypy_cache/3.9/numbers.meta.json +0 -1
- np_services/.mypy_cache/3.9/os/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/os/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/os/path.data.json +0 -1
- np_services/.mypy_cache/3.9/os/path.meta.json +0 -1
- np_services/.mypy_cache/3.9/pathlib.data.json +0 -1
- np_services/.mypy_cache/3.9/pathlib.meta.json +0 -1
- np_services/.mypy_cache/3.9/pickle.data.json +0 -1
- np_services/.mypy_cache/3.9/pickle.meta.json +0 -1
- np_services/.mypy_cache/3.9/platform.data.json +0 -1
- np_services/.mypy_cache/3.9/platform.meta.json +0 -1
- np_services/.mypy_cache/3.9/posixpath.data.json +0 -1
- np_services/.mypy_cache/3.9/posixpath.meta.json +0 -1
- np_services/.mypy_cache/3.9/random.data.json +0 -1
- np_services/.mypy_cache/3.9/random.meta.json +0 -1
- np_services/.mypy_cache/3.9/re.data.json +0 -1
- np_services/.mypy_cache/3.9/re.meta.json +0 -1
- np_services/.mypy_cache/3.9/shutil.data.json +0 -1
- np_services/.mypy_cache/3.9/shutil.meta.json +0 -1
- np_services/.mypy_cache/3.9/socket.data.json +0 -1
- np_services/.mypy_cache/3.9/socket.meta.json +0 -1
- np_services/.mypy_cache/3.9/sre_compile.data.json +0 -1
- np_services/.mypy_cache/3.9/sre_compile.meta.json +0 -1
- np_services/.mypy_cache/3.9/sre_constants.data.json +0 -1
- np_services/.mypy_cache/3.9/sre_constants.meta.json +0 -1
- np_services/.mypy_cache/3.9/sre_parse.data.json +0 -1
- np_services/.mypy_cache/3.9/sre_parse.meta.json +0 -1
- np_services/.mypy_cache/3.9/string.data.json +0 -1
- np_services/.mypy_cache/3.9/string.meta.json +0 -1
- np_services/.mypy_cache/3.9/subprocess.data.json +0 -1
- np_services/.mypy_cache/3.9/subprocess.meta.json +0 -1
- np_services/.mypy_cache/3.9/sys.data.json +0 -1
- np_services/.mypy_cache/3.9/sys.meta.json +0 -1
- np_services/.mypy_cache/3.9/threading.data.json +0 -1
- np_services/.mypy_cache/3.9/threading.meta.json +0 -1
- np_services/.mypy_cache/3.9/time.data.json +0 -1
- np_services/.mypy_cache/3.9/time.meta.json +0 -1
- np_services/.mypy_cache/3.9/types.data.json +0 -1
- np_services/.mypy_cache/3.9/types.meta.json +0 -1
- np_services/.mypy_cache/3.9/typing.data.json +0 -1
- np_services/.mypy_cache/3.9/typing.meta.json +0 -1
- np_services/.mypy_cache/3.9/typing_extensions.data.json +0 -1
- np_services/.mypy_cache/3.9/typing_extensions.meta.json +0 -1
- np_services/.mypy_cache/3.9/warnings.data.json +0 -1
- np_services/.mypy_cache/3.9/warnings.meta.json +0 -1
- np_services/.mypy_cache/3.9/weakref.data.json +0 -1
- np_services/.mypy_cache/3.9/weakref.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/_typing.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/_typing.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/select.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/select.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/constants.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/constants.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/error.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/error.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/attrsettr.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/attrsettr.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/context.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/context.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/frame.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/frame.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/poll.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/poll.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/socket.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/socket.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/tracker.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/tracker.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/version.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/version.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/interop.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/interop.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/jsonapi.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/jsonapi.meta.json +0 -1
- np_services/.mypy_cache/CACHEDIR.TAG +0 -3
- np_services/resources/black_desktop.ps1 +0 -66
- np_services/resources/grey_desktop.ps1 +0 -66
- np_services/resources/reset_desktop.ps1 +0 -66
- np_services-0.1.59.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()
|