diff --git a/changes.d/7365.feat.md b/changes.d/7365.feat.md index ae8ff76376e..10f6ef284ce 100644 --- a/changes.d/7365.feat.md +++ b/changes.d/7365.feat.md @@ -1 +1 @@ -Python warnings raised in users' custom jinja2 filters, globals and tests are now logged. +Python warnings raised during Jinja2 preprocessing are now logged. diff --git a/cylc/flow/parsec/fileparse.py b/cylc/flow/parsec/fileparse.py index 849723136e7..35c4be616ec 100644 --- a/cylc/flow/parsec/fileparse.py +++ b/cylc/flow/parsec/fileparse.py @@ -125,6 +125,8 @@ TEMPLATING_DETECTED: None } +_J2_WARN_LOCS = re.compile(rf'jinja2{re.escape(os.sep)}(lexer|runtime)\.py$') + def get_cylc_env_vars() -> dict[str, str]: """Return a restricted dict of CYLC_ environment variables for templating. @@ -513,6 +515,13 @@ def read_and_proc( flines = jinja2process( fpath, flines, fdir, template_vars ) + for w in warns: + if _J2_WARN_LOCS.search(w.filename): + # Warning originating from processing of a jinja2 template. + # Unfortunately, we can't know exactly where it originated, + # so just set the config filename and vague line "number": + w.filename = fpath + w.lineno = '' # type: ignore if warns: LOG.warning( "The following warnings were raised during Jinja2 " diff --git a/tests/unit/parsec/test_fileparse.py b/tests/unit/parsec/test_fileparse.py index d0190246a7c..79caa7439a9 100644 --- a/tests/unit/parsec/test_fileparse.py +++ b/tests/unit/parsec/test_fileparse.py @@ -20,6 +20,7 @@ import re import sqlite3 from tempfile import NamedTemporaryFile +from textwrap import dedent from types import SimpleNamespace import warnings @@ -483,6 +484,28 @@ def mock_jinja2process(fpath, *a, **k): ) +def test_read_and_proc_jinja2_warnings_invalid_escape( + tmp_path: Path, caplog: pytest.LogCaptureFixture +): + """When a warning comes from a j2 template expression, it should be logged + with the cylc config filepath.""" + (fpath := tmp_path / 'flow.cylc').write_text( + dedent(r""" + #!jinja2 + {% from 'warnings' import warn %} + {% do warn('Mock warning') %} + """).lstrip() + ) + read_and_proc( + fpath=str(fpath), + viewcfg={'jinja2': True, 'contin': False, 'inline': False}, + ) + + assert ( + f"{fpath}:: UserWarning: Mock warning" in caplog.text + ) + + def test_parse_keys_only_singleline(): with NamedTemporaryFile() as of, NamedTemporaryFile() as tf: fpath = tf.name