ezmsg-baseproc 1.0__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.
- ezmsg/baseproc/__init__.py +155 -0
- ezmsg/baseproc/__version__.py +34 -0
- ezmsg/baseproc/composite.py +323 -0
- ezmsg/baseproc/processor.py +209 -0
- ezmsg/baseproc/protocols.py +147 -0
- ezmsg/baseproc/stateful.py +323 -0
- ezmsg/baseproc/units.py +282 -0
- ezmsg/baseproc/util/__init__.py +1 -0
- ezmsg/baseproc/util/asio.py +138 -0
- ezmsg/baseproc/util/message.py +31 -0
- ezmsg/baseproc/util/profile.py +171 -0
- ezmsg/baseproc/util/typeresolution.py +81 -0
- ezmsg_baseproc-1.0.dist-info/METADATA +106 -0
- ezmsg_baseproc-1.0.dist-info/RECORD +16 -0
- ezmsg_baseproc-1.0.dist-info/WHEEL +4 -0
- ezmsg_baseproc-1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from types import UnionType
|
|
3
|
+
|
|
4
|
+
from typing_extensions import get_original_bases
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def resolve_typevar(cls: type, target_typevar: typing.TypeVar) -> type:
|
|
8
|
+
"""
|
|
9
|
+
Resolve the concrete type bound to a TypeVar in a class hierarchy.
|
|
10
|
+
This function traverses the method resolution order (MRO) of the class
|
|
11
|
+
and checks the original bases of each class in the MRO for the TypeVar.
|
|
12
|
+
If the TypeVar is found, it returns the concrete type bound to it.
|
|
13
|
+
If the TypeVar is not found, it raises a TypeError.
|
|
14
|
+
Args:
|
|
15
|
+
cls (type): The class to inspect.
|
|
16
|
+
target_typevar (typing.TypeVar): The TypeVar to resolve.
|
|
17
|
+
Returns:
|
|
18
|
+
type: The concrete type bound to the TypeVar.
|
|
19
|
+
"""
|
|
20
|
+
for base in cls.__mro__:
|
|
21
|
+
orig_bases = get_original_bases(base)
|
|
22
|
+
for orig_base in orig_bases:
|
|
23
|
+
origin = typing.get_origin(orig_base)
|
|
24
|
+
if origin is None:
|
|
25
|
+
continue
|
|
26
|
+
params = getattr(origin, "__parameters__", ())
|
|
27
|
+
if not params:
|
|
28
|
+
continue
|
|
29
|
+
if target_typevar in params:
|
|
30
|
+
index = params.index(target_typevar)
|
|
31
|
+
args = typing.get_args(orig_base)
|
|
32
|
+
try:
|
|
33
|
+
return args[index]
|
|
34
|
+
except IndexError:
|
|
35
|
+
pass
|
|
36
|
+
raise TypeError(f"Could not resolve {target_typevar} in {cls}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
TypeLike = typing.Union[type[typing.Any], typing.Any, type(None), None]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def check_message_type_compatibility(type1: TypeLike, type2: TypeLike) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Check if two types are compatible for message passing.
|
|
45
|
+
Returns True if:
|
|
46
|
+
- Both are None/NoneType
|
|
47
|
+
- Either is typing.Any
|
|
48
|
+
- type1 is a subclass of type2, which includes
|
|
49
|
+
- type1 and type2 are concrete types and type1 is a subclass of type2
|
|
50
|
+
- type1 is None/NoneType and type2 is typing.Optional, or
|
|
51
|
+
- type1 is subtype of the non-None inner type of type2 if type2 is Optional
|
|
52
|
+
- type1 is a Union/Optional type and all inner types are compatible with type2
|
|
53
|
+
Args:
|
|
54
|
+
type1: First type to compare
|
|
55
|
+
type2: Second type to compare
|
|
56
|
+
Returns:
|
|
57
|
+
bool: True if the types are compatible, False otherwise
|
|
58
|
+
"""
|
|
59
|
+
# If either is Any, they are compatible
|
|
60
|
+
if type1 is typing.Any or type2 is typing.Any:
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
# Handle None as NoneType
|
|
64
|
+
if type1 is None:
|
|
65
|
+
type1 = type(None)
|
|
66
|
+
if type2 is None:
|
|
67
|
+
type2 = type(None)
|
|
68
|
+
|
|
69
|
+
# Handle if type1 is Optional/Union type
|
|
70
|
+
if typing.get_origin(type1) in {typing.Union, UnionType}:
|
|
71
|
+
return all(check_message_type_compatibility(inner_type, type2) for inner_type in typing.get_args(type1))
|
|
72
|
+
|
|
73
|
+
# Regular issubclass check. Handles cases like:
|
|
74
|
+
# - type1 is a subclass of concrete type2
|
|
75
|
+
# - type1 is a subclass of the inner type of type2 if type2 is Optional
|
|
76
|
+
# - type1 is a subclass of one of the inner types of type2 if type2 is Union
|
|
77
|
+
# - type1 is NoneType and type2 is Optional or Union[None, ...] or Union[NoneType, ...]
|
|
78
|
+
try:
|
|
79
|
+
return issubclass(type1, type2)
|
|
80
|
+
except TypeError:
|
|
81
|
+
return False
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ezmsg-baseproc
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Base processor classes and protocols for ezmsg signal processing pipelines
|
|
5
|
+
Author-email: Griffin Milsap <griffin.milsap@gmail.com>, Preston Peranich <pperanich@gmail.com>, Chadwick Boulay <chadwick.boulay@gmail.com>, Kyle McGraw <kmcgraw@blackrockneuro.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: ezmsg[axisarray]>=3.6.0
|
|
10
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# ezmsg-baseproc
|
|
14
|
+
|
|
15
|
+
Base processor classes and protocols for building signal processing pipelines in [ezmsg](https://github.com/ezmsg-org/ezmsg).
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install ezmsg-baseproc
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
This package provides the foundational processor architecture for ezmsg signal processing:
|
|
26
|
+
|
|
27
|
+
- **Protocols** - Type definitions for processors, transformers, consumers, and producers
|
|
28
|
+
- **Base Classes** - Abstract base classes for building stateless and stateful processors
|
|
29
|
+
- **Composite Processors** - Classes for chaining processors into pipelines
|
|
30
|
+
- **Unit Wrappers** - ezmsg Unit base classes that wrap processors for graph integration
|
|
31
|
+
|
|
32
|
+
## Module Structure
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
ezmsg.baseproc/
|
|
36
|
+
├── protocols.py # Protocol definitions and type variables
|
|
37
|
+
├── processor.py # Base non-stateful processors
|
|
38
|
+
├── stateful.py # Stateful processor base classes
|
|
39
|
+
├── composite.py # CompositeProcessor and CompositeProducer
|
|
40
|
+
├── units.py # ezmsg Unit wrappers
|
|
41
|
+
└── util/
|
|
42
|
+
├── asio.py # Async/sync utilities
|
|
43
|
+
├── message.py # SampleMessage definitions
|
|
44
|
+
├── profile.py # Profiling decorators
|
|
45
|
+
└── typeresolution.py # Type resolution helpers
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Creating a Simple Transformer
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from dataclasses import dataclass
|
|
54
|
+
from ezmsg.baseproc import BaseTransformer
|
|
55
|
+
from ezmsg.util.messages.axisarray import AxisArray
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class MySettings:
|
|
59
|
+
scale: float = 1.0
|
|
60
|
+
|
|
61
|
+
class MyTransformer(BaseTransformer[MySettings, AxisArray, AxisArray]):
|
|
62
|
+
def _process(self, message: AxisArray) -> AxisArray:
|
|
63
|
+
return message.replace(data=message.data * self.settings.scale)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Creating a Stateful Transformer
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from ezmsg.baseproc import BaseStatefulTransformer, processor_state
|
|
70
|
+
|
|
71
|
+
@processor_state
|
|
72
|
+
class MyState:
|
|
73
|
+
count: int = 0
|
|
74
|
+
hash: int = -1
|
|
75
|
+
|
|
76
|
+
class MyStatefulTransformer(BaseStatefulTransformer[MySettings, AxisArray, AxisArray, MyState]):
|
|
77
|
+
def _reset_state(self, message: AxisArray) -> None:
|
|
78
|
+
self._state.count = 0
|
|
79
|
+
|
|
80
|
+
def _process(self, message: AxisArray) -> AxisArray:
|
|
81
|
+
self._state.count += 1
|
|
82
|
+
return message
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Creating an ezmsg Unit
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from ezmsg.baseproc import BaseTransformerUnit
|
|
89
|
+
|
|
90
|
+
class MyUnit(BaseTransformerUnit[MySettings, AxisArray, AxisArray, MyTransformer]):
|
|
91
|
+
SETTINGS = MySettings
|
|
92
|
+
# That's all - the base class handles everything else!
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
We use [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for development.
|
|
98
|
+
|
|
99
|
+
1. Install `uv` if not already installed
|
|
100
|
+
2. Clone and cd into the repository
|
|
101
|
+
3. Run `uv sync` to create a `.venv` and install dependencies
|
|
102
|
+
4. Run `uv run pytest tests` to run tests
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ezmsg/baseproc/__init__.py,sha256=zmhdrRTnj3-ilBitu6zBniV04HJXc_fM_tKYiapFGko,3830
|
|
2
|
+
ezmsg/baseproc/__version__.py,sha256=T-YAefOAMONzdzJN9AfYa3q6PjJ-HRflYoFg45W1xFU,699
|
|
3
|
+
ezmsg/baseproc/composite.py,sha256=Lin4K_rmS2Tnxt-m8daP-PUyeeqL4id5JkVh-AUNrQw,14901
|
|
4
|
+
ezmsg/baseproc/processor.py,sha256=Ir9FtNuVG4yc-frwNxoYrlld99ff1mXwwGWaHxEJ6tY,8056
|
|
5
|
+
ezmsg/baseproc/protocols.py,sha256=QRFh2t-rQgOzU_nlvz5muH4RZWVuzt2VvQ0e_yFy3ZE,5062
|
|
6
|
+
ezmsg/baseproc/stateful.py,sha256=-jjAZIyJA5eiTECi1fSfazfqgv__RtyqPp1ZvLFFIDI,11424
|
|
7
|
+
ezmsg/baseproc/units.py,sha256=TRhjDKw0lqUUst7BHYJKP3AhGpRd6mvcdKxULfeWjA0,10283
|
|
8
|
+
ezmsg/baseproc/util/__init__.py,sha256=hvMUJOBuqioER50GZ5-GZiQbQ9NtQYEze13ZlR2jbMA,37
|
|
9
|
+
ezmsg/baseproc/util/asio.py,sha256=0sF5oDc58DSLlcEgoUpNiqjjcbqnZhjSpQrXn6IdosM,4960
|
|
10
|
+
ezmsg/baseproc/util/message.py,sha256=l_b1b6bXX8N6VF9RbUELzsHs73cKkDURBdIr0lt3CY0,909
|
|
11
|
+
ezmsg/baseproc/util/profile.py,sha256=QlBcIE5H6pA8Z2xbR08NYO3ANjCrJRWy8_BDIURWyOc,5760
|
|
12
|
+
ezmsg/baseproc/util/typeresolution.py,sha256=WCHHYIrMMZ1CfwJWVlJPQgFyY2gnGRNFJVQynAsee7Y,3113
|
|
13
|
+
ezmsg_baseproc-1.0.dist-info/METADATA,sha256=nevLbn7a_ZNfR3KgUA1UUa-WVjq8xYFUnUcfQGIx2YM,3308
|
|
14
|
+
ezmsg_baseproc-1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
+
ezmsg_baseproc-1.0.dist-info/licenses/LICENSE,sha256=BDD8rfac1Ur7mp0_3izEdr6fHgSA3Or6U1Kb0ZAWsow,1066
|
|
16
|
+
ezmsg_baseproc-1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ezmsg-org
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|