pynode-flow 0.2.2__tar.gz → 0.2.4__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 (330) hide show
  1. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/.dockerignore +0 -1
  2. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/.gitignore +2 -0
  3. pynode_flow-0.2.4/DOCKER.md +196 -0
  4. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/Dockerfile +22 -8
  5. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/Dockerfile.cpu +22 -9
  6. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/PKG-INFO +20 -1
  7. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/README.md +19 -0
  8. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/docker-compose.yml +6 -1
  9. pynode_flow-0.2.4/docker-entrypoint.sh +12 -0
  10. pynode_flow-0.2.4/plan-pynodeReview-2026-07-18.prompt.md +135 -0
  11. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/__init__.py +5 -1
  12. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/_version.py +3 -3
  13. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/api/nodes.py +5 -1
  14. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/config.py +42 -0
  15. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/main.py +9 -0
  16. pynode_flow-0.2.4/pynode/nodes/ControlSliderNode/control_slider_node.py +208 -0
  17. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CropNode/crop_node.py +141 -12
  18. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DelayNode/delay_node.py +2 -2
  19. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/frame_source_node.py +1 -1
  20. pynode_flow-0.2.4/pynode/nodes/FunctionNode/function_node.py +309 -0
  21. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/image_format_node.py +2 -2
  22. pynode_flow-0.2.4/pynode/nodes/ImageUploadNode/image_upload_node.py +246 -0
  23. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/image_viewer_node.py +16 -9
  24. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +1 -1
  25. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_bus.py +7 -1
  26. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_in_node.py +4 -3
  27. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_out_node.py +1 -1
  28. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_service.py +1 -1
  29. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/omron_camera_node.py +13 -3
  30. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/arithmetic_node.py +5 -5
  31. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blob_detector_node.py +1 -1
  32. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/colormap_node.py +1 -1
  33. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/fft_node.py +1 -1
  34. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/focus_estimation_node.py +1 -1
  35. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/histogram_node.py +2 -2
  36. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/hough_circles_node.py +2 -2
  37. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/in_range_node.py +1 -1
  38. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/perspective_node.py +6 -6
  39. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/realsense_depth_node.py +2 -2
  40. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +1 -1
  41. pynode_flow-0.2.4/pynode/nodes/RangeNode/range_node.py +220 -0
  42. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +1 -1
  43. pynode_flow-0.2.4/pynode/nodes/SocketNode/__init__.py +28 -0
  44. pynode_flow-0.2.4/pynode/nodes/SocketNode/interop/README.md +239 -0
  45. pynode_flow-0.2.4/pynode/nodes/SocketNode/interop/nodered-example-flow.json +318 -0
  46. pynode_flow-0.2.4/pynode/nodes/SocketNode/ndjson_protocol.py +176 -0
  47. pynode_flow-0.2.4/pynode/nodes/SocketNode/nodered_snippets.py +147 -0
  48. pynode_flow-0.2.4/pynode/nodes/SocketNode/requirements.txt +4 -0
  49. pynode_flow-0.2.4/pynode/nodes/SocketNode/tcp_in_node.py +269 -0
  50. pynode_flow-0.2.4/pynode/nodes/SocketNode/tcp_out_node.py +270 -0
  51. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_in_node.py +257 -0
  52. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_out_node.py +253 -0
  53. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_probe.py +105 -0
  54. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_protocol.py +479 -0
  55. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/split_node.py +2 -2
  56. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SwitchNode/switch_node.py +8 -2
  57. pynode_flow-0.2.4/pynode/nodes/UltralyticsNode/__init__.py +11 -0
  58. pynode_flow-0.2.4/pynode/nodes/UltralyticsNode/model_paths.py +115 -0
  59. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/ultralytics_node.py +20 -14
  60. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/WebhookNode/webhook_node.py +1 -1
  61. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/__init__.py +6 -5
  62. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/base_node.py +161 -53
  63. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/image_utils.py +1 -1
  64. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/info.py +36 -2
  65. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_discovery_node.py +1 -1
  66. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_manager.py +31 -6
  67. pynode_flow-0.2.4/pynode/static/examples/16-socket-bridge.json +75 -0
  68. pynode_flow-0.2.4/pynode/static/examples/17-slider-crop.json +105 -0
  69. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/events.js +35 -0
  70. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/main.js +5 -1
  71. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/nodes.js +56 -2
  72. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/properties.js +182 -1
  73. pynode_flow-0.2.4/pynode/static/js/ui-utils.js +82 -0
  74. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/style.css +226 -0
  75. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/workflow_engine.py +0 -1
  76. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode_flow.egg-info/PKG-INFO +20 -1
  77. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode_flow.egg-info/SOURCES.txt +26 -0
  78. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode_flow.egg-info/scm_file_list.json +266 -240
  79. pynode_flow-0.2.4/pynode_flow.egg-info/scm_version.json +8 -0
  80. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pyproject.toml +10 -1
  81. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/setup.sh +4 -5
  82. pynode_flow-0.2.4/tests/test_control_slider_node.py +199 -0
  83. pynode_flow-0.2.4/tests/test_crop_node.py +138 -0
  84. pynode_flow-0.2.4/tests/test_function_node.py +200 -0
  85. pynode_flow-0.2.4/tests/test_image_upload_node.py +176 -0
  86. pynode_flow-0.2.4/tests/test_models_dir.py +230 -0
  87. pynode_flow-0.2.4/tests/test_range_node.py +180 -0
  88. pynode_flow-0.2.4/tests/test_send_copy_semantics.py +226 -0
  89. pynode_flow-0.2.4/tests/test_socket_nodes.py +1292 -0
  90. pynode_flow-0.2.2/DOCKER.md +0 -113
  91. pynode_flow-0.2.2/pynode/nodes/FunctionNode/function_node.py +0 -168
  92. pynode_flow-0.2.2/pynode/nodes/ImageUploadNode/image_upload_node.py +0 -96
  93. pynode_flow-0.2.2/pynode/nodes/RangeNode/range_node.py +0 -115
  94. pynode_flow-0.2.2/pynode/nodes/UltralyticsNode/__init__.py +0 -4
  95. pynode_flow-0.2.2/pynode/static/js/ui-utils.js +0 -28
  96. pynode_flow-0.2.2/pynode_flow.egg-info/scm_version.json +0 -8
  97. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/.claude/skills/pynode-workflows/SKILL.md +0 -0
  98. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/.github/workflows/ci.yml +0 -0
  99. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/.github/workflows/publish.yaml +0 -0
  100. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/INSTALL.md +0 -0
  101. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/LICENSE +0 -0
  102. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/MANIFEST.in +0 -0
  103. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/docs/CREATING_NODES.md +0 -0
  104. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/docs/CUSTOM_NODES.md +0 -0
  105. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/docs/DOCKER.md +0 -0
  106. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/docs/EXTENSIBILITY.md +0 -0
  107. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/docs/UI_COMPONENTS.md +0 -0
  108. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/examples/README.md +0 -0
  109. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/examples/camera_workflow.py +0 -0
  110. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/examples/camera_yolo_workflow.py +0 -0
  111. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/install_nodes.bat +0 -0
  112. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/install_nodes.sh +0 -0
  113. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/plan-pynodeRepoReview.prompt.md +0 -0
  114. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/__main__.py +0 -0
  115. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/_perf.py +0 -0
  116. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/api/__init__.py +0 -0
  117. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/api/helpers.py +0 -0
  118. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/api/services.py +0 -0
  119. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/api/sse.py +0 -0
  120. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/api/uploads.py +0 -0
  121. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/api/workflows.py +0 -0
  122. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/install_nodes.py +0 -0
  123. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/node_registry.py +0 -0
  124. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/AutoSyncNode/auto_sync_node.py +0 -0
  125. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/__init__.py +0 -0
  126. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +0 -0
  127. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/requirements.txt +0 -0
  128. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/__init__.py +0 -0
  129. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/batch_node.py +0 -0
  130. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/requirements.txt +0 -0
  131. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/__init__.py +0 -0
  132. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/camera_node.py +0 -0
  133. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/requirements.txt +0 -0
  134. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/__init__.py +0 -0
  135. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/change_node.py +0 -0
  136. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/requirements.txt +0 -0
  137. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/__init__.py +0 -0
  138. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +0 -0
  139. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/requirements.txt +0 -0
  140. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CounterNode/__init__.py +0 -0
  141. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CounterNode/counter_node.py +0 -0
  142. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CropNode/__init__.py +0 -0
  143. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/CropNode/requirements.txt +0 -0
  144. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/__init__.py +0 -0
  145. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/debug_node.py +0 -0
  146. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/requirements.txt +0 -0
  147. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DelayNode/requirements.txt +0 -0
  148. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/__init__.py +0 -0
  149. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +0 -0
  150. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/requirements.txt +0 -0
  151. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ErrorNode/error_node.py +0 -0
  152. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ErrorNode/requirements.txt +0 -0
  153. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/__init__.py +0 -0
  154. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/filter_node.py +0 -0
  155. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/requirements.txt +0 -0
  156. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/__init__.py +0 -0
  157. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/requirements.txt +0 -0
  158. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FunctionNode/__init__.py +0 -0
  159. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/FunctionNode/requirements.txt +0 -0
  160. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/GateNode/gate_node.py +0 -0
  161. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/GateNode/requirements.txt +0 -0
  162. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/__init__.py +0 -0
  163. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/requirements.txt +0 -0
  164. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/__init__.py +0 -0
  165. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/requirements.txt +0 -0
  166. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/__init__.py +0 -0
  167. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/imagewriter_node.py +0 -0
  168. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/requirements.txt +0 -0
  169. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +0 -0
  170. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +0 -0
  171. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +0 -0
  172. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +0 -0
  173. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +0 -0
  174. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +0 -0
  175. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +0 -0
  176. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/readme.md +0 -0
  177. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +0 -0
  178. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/inference_node.py +0 -0
  179. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/requirements.txt +0 -0
  180. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InjectNode/inject_node.py +0 -0
  181. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/InjectNode/requirements.txt +0 -0
  182. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/__init__.py +0 -0
  183. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/join_node.py +0 -0
  184. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/requirements.txt +0 -0
  185. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/__init__.py +0 -0
  186. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/label_filter_node.py +0 -0
  187. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/requirements.txt +0 -0
  188. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/__init__.py +0 -0
  189. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_in_node.py +0 -0
  190. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_out_node.py +0 -0
  191. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/requirements.txt +0 -0
  192. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/__init__.py +0 -0
  193. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/requirements.txt +0 -0
  194. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/__init__.py +0 -0
  195. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/messagereader_node.py +0 -0
  196. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/messagewriter_node.py +0 -0
  197. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/__init__.py +0 -0
  198. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/requirements.txt +0 -0
  199. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/__init__.py +0 -0
  200. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/bitwise_node.py +0 -0
  201. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blend_node.py +0 -0
  202. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blur_node.py +0 -0
  203. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/color_space_node.py +0 -0
  204. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/contours_node.py +0 -0
  205. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/denormalize_coords_node.py +0 -0
  206. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/draw_node.py +0 -0
  207. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/edge_detector_node.py +0 -0
  208. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/equirectangular_node.py +0 -0
  209. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/frequency_filter_node.py +0 -0
  210. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/hough_lines_node.py +0 -0
  211. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/morphology_node.py +0 -0
  212. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/normalize_coords_node.py +0 -0
  213. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/paste_node.py +0 -0
  214. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/requirements.txt +0 -0
  215. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/resize_node.py +0 -0
  216. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/rotate_node.py +0 -0
  217. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/template_match_node.py +0 -0
  218. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/threshold_node.py +0 -0
  219. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/__init__.py +0 -0
  220. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/point_in_shape_node.py +0 -0
  221. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/requirements.txt +0 -0
  222. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/__init__.py +0 -0
  223. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +0 -0
  224. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/requirements.txt +0 -0
  225. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/QueueLengthProbeNode/__init__.py +0 -0
  226. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +0 -0
  227. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/__init__.py +0 -0
  228. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/requirements.txt +0 -0
  229. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RESTEndpointNode/__init__.py +0 -0
  230. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +0 -0
  231. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RangeNode/__init__.py +0 -0
  232. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RangeNode/requirements.txt +0 -0
  233. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/__init__.py +0 -0
  234. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/rate_probe_node.py +0 -0
  235. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/requirements.txt +0 -0
  236. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/__init__.py +0 -0
  237. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/requirements.txt +0 -0
  238. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/README.md +0 -0
  239. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/__init__.py +0 -0
  240. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/merge_predictions_node.py +0 -0
  241. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/requirements.txt +0 -0
  242. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/slice_collector_node.py +0 -0
  243. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/slice_image_node.py +0 -0
  244. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/__init__.py +0 -0
  245. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/requirements.txt +0 -0
  246. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SwitchNode/requirements.txt +0 -0
  247. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SyncNode/__init__.py +0 -0
  248. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/SyncNode/sync_node.py +0 -0
  249. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/TemplateNode/requirements.txt +0 -0
  250. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/TemplateNode/template_node.py +0 -0
  251. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/__init__.py +0 -0
  252. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/supervision_tracker_node.py +0 -0
  253. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/tracker_node.py +0 -0
  254. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/requirements.txt +0 -0
  255. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/UnknownNode/__init__.py +0 -0
  256. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/VLLMNode/__init__.py +0 -0
  257. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/VLLMNode/vllm_node.py +0 -0
  258. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/VideoReaderNode/__init__.py +0 -0
  259. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/VideoReaderNode/video_reader_node.py +0 -0
  260. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/VideoWriterNode/__init__.py +0 -0
  261. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/VideoWriterNode/video_writer_node.py +0 -0
  262. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/WebhookNode/__init__.py +0 -0
  263. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/__init__.py +0 -0
  264. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_broadcast_node.py +0 -0
  265. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/requirements.txt +0 -0
  266. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/nodes/messages.py +0 -0
  267. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/server.py +0 -0
  268. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/01-hello-world.json +0 -0
  269. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/02-function-transform.json +0 -0
  270. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/03-switch-routing.json +0 -0
  271. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/04-flow-control.json +0 -0
  272. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/05-webcam-viewer.json +0 -0
  273. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/06-webcam-yolo.json +0 -0
  274. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/07-blur-people.json +0 -0
  275. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/08-video-yolo-recorder.json +0 -0
  276. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/09-mqtt-pub-sub.json +0 -0
  277. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/10-person-alert-mqtt.json +0 -0
  278. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/README.md +0 -0
  279. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/examples/manifest.json +0 -0
  280. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/images/favicon4.png +0 -0
  281. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/index.html +0 -0
  282. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/README.md +0 -0
  283. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/auth.js +0 -0
  284. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/clipboard.js +0 -0
  285. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/config.js +0 -0
  286. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/connections.js +0 -0
  287. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/debug.js +0 -0
  288. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/history.js +0 -0
  289. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/minimap.js +0 -0
  290. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/mqtt-services.js +0 -0
  291. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/palette.js +0 -0
  292. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/selection.js +0 -0
  293. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/state.js +0 -0
  294. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/viewport.js +0 -0
  295. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/workflow.js +0 -0
  296. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/static/js/workflows.js +0 -0
  297. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode/workflow_manager.py +0 -0
  298. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode_flow.egg-info/dependency_links.txt +0 -0
  299. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode_flow.egg-info/entry_points.txt +0 -0
  300. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode_flow.egg-info/requires.txt +0 -0
  301. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/pynode_flow.egg-info/top_level.txt +0 -0
  302. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/requirements.txt +0 -0
  303. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/setup.bat +0 -0
  304. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/setup.cfg +0 -0
  305. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/setup.py +0 -0
  306. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/__init__.py +0 -0
  307. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/conftest.py +0 -0
  308. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_api_auth.py +0 -0
  309. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_api_errors.py +0 -0
  310. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_api_nodes.py +0 -0
  311. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_api_security.py +0 -0
  312. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_api_workflows.py +0 -0
  313. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_base_node_messaging.py +0 -0
  314. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_camera_node_backend.py +0 -0
  315. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_concurrency.py +0 -0
  316. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_config.py +0 -0
  317. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_counter_sse.py +0 -0
  318. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_debug_workflow_scope.py +0 -0
  319. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_delay_rate_limit.py +0 -0
  320. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_device_detection.py +0 -0
  321. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_link_nodes.py +0 -0
  322. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_message_reader_pickle.py +0 -0
  323. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_mqtt_services.py +0 -0
  324. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_node_registration.py +0 -0
  325. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_node_types_api.py +0 -0
  326. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_persistence.py +0 -0
  327. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_serialization.py +0 -0
  328. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_video_reader_node.py +0 -0
  329. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_workflow_engine.py +0 -0
  330. {pynode_flow-0.2.2 → pynode_flow-0.2.4}/tests/test_workflow_roundtrip.py +0 -0
