PyTraceToIX – How to debug Jinja2 templates, Flask web apps without breaking the design or code changes

PyTraceToIX is an expression tracer designed for debugging Jinja2 templates, Flask web apps, lambdas, list comprehensions, method chaining, and expressions in general.

Code editors often cannot set breakpoints within these kinds of expressions, which requires significant code modifications to debug effectively.

For Jinja2 templates, the debug extension can be used, but it typically dumps the entire context, making it difficult to isolate specific issues. PyTraceToIX solves this by allowing developers to trace and write specific data directly to sys.stdout or a stream without altering the design or making any changes to the web application.

Additionally, PyTraceToIX can capture multiple inputs and their results, displaying them all in a single line, making it easier to view aggregated data and trace the flow of values.

PyTraceToIX offers a straightforward solution to these challenges, simplifying debugging while preserving the integrity of the original codebase.

It was designed to be simple, with easily identifiable functions that can be removed once the bug is found.

PyTraceToIX has 2 major functions:

  • c__ capture the input of an expression input. ex: c__(x)
  • d__ display the result of an expression and all the captured inputs. ex: d__(c__(x) + c__(y))

And 2 optional functions:

  • init__ initializes display format, output stream and multithreading.
  • t__ defines a name for the current thread.

Installation

pip <span>install </span>pytracetoix
pip <span>install </span>pytracetoix
pip install pytracetoix

Enter fullscreen mode Exit fullscreen mode

Jinja2 templates Usage

In this example:

  • A flask web app uses a Jinja2 template
  • It generates a shopping card html table with product, quantity and final price
Product Qty Final Price
Smartphone 5 2500
Wireless B 50 49960
Smartphone 20 1990
  • The product name is only the first 11 characters, but we need to know the full name.
  • It only shows the final price which is Price * Qty – discount.
  • The discount is dependent of the quantity.
  • c__ captures the complete name but doesn’t change the design.
  • c__ captures the qty and labels it as Qty.
  • c__ captures the discount value.
  • d__ outputs to sys.stdout all the captured inputs and the final price.

The stdout will display these lines:

i0:`Smartphone 128GB` | qty:`5` | i2:`500` | discount:`0` | _:`2500`
i0:`Wireless Bluetooth Headphones` | qty:`50` | i2:`1000` | discount:`40` | _:`49960`
i0:`Smartphone 64GB Black` | qty:`20` | i2:`100` | discount:`10` | _:`1990`
i0:`Smartphone 128GB` | qty:`5` | i2:`500` | discount:`0` | _:`2500`
i0:`Wireless Bluetooth Headphones` | qty:`50` | i2:`1000` | discount:`40` | _:`49960`
i0:`Smartphone 64GB Black` | qty:`20` | i2:`100` | discount:`10` | _:`1990`
i0:`Smartphone 128GB` | qty:`5` | i2:`500` | discount:`0` | _:`2500` i0:`Wireless Bluetooth Headphones` | qty:`50` | i2:`1000` | discount:`40` | _:`49960` i0:`Smartphone 64GB Black` | qty:`20` | i2:`100` | discount:`10` | _:`1990`

Enter fullscreen mode Exit fullscreen mode

Jinja2 template:

<span><html</span> <span>lang=</span><span>"en"</span><span>></span>
<span><head><link</span> <span>href=</span><span>"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css"</span> <span>rel=</span><span>"stylesheet"</span><span>></head></span>
<span><body></span>
<span><div</span> <span>class=</span><span>"container mt-5"</span><span>></span>
<span><h1></span>Shopping Cart<span></h1></span>
<span><table</span> <span>class=</span><span>"table table-striped"</span><span>></span>
<span><tr><th></span>Product<span></th><th></span>Qty<span></th><th></span>Final Price<span></th></tr></span>
{% for item in purchases %}
{% set product = products[item['product']] %}
<span><tr></span>
<span><td></span>{{ c__(product['name'])[0:10] }}<span></td></span>
<span><td></span>{{ c__(item['qty'], name='qty') }}<span></td></span>
<span><td></span>{{ d__(c__(product['price']) * item['qty']
- c__(discount(item['qty']), name='discount')) }}<span></td></span>
<span></tr></span>
{% endfor %}
<span></table></span>
<span></div></span>
<span></body></span>
<span></html></span>
<span><html</span> <span>lang=</span><span>"en"</span><span>></span>
<span><head><link</span> <span>href=</span><span>"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css"</span> <span>rel=</span><span>"stylesheet"</span><span>></head></span>
<span><body></span>
    <span><div</span> <span>class=</span><span>"container mt-5"</span><span>></span>
        <span><h1></span>Shopping Cart<span></h1></span>
        <span><table</span> <span>class=</span><span>"table table-striped"</span><span>></span>
            <span><tr><th></span>Product<span></th><th></span>Qty<span></th><th></span>Final Price<span></th></tr></span>
            {% for item in purchases %}
            {% set product = products[item['product']] %}
            <span><tr></span>
                <span><td></span>{{ c__(product['name'])[0:10] }}<span></td></span>
                <span><td></span>{{ c__(item['qty'], name='qty') }}<span></td></span>
                <span><td></span>{{ d__(c__(product['price']) * item['qty']
                    - c__(discount(item['qty']), name='discount')) }}<span></td></span>
            <span></tr></span>
            {% endfor %}
        <span></table></span>
    <span></div></span>
