Skip to content

Remove implicit node instantiation in Python#1763

Closed
aljazkonec1 wants to merge 1 commit into
developfrom
feat/remove_implicit_node_creation
Closed

Remove implicit node instantiation in Python#1763
aljazkonec1 wants to merge 1 commit into
developfrom
feat/remove_implicit_node_creation

Conversation

@aljazkonec1

@aljazkonec1 aljazkonec1 commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

Purpose

This PR removes the following deprecated feature:

with dai.Pipeline as pipeline:
    myNode = Node()

The only exception is (Threaded)HostNodes as they might require implicit pipeline in their init() methods as in the case of ParsingNeuralNetwork:

class ParsingNeuralNetwork(dai.node.ThreadedHostNode):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs) # Calls ThreadedHostNode.__init__(), which adds this node to the pipeline making getParentPipeline() available
        self._pipeline = self.getParentPipeline()
        self._nn = self._pipeline.create(dai.node.NeuralNetwork)
        self._parsers: Dict[int, BaseParser] = {}

Specification

None / not applicable

Dependencies & Potential Impact

None / not applicable

Deployment Plan

None / not applicable

Testing & Validation

Tested by running a couple of oak-examples.

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced error handling in node creation with more informative error messages and improved resource cleanup.
  • API Changes

    • Simplified constructors for ColorCamera, DetectionNetwork, StereoDepth, and VideoEncoder nodes. Users now initialize these nodes without arguments and configure them explicitly using dedicated methods.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Python bindings for node types are refactored to remove implicit pipeline creation from constructors. Constructors now either accept a device parameter, are parameterless, or no-argument variants, with explicit build() methods handling node configuration. Exception handling in PipelineBindings.cpp is tightened to catch and wrap std::runtime_error exceptions with cleanup logic.

Changes

Cohort / File(s) Summary
Exception Handling Enhancement
bindings/python/src/pipeline/PipelineBindings.cpp
Replaced blanket catch(...) with specific catch(std::runtime_error& e) handler that performs resource cleanup and wraps exceptions with "Error creating node: " prefix.
Node Binding Refactoring — Constructor Simplification
bindings/python/src/pipeline/node/ColorCameraBindings.cpp, bindings/python/src/pipeline/node/StereoDepthBindings.cpp, bindings/python/src/pipeline/node/VideoEncoderBindings.cpp
Removed custom constructors that implicitly created nodes via implicit pipeline and performed auto-build. Replaced with parameterless py::init<>() constructors; explicit build() method calls now handle node configuration.
Node Binding Refactoring — Device-Based Initialization
bindings/python/src/pipeline/node/DetectionNetworkBindings.cpp
Removed constructor overload that implicitly built nodes via implicit pipeline. Added new overload accepting std::shared_ptr<Device> parameter, shifting initialization responsibility from constructor to explicit methods.
Header Update
include/depthai/pipeline/node/DetectionNetwork.hpp
Added explicit keyword to DetectionNetwork constructor to prevent implicit type conversion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Constructor calls simplified, bindings refine,
Implicit pipelines fade—now explicit design!
Build() methods rise up, handling setup with care,
Error handling wrapped, cleanup shows we care!
Rustling whiskers of approval

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately describes the main objective of the pull request—removing implicit node instantiation in Python bindings while preserving the feature for HostNode creation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/remove_implicit_node_creation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
bindings/python/src/pipeline/PipelineBindings.cpp (1)

292-304: ⚠️ Potential issue | 🔴 Critical

Narrowed catch leaks thread-local pipeline state on non-runtime_error exceptions.

Replacing catch(...) with catch(std::runtime_error&) means exceptions that don't inherit from std::runtime_error skip the cleanup calls, leaking TLS state:

  • py::error_already_set (Python exceptions from __init__ or subclass code) — does NOT inherit from std::runtime_error; it's a separate pybind11 exception hierarchy.
  • py::cast_error (cast failures in py::cast<std::shared_ptr<node::ThreadedHostNode>>(...)) — also separate from std::runtime_error.
  • Standard exceptions like std::invalid_argument, std::bad_alloc, etc.

