diff --git a/changes.d/6476.break.md b/changes.d/6476.break.md
new file mode 100644
index 00000000000..e86af9d978f
--- /dev/null
+++ b/changes.d/6476.break.md
@@ -0,0 +1 @@
+Remove support for the EmPy template engine.
diff --git a/conda-environment.yml b/conda-environment.yml
index 4a598468d67..6d31dce5489 100644
--- a/conda-environment.yml
+++ b/conda-environment.yml
@@ -21,7 +21,6 @@ dependencies:
- tomli >=2 # [py<3.11]
# optional dependencies
- #- empy >=3.3,<3.4
#- pandas >=1.0,<2
#- pympler
#- matplotlib-base
diff --git a/cylc/flow/parsec/empysupport.py b/cylc/flow/parsec/empysupport.py
deleted file mode 100644
index e3dc5e28df4..00000000000
--- a/cylc/flow/parsec/empysupport.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
-# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-"""cylc support for the EmPy template processor
-
-Importing code should catch ImportError in case EmPy is not installed.
-"""
-
-from io import StringIO
-import em
-import os
-import typing as t
-
-from cylc.flow.parsec.exceptions import EmPyError
-from cylc.flow.parsec.fileparse import get_cylc_env_vars
-
-
-def empyprocess(
- _fpath: str,
- flines: t.List[str],
- dir_: str,
- template_vars: t.Optional[t.Dict[str, t.Any]] = None,
-) -> t.List[str]:
- """Pass configure file through EmPy processor.
-
- Args:
- _fpath:
- The path to the root template file (i.e. the flow.cylc file)
- flines:
- List of template lines to process.
- dir_:
- The path to the configuration directory.
- template_vars:
- Dictionary of template variables.
-
- """
-
- cwd = os.getcwd()
-
- os.chdir(dir_)
- 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_vars)
- except Exception as exc:
- lineno = interpreter.contexts[-1].identify()[1]
- raise EmPyError(
- str(exc),
- lines={'': flines[max(lineno - 4, 0): lineno]},
- ) from None
- finally:
- interpreter.shutdown()
- xworkflow = xtempl.getvalue()
- os.chdir(cwd)
- ftempl.close()
- xtempl.close()
-
- flow_config = []
- for line in xworkflow.splitlines():
- # EmPy leaves blank lines where source lines contain
- # only EmPy 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
diff --git a/cylc/flow/parsec/exceptions.py b/cylc/flow/parsec/exceptions.py
index 690d583b885..545a62cf753 100644
--- a/cylc/flow/parsec/exceptions.py
+++ b/cylc/flow/parsec/exceptions.py
@@ -141,10 +141,6 @@ class TemplateVarLanguageClash(FileParseError):
"""Multiple workflow configuration templating engines configured."""
-class EmPyError(FileParseError):
- """Wrapper class for EmPy exceptions."""
-
-
class Jinja2Error(FileParseError):
"""Wrapper class for Jinja2 exceptions.
diff --git a/cylc/flow/parsec/fileparse.py b/cylc/flow/parsec/fileparse.py
index f4de85aee3c..5b4f0130e84 100644
--- a/cylc/flow/parsec/fileparse.py
+++ b/cylc/flow/parsec/fileparse.py
@@ -258,8 +258,7 @@ def process_plugins(fpath: 'Union[str, Path]', opts: 'Values'):
'env': A dictionary of environment variables.
'template_variables': A dictionary of template variables.
'templating_detected': Where the plugin identifies a templating
- language this is specified here. Expected values are ``jinja2``
- or ``empy``.
+ language this is specified here.
args:
fpath: Path to a config file. Plugin will look at the parent
@@ -446,7 +445,6 @@ def read_and_proc(
flines = [line.rstrip('\n') for line in f]
do_inline = True
- do_empy = True
do_jinja2 = True
do_contin = True
@@ -456,8 +454,6 @@ def read_and_proc(
template_vars = {}
if viewcfg:
- if not viewcfg['empy']:
- do_empy = False
if not viewcfg['jinja2']:
do_jinja2 = False
if not viewcfg['contin']:
@@ -479,27 +475,6 @@ def read_and_proc(
process_with = hashbang_and_plugin_templating_clash(
extra_vars[TEMPLATING_DETECTED], flines
)
- # process with EmPy
- if do_empy:
- if (
- extra_vars[TEMPLATING_DETECTED] == 'empy' and
- not process_with and
- process_with != 'empy'
- ):
- flines.insert(0, '#!empy')
-
- if flines and re.match(r'^#![Ee]m[Pp]y\s*', flines[0]):
- LOG.debug('Processing with EmPy')
- try:
- from cylc.flow.parsec.empysupport import empyprocess
- except ImportError:
- raise ParsecError(
- 'EmPy Python package must be installed '
- 'to process file: ' + fpath
- ) from None
- flines = empyprocess(
- fpath, flines, fdir, template_vars
- )
# process with Jinja2
if do_jinja2:
@@ -546,8 +521,7 @@ def hashbang_and_plugin_templating_clash(
flines: The lines of text from file.
Returns:
- The hashbang, in lower case, to allow for users using any of
- ['empy', 'EmPy', 'EMPY'], or similar in other templating languages.
+ The hashbang, in lower case.
Examples:
- Hashbang and templating_detected match:
@@ -559,7 +533,7 @@ def hashbang_and_plugin_templating_clash(
>>> thisfunc('', [''])
- Function raises if templating engines clash:
- >>> thisfunc('empy', ['#!jinja2'])
+ >>> thisfunc('other', ['#!jinja2'])
Traceback (most recent call last):
...
cylc.flow.parsec.exceptions.TemplateVarLanguageClash: ...
diff --git a/cylc/flow/scripts/lint.py b/cylc/flow/scripts/lint.py
index 0dd991b8fa9..cf9705232d1 100755
--- a/cylc/flow/scripts/lint.py
+++ b/cylc/flow/scripts/lint.py
@@ -421,8 +421,6 @@ def check_indentation(line: str) -> bool:
| <[^>]+>
# or Jinja2
| {{.*?}} | {%.*?%} | {\#.*?\#}
- # or EmPy
- | (@[\[{\(]).*([\]\}\)])
''',
re.X
)
diff --git a/cylc/flow/scripts/view.py b/cylc/flow/scripts/view.py
index 6eaa2bd6bb1..4050029e3a1 100644
--- a/cylc/flow/scripts/view.py
+++ b/cylc/flow/scripts/view.py
@@ -21,7 +21,7 @@
Print a processed workflow configuration.
Print workflow configurations as processed before full parsing by Cylc. This
-includes Jinja2 or Empy template processing, and inlining of include-files.
+includes Jinja2 template processing, and inlining of include-files.
Some explanatory markup may also be requested.
Warning:
@@ -63,12 +63,6 @@ def get_option_parser():
"--inline", "-i", help="Inline include-files.", action="store_true",
default=False, dest="inline")
- parser.add_option(
- "--empy", "-e",
- help="View after EmPy template processing "
- "(implies '-i/--inline' as well).",
- action="store_true", default=False, dest="empy")
-
parser.add_option(
"--jinja2", "-j",
help="View after Jinja2 template processing "
@@ -77,7 +71,7 @@ def get_option_parser():
parser.add_option(
"-p", "--process",
- help="View after all processing (EmPy, Jinja2, inlining, "
+ help="View after all processing (Jinja2, inlining, "
"line-continuation joining).",
action="store_true", default=False, dest="process")
@@ -138,13 +132,9 @@ async def _main(options: 'Values', workflow_id: str) -> None:
'mark': options.mark,
'single': options.single,
'label': options.label,
- 'empy': options.empy or options.process,
'jinja2': options.jinja2 or options.process,
'contin': options.cat or options.process,
- 'inline': (
- options.jinja2 or options.empy or
- options.inline or options.process
- ),
+ 'inline': options.inline or options.process,
},
opts=options,
):
diff --git a/pytest.ini b/pytest.ini
index 04d12d224b4..ae3ad912fee 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -23,7 +23,6 @@ addopts = --verbose
--dist=loadscope
# ignore files which cause issues with test collection
--ignore=cylc/flow/data_messages_pb2.py
- --ignore=cylc/flow/parsec/empysupport.py
--ignore=cylc/flow/parsec/example
# disable pytest-tornasync because it conflicts with pytest-asyncio's auto mode
-p no:tornado
diff --git a/setup.cfg b/setup.cfg
index 4e3b2f65200..3dfba8e9a73 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -86,8 +86,6 @@ install_requires =
include = cylc*
[options.extras_require]
-empy =
- EmPy==3.3.*
graph =
pillow
main_loop-log_data_store =
@@ -137,7 +135,6 @@ tutorials =
pillow
requests
all =
- %(empy)s
%(graph)s
%(main_loop-log_data_store)s
%(main_loop-log_db)s
diff --git a/tests/functional/empy/00-simple.t b/tests/functional/empy/00-simple.t
deleted file mode 100644
index 965fc1cb47f..00000000000
--- a/tests/functional/empy/00-simple.t
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
-# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#-------------------------------------------------------------------------------
-# basic EmPy expansion test
-. "$(dirname "$0")/test_header"
-if ! python -c 'import em'; then
- skip_all '"EmPy" not installed'
-fi
-#-------------------------------------------------------------------------------
-set_test_number 2
-#-------------------------------------------------------------------------------
-install_workflow "${TEST_NAME_BASE}" '00-simple'
-#-------------------------------------------------------------------------------
-TEST_NAME="${TEST_NAME_BASE}-validate"
-run_ok "${TEST_NAME}" cylc validate -o 'flow.cylc.processed' "${WORKFLOW_NAME}"
-#-------------------------------------------------------------------------------
-TEST_NAME="${TEST_NAME_BASE}-check-expansion"
-cmp_ok 'flow.cylc.processed' "${TEST_SOURCE_DIR}/00-simple/flow.cylc-expanded"
-#-------------------------------------------------------------------------------
-purge
diff --git a/tests/functional/empy/00-simple/flow.cylc b/tests/functional/empy/00-simple/flow.cylc
deleted file mode 100644
index f10b267dcd5..00000000000
--- a/tests/functional/empy/00-simple/flow.cylc
+++ /dev/null
@@ -1,17 +0,0 @@
-#!empy
-@{ MYVAR = "this is a set variable" }@
-
-[scheduling]
- [[graph]]
- R1 = "a => FAM"
-[runtime]
- [[a]]
- script = echo @(MYVAR)
- [[FAM]]
- [[[environment]]]
- TITLE="member"
-@[ for num in range(5) ]@
- [[member_@num]]
- inherit = FAM
- script = echo I am $TITLE @num
-@[ end for ]@
diff --git a/tests/unit/parsec/test_empysupport.py b/tests/unit/parsec/test_empysupport.py
deleted file mode 100644
index 8bd4260a323..00000000000
--- a/tests/unit/parsec/test_empysupport.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
-# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-"""
-EmPy is an optional dependency, so tests need to check if that is installed!
-"""
-
-import sys
-import tempfile
-import unittest
-
-from cylc.flow.parsec.exceptions import EmPyError
-from cylc.flow.parsec.fileparse import read_and_proc
-
-IS_EMPY_INSTALLED = True
-
-try:
- from cylc.flow.parsec import empysupport
-except ImportError:
- IS_EMPY_INSTALLED = False
-
-
-@unittest.skipUnless(IS_EMPY_INSTALLED, "EmPy not installed")
-class TestEmpysupport1(unittest.TestCase):
-
- def test_empysupport_empyprocess(self):
- lines = ["My name is @name", ""]
- variables = {'name': 'Cylc'}
- template_dir = tempfile.gettempdir()
-
- r = empysupport.empyprocess(None, lines, template_dir, variables)
- # after this, we would normally have an error in unittest as follows:
- # AttributeError: ProxyFile instance has no attribute 'getvalue'
- # That's due to a Proxy installed by EmPy to replace sys.stdout.
- # We restore the sys.stdout in the end of the tests
- # TODO: is it OK? Does everything else works OK in Jinja after this?
- # Note: writing multiple methods will result in an error too
-
- self.assertEqual(1, len(r))
- self.assertEqual('My name is Cylc', r[0])
- sys.stdout.getvalue = lambda: ''
-
- lines = []
- template_dir = tempfile.gettempdir()
-
- r = empysupport.empyprocess(None, lines, template_dir)
-
- self.assertEqual(0, len(r))
-
- # --- testing fileparse (here due to stdout issue)
-
- with tempfile.NamedTemporaryFile() as tf:
- fpath = tf.name
- template_vars = {
- 'name': 'Cylc'
- }
- viewcfg = {
- 'empy': True, 'jinja2': False,
- 'contin': False, 'inline': False
- }
- tf.write("#!empy\na=@name\n".encode())
- tf.flush()
- r = read_and_proc(fpath=fpath, template_vars=template_vars,
- viewcfg=viewcfg)
- self.assertEqual(['a=Cylc'], r)
-
- del template_vars['name']
-
- with self.assertRaises(EmPyError):
- read_and_proc(fpath=fpath, template_vars=template_vars,
- viewcfg=viewcfg)
- sys.stdout.getvalue = lambda: ''
-
- sys.stdout.getvalue = lambda: ''
diff --git a/tests/unit/parsec/test_fileparse.py b/tests/unit/parsec/test_fileparse.py
index f09673903f9..85e03eca2cb 100644
--- a/tests/unit/parsec/test_fileparse.py
+++ b/tests/unit/parsec/test_fileparse.py
@@ -286,8 +286,9 @@ def test_read_and_proc_no_template_engine():
fpath = tf.name
template_vars = None
viewcfg = {
- 'empy': False, 'jinja2': False,
- 'contin': False, 'inline': False
+ 'jinja2': False,
+ 'contin': False,
+ 'inline': False,
}
tf.write("a=b\n".encode())
tf.flush()
@@ -300,8 +301,9 @@ def test_read_and_proc_no_template_engine():
tf.flush()
viewcfg = {
- 'empy': False, 'jinja2': False,
- 'contin': True, 'inline': False
+ 'jinja2': False,
+ 'contin': True,
+ 'inline': False,
}
r = read_and_proc(fpath=fpath, template_vars=template_vars,
viewcfg=viewcfg)
@@ -313,9 +315,12 @@ def test_inline():
fpath = tf.name
template_vars = None
viewcfg = {
- 'empy': False, 'jinja2': False,
- 'contin': False, 'inline': True,
- 'mark': None, 'single': None, 'label': None
+ 'jinja2': False,
+ 'contin': False,
+ 'inline': True,
+ 'mark': None,
+ 'single': None,
+ 'label': None,
}
with NamedTemporaryFile() as include_file:
include_file.write("c=d".encode())
@@ -333,9 +338,12 @@ def test_inline_error():
fpath = tf.name
template_vars = None
viewcfg = {
- 'empy': False, 'jinja2': False,
- 'contin': False, 'inline': True,
- 'mark': None, 'single': None, 'label': None
+ 'jinja2': False,
+ 'contin': False,
+ 'inline': True,
+ 'mark': None,
+ 'single': None,
+ 'label': None,
}
tf.write("a=b\n%include \"404.txt\"".encode())
tf.flush()
@@ -352,8 +360,9 @@ def test_read_and_proc_jinja2():
'name': 'Cylc'
}
viewcfg = {
- 'empy': False, 'jinja2': True,
- 'contin': False, 'inline': False
+ 'jinja2': True,
+ 'contin': False,
+ 'inline': False,
}
tf.write("#!jinja2\na={{ name }}\n".encode())
tf.flush()
@@ -375,7 +384,6 @@ def test_read_and_proc_cwd(tmp_path):
(sdir / sub).touch()
viewcfg = {
- 'empy': False,
'jinja2': True,
'contin': False,
'inline': False
@@ -405,8 +413,9 @@ def test_read_and_proc_jinja2_error():
'name': 'Cylc'
}
viewcfg = {
- 'empy': False, 'jinja2': True,
- 'contin': False, 'inline': False
+ 'jinja2': True,
+ 'contin': False,
+ 'inline': False,
}
tf.write("#!jinja2\na={{ name \n".encode())
tf.flush()
@@ -426,8 +435,9 @@ def test_read_and_proc_jinja2_error_missing_shebang():
'name': 'Cylc'
}
viewcfg = {
- 'empy': False, 'jinja2': True,
- 'contin': False, 'inline': False
+ 'jinja2': True,
+ 'contin': False,
+ 'inline': False,
}
# first line is missing shebang!
tf.write("a={{ name }}\n".encode())
@@ -437,8 +447,6 @@ def test_read_and_proc_jinja2_error_missing_shebang():
assert r == ['a={{ name }}']
-# --- originally we had a test for empy here, moved to test_empysupport
-
def test_parse_keys_only_singleline():
with NamedTemporaryFile() as of, NamedTemporaryFile() as tf:
fpath = tf.name
diff --git a/tests/unit/parsec/test_fileparse_templating_clash.py b/tests/unit/parsec/test_fileparse_templating_clash.py
index d8cfddd58bb..33a52ad505a 100644
--- a/tests/unit/parsec/test_fileparse_templating_clash.py
+++ b/tests/unit/parsec/test_fileparse_templating_clash.py
@@ -25,8 +25,8 @@
@pytest.mark.parametrize(
'templating, hashbang',
[
- ['empy', 'jinja2'],
- ['jinja2', 'empy']
+ ['other', 'jinja2'],
+ ['jinja2', 'other']
]
)
def test_read_and_proc_raises_TemplateVarLanguageClash(
diff --git a/tests/unit/scripts/test_lint.py b/tests/unit/scripts/test_lint.py
index 20ff738a1ac..ec4d8936b91 100644
--- a/tests/unit/scripts/test_lint.py
+++ b/tests/unit/scripts/test_lint.py
@@ -274,7 +274,7 @@ def test_check_cylc_file_line_no():
'inherit = FOO_BAr',
# whitespace & trailing commas
' inherit = a , ',
- # parameters, jinja2 and empy should be ignored
+ # parameters, templating code should be ignored
# but any lowercase chars before or after should not
'inherit = Az',
'inherit = A{{ x }}z',
@@ -313,8 +313,6 @@ def test_check_lowercase_family_names__true(line):
'A{{ x }}, {% endfor %}',
id='jinja2-long'
),
- # empy should be ignored
- 'inherit = A@( a )Z',
# trailing comments should be ignored
'inherit = A, B # no, comment',
'inherit = # a',
@@ -325,13 +323,13 @@ def test_check_lowercase_family_names__true(line):
'inherit = ',
# one really awkward, but valid example
param(
- 'inherit = none, FOO_BAR_0, "", AZ, A{{a}}Z, A@(a)Z',
+ 'inherit = none, FOO_BAR_0, "", AZ, A{{a}}Z',
id='awkward'
),
]
)
def test_check_lowercase_family_names__false(line):
- assert check_lowercase_family_names(line) is False
+ assert check_lowercase_family_names(line) is False
def test_inherit_lowercase_matches():
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
index 4a820fdb126..d9317f9e732 100644
--- a/tests/unit/test_config.py
+++ b/tests/unit/test_config.py
@@ -35,7 +35,7 @@
WorkflowConfigError,
XtriggerConfigError,
)
-from cylc.flow.parsec.exceptions import Jinja2Error, EmPyError
+from cylc.flow.parsec.exceptions import Jinja2Error
from cylc.flow.scheduler_cli import RunOptions
from cylc.flow.scripts.validate import ValidateOptions
from cylc.flow.workflow_files import WorkflowFiles
@@ -1074,47 +1074,6 @@ 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 _: ''
-
- if expected_err is None:
- WorkflowConfig(workflow=reg, fpath=flow_file, options=Values())
- else:
- with pytest.raises(expected_err[0]) as exc:
- WorkflowConfig(workflow=reg, fpath=flow_file, options=Values())
- 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'