Skills
A skill is a package of structured files that teaches an AI coding agent how to work with a specific tool or framework. The skill below was generated by Great Docs from this project’s documentation. Install it in your agent and it will be able to run commands, edit configuration, write content, and troubleshoot problems without step-by-step guidance from you.
Any agent — install with npx:
npx skills add https://mkennedy.codes/docs/chameleon-flask/Codex / OpenCode
Tell the agent:
Fetch the skill file at https://mkennedy.codes/docs/chameleon-flask/skill.md and follow the instructions.Manual — download the skill file:
curl -O https://mkennedy.codes/docs/chameleon-flask/skill.mdOr browse the SKILL.md file.
SKILL.md
--- name: chameleon-flask description: > Adds integration of the Chameleon template language to Flask and Quart. Use when writing Python code that uses the chameleon_flask package. license: MIT compatibility: Requires Python >=3.10. --- # chameleon-flask Adds integration of the Chameleon template language to Flask and Quart. ## Installation ```bash pip install chameleon-flask ``` ## When to use what | Need | Use | |------|-----| | Render a Chameleon template from a Flask/Quart view | `@chameleon_flask.template('home/index.pt') on the view function` | | Return a friendly 404 page from a view | `chameleon_flask.not_found()` | | Build a rendered Response outside a decorated view | `chameleon_flask.response(template_file, **model)` | | Get rendered HTML as a plain string | `chameleon_flask.engine.render(template_file, **model)` | | Use Alpine.js/Vue shorthand attributes in templates | `global_init(..., restricted_namespace=False)` | ## API overview ### Configuration Set up the Chameleon template engine once at app startup. - `global_init`: Initialize the Chameleon template engine - `engine.clear` ### Decorating views Render templates from Flask/Quart view functions (sync or async) and return friendly 404s. - `template`: Decorate a Flask or Quart view method to render a Chameleon template - `not_found`: Abort the current view and render a friendly 404 page ### Direct rendering Render a template to a Response or raw HTML without the decorator. - `response`: Render a Chameleon template directly to a `flask.Response` - `engine.render` ### Exceptions Errors raised by the engine. Importable from `chameleon_flask` directly or from `chameleon_flask.exceptions`. - `FlaskChameleonException`: Base exception for all chameleon-flask errors - `FlaskChameleonNotFoundException`: Raised by `not_found()` to signal that a view should render a 404 page ## Gotchas 1. global_init() is one-shot by default: with cache_init=True, later calls are silently ignored. Pass cache_init=False or call chameleon_flask.engine.clear() to re-initialize. 2. The bare @template form resolves the template file name once, at decoration time — call global_init() before defining views that rely on it. 3. Decorated views must return a dict (the template model) or a Flask/Quart Response; any other return type raises FlaskChameleonException at request time. 4. Chameleon's restricted namespace (the default) rejects Alpine.js/Vue shorthand attributes like @click, :class, and x-data. Pass restricted_namespace=False to global_init() to allow them. 5. The 404 path from not_found() always renders with text/html and status 404; the view's own content_type and status_code do not apply. 6. The decorator keyword is content_type, not mimetype (renamed in an earlier release). ## Best practices - Call global_init(template_folder, auto_reload=dev_mode) exactly once at app startup, before any views are defined. - Return plain dicts from views; return a Response only for redirects and other pass-through cases. - Enable auto_reload only during development so templates stay cached in production. - Use chameleon_flask.response() inside error handlers and other spots where the decorator doesn't fit. ## End-to-end wiring A complete, minimal app — engine init, a decorated view, and the template it renders. The `@template` path is always relative to the folder passed to `global_init()`. ```python # app.py from pathlib import Path import flask import chameleon_flask app = flask.Flask(__name__) # Init once at startup, BEFORE any decorated view is defined. templates = Path(__file__).resolve().parent / 'templates' chameleon_flask.global_init(str(templates), auto_reload=True) # auto_reload for dev only @app.get('/') @chameleon_flask.template('home/index.pt') # route decorator OUTERMOST, @template just above the function def index(): return {'title': 'Home', 'items': ['a', 'b', 'c']} # dict == the template model ``` ```html <!-- templates/home/index.pt --> <!DOCTYPE html> <html lang="en"> <body> <h1>${title}</h1> <ul> <li tal:repeat="item items">${item}</li> </ul> </body> </html> ``` ## Chameleon template syntax (this is TAL, not Jinja) Chameleon templates are valid XML/HTML where directives live in `tal:`, `metal:`, and `i18n:` attributes. There is no `{% ... %}` or `{ ... }` — do not use Jinja/Django syntax. Interpolation uses `${ ... }` and may contain arbitrary Python expressions. ```html <!-- Interpolation: any Python expression inside ${ } --> <h1>Hello, ${user.name.title()}!</h1> <p>You have ${len(items)} item(s).</p> <!-- Loop --> <li tal:repeat="item items">${item.name} — ${item.price}</li> <!-- The repeat variable exposes index/number/even/odd/first/last --> <li tal:repeat="item items" tal:attributes="class 'odd' if repeat.item.odd else 'even'"> ${repeat.item.number}. ${item} </li> <!-- Condition (element is omitted entirely when falsy) --> <div tal:condition="user">Welcome back, ${user.name}.</div> <div tal:condition="not user">Please sign in.</div> <!-- Set element content / replace whole element --> <span tal:content="message">placeholder shown only in a browser preview</span> <span tal:replace="formatted_date">2024-01-01</span> <!-- Set attributes (semicolon-separated); escaping is automatic --> <a tal:attributes="href item.url; class item.css_class">${item.name}</a> <!-- Define a reusable local variable --> <span tal:define="total sum(i.price for i in items)">Total: ${total}</span> ``` Escaping is on by default (`${expr}` is HTML-escaped). Use `structure:` to emit already-safe HTML without escaping: `<div tal:content="structure: raw_html"></div>`. ## Shared layouts with METAL macros METAL is how Chameleon does template inheritance / partials — the equivalent of Jinja's `{% extends %}`/`{% block %}`. ```html <!-- templates/shared/layout.pt --> <html metal:define-macro="layout"> <head><title>${title}</title></head> <body> <main metal:define-slot="content">default content</main> </body> </html> ``` ```html <!-- templates/home/index.pt --> <div metal:use-macro="load: ../shared/layout.pt"> <div metal:fill-slot="content"> <h1>${title}</h1> </div> </div> ``` ## Template resolution & project layout With an explicit path (`@template('catalog/item.pt')`) the string is resolved relative to the `global_init()` folder. With the bare form (`@template` or `@template()`) the path is derived **once at decoration time** as `{last segment of module}/{function_name}.html`, falling back to `.pt` if the `.html` file does not exist on disk. ``` my_app/ ├── app.py # global_init() here, before views are imported/defined ├── views/ │ └── home.py # def index(...) -> bare @template looks for home/index.html|.pt ├── templates/ │ ├── home/index.pt │ ├── errors/404.pt # default target of not_found() │ └── shared/layout.pt # METAL macros └── static/ ``` ## Flask and Quart The same decorator API works for both frameworks and for both sync and async views — async is detected automatically, so no separate import or flag is needed. The library never imports Quart; it recognizes Quart responses through the shared werkzeug response base class. ```python # Works identically whether app is flask.Flask(__name__) or quart.Quart(__name__) @app.get('/') @chameleon_flask.template('home/index.pt') async def index(): return {'items': await load_items()} ``` ## Alpine.js / Vue shorthand in templates `restricted_namespace=True` (the default) makes Chameleon reject non-TAL/METAL/i18n namespaced attributes, which includes Alpine.js/Vue shorthand like `@click`, `:class`, and `x-data`. Initialize with `restricted_namespace=False` to allow them, then use the shorthand normally in templates. ```python chameleon_flask.global_init(str(templates), restricted_namespace=False) ``` ```html <div x-data="{ open: false }"> <button @click="open = !open">Toggle</button> <div :class="{ hidden: !open }">Content</div> </div> ``` ## Fetching the docs as Markdown Every page on the documentation site has a plain-Markdown twin: swap the `.html` extension for `.md` to get token-efficient source without the site chrome. For example https://mkennedy.codes/docs/chameleon-flask/reference/template.html is also available at https://mkennedy.codes/docs/chameleon-flask/reference/template.md. Prefer the `.md` form when reading these docs programmatically. ## Resources - [Full documentation](https://mkennedy.codes/docs/chameleon-flask/) - [llms.txt](llms.txt) — Indexed API reference for LLMs - [llms-full.txt](llms-full.txt) — Comprehensive documentation for LLMs