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.
@@ -10,5 +10,8 @@ build/
10
10
  .mypy_cache/
11
11
  .ruff_cache/
12
12
 
13
+ # library: do not commit the lockfile
14
+ uv.lock
15
+
13
16
  # hatch-vcs generated version file
14
17
  src/msw_plugin_api/_version.py
@@ -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
@@ -52,7 +52,7 @@ dev = [
52
52
  [tool.commitizen]
53
53
  name = "cz_conventional_commits"
54
54
  version_provider = "commitizen"
55
- version = "0.2.4"
55
+ version = "0.4.0"
56
56
  version_files = ["VERSION"]
57
57
  tag_format = "v$version"
58
58
  update_changelog_on_bump = false
@@ -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.2.4'
22
- __version_tuple__ = version_tuple = (0, 2, 4)
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
@@ -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
@@ -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.
@@ -1 +0,0 @@
1
- 0.2.4