fix: resolve optional inputs without defaults to None (fixes #47)#49
Conversation
Optional inputs declared with `required: false` but no `default` value
now resolve to `None` instead of failing with "Unresolved variables".
Changes:
- workflow_validator.py: Add None to context for optional inputs without defaults
- node_wrapper.py: Filter error messages to only show actually unresolved variables
This allows templates like ${optional_param} to work when the optional
input is not provided, resolving to null in the output.
Fixes #47
Addresses code review feedback on PR #49: - Add test for nested path access on None-valued optional input (verifies ${optional_config.field} fails gracefully when config is None) - Add test for nested path access when optional input IS provided (verifies normal case still works) - Enhance comment in workflow_validator.py explaining the design decision and noting that nested access on None will still fail (intentionally) This documents the expected behavior: optional inputs resolve to None, but attempting to traverse into None (e.g., ${config.api_key}) correctly fails with a clear error message.
Code ReviewSummaryThis PR fixes a legitimate bug where optional workflow inputs without explicit defaults fail template resolution. The fix is sound: optional inputs now resolve to Critical — must fix before merge1. Missing edge case test: nested path access on None-valued optional inputsIssue: When an optional input without a default resolves to Current behavior (based on existing test
What happens with this PR:
Required action: Add test case to def test_nested_path_on_none_optional_input(self):
"""Test that nested path access on None-valued optional input fails gracefully."""
workflow_ir = {
"ir_version": "0.1.0",
"nodes": [{
"id": "test",
"type": "shell",
"params": {
"stdin": "${optional_param.nested_field}" # optional_param will be None
}
}],
"edges": [],
"inputs": {
"optional_param": {
"type": "object",
"required": False
# No default - will resolve to None
}
}
}
registry = Registry()
# This should either:
# 1. Fail validation with clear error ("Cannot access .nested_field on None")
# 2. Resolve to None (if that's the desired behavior)
#
# Document which behavior is expected and test for it
with pytest.raises(ValueError, match="optional_param"):
compile_ir_to_flow(workflow_ir, registry, initial_params={})Why this matters: This is a real-world scenario where users might template Warnings — should be addressed2. Potential confusion with type declarationsIssue: The tests use "optional_param": {
"type": "string", # Says it's a string...
"required": False,
# No default specified - will resolve to None # ...but will be None
}Question: Should optional inputs without defaults have type validation relaxed to allow Current behavior: Type is declared as Suggestion:
This isn't a blocker, but could prevent user confusion down the line. 3. Error message filtering logic could have false negativesLocation: variables = {v for v in all_variables if not TemplateResolver.variable_exists(v, context)}Issue: This filters out variables that "exist" in context, but what if a variable exists but path traversal fails? Example: context = {"data": {"nested": None}}
template = "${data.nested.field}" # data exists, data.nested exists, but .field failsCurrent code:
Actually, this looks fine! Suggestions — optional improvements4. Test organizationObservation:
Suggestion: Consider renaming the file or splitting it:
Not urgent, but improves discoverability as the codebase grows. 5. Documentation clarityLocation: # Optional with no default key - use None so templates can resolve
# This allows ${optional_param} to resolve to None instead of failingSuggestion: Enhance comment to explain the semantic decision: # Optional inputs without explicit default resolve to None.
# Rationale: "required: false" means "can be omitted", not "must have explicit default".
# This allows templates like ${optional_param} to resolve (to None) rather than fail validation.
# Nodes can then use None to apply their own smart defaults or skip optional behavior.This helps future maintainers understand why this design choice was made. 6. Consider adding integration testCurrent tests: All tests use Suggestion: Add one end-to-end test that actually executes a workflow with optional input = None to verify runtime behavior: def test_execute_workflow_with_none_optional_input(self):
"""Integration test: workflow execution with None optional input succeeds."""
workflow_ir = {
"ir_version": "0.1.0",
"nodes": [{
"id": "echo",
"type": "shell",
"params": {
"command": "echo",
"stdin": "${optional_message}"
}
}],
"edges": [],
"inputs": {
"optional_message": {
"type": "string",
"required": False
}
}
}
# Execute workflow without providing optional input
registry = Registry()
flow = compile_ir_to_flow(workflow_ir, registry, initial_params={})
shared = {}
result = flow.run(shared) # Should execute successfully with stdin=None
# Verify execution completed (specific assertion depends on shell node behavior with None stdin)This would catch issues where compilation succeeds but runtime fails. Security Considerations✅ No security concerns identified The changes correctly handle None values and don't introduce injection risks or expose sensitive data. Test Coverage AssessmentTest quality: ✅ Excellent The PR adds:
Test quantity: ✅ Appropriate 77 new lines in Missing coverage: See Critical #1 (nested path access edge case) Performance Considerations✅ No performance concerns The filtering in Code QualityStrengths
Style adherence
Final RecommendationStatus: This is a solid bug fix that addresses a real usability issue. The implementation is clean and well-tested. Once the nested path edge case is covered, this is ready to merge. Merge checklist:
Detailed Code Review
|
Addresses code review feedback on PR #49: - Add test for nested path access on None-valued optional input (verifies ${optional_config.field} fails gracefully when config is None) - Add test for nested path access when optional input IS provided (verifies normal case still works) - Enhance comment in workflow_validator.py explaining the design decision and noting that nested access on None will still fail (intentionally) This documents the expected behavior: optional inputs resolve to None, but attempting to traverse into None (e.g., ${config.api_key}) correctly fails with a clear error message.
fcf3f25 to
983aa28
Compare
Code Review - PR #49This PR fixes a bug where optional workflow inputs without explicit defaults failed template resolution. The fix is well-designed and production-ready, with excellent test coverage and thoughtful edge case handling. ✅ Approved - No blocking issuesCritical — must fix before mergeNone. Warnings — should be addressedNone. The implementation is solid. Suggestions — optional improvements1. Comment Clarity in workflow_validator.py (lines 176-182)The comment explaining the design decision is excellent. Consider a minor rewording for even better clarity: # Current:
# Optional inputs without explicit default resolve to None.
# Rationale: "required: false" means "can be omitted", and omitted
# values should still be available in context (as None) so templates
# like ${optional_param} can resolve rather than fail validation.
# Note: Nested access like ${optional_param.field} will still fail
# at runtime since you can't traverse into None - this is intentional.
# Suggested alternative (slightly more direct):
# Optional inputs without defaults resolve to None to enable template resolution.
# Design rationale:
# - "required: false" means "can be omitted"
# - Templates like ${optional_param} should resolve (to None) rather than fail
# - Nested access ${optional_param.field} intentionally fails at runtime
# (you can't traverse into None - this provides clear error messages)This is purely stylistic - the current comment is already clear. 2. Test Organization (test_null_defaults.py)The new tests are comprehensive and well-named. Consider extracting the edge case tests into a separate test class for better organization: class TestOptionalInputsWithoutDefaults:
"""Test optional inputs without explicit defaults resolve to None."""
def test_optional_input_without_default_resolves_to_none(self): ...
def test_optional_input_without_default_can_be_overridden(self): ...
def test_nested_path_on_none_optional_input_fails_gracefully(self): ...
def test_nested_path_on_provided_optional_input_succeeds(self): ...This would improve discoverability when scanning test failures. Current organization is fine, just a minor suggestion. 🎯 What This PR Does Well1. Minimal, Surgical ChangesOnly 2 production files changed with 7 lines of actual logic:
This is textbook "fix the root cause, nothing more." 2. Excellent Test CoverageThe test additions are exemplary: ✅ Primary bug fix tested ( The test names clearly describe expected behavior, making them excellent living documentation. 3. Thoughtful Error HandlingThe fix correctly handles the distinction between:
This provides predictable behavior without surprises. 4. Improved Error MessagesThe secondary fix (filtering error messages to only show actually unresolved variables) is a great UX improvement: Before: After: This eliminates confusion when debugging template resolution issues. 5. Comprehensive DocumentationThe
This is excellent practice for complex bug fixes. 🔍 Code Quality AssessmentArchitectural Quality: ✅ Excellent
Code Quality: ✅ Excellent
Security: ✅ Safe
🎓 Lessons for Future WorkThis PR exemplifies the principles from ✅ "Write code optimized for change" - Minimal diff, clear separation of concerns The only thing this PR doesn't do is break existing functionality - and that's verified by the passing test suite. 📊 Final Assessment
Recommendation: ✅ Approve and merge immediately. This is production-ready code that improves both functionality and developer experience. 🙏 AcknowledgmentGreat work on the second commit ( The enhanced comment in Ready to merge. 🚀 |
Summary
Fixes the bug where optional workflow inputs declared with
required: falsebut nodefaultvalue would fail template resolution with "Unresolved variables" error.Now, optional inputs without defaults resolve to
None, allowing templates to work correctly.Changes
Noneto context for optional inputs without explicit defaultsBefore/After
Before:
After:
Testing
make checkpasses (lint, type check, formatting)Run
make testto verify.