msw-plugin-api 0.2.4__tar.gz → 0.4.0__tar.gz
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.
- {msw_plugin_api-0.2.4 → msw_plugin_api-0.4.0}/.gitignore +3 -0
- msw_plugin_api-0.4.0/LICENSE +35 -0
- msw_plugin_api-0.4.0/PKG-INFO +117 -0
- {msw_plugin_api-0.2.4 → msw_plugin_api-0.4.0}/README.md +18 -0
- msw_plugin_api-0.4.0/VERSION +1 -0
- {msw_plugin_api-0.2.4 → msw_plugin_api-0.4.0}/pyproject.toml +1 -1
- {msw_plugin_api-0.2.4 → msw_plugin_api-0.4.0}/src/msw_plugin_api/__init__.py +66 -0
- {msw_plugin_api-0.2.4 → msw_plugin_api-0.4.0}/src/msw_plugin_api/_version.py +2 -2
- msw_plugin_api-0.4.0/src/msw_plugin_api/camera.py +194 -0
- msw_plugin_api-0.2.4/LICENSE +0 -31
- msw_plugin_api-0.2.4/PKG-INFO +0 -95
- msw_plugin_api-0.2.4/VERSION +0 -1
- {msw_plugin_api-0.2.4 → msw_plugin_api-0.4.0}/src/msw_plugin_api/py.typed +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
BSD-3-Clause-NonCommercial
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020-present, Lars B. Rollik.
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted for NON-COMMERCIAL research, academic, and
|
|
8
|
+
educational purposes only, provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
and/or other materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
18
|
+
may be used to endorse or promote products derived from this software
|
|
19
|
+
without specific prior written permission.
|
|
20
|
+
|
|
21
|
+
4. Any use of this software or its derivatives for COMMERCIAL purposes -
|
|
22
|
+
including sale, paid licensing, or providing paid services built on it -
|
|
23
|
+
requires a separate commercial licence. Contact lars@rollik.me.
|
|
24
|
+
|
|
25
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
26
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
27
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
28
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
29
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
30
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
31
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
32
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
33
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
34
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
35
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: msw-plugin-api
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Minimal structural interfaces for MSW plugin packages
|
|
5
|
+
Project-URL: Homepage, https://github.com/MurineShiftWork/msw-plugin-api
|
|
6
|
+
Project-URL: Issue Tracker, https://github.com/MurineShiftWork/msw-plugin-api/issues
|
|
7
|
+
Author-email: "Lars B. Rollik" <lars@rollik.me>
|
|
8
|
+
License: BSD-3-Clause-NonCommercial
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2020-present, Lars B. Rollik.
|
|
11
|
+
All rights reserved.
|
|
12
|
+
|
|
13
|
+
Redistribution and use in source and binary forms, with or without
|
|
14
|
+
modification, are permitted for NON-COMMERCIAL research, academic, and
|
|
15
|
+
educational purposes only, provided that the following conditions are met:
|
|
16
|
+
|
|
17
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
18
|
+
this list of conditions and the following disclaimer.
|
|
19
|
+
|
|
20
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
and/or other materials provided with the distribution.
|
|
23
|
+
|
|
24
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
25
|
+
may be used to endorse or promote products derived from this software
|
|
26
|
+
without specific prior written permission.
|
|
27
|
+
|
|
28
|
+
4. Any use of this software or its derivatives for COMMERCIAL purposes -
|
|
29
|
+
including sale, paid licensing, or providing paid services built on it -
|
|
30
|
+
requires a separate commercial licence. Contact lars@rollik.me.
|
|
31
|
+
|
|
32
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
33
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
34
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
35
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
36
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
37
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
38
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
39
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
40
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
41
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
42
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Classifier: Development Status :: 4 - Beta
|
|
45
|
+
Classifier: Intended Audience :: Developers
|
|
46
|
+
Classifier: Intended Audience :: Science/Research
|
|
47
|
+
Classifier: License :: Free for non-commercial use
|
|
48
|
+
Classifier: Operating System :: OS Independent
|
|
49
|
+
Classifier: Programming Language :: Python :: 3
|
|
50
|
+
Classifier: Typing :: Typed
|
|
51
|
+
Requires-Python: >=3.11
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: commitizen; extra == 'dev'
|
|
54
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
55
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
56
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
57
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# msw-plugin-api
|
|
61
|
+
|
|
62
|
+
Minimal structural interfaces for [MurineShiftWork](https://github.com/MurineShiftWork/murineshiftwork) plugin packages.
|
|
63
|
+
|
|
64
|
+
Zero dependencies (stdlib `typing` and `dataclasses` only). Provides:
|
|
65
|
+
|
|
66
|
+
- `HostSessionInfo` — concrete dataclass plugins return after attaching
|
|
67
|
+
- `HostSessionInfoProtocol` — `runtime_checkable` Protocol for structural typing
|
|
68
|
+
- `HostSessionProtocol` — `runtime_checkable` Protocol for host session plugins
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from msw_plugin_api import HostSessionInfo, HostSessionProtocol
|
|
74
|
+
|
|
75
|
+
class MyHostSession:
|
|
76
|
+
def attach(self, **kwargs) -> HostSessionInfo:
|
|
77
|
+
return HostSessionInfo(
|
|
78
|
+
backend="myplugin",
|
|
79
|
+
acquisition_name="subject__20260609_143022__ext",
|
|
80
|
+
subject="subject",
|
|
81
|
+
parent_directory="/data/recordings",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def start(self) -> None: ...
|
|
85
|
+
def stop(self) -> None: ...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Declare the plugin entry point in `pyproject.toml`:
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[project.entry-points."msw.host"]
|
|
92
|
+
myplugin = "my_package.session:MyHostSession"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Plugin contract
|
|
96
|
+
|
|
97
|
+
MSW core checks `isinstance(session, HostSessionProtocol)` at dispatch time.
|
|
98
|
+
Plugins may implement the interface structurally (without importing this package)
|
|
99
|
+
or depend on `msw-plugin-api` explicitly for IDE support and test conformance checks.
|
|
100
|
+
|
|
101
|
+
## Camera backends
|
|
102
|
+
|
|
103
|
+
A camera backend drives one group of cameras of a setup and registers under
|
|
104
|
+
the `msw.camera` entry-point group, named as the setup's `backend:` key:
|
|
105
|
+
|
|
106
|
+
```toml
|
|
107
|
+
[project.entry-points."msw.camera"]
|
|
108
|
+
flir_bonsai = "msw_flir_bonsai.msw:FlirBonsaiBackend"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The host calls `configure(settings, units)` once, then `preflight()`,
|
|
112
|
+
`start(target)`, `stop(reason)` and `close()`. Each of `preflight`, `start` and
|
|
113
|
+
`stop` returns one `UnitResult` per camera: a backend reports a missing or failed
|
|
114
|
+
camera instead of raising, and the host decides whether that aborts the run.
|
|
115
|
+
`configure` raises `CameraConfigError` for invalid settings, so a broken setup
|
|
116
|
+
fails before any hardware starts. `load_camera_backend(name)` finds and creates
|
|
117
|
+
an installed backend.
|
|
@@ -38,3 +38,21 @@ myplugin = "my_package.session:MyHostSession"
|
|
|
38
38
|
MSW core checks `isinstance(session, HostSessionProtocol)` at dispatch time.
|
|
39
39
|
Plugins may implement the interface structurally (without importing this package)
|
|
40
40
|
or depend on `msw-plugin-api` explicitly for IDE support and test conformance checks.
|
|
41
|
+
|
|
42
|
+
## Camera backends
|
|
43
|
+
|
|
44
|
+
A camera backend drives one group of cameras of a setup and registers under
|
|
45
|
+
the `msw.camera` entry-point group, named as the setup's `backend:` key:
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
[project.entry-points."msw.camera"]
|
|
49
|
+
flir_bonsai = "msw_flir_bonsai.msw:FlirBonsaiBackend"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The host calls `configure(settings, units)` once, then `preflight()`,
|
|
53
|
+
`start(target)`, `stop(reason)` and `close()`. Each of `preflight`, `start` and
|
|
54
|
+
`stop` returns one `UnitResult` per camera: a backend reports a missing or failed
|
|
55
|
+
camera instead of raising, and the host decides whether that aborts the run.
|
|
56
|
+
`configure` raises `CameraConfigError` for invalid settings, so a broken setup
|
|
57
|
+
fails before any hardware starts. `load_camera_backend(name)` finds and creates
|
|
58
|
+
an installed backend.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.4.0
|
|
@@ -95,3 +95,69 @@ class HostSessionProtocol(Protocol):
|
|
|
95
95
|
def stop(self) -> None:
|
|
96
96
|
"""Stop the recording session on the backend."""
|
|
97
97
|
...
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@runtime_checkable
|
|
101
|
+
class MetadataProtocol(Protocol):
|
|
102
|
+
"""Structural protocol for an MSW metadata section.
|
|
103
|
+
|
|
104
|
+
A metadata addon owns exactly one top-level subtree of the manifest
|
|
105
|
+
``metadata`` key-tree (e.g. ``metadata.probes``, ``metadata.opto``). It
|
|
106
|
+
keeps its schema in its own package and advertises itself with an entry
|
|
107
|
+
point::
|
|
108
|
+
|
|
109
|
+
[project.entry-points."msw.metadata"]
|
|
110
|
+
probes = "my_package.metadata:ProbesMetadata"
|
|
111
|
+
|
|
112
|
+
MSW core discovers these via ``importlib.metadata`` and checks
|
|
113
|
+
``isinstance(obj, MetadataProtocol)``, so any object exposing ``subtree``,
|
|
114
|
+
``schema_version``, and ``validate`` satisfies it (no import of this
|
|
115
|
+
package required). ``validate`` must be non-fatal: it returns a list of
|
|
116
|
+
human-readable warnings and never raises, because ``metadata`` is optional
|
|
117
|
+
and legacy/partial trees must still load.
|
|
118
|
+
|
|
119
|
+
Attributes:
|
|
120
|
+
subtree: The reserved top-level metadata key this addon owns.
|
|
121
|
+
schema_version: The version of this subtree's schema.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
subtree: str
|
|
125
|
+
schema_version: int
|
|
126
|
+
|
|
127
|
+
def validate(self, block: object) -> list[str]:
|
|
128
|
+
"""Validate this addon's metadata subtree.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
block: The value at ``metadata[self.subtree]``.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A list of human-readable warnings (empty when valid). Never raises.
|
|
135
|
+
"""
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
from msw_plugin_api.camera import ( # noqa: E402
|
|
140
|
+
CAMERA_ENTRY_POINT_GROUP,
|
|
141
|
+
CameraBackend,
|
|
142
|
+
CameraConfigError,
|
|
143
|
+
CameraTarget,
|
|
144
|
+
CameraUnitSpec,
|
|
145
|
+
UnitResult,
|
|
146
|
+
camera_backend_names,
|
|
147
|
+
load_camera_backend,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
__all__ = [
|
|
151
|
+
"CAMERA_ENTRY_POINT_GROUP",
|
|
152
|
+
"CameraBackend",
|
|
153
|
+
"CameraConfigError",
|
|
154
|
+
"CameraTarget",
|
|
155
|
+
"CameraUnitSpec",
|
|
156
|
+
"HostSessionInfo",
|
|
157
|
+
"HostSessionInfoProtocol",
|
|
158
|
+
"HostSessionProtocol",
|
|
159
|
+
"MetadataProtocol",
|
|
160
|
+
"UnitResult",
|
|
161
|
+
"camera_backend_names",
|
|
162
|
+
"load_camera_backend",
|
|
163
|
+
]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Camera backend contract.
|
|
2
|
+
|
|
3
|
+
A camera backend drives one group of cameras of a setup (for example the FLIR
|
|
4
|
+
cameras run through Bonsai, or a Raspberry Pi camera ensemble). The host (MSW
|
|
5
|
+
core) owns the session: it decides which groups record, where they write, and
|
|
6
|
+
whether a failed camera aborts the run. A backend only drives its cameras and
|
|
7
|
+
reports what happened to each one.
|
|
8
|
+
|
|
9
|
+
Division of responsibility:
|
|
10
|
+
|
|
11
|
+
- **Backends report, the host decides.** ``preflight``, ``start`` and ``stop``
|
|
12
|
+
return one ``UnitResult`` per camera and do not raise because a camera failed.
|
|
13
|
+
An exception escaping a backend is treated by the host as every camera of the
|
|
14
|
+
group failing.
|
|
15
|
+
- **Configuration errors raise.** ``configure`` raises ``CameraConfigError`` when
|
|
16
|
+
the backend settings or units are invalid, so a broken setup fails at load
|
|
17
|
+
time, before any hardware is touched.
|
|
18
|
+
- **``stop`` is idempotent** and saves everything it can, whatever the state.
|
|
19
|
+
|
|
20
|
+
Backends are found through the ``msw.camera`` entry-point group; the entry
|
|
21
|
+
point names the backend as it appears in a setup's ``backend:`` key::
|
|
22
|
+
|
|
23
|
+
[project.entry-points."msw.camera"]
|
|
24
|
+
flir_bonsai = "msw_flir_bonsai.msw:FlirBonsaiBackend"
|
|
25
|
+
|
|
26
|
+
The entry point must load a class (or factory) that takes no arguments; the
|
|
27
|
+
host then calls ``configure`` once before any other method.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from dataclasses import dataclass, field
|
|
33
|
+
from importlib.metadata import entry_points
|
|
34
|
+
from typing import TYPE_CHECKING, Any, Final, Protocol, runtime_checkable
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from collections.abc import Mapping
|
|
38
|
+
|
|
39
|
+
#: Entry-point group under which camera backends register.
|
|
40
|
+
CAMERA_ENTRY_POINT_GROUP: Final = "msw.camera"
|
|
41
|
+
|
|
42
|
+
# Unit states reported in ``UnitResult.state``.
|
|
43
|
+
UNIT_READY: Final = "ready" # reachable and identified; not recording
|
|
44
|
+
UNIT_RECORDING: Final = "recording"
|
|
45
|
+
UNIT_STOPPED: Final = "stopped" # stopped cleanly, files saved
|
|
46
|
+
UNIT_MISSING: Final = "missing" # not found (unplugged, unreachable, wrong id)
|
|
47
|
+
UNIT_FAILED: Final = "failed" # found but did not do what was asked
|
|
48
|
+
|
|
49
|
+
UNIT_STATES: Final = frozenset(
|
|
50
|
+
{UNIT_READY, UNIT_RECORDING, UNIT_STOPPED, UNIT_MISSING, UNIT_FAILED}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CameraConfigError(ValueError):
|
|
55
|
+
"""The backend settings or camera units of a setup are invalid."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class CameraUnitSpec:
|
|
60
|
+
"""One camera as the setup names it.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
name: Camera name, unique across all camera groups of a setup. Used in
|
|
64
|
+
file names and to select cameras per task.
|
|
65
|
+
hw_id: Hardware identity, when the backend has one: a FLIR serial
|
|
66
|
+
number, a Raspberry Pi hostname. Optional.
|
|
67
|
+
settings: Backend-specific per-camera keys (for example a FLIR SDK
|
|
68
|
+
``index``), validated by the backend in ``configure``.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
name: str
|
|
72
|
+
hw_id: str = ""
|
|
73
|
+
settings: dict[str, Any] = field(default_factory=dict)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class CameraTarget:
|
|
78
|
+
"""Where a camera group writes one recording.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
acqdir: Absolute local directory of the camera acquisition.
|
|
82
|
+
basename: Basename of the camera acquisition; per-camera files are
|
|
83
|
+
named below it.
|
|
84
|
+
host_relpath: Folder of the behaviour acquisition, relative to the data
|
|
85
|
+
root. For backends that record on another machine under their own
|
|
86
|
+
data root.
|
|
87
|
+
host_name: Basename of the behaviour acquisition, for the same backends.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
acqdir: str
|
|
91
|
+
basename: str
|
|
92
|
+
host_relpath: str = ""
|
|
93
|
+
host_name: str = ""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class UnitResult:
|
|
98
|
+
"""What happened to one camera.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
name: The camera's ``CameraUnitSpec.name``.
|
|
102
|
+
ok: Whether the camera did what was asked.
|
|
103
|
+
state: One of ``UNIT_STATES``.
|
|
104
|
+
error: Human-readable reason when ``ok`` is false.
|
|
105
|
+
error_code: Stable machine-readable reason, backend-defined.
|
|
106
|
+
files: Files the camera wrote, relative to the acquisition directory
|
|
107
|
+
(or to the backend's own data root for remote backends).
|
|
108
|
+
extra: Backend facts worth keeping in the manifest: serial, hostname,
|
|
109
|
+
software versions, clock anchors, frame counts.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
name: str
|
|
113
|
+
ok: bool
|
|
114
|
+
state: str
|
|
115
|
+
error: str = ""
|
|
116
|
+
error_code: str = ""
|
|
117
|
+
files: list[str] = field(default_factory=list)
|
|
118
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
119
|
+
|
|
120
|
+
def __post_init__(self) -> None:
|
|
121
|
+
if self.state not in UNIT_STATES:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"unknown camera state {self.state!r}; "
|
|
124
|
+
f"expected one of {sorted(UNIT_STATES)}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@runtime_checkable
|
|
129
|
+
class CameraBackend(Protocol):
|
|
130
|
+
"""Structural protocol for a camera backend driving one camera group.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
acq_type: Namespace acquisition type the group records under
|
|
134
|
+
(``video_flir``, ``video_rce``, ...).
|
|
135
|
+
version: Version of the backend package, recorded in the manifest.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
acq_type: str
|
|
139
|
+
version: str
|
|
140
|
+
|
|
141
|
+
def configure(
|
|
142
|
+
self, settings: Mapping[str, Any], units: list[CameraUnitSpec]
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Validate and store the group's settings and cameras.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
CameraConfigError: The settings or units are invalid.
|
|
148
|
+
"""
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
def preflight(self) -> list[UnitResult]:
|
|
152
|
+
"""Reach and identify every camera without recording."""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
def start(self, target: CameraTarget) -> list[UnitResult]:
|
|
156
|
+
"""Start recording into ``target``; one result per camera."""
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
def stop(self, reason: str) -> list[UnitResult]:
|
|
160
|
+
"""Stop every camera and save what it can. Safe to call repeatedly."""
|
|
161
|
+
...
|
|
162
|
+
|
|
163
|
+
def close(self) -> None:
|
|
164
|
+
"""Release connections and processes. Safe to call repeatedly."""
|
|
165
|
+
...
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def camera_backend_names() -> list[str]:
|
|
169
|
+
"""Names of the installed camera backends, sorted."""
|
|
170
|
+
return sorted(ep.name for ep in entry_points(group=CAMERA_ENTRY_POINT_GROUP))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def load_camera_backend(name: str) -> CameraBackend:
|
|
174
|
+
"""Create the installed camera backend registered as ``name``.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
LookupError: No installed package registers a backend of that name.
|
|
178
|
+
TypeError: The registered object does not satisfy ``CameraBackend``.
|
|
179
|
+
"""
|
|
180
|
+
matches = [
|
|
181
|
+
ep for ep in entry_points(group=CAMERA_ENTRY_POINT_GROUP) if ep.name == name
|
|
182
|
+
]
|
|
183
|
+
if not matches:
|
|
184
|
+
installed = ", ".join(camera_backend_names()) or "none"
|
|
185
|
+
raise LookupError(
|
|
186
|
+
f"camera backend {name!r} is not installed (installed: {installed})"
|
|
187
|
+
)
|
|
188
|
+
backend = matches[0].load()()
|
|
189
|
+
if not isinstance(backend, CameraBackend):
|
|
190
|
+
raise TypeError(
|
|
191
|
+
f"camera backend {name!r} ({matches[0].value}) "
|
|
192
|
+
"does not implement CameraBackend"
|
|
193
|
+
)
|
|
194
|
+
return backend
|
msw_plugin_api-0.2.4/LICENSE
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
Copyright (c) 2020-present Lars B. Rollik. All rights reserved.
|
|
2
|
-
|
|
3
|
-
Permission is granted, free of charge, to use, copy, modify, and distribute
|
|
4
|
-
this software and associated documentation files (the "Software") for
|
|
5
|
-
non-commercial research or academic purposes only, subject to the following
|
|
6
|
-
conditions:
|
|
7
|
-
|
|
8
|
-
1. This copyright notice and permission notice must be included in all copies
|
|
9
|
-
or substantial portions of the Software.
|
|
10
|
-
|
|
11
|
-
2. Any publication, presentation, or product that uses or builds upon the
|
|
12
|
-
Software must give clear attribution to the original work and its authors.
|
|
13
|
-
|
|
14
|
-
3. Commercial use is prohibited without a separate written commercial licence
|
|
15
|
-
agreement with the copyright holder. Commercial use means incorporation of
|
|
16
|
-
the Software into anything for which fees or other compensation are charged
|
|
17
|
-
or received, including commercial products and commercial services.
|
|
18
|
-
|
|
19
|
-
4. No patent licence, express or implied, is granted under these terms. Any
|
|
20
|
-
use that would require a patent licence from the copyright holder requires
|
|
21
|
-
a separate written agreement.
|
|
22
|
-
|
|
23
|
-
5. Redistribution, in source or binary form, is permitted only for
|
|
24
|
-
non-commercial purposes and must retain this notice unmodified.
|
|
25
|
-
|
|
26
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
-
IMPLIED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
|
|
28
|
-
DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
29
|
-
SOFTWARE OR THE USE OR DEALINGS IN THE SOFTWARE.
|
|
30
|
-
|
|
31
|
-
For commercial or patent licensing enquiries contact: lars@rollik.me
|
msw_plugin_api-0.2.4/PKG-INFO
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: msw-plugin-api
|
|
3
|
-
Version: 0.2.4
|
|
4
|
-
Summary: Minimal structural interfaces for MSW plugin packages
|
|
5
|
-
Project-URL: Homepage, https://github.com/MurineShiftWork/msw-plugin-api
|
|
6
|
-
Project-URL: Issue Tracker, https://github.com/MurineShiftWork/msw-plugin-api/issues
|
|
7
|
-
Author-email: "Lars B. Rollik" <lars@rollik.me>
|
|
8
|
-
License: Copyright (c) 2020-present Lars B. Rollik. All rights reserved.
|
|
9
|
-
|
|
10
|
-
Permission is granted, free of charge, to use, copy, modify, and distribute
|
|
11
|
-
this software and associated documentation files (the "Software") for
|
|
12
|
-
non-commercial research or academic purposes only, subject to the following
|
|
13
|
-
conditions:
|
|
14
|
-
|
|
15
|
-
1. This copyright notice and permission notice must be included in all copies
|
|
16
|
-
or substantial portions of the Software.
|
|
17
|
-
|
|
18
|
-
2. Any publication, presentation, or product that uses or builds upon the
|
|
19
|
-
Software must give clear attribution to the original work and its authors.
|
|
20
|
-
|
|
21
|
-
3. Commercial use is prohibited without a separate written commercial licence
|
|
22
|
-
agreement with the copyright holder. Commercial use means incorporation of
|
|
23
|
-
the Software into anything for which fees or other compensation are charged
|
|
24
|
-
or received, including commercial products and commercial services.
|
|
25
|
-
|
|
26
|
-
4. No patent licence, express or implied, is granted under these terms. Any
|
|
27
|
-
use that would require a patent licence from the copyright holder requires
|
|
28
|
-
a separate written agreement.
|
|
29
|
-
|
|
30
|
-
5. Redistribution, in source or binary form, is permitted only for
|
|
31
|
-
non-commercial purposes and must retain this notice unmodified.
|
|
32
|
-
|
|
33
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
34
|
-
IMPLIED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
|
|
35
|
-
DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
36
|
-
SOFTWARE OR THE USE OR DEALINGS IN THE SOFTWARE.
|
|
37
|
-
|
|
38
|
-
For commercial or patent licensing enquiries contact: lars@rollik.me
|
|
39
|
-
License-File: LICENSE
|
|
40
|
-
Classifier: Development Status :: 4 - Beta
|
|
41
|
-
Classifier: Intended Audience :: Developers
|
|
42
|
-
Classifier: Intended Audience :: Science/Research
|
|
43
|
-
Classifier: License :: Free for non-commercial use
|
|
44
|
-
Classifier: Operating System :: OS Independent
|
|
45
|
-
Classifier: Programming Language :: Python :: 3
|
|
46
|
-
Classifier: Typing :: Typed
|
|
47
|
-
Requires-Python: >=3.11
|
|
48
|
-
Provides-Extra: dev
|
|
49
|
-
Requires-Dist: commitizen; extra == 'dev'
|
|
50
|
-
Requires-Dist: mypy; extra == 'dev'
|
|
51
|
-
Requires-Dist: pre-commit; extra == 'dev'
|
|
52
|
-
Requires-Dist: pytest>=8; extra == 'dev'
|
|
53
|
-
Requires-Dist: ruff; extra == 'dev'
|
|
54
|
-
Description-Content-Type: text/markdown
|
|
55
|
-
|
|
56
|
-
# msw-plugin-api
|
|
57
|
-
|
|
58
|
-
Minimal structural interfaces for [MurineShiftWork](https://github.com/MurineShiftWork/murineshiftwork) plugin packages.
|
|
59
|
-
|
|
60
|
-
Zero dependencies (stdlib `typing` and `dataclasses` only). Provides:
|
|
61
|
-
|
|
62
|
-
- `HostSessionInfo` — concrete dataclass plugins return after attaching
|
|
63
|
-
- `HostSessionInfoProtocol` — `runtime_checkable` Protocol for structural typing
|
|
64
|
-
- `HostSessionProtocol` — `runtime_checkable` Protocol for host session plugins
|
|
65
|
-
|
|
66
|
-
## Usage
|
|
67
|
-
|
|
68
|
-
```python
|
|
69
|
-
from msw_plugin_api import HostSessionInfo, HostSessionProtocol
|
|
70
|
-
|
|
71
|
-
class MyHostSession:
|
|
72
|
-
def attach(self, **kwargs) -> HostSessionInfo:
|
|
73
|
-
return HostSessionInfo(
|
|
74
|
-
backend="myplugin",
|
|
75
|
-
acquisition_name="subject__20260609_143022__ext",
|
|
76
|
-
subject="subject",
|
|
77
|
-
parent_directory="/data/recordings",
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
def start(self) -> None: ...
|
|
81
|
-
def stop(self) -> None: ...
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Declare the plugin entry point in `pyproject.toml`:
|
|
85
|
-
|
|
86
|
-
```toml
|
|
87
|
-
[project.entry-points."msw.host"]
|
|
88
|
-
myplugin = "my_package.session:MyHostSession"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Plugin contract
|
|
92
|
-
|
|
93
|
-
MSW core checks `isinstance(session, HostSessionProtocol)` at dispatch time.
|
|
94
|
-
Plugins may implement the interface structurally (without importing this package)
|
|
95
|
-
or depend on `msw-plugin-api` explicitly for IDE support and test conformance checks.
|
msw_plugin_api-0.2.4/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.4
|
|
File without changes
|