<span></body></span>
<span></html></span>
<html lang="en"> <head><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"></head> <body> <div class="container mt-5"> <h1>Shopping Cart</h1> <table class="table table-striped"> <tr><th>Product</th><th>Qty</th><th>Final Price</th></tr> {% for item in purchases %} {% set product = products[item['product']] %} <tr> <td>{{ c__(product['name'])[0:10] }}</td> <td>{{ c__(item['qty'], name='qty') }}</td> <td>{{ d__(c__(product['price']) * item['qty'] - c__(discount(item['qty']), name='discount')) }}</td> </tr> {% endfor %} </table> </div> </body> </html>

Enter fullscreen mode Exit fullscreen mode

app.py:

<span>from</span> <span>flask</span> <span>import</span> <span>Flask</span><span>,</span> <span>render_template</span>
<span>from</span> <span>pytracetoix</span> <span>import</span> <span>c__</span><span>,</span> <span>d__</span>
<span>app</span> <span>=</span> <span>Flask</span><span>(</span><span>__name__</span><span>)</span>
<span>app</span><span>.</span><span>Jinja2_env</span><span>.</span><span>globals</span><span>[</span><span>'</span><span>d__</span><span>'</span><span>]</span> <span>=</span> <span>d__</span>
<span>app</span><span>.</span><span>Jinja2_env</span><span>.</span><span>globals</span><span>[</span><span>'</span><span>c__</span><span>'</span><span>]</span> <span>=</span> <span>c__</span>
<span>DISCOUNTS</span> <span>=</span> <span>{</span><span>50</span><span>:</span> <span>40</span><span>,</span> <span>20</span><span>:</span> <span>10</span><span>,</span> <span>10</span><span>:</span> <span>5</span><span>,</span> <span>0</span><span>:</span> <span>0</span><span>}</span>
<span>PRODUCTS</span> <span>=</span> <span>{</span>
<span>'</span><span>WB50CC</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>name</span><span>'</span><span>:</span> <span>'</span><span>Wireless Bluetooth Headphones</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>:</span> <span>1000</span><span>},</span>
<span>'</span><span>PH20XX</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>name</span><span>'</span><span>:</span> <span>'</span><span>Smartphone 128GB</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>:</span> <span>500</span><span>},</span>
<span>'</span><span>PH50YY</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>name</span><span>'</span><span>:</span> <span>'</span><span>Smartphone 64GB Black</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>:</span> <span>100</span><span>}</span>
<span>}</span>
<span>PURCHASES</span> <span>=</span> <span>[</span>
<span>{</span><span>'</span><span>product</span><span>'</span><span>:</span> <span>'</span><span>PH20XX</span><span>'</span><span>,</span> <span>'</span><span>qty</span><span>'</span><span>:</span> <span>5</span><span>},</span>
<span>{</span><span>'</span><span>product</span><span>'</span><span>:</span> <span>'</span><span>WB50CC</span><span>'</span><span>,</span> <span>'</span><span>qty</span><span>'</span><span>:</span> <span>50</span><span>},</span>
<span>{</span><span>'</span><span>product</span><span>'</span><span>:</span> <span>'</span><span>PH50YY</span><span>'</span><span>,</span> <span>'</span><span>qty</span><span>'</span><span>:</span> <span>20</span><span>}</span>
<span>]</span>
<span>def</span> <span>discount</span><span>(</span><span>qty</span><span>):</span> <span>return</span> <span>next</span><span>((</span><span>k</span><span>,</span> <span>v</span><span>)</span> <span>for</span> <span>k</span><span>,</span> <span>v</span> <span>in</span> <span>DISCOUNTS</span><span>.</span><span>items</span><span>()</span> <span>if</span> <span>k</span> <span><=</span> <span>qty</span><span>)[</span><span>1</span><span>]</span>
<span>@app.route</span><span>(</span><span>'</span><span>/</span><span>'</span><span>,</span> <span>methods</span><span>=</span><span>[</span><span>'</span><span>GET</span><span>'</span><span>])</span>
<span>def</span> <span>index</span><span>():</span>
<span>return</span> <span>render_template</span><span>(</span><span>'</span><span>index.html</span><span>'</span><span>,</span> <span>products</span><span>=</span><span>PRODUCTS</span><span>,</span> <span>purchases</span><span>=</span><span>PURCHASES</span><span>,</span> <span>discount</span><span>=</span><span>discount</span><span>)</span>
<span>if</span> <span>__name__</span> <span>==</span> <span>'</span><span>__main__</span><span>'</span><span>:</span>
<span>app</span><span>.</span><span>run</span><span>(</span><span>debug</span><span>=</span><span>True</span><span>)</span>
<span>from</span> <span>flask</span> <span>import</span> <span>Flask</span><span>,</span> <span>render_template</span>
<span>from</span> <span>pytracetoix</span> <span>import</span> <span>c__</span><span>,</span> <span>d__</span>