Any of these will skip delCreatingNodeFromPipelineCreate() and delImplicitPipeline(), corrupting the implicit-pipeline TLS and "creating node" flag for all subsequent Pipeline::create() calls in the same thread.

Catch std::exception (or reinstate catch(...) with rethrow) to ensure cleanup always runs. Even better, wrap the two TLS flags in a small RAII guard to make this leak-proof on any future refactor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/python/src/pipeline/PipelineBindings.cpp` around lines 292 - 304,
The catch block around py::cast and class_ currently only handles
std::runtime_error, which allows other exceptions (py::error_already_set,
py::cast_error, std::bad_alloc, etc.) to skip calling
delCreatingNodeFromPipelineCreate() and delImplicitPipeline(), leaking TLS
state; replace the narrow catch with either a catch(const std::exception&) (or
reinstate catch(...) and rethrow) so cleanup always runs, or better yet
introduce a small RAII guard object around setImplicitPipeline(&p) /
setCreatingNodeFromPipelineCreate() that calls
delCreatingNodeFromPipelineCreate() and delImplicitPipeline() in its destructor
to guarantee cleanup on all exception paths (references: setImplicitPipeline,
setCreatingNodeFromPipelineCreate, delCreatingNodeFromPipelineCreate,
delImplicitPipeline, py::cast, class_).
bindings/python/src/pipeline/node/StereoDepthBindings.cpp (1)

86-99: 🧹 Nitpick | 🔵 Trivial

Document that StereoDepth.build(autoCreateCameras=True) requires the node to already belong to a pipeline.

The C++ implementation correctly uses getParentPipeline() (which throws "Pipeline is null" if the parent is null) to create Camera nodes. However, the Python bindings do not clearly document that calling build(autoCreateCameras=True) on an unattached node (e.g., dai.node.StereoDepth() created directly instead of via pipeline.create(StereoDepth)) will raise a runtime exception. The C++ header mentions "this API is global" but should explicitly clarify the pipeline attachment requirement in both C++ and Python documentation to prevent user confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/python/src/pipeline/node/StereoDepthBindings.cpp` around lines 86 -
99, The Python binding for StereoDepth.build does not warn that calling
build(autoCreateCameras=True) requires the StereoDepth node to already belong to
a Pipeline and will raise a runtime error via getParentPipeline() if it is
unattached; update the binding docstring/documentation for StereoDepth.build
(both overloads exposed in StereoDepthBindings.cpp) to explicitly state that
autoCreateCameras=True requires the node to be created via pipeline.create(...)
or otherwise attached to a Pipeline (and that getParentPipeline() will throw
"Pipeline is null" if not), so users know to attach the node before calling
build with autoCreateCameras enabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bindings/python/src/pipeline/node/ColorCameraBindings.cpp`:
- Line 94: The binding for ColorCamera uses py::init() which is inconsistent
with the rest of the PR; update the constructor registration in
ColorCameraBindings.cpp by replacing the call to colorCamera.def(py::init())
with the angle-bracket form colorCamera.def(py::init<>()) so it matches other
bindings like VideoEncoderBindings.cpp and StereoDepthBindings.cpp.

In `@bindings/python/src/pipeline/node/VideoEncoderBindings.cpp`:
- Line 77: The binding uses py::init<>() but VideoEncoder inherits only
protected constructors from DeviceNodeCRTP so the default constructor is not
public; either add a public default constructor to VideoEncoder (make
VideoEncoder() public and call the base protected constructors as needed) or
replace py::init<>() with a factory-based initializer in the pybind binding
(wrap creation in a lambda/factory that returns a new VideoEncoder or
unique_ptr<VideoEncoder>), referencing VideoEncoder, DeviceNodeCRTP and
py::init<>() so the binding compiles without needing a public base constructor.

---

Outside diff comments:
In `@bindings/python/src/pipeline/node/StereoDepthBindings.cpp`:
- Around line 86-99: The Python binding for StereoDepth.build does not warn that
calling build(autoCreateCameras=True) requires the StereoDepth node to already
belong to a Pipeline and will raise a runtime error via getParentPipeline() if
it is unattached; update the binding docstring/documentation for
StereoDepth.build (both overloads exposed in StereoDepthBindings.cpp) to
explicitly state that autoCreateCameras=True requires the node to be created via
pipeline.create(...) or otherwise attached to a Pipeline (and that
getParentPipeline() will throw "Pipeline is null" if not), so users know to
attach the node before calling build with autoCreateCameras enabled.

In `@bindings/python/src/pipeline/PipelineBindings.cpp`:
- Around line 292-304: The catch block around py::cast and class_ currently only
handles std::runtime_error, which allows other exceptions
(py::error_already_set, py::cast_error, std::bad_alloc, etc.) to skip calling
delCreatingNodeFromPipelineCreate() and delImplicitPipeline(), leaking TLS
state; replace the narrow catch with either a catch(const std::exception&) (or
reinstate catch(...) and rethrow) so cleanup always runs, or better yet
introduce a small RAII guard object around setImplicitPipeline(&p) /
setCreatingNodeFromPipelineCreate() that calls
delCreatingNodeFromPipelineCreate() and delImplicitPipeline() in its destructor
to guarantee cleanup on all exception paths (references: setImplicitPipeline,
setCreatingNodeFromPipelineCreate, delCreatingNodeFromPipelineCreate,
delImplicitPipeline, py::cast, class_).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a24d33c8-af93-4407-b806-1a3998ce47d8

📥 Commits

Reviewing files that changed from the base of the PR and between ad1061f and 567babb.

📒 Files selected for processing (6)
  • bindings/python/src/pipeline/PipelineBindings.cpp
  • bindings/python/src/pipeline/node/ColorCameraBindings.cpp
  • bindings/python/src/pipeline/node/DetectionNetworkBindings.cpp
  • bindings/python/src/pipeline/node/StereoDepthBindings.cpp
  • bindings/python/src/pipeline/node/VideoEncoderBindings.cpp
  • include/depthai/pipeline/node/DetectionNetwork.hpp
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-03-16T11:17:12.819Z
Learnt from: pheec
Repo: luxonis/depthai-core PR: 1715
File: include/depthai/pipeline/node/PointCloud.hpp:34-98
Timestamp: 2026-03-16T11:17:12.819Z
Learning: In `include/depthai/pipeline/node/PointCloud.hpp`, the nested `Impl` class inside `PointCloud` is intentionally public (not private/forward-declared) because the on-host unit tests in `tests/src/onhost_tests/point_cloud_test.cpp` directly access `Impl` methods such as `setIntrinsics`, `setExtrinsics`, `useCPU`, `useCPUMT`, `computePointCloudDense`, `applyTransformation`, and `filterValidPoints`. Do not suggest making `Impl` private or forward-declaring it.

Applied to files:

  • bindings/python/src/pipeline/PipelineBindings.cpp
📚 Learning: 2026-03-24T22:39:00.650Z
Learnt from: MaticTonin
Repo: luxonis/depthai-core PR: 1732
File: src/pipeline/Pipeline.cpp:662-662
Timestamp: 2026-03-24T22:39:00.650Z
Learning: In `src/pipeline/Pipeline.cpp`, the `DEPTHAI_AUTOCALIBRATION` environment variable intentionally defaults to `"ON_START"` (not `""`). This means auto-calibration is initialized by default for stereo pipelines at pipeline build time. The resulting implicit EEPROM/calibration writes on startup are by design and should not be flagged as a backward-incompatible change or unintended side-effect in future reviews.

Applied to files:

  • bindings/python/src/pipeline/node/StereoDepthBindings.cpp
📚 Learning: 2026-04-16T01:04:34.569Z
Learnt from: MaticTonin
Repo: luxonis/depthai-core PR: 1753
File: src/pipeline/Pipeline.cpp:834-839
Timestamp: 2026-04-16T01:04:34.569Z
Learning: In `src/pipeline/Pipeline.cpp` (`PipelineImpl::build()`), when `autoCalibrationModeSetByApi` is false, `DEPTHAI_AUTOCALIBRATION` is read with an empty-string default (`""`). If the env var is unset or explicitly empty, `autoCalibrationMode` falls back to the pipeline-configured default (`ON_START`). This means an explicitly empty env var no longer disables auto-calibration — it simply defers to the API/default mode. This behavior is intentional and should not be flagged in future reviews.

Applied to files:

  • bindings/python/src/pipeline/node/StereoDepthBindings.cpp
🔇 Additional comments (2)
include/depthai/pipeline/node/DetectionNetwork.hpp (1)

23-23: LGTM — explicit on single-arg constructor.

Good practice; prevents unintended implicit conversions from std::shared_ptr<Device> to DetectionNetwork. Consistent with the PR intent to require explicit construction.

bindings/python/src/pipeline/node/DetectionNetworkBindings.cpp (1)

129-129: Remove the py::init<Device> binding from DetectionNetwork.

The binding violates the PR's design principle. The commit message explicitly states implicit node creation should be removed except for HostNodes. DetectionNetwork is a DeviceNodeGroup, not a HostNode. Other DeviceNodeGroup nodes (ToF, SpatialDetectionNetwork) lack this binding, making DetectionNetwork an inconsistent exception. Direct construction via dai.node.DetectionNetwork(device) would bypass pipeline registration, defeating the new design. Remove the binding at line 129 and rely solely on pipeline.create(DetectionNetwork).

			> Likely an incorrect or invalid review comment.

auto camera = getImplicitPipeline()->create<ColorCamera>();
return camera;
}))
colorCamera.def(py::init())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Nit: style consistency — prefer py::init<>().

Other bindings in this PR (VideoEncoderBindings.cpp, StereoDepthBindings.cpp) use py::init<>() with empty angle brackets. Using py::init() here is functionally equivalent in pybind11 but inconsistent with the rest of the PR.

🎨 Proposed nit
-    colorCamera.def(py::init())
+    colorCamera.def(py::init<>())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
colorCamera.def(py::init())
colorCamera.def(py::init<>())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/python/src/pipeline/node/ColorCameraBindings.cpp` at line 94, The
binding for ColorCamera uses py::init() which is inconsistent with the rest of
the PR; update the constructor registration in ColorCameraBindings.cpp by
replacing the call to colorCamera.def(py::init()) with the angle-bracket form
colorCamera.def(py::init<>()) so it matches other bindings like
VideoEncoderBindings.cpp and StereoDepthBindings.cpp.

}),
VIDEO_ENCODER_BUILD_PYARGS,
VIDEO_ENCODER_PYARGS)
.def(py::init<>())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm VideoEncoder has an accessible default constructor.
ast-grep --pattern $'class VideoEncoder $_ {
  $$$
  VideoEncoder();
  $$$
}'
rg -nP -C2 '\bVideoEncoder\s*\(\s*\)\s*(;|\{|=)' include/ src/

