diff --git a/copier/_main.py b/copier/_main.py index b8d897edc..958d560a4 100644 --- a/copier/_main.py +++ b/copier/_main.py @@ -710,6 +710,14 @@ def jinja_env(self) -> SandboxedEnvironment: f"Unsupported envops.undefined value specified: {undefined_class}.\n" 'Supported values are "jinja2.Undefined" and "jinja2.StrictUndefined".' ) + else: + warnings.warn( + "Copier does not detect undefined variables in templates unless `undefined: jinja2.StrictUndefined` is specified. " + "Consider enabling StrictUndefined to find undefined variables in your templates. " + "This may be enabled by default in the future." + "To silence this warning, set `undefined: jinja2.Undefined` in your template's _envops.", + FutureWarning, + ) try: env = SandboxedEnvironment(loader=loader, extensions=extensions, **envops) except ModuleNotFoundError as error: diff --git a/tests/test_dynamic_file_structures.py b/tests/test_dynamic_file_structures.py index f86bc8d52..1644586b8 100644 --- a/tests/test_dynamic_file_structures.py +++ b/tests/test_dynamic_file_structures.py @@ -20,6 +20,7 @@ def test_folder_loop(tmp_path_factory: pytest.TempPathFactory) -> None: ) with warnings.catch_warnings(): warnings.simplefilter("error") + warnings.filterwarnings("ignore", message=".*jinja2.StrictUndefined.*") copier.run_copy( str(src), dst, @@ -56,6 +57,7 @@ def test_nested_folder_loop(tmp_path_factory: pytest.TempPathFactory) -> None: ) with warnings.catch_warnings(): warnings.simplefilter("error") + warnings.filterwarnings("ignore", message=".*jinja2.StrictUndefined.*") copier.run_copy( str(src), dst, @@ -95,6 +97,7 @@ def test_file_loop(tmp_path_factory: pytest.TempPathFactory) -> None: ) with warnings.catch_warnings(): warnings.simplefilter("error") + warnings.filterwarnings("ignore", message=".*jinja2.StrictUndefined.*") copier.run_copy( str(src), dst, @@ -153,6 +156,7 @@ def test_folder_loop_dict_items(tmp_path_factory: pytest.TempPathFactory) -> Non with warnings.catch_warnings(): warnings.simplefilter("error") + warnings.filterwarnings("ignore", message=".*jinja2.StrictUndefined.*") copier.run_copy( str(src), diff --git a/tests/test_jinja2_undefined.py b/tests/test_jinja2_undefined.py index bacb95ce3..8a09b85c4 100644 --- a/tests/test_jinja2_undefined.py +++ b/tests/test_jinja2_undefined.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest from jinja2.exceptions import UndefinedError @@ -80,3 +82,39 @@ def test_strictundefined_undefined_variable( with pytest.raises(UndefinedError, match="'undefined_variable' is undefined"): copier.run_copy(str(src), dst) + + +def test_futurewarning(tmp_path: Path) -> None: + with pytest.warns( + FutureWarning, + match="Copier does not detect undefined variables in templates unless `undefined: jinja2.StrictUndefined` is specified.", + ): + copier.run_copy("./tests/demo_config_empty", tmp_path) + + +# fail on warnings +@pytest.mark.filterwarnings("error") +def test_silenced_warning( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) + + build_file_tree( + { + (src / ".copier-answers.yml.jinja"): ( + """\ + # Changes here will be overwritten by Copier + {{ _copier_answers|to_nice_yaml }} + """ + ), + (src / "copier.yaml"): ( + """\ + _envops: + undefined: jinja2.Undefined + """ + ), + (src / "test.jinja"): "{{ undefined_variable }}", + } + ) + + copier.run_copy(str(src), dst) diff --git a/tests/test_normal_jinja2.py b/tests/test_normal_jinja2.py index 6168752c8..b2f846dac 100644 --- a/tests/test_normal_jinja2.py +++ b/tests/test_normal_jinja2.py @@ -24,6 +24,7 @@ def test_normal_jinja2(tmp_path_factory: pytest.TempPathFactory) -> None: comment_end_string: "#}" lstrip_blocks: true trim_blocks: true + undefined: jinja2.Undefined name: Guybrush todo: Become a pirate """ @@ -60,6 +61,7 @@ def test_to_not_keep_trailing_newlines_in_jinja2( _templates_suffix: .jinja _envops: keep_trailing_newline: false + undefined: jinja2.Undefined data: foo """ ), diff --git a/tests/test_output.py b/tests/test_output.py index aa97865ba..7038d13fb 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -111,6 +111,8 @@ def test_messages_with_inline_text( _message_after_copy: Project {{ project_name }} successfully created _message_before_update: Updating on {{ _copier_conf.os }} _message_after_update: Project {{ project_name }} successfully updated + _envops: + undefined: jinja2.Undefined """ ), (src / "{{ _copier_conf.answers_file }}.jinja"): ( @@ -231,6 +233,8 @@ def test_messages_with_included_text( _message_after_copy: "{% include 'message_after_copy.md.jinja' %}" _message_before_update: "{% include 'message_before_update.md.jinja' %}" _message_after_update: "{% include 'message_after_update.md.jinja' %}" + _envops: + undefined: jinja2.Undefined """ ), (src / "message_before_copy.md.jinja"): ( diff --git a/tests/test_templated_prompt.py b/tests/test_templated_prompt.py index 623d9069e..7d27fb1e3 100644 --- a/tests/test_templated_prompt.py +++ b/tests/test_templated_prompt.py @@ -17,7 +17,6 @@ from .helpers import ( BRACKET_ENVOPS, - BRACKET_ENVOPS_JSON, COPIER_PATH, SUFFIX_TMPL, Keyboard, @@ -227,12 +226,15 @@ def test_templated_prompt_custom_envops( def test_templated_prompt_builtins(tmp_path_factory: pytest.TempPathFactory) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) + envops = BRACKET_ENVOPS.copy() + # suppress FutureWarning about StrictUndefined + envops["undefined"] = "jinja2.Undefined" build_file_tree( { (src / "copier.yaml"): ( f"""\ _templates_suffix: {SUFFIX_TMPL} - _envops: {BRACKET_ENVOPS_JSON} + _envops: {json.dumps(envops)} question1: default: "[[ now() ]]" question2: