Skip to main content

PAX Loader

The PAXLoader is a custom Jinja2 loader that extends the standard FileSystemLoader. It adds three features:

  1. Template discovery across multiple folders
  2. Extension fallback between .pax and .html
  3. Slice syntax rewriting for backwards compatibility

This page explains how it works and why it exists.


Why PAXLoader Exists​

Standard Jinja2 requires exact file paths. If you write {% extends "base" %}, Jinja2 looks for templates/base.html — and fails if the file is named base.pax.

PAXLoader fixes this by searching multiple locations and extensions automatically.

It also rewrites Python-style slice syntax (posts[:3]) into a Jinja2 filter (posts | limit(3)), so templates written for other engines keep working.


Search Order​

When a template is requested, PAXLoader searches in this order:

  1. templates/<name>.pax
  2. templates/<name>.html
  3. templates/layouts/<name>.pax
  4. templates/layouts/<name>.html
  5. templates/partials/<name>.pax
  6. templates/partials/<name>.html

If none of these exist, it recursively searches templates/ for a file with the same basename.


Extension Fallback​

If you request a template without an extension:

{% extends "base" %}

PAXLoader tries:

  • templates/base.pax
  • templates/base.html
  • templates/layouts/base.pax
  • templates/layouts/base.html

If you request with an extension:

{% extends "base.pax" %}

PAXLoader tries:

  • templates/base.pax
  • templates/base.html (stem fallback)

Either way, the loader finds the template.


Slice Syntax Rewriting​

PAXLoader rewrites Python-style slicing into Jinja2 filters before the template is rendered.

OriginalRewritten
{{ posts[:3] }}{{ posts | limit(3) }}
{{ posts[1:3] }}{{ posts | slice(1, 3) }}
{{ posts[:5] }}{{ posts | limit(5) }}

This lets templates written for a Liquid-style or Python-style engine keep working without changes.

What It Does Not Rewrite​

  • Negative indices: posts[-3:]
  • Step syntax: posts[::2]
  • Multi-dimensional: posts[0][1]

Use the filter syntax directly for these.


How It Works Internally​

The loader overrides get_source — the method Jinja2 calls to load a template.

Here is the simplified flow:

get_source(env, template)
│
▼
Try exact match
│
▼
Try extension variants (.pax, .html)
│
▼
Try folder variants (layouts/, partials/)
│
▼
Recursive basename search
│
▼
Rewrite slice syntax
│
▼
Return source

If nothing matches, it raises TemplateNotFound.


Practical Examples​

Example 1 — Simple Layout​

Page template about.pax:

{% extends "layouts/base" %}

{% block content %}
<h1>{{ page.title }}</h1>
{{ content | safe }}
{% endblock %}

Loader finds templates/layouts/base.pax (or .html).

Example 2 — Partial Include​

Layout base.pax:

<body>
{% include "partials/nav" %}
<main>{% block content %}{% endblock %}</main>
{% include "partials/footer" %}
</body>

Loader finds templates/partials/nav.pax and templates/partials/footer.pax.

Example 3 — Slice Syntax​

Template with slice syntax:

{% for post in posts[:3] %}
<h2>{{ post.title }}</h2>
{% endfor %}

Loader rewrites to:

{% for post in posts | limit(3) %}
<h2>{{ post.title }}</h2>
{% endfor %}

When to Use .pax vs .html​

Functionally, they are identical. The convention:

UseFor
.paxTemplates that rely on slice syntax
.htmlEverything else

If you are not sure, use .html.


When to Avoid PAXLoader​

If you are building a template for a different Jinja2 project, use standard FileSystemLoader — PAXLoader is Pycora-specific.


Debugging Template Loading​

If a template is not found:

  1. Check that the file exists in templates/
  2. Check the spelling
  3. Check the file extension
  4. Run python ssg.py and look for the error message
  5. The error shows the exact template name that failed

Best Practices​

  • Use layouts/ for full page templates
  • Use partials/ for fragments
  • Prefer explicit paths — layouts/base over base
  • Use .html unless you need slice syntax
  • Keep the folder structure flat — do not nest deeper than layouts/ or partials/

Troubleshooting​

TemplateNotFound — file exists but not found​

  1. Check that the file is inside templates/
  2. Check that it is not in a subfolder other than layouts/ or partials/
  3. Check the file extension — .pax or .html

Wrong template is loaded​

If two templates share a name (e.g., base.pax and base.html), PAXLoader prefers .pax. Rename one to avoid confusion.

Slice syntax does not work​

  1. Check the template uses .pax extension
  2. Or convert to | limit(n) manually

Custom filter not found​

Check that the filter is registered in ssg.py on self.env.filters.


What's Next?​