From c76814b8d54dd0dcedc30b13118d496363b33a63 Mon Sep 17 00:00:00 2001 From: AHMET ERCAN <294280988+ahmetmusab42-stack@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:30:09 +0300 Subject: [PATCH] Improve missing subdoc dependency error --- docxtpl/template.py | 11 ++++++- tests/subdoc_optional_dependency.py | 45 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/subdoc_optional_dependency.py diff --git a/docxtpl/template.py b/docxtpl/template.py index f20280a..055d381 100644 --- a/docxtpl/template.py +++ b/docxtpl/template.py @@ -614,7 +614,16 @@ def fix_docpr_ids(self, tree): elt.attrib["id"] = str(self.docx_ids_index) def new_subdoc(self, docpath=None) -> Subdoc: - from .subdoc import Subdoc + try: + from .subdoc import Subdoc + except ModuleNotFoundError as exc: + if exc.name == "docxcompose": + raise ModuleNotFoundError( + "new_subdoc() requires the optional docxcompose dependency. " + 'Install it with: pip install "docxtpl[subdoc]"', + name="docxcompose", + ) from exc + raise self.init_docx() return Subdoc(self, docpath) diff --git a/tests/subdoc_optional_dependency.py b/tests/subdoc_optional_dependency.py new file mode 100644 index 0000000..6b6cf3e --- /dev/null +++ b/tests/subdoc_optional_dependency.py @@ -0,0 +1,45 @@ +import importlib.abc +import sys + + +class BlockDocxcompose(importlib.abc.MetaPathFinder): + missing_name = "docxcompose" + + def find_spec(self, fullname, path, target=None): + if fullname == "docxcompose" or fullname.startswith("docxcompose."): + raise ModuleNotFoundError( + "No module named '%s'" % self.missing_name, + name=self.missing_name, + ) + return None + + +blocker = BlockDocxcompose() +sys.meta_path.insert(0, blocker) + +try: + from docxtpl import DocxTemplate + + template = DocxTemplate("templates/subdoc_tpl.docx") + + try: + template.new_subdoc() + except ModuleNotFoundError as exc: + assert exc.name == "docxcompose" + assert "new_subdoc() requires the optional docxcompose dependency" in str(exc) + assert 'pip install "docxtpl[subdoc]"' in str(exc) + assert isinstance(exc.__cause__, ModuleNotFoundError) + else: + raise AssertionError("new_subdoc() did not report the missing dependency") + + blocker.missing_name = "some_other_dependency" + try: + template.new_subdoc() + except ModuleNotFoundError as exc: + assert exc.name == "some_other_dependency" + assert str(exc) == "No module named 'some_other_dependency'" + assert "pip install" not in str(exc) + else: + raise AssertionError("an unrelated import failure was masked") +finally: + sys.meta_path.remove(blocker)