Compatibility
chameleon-rs’s goal is to produce exactly what Chameleon produces, for every template, and to raise the same errors. This page says how that’s measured, and lists the few places where chameleon-rs knowingly differs.
How it’s measured
Each level is a check that runs on every change:
| Check | Result |
|---|---|
| Chameleon’s own test suite. All 233 tests, vendored unmodified, run against chameleon-rs. | Every test that passes on Chameleon passes on chameleon-rs: 227 of 227. The other 6 depend on the test environment and are skipped on both engines. |
| Fixture renders. Every input template in Chameleon’s test suite, rendered by both engines. | All 122 byte-identical. |
| Differential fuzzing. Generated templates, models and expressions, rendered by both engines and compared. | No differences in 20,000 examples per generator. |
| Integration suites. The test suites of fastapi-chameleon, chameleon-flask, chameleon-robyn and chameleon_partials, unmodified, on chameleon-rs. | All pass. |
| Real sites. Every page of two production Flask/Quart apps, crawled with both engines: anonymous and signed in, with all their partials and layouts. | Every page byte-identical. |
The upstream suite and the fuzzers compare output byte for byte, and they also compare the type and message of any error a template raises.
What’s identical
- The template language: TAL, METAL, i18n,
${...}, every expression type, and whitespace and attribute handling, down to the byte. - The Python API: the template classes and their options, PageTemplateLoader, macros,
translatefunctions,tal:on-errorhandlers, andCHAMELEON_*environment variables. - The modules: chameleon_rs.compat.install() maps each Chameleon module to the chameleon-rs module of the same name.
- Errors: the same exception types, and the same formatted messages with the template position and expression.
- The helper libraries: chameleon_partials’ and chameleon-flask’s APIs, built in.
Known differences
These are deliberate. Each comes from chameleon-rs compiling to a native program rather than to Python source code.
| Area | Chameleon | chameleon-rs |
|---|---|---|
template.source |
The generated Python module’s code. | With keep_source=True, a readable dump of the compiled program. It isn’t Python. |
| Custom expression types | A class whose compiler returns Python AST statements. | Supported through a bridge that wraps the AST in a Python function, at the speed of an ordinary Python call. Types that depend on Chameleon’s compiler internals beyond that interface aren’t supported. |
CHAMELEON_CACHE |
Writes the generated modules to disk and loads them on the next start. | Checks the directory but writes nothing. Compiling is about 70x faster than Chameleon, so there’s little to cache. |
| Code-generation modules | chameleon.nodes, chameleon.program, chameleon.zpt.program, chameleon.benchmark. |
Not provided, since chameleon-rs doesn’t generate code. Importing them raises ModuleNotFoundError. |
The built-in partials have one behaviour change, which you can turn off: a path that isn’t in the configured template folder is also looked for next to the calling template, where chameleon_partials would raise. configure(partial_search='registered') restores the original lookup. See Partials.
Found a difference?
If a template renders differently on chameleon-rs than on Chameleon, that’s a bug. Please open an issue with the template, the model, and both outputs. Shadow mode can find such templates in a running app.