ezmsg 3.3.3__tar.gz → 3.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.
Files changed (108) hide show
  1. ezmsg-3.4.0/PKG-INFO +114 -0
  2. ezmsg-3.4.0/README.md +85 -0
  3. ezmsg-3.4.0/pyproject.toml +79 -0
  4. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/__init__.py +6 -0
  5. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/backend.py +31 -6
  6. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/backendprocess.py +24 -20
  7. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/collection.py +19 -4
  8. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/command.py +114 -6
  9. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/component.py +27 -10
  10. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/graphcontext.py +3 -1
  11. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/graphserver.py +15 -11
  12. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/message.py +8 -2
  13. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/messagecache.py +1 -1
  14. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/server.py +1 -1
  15. ezmsg-3.4.0/src/ezmsg/core/settings.py +65 -0
  16. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/shmserver.py +11 -1
  17. ezmsg-3.4.0/src/ezmsg/core/state.py +56 -0
  18. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/stream.py +12 -1
  19. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/subclient.py +0 -1
  20. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/unit.py +68 -10
  21. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/debuglog.py +18 -2
  22. ezmsg-3.4.0/src/ezmsg/util/gen_to_unit.py +195 -0
  23. ezmsg-3.4.0/src/ezmsg/util/generator.py +122 -0
  24. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/messagecodec.py +36 -22
  25. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/messagegate.py +23 -2
  26. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/messagelogger.py +44 -6
  27. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/messagequeue.py +14 -0
  28. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/messagereplay.py +100 -27
  29. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/messages/axisarray.py +67 -1
  30. ezmsg-3.4.0/src/ezmsg/util/messages/modify.py +38 -0
  31. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/terminate.py +30 -2
  32. ezmsg-3.3.3/PKG-INFO +0 -67
  33. ezmsg-3.3.3/README.md +0 -44
  34. ezmsg-3.3.3/docs/source/conf.py +0 -35
  35. ezmsg-3.3.3/examples/ezmsg_attach.py +0 -8
  36. ezmsg-3.3.3/examples/ezmsg_configs.py +0 -245
  37. ezmsg-3.3.3/examples/ezmsg_count.py +0 -64
  38. ezmsg-3.3.3/examples/ezmsg_intro.py +0 -187
  39. ezmsg-3.3.3/examples/ezmsg_normalterm.py +0 -123
  40. ezmsg-3.3.3/examples/ezmsg_stop.py +0 -117
  41. ezmsg-3.3.3/examples/ezmsg_toy.py +0 -198
  42. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/__init__.py +0 -1
  43. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/__version__.py +0 -1
  44. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/butterworthfilter.py +0 -66
  45. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/decimate.py +0 -40
  46. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/downsample.py +0 -63
  47. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/ewmfilter.py +0 -127
  48. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/filter.py +0 -140
  49. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/messages.py +0 -31
  50. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/sampler.py +0 -287
  51. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/spectral.py +0 -132
  52. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/synth.py +0 -411
  53. ezmsg-3.3.3/extensions/ezmsg-sigproc/ezmsg/sigproc/window.py +0 -144
  54. ezmsg-3.3.3/extensions/ezmsg-sigproc/setup.py +0 -7
  55. ezmsg-3.3.3/extensions/ezmsg-sigproc/tests/test_butterworth.py +0 -142
  56. ezmsg-3.3.3/extensions/ezmsg-sigproc/tests/test_downsample.py +0 -132
  57. ezmsg-3.3.3/extensions/ezmsg-sigproc/tests/test_window.py +0 -139
  58. ezmsg-3.3.3/extensions/ezmsg-sigproc/tests/util.py +0 -25
  59. ezmsg-3.3.3/extensions/ezmsg-websocket/examples/ezmsg_websocket.py +0 -107
  60. ezmsg-3.3.3/extensions/ezmsg-websocket/ezmsg/websocket/__init__.py +0 -1
  61. ezmsg-3.3.3/extensions/ezmsg-websocket/ezmsg/websocket/__version__.py +0 -1
  62. ezmsg-3.3.3/extensions/ezmsg-websocket/ezmsg/websocket/units.py +0 -158
  63. ezmsg-3.3.3/extensions/ezmsg-websocket/setup.py +0 -7
  64. ezmsg-3.3.3/extensions/ezmsg-zmq/ezmsg/zmq/__init__.py +0 -1
  65. ezmsg-3.3.3/extensions/ezmsg-zmq/ezmsg/zmq/__version__.py +0 -1
  66. ezmsg-3.3.3/extensions/ezmsg-zmq/ezmsg/zmq/units.py +0 -218
  67. ezmsg-3.3.3/extensions/ezmsg-zmq/setup.py +0 -7
  68. ezmsg-3.3.3/ezmsg/core/settings.py +0 -31
  69. ezmsg-3.3.3/ezmsg/core/state.py +0 -29
  70. ezmsg-3.3.3/ezmsg/testing/__init__.py +0 -28
  71. ezmsg-3.3.3/ezmsg/testing/debuglog.py +0 -1
  72. ezmsg-3.3.3/ezmsg/testing/lfo.py +0 -28
  73. ezmsg-3.3.3/ezmsg/testing/multiplier.py +0 -44
  74. ezmsg-3.3.3/ezmsg/testing/terminate.py +0 -4
  75. ezmsg-3.3.3/ezmsg/version/__init__.py +0 -1
  76. ezmsg-3.3.3/ezmsg/version/__version__.py +0 -1
  77. ezmsg-3.3.3/ezmsg.egg-info/PKG-INFO +0 -67
  78. ezmsg-3.3.3/ezmsg.egg-info/SOURCES.txt +0 -99
  79. ezmsg-3.3.3/ezmsg.egg-info/dependency_links.txt +0 -1
  80. ezmsg-3.3.3/ezmsg.egg-info/entry_points.txt +0 -2
  81. ezmsg-3.3.3/ezmsg.egg-info/requires.txt +0 -12
  82. ezmsg-3.3.3/ezmsg.egg-info/top_level.txt +0 -6
  83. ezmsg-3.3.3/pyproject.toml +0 -6
  84. ezmsg-3.3.3/setup.cfg +0 -38
  85. ezmsg-3.3.3/setup.py +0 -3
  86. ezmsg-3.3.3/tests/test_addressable.py +0 -31
  87. ezmsg-3.3.3/tests/test_attach.py +0 -131
  88. ezmsg-3.3.3/tests/test_axisarray.py +0 -127
  89. ezmsg-3.3.3/tests/test_connections.py +0 -44
  90. ezmsg-3.3.3/tests/test_dag.py +0 -113
  91. ezmsg-3.3.3/tests/test_graph.py +0 -248
  92. ezmsg-3.3.3/tests/test_perf.py +0 -225
  93. ezmsg-3.3.3/tests/test_run.py +0 -152
  94. ezmsg-3.3.3/tests/test_shm.py +0 -107
  95. ezmsg-3.3.3/tests/test_state.py +0 -76
  96. ezmsg-3.3.3/tests/test_test.py +0 -8
  97. {ezmsg-3.3.3 → ezmsg-3.4.0}/LICENSE +0 -0
  98. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/__main__.py +0 -0
  99. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/addressable.py +0 -0
  100. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/backpressure.py +0 -0
  101. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/dag.py +0 -0
  102. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/messagemarshal.py +0 -0
  103. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/netprotocol.py +0 -0
  104. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/pubclient.py +0 -0
  105. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/core/util.py +0 -0
  106. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/__init__.py +0 -0
  107. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/messages/__init__.py +0 -0
  108. {ezmsg-3.3.3 → ezmsg-3.4.0/src}/ezmsg/util/rate.py +0 -0
