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

Error with Jinja2 3.1.5 and new Plotly 6.0.0 #5055

Open
bsense-rius opened this issue Feb 25, 2025 · 4 comments
Open

Error with Jinja2 3.1.5 and new Plotly 6.0.0 #5055

bsense-rius opened this issue Feb 25, 2025 · 4 comments
Labels
bug something broken P3 backlog

Comments

@bsense-rius
Copy link

bsense-rius commented Feb 25, 2025

Dear community,

Scope

Since few days ago I had a notebook in sagemaker studio (with sagemaker-distribution 2.2.2) that ran smoothly without issues with Jinja2 3.1.5 and Plotly 5.24.1 that used plotly's to_html().

17 days ago after an update of sagemaker distribution 2.2.2, plotly 5.24.1 was upgraded to plotly 6.0,0, amongst several other libaries. Our code stopped working. We ensured that the issue was with plotly 6.0.0 by going to our google colab instance that had plotly 5.24.1 with our code also runnining without issues. BUT when we installed package plotly 6.0.0 it stopped working with the very same error.

Error details

We use a Jinja2 template with several placeholders for plotly charts that are updated in a while loop. Only the first iteration over the template works, the second is failing with the following error.

---------------------------------------------------------------------------
TemplateSyntaxError                       Traceback (most recent call last)
Cell In[15], line 809
    806             with open(htmlFilename, 'w') as f:
    807                 f.write(htmlTemplate)
--> 809 analyzeSKF()
    811 # Clear memory
    812 import gc

Cell In[15], line 651, in analyzeSKF()
    644 if wdgChartHTML.value == True:
   (...)
--> 651     htmlTemplate = htmlEnvAdd.from_string(htmlTemplate)
    652     htmlTemplate = htmlTemplate.render({f"figTimeChart{htmlTimeChartIdx}": fig.to_html(full_html=False)})
    653     htmlTimeChartIdx = htmlTimeChartIdx + 1

File /opt/conda/lib/python3.11/site-packages/jinja2/environment.py:1111, in Environment.from_string(self, source, globals, template_class)
   1109 gs = self.make_globals(globals)
   1110 cls = template_class or self.template_class
-> 1111 return cls.from_code(self, self.compile(source), gs, None)

File /opt/conda/lib/python3.11/site-packages/jinja2/environment.py:771, in Environment.compile(self, source, name, filename, raw, defer_init)
    769     return self._compile(source, filename)
    770 except TemplateSyntaxError:
--> 771     self.handle_exception(source=source_hint)

File /opt/conda/lib/python3.11/site-packages/jinja2/environment.py:942, in Environment.handle_exception(self, source)
    937 """Exception handling helper.  This is used internally to either raise
    938 rewritten exceptions or return a rendered traceback for the template.
    939 """
    940 from .debug import rewrite_traceback_stack
--> 942 raise rewrite_traceback_stack(source=source)

File <unknown>:2015, in template()

TemplateSyntaxError: expected token 'end of print statement', got 'q'

As you can see below the code is pretty straight forward: We init the Jinja2 Environment objects and in the main body we update the htmlTemplate with plotly's figure to_html(),

First iteration is correct, but next one just returns the from_string() error shown above.


from jinja2 import Environment, Template, DebugUndefined, Undefined, StrictUndefined, FileSystemLoader, select_autoescape

# Initializes globals (not best practice)
def reloadHTMLTemplate():
    global htmlTemplate, htmlEnvAdd, htmlEnvFinish

    # Load the template from file
    htmlEnvAdd    = Environment(trim_blocks=True, lstrip_blocks=True, undefined=DebugUndefined)
    htmlEnvFinish = Environment(trim_blocks=True, lstrip_blocks=True, undefined=Undefined)

    # The {% %} are evaluated just once, so they are ALL processed in first render
    htmlTemplate = u'''\
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />   <!--It is necessary to use the UTF-8 encoding with plotly graphics to get e.g. negative signs to render correctly -->
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <h2 style="font-family:'Open Sans', verdana, arial, sans-serif; color: #2A3F5F">{{ reportTitle }}</h2>
        <h3 style="font-family:'Open Sans', verdana, arial, sans-serif; color: #2A3F5F">{{ titleTimeCharts }}</h3>
        {{ figTimeChart0 }}
        {{ figTimeChart1 }}
        {{ figTimeChart2 }}
        {{ figTimeChart3 }}

        <h3 style="font-family:'Open Sans', verdana, arial, sans-serif; color: #2A3F5F">{{ titleFreqCharts }}</h3>
        {{ figFreqChart0 }}
        {{ figFreqChart1 }}
        {{ figFreqChart2 }}
        {{ figFreqChart3 }}
      </body>
    </html>'''

##############################################
# Main function point that uses the Jinja2 variables 
##############################################
reloadHTMLTemplate()
htmlTimeChartIdx = 0
(...)
while htmlTimeChartIdx < 4:
    # create the figure 'fig' with plotly
    (...)
    if wdgChartHTML.value == True:
        htmlTemplate = htmlEnvAdd.from_string(htmlTemplate)
        htmlTemplate = htmlTemplate.render({f"figTimeChart{htmlTimeChartIdx}": fig.to_html(full_html=False)})
        htmlTimeChartIdx = htmlTimeChartIdx + 1

Conclusion

It seems that there might be a kind of change in the to_html() or in the figure object itself that fools Jinja2's from_sting in the next loop iteration.

Thank you very much!

@rl-utility-man
Copy link
Contributor

rl-utility-man commented Feb 28, 2025

There's an analogous piece of code in the documentation here: https://plotly.com/python/dropdowns/#graph-selection-dropdowns-in-jinja

I just confirmed that it works with Plotly 6.0.0 and Jinja2 3.1.5

A key difference between the example and your approach appears to be that the example loads all the figures into a dictionary and then runs them through template.render just once. You might see 1) if loading a data_for_jinja dict with all your figs and rendering just once works around the problem and 2) if modifying the example to iteratively render and create a new template reproduces the problem.

It might also be diagnostic to strip the template down to just, say,

<!DOCTYPE html>
    <html>
      <body>
        {{ figTimeChart0 }}
        {{ figTimeChart1 }}
      </body>
    </html>

and then write it to disk at the end of each iteration of the loop. Looking at the version that jinja reports to be an invalid template might reveal the offending output -- perhaps some escape character or the like that is making the special jinja braces invisible -- and then it would be clear exactly how to_html and jinja are in conflict.

What kind of figure(s) cause this crash?

@gvwilson
Copy link
Contributor

hi @bsense-rius - as @rl-utility-man says, it would help a lot if you could strip this down to a minimal example (the two-figure page they show would be great). thanks - @gvwilson

@gvwilson gvwilson added bug something broken P3 backlog labels Feb 28, 2025
@bsense-rius
Copy link
Author

I tested a minimal example of a very simple charts (two cosines) and it still fails. The first iteration runs fine but not the second

However, if i follow the advice of @rl-utility-man by just running the render once, it works with simple and complex charts.

I am happy that I found a solution, but on the other hand I am not totally confortable because I was not able to tell what was going under the hood for that "error".

@rl-utility-man
Copy link
Contributor

@bsense-rius If you share your minimal example, the Plotly team and community may be able to tell what was going under the hood for that error.

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

No branches or pull requests

3 participants