Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EmPy as alternative to Jinja2 #2734

Merged
merged 11 commits into from
Aug 14, 2018
Merged

Conversation

TomekTrzeciak
Copy link
Contributor

@TomekTrzeciak TomekTrzeciak commented Jul 23, 2018

This PR adds support for EmPy templating as an alternative to Jinja2. While it might seem of little benefit to add another templating system to Cylc, especially in light of future plans for proper APIs, I think that EmPy would be a much better fit for Cylc than Jinja2 and the added complexity to the Cylc codebase is very small.

Motivation:

Jinja2 project has a very rigid view of how templating should work and how it should be used. It follows the mantra of strict separation between presentation and logic (data) layers. While this might make sense for something like web pages, where there truly is a presentation layer, I would argue that Cylc configuration files do not fall into this category at all. In fact, templating of Cylc suites is used precisely to provide some additional logic during suite construction. This often starts simple, with a few for loops and list variables to express some more repetitive parts of the suite, but over time grows with more and more state being held and passed around in various ad-hoc variables, lists and dictionaries. One might later attempt some modularization by using include files, macros and eventually custom filters, but it feels like Jinja2 is actively resisting, rather than helping, these efforts. Before long one ends up with substantial logic being trapped within Jinja2 templates, with no easy way to refactor it or migrate to something more modular.

In contrast, EmPy is based around embedding straight Python code within templates and doesn't enforce any particular templating philosophy. As a result, it allows more uses cases than Jinja2 and integrates better with Python ecosystem. In simple use it doesn't looks very dissimilar to Jinja2 and offers the same basic control structures (for loops, if statements, macros - see the examples/empy/cities/suite.rc file in this PR for an example). The differences will start to show up, however, once the complexity of the template grows. One might implement some more complex logic as functions instead of macros, which in time might be refactored into one or more modules. This modularization can be accomplished much easier than in Jinja2, since the code being written is pure Python and there is no barrier to integrate it with the rest of Python ecosystem.

While EmPy looks somewhat less polished and lacks some of the Jinja2 advanced features, like template inheritance (which is of little use for Cylc, however), it is more generic and not designed for any specific type of templating, like web pages. The project seems mature, but it's still actively maintained (there've been a bug fix release last year and the one before that was 4 years ago, so it's not a rapidly changing system). The license is LGPL, so compatible with Cylc.