<span>app</span> <span>=</span> <span>Flask</span><span>(</span><span>__name__</span><span>)</span>

<span>app</span><span>.</span><span>Jinja2_env</span><span>.</span><span>globals</span><span>[</span><span>'</span><span>d__</span><span>'</span><span>]</span> <span>=</span> <span>d__</span>
<span>app</span><span>.</span><span>Jinja2_env</span><span>.</span><span>globals</span><span>[</span><span>'</span><span>c__</span><span>'</span><span>]</span> <span>=</span> <span>c__</span>

<span>DISCOUNTS</span> <span>=</span> <span>{</span><span>50</span><span>:</span> <span>40</span><span>,</span> <span>20</span><span>:</span> <span>10</span><span>,</span> <span>10</span><span>:</span> <span>5</span><span>,</span> <span>0</span><span>:</span> <span>0</span><span>}</span>
<span>PRODUCTS</span> <span>=</span> <span>{</span>
    <span>'</span><span>WB50CC</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>name</span><span>'</span><span>:</span> <span>'</span><span>Wireless Bluetooth Headphones</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>:</span> <span>1000</span><span>},</span>
    <span>'</span><span>PH20XX</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>name</span><span>'</span><span>:</span> <span>'</span><span>Smartphone 128GB</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>:</span> <span>500</span><span>},</span>
    <span>'</span><span>PH50YY</span><span>'</span><span>:</span> <span>{</span><span>'</span><span>name</span><span>'</span><span>:</span> <span>'</span><span>Smartphone 64GB Black</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>:</span> <span>100</span><span>}</span>
<span>}</span>

<span>PURCHASES</span> <span>=</span> <span>[</span>
    <span>{</span><span>'</span><span>product</span><span>'</span><span>:</span> <span>'</span><span>PH20XX</span><span>'</span><span>,</span> <span>'</span><span>qty</span><span>'</span><span>:</span> <span>5</span><span>},</span>
    <span>{</span><span>'</span><span>product</span><span>'</span><span>:</span> <span>'</span><span>WB50CC</span><span>'</span><span>,</span> <span>'</span><span>qty</span><span>'</span><span>:</span> <span>50</span><span>},</span>
    <span>{</span><span>'</span><span>product</span><span>'</span><span>:</span> <span>'</span><span>PH50YY</span><span>'</span><span>,</span> <span>'</span><span>qty</span><span>'</span><span>:</span> <span>20</span><span>}</span>
<span>]</span>


<span>def</span> <span>discount</span><span>(</span><span>qty</span><span>):</span> <span>return</span> <span>next</span><span>((</span><span>k</span><span>,</span> <span>v</span><span>)</span> <span>for</span> <span>k</span><span>,</span> <span>v</span> <span>in</span> <span>DISCOUNTS</span><span>.</span><span>items</span><span>()</span> <span>if</span> <span>k</span> <span><=</span> <span>qty</span><span>)[</span><span>1</span><span>]</span>


<span>@app.route</span><span>(</span><span>'</span><span>/</span><span>'</span><span>,</span> <span>methods</span><span>=</span><span>[</span><span>'</span><span>GET</span><span>'</span><span>])</span>
<span>def</span> <span>index</span><span>():</span>
    <span>return</span> <span>render_template</span><span>(</span><span>'</span><span>index.html</span><span>'</span><span>,</span> <span>products</span><span>=</span><span>PRODUCTS</span><span>,</span> <span>purchases</span><span>=</span><span>PURCHASES</span><span>,</span> <span>discount</span><span>=</span><span>discount</span><span>)</span>


<span>if</span> <span>__name__</span> <span>==</span> <span>'</span><span>__main__</span><span>'</span><span>:</span>
    <span>app</span><span>.</span><span>run</span><span>(</span><span>debug</span><span>=</span><span>True</span><span>)</span>
from flask import Flask, render_template from pytracetoix import c__, d__ app = Flask(__name__) app.Jinja2_env.globals['d__'] = d__ app.Jinja2_env.globals['c__'] = c__ DISCOUNTS = {50: 40, 20: 10, 10: 5, 0: 0} PRODUCTS = { 'WB50CC': {'name': 'Wireless Bluetooth Headphones', 'price': 1000}, 'PH20XX': {'name': 'Smartphone 128GB', 'price': 500}, 'PH50YY': {'name': 'Smartphone 64GB Black', 'price': 100} } PURCHASES = [ {'product': 'PH20XX', 'qty': 5}, {'product': 'WB50CC', 'qty': 50}, {'product': 'PH50YY', 'qty': 20} ] def discount(qty): return next((k, v) for k, v in DISCOUNTS.items() if k <= qty)[1] @app.route('/', methods=['GET']) def index(): return render_template('index.html', products=PRODUCTS, purchases=PURCHASES, discount=discount) if __name__ == '__main__': app.run(debug=True)

Enter fullscreen mode Exit fullscreen mode

If the previous example, we add c__ to the discount function on app.py:

<span>def</span> <span>discount</span><span>(</span><span>qty</span><span>):</span> <span>return</span> <span>c__</span><span>(</span><span>next</span><span>((</span><span>k</span><span>,</span> <span>v</span><span>)</span> <span>for</span> <span>k</span><span>,</span> <span>v</span> <span>in</span> <span>DISCOUNTS</span><span>.</span><span>items</span><span>()</span> <span>if</span> <span>k</span> <span><=</span> <span>qty</span><span>))[</span><span>1</span><span>]</span>
<span>def</span> <span>discount</span><span>(</span><span>qty</span><span>):</span> <span>return</span> <span>c__</span><span>(</span><span>next</span><span>((</span><span>k</span><span>,</span> <span>v</span><span>)</span> <span>for</span> <span>k</span><span>,</span> <span>v</span> <span>in</span> <span>DISCOUNTS</span><span>.</span><span>items</span><span>()</span> <span>if</span> <span>k</span> <span><=</span> <span>qty</span><span>))[</span><span>1</span><span>]</span>
def discount(qty): return c__(next((k, v) for k, v in DISCOUNTS.items() if k <= qty))[1]

Enter fullscreen mode Exit fullscreen mode

It will add richer discount information to the output:

i0:`Smartphone 128GB` | qty:`5` | i2:`500` | i3:`(0, 0)` | discount:`0` | _:`2500`
i0:`Wireless Bluetooth Headphones` | qty:`50` | i2:`1000` | i3:`(50, 40)` | discount:`40` | _:`49960`
i0:`Smartphone 64GB Black` | qty:`20` | i2:`100` | i3:`(20, 10)` | discount:`10` | _:`1990`
i0:`Smartphone 128GB` | qty:`5` | i2:`500` | i3:`(0, 0)` | discount:`0` | _:`2500`
i0:`Wireless Bluetooth Headphones` | qty:`50` | i2:`1000` | i3:`(50, 40)` | discount:`40` | _:`49960`
i0:`Smartphone 64GB Black` | qty:`20` | i2:`100` | i3:`(20, 10)` | discount:`10` | _:`1990`
i0:`Smartphone 128GB` | qty:`5` | i2:`500` | i3:`(0, 0)` | discount:`0` | _:`2500` i0:`Wireless Bluetooth Headphones` | qty:`50` | i2:`1000` | i3:`(50, 40)` | discount:`40` | _:`49960` i0:`Smartphone 64GB Black` | qty:`20` | i2:`100` | i3:`(20, 10)` | discount:`10` | _:`1990`

Enter fullscreen mode Exit fullscreen mode

原文链接:PyTraceToIX – How to debug Jinja2 templates, Flask web apps without breaking the design or code changes

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
Have faith in your dreams and someday your rainbow will come smiling through.
请对梦想充满信心,总有一天属于你的彩虹会在天空微笑
评论 抢沙发

请登录后发表评论

    暂无评论内容