Repository: luxonis/depthai-core

Length of output: 46


🏁 Script executed:

# Find VideoEncoder class definition
fd -type f -name "*.hpp" -o -name "*.h" | xargs rg -l "class VideoEncoder"

# Locate and read VideoEncoderBindings.cpp context
fd -type f -name "VideoEncoderBindings.cpp"

Repository: luxonis/depthai-core

Length of output: 469


🏁 Script executed:

# Search more broadly for VideoEncoder in header files
rg "class VideoEncoder" -A 10 --max-count=3

Repository: luxonis/depthai-core

Length of output: 1056


🏁 Script executed:

# Check the binding file to understand full context
wc -l bindings/python/src/pipeline/node/VideoEncoderBindings.cpp

Repository: luxonis/depthai-core

Length of output: 126


🏁 Script executed:

# Read the full VideoEncoderBindings.cpp file to see context
cat -n bindings/python/src/pipeline/node/VideoEncoderBindings.cpp

Repository: luxonis/depthai-core

Length of output: 8973


🏁 Script executed:

# Read the VideoEncoder class definition to understand constructors
cat -n include/depthai/pipeline/node/VideoEncoder.hpp

Repository: luxonis/depthai-core

Length of output: 5058


🏁 Script executed:

# Check the base class DeviceNodeCRTP to understand inherited constructors
rg "class DeviceNodeCRTP" -A 30 include/