TODO:

  • CUG documentation (I didn't want to spend time on it before hearing some feedback first)
  • add [empy:suite.rc] support in Rose (Empy support metomi/rose#2217)

@hjoliver
Copy link
Member

hjoliver commented Jul 23, 2018

@TomekTrzeciak - I can imagine arguing that supporting multiple templating systems will make it harder to support Cylc users, but if this is better than Jinja2 for our purposes and it makes bleeding edge complex suites easier to write (and you make a convincing argument) then I think it's worth it.

One question, EmPy looks pretty old (not that that's necessarily bad) - did you consider other options too, e.g. from here https://wiki.python.org/moin/Templating ? Presumably most suffer from similar issues to Jinja2 (for Cylc usage) but maybe some don't? Just curious...

@hjoliver
Copy link
Member

This isn't working for me, e.g.:

➤ cat junk2/suite.rc 
#!Empy
[scheduling]
    [[dependencies]]
          graph = foo
[runtime]
    [[foo]]

Validation (etc.) prints out file contents and then fails:

➤ cylc val --debug junk2
[scheduling]
    [[dependencies]]
          graph = foo
[runtime]
    [[foo]]
Traceback (most recent call last):
  File "/home/vagrant/cylc.git/bin/cylc-validate", line 123, in <module>
    main()
  File "/home/vagrant/cylc.git/bin/cylc-validate", line 83, in main
    mem_log_func=profiler.log_memory)
  File "/home/vagrant/cylc.git/lib/cylc/config.py", line 158, in __init__
    self.pcfg = RawSuiteConfig(fpath, output_fname, template_vars)
  File "/home/vagrant/cylc.git/lib/cylc/cfgspec/suite.py", line 566, in __init__
    self.loadcfg(fpath, "suite definition")
  File "/home/vagrant/cylc.git/lib/parsec/config.py", line 68, in loadcfg
    sparse = parse(rcfile, self.output_fname, self.tvars)
  File "/home/vagrant/cylc.git/lib/parsec/fileparse.py", line 393, in parse
    'Invalid line ' + str(index + 1) + ': ' + line)
FileParseError: FileParseError:
Invalid line 1: None

@matthewrmshin matthewrmshin added this to the soon milestone Jul 24, 2018
@TomekTrzeciak
Copy link
Contributor Author

@hjoliver, sorry I think I made some changes that broke it and then forgot about them and pushed it. I fixed it and rebased it - should be working now.

As for your earlier comment - I had the same dilemma that an additional templating system would be extra support burden, but in practice I don't think this will be that significant. The real support problem is that there is templating in Cylc, what templating system is used is probably secondary to that. Unfortunately, some form of templating is needed (or people would roll their own), but fortunately this part of Cylc is really well separated from the rest of the code base (which made adding EmPy so easy for me!).

I did have a look at other templating systems, but they all seemed to me like a worse fit for Cylc than EmPy. I had the following criteria:

  • Python based rather than some home-brew DSL
  • mature, but not abandon-ware
  • decent syntax that doesn't clash with Cylc

Mako came close to these requirements. In many respects it is quite similar to Jinja, but lets you use "straight Python blocks, inline or at the module-level". Unfortunately, the syntax is likely to clash with embedded shell scripts in Cylc (Mako uses shell/Perl inspired ${var} substitution and this is not configurable) and it probably has far more features than needed. By comparison, EmPy is fairly minimal and syntactically a good fit for Cylc.

@hjoliver
Copy link
Member

hjoliver commented Jul 24, 2018

@TomekTrzeciak - works now, thanks. Note I've put up a PR to your branch with a vim syntax update, and to move your example into the correct etc/ location. Your explanation on the choice of empy seems perfectly reasonable.

Copy link
Member

@hjoliver hjoliver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revoking accidental premature approval, sorry! (given my tweak PR and some docs needed).

@oliver-sanders
Copy link
Member

@hjoliver How do we want to document this? I'm guessing we don't want to produce all of our code examples in both Jinja2 and EmPy. Given the current usage of Jinja2 it might be an idea to keep Jinja2 as the "dafacto" templating engine but add a short section documenting the support for EmPy.

@hjoliver
Copy link
Member

Yeah, minimal documentation of Empy is fine, along with some explanation of where or why it might be preferred over Jinja2.

@hjoliver
Copy link
Member

Many Codacy issues flagged here. Perhaps we don't need to bundle EmPy? It is very mature and stable after all.

@TomekTrzeciak
Copy link
Contributor Author

@hjoliver, I guess you don't run codacy for Jinja2 (it has many problems too), so the same should go for EmPy too, shouldn't it?

@hjoliver
Copy link
Member

@TomekTrzeciak - that's true, and we can certainly tell Codacy not to analyse EmPy. But the reason we bundle Jinja2 is (at least partly, as I recall) that it is still evolving fast enough that we've had occasional serious issues with different behaviour between versions. It's seems unlikely that we'll have those kinds of problems with EmPy. Let's see what the concensus is from @cylc/core ...

@oliver-sanders
Copy link
Member

oliver-sanders commented Jul 26, 2018

I don't think we need to bundle EmPy, the interface isn't evolving and we've bundled quite enough already. We can add it as an optional dependency in cylc check-software, wrap the EmPy import statement in a try, except block and dump out an error message informing users to install EmPy before trying to use it if not installed. We already use this approach for matplotlib (profiling) and pandas (report-timings).

@TomekTrzeciak
Copy link
Contributor Author

I admit that I would prefer to bundle EmPy with Cylc. It's not really optional like the analytics in the sense that the suite won't even run without it. Otherwise, to make it developer-friendly, it will have to be bundled with every suite that uses it, because I don't expect to find it on any site installation. Also, I think it will be easier to make sure it keeps working if it can be included within the standard cylc test-battery (I will add some tests for it soon).

@hjoliver
Copy link
Member

@TomekTrzeciak - I can appreciate your position, but we do have other "compulsory" dependencies that are not bundled (Jinja2 used to be one of them), and I have the impression that bundling is generally not considered a good thing unless you're forced into it by (e.g.) overly rapid changes in interface or behaviour that you depend on, or not easily available by standard installation methods. We can list EmPy in the software dependencies section and add it to cylc check-software. Tests can easily be skipped if EmPy is not installed, but will of course run wherever it is installed (and we can configure Travis CI to install it).

@TomekTrzeciak
Copy link
Contributor Author

@hjoliver, OK I'll unbundle em.py from this PR, but I'm not sure how to write tests then. Would something like this at the top of the test do?

python -c 'import em' 2>/dev/null || skip_all 'EmPy not available'

@hjoliver
Copy link
Member

@TomekTrzeciak - thanks. Yes, that ought to do it!

@TomekTrzeciak
Copy link
Contributor Author

Some tests failed, but I don't understand why. They pass for me when run locally.

@hjoliver
Copy link
Member

@TomekTrzeciak - I see you've git added tests/empy/test_header; can you delete the file and replace it with a relative symlink please?:

test_header -> ../lib/bash/test_header

@hjoliver
Copy link
Member

Error local variable 'interpreter' referenced before assignment suggests this try block in lib/parsec/empysupport.py is failing early:

    try:
        os.chdir(dir_)
        ftempl = StringIO('\n'.join(flines))
        xtempl = StringIO()
        interpreter = em.Interpreter(output=em.UncloseableFile(xtempl))
        interpreter.file(ftempl, '<template>', template_vars)
    except Exception as exc:
        lineno = interpreter.contexts[-1].identify()[1]  # ??? not defined here ???

@hjoliver
Copy link
Member

hjoliver commented Jul 30, 2018

Printing the actual exception message (on Travis CI, with a branch modified to run just the failing tests) I get:

'module' object has no attribute 'Interpreter'

@hjoliver
Copy link
Member

Ah ha - an unfortunate package name clash!

On my own ubuntu VM:

  • sudo apt-get install python-empy - works
  • sudo pip install em - reproduces the error

In the latter case, >>> help(em) says:

Em is a terminal tool that prints FILE(s), or standard input to standard
output and highlights the expressions that are matched the PATTERN.

@hjoliver
Copy link
Member

(You should warn about this in the Cylc EmPy documentation!)

if cylc.flags.verbose:
print "Processing with EmPy"

# import EmPy only when truly required (might throw ImportError)
from parsec.empysupport import EmPyError, empyprocess
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to provide an explanation with this traceback so that it doesn't look like an internal error to the uninformed user:

try:
    from parsec.empysupport import EmPyError, empyprocess
except ImportError as exc:
    exc.message = ('The empy Python module must be installed in '
                   'order to perform empy templating.\n%s' % exc.message)
    raise exc

Or

raise ParsecError("he empy Python module...")

expressions, loop control structures, conditional logic, etc., that are expanded
to generate the final suite configuration seen by cylc. See
\href{http://www.alcyone.com/software/empy}{EmPy documentation} for more details
on its templating features and how to use them.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth noting here that empy is not bundled with Cylc.

@TomekTrzeciak
Copy link
Contributor Author

@hjoliver, thanks for debugging this! Looks like import em is not good enough to test it, I've added a more elaborate test to cylc check-software and I grep that in tests/empy. As a side note, a nice interface would be if cylc check-software EmPy ... checked only for selected software and returned non-zero status if not found. All tests pass now, including the added EmPy test.

@hjoliver
Copy link
Member

hjoliver commented Aug 4, 2018

... if cylc check-software EmPy ... checked only for selected software and returned non-zero status if not found.

@sadielbartholomew - as the architect of the new improved cylc check-software, would this be easy for you to do? Not a high priority, but if it's easy...

Copy link
Member

@hjoliver hjoliver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look good, just one minor comment.

xtempl = StringIO()
interpreter = em.Interpreter(output=em.UncloseableFile(xtempl))
interpreter.file(ftempl, '<template>', template_vars)
except Exception as exc:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do lines 43-45 need to be inside this try block? If they raise an exception, it is presumably not due to an EmPy error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're quite right! In fact line 46 should be outside the try block too, since error handling code uses interpreter object. Fixed now.

@sadielbartholomew
Copy link
Collaborator

sadielbartholomew commented Aug 6, 2018

@hjoliver, yes it wouldn't take long to incorporate command-line arguments to only check for and output results for alternative software dependencies. While I do this I can add in options to ignore certain functionality needs for the current output e.g. requirements for one or either of the User Guide formats, as I am sure they will not be of concern to many users who might just view the User Guide on the web. I will add this to my To Do list.

Copy link
Member

@oliver-sanders oliver-sanders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all works as expected.

We now have EmPy support in the vim syntax file, Cylc also supports emacs, gedit and kate (also via the Rose docs pygments).

Whilst vim is of course the master editor should we extend this support to the lesser tools?

It is a translation of \lstinline=jinja2.cities= example from
Section~\ref{Jinja2} and can be directly compared against it.

\lstset{language=suiterc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eh?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't reply to your review summary, but let me just register a downvote at the use of the phrase 'lesser tools'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliver-sanders - what's your "eh?" refer to? The phrasing of "can be directly compared against it."? (should be "compared with it" I guess).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it was the lstset command.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with the lstset command?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing, I was reading it wrong.

@hjoliver
Copy link
Member

@oliver-sanders and @sadielbartholomew - yes I suppose we should be more inclusive of those poor folks who choose to encumber themselves with "the lesser tools" 😁

It's fine with me though if we leave editor syntax file updates for other PRs, possibly by aficionados of said tools (since proper testing involves knowing how to use the editor).

@oliver-sanders
Copy link
Member

oliver-sanders commented Aug 14, 2018

OK, I might create an issue to record this.

[edit] #2752

Copy link
Member

@oliver-sanders oliver-sanders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think we are OK to bump the syntax issue. I will leave this un-merged unless we want to change the milestone to next-release.

@TomekTrzeciak
Copy link
Contributor Author

The corresponding Rose issue metomi/rose#2217 is labelled with next-release. It would make sense to for these two to go in together.

@hjoliver
Copy link
Member

Agreed, going to next-release...and merging...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants