From 9cdee003c2f1a0015f7d448d133d2c6d76d205a9 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 29 May 2026 13:07:58 +0200 Subject: [PATCH 1/2] Allow user context to override computed and constant questions --- src/tui_forms/form/question.py | 15 ++++-- tests/form/test_question_overrides.py | 74 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 tests/form/test_question_overrides.py diff --git a/src/tui_forms/form/question.py b/src/tui_forms/form/question.py index 467cc7e..8f39985 100644 --- a/src/tui_forms/form/question.py +++ b/src/tui_forms/form/question.py @@ -60,11 +60,11 @@ def _render_variable( self, env: Environment, answers: dict[str, Any], value: Any, root_key: str = "" ) -> Any: key = self.key - current_value = (answers.get(root_key, {}) if root_key else answers).get( - key, _NOVALUE - ) + current_answers = answers.get(root_key, {}) if root_key else answers + current_value = current_answers.get(key, _NOVALUE) if current_value is _NOVALUE: - value = template.render_variable(env, value, answers) + # print(f"DEBUG: Rendering {key} with answers keys: {list(current_answers.keys())} and root_key: {root_key}") + value = template.render_variable(env, value, current_answers, root_key=root_key) else: value = current_value return value @@ -90,6 +90,10 @@ class QuestionComputed(QuestionHidden): def default_value( self, env: Environment, answers: dict[str, Any], root_key: str = "" ) -> Any: + current_answers = answers.get(root_key, {}) if root_key else answers + if (current_value := current_answers.get(self.key, _NOVALUE)) is not _NOVALUE: + return current_value + val = ( self.default["default"] if isinstance(self.default, dict) else self.default ) @@ -115,6 +119,9 @@ def default_value( self, env: Environment, answers: dict[str, Any], root_key: str = "" ) -> Any: """Return the raw constant value without rendering.""" + current_answers = answers.get(root_key, {}) if root_key else answers + if (current_value := current_answers.get(self.key, _NOVALUE)) is not _NOVALUE: + return current_value return self.default diff --git a/tests/form/test_question_overrides.py b/tests/form/test_question_overrides.py new file mode 100644 index 0000000..5033957 --- /dev/null +++ b/tests/form/test_question_overrides.py @@ -0,0 +1,74 @@ +from jinja2 import Environment +from tui_forms.form.question import QuestionComputed +from tui_forms.form.question import QuestionConstant + + +def test_question_computed_respects_existing_answer(): + """Verify that QuestionComputed uses an existing answer if provided.""" + env = Environment() + q = QuestionComputed( + key="test_key", + type="string", + title="Test", + description="", + default="computed-{{ base }}", + ) + answers = {"base": "value", "test_key": "user-override"} + # Should use 'user-override' instead of computing 'computed-value' + assert q.default_value(env, answers) == "user-override" + + +def test_question_computed_computes_when_missing(): + """Verify that QuestionComputed computes the value when answer is missing.""" + env = Environment() + q = QuestionComputed( + key="test_key", + type="string", + title="Test", + description="", + default="computed-{{ base }}", + ) + answers = {"base": "value"} + assert q.default_value(env, answers) == "computed-value" + + +def test_question_constant_respects_existing_answer(): + """Verify that QuestionConstant uses an existing answer if provided.""" + env = Environment() + q = QuestionConstant( + key="test_key", + type="string", + title="Test", + description="", + default="constant-value", + ) + answers = {"test_key": "user-override"} + assert q.default_value(env, answers) == "user-override" + + +def test_question_constant_uses_default_when_missing(): + """Verify that QuestionConstant uses the default when answer is missing.""" + env = Environment() + q = QuestionConstant( + key="test_key", + type="string", + title="Test", + description="", + default="constant-value", + ) + answers = {} + assert q.default_value(env, answers) == "constant-value" + + +def test_question_computed_with_root_key(): + """Verify QuestionComputed works with root_key nesting.""" + env = Environment() + q = QuestionComputed( + key="test_key", + type="string", + title="Test", + description="", + default="computed-{{ base }}", + ) + answers = {"myroot": {"base": "value", "test_key": "user-override"}} + assert q.default_value(env, answers, root_key="myroot") == "user-override" From 56f09064d162ae8fa513ba4c052c4526675de073 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Fri, 29 May 2026 13:58:53 +0200 Subject: [PATCH 2/2] fix linting --- news/27.bugfix | 1 + src/tui_forms/form/question.py | 5 +++-- tests/form/test_question_overrides.py | 10 +++++----- 3 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 news/27.bugfix diff --git a/news/27.bugfix b/news/27.bugfix new file mode 100644 index 0000000..ea28ffc --- /dev/null +++ b/news/27.bugfix @@ -0,0 +1 @@ +Update QuestionComputed and QuestionConstant to respect existing answers if provided in the context, preventing unnecessary recomputations when overrides are intended. @erral diff --git a/src/tui_forms/form/question.py b/src/tui_forms/form/question.py index 8f39985..7f9f437 100644 --- a/src/tui_forms/form/question.py +++ b/src/tui_forms/form/question.py @@ -63,8 +63,9 @@ def _render_variable( current_answers = answers.get(root_key, {}) if root_key else answers current_value = current_answers.get(key, _NOVALUE) if current_value is _NOVALUE: - # print(f"DEBUG: Rendering {key} with answers keys: {list(current_answers.keys())} and root_key: {root_key}") - value = template.render_variable(env, value, current_answers, root_key=root_key) + value = template.render_variable( + env, value, current_answers, root_key=root_key + ) else: value = current_value return value diff --git a/tests/form/test_question_overrides.py b/tests/form/test_question_overrides.py index 5033957..6e79ca0 100644 --- a/tests/form/test_question_overrides.py +++ b/tests/form/test_question_overrides.py @@ -5,7 +5,7 @@ def test_question_computed_respects_existing_answer(): """Verify that QuestionComputed uses an existing answer if provided.""" - env = Environment() + env = Environment(autoescape=True) q = QuestionComputed( key="test_key", type="string", @@ -20,7 +20,7 @@ def test_question_computed_respects_existing_answer(): def test_question_computed_computes_when_missing(): """Verify that QuestionComputed computes the value when answer is missing.""" - env = Environment() + env = Environment(autoescape=True) q = QuestionComputed( key="test_key", type="string", @@ -34,7 +34,7 @@ def test_question_computed_computes_when_missing(): def test_question_constant_respects_existing_answer(): """Verify that QuestionConstant uses an existing answer if provided.""" - env = Environment() + env = Environment(autoescape=True) q = QuestionConstant( key="test_key", type="string", @@ -48,7 +48,7 @@ def test_question_constant_respects_existing_answer(): def test_question_constant_uses_default_when_missing(): """Verify that QuestionConstant uses the default when answer is missing.""" - env = Environment() + env = Environment(autoescape=True) q = QuestionConstant( key="test_key", type="string", @@ -62,7 +62,7 @@ def test_question_constant_uses_default_when_missing(): def test_question_computed_with_root_key(): """Verify QuestionComputed works with root_key nesting.""" - env = Environment() + env = Environment(autoescape=True) q = QuestionComputed( key="test_key", type="string",