pynode-flow 0.2.3__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 (328) hide show
  1. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.dockerignore +0 -1
  2. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.gitignore +2 -0
  3. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/DOCKER.md +7 -0
  4. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/Dockerfile +22 -8
  5. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/Dockerfile.cpu +22 -9
  6. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/PKG-INFO +1 -1
  7. pynode_flow-0.2.4/docker-entrypoint.sh +12 -0
  8. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/__init__.py +5 -1
  9. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/_version.py +3 -3
  10. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/nodes.py +5 -1
  11. pynode_flow-0.2.4/pynode/nodes/ControlSliderNode/control_slider_node.py +208 -0
  12. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CropNode/crop_node.py +141 -12
  13. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DelayNode/delay_node.py +2 -2
  14. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/frame_source_node.py +1 -1
  15. pynode_flow-0.2.4/pynode/nodes/FunctionNode/function_node.py +309 -0
  16. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/image_format_node.py +2 -2
  17. pynode_flow-0.2.4/pynode/nodes/ImageUploadNode/image_upload_node.py +246 -0
  18. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +1 -1
  19. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_in_node.py +4 -3
  20. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_out_node.py +1 -1
  21. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_service.py +1 -1
  22. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/arithmetic_node.py +5 -5
  23. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blob_detector_node.py +1 -1
  24. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/colormap_node.py +1 -1
  25. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/fft_node.py +1 -1
  26. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/focus_estimation_node.py +1 -1
  27. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/histogram_node.py +2 -2
  28. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/hough_circles_node.py +2 -2
  29. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/in_range_node.py +1 -1
  30. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/perspective_node.py +6 -6
  31. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/realsense_depth_node.py +2 -2
  32. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +1 -1
  33. pynode_flow-0.2.4/pynode/nodes/RangeNode/range_node.py +220 -0
  34. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +1 -1
  35. pynode_flow-0.2.4/pynode/nodes/SocketNode/__init__.py +28 -0
  36. pynode_flow-0.2.4/pynode/nodes/SocketNode/interop/README.md +239 -0
  37. pynode_flow-0.2.4/pynode/nodes/SocketNode/interop/nodered-example-flow.json +318 -0
  38. pynode_flow-0.2.4/pynode/nodes/SocketNode/ndjson_protocol.py +176 -0
  39. pynode_flow-0.2.4/pynode/nodes/SocketNode/nodered_snippets.py +147 -0
  40. pynode_flow-0.2.4/pynode/nodes/SocketNode/requirements.txt +4 -0
  41. pynode_flow-0.2.4/pynode/nodes/SocketNode/tcp_in_node.py +269 -0
  42. pynode_flow-0.2.4/pynode/nodes/SocketNode/tcp_out_node.py +270 -0
  43. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_in_node.py +257 -0
  44. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_out_node.py +253 -0
  45. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_probe.py +105 -0
  46. pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_protocol.py +479 -0
  47. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/split_node.py +2 -2
  48. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/ultralytics_node.py +5 -5
  49. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/WebhookNode/webhook_node.py +1 -1
  50. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/base_node.py +1 -1
  51. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/image_utils.py +1 -1
  52. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/info.py +36 -2
  53. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_discovery_node.py +1 -1
  54. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_manager.py +3 -3
  55. pynode_flow-0.2.4/pynode/static/examples/16-socket-bridge.json +75 -0
  56. pynode_flow-0.2.4/pynode/static/examples/17-slider-crop.json +105 -0
  57. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/events.js +35 -0
  58. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/main.js +5 -1
  59. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/nodes.js +56 -2
  60. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/properties.js +182 -1
  61. pynode_flow-0.2.4/pynode/static/js/ui-utils.js +82 -0
  62. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/style.css +226 -0
  63. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/PKG-INFO +1 -1
  64. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/SOURCES.txt +22 -0
  65. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/scm_file_list.json +266 -244
  66. pynode_flow-0.2.4/pynode_flow.egg-info/scm_version.json +8 -0
  67. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pyproject.toml +3 -1
  68. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.sh +4 -5
  69. pynode_flow-0.2.4/tests/test_control_slider_node.py +199 -0
  70. pynode_flow-0.2.4/tests/test_crop_node.py +138 -0
  71. pynode_flow-0.2.4/tests/test_function_node.py +200 -0
  72. pynode_flow-0.2.4/tests/test_image_upload_node.py +176 -0
  73. pynode_flow-0.2.4/tests/test_range_node.py +180 -0
  74. pynode_flow-0.2.4/tests/test_socket_nodes.py +1292 -0
  75. pynode_flow-0.2.3/pynode/nodes/FunctionNode/function_node.py +0 -168
  76. pynode_flow-0.2.3/pynode/nodes/ImageUploadNode/image_upload_node.py +0 -96
  77. pynode_flow-0.2.3/pynode/nodes/RangeNode/range_node.py +0 -115
  78. pynode_flow-0.2.3/pynode/static/js/ui-utils.js +0 -28
  79. pynode_flow-0.2.3/pynode_flow.egg-info/scm_version.json +0 -8
  80. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.claude/skills/pynode-workflows/SKILL.md +0 -0
  81. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.github/workflows/ci.yml +0 -0
  82. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.github/workflows/publish.yaml +0 -0
  83. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/INSTALL.md +0 -0
  84. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/LICENSE +0 -0
  85. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/MANIFEST.in +0 -0
  86. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/README.md +0 -0
  87. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docker-compose.yml +0 -0
  88. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/CREATING_NODES.md +0 -0
  89. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/CUSTOM_NODES.md +0 -0
  90. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/DOCKER.md +0 -0
  91. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/EXTENSIBILITY.md +0 -0
  92. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/UI_COMPONENTS.md +0 -0
  93. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/examples/README.md +0 -0
  94. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/examples/camera_workflow.py +0 -0
  95. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/examples/camera_yolo_workflow.py +0 -0
  96. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/install_nodes.bat +0 -0
  97. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/install_nodes.sh +0 -0
  98. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/plan-pynodeRepoReview.prompt.md +0 -0
  99. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/plan-pynodeReview-2026-07-18.prompt.md +0 -0
  100. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/__main__.py +0 -0
  101. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/_perf.py +0 -0
  102. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/__init__.py +0 -0
  103. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/helpers.py +0 -0
  104. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/services.py +0 -0
  105. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/sse.py +0 -0
  106. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/uploads.py +0 -0
  107. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/workflows.py +0 -0
  108. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/config.py +0 -0
  109. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/install_nodes.py +0 -0
  110. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/main.py +0 -0
  111. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/node_registry.py +0 -0
  112. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/AutoSyncNode/auto_sync_node.py +0 -0
  113. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/__init__.py +0 -0
  114. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +0 -0
  115. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/requirements.txt +0 -0
  116. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/__init__.py +0 -0
  117. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/batch_node.py +0 -0
  118. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/requirements.txt +0 -0
  119. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/__init__.py +0 -0
  120. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/camera_node.py +0 -0
  121. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/requirements.txt +0 -0
  122. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/__init__.py +0 -0
  123. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/change_node.py +0 -0
  124. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/requirements.txt +0 -0
  125. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/__init__.py +0 -0
  126. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +0 -0
  127. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/requirements.txt +0 -0
  128. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CounterNode/__init__.py +0 -0
  129. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CounterNode/counter_node.py +0 -0
  130. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CropNode/__init__.py +0 -0
  131. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CropNode/requirements.txt +0 -0
  132. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/__init__.py +0 -0
  133. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/debug_node.py +0 -0
  134. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/requirements.txt +0 -0
  135. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DelayNode/requirements.txt +0 -0
  136. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/__init__.py +0 -0
  137. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +0 -0
  138. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/requirements.txt +0 -0
  139. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ErrorNode/error_node.py +0 -0
  140. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ErrorNode/requirements.txt +0 -0
  141. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/__init__.py +0 -0
  142. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/filter_node.py +0 -0
  143. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/requirements.txt +0 -0
  144. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/__init__.py +0 -0
  145. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/requirements.txt +0 -0
  146. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FunctionNode/__init__.py +0 -0
  147. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FunctionNode/requirements.txt +0 -0
  148. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/GateNode/gate_node.py +0 -0
  149. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/GateNode/requirements.txt +0 -0
  150. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/__init__.py +0 -0
  151. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/requirements.txt +0 -0
  152. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/__init__.py +0 -0
  153. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/image_viewer_node.py +0 -0
  154. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/requirements.txt +0 -0
  155. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/__init__.py +0 -0
  156. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/imagewriter_node.py +0 -0
  157. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/requirements.txt +0 -0
  158. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +0 -0
  159. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +0 -0
  160. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +0 -0
  161. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +0 -0
  162. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +0 -0
  163. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +0 -0
  164. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +0 -0
  165. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/readme.md +0 -0
  166. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +0 -0
  167. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/inference_node.py +0 -0
  168. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/requirements.txt +0 -0
  169. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InjectNode/inject_node.py +0 -0
  170. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InjectNode/requirements.txt +0 -0
  171. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/__init__.py +0 -0
  172. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/join_node.py +0 -0
  173. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/requirements.txt +0 -0
  174. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/__init__.py +0 -0
  175. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/label_filter_node.py +0 -0
  176. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/requirements.txt +0 -0
  177. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/__init__.py +0 -0
  178. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_bus.py +0 -0
  179. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_in_node.py +0 -0
  180. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_out_node.py +0 -0
  181. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/requirements.txt +0 -0
  182. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/__init__.py +0 -0
  183. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/requirements.txt +0 -0
  184. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/__init__.py +0 -0
  185. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/messagereader_node.py +0 -0
  186. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/messagewriter_node.py +0 -0
  187. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/__init__.py +0 -0
  188. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/omron_camera_node.py +0 -0
  189. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/requirements.txt +0 -0
  190. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/__init__.py +0 -0
  191. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/bitwise_node.py +0 -0
  192. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blend_node.py +0 -0
  193. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blur_node.py +0 -0
  194. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/color_space_node.py +0 -0
  195. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/contours_node.py +0 -0
  196. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/denormalize_coords_node.py +0 -0
  197. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/draw_node.py +0 -0
  198. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/edge_detector_node.py +0 -0
  199. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/equirectangular_node.py +0 -0
  200. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/frequency_filter_node.py +0 -0
  201. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/hough_lines_node.py +0 -0
  202. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/morphology_node.py +0 -0
  203. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/normalize_coords_node.py +0 -0
  204. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/paste_node.py +0 -0
  205. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/requirements.txt +0 -0
  206. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/resize_node.py +0 -0
  207. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/rotate_node.py +0 -0
  208. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/template_match_node.py +0 -0
  209. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/threshold_node.py +0 -0
  210. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/__init__.py +0 -0
  211. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/point_in_shape_node.py +0 -0
  212. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/requirements.txt +0 -0
  213. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/__init__.py +0 -0
  214. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +0 -0
  215. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/requirements.txt +0 -0
  216. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/QueueLengthProbeNode/__init__.py +0 -0
  217. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +0 -0
  218. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/__init__.py +0 -0
  219. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/requirements.txt +0 -0
  220. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RESTEndpointNode/__init__.py +0 -0
  221. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +0 -0
  222. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RangeNode/__init__.py +0 -0
  223. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RangeNode/requirements.txt +0 -0
  224. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/__init__.py +0 -0
  225. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/rate_probe_node.py +0 -0
  226. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/requirements.txt +0 -0
  227. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/__init__.py +0 -0
  228. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/requirements.txt +0 -0
  229. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/README.md +0 -0
  230. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/__init__.py +0 -0
  231. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/merge_predictions_node.py +0 -0
  232. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/requirements.txt +0 -0
  233. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/slice_collector_node.py +0 -0
  234. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/slice_image_node.py +0 -0
  235. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/__init__.py +0 -0
  236. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/requirements.txt +0 -0
  237. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SwitchNode/requirements.txt +0 -0
  238. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SwitchNode/switch_node.py +0 -0
  239. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SyncNode/__init__.py +0 -0
  240. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SyncNode/sync_node.py +0 -0
  241. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TemplateNode/requirements.txt +0 -0
  242. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TemplateNode/template_node.py +0 -0
  243. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/__init__.py +0 -0
  244. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/supervision_tracker_node.py +0 -0
  245. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/tracker_node.py +0 -0
  246. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/__init__.py +0 -0
  247. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/model_paths.py +0 -0
  248. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/requirements.txt +0 -0
  249. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UnknownNode/__init__.py +0 -0
  250. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VLLMNode/__init__.py +0 -0
  251. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VLLMNode/vllm_node.py +0 -0
  252. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoReaderNode/__init__.py +0 -0
  253. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoReaderNode/video_reader_node.py +0 -0
  254. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoWriterNode/__init__.py +0 -0
  255. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoWriterNode/video_writer_node.py +0 -0
  256. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/WebhookNode/__init__.py +0 -0
  257. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/__init__.py +0 -0
  258. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/__init__.py +0 -0
  259. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_broadcast_node.py +0 -0
  260. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/requirements.txt +0 -0
  261. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/messages.py +0 -0
  262. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/server.py +0 -0
  263. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/01-hello-world.json +0 -0
  264. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/02-function-transform.json +0 -0
  265. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/03-switch-routing.json +0 -0
  266. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/04-flow-control.json +0 -0
  267. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/05-webcam-viewer.json +0 -0
  268. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/06-webcam-yolo.json +0 -0
  269. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/07-blur-people.json +0 -0
  270. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/08-video-yolo-recorder.json +0 -0
  271. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/09-mqtt-pub-sub.json +0 -0
  272. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/10-person-alert-mqtt.json +0 -0
  273. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/README.md +0 -0
  274. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/manifest.json +0 -0
  275. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/images/favicon4.png +0 -0
  276. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/index.html +0 -0
  277. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/README.md +0 -0
  278. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/auth.js +0 -0
  279. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/clipboard.js +0 -0
  280. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/config.js +0 -0
  281. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/connections.js +0 -0
  282. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/debug.js +0 -0
  283. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/history.js +0 -0
  284. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/minimap.js +0 -0
  285. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/mqtt-services.js +0 -0
  286. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/palette.js +0 -0
  287. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/selection.js +0 -0
  288. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/state.js +0 -0
  289. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/viewport.js +0 -0
  290. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/workflow.js +0 -0
  291. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/workflows.js +0 -0
  292. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/workflow_engine.py +0 -0
  293. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/workflow_manager.py +0 -0
  294. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/dependency_links.txt +0 -0
  295. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/entry_points.txt +0 -0
  296. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/requires.txt +0 -0
  297. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/top_level.txt +0 -0
  298. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/requirements.txt +0 -0
  299. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.bat +0 -0
  300. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.cfg +0 -0
  301. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.py +0 -0
  302. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/__init__.py +0 -0
  303. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/conftest.py +0 -0
  304. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_auth.py +0 -0
  305. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_errors.py +0 -0
  306. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_nodes.py +0 -0
  307. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_security.py +0 -0
  308. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_workflows.py +0 -0
  309. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_base_node_messaging.py +0 -0
  310. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_camera_node_backend.py +0 -0
  311. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_concurrency.py +0 -0
  312. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_config.py +0 -0
  313. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_counter_sse.py +0 -0
  314. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_debug_workflow_scope.py +0 -0
  315. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_delay_rate_limit.py +0 -0
  316. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_device_detection.py +0 -0
  317. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_link_nodes.py +0 -0
  318. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_message_reader_pickle.py +0 -0
  319. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_models_dir.py +0 -0
  320. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_mqtt_services.py +0 -0
  321. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_node_registration.py +0 -0
  322. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_node_types_api.py +0 -0
  323. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_persistence.py +0 -0
  324. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_send_copy_semantics.py +0 -0
  325. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_serialization.py +0 -0
  326. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_video_reader_node.py +0 -0
  327. {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_workflow_engine.py +0 -0
  328. {pynode_flow-0.2.3 → 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
@@ -65,6 +65,13 @@ The following directories are mounted for persistence:
65
65
 
66
66
  ## Building
67
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
+
68
75
  ### Custom CUDA Version
69
76
 
70
77
  To use a different CUDA version, edit the `Dockerfile`:
@@ -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.3
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
@@ -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 "$@"
@@ -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.3'
22
- __version_tuple__ = version_tuple = (0, 2, 3)
21
+ __version__ = version = '0.2.4'
22
+ __version_tuple__ = version_tuple = (0, 2, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g6a8c7c6b1'
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
 
@@ -0,0 +1,208 @@
1
+ """
2
+ Slider control node - an interactive slider on the node card that stamps its
3
+ current value onto a configurable path of every message flowing through.
4
+
5
+ Drag the slider to tune a value live and watch the effect ripple through the
6
+ downstream pipeline in real time (no redeploy). Because the node has an input
7
+ and an output and only sets one path, several can be daisy-chained to control
8
+ a compound value - e.g. four sliders writing bbox[0..3] to drive a Crop node's
9
+ detection box.
10
+ """
11
+
12
+ from typing import Any, Dict
13
+
14
+ from pynode.nodes.base_node import BaseNode, Info, MessageKeys
15
+
16
+ _info = Info()
17
+ _info.add_text("Interactive slider. Drag it on the node card to set a value; "
18
+ "each message passing through has that value written to the "
19
+ "configured path and is then forwarded. Changes take effect "
20
+ "live on the next message - no redeploy needed.")
21
+ _info.add_header("Inputs")
22
+ _info.add_bullets(
23
+ ("Input 0:", "Any message. The slider value is written to Target Path, "
24
+ "then the message is forwarded."),
25
+ )
26
+ _info.add_header("Outputs")
27
+ _info.add_bullets(
28
+ ("Output 0:", "The same message with Target Path set to the slider value."),
29
+ )
30
+ _info.add_header("Configuration")
31
+ _info.add_bullets(
32
+ ("Target Path:", "Dot/bracket path to set, e.g. 'payload.threshold', "
33
+ "'payload.crop.x', or 'payload.detections[0].bbox[2]'. "
34
+ "Intermediate objects/lists are created as needed."),
35
+ ("Min / Max:", "Slider range (e.g. 0 and 1)."),
36
+ ("Step:", "Slider granularity (e.g. 0.01). A whole-number step emits "
37
+ "integers, otherwise floats."),
38
+ ("Value:", "Current/initial value; persisted with the workflow."),
39
+ ("Label:", "Optional caption shown on the node card."),
40
+ ("Send on Change:", "When on, moving the slider emits a message (value "
41
+ "at Target Path) immediately - even with no input "
42
+ "wired - so the slider can act as a live source."),
43
+ )
44
+ _info.add_header("Tips")
45
+ _info.add_bullets(
46
+ ("Daisy-chain:", "Wire several sliders in series to control a compound "
47
+ "value - 4 sliders -> a crop box, 2 -> an x/y point. "
48
+ "Leave Send on Change OFF for this - the frames drive it."),
49
+ ("As a source:", "Turn Send on Change ON and leave the input unwired to "
50
+ "drive a downstream value purely by dragging."),
51
+ ("Live tuning:", "Feed a steady message stream (e.g. a camera) and the "
52
+ "downstream result updates as you drag."),
53
+ )
54
+
55
+
56
+ class ControlSliderNode(BaseNode):
57
+ """Stamps an interactive slider's value onto a message path, then forwards."""
58
+
59
+ display_name = 'Slider'
60
+ icon = '🎚'
61
+ category = 'function'
62
+ color = '#D8C7ED'
63
+ border_color = '#A98FC9'
64
+ text_color = '#000000'
65
+ input_count = 1
66
+ output_count = 1
67
+ info = str(_info)
68
+
69
+ ui_component = 'slider'
70
+
71
+ # UI-triggerable action: the on-card slider POSTs its live value here.
72
+ actions = ['set_value']
73
+
74
+ DEFAULT_CONFIG = {
75
+ 'path': 'msg.payload',
76
+ 'min': 0,
77
+ 'max': 1,
78
+ 'step': 0.01,
79
+ 'value': 0.5,
80
+ 'label': '',
81
+ 'send_on_change': False,
82
+ # A control shouldn't silently drop frames while tuning a pipeline.
83
+ MessageKeys.DROP_MESSAGES: False,
84
+ }
85
+
86
+ properties = [
87
+ {
88
+ 'name': 'path',
89
+ 'label': 'Target Path',
90
+ 'type': 'text',
91
+ 'default': DEFAULT_CONFIG['path'],
92
+ 'help': "Message path to set, e.g. payload.crop.x or "
93
+ "payload.detections[0].bbox[2]",
94
+ },
95
+ {
96
+ 'name': 'min',
97
+ 'label': 'Min',
98
+ 'type': 'number',
99
+ 'default': DEFAULT_CONFIG['min'],
100
+ },
101
+ {
102
+ 'name': 'max',
103
+ 'label': 'Max',
104
+ 'type': 'number',
105
+ 'default': DEFAULT_CONFIG['max'],
106
+ },
107
+ {
108
+ 'name': 'step',
109
+ 'label': 'Step',
110
+ 'type': 'number',
111
+ 'default': DEFAULT_CONFIG['step'],
112
+ },
113
+ {
114
+ 'name': 'value',
115
+ 'label': 'Value',
116
+ 'type': 'number',
117
+ 'default': DEFAULT_CONFIG['value'],
118
+ },
119
+ {
120
+ 'name': 'label',
121
+ 'label': 'Label',
122
+ 'type': 'text',
123
+ 'default': DEFAULT_CONFIG['label'],
124
+ 'help': 'Optional caption shown on the node card',
125
+ },
126
+ {
127
+ 'name': 'send_on_change',
128
+ 'label': 'Send on Change',
129
+ 'type': 'checkbox',
130
+ 'default': DEFAULT_CONFIG['send_on_change'],
131
+ 'help': 'Emit a message every time the slider moves, even with no '
132
+ 'input wired (use as a live source). Redeploy to apply.',
133
+ },
134
+ ]
135
+
136
+ def __init__(self, node_id=None, name="slider"):
137
+ super().__init__(node_id, name)
138
+ # Live value set from the UI; None until set, then falls back to config.
139
+ self._live_value = None
140
+
141
+ def _coerce(self, raw: Any) -> Any:
142
+ """Parse to a number, clamp to [min, max], and pick int vs float.
143
+
144
+ A whole-number result is returned as ``int`` so paths like a bbox
145
+ pixel come out as ``200`` rather than ``200.0``; fractional values
146
+ stay ``float``.
147
+ """
148
+ try:
149
+ val = float(raw)
150
+ except (TypeError, ValueError):
151
+ val = self.get_config_float('value', float(self.DEFAULT_CONFIG['value']))
152
+
153
+ lo = self.get_config_float('min', float(self.DEFAULT_CONFIG['min']))
154
+ hi = self.get_config_float('max', float(self.DEFAULT_CONFIG['max']))
155
+ if lo > hi:
156
+ lo, hi = hi, lo
157
+ val = max(lo, min(hi, val))
158
+
159
+ return int(val) if val.is_integer() else val
160
+
161
+ def _active_value(self) -> Any:
162
+ """The value to stamp: the live UI value if set, else the config value."""
163
+ raw = self._live_value if self._live_value is not None else self.config.get('value', 0)
164
+ return self._coerce(raw)
165
+
166
+ def set_value(self, value: Any):
167
+ """UI action: update the live slider value.
168
+
169
+ The new value is applied to the next incoming message. When
170
+ ``send_on_change`` is enabled it is ALSO emitted immediately as a
171
+ fresh message, so dragging drives downstream nodes with no input
172
+ wired. Called on the deployed node from the Flask action thread -
173
+ the same place InjectNode.inject() sends from, so ``send()`` here is
174
+ safe.
175
+ """
176
+ self._live_value = self._coerce(value)
177
+ if self.get_config_bool('send_on_change', False):
178
+ self._emit_value()
179
+
180
+ def _emit_value(self):
181
+ """Emit a fresh message carrying the current value at the target path."""
182
+ msg = self.create_message()
183
+ path = str(self.config.get('path', '')).strip()
184
+ try:
185
+ if path:
186
+ self._set_nested_value(msg, path, self._active_value())
187
+ else:
188
+ # No path: the value is the payload.
189
+ msg[MessageKeys.PAYLOAD] = self._active_value()
190
+ except Exception as e:
191
+ self.report_error(f"Slider: could not set '{path}': {e}")
192
+ return
193
+ self.send(msg)
194
+
195
+ def on_start(self):
196
+ """Seed the live value from config so it is active right after deploy."""
197
+ super().on_start()
198
+ self._live_value = self._coerce(self.config.get('value', 0))
199
+
200
+ def on_input(self, msg: Dict[str, Any], input_index: int = 0):
201
+ """Write the current value to the target path and forward the message."""
202
+ path = str(self.config.get('path', '')).strip()
203
+ if path:
204
+ try:
205
+ self._set_nested_value(msg, path, self._active_value())
206
+ except Exception as e:
207
+ self.report_error(f"Slider: could not set '{path}': {e}")
208
+ self.send(msg)