rogfrich / poller

A CLI app to construct a Discourse poll from the posters in a thread
1 stars 0 forks source link

Jinja2 only finds the template if the code is run from the top level directory #1

Open rogfrich opened 1 year ago

rogfrich commented 1 year ago

If I run the code in the console in PyCharm it works fine:

import os
os.getcwd()
'/Users/rich/code/poller'
from poller import manager
m = manager.Manager("984627", "2023-10-22")
m.output
'Please vote for your three favourite photos [output truncated]'

But if we run output.py directly in place to test it using the if __name__ == '__main__' code that only runs when the module is run directly, we get:

/Users/rich/code/poller/.venv/bin/python /Users/rich/code/poller/poller/output.py 
Traceback (most recent call last):
  File "/Users/rich/code/poller/poller/output.py", line 28, in <module>
    output = Output(entrants=["Person1", "Person2"], voting_deadline="1/1/2024")
  File "/Users/rich/code/poller/poller/output.py", line 18, in __init__
    self.template = self.environment.get_template("template.txt")
  File "/Users/rich/code/poller/.venv/lib/python3.9/site-packages/jinja2/environment.py", line 1010, in get_template
    return self._load_template(name, globals)
  File "/Users/rich/code/poller/.venv/lib/python3.9/site-packages/jinja2/environment.py", line 969, in _load_template
    template = self.loader.load(self, name, self.make_globals(globals))
  File "/Users/rich/code/poller/.venv/lib/python3.9/site-packages/jinja2/loaders.py", line 126, in load
    source, filename, uptodate = self.get_source(environment, name)
  File "/Users/rich/code/poller/.venv/lib/python3.9/site-packages/jinja2/loaders.py", line 218, in get_source
    raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: template.txt
/Users/rich/code/poller/poller

This is because output.py specifies the location of the Jinja template thus:

 self.environment = Environment(loader=FileSystemLoader("./poller/template/"))
 self.template = self.environment.get_template("template.txt")

As currently coded, FileSystemLoader always looks in <cwd>/poller/template/ for the template, which is fine when the executes in the context of the top level root poller directory (which it does when you import it into a REPL session as above) but not when you run output.py directly, because then it executes in the context of poller/poller meaning it would be looking for poller/poller/poller/template/ which doesn't exist, leading to an error.

So what I need to do is find a way to say "no matter what context the code is executing under, look in poller/poller/template/ for the template.

rogfrich commented 1 year ago

Lots of discussion in this Stackoverflow thread

rogfrich commented 1 year ago

And here

rogfrich commented 1 year ago

I've basically fudged this by moving the tests into the base directory. Bad Rich.

rogfrich commented 1 year ago

See #2 - this is breaking the packaging.

So what we're going to do is, we're going to ditch the txt file based template and use a function in the Output class instead. See the docs for the Jinja loaders where I took this example from:

FunctionLoader Loads templates by calling a function which has to return a string or None if an error occoured.

from jinja import Environment, FunctionLoader

def my_load_func(template_name):
    if template_name == 'foo':
        return '...'

e = Environment(loader=FunctionLoader(my_load_func))

Just needs a simple class that returns the template as a string (and tests, of course).