pynode-flow 0.2.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 (277) hide show
  1. pynode_flow-0.2.0/.dockerignore +23 -0
  2. pynode_flow-0.2.0/.github/workflows/ci.yml +44 -0
  3. pynode_flow-0.2.0/.github/workflows/publish.yaml +60 -0
  4. pynode_flow-0.2.0/.gitignore +193 -0
  5. pynode_flow-0.2.0/DOCKER.md +113 -0
  6. pynode_flow-0.2.0/Dockerfile +66 -0
  7. pynode_flow-0.2.0/Dockerfile.cpu +49 -0
  8. pynode_flow-0.2.0/INSTALL.md +145 -0
  9. pynode_flow-0.2.0/LICENSE +21 -0
  10. pynode_flow-0.2.0/MANIFEST.in +7 -0
  11. pynode_flow-0.2.0/PKG-INFO +520 -0
  12. pynode_flow-0.2.0/README.md +453 -0
  13. pynode_flow-0.2.0/docker-compose.yml +32 -0
  14. pynode_flow-0.2.0/docs/CREATING_NODES.md +695 -0
  15. pynode_flow-0.2.0/docs/CUSTOM_NODES.md +611 -0
  16. pynode_flow-0.2.0/docs/DOCKER.md +398 -0
  17. pynode_flow-0.2.0/docs/EXTENSIBILITY.md +612 -0
  18. pynode_flow-0.2.0/docs/UI_COMPONENTS.md +560 -0
  19. pynode_flow-0.2.0/examples/README.md +117 -0
  20. pynode_flow-0.2.0/examples/camera_workflow.py +196 -0
  21. pynode_flow-0.2.0/examples/camera_yolo_workflow.py +228 -0
  22. pynode_flow-0.2.0/install_nodes.bat +58 -0
  23. pynode_flow-0.2.0/install_nodes.sh +50 -0
  24. pynode_flow-0.2.0/plan-pynodeRepoReview.prompt.md +117 -0
  25. pynode_flow-0.2.0/pynode/__init__.py +37 -0
  26. pynode_flow-0.2.0/pynode/__main__.py +9 -0
  27. pynode_flow-0.2.0/pynode/_perf.py +65 -0
  28. pynode_flow-0.2.0/pynode/_version.py +24 -0
  29. pynode_flow-0.2.0/pynode/api/__init__.py +15 -0
  30. pynode_flow-0.2.0/pynode/api/helpers.py +79 -0
  31. pynode_flow-0.2.0/pynode/api/nodes.py +423 -0
  32. pynode_flow-0.2.0/pynode/api/services.py +124 -0
  33. pynode_flow-0.2.0/pynode/api/sse.py +58 -0
  34. pynode_flow-0.2.0/pynode/api/uploads.py +59 -0
  35. pynode_flow-0.2.0/pynode/api/workflows.py +466 -0
  36. pynode_flow-0.2.0/pynode/config.py +93 -0
  37. pynode_flow-0.2.0/pynode/install_nodes.py +143 -0
  38. pynode_flow-0.2.0/pynode/main.py +118 -0
  39. pynode_flow-0.2.0/pynode/node_registry.py +156 -0
  40. pynode_flow-0.2.0/pynode/nodes/AutoSyncNode/auto_sync_node.py +151 -0
  41. pynode_flow-0.2.0/pynode/nodes/BBoxMetricsNode/__init__.py +3 -0
  42. pynode_flow-0.2.0/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +250 -0
  43. pynode_flow-0.2.0/pynode/nodes/BBoxMetricsNode/requirements.txt +1 -0
  44. pynode_flow-0.2.0/pynode/nodes/BatchNode/__init__.py +3 -0
  45. pynode_flow-0.2.0/pynode/nodes/BatchNode/batch_node.py +86 -0
  46. pynode_flow-0.2.0/pynode/nodes/BatchNode/requirements.txt +1 -0
  47. pynode_flow-0.2.0/pynode/nodes/CameraNode/__init__.py +4 -0
  48. pynode_flow-0.2.0/pynode/nodes/CameraNode/camera_node.py +204 -0
  49. pynode_flow-0.2.0/pynode/nodes/CameraNode/requirements.txt +2 -0
  50. pynode_flow-0.2.0/pynode/nodes/ChangeNode/__init__.py +1 -0
  51. pynode_flow-0.2.0/pynode/nodes/ChangeNode/change_node.py +255 -0
  52. pynode_flow-0.2.0/pynode/nodes/ChangeNode/requirements.txt +1 -0
  53. pynode_flow-0.2.0/pynode/nodes/ConfidenceFilterNode/__init__.py +4 -0
  54. pynode_flow-0.2.0/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +208 -0
  55. pynode_flow-0.2.0/pynode/nodes/ConfidenceFilterNode/requirements.txt +1 -0
  56. pynode_flow-0.2.0/pynode/nodes/CounterNode/__init__.py +3 -0
  57. pynode_flow-0.2.0/pynode/nodes/CounterNode/counter_node.py +191 -0
  58. pynode_flow-0.2.0/pynode/nodes/CropNode/__init__.py +4 -0
  59. pynode_flow-0.2.0/pynode/nodes/CropNode/crop_node.py +317 -0
  60. pynode_flow-0.2.0/pynode/nodes/CropNode/requirements.txt +2 -0
  61. pynode_flow-0.2.0/pynode/nodes/DebugNode/__init__.py +4 -0
  62. pynode_flow-0.2.0/pynode/nodes/DebugNode/debug_node.py +169 -0
  63. pynode_flow-0.2.0/pynode/nodes/DebugNode/requirements.txt +1 -0
  64. pynode_flow-0.2.0/pynode/nodes/DelayNode/delay_node.py +218 -0
  65. pynode_flow-0.2.0/pynode/nodes/DelayNode/requirements.txt +1 -0
  66. pynode_flow-0.2.0/pynode/nodes/DrawPredictionsNode/__init__.py +3 -0
  67. pynode_flow-0.2.0/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +365 -0
  68. pynode_flow-0.2.0/pynode/nodes/DrawPredictionsNode/requirements.txt +2 -0
  69. pynode_flow-0.2.0/pynode/nodes/ErrorNode/error_node.py +94 -0
  70. pynode_flow-0.2.0/pynode/nodes/ErrorNode/requirements.txt +1 -0
  71. pynode_flow-0.2.0/pynode/nodes/FilterNode/__init__.py +3 -0
  72. pynode_flow-0.2.0/pynode/nodes/FilterNode/filter_node.py +102 -0
  73. pynode_flow-0.2.0/pynode/nodes/FilterNode/requirements.txt +1 -0
  74. pynode_flow-0.2.0/pynode/nodes/FrameSourceNode/__init__.py +4 -0
  75. pynode_flow-0.2.0/pynode/nodes/FrameSourceNode/frame_source_node.py +319 -0
  76. pynode_flow-0.2.0/pynode/nodes/FrameSourceNode/requirements.txt +1 -0
  77. pynode_flow-0.2.0/pynode/nodes/FunctionNode/__init__.py +3 -0
  78. pynode_flow-0.2.0/pynode/nodes/FunctionNode/function_node.py +168 -0
  79. pynode_flow-0.2.0/pynode/nodes/FunctionNode/requirements.txt +1 -0
  80. pynode_flow-0.2.0/pynode/nodes/GateNode/gate_node.py +60 -0
  81. pynode_flow-0.2.0/pynode/nodes/GateNode/requirements.txt +1 -0
  82. pynode_flow-0.2.0/pynode/nodes/GetiUploadNode/__init__.py +3 -0
  83. pynode_flow-0.2.0/pynode/nodes/GetiUploadNode/geti_upload_node.py +278 -0
  84. pynode_flow-0.2.0/pynode/nodes/GetiUploadNode/requirements.txt +1 -0
  85. pynode_flow-0.2.0/pynode/nodes/ImageFormatNode/__init__.py +4 -0
  86. pynode_flow-0.2.0/pynode/nodes/ImageFormatNode/image_format_node.py +310 -0
  87. pynode_flow-0.2.0/pynode/nodes/ImageFormatNode/requirements.txt +2 -0
  88. pynode_flow-0.2.0/pynode/nodes/ImageUploadNode/image_upload_node.py +96 -0
  89. pynode_flow-0.2.0/pynode/nodes/ImageViewerNode/__init__.py +4 -0
  90. pynode_flow-0.2.0/pynode/nodes/ImageViewerNode/image_viewer_node.py +162 -0
  91. pynode_flow-0.2.0/pynode/nodes/ImageViewerNode/requirements.txt +2 -0
  92. pynode_flow-0.2.0/pynode/nodes/ImageWriterNode/__init__.py +3 -0
  93. pynode_flow-0.2.0/pynode/nodes/ImageWriterNode/imagewriter_node.py +352 -0
  94. pynode_flow-0.2.0/pynode/nodes/ImageWriterNode/requirements.txt +1 -0
  95. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +15 -0
  96. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +149 -0
  97. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +155 -0
  98. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +272 -0
  99. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/engines/geti_engine.py +309 -0
  100. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +346 -0
  101. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +86 -0
  102. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +863 -0
  103. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +533 -0
  104. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/readme.md +107 -0
  105. pynode_flow-0.2.0/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +121 -0
  106. pynode_flow-0.2.0/pynode/nodes/InferenceNode/inference_node.py +451 -0
  107. pynode_flow-0.2.0/pynode/nodes/InferenceNode/requirements.txt +13 -0
  108. pynode_flow-0.2.0/pynode/nodes/InjectNode/inject_node.py +245 -0
  109. pynode_flow-0.2.0/pynode/nodes/InjectNode/requirements.txt +1 -0
  110. pynode_flow-0.2.0/pynode/nodes/JoinNode/__init__.py +3 -0
  111. pynode_flow-0.2.0/pynode/nodes/JoinNode/join_node.py +156 -0
  112. pynode_flow-0.2.0/pynode/nodes/JoinNode/requirements.txt +1 -0
  113. pynode_flow-0.2.0/pynode/nodes/LabelFilterNode/__init__.py +3 -0
  114. pynode_flow-0.2.0/pynode/nodes/LabelFilterNode/label_filter_node.py +188 -0
  115. pynode_flow-0.2.0/pynode/nodes/LabelFilterNode/requirements.txt +1 -0
  116. pynode_flow-0.2.0/pynode/nodes/MQTTNode/__init__.py +8 -0
  117. pynode_flow-0.2.0/pynode/nodes/MQTTNode/mqtt_in_node.py +148 -0
  118. pynode_flow-0.2.0/pynode/nodes/MQTTNode/mqtt_out_node.py +157 -0
  119. pynode_flow-0.2.0/pynode/nodes/MQTTNode/mqtt_service.py +367 -0
  120. pynode_flow-0.2.0/pynode/nodes/MQTTNode/requirements.txt +1 -0
  121. pynode_flow-0.2.0/pynode/nodes/MessageWriterNode/__init__.py +7 -0
  122. pynode_flow-0.2.0/pynode/nodes/MessageWriterNode/messagereader_node.py +568 -0
  123. pynode_flow-0.2.0/pynode/nodes/MessageWriterNode/messagewriter_node.py +505 -0
  124. pynode_flow-0.2.0/pynode/nodes/OmronCameraNode/__init__.py +3 -0
  125. pynode_flow-0.2.0/pynode/nodes/OmronCameraNode/omron_camera_node.py +652 -0
  126. pynode_flow-0.2.0/pynode/nodes/OmronCameraNode/requirements.txt +4 -0
  127. pynode_flow-0.2.0/pynode/nodes/OpenCV/__init__.py +1 -0
  128. pynode_flow-0.2.0/pynode/nodes/OpenCV/arithmetic_node.py +376 -0
  129. pynode_flow-0.2.0/pynode/nodes/OpenCV/bitwise_node.py +127 -0
  130. pynode_flow-0.2.0/pynode/nodes/OpenCV/blend_node.py +127 -0
  131. pynode_flow-0.2.0/pynode/nodes/OpenCV/blob_detector_node.py +233 -0
  132. pynode_flow-0.2.0/pynode/nodes/OpenCV/blur_node.py +141 -0
  133. pynode_flow-0.2.0/pynode/nodes/OpenCV/color_space_node.py +139 -0
  134. pynode_flow-0.2.0/pynode/nodes/OpenCV/colormap_node.py +262 -0
  135. pynode_flow-0.2.0/pynode/nodes/OpenCV/contours_node.py +258 -0
  136. pynode_flow-0.2.0/pynode/nodes/OpenCV/denormalize_coords_node.py +365 -0
  137. pynode_flow-0.2.0/pynode/nodes/OpenCV/draw_node.py +312 -0
  138. pynode_flow-0.2.0/pynode/nodes/OpenCV/edge_detector_node.py +177 -0
  139. pynode_flow-0.2.0/pynode/nodes/OpenCV/equirectangular_node.py +447 -0
  140. pynode_flow-0.2.0/pynode/nodes/OpenCV/fft_node.py +102 -0
  141. pynode_flow-0.2.0/pynode/nodes/OpenCV/focus_estimation_node.py +189 -0
  142. pynode_flow-0.2.0/pynode/nodes/OpenCV/frequency_filter_node.py +241 -0
  143. pynode_flow-0.2.0/pynode/nodes/OpenCV/histogram_node.py +160 -0
  144. pynode_flow-0.2.0/pynode/nodes/OpenCV/hough_circles_node.py +224 -0
  145. pynode_flow-0.2.0/pynode/nodes/OpenCV/hough_lines_node.py +243 -0
  146. pynode_flow-0.2.0/pynode/nodes/OpenCV/in_range_node.py +186 -0
  147. pynode_flow-0.2.0/pynode/nodes/OpenCV/morphology_node.py +136 -0
  148. pynode_flow-0.2.0/pynode/nodes/OpenCV/normalize_coords_node.py +344 -0
  149. pynode_flow-0.2.0/pynode/nodes/OpenCV/paste_node.py +244 -0
  150. pynode_flow-0.2.0/pynode/nodes/OpenCV/perspective_node.py +174 -0
  151. pynode_flow-0.2.0/pynode/nodes/OpenCV/realsense_depth_node.py +253 -0
  152. pynode_flow-0.2.0/pynode/nodes/OpenCV/requirements.txt +2 -0
  153. pynode_flow-0.2.0/pynode/nodes/OpenCV/resize_node.py +175 -0
  154. pynode_flow-0.2.0/pynode/nodes/OpenCV/rotate_node.py +159 -0
  155. pynode_flow-0.2.0/pynode/nodes/OpenCV/template_match_node.py +227 -0
  156. pynode_flow-0.2.0/pynode/nodes/OpenCV/threshold_node.py +167 -0
  157. pynode_flow-0.2.0/pynode/nodes/PointInShapeNode/__init__.py +3 -0
  158. pynode_flow-0.2.0/pynode/nodes/PointInShapeNode/point_in_shape_node.py +313 -0
  159. pynode_flow-0.2.0/pynode/nodes/PointInShapeNode/requirements.txt +2 -0
  160. pynode_flow-0.2.0/pynode/nodes/PolygonMetricsNode/__init__.py +3 -0
  161. pynode_flow-0.2.0/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +331 -0
  162. pynode_flow-0.2.0/pynode/nodes/PolygonMetricsNode/requirements.txt +2 -0
  163. pynode_flow-0.2.0/pynode/nodes/QueueLengthProbeNode/__init__.py +3 -0
  164. pynode_flow-0.2.0/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +116 -0
  165. pynode_flow-0.2.0/pynode/nodes/Qwen3VLMNode/__init__.py +3 -0
  166. pynode_flow-0.2.0/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +519 -0
  167. pynode_flow-0.2.0/pynode/nodes/Qwen3VLMNode/requirements.txt +5 -0
  168. pynode_flow-0.2.0/pynode/nodes/RESTEndpointNode/__init__.py +3 -0
  169. pynode_flow-0.2.0/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +491 -0
  170. pynode_flow-0.2.0/pynode/nodes/RangeNode/__init__.py +3 -0
  171. pynode_flow-0.2.0/pynode/nodes/RangeNode/range_node.py +115 -0
  172. pynode_flow-0.2.0/pynode/nodes/RangeNode/requirements.txt +1 -0
  173. pynode_flow-0.2.0/pynode/nodes/RateProbeNode/__init__.py +3 -0
  174. pynode_flow-0.2.0/pynode/nodes/RateProbeNode/rate_probe_node.py +183 -0
  175. pynode_flow-0.2.0/pynode/nodes/RateProbeNode/requirements.txt +1 -0
  176. pynode_flow-0.2.0/pynode/nodes/RoboflowUploadNode/__init__.py +3 -0
  177. pynode_flow-0.2.0/pynode/nodes/RoboflowUploadNode/requirements.txt +1 -0
  178. pynode_flow-0.2.0/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +229 -0
  179. pynode_flow-0.2.0/pynode/nodes/SliceNode/README.md +149 -0
  180. pynode_flow-0.2.0/pynode/nodes/SliceNode/__init__.py +5 -0
  181. pynode_flow-0.2.0/pynode/nodes/SliceNode/merge_predictions_node.py +444 -0
  182. pynode_flow-0.2.0/pynode/nodes/SliceNode/requirements.txt +3 -0
  183. pynode_flow-0.2.0/pynode/nodes/SliceNode/slice_collector_node.py +370 -0
  184. pynode_flow-0.2.0/pynode/nodes/SliceNode/slice_image_node.py +380 -0
  185. pynode_flow-0.2.0/pynode/nodes/SplitNode/__init__.py +3 -0
  186. pynode_flow-0.2.0/pynode/nodes/SplitNode/requirements.txt +1 -0
  187. pynode_flow-0.2.0/pynode/nodes/SplitNode/split_node.py +155 -0
  188. pynode_flow-0.2.0/pynode/nodes/SwitchNode/requirements.txt +1 -0
  189. pynode_flow-0.2.0/pynode/nodes/SwitchNode/switch_node.py +259 -0
  190. pynode_flow-0.2.0/pynode/nodes/SyncNode/__init__.py +3 -0
  191. pynode_flow-0.2.0/pynode/nodes/SyncNode/sync_node.py +287 -0
  192. pynode_flow-0.2.0/pynode/nodes/TemplateNode/requirements.txt +1 -0
  193. pynode_flow-0.2.0/pynode/nodes/TemplateNode/template_node.py +86 -0
  194. pynode_flow-0.2.0/pynode/nodes/TrackerNode/__init__.py +2 -0
  195. pynode_flow-0.2.0/pynode/nodes/TrackerNode/supervision_tracker_node.py +248 -0
  196. pynode_flow-0.2.0/pynode/nodes/TrackerNode/tracker_node.py +745 -0
  197. pynode_flow-0.2.0/pynode/nodes/UltralyticsNode/__init__.py +4 -0
  198. pynode_flow-0.2.0/pynode/nodes/UltralyticsNode/requirements.txt +2 -0
  199. pynode_flow-0.2.0/pynode/nodes/UltralyticsNode/ultralytics_node.py +364 -0
  200. pynode_flow-0.2.0/pynode/nodes/UnknownNode/__init__.py +93 -0
  201. pynode_flow-0.2.0/pynode/nodes/VLLMNode/__init__.py +3 -0
  202. pynode_flow-0.2.0/pynode/nodes/VLLMNode/vllm_node.py +345 -0
  203. pynode_flow-0.2.0/pynode/nodes/VideoReaderNode/__init__.py +3 -0
  204. pynode_flow-0.2.0/pynode/nodes/VideoReaderNode/video_reader_node.py +425 -0
  205. pynode_flow-0.2.0/pynode/nodes/VideoWriterNode/__init__.py +3 -0
  206. pynode_flow-0.2.0/pynode/nodes/VideoWriterNode/video_writer_node.py +441 -0
  207. pynode_flow-0.2.0/pynode/nodes/WebhookNode/__init__.py +3 -0
  208. pynode_flow-0.2.0/pynode/nodes/WebhookNode/webhook_node.py +591 -0
  209. pynode_flow-0.2.0/pynode/nodes/__init__.py +109 -0
  210. pynode_flow-0.2.0/pynode/nodes/base_node.py +562 -0
  211. pynode_flow-0.2.0/pynode/nodes/image_utils.py +291 -0
  212. pynode_flow-0.2.0/pynode/nodes/info.py +109 -0
  213. pynode_flow-0.2.0/pynode/nodes/mDNSNode/__init__.py +13 -0
  214. pynode_flow-0.2.0/pynode/nodes/mDNSNode/mdns_broadcast_node.py +214 -0
  215. pynode_flow-0.2.0/pynode/nodes/mDNSNode/mdns_discovery_node.py +362 -0
  216. pynode_flow-0.2.0/pynode/nodes/mDNSNode/mdns_manager.py +399 -0
  217. pynode_flow-0.2.0/pynode/nodes/mDNSNode/requirements.txt +1 -0
  218. pynode_flow-0.2.0/pynode/nodes/messages.py +81 -0
  219. pynode_flow-0.2.0/pynode/server.py +187 -0
  220. pynode_flow-0.2.0/pynode/static/images/favicon4.png +0 -0
  221. pynode_flow-0.2.0/pynode/static/index.html +200 -0
  222. pynode_flow-0.2.0/pynode/static/js/README.md +139 -0
  223. pynode_flow-0.2.0/pynode/static/js/auth.js +89 -0
  224. pynode_flow-0.2.0/pynode/static/js/clipboard.js +152 -0
  225. pynode_flow-0.2.0/pynode/static/js/config.js +14 -0
  226. pynode_flow-0.2.0/pynode/static/js/connections.js +844 -0
  227. pynode_flow-0.2.0/pynode/static/js/debug.js +524 -0
  228. pynode_flow-0.2.0/pynode/static/js/events.js +775 -0
  229. pynode_flow-0.2.0/pynode/static/js/history.js +199 -0
  230. pynode_flow-0.2.0/pynode/static/js/main.js +55 -0
  231. pynode_flow-0.2.0/pynode/static/js/minimap.js +242 -0
  232. pynode_flow-0.2.0/pynode/static/js/nodes.js +995 -0
  233. pynode_flow-0.2.0/pynode/static/js/palette.js +255 -0
  234. pynode_flow-0.2.0/pynode/static/js/properties.js +1270 -0
  235. pynode_flow-0.2.0/pynode/static/js/selection.js +241 -0
  236. pynode_flow-0.2.0/pynode/static/js/state.js +211 -0
  237. pynode_flow-0.2.0/pynode/static/js/ui-utils.js +28 -0
  238. pynode_flow-0.2.0/pynode/static/js/viewport.js +204 -0
  239. pynode_flow-0.2.0/pynode/static/js/workflow.js +469 -0
  240. pynode_flow-0.2.0/pynode/static/js/workflows.js +344 -0
  241. pynode_flow-0.2.0/pynode/static/style.css +2751 -0
  242. pynode_flow-0.2.0/pynode/workflow_engine.py +483 -0
  243. pynode_flow-0.2.0/pynode/workflow_manager.py +440 -0
  244. pynode_flow-0.2.0/pynode_flow.egg-info/PKG-INFO +520 -0
  245. pynode_flow-0.2.0/pynode_flow.egg-info/SOURCES.txt +275 -0
  246. pynode_flow-0.2.0/pynode_flow.egg-info/dependency_links.txt +1 -0
  247. pynode_flow-0.2.0/pynode_flow.egg-info/entry_points.txt +3 -0
  248. pynode_flow-0.2.0/pynode_flow.egg-info/requires.txt +52 -0
  249. pynode_flow-0.2.0/pynode_flow.egg-info/scm_file_list.json +270 -0
  250. pynode_flow-0.2.0/pynode_flow.egg-info/scm_version.json +8 -0
  251. pynode_flow-0.2.0/pynode_flow.egg-info/top_level.txt +1 -0
  252. pynode_flow-0.2.0/pyproject.toml +180 -0
  253. pynode_flow-0.2.0/requirements.txt +13 -0
  254. pynode_flow-0.2.0/setup.bat +165 -0
  255. pynode_flow-0.2.0/setup.cfg +4 -0
  256. pynode_flow-0.2.0/setup.py +11 -0
  257. pynode_flow-0.2.0/setup.sh +83 -0
  258. pynode_flow-0.2.0/tests/__init__.py +0 -0
  259. pynode_flow-0.2.0/tests/conftest.py +109 -0
  260. pynode_flow-0.2.0/tests/test_api_auth.py +199 -0
  261. pynode_flow-0.2.0/tests/test_api_errors.py +93 -0
  262. pynode_flow-0.2.0/tests/test_api_nodes.py +261 -0
  263. pynode_flow-0.2.0/tests/test_api_security.py +153 -0
  264. pynode_flow-0.2.0/tests/test_api_workflows.py +565 -0
  265. pynode_flow-0.2.0/tests/test_base_node_messaging.py +456 -0
  266. pynode_flow-0.2.0/tests/test_concurrency.py +104 -0
  267. pynode_flow-0.2.0/tests/test_config.py +137 -0
  268. pynode_flow-0.2.0/tests/test_counter_sse.py +60 -0
  269. pynode_flow-0.2.0/tests/test_device_detection.py +295 -0
  270. pynode_flow-0.2.0/tests/test_message_reader_pickle.py +77 -0
  271. pynode_flow-0.2.0/tests/test_node_registration.py +36 -0
  272. pynode_flow-0.2.0/tests/test_node_types_api.py +125 -0
  273. pynode_flow-0.2.0/tests/test_persistence.py +125 -0
  274. pynode_flow-0.2.0/tests/test_serialization.py +118 -0
  275. pynode_flow-0.2.0/tests/test_video_reader_node.py +304 -0
  276. pynode_flow-0.2.0/tests/test_workflow_engine.py +98 -0
  277. pynode_flow-0.2.0/tests/test_workflow_roundtrip.py +230 -0
