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

Add templates using jinja2 #107

Merged
merged 7 commits into from
Sep 29, 2023
Merged

Add templates using jinja2 #107

merged 7 commits into from
Sep 29, 2023

Conversation

joseph-sentry
Copy link
Contributor

This commit creates the TemplateService that uses
jinja to get and render templates.

This TemplateService provides the get_template method to get a rendered template. It takes the file name of the template as an arg and the template variables as the kwargs.

Templates should be added into the templates directory at the top level of the repository. This commit creates two example templates.

@codecov
Copy link

codecov bot commented Sep 21, 2023

Codecov Report

Merging #107 (e570cc6) into main (a7fc0fd) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #107   +/-   ##
=======================================
  Coverage   98.45%   98.45%           
=======================================
  Files         364      366    +2     
  Lines       26989    27023   +34     
=======================================
+ Hits        26571    26605   +34     
  Misses        418      418           
Flag Coverage Δ
integration 98.42% <100.00%> (+<0.01%) ⬆️
latest-uploader-overall 98.42% <100.00%> (+<0.01%) ⬆️
onlysomelabels 98.45% <100.00%> (+<0.01%) ⬆️
unit 98.42% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 97.04% <100.00%> (+<0.01%) ⬆️
OutsideTasks 98.20% <100.00%> (+<0.01%) ⬆️
Files Coverage Δ
services/template.py 100.00% <100.00%> (ø)
services/tests/test_template.py 100.00% <100.00%> (ø)

This change has been scanned for critical changes. Learn more

@codecov-staging
Copy link

codecov-staging bot commented Sep 21, 2023

Codecov Report

Merging #107 (e570cc6) into main (a7fc0fd) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #107   +/-   ##
=======================================
  Coverage   93.19%   93.20%           
=======================================
  Files         341      343    +2     
  Lines       26645    26679   +34     
=======================================
+ Hits        24832    24866   +34     
  Misses       1813     1813           
Flag Coverage Δ
integration 93.20% <100.00%> (+<0.01%) ⬆️
latest-uploader-overall 93.20% <100.00%> (+<0.01%) ⬆️
unit 93.20% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 93.91% <100.00%> (+<0.01%) ⬆️
OutsideTasks 96.69% <100.00%> (+<0.01%) ⬆️
Files Coverage Δ
services/template.py 100.00% <100.00%> (ø)
services/tests/test_template.py 100.00% <100.00%> (ø)

@codecov-public-qa
Copy link

codecov-public-qa bot commented Sep 21, 2023

Codecov Report

Merging #107 (e570cc6) into main (a7fc0fd) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #107   +/-   ##
=======================================
  Coverage   93.19%   93.20%           
=======================================
  Files         341      343    +2     
  Lines       26645    26679   +34     
=======================================
+ Hits        24832    24866   +34     
  Misses       1813     1813           
Flag Coverage Δ
integration 93.20% <100.00%> (+<0.01%) ⬆️
latest-uploader-overall 93.20% <100.00%> (+<0.01%) ⬆️
unit 93.20% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 93.91% <100.00%> (+<0.01%) ⬆️
OutsideTasks 96.69% <100.00%> (+<0.01%) ⬆️
Files Changed Coverage Δ
services/template.py 100.00% <100.00%> (ø)
services/tests/test_template.py 100.00% <100.00%> (ø)

return _get_cached_template_service()


def _get_cached_template_service():
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this to be a singleton?...
I ask because it doesn't seem to be a pattern we use much with services in the worker, and I don't know if it provides benefits to implement it like that.

My real concern is that we had issues before with tests (read ATS) around the cached ENV.

I'm not opposed to this idea, I'm just curious what are the benefits of doing it as a singleton. Does the loading process takes long? Is that recommended?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it has to be, I guess it does add complexity for no real reason, i just thought it's probably an object that we don't need to instantiate a bunch of times, it's sort of just like load it and have it stay the same until we redeploy, but you're correct that we don't need it to be a singleton

from jinja2 import Environment, PackageLoader, StrictUndefined, select_autoescape


def get_template_service():
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is unnecessary. We can very well just instantiate TemplateService() directly

undefined=StrictUndefined,
)

def get_template(self, name, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] change "name" to "template_name".

[less nit]
Personally I don't love that we return a rendered template from this function. I think that this function should simply get and return the template we specified, AND handle errors when we fail to get the template (like you ask for one that doesn't exist)

Then we modify the template object as we see fit and render it ourselves later on.

So I do think the responsibilities of this function should be limited to fetching a template and returning it (as the name suggests) and handling errors.

from services.template import get_template_service


class TestTemplate(object):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to see a test where you ask for a template that doesn't exist

Copy link
Contributor

@matt-codecov matt-codecov left a comment

Choose a reason for hiding this comment

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

all good to me, except unfortunately i have to give you whiplash on the singleton issue :v

i googled "jinja environment expensive" and learned you were on the right track making it a singleton before. i think gio's feedback about following the FooService().bar() convention of other services in our code is also valid, but we can bridge the gap under the hood

class TemplateService:
def __init__(self):
# this loads the templates from the templates directory in this repository since it's looking for a dir named `templates` next to app.py
self.env = Environment(
Copy link
Contributor

Choose a reason for hiding this comment

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

so, regarding using a singleton here:

https://jinja.palletsprojects.com/en/3.0.x/api/

Most applications will create one Environment object on application initialization and use that to load templates. In some cases however, it’s useful to have multiple environments side by side, if different configurations are in use.

https://stackoverflow.com/questions/618827/optimizing-jinja2-environment-creation

My application is running on Google App Engine and most of requests constantly gets yellow flag due to high CPU usage. Using profiler I tracked the issue down to the routine of creating jinja2.Environment instance.

it looks like creating an Environment is expensive so we should try to only do it once. either that or we precompile templates with something like https://github.com/MiCHiLU/jinja2-precompiler but that approach doesn't seem well-documented.

i don't have a strong opinion on how to accomplish this. you could make env a global or class-level variable instead of an instance variable and be done with it, or you could make TemplateService a real singleton again. if you go the singleton route, there's a way to make it a singleton that you still invoke like TemplateService().get_template(): https://code.activestate.com/recipes/66531/ (maybe not technically a singleton but splitting hairs)

@giovanni-guidini what were the "cached ENV" issues you alluded to? were those, like, env vars or specifically a jinja2.Environment?

Copy link
Contributor

@matt-codecov matt-codecov left a comment

Choose a reason for hiding this comment

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

looks great, tiny tweak!


def __init__(self):
# this loads the templates from the templates directory in this repository since it's looking for a dir named `templates` next to app.py
TemplateService.env = Environment(
Copy link
Contributor

Choose a reason for hiding this comment

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

this will reinitialize TemplateService.env each time we create an instance, we should only initialize it if it's still None

This commit creates the TemplateService that uses
jinja to get and render templates.

This TemplateService provides the get_template method
to get a rendered template. It takes the file name of the
template as an arg and the template variables as the kwargs.

Templates should be added into the templates directory
at the top level of the repository. This commit creates two
example templates.

Signed-off-by: joseph-sentry <[email protected]>
Signed-off-by: joseph-sentry <[email protected]>
Signed-off-by: joseph-sentry <[email protected]>
Signed-off-by: joseph-sentry <[email protected]>
@joseph-sentry joseph-sentry merged commit 3428227 into main Sep 29, 2023
31 of 32 checks passed
@joseph-sentry joseph-sentry deleted the joseph/template-service branch September 29, 2023 20:49
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.

3 participants