Skip to content

Commit

Permalink
Add CYLC_ variables to empy globals; and tidy up.
Browse files Browse the repository at this point in the history
  • Loading branch information
hjoliver committed Jul 24, 2023
1 parent 199cd1f commit 783d4b4
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 24 deletions.
7 changes: 7 additions & 0 deletions cylc/flow/parsec/empysupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import typing as t

from cylc.flow.parsec.exceptions import EmPyError
from cylc.flow.parsec.fileparse import get_cylc_env_vars


def empyprocess(
Expand Down Expand Up @@ -52,6 +53,12 @@ def empyprocess(
ftempl = StringIO('\n'.join(flines))
xtempl = StringIO()
interpreter = em.Interpreter(output=em.UncloseableFile(xtempl))

# Add `CYLC_` environment variables to the global namespace.
interpreter.updateGlobals(
get_cylc_env_vars()
)

try:
interpreter.file(ftempl, '<template>', template_vars)
except Exception as exc:
Expand Down
24 changes: 22 additions & 2 deletions cylc/flow/parsec/fileparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,27 @@
)


def get_cylc_env_vars() -> t.Dict[str, str]:
"""Return a restricted dict of CYLC_ environment variables for templating.
The following variables are ignored because the do not necessarily reflect
the running code version (I might not use the "cylc" wrapper, or it might
select a different version):
CYLC_VERSION
Set as a template variable elsewhere, from the hardwired code version.
CYLC_ENV_NAME
Providing it as a template variable would just be misleading.
"""
return {
key: val
for key, val in os.environ.items()
if key.startswith('CYLC_')
if key not in ["CYLC_VERSION", "CYLC_ENV_NAME"]
}


def _concatenate(lines):
"""concatenate continuation lines"""
index = 0
Expand Down Expand Up @@ -446,10 +467,9 @@ def read_and_proc(
flines = inline(
flines, fdir, fpath, viewcfg=viewcfg)

# Add the hardwired code version to template vars as CYLC_VERSION
template_vars['CYLC_VERSION'] = __version__

template_vars = merge_template_vars(template_vars, extra_vars)

template_vars['CYLC_TEMPLATE_VARS'] = template_vars

# Fail if templating_detected ≠ hashbang
Expand Down
27 changes: 7 additions & 20 deletions cylc/flow/parsec/jinja2support.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

from cylc.flow import LOG
from cylc.flow.parsec.exceptions import Jinja2Error
from cylc.flow.parsec.fileparse import get_cylc_env_vars

TRACEBACK_LINENO = re.compile(
r'\s+?File "(?P<file>.*)", line (?P<line>\d+), in .*template'
Expand Down Expand Up @@ -188,15 +189,13 @@ def jinja2environment(dir_=None):
# Import WORKFLOW HOST USER ENVIRONMENT into template:
# (Usage e.g.: {{environ['HOME']}}).
env.globals['environ'] = os.environ

env.globals['raise'] = raise_helper
env.globals['assert'] = assert_helper

# And all `CYLC_` variables (this will include CYLC_WORKFLOW environment
# variables exported by `cylc` commands before config file parsing.
for key, val in os.environ.items():
if key.startswith('CYLC_'):
env.globals[key] = val
# Add `CYLC_` environment variables to the global namespace.
env.globals.update(
get_cylc_env_vars()
)
return env


Expand Down Expand Up @@ -275,11 +274,11 @@ def jinja2process(
# CALLERS SHOULD HANDLE JINJA2 TEMPLATESYNTAXERROR AND TEMPLATEERROR
# AND TYPEERROR (e.g. for not using "|int" filter on number inputs.
# Convert unicode to plain str, ToDo - still needed for parsec?)

try:
env = jinja2environment(dir_)
template = env.from_string('\n'.join(flines[1:]))
lines = str(template.render(template_vars)).splitlines()
print()
except TemplateSyntaxError as exc:
filename = None
# extract source lines
Expand All @@ -306,16 +305,4 @@ def jinja2process(
lines=get_error_lines(fpath, flines),
)

flow_config = []
for line in lines:
# Jinja2 leaves blank lines where source lines contain
# only Jinja2 code; this matters if line continuation
# markers are involved, so we remove blank lines here.
if not line.strip():
continue
# restoring newlines here is only necessary for display by
# the cylc view command:
# ##flow_config.append(line + '\n')
flow_config.append(line)

return flow_config
return [line for line in lines if line.strip()]
1 change: 1 addition & 0 deletions cylc/flow/scripts/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
CylcOptionParser as COP,
)
from cylc.flow.parsec.fileparse import read_and_proc
from cylc.flow.templatevars import get_template_vars
from cylc.flow.terminal import cli_function

if TYPE_CHECKING:
Expand Down
48 changes: 46 additions & 2 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import sys
from optparse import Values
from typing import Any, Callable, Dict, List, Optional, Tuple, Type
from pathlib import Path
Expand All @@ -34,7 +35,7 @@
WorkflowConfigError,
XtriggerConfigError,
)
from cylc.flow.parsec.exceptions import Jinja2Error
from cylc.flow.parsec.exceptions import Jinja2Error, EmPyError
from cylc.flow.scheduler_cli import RunOptions
from cylc.flow.scripts.validate import ValidateOptions
from cylc.flow.workflow_files import WorkflowFiles
Expand Down Expand Up @@ -1028,7 +1029,11 @@ def test_rsync_includes_will_not_accept_sub_directories(tmp_flow_config):
]
)
def test_jinja2_cylc_vars(tmp_flow_config, cylc_var, expected_err):
"""Valid CYLC variables should be available to Jinja2 during parsing."""
"""Defined CYLC_ variables should be available to Jinja2 during parsing.
This test is not located in the jinja2_support unit test module because
CYLC_ variables are only defined during workflow config parsing.
"""
reg = 'nodule'
flow_file = tmp_flow_config(reg, """#!Jinja2
# {{""" + cylc_var + """}}
Expand All @@ -1044,6 +1049,45 @@ def test_jinja2_cylc_vars(tmp_flow_config, cylc_var, expected_err):
assert expected_err[1] in str(exc)


@pytest.mark.parametrize(
'cylc_var, expected_err',
[
["CYLC_WORKFLOW_NAME", None],
["CYLC_BEEF_WELLINGTON", (EmPyError, "is not defined")],
]
)
def test_empy_cylc_vars(tmp_flow_config, cylc_var, expected_err):
"""Defined CYLC_ variables should be available to empy during parsing.
This test is not located in the empy_support unit test module because
CYLC_ variables are only defined during workflow config parsing.
"""
reg = 'nodule'
flow_file = tmp_flow_config(reg, """#!empy
# @(""" + cylc_var + """)
[scheduler]
allow implicit tasks = True
[scheduling]
[[graph]]
R1 = foo
""")

# empy replaces sys.stdout with a "proxy". And pytest needs it for capture?
# (clue: "pytest --capture=no" avoids the error)
stdout = sys.stdout
sys.stdout._testProxy = lambda: ''
sys.stdout.pop = lambda _: ''
sys.stdout.push = lambda _: ''
sys.stdout.clear = lambda _: ''

try:
WorkflowConfig(workflow=reg, fpath=flow_file, options=Values())
except expected_err[0] as exc:
assert expected_err[1] in str(exc)

sys.stdout = stdout


def test_valid_rsync_includes_returns_correct_list(tmp_flow_config):
"""Test that the rsync includes in the correct """
id_ = 'rsynctest'
Expand Down

0 comments on commit 783d4b4

Please sign in to comment.