Web dashboard broken on Python 3.14 + Starlette 1.0.0 - TemplateResponse API incompatibility #39

Closed
opened 2026-10-04 23:09:56 +02:00 by adam · 2 comments
Owner

Fresh install on Raspberry Pi 5 (ARM64), Raspberry Pi OS Bookworm 64-bit. Running the quickstart docker compose setup.

Environment

  • Python 3.14 (inside container)
  • Starlette 1.0.0 (inside container)
  • Docker on Raspberry Pi 5, ARM64
  • meshcore-hub latest tag

Steps to reproduce

  1. Clone repo
  2. Copy .env.example to .env, set SERIAL_PORT, TZ, WEB_DATETIME_LOCALE
  3. docker compose --profile migrate run --rm db-migrate
  4. docker compose --profile mqtt --profile core --profile receiver up -d
  5. Browse to http://[host]:8080

Result

500 Internal Server Error on every page request. Dashboard completely unusable out of the box.

Error 1 — Python 3.14 + Jinja2 LRU cache incompatibility

File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 148, in TemplateResponse
template = self.get_template(name)
File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 115, in get_template
return self.env.get_template(name)
File "/opt/venv/lib/python3.14/site-packages/jinja2/environment.py", line 964, in _load_template
template = self.cache.get(cache_key)
File "/opt/venv/lib/python3.14/site-packages/jinja2/utils.py", line 515, in getitem
rv = self._mapping[key]
TypeError: cannot use 'tuple' as a dict key (unhashable type: 'dict')

Error 2 — After patching Jinja2 cache (templates.env.cache = None), reveals underlying Starlette 1.0.0 API break

File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 148, in TemplateResponse
template = self.get_template(name)
File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 115, in get_template
return self.env.get_template(name)
File "/opt/venv/lib/python3.14/site-packages/jinja2/environment.py", line 975, in _load_template
template = self.loader.load(self, name, self.make_globals(globals))
File "/opt/venv/lib/python3.14/site-packages/jinja2/loaders.py", line 126, in load
source, filename, uptodate = self.get_source(environment, name)
File "/opt/venv/lib/python3.14/site-packages/jinja2/loaders.py", line 197, in get_source
pieces = split_template_path(template)
File "/opt/venv/lib/python3.14/site-packages/jinja2/loaders.py", line 30, in split_template_path
for piece in template.split("/"):
AttributeError: 'dict' object has no attribute 'split'

Root cause

Starlette 1.0.0 changed the TemplateResponse signature.

Old API (pre-1.0):

TemplateResponse("template.html", {"request": request, "key": "value"})

New API (Starlette 1.0.0+):

TemplateResponse(request, "template.html", {"key": "value"})

The codebase uses the old positional argument order throughout app.py, causing the context dict to be passed as the template name argument. The Jinja2 cache error in Python 3.14 masks this underlying issue until the cache is disabled.

Affected file

src/meshcore_hub/web/app.py — all TemplateResponse calls need updating to the new Starlette 1.0.0 signature.

Fresh install on Raspberry Pi 5 (ARM64), Raspberry Pi OS Bookworm 64-bit. Running the quickstart docker compose setup. ### Environment - Python 3.14 (inside container) - Starlette 1.0.0 (inside container) - Docker on Raspberry Pi 5, ARM64 - meshcore-hub `latest` tag ### Steps to reproduce 1. Clone repo 2. Copy `.env.example` to `.env`, set `SERIAL_PORT`, `TZ`, `WEB_DATETIME_LOCALE` 3. `docker compose --profile migrate run --rm db-migrate` 4. `docker compose --profile mqtt --profile core --profile receiver up -d` 5. Browse to `http://[host]:8080` ### Result `500 Internal Server Error` on every page request. Dashboard completely unusable out of the box. ### Error 1 — Python 3.14 + Jinja2 LRU cache incompatibility File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 148, in TemplateResponse template = self.get_template(name) File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 115, in get_template return self.env.get_template(name) File "/opt/venv/lib/python3.14/site-packages/jinja2/environment.py", line 964, in _load_template template = self.cache.get(cache_key) File "/opt/venv/lib/python3.14/site-packages/jinja2/utils.py", line 515, in getitem rv = self._mapping[key] TypeError: cannot use 'tuple' as a dict key (unhashable type: 'dict') ### Error 2 — After patching Jinja2 cache (`templates.env.cache = None`), reveals underlying Starlette 1.0.0 API break File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 148, in TemplateResponse template = self.get_template(name) File "/opt/venv/lib/python3.14/site-packages/starlette/templating.py", line 115, in get_template return self.env.get_template(name) File "/opt/venv/lib/python3.14/site-packages/jinja2/environment.py", line 975, in _load_template template = self.loader.load(self, name, self.make_globals(globals)) File "/opt/venv/lib/python3.14/site-packages/jinja2/loaders.py", line 126, in load source, filename, uptodate = self.get_source(environment, name) File "/opt/venv/lib/python3.14/site-packages/jinja2/loaders.py", line 197, in get_source pieces = split_template_path(template) File "/opt/venv/lib/python3.14/site-packages/jinja2/loaders.py", line 30, in split_template_path for piece in template.split("/"): AttributeError: 'dict' object has no attribute 'split' ### Root cause Starlette 1.0.0 changed the `TemplateResponse` signature. Old API (pre-1.0): ```python TemplateResponse("template.html", {"request": request, "key": "value"}) ``` New API (Starlette 1.0.0+): ```python TemplateResponse(request, "template.html", {"key": "value"}) ``` The codebase uses the old positional argument order throughout `app.py`, causing the context dict to be passed as the template name argument. The Jinja2 cache error in Python 3.14 masks this underlying issue until the cache is disabled. ### Affected file `src/meshcore_hub/web/app.py` — all `TemplateResponse` calls need updating to the new Starlette 1.0.0 signature. <!-- github-sync-id:4271804731 -->
adam closed this issue 2026-10-04 23:09:56 +02:00
Author
Owner

Thanks for this. This has been fixed in an upcoming release, which is a major one.

We are replacing our proprietary receiver service to support LetsMesh Observers.

It is quite a substantial release and does require some upgrades and migrations. Unfortunately this does take a bit of effort for admins of the application.

https://github.com/ipnet-mesh/meshcore-hub/blob/feat/meshcore-packet-capture-receiver/docs/upgrading.md#v090

If you want, I could backport this Starlette fix if you are unable to commit to upgrading. If you are just starting out, I suggest waiting for the new release.

Thanks for this. This has been fixed in an upcoming release, which is a major one. We are replacing our proprietary receiver service to support LetsMesh Observers. It is quite a substantial release and does require some upgrades and migrations. Unfortunately this does take a bit of effort for admins of the application. https://github.com/ipnet-mesh/meshcore-hub/blob/feat/meshcore-packet-capture-receiver/docs/upgrading.md#v090 If you want, I could backport this Starlette fix if you are unable to commit to upgrading. If you are just starting out, I suggest waiting for the new release. <!-- github-comment-id:4263004521 -->
Author
Owner

This fix has now been merged. This issue should be fixed now.

This fix has now been merged. This issue should be fixed now. <!-- github-comment-id:4271672728 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/meshcore-hub#39