Starlette 1.0 渲染模板报 unhashable type: dict,先查参数顺序
Starlette 1.0 的 TemplateResponse 要求 request 在模板名前。旧调用会把 context 字典当成模板名,错误最终出现在 Jinja 缓存键;关闭缓存无法修正参数错位。
升级 Starlette 后,模板明明存在,渲染却在 jinja2/environment.py 的缓存查找处报 unhashable type: 'dict',先检查调用是否还写成:
templates.TemplateResponse("index.html", {"request": request})
Starlette 1.0.0 的正式接口已是 TemplateResponse(request, name, context=None, ...)。旧写法会让 "index.html" 落到 request 位置,让 context 字典落到 name 位置。错误虽然由 Jinja 的缓存操作抛出,传错值的位置却在上层模板响应调用。
看看缓存键中的 name 到底是什么
Jinja 3.1.6 用 loader 的弱引用与模板名组成缓存键。字典被当成模板名后,缓存键就含有不可哈希对象,可能显示 cannot use 'tuple' as a dict key,同时指出里面有 unhashable type: 'dict'。
这不能推出 Python 3.14 不再允许元组作为字典键。元组只要包含不可哈希对象,本来就不能作为键。Windows 或 PyInstaller 环境也可能遇到这项参数错位,但打包本身不是判断根因的必要条件。
在现有路由中改为明确的关键字参数,可以避免再次混淆:
return templates.TemplateResponse(
request=request,
name="index.html",
context={"items": items},
)
这里假设 templates 是已经配置好模板目录或 Environment 的 Jinja2Templates,request 是当前请求,items 是原本需要传给模板的业务数据。Starlette 会将 request 放入上下文,不需要把模板文件名塞进 request 位置。
保留原来的状态码、响应头、后台任务及 context processor。若上下文本来自己携带 request 键,检查它确实指向当前请求;不要让旧缓存对象或别的请求对象覆盖它。
不要靠关闭缓存掩盖错位
设置 templates.env.cache = None 可能绕过最先报错的缓存查找,但模板名仍然是字典,之后的 loader 或渲染路径仍不正确。把字典强转成字符串,也只会生成一个无意义的模板名。
确认项目实际加载的 Starlette 版本,以及外层 FastAPI 或其他框架允许的依赖范围。由框架间接依赖时,应在兼容组合里迁移调用,不要为修一个参数顺序而强制安装超出框架约束的版本。应用中的封装函数、邮件页面渲染和测试辅助入口也可能仍保留旧签名,需逐一检查。
如果 name 已是正确字符串,堆栈却仍显示不可哈希值,再检查自定义 loader、缓存或封装层;不能把所有 Jinja TypeError 都归到这一项迁移。
用实际路由验收模板内容、上下文变量、url_for、状态码和响应头,并确认缓存开启时仍正常。打包项目还应验证资源目录确实进入产物;调用顺序修好后新出现的 TemplateNotFound,应沿文件路径与打包资源处理,不要再次关闭缓存。


