{"id":13424708,"url":"https://github.com/mikeckennedy/fastapi-chameleon","last_synced_at":"2026-06-11T18:00:42.429Z","repository":{"id":38289265,"uuid":"307430870","full_name":"mikeckennedy/fastapi-chameleon","owner":"mikeckennedy","description":"Adds integration of the Chameleon template language to FastAPI. #pypackage","archived":false,"fork":false,"pushed_at":"2026-06-10T02:45:38.000Z","size":628,"stargazers_count":227,"open_issues_count":1,"forks_count":37,"subscribers_count":8,"default_branch":"main","last_synced_at":"2026-06-10T04:07:49.053Z","etag":null,"topics":["chameleon","fastapi","fastapi-template","python","web"],"latest_commit_sha":null,"homepage":"https://mkennedy.codes/docs/fastapi-chameleon/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mikeckennedy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["mikeckennedy"]}},"created_at":"2020-10-26T16:11:14.000Z","updated_at":"2026-06-10T02:45:41.000Z","dependencies_parsed_at":"2025-03-25T12:10:51.202Z","dependency_job_id":"391a47e6-3163-4b91-9aa1-c85debe5c6b2","html_url":"https://github.com/mikeckennedy/fastapi-chameleon","commit_stats":{"total_commits":45,"total_committers":7,"mean_commits":6.428571428571429,"dds":0.2222222222222222,"last_synced_commit":"7ef7627de5b508a009e2350bc7d5548a710b800b"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/mikeckennedy/fastapi-chameleon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeckennedy%2Ffastapi-chameleon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeckennedy%2Ffastapi-chameleon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeckennedy%2Ffastapi-chameleon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeckennedy%2Ffastapi-chameleon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikeckennedy","download_url":"https://codeload.github.com/mikeckennedy/fastapi-chameleon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikeckennedy%2Ffastapi-chameleon/sbom","scorecard":{"id":645367,"data":{"date":"2025-08-11","repo":{"name":"github.com/mikeckennedy/fastapi-chameleon","commit":"29ef54fe9b2d34722b0013582e8ce6f800b3560d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.7,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":1,"reason":"Found 3/26 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2021-100 / GHSA-8h2j-cgx8-6xv7","Warn: Project is vulnerable to: PYSEC-2024-38","Warn: Project is vulnerable to: PYSEC-2020-150 / GHSA-33c7-2mpw-hg34","Warn: Project is vulnerable to: PYSEC-2020-151 / GHSA-f97h-2pfx-f59f"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 7 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-21T12:00:43.924Z","repository_id":38289265,"created_at":"2025-08-21T12:00:43.924Z","updated_at":"2025-08-21T12:00:43.924Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34211067,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chameleon","fastapi","fastapi-template","python","web"],"created_at":"2024-07-31T00:00:58.156Z","updated_at":"2026-06-11T18:00:42.423Z","avatar_url":"https://github.com/mikeckennedy.png","language":"Python","funding_links":["https://github.com/sponsors/mikeckennedy"],"categories":["Third-Party Extensions"],"sub_categories":["Utils"],"readme":"# fastapi-chameleon\n\n[![PyPI version](https://img.shields.io/pypi/v/fastapi-chameleon.svg)](https://pypi.org/project/fastapi-chameleon/)\n[![Python versions](https://img.shields.io/pypi/pyversions/fastapi-chameleon.svg)](https://pypi.org/project/fastapi-chameleon/)\n[![License](https://img.shields.io/pypi/l/fastapi-chameleon.svg)](https://github.com/mikeckennedy/fastapi-chameleon/blob/main/LICENSE)\n[![Docs](https://img.shields.io/badge/docs-mkennedy.codes-blue.svg)](https://mkennedy.codes/docs/fastapi-chameleon/)\n\nAdds integration of the [Chameleon template language](https://chameleon.readthedocs.io/) to [FastAPI](https://fastapi.tiangolo.com/). If you are interested in Jinja instead, see the sister project: [github.com/AGeekInside/fastapi-jinja](https://github.com/AGeekInside/fastapi-jinja).\n\n**Documentation:** full docs and a per-function API reference live at [mkennedy.codes/docs/fastapi-chameleon](https://mkennedy.codes/docs/fastapi-chameleon/). An [llms.txt](https://mkennedy.codes/docs/fastapi-chameleon/llms.txt) index is available for AI coding tools.\n\n## Features\n\n- **One decorator** turns a FastAPI view into a server-rendered HTML page: return a `dict`, get a rendered template.\n- **Sync and async views** are both fully supported.\n- **`fastapi.Response` pass-through**: return a `Response` (redirect, JSON, etc.) from a decorated view and the template is skipped entirely.\n- **Friendly error pages**: `not_found()` renders a custom 404 page, `generic_error()` renders any template with any status code.\n- **Template name inference**: leave the template name off and it's derived from the module and function name.\n- **Dev mode**: `auto_reload=True` picks up template edits without restarting the server.\n- **Fully typed**: ships inline type hints with a `py.typed` marker (PEP 561). The decorator uses `ParamSpec`-based overloads and `functools.wraps`, so a decorated view keeps its exact parameter signature — FastAPI's dependency injection and type checkers like [ty](https://github.com/astral-sh/ty) and [pyrefly](https://pyrefly.org/) keep working.\n- **Tiny dependency footprint**: just `fastapi` and `chameleon`.\n\n## Installation\n\n```bash\npip install fastapi-chameleon\n```\n\n## Quick start\n\nA minimal but complete app — two files.\n\n**`main.py`**\n\n```python\nfrom pathlib import Path\n\nimport fastapi\nimport uvicorn\n\nimport fastapi_chameleon\n\napp = fastapi.FastAPI()\n\n# Point the engine at your template folder (do this before views are registered).\nBASE_DIR = Path(__file__).resolve().parent\nfastapi_chameleon.global_init(str(BASE_DIR / 'templates'), auto_reload=True)\n\n\n@app.get('/')\n@fastapi_chameleon.template('index.pt')\ndef hello_world():\n    return {'message': \"Let's go Chameleon and FastAPI!\"}\n\n\nif __name__ == '__main__':\n    uvicorn.run(app, host='127.0.0.1', port=8000)\n```\n\n**`templates/index.pt`**\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003ctitle\u003eTitle\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eHello world\u003c/h1\u003e\n\u003cp\u003eYour message is \u003cstrong\u003e${message}\u003c/strong\u003e\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nRun it with `python main.py` (or `uvicorn main:app`) and visit `http://127.0.0.1:8000`. The dict returned from the view becomes the template's variables: `{'message': ...}` renders into `${message}`.\n\nChameleon templates are plain HTML5 with `${expr}` interpolation plus the full TAL attribute language (`tal:repeat`, `tal:content`, and friends) in either `.pt` or `.html` files.\n\nNote the decorator order: the route decorator (`@app.get(...)`) goes on the outside, and `@fastapi_chameleon.template(...)` is applied directly to the view function.\n\n## Usage\n\n### Project layout\n\nCreate a folder within your web app to hold the templates, such as:\n\n```\n├── main.py\n├── views.py\n│\n└── templates\n    ├── home\n    │   └── index.pt\n    ├── errors\n    │   └── 404.pt\n    └── shared\n        └── layout.pt\n```\n\nIn the app startup, tell the library about the folder you wish to use:\n\n```python\nfrom pathlib import Path\nimport fastapi_chameleon\n\ndev_mode = True\n\nBASE_DIR = Path(__file__).resolve().parent\ntemplate_folder = str(BASE_DIR / 'templates')\nfastapi_chameleon.global_init(template_folder, auto_reload=dev_mode)\n```\n\n`global_init()` validates the folder (it raises `FastAPIChameleonException` if the path is empty or not an existing directory) and is idempotent by default: a second call is a no-op while templates are already initialized. Pass `cache_init=False` to force re-initialization (handy in tests).\n\n\u003e **Order matters:** call `global_init()` *before* importing/registering your view modules. Template name inference (below) resolves at decoration time; if the engine isn't initialized yet, the path silently defaults to `templates/` relative to the current working directory, which may not be what you want. If you always pass explicit template names, this is much less of a concern.\n\n### Decorating views\n\nThen just decorate the FastAPI view methods (works on sync and async methods):\n\n```python\n@router.post('/')\n@fastapi_chameleon.template('home/index.pt')\nasync def home_post(request: Request):\n    form = await request.form()\n    vm = PersonViewModel(**form)\n\n    return vm.dict()  # {'first': 'Michael', 'last': 'Kennedy', ...}\n```\n\nThe view method should return a `dict` to be passed as variables/values to the template.\n\nIf a `fastapi.Response` is returned, the template is skipped and the response along with `status_code` and other values is directly passed through. This is common for redirects and error responses not meant for this page template:\n\n```python\n@router.post('/account/login')\n@fastapi_chameleon.template('account/login.pt')\nasync def login(request: Request):\n    user = await try_login(request)\n    if user:\n        return fastapi.responses.RedirectResponse('/account', status_code=302)\n\n    return {'error': 'Invalid login'}  # re-render the form with an error\n```\n\nReturning anything other than a `dict` or a `fastapi.Response` raises `FastAPIChameleonException`.\n\nThe decorator also accepts a `mimetype` for non-HTML output, e.g. `@fastapi_chameleon.template('seo/sitemap.pt', mimetype='application/xml')`.\n\n### Three ways to use the decorator\n\n```python\n@fastapi_chameleon.template('home/index.pt')   # explicit template file\n@fastapi_chameleon.template()                  # inferred template name\n@fastapi_chameleon.template                    # bare form, also inferred\n```\n\nWhen no template name is given, it's derived from where the view lives:\n\n- The file is `{module}/{function_name}` under the template folder, where `module` is the last segment of the view's dotted module name.\n- An `.html` file is preferred; if it doesn't exist, `.pt` is the fallback.\n- Example: `def index()` in `views/home.py` resolves to `templates/home/index.html`, falling back to `templates/home/index.pt`.\n\nThis resolution happens once at import time, so there is zero per-request filesystem overhead.\n\nA few error behaviors worth knowing:\n\n- Calling a decorated view without ever calling `global_init()` raises `FastAPIChameleonException` at request time.\n- Referencing a template file that doesn't exist raises `ValueError` (from Chameleon's loader) when the view is called.\n\n## Friendly 404s and errors\n\nA common technique for user-friendly sites is to use a [custom HTML page for 404 responses](https://www.instantshift.com/2019/10/16/user-friendly-404-pages/). This is especially important in FastAPI because FastAPI returns a 404 response + JSON by default. This library has support for friendly 404 pages using the `fastapi_chameleon.not_found()` function.\n\nHere's an example:\n\n```python\n@router.get('/catalog/item/{item_id}')\n@fastapi_chameleon.template('catalog/item.pt')\nasync def item(item_id: int):\n    item = service.get_item_by_id(item_id)\n    if not item:\n        fastapi_chameleon.not_found()\n\n    return item.dict()\n```\n\nThis will render a 404 response using the template file `templates/errors/404.pt`. You can specify another template to use for the response, but it's not required:\n\n```python\nfastapi_chameleon.not_found(four04template_file='errors/custom_404.pt')\n```\n\n`not_found()` works by raising an exception, so execution stops right there — code after the call never affects the response. The 404 template is rendered with an empty model.\n\nBecause the decorator is what catches the exception, you can call `not_found()` or `generic_error()` anywhere beneath a decorated view — deep in a service or data-access layer works fine. The flip side: calling them from a route that is *not* decorated with `@fastapi_chameleon.template` (or from middleware/dependencies) leaves the exception unhandled and FastAPI will return a 500 instead of your error page.\n\nIf you need to return errors other than `Not Found` (status code `404`), you can use a more generic function: `fastapi_chameleon.generic_error()`. It lets you render any error template with any status code:\n\n```python\n@router.get('/catalog/item/{item_id}')\n@fastapi_chameleon.template('catalog/item.pt')\nasync def item(item_id: int):\n    item = service.get_item_by_id(item_id)\n    if not item:\n        fastapi_chameleon.generic_error('errors/unauthorized.pt',\n                                        fastapi.status.HTTP_401_UNAUTHORIZED)\n\n    return item.dict()\n```\n\nYou can also pass data into the error template via the optional `template_data` dict:\n\n```python\nfastapi_chameleon.generic_error('errors/500.pt', 500,\n                                template_data={'detail': 'Something went sideways.'})\n```\n\nNote that error pages are always rendered as `text/html`, regardless of the `mimetype` passed to the `@template` decorator.\n\n## Manual rendering with `response()`\n\nIf you need full manual control — say, a non-200 status code or a non-HTML mimetype — without going through the decorator, use `response()`:\n\n```python\nimport fastapi_chameleon\n\n@router.get('/report')\ndef report():\n    return fastapi_chameleon.response('reports/summary.pt',\n                                      status_code=202,\n                                      title='Monthly summary')\n```\n\nIt renders the template with the keyword arguments as the model and wraps the result in a `fastapi.Response` with your chosen `mimetype` (default `'text/html'`) and `status_code` (default `200`).\n\n## API reference\n\nFull, per-function docs are at [mkennedy.codes/docs/fastapi-chameleon](https://mkennedy.codes/docs/fastapi-chameleon/). The summary below mirrors the public surface.\n\nEverything public is importable straight from `fastapi_chameleon`:\n\n```python\n__all__ = ['template', 'global_init', 'not_found', 'response', 'generic_error']\n```\n\n| Function | Signature | Purpose |\n|---|---|---|\n| `global_init` | `global_init(template_folder: str, auto_reload: bool = False, cache_init: bool = True) -\u003e None` | Initialize the template engine once at startup. No-op if already initialized (unless `cache_init=False`). |\n| `template` | `template(template_file=None, mimetype='text/html')` | Decorator for view functions. Usable bare, with empty parens, or with an explicit template path. |\n| `response` | `response(template_file: str, mimetype: str = 'text/html', status_code: int = 200, **template_data) -\u003e fastapi.Response` | Render a template and wrap it in a `Response` with full manual control. |\n| `not_found` | `not_found(four04template_file: str = 'errors/404.pt') -\u003e NoReturn` | Abort the view and render a friendly 404 page (always raises). |\n| `generic_error` | `generic_error(template_file: str, status_code: int, template_data: Optional[dict] = None) -\u003e NoReturn` | Abort the view and render any error template with any status code (always raises). |\n\nTwo more functions live in `fastapi_chameleon.engine` (not exported at package level):\n\n| Function | Signature | Purpose |\n|---|---|---|\n| `engine.render` | `render(template_file: str, **template_data) -\u003e str` | Render a template directly to an HTML string. |\n| `engine.clear` | `clear() -\u003e None` | Reset the cached loader and template path — the test-isolation hook. |\n\nExceptions, in `fastapi_chameleon.exceptions`:\n\n- `FastAPIChameleonException(Exception)` — base class; also raised for bad `global_init` input, missing init at render time, and invalid view return types.\n- `FastAPIChameleonNotFoundException` — raised by `not_found()`; carries `.template_file` and `.message`.\n- `FastAPIChameleonGenericException` — raised by `generic_error()`; carries `.template_file`, `.status_code`, `.message`, and `.template_data`.\n\n## Dev mode, caching, and performance\n\n- `auto_reload` defaults to `False`: Chameleon caches compiled templates for production performance. Set `auto_reload=True` during development to pick up template edits without restarting.\n- Engine state is a single module-global template loader per process. Call `global_init()` once before serving requests; after that the loader is read-only.\n\n## Testing your views\n\nDecorated views remain plain callables — no `TestClient` required. Call them directly (or via `asyncio.run()` for async views) and inspect the returned `fastapi.Response`:\n\n```python\n# conftest.py\nfrom pathlib import Path\n\nimport pytest\nimport fastapi_chameleon as fc\n\n@pytest.fixture\ndef test_templates_path(pytestconfig):\n    return Path(pytestconfig.rootdir, 'tests', 'templates')\n\n@pytest.fixture\ndef setup_global_template(test_templates_path):\n    fc.global_init(str(test_templates_path))\n    yield\n    fc.engine.clear()  # don't leak engine state between tests\n```\n\n```python\n# test_views.py\n# index_view is any view function decorated with @fastapi_chameleon.template(...)\ndef test_index_renders(setup_global_template):\n    resp = index_view()\n    assert resp.status_code == 200\n    assert 'Hello' in resp.body.decode('utf-8')\n```\n\nThis is exactly the pattern this project's own [test suite](https://github.com/mikeckennedy/fastapi-chameleon/tree/main/tests) uses.\n\n## Example app\n\nA small, runnable FastAPI app showing sync and async views lives in the [`example/`](https://github.com/mikeckennedy/fastapi-chameleon/tree/main/example) folder:\n\n```bash\ncd example\npython example_app.py\n```\n\nThen visit `http://127.0.0.1:8000` (and `/async` for the async view). Note that the example calls `global_init()` at runtime (from `main()`, via an `add_chameleon()` helper) rather than at import time, so run it with `python example_app.py` rather than via the `uvicorn` CLI.\n\n## Requirements\n\n- Python **3.10+** (supports up through 3.14)\n- `fastapi`\n- `chameleon`\n\nThat's the entire runtime dependency list.\n\n## Contributing\n\nPRs and issues are welcome at [github.com/mikeckennedy/fastapi-chameleon](https://github.com/mikeckennedy/fastapi-chameleon).\n\n```bash\ngit clone https://github.com/mikeckennedy/fastapi-chameleon.git\ncd fastapi-chameleon\npython -m venv venv \u0026\u0026 source venv/bin/activate\npip install -e \".[dev]\"   # pytest + ty + pyrefly\npytest\n```\n\nCode style is enforced with [Ruff](https://docs.astral.sh/ruff/) (`ruff.toml`: 120-character lines, single quotes), and the package is type-checked with [ty](https://github.com/astral-sh/ty) and [pyrefly](https://pyrefly.org/). Please run the full check before submitting:\n\n```bash\nruff check .\nty check fastapi_chameleon\npyrefly check fastapi_chameleon\npytest\n```\n\n(The `requirements-dev.txt` file additionally pulls in the docs toolchain — `great-docs`, `uvicorn`, `twine` — for building the documentation site.)\n\n## License\n\nMIT — see [LICENSE](https://github.com/mikeckennedy/fastapi-chameleon/blob/main/LICENSE).\n\nCreated by [Michael Kennedy](https://github.com/mikeckennedy) of [Talk Python](https://talkpython.fm).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikeckennedy%2Ffastapi-chameleon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikeckennedy%2Ffastapi-chameleon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikeckennedy%2Ffastapi-chameleon/lists"}