Skip to content

Commit 3f7cd09

Browse files
committed
refactor: remove render template executor and related functionality
BREAKING CHANGE: removes the RenderTemplate executor and all associated code, including schema validation, template rendering logic, and test workflows that depended on it. this change simplifies the file operation executors by removing the template rendering capability and updates existing workflows to use alternative approaches like Shell commands or CreateFile blocks. the RenderTemplate functionality was replaced with direct shell command execution and content rendering in templates, reducing complexity and removing the dependency on jinja2 templating for these use cases.
1 parent e609adf commit 3f7cd09

14 files changed

Lines changed: 29 additions & 684 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Individual tasks within a workflow. Available block types:
150150
- `Shell` - Run shell commands
151151
- `LLMCall` - Call AI/LLM APIs
152152
- `HttpCall` - Make HTTP requests
153-
- `CreateFile`, `ReadFiles`, `EditFile`, `RenderTemplate` - File operations
153+
- `CreateFile`, `ReadFiles`, `EditFile` - File operations
154154
- `Workflow` - Call other workflows (composition)
155155
- `Prompt` - Interactive user prompts
156156
- `ReadJSONState`, `WriteJSONState`, `MergeJSONState` - State management
@@ -1253,7 +1253,7 @@ workflows-mcp/
12531253
│ ├── engine/ # Workflow execution engine
12541254
│ │ ├── executor_base.py # Base executor class
12551255
│ │ ├── executors_core.py # Shell, Workflow executors
1256-
│ │ ├── executors_file.py # File operation executors (CreateFile, ReadFiles, EditFile, RenderTemplate)
1256+
│ │ ├── executors_file.py # File operation executors (CreateFile, ReadFiles, EditFile)
12571257
│ │ ├── file_outline.py # File outline extraction utilities
12581258
│ │ ├── executors_http.py # HTTP call executor
12591259
│ │ ├── executors_llm.py # LLM call executor

schema.json

