Skip to content

refactor(workflow): add Jinja2 renderer abstraction for template transform#3

Open
tomerqodo wants to merge 6 commits intocopilot_full_base_refactorworkflow_add_jinja2_renderer_abstraction_for_template_transform_pr3from
copilot_full_head_refactorworkflow_add_jinja2_renderer_abstraction_for_template_transform_pr3
Open

refactor(workflow): add Jinja2 renderer abstraction for template transform#3
tomerqodo wants to merge 6 commits intocopilot_full_base_refactorworkflow_add_jinja2_renderer_abstraction_for_template_transform_pr3from
copilot_full_head_refactorworkflow_add_jinja2_renderer_abstraction_for_template_transform_pr3

Conversation

@tomerqodo
Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#3

laipz8200 and others added 6 commits January 25, 2026 12:04
…de and threaded it through DifyNodeFactory so TemplateTransform nodes receive the dependency by default, keeping behavior unchanged unless an override is provided. Changes are in `api/core/workflow/nodes/template_transform/template_transform_node.py` and `api/core/workflow/nodes/node_factory.py`.

**Commits**
- chore(workflow): identify TemplateTransform dependency on CodeExecutor
- feat(workflow): add CodeExecutor constructor injection to TemplateTransformNode (defaulting to current behavior)
- feat(workflow): inject CodeExecutor from DifyNodeFactory when creating TemplateTransform nodes

**Tests**
- Not run (not requested)

Next step: run `make lint` and `make type-check` if you want to validate the backend checks.
…Transform to use it, keeping CodeExecutor as the default adapter while preserving current behavior. Updates are in `api/core/workflow/nodes/template_transform/template_renderer.py`, `api/core/workflow/nodes/template_transform/template_transform_node.py`, `api/core/workflow/nodes/node_factory.py`, and `api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py`.

Commit-style summary:
- feat(template-transform): add Jinja2 template renderer abstraction with CodeExecutor adapter
- refactor(template-transform): use renderer in node/factory and update unit test patches

Tests not run (not requested).
…ode creation to return TemplateTransformNode directly for template-transform nodes in `api/core/workflow/nodes/node_factory.py`.

Commit-style summary:
- refactor(template-transform): derive TemplateRenderError from ValueError
- refactor(node-factory): instantiate TemplateTransformNode directly with injected renderer

