Repository navigation
Description
On Python 3.15, formatting a traceback for an exception raised inside a Mako
template emits DeprecationWarning: Module globals is missing a __spec__.loader. Under default warning filters this is silent, but any project
that turns warnings into errors (a very common pytest configuration) sees the
warning raised from inside traceback formatting, which destroys the error
report rather than producing a normal failure.
Reproduction
import warnings, traceback
from mako.template import Template
warnings.simplefilter("error")
try:
Template("${1/0}").render()
except Exception:
traceback.format_exc()On Python 3.15.0b4:
DeprecationWarning: Module globals is missing a __spec__.loader
Remove the simplefilter("error") line and it is silent, but the warning is
still being emitted.
Root cause
_compile_text() compiles in-memory templates into a bare module:
module = types.ModuleType(cid)
code = compile(source, cid, "exec")
exec(code, module.__dict__, module.__dict__)A bare types.ModuleType has both __spec__ and __loader__ present in its
globals, each set to None:
>>> import types
>>> m = types.ModuleType("probe")
>>> m.__dict__.get("__spec__"), m.__dict__.get("__loader__")
(None, None)Python 3.15 added linecache._bless_my_loader(), which linecache.lazycache()
calls for every frame while a traceback is being built:
def _bless_my_loader(module_globals):
loader = module_globals.get('__loader__')
if loader is None and '__spec__' not in module_globals:
return None # not taken: __spec__ IS present
spec = module_globals.get('__spec__') # None
if spec is None and module_globals.get('__name__') == '__main__':
return loader # not taken: __name__ is the template id
spec_loader = getattr(spec, 'loader', None) # None
if spec_loader is None:
import warnings
warnings.warn(
'Module globals is missing a __spec__.loader',
DeprecationWarning)
return loaderThe early return on the second line is the one that would normally protect a
synthetic module, but it only fires when __spec__ is absent. Because
ModuleType puts __spec__ in the dict as None, Mako's template modules
fall through to the warnings.warn() branch.
Walking the traceback frames of a failing render confirms the template module
is the only offender:
__name__='mako.template' __spec__=ModuleSpec(... loader=SourceFileLoader ...)
__name__='mako.runtime' __spec__=ModuleSpec(... loader=SourceFileLoader ...)
WARN -> __name__='memory:0x7f37607657f0' __spec__=None __loader__present=True
Affected configurations
Three of the four template setups are affected; only the file-backed path with
a module_directory escapes, because that one goes through
compat.load_module() and therefore gets a genuine ModuleSpec.
| Configuration | default filters | warnings as errors |
|---|---|---|
in-memory, no module_directory |
silent | raises |
in-memory + module_directory |
silent | raises |
file-backed, no module_directory |
silent | raises |
file-backed + module_directory |
silent | OK |
Impact
No behaviour change for an application running with default warning filters,
since DeprecationWarning is ignored outside __main__.
The problem is for downstream test suites configured with -W error /
filterwarnings = error. Because the warning is raised while the traceback is
being formatted, the original template exception is lost and replaced by the
warning. Under pytest this surfaces as an INTERNALERROR rather than a test
failure, and under pytest-xdist it kills the worker and aborts the entire
run.
Alembic hit exactly this while being tested on 3.15;
tests/test_script_production.py::TemplateArgsTest::test_bad_render renders a
deliberately broken template and takes the whole run down with it. The rest of
that suite passes on 3.15.
Suggested fix
Drop the two empty entries so _bless_my_loader() takes its early return and
treats the template module as carrying no loader information at all:
module = types.ModuleType(cid)
# a bare ModuleType carries __spec__ = None and __loader__ = None, a shape
# that Python 3.15's linecache._bless_my_loader() warns about; remove them
# so it takes its early "no loader information" return instead
module.__dict__.pop("__spec__", None)
module.__dict__.pop("__loader__", None)Populating __spec__ with a real ModuleSpec would work too, but there is no
meaningful loader for a template compiled from a string, and Mako does its own
source resolution for tracebacks via RichTraceback.
For what it is worth, this is not unique to Mako. Any library that execs into
a bare types.ModuleType produces the same globals shape, so the new check is
arguably aggressive about a common pattern. Given the 3.15 release candidate
phase begins 2026-08-04, it may be worth raising upstream as well if the
early-return condition should also cover __spec__ being present but None.
Environment
- Python 3.15.0b4
- Mako 1.3.12 (PyPI) and current
main(1.3.13) —_compile_text()is
unchanged between them, both reproduce
Activity
sqla-tester commentedon Aug 3, 2026
Mike Bayer has proposed a fix for this issue in the main branch:
Provide a module spec for string-based template modules https://gerrit.sqlalchemy.org/c/sqlalchemy/mako/+/6859
add Python 3.15
Provide a module spec for string-based template modules