Lines changed: 0 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@
108108
"CreateFile",
109109
"EditFile",
110110
"ReadFiles",
111-
"RenderTemplate",
112111
"HttpCall",
113112
"LLMCall",
114113
"Prompt",
@@ -605,86 +604,6 @@
605604
}
606605
}
607606
},
608-
{
609-
"if": {
610-
"properties": {
611-
"type": {
612-
"const": "RenderTemplate"
613-
}
614-
}
615-
},
616-
"then": {
617-
"properties": {
618-
"inputs": {
619-
"additionalProperties": false,
620-
"description": "Inputs for RenderTemplate block",
621-
"properties": {
622-
"template": {
623-
"description": "Jinja2 template string",
624-
"title": "Template",
625-
"type": "string"
626-
},
627-
"variables": {
628-
"additionalProperties": true,
629-
"description": "Variables to substitute in template",
630-
"title": "Variables",
631-
"type": "object"
632-
},
633-
"output_path": {
634-
"anyOf": [
635-
{
636-
"type": "string"
637-
},
638-
{
639-
"type": "null"
640-
}
641-
],
642-
"default": null,
643-
"description": "Optional file path to write rendered content",
644-
"title": "Output Path"
645-
},
646-
"encoding": {
647-
"default": "utf-8",
648-
"description": "Text encoding for output file",
649-
"title": "Encoding",
650-
"type": "string"
651-
},
652-
"overwrite": {
653-
"anyOf": [
654-
{
655-
"type": "boolean"
656-
},
657-
{
658-
"type": "string"
659-
}
660-
],
661-
"default": true,
662-
"description": "Whether to overwrite existing output file (or interpolation string)",
663-
"title": "Overwrite"
664-
},
665-
"create_parents": {
666-
"anyOf": [
667-
{
668-
"type": "boolean"
669-
},
670-
{
671-
"type": "string"
672-
}
673-
],
674-
"default": true,
675-
"description": "Create parent directories for output file (or interpolation string)",
676-
"title": "Create Parents"
677-
}
678-
},
679-
"required": [
680-
"template"
681-
],
682-
"title": "RenderTemplateInput",
683-
"type": "object"
684-
}
685-
}
686-
}
687-
},
688607
{
689608
"if": {
690609
"properties": {
@@ -1702,73 +1621,6 @@
17021621
"title": "ReadFilesInput",
17031622
"type": "object"
17041623
},
1705-
"RenderTemplateInput": {
1706-
"additionalProperties": false,
1707-
"description": "Input model for RenderTemplate executor.",
1708-
"properties": {
1709-
"template": {
1710-
"description": "Jinja2 template string",
1711-
"title": "Template",
1712-
"type": "string"
1713-
},
1714-
"variables": {
1715-
"additionalProperties": true,
1716-
"description": "Variables to substitute in template",
1717-
"title": "Variables",
1718-
"type": "object"
1719-
},
1720-
"output_path": {
1721-
"anyOf": [
1722-
{
1723-
"type": "string"
1724-
},
1725-
{
1726-
"type": "null"
1727-
}
1728-
],
1729-
"default": null,
1730-
"description": "Optional file path to write rendered content",
1731-
"title": "Output Path"
1732-
},
1733-
"encoding": {
1734-
"default": "utf-8",
1735-
"description": "Text encoding for output file",
1736-
"title": "Encoding",
1737-
"type": "string"
1738-
},
1739-
"overwrite": {
1740-
"anyOf": [
1741-
{
1742-
"type": "boolean"
1743-
},
1744-
{
1745-
"type": "string"
1746-
}
1747-
],
1748-
"default": true,
1749-
"description": "Whether to overwrite existing output file (or interpolation string)",
1750-
"title": "Overwrite"
1751-
},
1752-
"create_parents": {
1753-
"anyOf": [
1754-
{
1755-
"type": "boolean"
1756-
},
1757-
{
1758-
"type": "string"
1759-
}
1760-
],
1761-
"default": true,
1762-
"description": "Create parent directories for output file (or interpolation string)",
1763-
"title": "Create Parents"
1764-
}
1765-
},
1766-
"required": [
1767-
"template"
1768-
],
1769-
"title": "RenderTemplateInput",
1770-
"type": "object"
1771-
},
17721624
"HttpCallInput": {
17731625
"additionalProperties": false,
17741626
"description": "Input model for HttpCall executor.\n\nField names match httpx.AsyncClient.request() parameters for consistency\nand to avoid type confusion during parameter passing.",

src/workflows_mcp/engine/__init__.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,6 @@
6363
EditFileInput,
6464
EditFileOutput,
6565
EditOperation,
66-
RenderTemplateExecutor,
67-
RenderTemplateInput,
68-
RenderTemplateOutput,
6966
)
7067
from .executors_http import (
7168
HttpCallExecutor,
@@ -132,9 +129,6 @@
132129
"EditFileInput",
133130
"EditFileOutput",
134131
"EditOperation",
135-
"RenderTemplateExecutor",
136-
"RenderTemplateInput",
137-
"RenderTemplateOutput",
138132
# HTTP Executors
139133
"HttpCallExecutor",
140134
"HttpCallInput",

src/workflows_mcp/engine/executor_base.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,6 @@ def test_workflow():
630630
CreateFileExecutor,
631631
EditFileExecutor,
632632
ReadFilesExecutor,
633-
RenderTemplateExecutor,
634633
)
635634
from .executors_http import HttpCallExecutor
636635
from .executors_interactive import PromptExecutor
@@ -652,7 +651,6 @@ def test_workflow():
652651
registry.register(CreateFileExecutor())
653652
registry.register(EditFileExecutor())
654653
registry.register(ReadFilesExecutor())
655-
registry.register(RenderTemplateExecutor())
656654

657655
# Register HTTP executor
658656
registry.register(HttpCallExecutor())

src/workflows_mcp/engine/executors_file.py

Lines changed: 1 addition & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""File operation executors - CreateFile, ReadFiles, RenderTemplate, EditFile.
1+
"""File operation executors - CreateFile, ReadFiles, EditFile.
22
33
Architecture:
44
- Execute returns output directly (no Result wrapper)
@@ -15,8 +15,6 @@
1515
from typing import Any, ClassVar, Literal
1616

1717
import yaml
18-
from jinja2 import StrictUndefined
19-
from jinja2.sandbox import SandboxedEnvironment
2018
from pydantic import BaseModel, Field, computed_field, field_validator
2119

2220
from .block import BlockInput, BlockOutput
@@ -547,150 +545,6 @@ async def execute( # type: ignore[override]
547545
)
548546

549547

550-
# ============================================================================
551-
# RenderTemplate Executor
552-
# ============================================================================
553-
554-
555-
class RenderTemplateInput(BlockInput):
556-
"""Input model for RenderTemplate executor."""
557-
558-
template: str = Field(description="Jinja2 template string")
559-
variables: dict[str, Any] = Field(
560-
default_factory=dict,
561-
description="Variables to substitute in template",
562-
)
563-
output_path: str | None = Field(
564-
default=None,
565-
description="Optional file path to write rendered content",
566-
)
567-
encoding: str = Field(default="utf-8", description="Text encoding for output file")
568-
overwrite: bool | str = Field(
569-
default=True,
570-
description="Whether to overwrite existing output file (or interpolation string)",
571-
)
572-
create_parents: bool | str = Field(
573-
default=True,
574-
description="Create parent directories for output file (or interpolation string)",
575-
)
576-
577-
# Validators for boolean fields with interpolation support
578-
_validate_overwrite = field_validator("overwrite", mode="before")(
579-
interpolatable_boolean_validator()
580-
)
581-
_validate_create_parents = field_validator("create_parents", mode="before")(
582-
interpolatable_boolean_validator()
583-
)
584-
585-
586-
class RenderTemplateOutput(BlockOutput):
587-
"""Output model for RenderTemplate executor.
588-
589-
All fields have defaults to support graceful degradation when template rendering fails.
590-
A default-constructed instance represents a failed/crashed rendering operation.
591-
"""
592-
593-
content: str = Field(
594-
default="",
595-
description="Rendered template content (empty string if failed)",
596-
)
597-
output_path: str | None = Field(
598-
default=None,
599-
description="Absolute path to output file (None if not specified or failed)",
600-
)
601-
size_bytes: int | None = Field(
602-
default=None,
603-
description="Output file size in bytes (None if not written or failed)",
604-
)
605-
606-
607-
class RenderTemplateExecutor(BlockExecutor):
608-
"""
609-
Jinja2 template rendering executor.
610-
611-
Architecture (ADR-006):
612-
- Returns RenderTemplateOutput directly
613-
- Raises TemplateSyntaxError, UndefinedError for template issues
614-
- Raises exceptions for file write failures
615-
"""
616-
617-
type_name: ClassVar[str] = "RenderTemplate"
618-
input_type: ClassVar[type[BlockInput]] = RenderTemplateInput
619-
output_type: ClassVar[type[BlockOutput]] = RenderTemplateOutput
620-
621-
security_level: ClassVar[ExecutorSecurityLevel] = ExecutorSecurityLevel.TRUSTED
622-
capabilities: ClassVar[ExecutorCapabilities] = ExecutorCapabilities(
623-
can_read_files=True,
624-
can_write_files=True,
625-
)
626-
627-
async def execute( # type: ignore[override]
628-
self, inputs: RenderTemplateInput, context: Execution
629-
) -> RenderTemplateOutput:
630-
"""RenderTemplate Jinja2 template.
631-
632-
Returns:
633-
RenderTemplateOutput with rendered content and optional file path
634-
635-
Raises:
636-
TemplateSyntaxError: Invalid template syntax
637-
UndefinedError: Undefined variable in template
638-
ValueError: Invalid output path
639-
FileExistsError: Output file exists and overwrite=False
640-
Exception: Other errors
641-
"""
642-
# Resolve interpolatable fields to their actual types
643-
overwrite = resolve_interpolatable_boolean(inputs.overwrite, "overwrite")
644-
create_parents = resolve_interpolatable_boolean(inputs.create_parents, "create_parents")
645-
646-
# RenderTemplate template (exceptions bubble up)
647-
env = SandboxedEnvironment(undefined=StrictUndefined, autoescape=False)
648-
template = env.from_string(inputs.template)
649-
rendered = template.render(**inputs.variables)
650-
651-
# Write to file if output_path specified
652-
output_path_str: str | None = None
653-
size_bytes: int | None = None
654-
655-
if inputs.output_path:
656-
# Resolve path
657-
path_result = PathResolver.resolve_and_validate(
658-
inputs.output_path, allow_traversal=True
659-
)
660-
if not path_result.is_success:
661-
raise ValueError(f"Invalid output_path: {path_result.error}")
662-
663-
# Type narrowing: is_success guarantees value is not None
664-
assert path_result.value is not None
665-
file_path = path_result.value
666-
667-
# Check overwrite protection
668-
if file_path.exists() and not overwrite:
669-
raise FileExistsError(f"Output file exists and overwrite=False: {file_path}")
670-
671-
# Write file using utility
672-
write_result = FileOperations.write_text(
673-
path=file_path,
674-
content=rendered,
675-
encoding=inputs.encoding,
676-
mode=None,
677-
create_parents=create_parents,
678-
)
679-
680-
if not write_result.is_success:
681-
raise OSError(write_result.error)
682-
683-
output_path_str = str(file_path)
684-
size_bytes = write_result.value
685-
686-
# Build output
687-
return RenderTemplateOutput(
688-
content=rendered,
689-
output_path=output_path_str,
690-
size_bytes=size_bytes,
691-
)
692-
693-
694548
# ============================================================================
695549
# EditFile Executor
696550
# ============================================================================

0 commit comments

Comments
 (0)