Tests not run (not requested).
…ts/core/workflow/nodes/template_transform/template_transform_node_spec.py`)

chore(type-check): ran `make type-check` (basedpyright clean, 0 errors)

No errors reported.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings January 25, 2026 22:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the template transform node to introduce a Jinja2 template renderer abstraction layer, separating rendering logic from the node implementation for better testability and maintainability.

Changes:

  • Introduces Jinja2TemplateRenderer Protocol and CodeExecutorJinja2TemplateRenderer adapter class for template rendering
  • Updates TemplateTransformNode to use dependency injection for the template renderer
  • Modifies DifyNodeFactory to initialize and inject template renderer into template transform nodes
  • Updates test suite to mock the new renderer abstraction and adds 23 comprehensive new test cases

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 28 comments.

File Description
api/core/workflow/nodes/template_transform/template_renderer.py New file introducing renderer abstraction with Protocol interface and CodeExecutor adapter implementation
api/core/workflow/nodes/template_transform/template_transform_node.py Refactored to accept injected template renderer and use new error type
api/core/workflow/nodes/node_factory.py Updated to instantiate and inject template renderer into TemplateTransformNode
api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py Updated mocks and added 23 new test cases for comprehensive template rendering scenarios
Comments suppressed due to low confidence (13)

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:66

        node = TemplateTransformNode(
            id="test_node",
            config=basic_node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:81

        node = TemplateTransformNode(
            id="test_node",
            config=basic_node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:93

        node = TemplateTransformNode(
            id="test_node",
            config=basic_node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:112

        node = TemplateTransformNode(
            id="test_node",
            config=node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:158

        node = TemplateTransformNode(
            id="test_node",
            config=basic_node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:187

        node = TemplateTransformNode(
            id="test_node",
            config=node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:210

        node = TemplateTransformNode(
            id="test_node",
            config=basic_node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:234

        node = TemplateTransformNode(
            id="test_node",
            config=basic_node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:278

        node = TemplateTransformNode(
            id="test_node",
            config=node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:324

        node = TemplateTransformNode(
            id="test_node",
            config=node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:364

        node = TemplateTransformNode(
            id="test_node",
            config=node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:394

        node = TemplateTransformNode(
            id="test_node",
            config=node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

api/tests/unit_tests/core/workflow/nodes/template_transform/template_transform_node_spec.py:425

        node = TemplateTransformNode(
            id="test_node",
            config=node_data,
            graph_init_params=graph_init_params,
            graph=mock_graph,
            graph_runtime_state=mock_graph_runtime_state,
        )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

max_string_array_length=dify_config.CODE_MAX_STRING_ARRAY_LENGTH,
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
)
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer(code_executor)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The parameter code_executor is used here instead of self._code_executor. While this may work if code_executor is None (passing None through), it's inconsistent with the pattern used for CodeNode initialization on lines 107-115, where self._code_executor is passed.

For consistency and clarity, this should be self._code_executor to ensure the same code executor instance is used across both CodeNode and TemplateTransformNode.

Suggested change
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer(code_executor)
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer(self._code_executor)

Copilot uses AI. Check for mistakes.
Comment on lines +455 to +456
graph=mock_graph,
graph_runtime_state=mock_graph_runtime_state,
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The test is passing an invalid graph parameter to TemplateTransformNode. The Node base class init (api/core/workflow/nodes/base/node.py:203-209) only accepts id, config, graph_init_params, and graph_runtime_state parameters. The graph parameter doesn't exist in the signature and will cause a TypeError at runtime.

This issue appears throughout all the new test methods. The graph=mock_graph, line should be removed from all TemplateTransformNode instantiations.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +18
class Jinja2TemplateRenderer(Protocol):
"""Render Jinja2 templates for template transform nodes."""

def render_template(self, template: str, variables: Mapping[str, Any]) -> str:
"""Render a Jinja2 template with provided variables."""
raise NotImplementedError
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

This PR introduces a Protocol class, which is a departure from the codebase convention. Throughout the codebase, abstract interfaces are defined using ABC (Abstract Base Class) from the abc module (e.g., api/core/helper/code_executor/template_transformer.py, api/core/helper/code_executor/code_node_provider.py, and many others).

For consistency with the established patterns, consider using ABC instead of Protocol. If Protocol is intentionally chosen for structural subtyping (duck typing), this should be documented, but the current implementation has CodeExecutorJinja2TemplateRenderer explicitly inheriting from the Protocol, which defeats the purpose of structural typing.

Copilot uses AI. Check for mistakes.

rendered = result.get("result")
if rendered is not None and not isinstance(rendered, str):
raise TemplateRenderError("Template render result must be a string.")
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The error message "Template render result must be a string." could be more helpful by including the actual type that was received. This would make debugging easier when this error occurs.

Consider changing to: f"Template render result must be a string, got {type(rendered).__name__}"

Suggested change
raise TemplateRenderError("Template render result must be a string.")
raise TemplateRenderError(
f"Template render result must be a string, got {type(rendered).__name__}"
)

Copilot uses AI. Check for mistakes.
raise TemplateRenderError(str(exc)) from exc

rendered = result.get("result")
if rendered is not None and not isinstance(rendered, str):
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The render_template method can return None when result.get("result") returns None, but the return type annotation indicates it should always return a str. This will cause type checking issues and potential runtime errors when the caller expects a string but receives None.

The condition on line 38 checks if rendered is not None and not a string, but doesn't handle the case where rendered is None. The method should either raise an error when the result is None or handle it explicitly.

Suggested change
if rendered is not None and not isinstance(rendered, str):
if rendered is None:
raise TemplateRenderError("Template render result is missing.")
if not isinstance(rendered, str):

Copilot uses AI. Check for mistakes.
Comment on lines +990 to +996
node = TemplateTransformNode(
id="test_node",
config=node_data,
graph_init_params=graph_init_params,
graph=mock_graph,
graph_runtime_state=mock_graph_runtime_state,
)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Keyword argument 'graph' is not a supported parameter name of TemplateTransformNode.init.

Copilot uses AI. Check for mistakes.
Comment on lines +1020 to +1026
node = TemplateTransformNode(
id="test_node",
config=node_data,
graph_init_params=graph_init_params,
graph=mock_graph,
graph_runtime_state=mock_graph_runtime_state,
)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Keyword argument 'graph' is not a supported parameter name of TemplateTransformNode.init.

Copilot uses AI. Check for mistakes.
Comment on lines +1050 to +1056
node = TemplateTransformNode(
id="test_node",
config=node_data,
graph_init_params=graph_init_params,
graph=mock_graph,
graph_runtime_state=mock_graph_runtime_state,
)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Keyword argument 'graph' is not a supported parameter name of TemplateTransformNode.init.

Copilot uses AI. Check for mistakes.
Comment on lines +1080 to +1086
node = TemplateTransformNode(
id="test_node",
config=node_data,
graph_init_params=graph_init_params,
graph=mock_graph,
graph_runtime_state=mock_graph_runtime_state,
)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Keyword argument 'graph' is not a supported parameter name of TemplateTransformNode.init.

Copilot uses AI. Check for mistakes.
Comment on lines +1110 to +1116
node = TemplateTransformNode(
id="test_node",
config=node_data,
graph_init_params=graph_init_params,
graph=mock_graph,
graph_runtime_state=mock_graph_runtime_state,
)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Keyword argument 'graph' is not a supported parameter name of TemplateTransformNode.init.

Copilot uses AI. Check for mistakes.
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.

3 participants