Repository: luxonis/depthai-core

Length of output: 2542


🏁 Script executed:

# Check the actual constructor definitions in DeviceNodeCRTP
rg "DeviceNodeCRTP\s*\(" include/depthai/pipeline/DeviceNode.hpp -A 3 -B 3

Repository: luxonis/depthai-core

Length of output: 1237


🏁 Script executed:

# Also check what ADD_NODE macro does
rg "define ADD_NODE" bindings/python/src/ -A 5

Repository: luxonis/depthai-core

Length of output: 1091


🏁 Script executed:

# Check if there's a default constructor template in DeviceNodeCRTP
cat -n include/depthai/pipeline/DeviceNode.hpp | head -80

Repository: luxonis/depthai-core

Length of output: 2800


🏁 Script executed:

# Find the addNode template implementation
rg "template.*addNode" bindings/python/src/ -A 20

Repository: luxonis/depthai-core

Length of output: 46


🏁 Script executed:

# Look at NodeBindings.hpp to see how ADD_NODE works
cat -n bindings/python/src/pipeline/node/NodeBindings.hpp | head -100

Repository: luxonis/depthai-core

Length of output: 1125


🏁 Script executed:

# Look for the addNode function implementation
find bindings/python/src -name "*.hpp" -o -name "*.cpp" | xargs rg -l "addNode"