@@ -11,7 +11,6 @@ __pycache__/
11
11
  *.egg-info
12
12
  dist/
13
13
  build/
14
- .git/
15
14
  .gitignore
16
15
  .vscode/
17
16
  .idea/
@@ -152,6 +152,8 @@ Thumbs.db
152
152
  *.json
153
153
  # ...but keep the bundled, GUI-loadable example workflows
154
154
  !pynode/static/examples/*.json
155
+ # ...and the example Node-RED flow shipped with the SocketNode nodes
156
+ !pynode/nodes/SocketNode/interop/*.json
155
157
 
156
158
  # PyTorch model files
157
159
  *.pt
@@ -0,0 +1,196 @@
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
+ ### Version
69
+
70
+ The version shown in the UI is derived automatically from git history during
71
+ the Docker build — no extra flags or environment variables are needed. Tagged
72
+ commits display a clean version (e.g. `v0.2.3`); untagged commits include the
73
+ distance and short SHA (e.g. `v0.2.3-1-gcd272cf`).
74
+
75
+ ### Custom CUDA Version
76
+
77
+ To use a different CUDA version, edit the `Dockerfile`:
78
+
79
+ ```dockerfile
80
+ FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04 # Change CUDA version
81
+ ```
82
+
83
+ ### Without GPU Support
84
+
85
+ For CPU-only deployment, use the CPU Dockerfile:
86
+
87
+ ```bash
88
+ docker build -f Dockerfile.cpu -t pynode-cpu .
89
+ docker run -p 5000:5000 pynode-cpu
90
+ ```
91
+
92
+ ## Accessing the Application
93
+
94
+ Once running, access the web interface at:
95
+ - http://localhost:5000
96
+
97
+ ## mDNS / Service Discovery
98
+
99
+ ### The Limitation
100
+
101
+ The default `docker-compose.yml` uses Docker's standard bridge network. mDNS
102
+ (multicast DNS) relies on link-local multicast traffic (UDP `224.0.0.251:5353`),
103
+ which the bridge network's NAT does not forward in either direction. With the
104
+ default compose file, PyNode's mDNS nodes can broadcast and listen *inside*
105
+ the container, but that traffic never reaches the LAN — so other machines on
106
+ your network cannot discover the container, and the container cannot discover
107
+ services running elsewhere.
108
+
109
+ ### `HOST_IP`
110
+
111
+ The `HOST_IP` environment variable (see `docker-compose.yml`) only controls
112
+ the IP address **advertised** in mDNS service records - it does *not* make
113
+ multicast traffic reach the LAN. Setting `HOST_IP` without one of the fixes
114
+ below just means the (unreachable) mDNS records will advertise the right
115
+ address instead of the container's internal one; it does not fix discovery.
116
+
117
+ ### Fixing It
118
+
119
+ **Option 1: `network_mode: host` (Linux hosts only, full fix)**
120
+
121
+ On Linux, host networking removes the network isolation between the
122
+ container and the host, so multicast works exactly as it would for a
123
+ process running directly on the host:
124
+
125
+ ```yaml
126
+ services:
127
+ pynode:
128
+ network_mode: host
129
+ # ports: mapping is ignored in host mode - the container shares the
130
+ # host's network stack directly, so remove/comment out `ports:` above.
131
+ ```
132
+
133
+ This does **not** work on Docker Desktop for Windows/macOS - Docker Desktop
134
+ runs containers inside a lightweight VM, and `network_mode: host` there still
135
+ does not bridge multicast across the VM boundary.
136
+
137
+ **Option 2: macvlan network**
138
+
139
+ A macvlan network gives the container its own IP address directly on the LAN
140
+ (as if it were a separate physical device), which also allows multicast to
141
+ work normally. This works on Docker Desktop as well as Linux, but requires
142
+ more setup (a dedicated IP, and typically a way for the host itself to reach
143
+ the container since macvlan interfaces are not reachable from the host by
144
+ default). See the
145
+ [Docker macvlan network documentation](https://docs.docker.com/network/drivers/macvlan/)
146
+ for setup details.
147
+
148
+ **Option 3: mDNS reflector on the host**
149
+
150
+ When host networking isn't available (e.g. Docker Desktop), run an mDNS
151
+ reflector on the host to bridge multicast between the host's network and the
152
+ container's bridge network:
153
+
154
+ - **avahi-daemon** (Linux) - enable reflection in `/etc/avahi/avahi-daemon.conf`:
155
+ ```ini
156
+ [reflector]
157
+ enable-reflector=yes
158
+ ```
159
+ - **mdns-repeater** - run as a sidecar/host process that repeats mDNS packets
160
+ between interfaces.
161
+
162
+ This is the option that works when host networking isn't an option, at the
163
+ cost of an extra moving part to run and maintain.
164
+
165
+ ### Port Matching
166
+
167
+ Whichever option you use, the port PyNode advertises in its mDNS records
168
+ must match the port other machines can actually reach it on (the *published*
169
+ port), not just the container's internal port. If you remap ports (e.g.
170
+ `"8080:5000"`), make sure discovery consumers connect using the published
171
+ port (`8080`), not the internal one.
172
+
173
+ ## Troubleshooting
174
+
175
+ ### GPU Not Detected
176
+
177
+ Verify NVIDIA Container Toolkit is installed:
178
+ ```bash
179
+ docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu22.04 nvidia-smi
180
+ ```
181
+
182
+ ### Permission Issues
183
+
184
+ Ensure the mounted directories have proper permissions:
185
+ ```bash
186
+ mkdir -p workflows logs
187
+ chmod 777 workflows logs
188
+ ```
189
+
190
+ ### Rebuild from Scratch
191
+
192
+ ```bash
193
+ docker-compose down
194
+ docker-compose build --no-cache
195
+ docker-compose up
196
+ ```
@@ -1,4 +1,4 @@
1
- FROM docker.io/nvidia/cuda:12.6.0-devel-ubuntu22.04
1
+ FROM docker.io/nvidia/cuda:12.6.0-runtime-ubuntu22.04
2
2
 
3
3
  # Set environment variables
4
4
  ENV DEBIAN_FRONTEND=noninteractive
@@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y \
12
12
  python3-venv \
13
13
  git \
14
14
  wget \
15
+ gosu \
15
16
  gfortran \
16
17
  libgl1-mesa-glx \
17
18
  libgtk-3-0 \
@@ -44,16 +45,29 @@ WORKDIR /app
44
45
  # Copy application files
45
46
  COPY . /app/
46
47
 
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"
48
+ # Derive the display version from git history, then remove .git to save
49
+ # space. SETUPTOOLS_SCM_PRETEND_VERSION keeps pip install happy (PEP 440);
50
+ # the real version string is written to _version.py afterwards.
51
+ RUN chmod +x /app/setup.sh /app/install_nodes.sh && \
52
+ RAW=$(git -C /app describe --tags --always 2>/dev/null || true) && \
53
+ if echo "$RAW" | grep -qE '^v?[0-9]+\.[0-9]+'; then \
54
+ VERSION=$(echo "$RAW" | sed 's/^v//'); \
55
+ elif [ -n "$RAW" ]; then \
56
+ VERSION="0.0.0+g${RAW}"; \
57
+ else \
58
+ VERSION="0.0.0"; \
59
+ fi && \
60
+ rm -rf /app/.git && \
61
+ SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 bash -c "source /app/setup.sh < /dev/null" && \
62
+ printf '__version__ = "%s"\n' "$VERSION" > /app/pynode/_version.py
52
63
 
53
64
  # Create a non-root user and give it ownership of the app directory
54
65
  RUN useradd --create-home --uid 1000 appuser \
55
66
  && chown -R appuser:appuser /app
56
- USER appuser
67
+
68
+ # Entrypoint ensures bind-mounted directories are writable by appuser
69
+ COPY docker-entrypoint.sh /app/docker-entrypoint.sh
70
+ RUN chmod +x /app/docker-entrypoint.sh
57
71
 
58
72
  # Expose port
59
73
  EXPOSE 5000
@@ -62,5 +76,5 @@ EXPOSE 5000
62
76
  HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
63
77
  CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
64
78
 
65
- # Run the application in production mode
79
+ ENTRYPOINT ["/app/docker-entrypoint.sh"]
66
80
  CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
@@ -6,6 +6,7 @@ ENV PYTHONUNBUFFERED=1
6
6
 
7
7
  # Install system dependencies
8
8
  RUN apt-get update && apt-get install -y \
9
+ gosu \
9
10
  libgl1-mesa-glx \
10
11
  libglib2.0-0 \
11
12
  libsm6 \
@@ -19,15 +20,24 @@ WORKDIR /app
19
20
  # Copy application files
20
21
  COPY . /app/
21
22
 
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 && \
23
+ # Derive the display version from git history, then remove .git to save
24
+ # space. SETUPTOOLS_SCM_PRETEND_VERSION keeps pip install happy (PEP 440);
25
+ # the real version string is written to _version.py afterwards.
26
+ RUN RAW=$(git describe --tags --always 2>/dev/null || true) && \
27
+ if echo "$RAW" | grep -qE '^v?[0-9]+\.[0-9]+'; then \
28
+ VERSION=$(echo "$RAW" | sed 's/^v//'); \
29
+ elif [ -n "$RAW" ]; then \
30
+ VERSION="0.0.0+g${RAW}"; \
31
+ else \
32
+ VERSION="0.0.0"; \
33
+ fi && \
34
+ rm -rf /app/.git && \
35
+ python3 -m venv appenv && \
36
+ . appenv/bin/activate && \
28
37
  pip install --upgrade pip && \
29
38
  pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu && \
30
- pip install -e ".[vision,mqtt]"
39
+ SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 pip install -e ".[vision,mqtt]" && \
40
+ printf '__version__ = "%s"\n' "$VERSION" > /app/pynode/_version.py
31
41
 
32
42
  # Install per-node dependencies (best-effort: a node whose vendor SDK is not
33
43
  # on PyPI, e.g. OmronCameraNode's stapipy, is skipped without failing the build)
@@ -36,7 +46,10 @@ RUN . appenv/bin/activate && pynode-install-nodes || true
36
46
  # Create a non-root user and give it ownership of the app directory
37
47
  RUN useradd --create-home --uid 1000 appuser \
38
48
  && chown -R appuser:appuser /app
39
- USER appuser
49
+
50
+ # Entrypoint ensures bind-mounted directories are writable by appuser
51
+ COPY docker-entrypoint.sh /app/docker-entrypoint.sh
52
+ RUN chmod +x /app/docker-entrypoint.sh
40
53
 
41
54
  # Expose port
42
55
  EXPOSE 5000
@@ -45,5 +58,5 @@ EXPOSE 5000
45
58
  HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
46
59
  CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
47
60
 
48
- # Run the application in production mode
61
+ ENTRYPOINT ["/app/docker-entrypoint.sh"]
49
62
  CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pynode-flow
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python based Node-RED-like visual workflow system
5
5
  Author: olkham
6
6
  License-Expression: MIT
@@ -214,6 +214,25 @@ PyNode persists workflows under `<data dir>/workflows/` (`workflow.json` plus ti
214
214
 
215
215
  The resolved location is logged at startup (`Workflow data directory: ...`).
216
216
 
217
+ ### Models Directory
218
+
219
+ Nodes that download or generate model weights (e.g. the YOLO node's `.pt`
220
+ files and exported OpenVINO models) write them into a shared **models
221
+ directory** instead of the process working directory. It is resolved in this
222
+ order:
223
+
224
+ 1. `pynode --models-dir <path>` CLI flag,
225
+ 2. `PYNODE_MODELS_DIR` environment variable,
226
+ 3. `<data dir>/models` otherwise — so `<repo>/models` for a source checkout and
227
+ `~/.pynode/models` for a regular `pip install`.
228
+
229
+ > **Upgrading:** older PyNode versions downloaded model files into whatever the
230
+ > working directory happened to be, so stray `.pt` files and
231
+ > `*_openvino_model/` folders may exist in the repo root, `pynode/models/` or
232
+ > `pynode/nodes/`. PyNode still reads models from those legacy locations, but
233
+ > nothing is migrated automatically — you can move them into the models
234
+ > directory manually when convenient.
235
+
217
236
  ## Securing PyNode
218
237
 
219
238
  **PyNode executes arbitrary Python by design** (e.g. FunctionNode runs whatever code is in the workflow), so anyone who can reach the API can run code on the host. Authentication is the trust boundary — secure the server before exposing it beyond your own machine:
@@ -149,6 +149,25 @@ PyNode persists workflows under `<data dir>/workflows/` (`workflow.json` plus ti
149
149
 
150
150
  The resolved location is logged at startup (`Workflow data directory: ...`).
151
151
 
152
+ ### Models Directory
153
+
154
+ Nodes that download or generate model weights (e.g. the YOLO node's `.pt`
155
+ files and exported OpenVINO models) write them into a shared **models
156
+ directory** instead of the process working directory. It is resolved in this
157
+ order:
158
+
159
+ 1. `pynode --models-dir <path>` CLI flag,
160
+ 2. `PYNODE_MODELS_DIR` environment variable,
161
+ 3. `<data dir>/models` otherwise — so `<repo>/models` for a source checkout and
162
+ `~/.pynode/models` for a regular `pip install`.
163
+
164
+ > **Upgrading:** older PyNode versions downloaded model files into whatever the
165
+ > working directory happened to be, so stray `.pt` files and
166
+ > `*_openvino_model/` folders may exist in the repo root, `pynode/models/` or
167
+ > `pynode/nodes/`. PyNode still reads models from those legacy locations, but
168
+ > nothing is migrated automatically — you can move them into the models
169
+ > directory manually when convenient.
170
+
152
171
  ## Securing PyNode
153
172
 
154
173
  **PyNode executes arbitrary Python by design** (e.g. FunctionNode runs whatever code is in the workflow), so anyone who can reach the API can run code on the host. Authentication is the trust boundary — secure the server before exposing it beyond your own machine:
@@ -5,7 +5,12 @@ services:
5
5
  dockerfile: Dockerfile
6
6
  container_name: pynode
7
7
  ports:
8
- - "500:5000"
8
+ - "5000:5000"
9
+ # Uncomment for mDNS support on Linux hosts (see DOCKER.md > mDNS /
10
+ # Service Discovery). Not effective on Docker Desktop for Windows/macOS.
11
+ # Remove the `ports:` mapping above when enabling this - it is ignored
12
+ # in host networking mode.
13
+ # network_mode: host
9
14
  volumes:
10
15
  # Mount persistent data
11
16
  - ./workflows:/app/workflows
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Ensure bind-mounted directories are writable by appuser (uid 1000).
5
+ # docker-compose mounts host directories that may be owned by root.
6
+ for dir in /app/workflows /app/workflows/services /app/logs; do
7
+ mkdir -p "$dir"
8
+ chown -R 1000:1000 "$dir" 2>/dev/null || true
9
+ done
10
+
11
+ # Drop privileges and exec the CMD
12
+ exec gosu appuser "$@"
@@ -0,0 +1,135 @@
1
+ # PyNode Project Review Plan — 2026-07-18
2
+
3
+ Follow-up to the July 2026 overhaul (see `plan-pynodeRepoReview.prompt.md`). This plan captures
4
+ the full findings of the 2026-07-18 project review. Phases A–D are being executed now;
5
+ E–H are deferred and can be picked up in any later session.
6
+
7
+ Status legend: `[NOW]` in progress this session · `[LATER]` deferred · `[DECIDE]` needs a user decision.
8
+
9
+ ---
10
+
11
+ ## Phase A [NOW] — Quick bug fixes + compose port
12
+
13
+ 1. **Dead `include_predictions` toggle** — `pynode/nodes/UltralyticsNode/ultralytics_node.py:348`
14
+ reads `if include_predictions or True:` so the "Include Predictions in Output" checkbox does
15
+ nothing. Honor the flag (use `get_config_bool` for string-safety; same for `include_image`).
16
+ 2. **docker-compose port typo** — `docker-compose.yml` publishes `"500:5000"`; DOCKER.md documents
17
+ port 5000. Change to `"5000:5000"`.
18
+ 3. **Silent node-import failures** — `pynode/nodes/__init__.py` `_try_import_python_files` swallows
19
+ every exception with bare `continue`; a custom node with a syntax error vanishes from the palette
20
+ with no log output. Log warnings (missing optional deps may stay at debug level).
21
+ 4. Minor cleanups: dead `system_error_node` variable in `workflow_engine.py:367`; ImageViewerNode
22
+ MJPEG generator loops forever after node stop (holds a waitress thread); ImageViewerNode's shared
23
+ `last_sent_timestamp` makes the SSE broadcaster and the `/frame` polling route steal frames from
24
+ each other; `socket.inet_ntoa` in `mdns_manager.py` throws on IPv6 service records.
25
+
26
+ Acceptance: full test suite passes; toggling "Include Predictions" actually changes output;
27
+ node import errors appear in the log.
28
+
29
+ ## Phase B [NOW] — Model / node storage directories
30
+
31
+ Problem: `YOLO('yolo26n.pt')` downloads weights into the process CWD and `export(format='openvino')`
32
+ writes next to the source; strays currently exist in 4 places (repo root, `models/`,
33
+ `pynode/models/`, `pynode/nodes/`). A pip-installed PyNode launched from inside a venv would scatter
34
+ files there. Also `[tool.setuptools.package-data] nodes/**/*` would bake a stray `.pt` in
35
+ `pynode/nodes/` into a locally built wheel.
36
+
37
+ 1. `pynode/config.py`: add `PYNODE_MODELS_DIR` env var + `resolve_models_dir()` → default
38
+ `<data_dir>/models/`; `--models-dir` CLI flag in `main.py` (mirror the `--data-dir` pattern).
39
+ 2. `BaseNode.get_storage_dir(subdir=None)` → `<data_dir>/node_storage/<NodeType>/`, created lazily.
40
+ General rule going forward: nodes never write binaries outside their storage dir.
41
+ 3. UltralyticsNode: resolve bare model names to an absolute path under the models dir before calling
42
+ `YOLO()` (Ultralytics downloads named assets to an absolute path; the OpenVINO export then lands
43
+ in the same folder). Check legacy locations first to avoid re-downloading; never move/delete
44
+ existing user files automatically (`pynode/models/` holds real user models).
45
+ 4. Packaging: `[tool.setuptools.exclude-package-data]` for `*.pt` / `*_openvino_model` so strays
46
+ can't enter wheels.
47
+ 5. Document (README/INSTALL): env var, CLI flag, where models live per install type.
48
+
49
+ Deferred idea: settings UI for the models folder.
50
+
51
+ ## Phase C [NOW] — mDNS in Docker: fix + docs
52
+
53
+ Root cause: default bridge network; mDNS is link-local multicast (224.0.0.251:5353) which Docker's
54
+ bridge/NAT does not forward. `HOST_IP` only fixes the advertised address, not reachability.
55
+
56
+ 1. DOCKER.md: new "mDNS / service discovery" section — explain the limitation; document `HOST_IP`
57
+ (currently undocumented); give options in order: `network_mode: host` (Linux; ports mapping
58
+ ignored; does NOT help on Docker Desktop Win/Mac), macvlan, host-side avahi reflector
59
+ (`enable-reflector=yes`) or mdns-repeater sidecar.
60
+ 2. docker-compose.yml: commented-out `network_mode: host` alternative block with the caveats.
61
+ 3. Advertised `service_port` must match the externally reachable port (relates to A2).
62
+
63
+ ## Phase D [NOW] — send() copy semantics (biggest perf win)
64
+
65
+ `BaseNode.send()` deep-copies the message per recipient (`base_node.py:254`); raw numpy frames make
66
+ this ~6 MB memcpy per hop per recipient. Adopt Node-RED semantics: **last recipient gets the
67
+ original message, only additional wires get deep copies** — zero frame copies in the common
68
+ single-connection case. Contract change: *senders must not mutate a message after calling send().*
69
+
70
+ 1. Audit every node for post-`send()` mutation and source nodes for numpy buffer reuse; fix
71
+ offenders to create fresh messages instead.
72
+ 2. Implement last-recipient-no-copy in both the queued path and the sink "direct processing" path.
73
+ 3. Update/adjust tests that assert sender-side isolation; document the contract in base_node.py.
74
+
75
+ ## Phase E [LATER] — Remaining performance items
76
+
77
+ - Sink "direct processing" runs on the sender's thread (ImageViewer JPEG encode inside the camera
78
+ loop) and skips `drop_while_busy`; route sinks through the normal queue or apply drop checks.
79
+ - Idle worker threads busy-poll every 10–100 ms (`base_node.py:296`); switch to blocking
80
+ `queue.get()` with a `None` sentinel pushed in `on_stop()`.
81
+ - `sort_msg_keys` runs on every send per recipient; sort at display time in DebugNode instead.
82
+ - Waitress `threads=10` (`main.py:103`): each SSE client and MJPEG viewer permanently holds a
83
+ thread → silent API starvation. Make threads a CLI flag, default higher (~24), document.
84
+ - Dockerfile uses `cuda:12.6.0-devel` (~8 GB base); switch to `-runtime` or multi-stage.
85
+
86
+ ## Phase F [LATER] — UI/UX
87
+
88
+ - Canvas node search (Ctrl+F: find by name/type, pan to node).
89
+ - Per-node runtime status text on canvas (Node-RED `status()` equivalent: "model loaded", fps,
90
+ drop count) — SSE plumbing (`sse_handlers`) already exists.
91
+ - Palette manager UI (falls out of Phase G).
92
+ - Light theme (style.css is dark-only; tokenize colors to CSS variables first).
93
+ - Groups/subflows — biggest structural gap vs Node-RED; affects workflow JSON schema, decide
94
+ deliberately before other schema work.
95
+
96
+ ## Phase G [LATER] — Live node install from an approved registry
97
+
98
+ Constraints today: registries built once at import (`node_registry.py:154-156`); node `api_routes`
99
+ registered as real Flask routes at app creation (`api/nodes.py:418`, frozen after first request).
100
+
101
+ 1. **Registry**: curated JSON index in a separate GitHub repo (`pynode-registry`), served raw:
102
+ name, description, pip requirement/git URL, version, author, category, `verified` flag.
103
+ Approval = PR review.
104
+ 2. **External nodes dir**: also scan `<data_dir>/nodes/` (Node-RED `~/.node-red` pattern) —
105
+ cheap, enables "drop a folder in, restart". Later: `[project.entry-points."pynode.nodes"]`.
106
+ 3. **API**: `GET /api/palette/catalog` (cached), `POST /api/palette/install` (pip via
107
+ `sys.executable -m pip`, reuse install_nodes.py pattern), `DELETE` for removal.
108
+ 4. **Activation v1**: install + "restart to activate" banner.
109
+ **v2 (hot reload)**: make cache builders re-runnable, `palette-updated` SSE event, convert
110
+ per-node API routes to one catch-all `/api/nodes/<node_id>/<path:sub>` dispatching through the
111
+ registry dict at request time (removes the Flask route-freeze problem).
112
+ 5. **Security**: only names from the curated index, pinned versions, behind the API key. Never
113
+ accept arbitrary pip specs from the UI.
114
+
115
+ ## Phase H [DECIDE] — Naming
116
+
117
+ - **GitHub repo rename** `olkham/pynode` → `olkham/pynode-flow`: recommended; cheap, GitHub
118
+ redirects, removes PyPI/repo mismatch. Update `[project.urls]`, README.
119
+ - **Import package rename** (`pynode` → `pynode_flow`): argument for = the unrelated PyPI `pynode`
120
+ 0.1.0 namespace-collision trap (see 2026-07-18 incident); argument against = breaks all imports
121
+ and custom nodes; mismatched dist/import names are normal (opencv-python/cv2).
122
+ Recommendation: keep `pynode` import; add a startup guard that detects the conflicting `pynode`
123
+ dist (importlib.metadata) and fails loudly with "uninstall the unrelated pynode package".
124
+ If ever renaming, the pre-1.0 window is the only sane time.
125
+
126
+ ---
127
+
128
+ ## Execution notes (any session picking this up)
129
+
130
+ - User commits phases themselves; do not commit.
131
+ - Test safety (hard rules, see memory `pynode-test-safety-incident`): tests must build apps via
132
+ `create_app` with tmp paths (tests/conftest.py `api_client`); join every thread before fixture
133
+ teardown; after test work verify `git status --porcelain -- workflows/` is empty. A live PyNode
134
+ server may be running against this repo — its own saves to workflows/ are not test leakage.
135
+ - Suite size at time of writing: 306 tests.
@@ -21,7 +21,11 @@ def _resolve_version() -> str:
21
21
  try:
22
22
  from importlib.metadata import PackageNotFoundError, version
23
23
  try:
24
- return version("pynode-flow")
24
+ v = version("pynode-flow")
25
+ # 0.0.0 is the placeholder used during Docker builds; skip it so
26
+ # the real version written to _version.py is preferred.
27
+ if v != "0.0.0":
28
+ return v
25
29
  except PackageNotFoundError:
26
30
  pass
27
31
  except Exception:
@@ -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.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.2.4'
22
+ __version_tuple__ = version_tuple = (0, 2, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g51b4c1354'
24
+ __commit_id__ = commit_id = 'ga1c7a9d87'
@@ -349,6 +349,7 @@ def _register_json_route(route, methods, handler_name, endpoint_name):
349
349
  _, method, error = _resolve_node_handler(node_id, handler_name)
350
350
  if error:
351
351
  return error
352
+ assert method is not None
352
353
  try:
353
354
  result = method()
354
355
  if result is None:
@@ -369,6 +370,8 @@ def _register_file_upload_route(route, methods, handler_name, endpoint_name, all
369
370
  payload, status = error
370
371
  return jsonify({'success': False, 'error': payload.get_json()['error']}), status
371
372
 
373
+ assert method is not None
374
+
372
375
  if 'file' not in request.files:
373
376
  return jsonify({'success': False, 'error': 'No file provided'}), 400
374
377
 
@@ -397,9 +400,10 @@ def _register_stream_route(route, handler_name, endpoint_name):
397
400
  _, method, error = _resolve_node_handler(node_id, handler_name)
398
401
  if error:
399
402
  return error
403
+ assert method is not None
400
404
 
401
405
  def generate():
402
- for img_data in method():
406
+ for img_data in method(): # type: ignore[reportGeneralTypeIssues]
403
407
  yield (b'--frame\r\n'
404
408
  b'Content-Type: image/jpeg\r\n\r\n' + img_data + b'\r\n')
405
409