ezmsg-3.4.0/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.1
2
+ Name: ezmsg
3
+ Version: 3.4.0
4
+ Summary: A simple DAG-based computation model
5
+ License: MIT
6
+ Author: Milsap, Griffin
7
+ Author-email: griffin.milsap@gmail.com
8
+ Requires-Python: >=3.8,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Provides-Extra: all-ext
18
+ Provides-Extra: sigproc
19
+ Provides-Extra: vispy
20
+ Provides-Extra: websocket
21
+ Provides-Extra: zmq
22
+ Requires-Dist: ezmsg-sigproc ; extra == "sigproc" or extra == "all-ext"
23
+ Requires-Dist: ezmsg-vispy ; extra == "vispy" or extra == "all-ext"
24
+ Requires-Dist: ezmsg-websocket ; extra == "websocket" or extra == "all-ext"
25
+ Requires-Dist: ezmsg-zmq ; extra == "zmq" or extra == "all-ext"
26
+ Requires-Dist: typing-extensions (>=4.9.0,<5.0.0)
27
+ Description-Content-Type: text/markdown
28
+
29
+ # <img src="https://raw.githubusercontent.com/iscoe/ezmsg/main/docs/source/logo.png" width="50"/> ezmsg
30
+
31
+ Messaging and Multiprocessing.
32
+
33
+ `ezmsg` is a pure-Python implementation of a directed acyclic graph (DAG) pub/sub messaging pattern based on [`labgraph`](https://github.com/facebookresearch/labgraph) which is optimized and intended for use in constructing real-time software. `ezmsg` implements much of the `labgraph` API (with a few notable differences), and owes a lot of its design to the `labgraph` developers/project.
34
+
35
+ `ezmsg` is very fast and uses Python's `multiprocessing.shared_memory` module to facilitate efficient message passing without C++ or any compilation/build tooling.
36
+
37
+ ## Installation
38
+
39
+ `pip install ezmsg`
40
+
41
+ ## Dependencies
42
+
43
+ - Due to reliance on `multiprocessing.shared_memory`, `ezmsg` requires **minimum Python 3.8**.
44
+ - `typing_extensions`
45
+
46
+ Testing `ezmsg` requires:
47
+
48
+ - `pytest`
49
+ - `pytest-cov`
50
+ - `pytest-asyncio`
51
+ - `numpy`
52
+
53
+ ## Setup (Development)
54
+
55
+ ```bash
56
+ $ python3 -m venv env
57
+ $ source env/bin/activate
58
+ (env) $ pip install --upgrade pip poetry
59
+ (env) $ poetry install --with test
60
+
61
+ (env) $ python -m pytest tests # Optionally, Perform tests
62
+ ```
63
+
64
+ Note that it is generally recommended to install poetry into its own standalone venv via the `pipx` cli tool.
65
+
66
+ ## Documentation
67
+
68
+ https://ezmsg.readthedocs.io/en/latest/
69
+
70
+ `ezmsg` is very similar to [`labgraph`](https://www.github.com/facebookresearch/labgraph), so you might get a primer with their documentation and examples. Additionally, there are many examples provided in the examples/tests directories strewn throughout this repository.
71
+
72
+ ## Extensions
73
+
74
+ `ezmsg` extensions can be installed individually or all at once. To install all the extension packages in one go, you can use the following command:
75
+
76
+ ```bash
77
+ pip install "ezmsg[all_ext]"
78
+ ```
79
+
80
+ This will install all the available public extension packages for `ezmsg` that are listed in `pyproject.toml`.
81
+ If you prefer to install a subset of extension packages, you can use the following command:
82
+
83
+ ```bash
84
+ pip install "ezmsg[zmq,sigproc,...]"
85
+ ```
86
+
87
+ Please note that the `ezmsg` package itself can still be installed without any additional extensions using `pip install ezmsg`.
88
+
89
+ Extensions can be managed manually as well. Here are some of the extensions we manage or are aware of:
90
+
91
+ - [ezmsg-sigproc](https://github.com/ezmsg-org/ezmsg-sigproc) -- Timeseries signal processing modules
92
+ - [ezmsg-websocket](https://github.com/ezmsg-org/ezmsg-websocket) -- Websocket server and client nodes for `ezmsg` graphs
93
+ - [ezmsg-zmq](https://github.com/ezmsg-org/ezmsg-zmq) -- ZeroMQ pub and sub nodes for `ezmsg` graphs
94
+ - [ezmsg-panel](https://github.com/griffinmilsap/ezmsg-panel) -- Plotting tools for `ezmsg` that use [panel](https://github.com/holoviz/panel)
95
+ - [ezmsg-blackrock](https://github.com/griffinmilsap/ezmsg-blackrock) -- Interface for Blackrock Cerebus ecosystem (incl. Neuroport) using `pycbsdk`
96
+ - [ezmsg-lsl](https://github.com/ezmsg-org/ezmsg-lsl) -- Source unit for LSL Inlet and sink unit for LSL Outlet
97
+ - [ezmsg-unicorn](https://github.com/griffinmilsap/ezmsg-unicorn) -- g.tec Unicorn Hybrid Black integration for `ezmsg`
98
+ - [ezmsg-gadget](https://github.com/griffinmilsap/ezmsg-gadget) -- USB-gadget with HID control integration for Raspberry Pi (Zero/W/2W, 4, CM4)
99
+ - [ezmsg-openbci](https://github.com/griffinmilsap/ezmsg-openbci) -- OpenBCI Cyton serial interface for `ezmsg`
100
+ - [ezmsg-ssvep](https://github.com/griffinmilsap/ezmsg-ssvep) -- Tools for running SSVEP experiments with `ezmsg`
101
+ - [ezmsg-vispy](https://github.com/pperanich/ezmsg-vispy) -- `ezmsg` visualization toolkit using PyQt6 and vispy.
102
+
103
+ ## Publications
104
+
105
+ A collection of academic papers, journals, and other publications that have cited or utilized `ezmsg` in research and development.
106
+ These publications provide insights into the practical applications and impact of `ezmsg` in various fields.
107
+
108
+ - [A click-based electrocorticographic brain-computer interface enables long-term high-performance switch-scan spelling](https://doi.org/10.21203/rs.3.rs-3158792/v1)
109
+ - [Stable Decoding from a Speech BCI Enables Control for an Individual with ALS without Recalibration for 3 Months](https://doi.org/10.1002/advs.202304853)
110
+
111
+ ## Financial Support
112
+
113
+ `ezmsg` is supported by Johns Hopkins University (JHU), the JHU Applied Physics Laboratory (APL), and by the Wyss Center for Bio and Neuro Engineering.
114
+
ezmsg-3.4.0/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # <img src="https://raw.githubusercontent.com/iscoe/ezmsg/main/docs/source/logo.png" width="50"/> ezmsg
2
+
3
+ Messaging and Multiprocessing.
4
+
5
+ `ezmsg` is a pure-Python implementation of a directed acyclic graph (DAG) pub/sub messaging pattern based on [`labgraph`](https://github.com/facebookresearch/labgraph) which is optimized and intended for use in constructing real-time software. `ezmsg` implements much of the `labgraph` API (with a few notable differences), and owes a lot of its design to the `labgraph` developers/project.
6
+
7
+ `ezmsg` is very fast and uses Python's `multiprocessing.shared_memory` module to facilitate efficient message passing without C++ or any compilation/build tooling.
8
+
9
+ ## Installation
10
+
11
+ `pip install ezmsg`
12
+
13
+ ## Dependencies
14
+
15
+ - Due to reliance on `multiprocessing.shared_memory`, `ezmsg` requires **minimum Python 3.8**.
16
+ - `typing_extensions`
17
+
18
+ Testing `ezmsg` requires:
19
+
20
+ - `pytest`
21
+ - `pytest-cov`
22
+ - `pytest-asyncio`
23
+ - `numpy`
24
+
25
+ ## Setup (Development)
26
+
27
+ ```bash
28
+ $ python3 -m venv env
29
+ $ source env/bin/activate
30
+ (env) $ pip install --upgrade pip poetry
31
+ (env) $ poetry install --with test
32
+
33
+ (env) $ python -m pytest tests # Optionally, Perform tests
34
+ ```
35
+
36
+ Note that it is generally recommended to install poetry into its own standalone venv via the `pipx` cli tool.
37
+
38
+ ## Documentation
39
+
40
+ https://ezmsg.readthedocs.io/en/latest/
41
+
42
+ `ezmsg` is very similar to [`labgraph`](https://www.github.com/facebookresearch/labgraph), so you might get a primer with their documentation and examples. Additionally, there are many examples provided in the examples/tests directories strewn throughout this repository.
43
+
44
+ ## Extensions
45
+
46
+ `ezmsg` extensions can be installed individually or all at once. To install all the extension packages in one go, you can use the following command:
47
+
48
+ ```bash
49
+ pip install "ezmsg[all_ext]"
50
+ ```
51
+
52
+ This will install all the available public extension packages for `ezmsg` that are listed in `pyproject.toml`.
53
+ If you prefer to install a subset of extension packages, you can use the following command:
54
+
55
+ ```bash
56
+ pip install "ezmsg[zmq,sigproc,...]"
57
+ ```
58
+
59
+ Please note that the `ezmsg` package itself can still be installed without any additional extensions using `pip install ezmsg`.
60
+
61
+ Extensions can be managed manually as well. Here are some of the extensions we manage or are aware of:
62
+
63
+ - [ezmsg-sigproc](https://github.com/ezmsg-org/ezmsg-sigproc) -- Timeseries signal processing modules
64
+ - [ezmsg-websocket](https://github.com/ezmsg-org/ezmsg-websocket) -- Websocket server and client nodes for `ezmsg` graphs
65
+ - [ezmsg-zmq](https://github.com/ezmsg-org/ezmsg-zmq) -- ZeroMQ pub and sub nodes for `ezmsg` graphs
66
+ - [ezmsg-panel](https://github.com/griffinmilsap/ezmsg-panel) -- Plotting tools for `ezmsg` that use [panel](https://github.com/holoviz/panel)
67
+ - [ezmsg-blackrock](https://github.com/griffinmilsap/ezmsg-blackrock) -- Interface for Blackrock Cerebus ecosystem (incl. Neuroport) using `pycbsdk`
68
+ - [ezmsg-lsl](https://github.com/ezmsg-org/ezmsg-lsl) -- Source unit for LSL Inlet and sink unit for LSL Outlet
69
+ - [ezmsg-unicorn](https://github.com/griffinmilsap/ezmsg-unicorn) -- g.tec Unicorn Hybrid Black integration for `ezmsg`
70
+ - [ezmsg-gadget](https://github.com/griffinmilsap/ezmsg-gadget) -- USB-gadget with HID control integration for Raspberry Pi (Zero/W/2W, 4, CM4)
71
+ - [ezmsg-openbci](https://github.com/griffinmilsap/ezmsg-openbci) -- OpenBCI Cyton serial interface for `ezmsg`
72
+ - [ezmsg-ssvep](https://github.com/griffinmilsap/ezmsg-ssvep) -- Tools for running SSVEP experiments with `ezmsg`
73
+ - [ezmsg-vispy](https://github.com/pperanich/ezmsg-vispy) -- `ezmsg` visualization toolkit using PyQt6 and vispy.
74
+
75
+ ## Publications
76
+
77
+ A collection of academic papers, journals, and other publications that have cited or utilized `ezmsg` in research and development.
78
+ These publications provide insights into the practical applications and impact of `ezmsg` in various fields.
79
+
80
+ - [A click-based electrocorticographic brain-computer interface enables long-term high-performance switch-scan spelling](https://doi.org/10.21203/rs.3.rs-3158792/v1)
81
+ - [Stable Decoding from a Speech BCI Enables Control for an Individual with ALS without Recalibration for 3 Months](https://doi.org/10.1002/advs.202304853)
82
+
83
+ ## Financial Support
84
+
85
+ `ezmsg` is supported by Johns Hopkins University (JHU), the JHU Applied Physics Laboratory (APL), and by the Wyss Center for Bio and Neuro Engineering.
@@ -0,0 +1,79 @@
1
+ [tool.poetry]
2
+ name = "ezmsg"
3
+ version = "3.4.0"
4
+ description = "A simple DAG-based computation model"
5
+ authors = [
6
+ "Milsap, Griffin <griffin.milsap@gmail.com>",
7
+ "Peranich, Preston <pperanich@gmail.com>",
8
+ ]
9
+ license = "MIT"
10
+ readme = "README.md"
11
+ packages = [{ include = "ezmsg", from = "src" }]
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Operating System :: OS Independent",
15
+ ]
16
+
17
+ [tool.poetry.scripts]
18
+ ezmsg = "ezmsg.core.command:cmdline"
19
+
20
+ [tool.poetry.dependencies]
21
+ python = "^3.8"
22
+ typing-extensions = "^4.9.0"
23
+ ezmsg-sigproc = { version = "*", source = "pypi", optional = true }
24
+ ezmsg-websocket = { version = "*", source = "pypi", optional = true }
25
+ ezmsg-zmq = { version = "*", source = "pypi", optional = true }
26
+ ezmsg-vispy = { version = "*", source = "pypi", optional = true }
27
+ # ezmsg-panel = { git = "https://github.com/griffinmilsap/ezmsg-panel.git", rev = "main", optional = true }
28
+ # ezmsg-blackrock = { python = "^3.9", git = "https://github.com/CerebusOSS/ezmsg-blackrock.git", rev = "main", optional = true }
29
+ # ezmsg-unicorn = { git = "https://github.com/griffinmilsap/ezmsg-unicorn.git", rev = "main", optional = true }
30
+ # ezmsg-gadget = { git = "https://github.com/griffinmilsap/ezmsg-gadget.git", rev = "main", optional = true }
31
+ # ezmsg-openbci = { git = "https://github.com/griffinmilsap/ezmsg-openbci.git", rev = "main", optional = true }
32
+ # ezmsg-ssvep = { git = "https://github.com/griffinmilsap/ezmsg-ssvep.git", rev = "main", optional = true }
33
+
34
+
35
+ [tool.poetry.group.test.dependencies]
36
+ pytest = "^7.0.0"
37
+ pytest-asyncio = "*"
38
+ pytest-cov = "*"
39
+ numpy = "^1.24.2"
40
+ flake8 = "*"
41
+
42
+
43
+ [tool.poetry.group.docs.dependencies]
44
+ sphinx = "<7.2"
45
+ sphinx-rtd-theme = "^2.0.0"
46
+ ezmsg-sigproc = { version = "*", source = "pypi" }
47
+
48
+ [tool.poetry.extras]
49
+ sigproc = ["ezmsg-sigproc"]
50
+ websocket = ["ezmsg-websocket"]
51
+ zmq = ["ezmsg-zmq"]
52
+ # panel = ["ezmsg-panel"]
53
+ # blackrock = ["ezmsg-blackrock"]
54
+ # unicorn = ["ezmsg-unicorn"]
55
+ # gadget = ["ezmsg-gadget"]
56
+ # openbci = ["ezmsg-openbci"]
57
+ # ssvep = ["ezmsg-ssvep"]
58
+ vispy = ["ezmsg-vispy"]
59
+ all_ext = [
60
+ "ezmsg-sigproc",
61
+ "ezmsg-websocket",
62
+ "ezmsg-zmq",
63
+ # "ezmsg-panel",
64
+ # "ezmsg-blackrock",
65
+ # "ezmsg-unicorn",
66
+ # "ezmsg-gadget",
67
+ # "ezmsg-openbci",
68
+ # "ezmsg-ssvep",
69
+ "ezmsg-vispy",
70
+ ]
71
+
72
+ [tool.pytest.ini_options]
73
+ addopts = ["--import-mode=importlib"]
74
+ pythonpath = ["src", "tests"]
75
+ testpaths = "tests"
76
+
77
+ [build-system]
78
+ requires = ["poetry-core"]
79
+ build-backend = "poetry.core.masonry.api"
@@ -1,3 +1,8 @@
1
+ import importlib.metadata
2
+
3
+
4
+ __version__ = importlib.metadata.version("ezmsg")
5
+
1
6
  __all__ = [
2
7
  "task",
3
8
  "publisher",
@@ -60,3 +65,4 @@ logger.addHandler(handler)
60
65
 
61
66
  LOGLEVEL = os.environ.get("EZMSG_LOGLEVEL", "INFO").upper()
62
67
  logger.setLevel(LOGLEVEL)
68
+ logger.debug(f'Log level set to {LOGLEVEL}')
@@ -167,6 +167,29 @@ def run(
167
167
  force_single_process: bool = False,
168
168
  **components_kwargs: Component,
169
169
  ) -> None:
170
+ """
171
+ Begin execution of a set of :obj:`Component` s.
172
+
173
+ `The old method` :obj:`run_system` `has been deprecated and uses` ``run()`` `instead.`
174
+
175
+ Args:
176
+ components: represents the nodes in the directed acyclic graph. It is a dictionary which contains the
177
+ ``Components`` to be run mapped to string names. On initialization, ``ezmsg`` will call ``initialize()``
178
+ for each :obj:`Unit` and ``configure()`` for each :obj:`Collection`, if defined.
179
+ root_name:
180
+ connections: represents the edges is a ``NetworkDefinition`` which connects
181
+ ``OutputStreams`` to ``InputStreams``. On initialization, ``ezmsg`` will create a directed acyclic graph
182
+ using the contents of this parameter.
183
+ process_components: a list of ``Components`` which should live in their own process.
184
+ backend_process: is currently under development.
185
+ graph_address: the hostname and port of the graph server which ``ezmsg`` should connect to.
186
+ If not defined, ``ezmsg`` will start a new graph server at 127.0.0.1:25978.
187
+ force_single_process: run all ``Components`` in one process.
188
+ This is necessary when running ``ezmsg`` in a notebook.
189
+ components_kwargs:
190
+ """
191
+ # FIXME: This function is the last major re-implementation needed to make this
192
+ # codebase more maintainable.
170
193
  graph_service = GraphService(graph_address)
171
194
  shm_service = SHMService()
172
195
 
@@ -195,16 +218,20 @@ def run(
195
218
  if execution_context is None:
196
219
  return
197
220
 
221
+ # FIXME: When done this way, we don't exit the graph_context on exception
198
222
  async def create_graph_context() -> GraphContext:
199
223
  return await GraphContext(graph_service, shm_service).__aenter__()
200
224
 
201
- async def cleanup_graph(context: GraphContext) -> None:
202
- await context.__aexit__(None, None, None)
203
-
225
+ # FIXME: This sort of stuff should all be done in a separate async function...
226
+ # Done this way, its ugly as hell and opens us up to a lot of issues with
227
+ # entering and exiting context properly on exceptions.
204
228
  graph_context = asyncio.run_coroutine_threadsafe(
205
229
  create_graph_context(), loop
206
230
  ).result()
207
231
 
232
+ async def cleanup_graph() -> None:
233
+ await graph_context.__aexit__(None, None, None)
234
+
208
235
  async def setup_graph() -> None:
209
236
  for edge in execution_context.connections:
210
237
  await graph_context.connect(*edge)
@@ -252,9 +279,7 @@ def run(
252
279
 
253
280
  finally:
254
281
  join_all_other_processes()
255
- asyncio.run_coroutine_threadsafe(
256
- cleanup_graph(graph_context), loop
257
- ).result()
282
+ asyncio.run_coroutine_threadsafe(cleanup_graph(), loop).result()
258
283
 
259
284
 
260
285
  def collect_processes(
@@ -41,10 +41,17 @@ logger = logging.getLogger("ezmsg")
41
41
 
42
42
 
43
43
  class Complete(Exception):
44
+ """
45
+ A type of ``Exception`` which signals to ``ezmsg`` that the function can be shut down gracefully.
46
+ If all functions in all :obj:`Units` raise ``Complete``, the entire pipeline will terminate execution.
47
+ """
44
48
  pass
45
49
 
46
50
 
47
51
  class NormalTermination(Exception):
52
+ """
53
+ A type of ``Exception`` which signals to ``ezmsg`` that the pipeline can be shut down gracefully.
54
+ """
48
55
  pass
49
56
 
50
57
 
@@ -294,32 +301,28 @@ class DefaultBackendProcess(BackendProcess):
294
301
  )
295
302
 
296
303
  pub_fn = perf_publish if hasattr(task, TIMEIT_ATTR) else publish
304
+
305
+ call_fn = lambda _: task(unit)
306
+ signature = inspect.signature(task)
307
+ if len(signature.parameters) == 1:
308
+ call_fn = lambda _: task(unit)
309
+ elif len(signature.parameters) == 2:
310
+ call_fn = lambda msg: task(unit, msg)
311
+ else:
312
+ logger.error(f'Incompatible call signature on task: {task.__name__}')
297
313
 
298
314
  @wraps(task)
299
315
  async def wrapped_task(msg: Any = None) -> None:
300
316
  try:
301
- # If we don't sub or pub anything, we are a simple task
302
- if not hasattr(task, SUBSCRIBES_ATTR) and not hasattr(
303
- task, PUBLISHES_ATTR
304
- ):
305
- await task(unit)
306
-
307
- # No subscriptions; only publications...
308
- elif not hasattr(task, SUBSCRIBES_ATTR):
309
- async for stream, obj in task(unit):
317
+ result = call_fn(msg)
318
+ if inspect.isasyncgen(result):
319
+ async for stream, obj in result:
320
+ if obj and getattr(task, ZERO_COPY_ATTR, False) and obj is msg:
321
+ obj = deepcopy(obj)
310
322
  await pub_fn(stream, obj)
311
323
 
312
- # Subscribers need to be called with a message
313
- else:
314
- if not getattr(task, ZERO_COPY_ATTR):
315
- msg = deepcopy(msg)
316
- if hasattr(task, PUBLISHES_ATTR):
317
- async for stream, obj in task(unit, msg):
318
- if getattr(task, ZERO_COPY_ATTR) and obj is msg:
319
- obj = deepcopy(obj)
320
- await pub_fn(stream, obj)
321
- else:
322
- await task(unit, msg)
324
+ elif asyncio.iscoroutine(result):
325
+ await result
323
326
 
324
327
  except Complete:
325
328
  logger.info(f"{task_address} Complete")
@@ -386,3 +389,4 @@ def new_threaded_event_loop(
386
389
  loop.call_soon_threadsafe(loop.stop)
387
390
  thread.join()
388
391
  loop.close()
392
+ logger.debug("Task thread stopped and closed")
@@ -34,21 +34,36 @@ class CollectionMeta(ComponentMeta):
34
34
 
35
35
 
36
36
  class Collection(Component, metaclass=CollectionMeta):
37
- """Collections can contain subunits and connect them together"""
37
+ """
38
+ Connects :obj:`Unit` s together by defining a graph which connects :obj:`OutputStream` s to :obj:`InputStream` s.
39
+ """
38
40
 
39
- def __init__(self, settings: typing.Optional[Settings] = None):
40
- super(Collection, self).__init__(settings)
41
+ def __init__(self, *args, settings: typing.Optional[Settings] = None, **kwargs):
42
+ super(Collection, self).__init__(*args, settings=settings, **kwargs)
41
43
 
42
44
  self._components = deepcopy(self.__class__.__components__)
43
45
  for comp_name, comp in self.components.items():
44
46
  setattr(self, comp_name, comp)
45
47
 
46
48
  def configure(self) -> None:
47
- """This is where to percolate apply_settings to subnodes"""
49
+ """
50
+ A lifecycle hook that runs when the :obj:`Collection` is instantiated.
51
+ This is the best place to call ``Unit.apply_settings()`` on each member :obj:`Unit` of the :obj:`Collection`.
52
+ """
48
53
  ...
49
54
 
50
55
  def network(self) -> NetworkDefinition:
56
+ """
57
+ Override this method and have the definition return a :obj:`NetworkDefinition` which defines how
58
+ :obj:`InputStream` and :obj:`OutputStream` from member :obj:`Unit` s will be connected.
59
+ """
51
60
  return ()
52
61
 
53
62
  def process_components(self) -> typing.Collection[Component]:
63
+ """
64
+ Override this method and have the definition return a tuple which contains :obj:`Unit` and :obj:`Collection`
65
+ which should run in their own processes.
66
+
67
+ Return: the :obj:`Collection`.
68
+ """
54
69
  return (self,)
@@ -1,9 +1,12 @@
1
1
  import os
2
+ import sys
3
+ import base64
2
4
  import asyncio
3
5
  import argparse
4
6
  import logging
5
7
  import subprocess
6
8
  import typing
9
+ import webbrowser
7
10
 
8
11
  from collections import defaultdict
9
12
  from uuid import uuid4
@@ -43,7 +46,7 @@ def cmdline() -> None:
43
46
  parser.add_argument(
44
47
  "command",
45
48
  help="command for ezmsg",
46
- choices=["serve", "start", "shutdown", "graphviz"],
49
+ choices=["serve", "start", "shutdown", "graphviz", "mermaid"],
47
50
  )
48
51
 
49
52
  parser.add_argument("--address", help="Address for GraphServer", default=None)
@@ -95,7 +98,7 @@ async def run_command(cmd: str, graph_address: Address, shm_address: Address) ->
95
98
 
96
99
  elif cmd == "start":
97
100
  popen = subprocess.Popen(
98
- ["python", "-m", "ezmsg.core", "serve", f"--address={graph_address}"]
101
+ [sys.executable, "-m", "ezmsg.core", "serve", f"--address={graph_address}"]
99
102
  )
100
103
 
101
104
  while True:
@@ -113,10 +116,14 @@ async def run_command(cmd: str, graph_address: Address, shm_address: Address) ->
113
116
  elif cmd == "shutdown":
114
117
  try:
115
118
  await graph_service.shutdown()
116
- logger.info(f"Issued shutdown command to GraphServer @ {graph_service.address}")
119
+ logger.info(
120
+ f"Issued shutdown command to GraphServer @ {graph_service.address}"
121
+ )
117
122
 
118
123
  except ConnectionRefusedError:
119
- logger.warning(f"Could not issue shutdown command to GraphServer @ {graph_service.address}; server not running?")
124
+ logger.warning(
125
+ f"Could not issue shutdown command to GraphServer @ {graph_service.address}; server not running?"
126
+ )
120
127
 
121
128
  elif cmd == "graphviz":
122
129
  try:
@@ -148,7 +155,9 @@ async def run_command(cmd: str, graph_address: Address, shm_address: Address) ->
148
155
  new_conns = conns.copy()
149
156
  new_conns.remove(proxy_topic)
150
157
  new_conns.union(downstreams)
151
- logger.info(f"Updating connections for {node} from {conns} to {new_conns}")
158
+ logger.info(
159
+ f"Updating connections for {node} from {conns} to {new_conns}"
160
+ )
152
161
  graph_connections[node] = new_conns
153
162
 
154
163
  graph_connections = dag.graph.copy()
@@ -192,7 +201,7 @@ async def run_command(cmd: str, graph_address: Address, shm_address: Address) ->
192
201
  ),
193
202
  IND,
194
203
  )
195
- elif type(g[leaf]) == str:
204
+ elif isinstance(g[leaf], str):
196
205
  out += indent(f"{node_map[g[leaf]]} [label={leaf}];" + "\n", IND)
197
206
  return out
198
207
 
@@ -209,3 +218,102 @@ async def run_command(cmd: str, graph_address: Address, shm_address: Address) ->
209
218
  )
210
219
 
211
220
  print(graphviz_out)
221
+ elif cmd == "mermaid":
222
+ try:
223
+ dag: DAG = await graph_service.dag()
224
+ except (ConnectionRefusedError, ConnectionResetError):
225
+ logger.info(
226
+ f"GraphServer not running @{graph_address}, or host is refusing connections"
227
+ )
228
+ return
229
+
230
+ graph_connections = dag.graph.copy()
231
+ # Let's eliminate proxy topics, i.e. connections with inputs and outputs.
232
+ source_nodes = []
233
+ for node, conns in graph_connections.items():
234
+ if len(conns) > 0:
235
+ source_nodes += [node]
236
+ proxy_topics = []
237
+ for conns in graph_connections.values():
238
+ for conn in conns:
239
+ if conn in source_nodes and conn not in proxy_topics:
240
+ proxy_topics += [conn]
241
+ # Replace Proxy Topics with actual source and downstream
242
+ for proxy_topic in proxy_topics:
243
+ downstreams = graph_connections.pop(proxy_topic)
244
+ logger.info(f"{proxy_topic} downstream connetions: {downstreams}")
245
+ for node, conns in graph_connections.items():
246
+ for conn in conns:
247
+ if conn == proxy_topic:
248
+ new_conns = conns.copy()
249
+ new_conns.remove(proxy_topic)
250
+ new_conns.union(downstreams)
251
+ logger.info(
252
+ f"Updating connections for {node} from {conns} to {new_conns}"
253
+ )
254
+ graph_connections[node] = new_conns
255
+
256
+ graph_connections = dag.graph.copy()
257
+ # Let's come up with UUID node names
258
+ nodes = set(graph_connections.keys())
259
+ node_map = {name: f"{str(uuid4())}" for name in nodes}
260
+
261
+ # Construct the graph
262
+ def tree():
263
+ return defaultdict(tree)
264
+
265
+ graph: defaultdict = tree()
266
+
267
+ connections = ""
268
+ for node, conns in graph_connections.items():
269
+ subgraph = graph
270
+ path = node.split("/")
271
+ route = path[:-1]
272
+ stream = path[-1]
273
+ for seg in route:
274
+ subgraph = subgraph[seg]
275
+ subgraph[stream] = node
276
+
277
+ for sub in conns:
278
+ connections += f"{node_map[node]} --> {node_map[sub]}" + "\n"
279
+
280
+ # Now convert to dot syntax
281
+ def recurse_graph(g: defaultdict):
282
+ out = ""
283
+ for leaf in g:
284
+ if type(g[leaf]) == defaultdict:
285
+ out += indent(
286
+ "\n".join(
287
+ [
288
+ f"subgraph {leaf.lower()} [{leaf}]",
289
+ # "direction LR",
290
+ f"{recurse_graph(g[leaf])}",
291
+ "end",
292
+ "",
293
+ ]
294
+ ),
295
+ IND,
296
+ )
297
+ elif isinstance(g[leaf], str):
298
+ out += indent(f"{node_map[g[leaf]]}[{leaf}]" + "\n", IND)
299
+ return out
300
+
301
+ subgraph_tree = recurse_graph(graph)
302
+
303
+ mermaid = "\n".join(
304
+ [
305
+ "flowchart LR",
306
+ subgraph_tree,
307
+ indent(connections, IND),
308
+ ]
309
+ )
310
+
311
+ # print(mermaid)
312
+ webbrowser.open(mm(mermaid))
313
+
314
+
315
+ def mm(graph):
316
+ graphbytes = graph.encode("utf8")
317
+ base64_bytes = base64.b64encode(graphbytes)
318
+ base64_string = base64_bytes.decode("ascii")
319
+ return f"https://mermaid.ink/img/{base64_string}"