@@ -0,0 +1,23 @@
1
+ # Docker ignore file
2
+ .venv/
3
+ appenv/
4
+ __pycache__/
5
+ *.pyc
6
+ *.pyo
7
+ *.pyd
8
+ .Python
9
+ *.so
10
+ *.egg
11
+ *.egg-info
12
+ dist/
13
+ build/
14
+ .git/
15
+ .gitignore
16
+ .vscode/
17
+ .idea/
18
+ *.swp
19
+ *.swo
20
+ *~
21
+ .DS_Store
22
+ logs/
23
+ *.log
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.11"
16
+ cache: pip
17
+ cache-dependency-path: pyproject.toml
18
+
19
+ - name: Install system libraries for OpenCV
20
+ run: sudo apt-get update && sudo apt-get install -y libgl1 libglib2.0-0
21
+
22
+ - name: Install package
23
+ run: pip install -e .[dev]
24
+
25
+ - name: Run tests
26
+ run: pytest -q
27
+
28
+ typecheck:
29
+ runs-on: ubuntu-latest
30
+ continue-on-error: true
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+
34
+ - uses: actions/setup-python@v5
35
+ with:
36
+ python-version: "3.11"
37
+ cache: pip
38
+ cache-dependency-path: pyproject.toml
39
+
40
+ - name: Install package and pyright
41
+ run: pip install -e .[dev] pyright
42
+
43
+ - name: Run pyright
44
+ run: pyright
@@ -0,0 +1,60 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ # Manual runs must be dispatched from a tag ref (e.g. v0.3.0), otherwise
8
+ # setuptools_scm produces a dev version that PyPI rejects.
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ # setuptools_scm derives the version from git tags, so it needs the
19
+ # full history and the tags (a shallow checkout would break it).
20
+ fetch-depth: 0
21
+
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.11"
25
+
26
+ - name: Build sdist and wheel
27
+ run: |
28
+ python -m pip install --upgrade build
29
+ python -m build
30
+
31
+ - name: Check metadata
32
+ run: |
33
+ python -m pip install --upgrade twine
34
+ twine check dist/*
35
+
36
+ - uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ name: Publish to PyPI
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ # Trusted Publishing (OIDC) -- no API token stored in the repo. Configure a
46
+ # trusted publisher for the `pynode-flow` project on PyPI pointing at this
47
+ # workflow, and create a GitHub environment named `pypi`.
48
+ environment:
49
+ name: pypi
50
+ url: https://pypi.org/p/pynode-flow
51
+ permissions:
52
+ id-token: write
53
+ steps:
54
+ - uses: actions/download-artifact@v4
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+
59
+ - name: Publish to PyPI
60
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,193 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ PIPFILE.lock
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ cover/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ .pybuilder/
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ Pipfile.lock
88
+
89
+ # poetry
90
+ poetry.lock
91
+
92
+ # pdm
93
+ .pdm.toml
94
+
95
+ # PEP 582
96
+ __pypackages__/
97
+
98
+ # Celery stuff
99
+ celerybeat-schedule
100
+ celerybeat.pid
101
+
102
+ # SageMath parsed files
103
+ *.sage.py
104
+
105
+ # Environments
106
+ .env
107
+ .venv
108
+ appenv/
109
+ env/
110
+ venv/
111
+ ENV/
112
+ env.bak/
113
+ venv.bak/
114
+
115
+ # Spyder project settings
116
+ .spyderproject
117
+ .spyproject
118
+
119
+ # Rope project settings
120
+ .ropeproject
121
+
122
+ # mkdocs documentation
123
+ /site
124
+
125
+ # mypy
126
+ .mypy_cache/
127
+ .dmypy.json
128
+ dmypy.json
129
+
130
+ # Pyre type checker
131
+ .pyre/
132
+
133
+ # pytype static type analyzer
134
+ .pytype/
135
+
136
+ # Cython debug symbols
137
+ cython_debug/
138
+
139
+ # IDE
140
+ .vscode/
141
+ .idea/
142
+ *.swp
143
+ *.swo
144
+ *~
145
+
146
+ # OS
147
+ .DS_Store
148
+ Thumbs.db
149
+
150
+ # Project specific - ignore all JSON files (workflows and backups)
151
+ *.json
152
+
153
+ # PyTorch model files
154
+ *.pt
155
+ models/
156
+ todo
157
+ *.bin
158
+ *.xml
159
+ *.yaml
160
+ # ...but always keep GitHub Actions workflows (ci.yml / publish.yaml live here)
161
+ !.github/
162
+ !.github/**
163
+ todo
164
+
165
+ # Media and data files
166
+ *.mp4
167
+ *.avi
168
+ *.mov
169
+ *.mkv
170
+ *.log
171
+ *.csv
172
+ *.db
173
+ *.sqlite
174
+ *.sqlite3
175
+ *.h5
176
+ *.ckpt
177
+ *.tflite
178
+ *.pb
179
+ *.npy
180
+ *.npz
181
+ *.weights
182
+
183
+ # Version file generated by setuptools_scm at build time
184
+ pynode/_version.py
185
+
186
+ # folders
187
+ data/
188
+ models/
189
+ logs/
190
+ checkpoints/
191
+ output/
192
+ output_low/
193
+ output_not/
@@ -0,0 +1,113 @@
1
+ # Docker Setup for PyNode
2
+
3
+ ## Prerequisites
4
+
5
+ - Docker Engine with GPU support (NVIDIA Container Toolkit)
6
+ - Docker Compose v1.28+ (for GPU support)
7
+ - NVIDIA GPU with CUDA support
8
+
9
+ ## Quick Start
10
+
11
+ ### Build and Run
12
+
13
+ ```bash
14
+ docker-compose up --build
15
+ ```
16
+
17
+ ### Run in Background
18
+
19
+ ```bash
20
+ docker-compose up -d
21
+ ```
22
+
23
+ ### View Logs
24
+
25
+ ```bash
26
+ docker-compose logs -f
27
+ ```
28
+
29
+ ### Stop the Application
30
+
31
+ ```bash
32
+ docker-compose down
33
+ ```
34
+
35
+ ## Configuration
36
+
37
+ ### Ports
38
+
39
+ The application runs on port `5000` by default. To change the port, edit `docker-compose.yml`:
40
+
41
+ ```yaml
42
+ ports:
43
+ - "8080:5000" # Change 8080 to your desired port
44
+ ```
45
+
46
+ ### GPU Support
47
+
48
+ The Docker Compose file is configured to use all available NVIDIA GPUs. To limit GPU access:
49
+
50
+ ```yaml
51
+ deploy:
52
+ resources:
53
+ reservations:
54
+ devices:
55
+ - driver: nvidia
56
+ device_ids: ['0'] # Use only GPU 0
57
+ capabilities: [gpu]
58
+ ```
59
+
60
+ ### Persistent Storage
61
+
62
+ The following directories are mounted for persistence:
63
+ - `./workflows` - Saved workflows
64
+ - `./logs` - Application logs
65
+
66
+ ## Building
67
+
68
+ ### Custom CUDA Version
69
+
70
+ To use a different CUDA version, edit the `Dockerfile`:
71
+
72
+ ```dockerfile
73
+ FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04 # Change CUDA version
74
+ ```
75
+
76
+ ### Without GPU Support
77
+
78
+ For CPU-only deployment, use the CPU Dockerfile:
79
+
80
+ ```bash
81
+ docker build -f Dockerfile.cpu -t pynode-cpu .
82
+ docker run -p 5000:5000 pynode-cpu
83
+ ```
84
+
85
+ ## Accessing the Application
86
+
87
+ Once running, access the web interface at:
88
+ - http://localhost:5000
89
+
90
+ ## Troubleshooting
91
+
92
+ ### GPU Not Detected
93
+
94
+ Verify NVIDIA Container Toolkit is installed:
95
+ ```bash
96
+ docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu22.04 nvidia-smi
97
+ ```
98
+
99
+ ### Permission Issues
100
+
101
+ Ensure the mounted directories have proper permissions:
102
+ ```bash
103
+ mkdir -p workflows logs
104
+ chmod 777 workflows logs
105
+ ```
106
+
107
+ ### Rebuild from Scratch
108
+
109
+ ```bash
110
+ docker-compose down
111
+ docker-compose build --no-cache
112
+ docker-compose up
113
+ ```
@@ -0,0 +1,66 @@
1
+ FROM docker.io/nvidia/cuda:12.6.0-devel-ubuntu22.04
2
+
3
+ # Set environment variables
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+ ENV PYTHONUNBUFFERED=1
6
+ ENV CUDA_VERSION=12.6
7
+
8
+ # Install system dependencies
9
+ RUN apt-get update && apt-get install -y \
10
+ python3.10 \
11
+ python3-pip \
12
+ python3-venv \
13
+ git \
14
+ wget \
15
+ gfortran \
16
+ libgl1-mesa-glx \
17
+ libgtk-3-0 \
18
+ libglib2.0-0 \
19
+ libsm6 \
20
+ libxext6 \
21
+ libxrender-dev \
22
+ libgomp1 \
23
+ libavcodec-dev \
24
+ libavformat-dev \
25
+ libswscale-dev \
26
+ libv4l-dev \
27
+ libxvidcore-dev \
28
+ libx264-dev \
29
+ libjpeg-dev \
30
+ libpng-dev \
31
+ libtiff-dev \
32
+ libopenblas-dev \
33
+ liblapack-dev \
34
+ portaudio19-dev \
35
+ libasound2-dev \
36
+ libusb-1.0-0 \
37
+ libusb-1.0-0-dev \
38
+ libudev-dev \
39
+ && rm -rf /var/lib/apt/lists/*
40
+
41
+ # Set working directory
42
+ WORKDIR /app
43
+
44
+ # Copy application files
45
+ COPY . /app/
46
+
47
+ # Make scripts executable
48
+ RUN chmod +x /app/setup.sh /app/install_nodes.sh
49
+
50
+ # Run setup script
51
+ RUN bash -c "source /app/setup.sh < /dev/null"
52
+
53
+ # Create a non-root user and give it ownership of the app directory
54
+ RUN useradd --create-home --uid 1000 appuser \
55
+ && chown -R appuser:appuser /app
56
+ USER appuser
57
+
58
+ # Expose port
59
+ EXPOSE 5000
60
+
61
+ # Health check (uses the bundled Python interpreter; no extra tools required)
62
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
63
+ CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
64
+
65
+ # Run the application in production mode
66
+ CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
@@ -0,0 +1,49 @@
1
+ FROM python:3.10-slim
2
+
3
+ # Set environment variables
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+ ENV PYTHONUNBUFFERED=1
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ libgl1-mesa-glx \
10
+ libglib2.0-0 \
11
+ libsm6 \
12
+ libxext6 \
13
+ libxrender-dev \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Set working directory
17
+ WORKDIR /app
18
+
19
+ # Copy application files
20
+ COPY . /app/
21
+
22
+ # Create virtual environment
23
+ RUN python3 -m venv appenv
24
+
25
+ # Activate venv and install dependencies (declared in pyproject.toml;
26
+ # CPU-only torch is installed first so the vision extra reuses it)
27
+ RUN . appenv/bin/activate && \
28
+ pip install --upgrade pip && \
29
+ pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu && \
30
+ pip install -e ".[vision,mqtt]"
31
+
32
+ # Install per-node dependencies (best-effort: a node whose vendor SDK is not
33
+ # on PyPI, e.g. OmronCameraNode's stapipy, is skipped without failing the build)
34
+ RUN . appenv/bin/activate && pynode-install-nodes || true
35
+
36
+ # Create a non-root user and give it ownership of the app directory
37
+ RUN useradd --create-home --uid 1000 appuser \
38
+ && chown -R appuser:appuser /app
39
+ USER appuser
40
+
41
+ # Expose port
42
+ EXPOSE 5000
43
+
44
+ # Health check (uses the bundled Python interpreter; no extra tools required)
45
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
46
+ CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
47
+
48
+ # Run the application in production mode
49
+ CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
@@ -0,0 +1,145 @@
1
+ # Installation Guide
2
+
3
+ PyNode is published on PyPI as **`pynode-flow`** (the import package is still
4
+ `pynode`).
5
+
6
+ ## Install from PyPI
7
+
8
+ ```bash
9
+ # Core install (Flask API + built-in nodes that need no extra packages)
10
+ pip install pynode-flow
11
+
12
+ # Everything PyPI-installable (all optional nodes)
13
+ pip install "pynode-flow[full]"
14
+ ```
15
+
16
+ Nodes whose optional dependencies are missing are simply skipped at startup, so
17
+ a core-only install still runs fine.
18
+
19
+ ### Optional extras
20
+
21
+ Install only the node groups you need:
22
+
23
+ | Extra | Installs | Nodes it enables |
24
+ |-------|----------|------------------|
25
+ | `vision` | ultralytics, torch, torchvision, supervision | UltralyticsNode, TrackerNode, DrawPredictionsNode, … |
26
+ | `mqtt` | paho-mqtt | MQTTNode |
27
+ | `camera` | framesource[full] | FrameSourceNode |
28
+ | `inference` | onnxruntime, geti-sdk (+ `vision`) | InferenceNode |
29
+ | `vlm` | transformers, qwen-vl-utils, Pillow (+ `vision`) | Qwen3VLMNode |
30
+ | `upload` | roboflow, geti-sdk | RoboflowUploadNode, GetiUploadNode |
31
+ | `discovery` | zeroconf | mDNSNode |
32
+ | `full` | all of the above | every PyPI-installable node |
33
+ | `dev` | pytest, type stubs | (development only) |
34
+
35
+ ```bash
36
+ pip install "pynode-flow[vision,mqtt]" # pick specific groups
37
+ pip install "pynode-flow[full]" # or grab everything
38
+ ```
39
+
40
+ > **Note:** `pip install pynode-flow` (or any extra) installs **only** what is
41
+ > declared in `pyproject.toml`. It does **not** run the per-node
42
+ > `requirements.txt` files — those are covered by the extras above, or by the
43
+ > `pynode-install-nodes` command below.
44
+
45
+ ## Development Installation
46
+
47
+ Install PyNode from a source checkout in editable mode (recommended for
48
+ development):
49
+
50
+ ```bash
51
+ # Clone or navigate to the project directory
52
+ cd pynode
53
+
54
+ # Install in editable mode (core dependencies only)
55
+ pip install -e .
56
+
57
+ # Optional extras (same names as above):
58
+ pip install -e ".[vision]" # ML vision nodes
59
+ pip install -e ".[mqtt]" # MQTT node
60
+ pip install -e ".[full]" # everything PyPI-installable
61
+ pip install -e ".[dev]" # development tools (pytest, type stubs)
62
+ ```
63
+
64
+ This installs PyNode as a package while keeping your source files editable.
65
+
66
+ > **Versioning:** the package version is derived from git tags by
67
+ > `setuptools_scm`. A clean checkout at tag `v0.2.0` builds as `0.2.0`; commits
68
+ > after a tag build as a dev version. To cut a release, tag the commit
69
+ > (`git tag v0.2.0 && git push --tags`) — see [Publishing](#publishing-to-pypi).
70
+
71
+ ## Running PyNode
72
+
73
+ After installation, you can run PyNode in multiple ways:
74
+
75
+ ### Option 1: Using the `pynode` command
76
+ ```bash
77
+ pynode
78
+ ```
79
+
80
+ ### Option 2: Using Python module syntax
81
+ ```bash
82
+ python -m pynode
83
+ ```
84
+
85
+ ### Option 3: With custom options
86
+ ```bash
87
+ pynode --host 0.0.0.0 --port 5000 --production
88
+ ```
89
+
90
+ ## Command Line Options
91
+
92
+ - `--host`: Host address to bind to (default: 0.0.0.0)
93
+ - `--port`: Port to bind to (default: 5000)
94
+ - `--production`: Run in production mode using Waitress server
95
+
96
+ ## Installing Node Dependencies
97
+
98
+ A few nodes rely on packages that cannot live in `pyproject.toml` — for example
99
+ the Omron/Sentech `stapipy` SDK (OmronCameraNode), which is a vendor download
100
+ rather than a PyPI package. Each such node ships its own `requirements.txt`.
101
+
102
+ **If you installed from PyPI**, use the bundled command to walk the installed
103
+ node folders and install their requirements:
104
+
105
+ ```bash
106
+ pynode-install-nodes # install every node's requirements.txt
107
+ pynode-install-nodes --list # list nodes that have a requirements.txt
108
+ pynode-install-nodes --node MQTTNode --node mDNSNode # only specific nodes
109
+ pynode-install-nodes --dry-run # preview without installing
110
+ ```
111
+
112
+ **If you're working from a source checkout**, you can instead run the helper
113
+ scripts, which iterate the node folders in the repo:
114
+
115
+ ```bash
116
+ # Windows
117
+ install_nodes.bat
118
+
119
+ # Linux/Mac
120
+ ./install_nodes.sh
121
+ ```
122
+
123
+ ## Publishing to PyPI
124
+
125
+ Releases are published by `.github/workflows/publish.yaml` via PyPI
126
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no API
127
+ token is stored in the repo). To release:
128
+
129
+ 1. Configure a trusted publisher for the `pynode-flow` project on PyPI, pointing
130
+ at this repository / `publish.yaml`, and create a GitHub environment named
131
+ `pypi` (one-time setup).
132
+ 2. Tag the release commit and push the tag:
133
+ ```bash
134
+ git tag v0.2.0
135
+ git push origin v0.2.0
136
+ ```
137
+ The tag drives the version via `setuptools_scm`. (A manual
138
+ `workflow_dispatch` run must also be dispatched from a tag ref, otherwise the
139
+ build produces a dev version that PyPI rejects.)
140
+
141
+ ## Uninstalling
142
+
143
+ ```bash
144
+ pip uninstall pynode-flow
145
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oliver Hamilton
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.