Repository: luxonis/depthai-core

Length of output: 108


🏁 Script executed:

# Check the Common.hpp file which defines ADD_NODE
cat -n bindings/python/src/pipeline/node/Common.hpp | head -150

Repository: luxonis/depthai-core

Length of output: 3066


Line 77 will fail to compile: py::init<>() cannot access the protected default constructor.

VideoEncoder inherits constructors from DeviceNodeCRTP via using DeviceNodeCRTP::DeviceNodeCRTP;, but all constructors in DeviceNodeCRTP are protected, not public. Pybind11's py::init<>() requires a public constructor to expose in Python. This binding will result in a C++ compilation error.

Either provide a public default constructor in VideoEncoder or use a custom factory-based initialization pattern (e.g., a lambda that wraps the pipeline creation logic).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bindings/python/src/pipeline/node/VideoEncoderBindings.cpp` at line 77, The
binding uses py::init<>() but VideoEncoder inherits only protected constructors
from DeviceNodeCRTP so the default constructor is not public; either add a
public default constructor to VideoEncoder (make VideoEncoder() public and call
the base protected constructors as needed) or replace py::init<>() with a
factory-based initializer in the pybind binding (wrap creation in a
lambda/factory that returns a new VideoEncoder or unique_ptr<VideoEncoder>),
referencing VideoEncoder, DeviceNodeCRTP and py::init<>() so the binding
compiles without needing a public base constructor.

@aljazkonec1 aljazkonec1 changed the title Remove implicit node creation. Keep it avaliable for HostNodeCreation. Remove implicit node instantiaion Apr 21, 2026
@aljazkonec1 aljazkonec1 changed the title Remove implicit node instantiaion Remove implicit node instantiaion in Python Apr 21, 2026
@aljazkonec1 aljazkonec1 changed the title Remove implicit node instantiaion in Python Remove implicit node instantiation in Python Apr 22, 2026
@aljazkonec1

Copy link
Copy Markdown
Collaborator Author

The ImplicitPipeline mechanism was not removed due to change in behavior in certain depthai-nodes where the pipeline is needed at initializaiton. If we fully remove the mechanism we need to either compensate it with another mechanism that gets the pipeline or update depthai-nodes and oak-examples (+ there is a posibility this would break user nodes).

@MaticTonin

Copy link
Copy Markdown
Collaborator

@aljazkonec1 based on comment above, should we just close this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants