From e3829e0c204192d0abcd513c2d24755c80d602ca Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 3 Jun 2026 20:39:39 +0200 Subject: [PATCH 1/2] Print errors as JSON when called from command line --- docxtpl/__main__.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/docxtpl/__main__.py b/docxtpl/__main__.py index 59cf049..bcd317e 100644 --- a/docxtpl/__main__.py +++ b/docxtpl/__main__.py @@ -1,6 +1,7 @@ import argparse import json import os +import sys from .template import DocxTemplate, TemplateError @@ -11,6 +12,19 @@ QUIET_ARG = "quiet" +def template_error_payload(exc: TemplateError) -> dict: + payload = {"error": "template_error", "message": str(exc)} + if hasattr(exc, "docx_context"): + payload["docx_context"] = list(exc.docx_context) + return payload + + +def emit_cli_error(payload: dict) -> None: + json.dump(payload, sys.stderr) + sys.stderr.write("\n") + sys.exit(1) + + def make_arg_parser(): parser = argparse.ArgumentParser( usage="python -m docxtpl [-h] [-o] [-q] {} {} {}".format( @@ -122,18 +136,12 @@ def get_json_data(json_path): def make_docxtemplate(template_path): - try: - return DocxTemplate(template_path) - except TemplateError: - raise RuntimeError("Could not create docx template.") + return DocxTemplate(template_path) def render_docx(doc, json_data): - try: - doc.render(json_data) - return doc - except TemplateError: - raise RuntimeError("An error ocurred while trying to render the docx") + doc.render(json_data) + return doc def save_file(doc, parsed_args): @@ -163,9 +171,10 @@ def main(): doc = make_docxtemplate(os.path.abspath(parsed_args[TEMPLATE_ARG])) doc = render_docx(doc, json_data) save_file(doc, parsed_args) + except TemplateError as e: + emit_cli_error(template_error_payload(e)) except RuntimeError as e: - print("Error: " + e.__str__()) - return + emit_cli_error({"error": "cli_error", "message": str(e)}) finally: if not parsed_args[QUIET_ARG]: print("Exiting program!") From 4009d9f07c721a2fa9f14e04c8f6d06c074f5d42 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 14 Oct 2026 12:32:57 +0200 Subject: [PATCH 2/2] JSON error output is now opt-in via --json-error --- docs/index.rst | 3 ++- docxtpl/__main__.py | 31 +++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 46cf69f..26a02fc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -471,7 +471,7 @@ Command-line execution One can use `docxtpl` module directly on command line to generate a docx from a template and a json file as a context:: - usage: python -m docxtpl [-h] [-o] [-q] template_path json_path output_filename + usage: python -m docxtpl [-h] [-o] [-q] [--json-error] template_path json_path output_filename Make docx file from existing template docx and json data. @@ -485,6 +485,7 @@ One can use `docxtpl` module directly on command line to generate a docx from a -o, --overwrite If output file already exists, overwrites without asking for confirmation -q, --quiet Do not display unnecessary messages + --json-error Emit errors as JSON on stderr See tests/module_execute.py for an example. diff --git a/docxtpl/__main__.py b/docxtpl/__main__.py index bcd317e..46d6236 100644 --- a/docxtpl/__main__.py +++ b/docxtpl/__main__.py @@ -10,6 +10,7 @@ OUTPUT_ARG = "output_filename" OVERWRITE_ARG = "overwrite" QUIET_ARG = "quiet" +JSON_ERROR_ARG = "json_error" def template_error_payload(exc: TemplateError) -> dict: @@ -19,15 +20,18 @@ def template_error_payload(exc: TemplateError) -> dict: return payload -def emit_cli_error(payload: dict) -> None: - json.dump(payload, sys.stderr) - sys.stderr.write("\n") +def emit_cli_error(payload: dict, json_error: bool = False) -> None: + if json_error: + json.dump(payload, sys.stderr) + sys.stderr.write("\n") + else: + print("Error: " + payload["message"], file=sys.stderr) sys.exit(1) def make_arg_parser(): parser = argparse.ArgumentParser( - usage="python -m docxtpl [-h] [-o] [-q] {} {} {}".format( + usage="python -m docxtpl [-h] [-o] [-q] [--json-error] {} {} {}".format( TEMPLATE_ARG, JSON_ARG, OUTPUT_ARG ), description="Make docx file from existing template docx and json data.", @@ -53,6 +57,12 @@ def make_arg_parser(): action="store_true", help="Do not display unnecessary messages", ) + parser.add_argument( + "--json-error", + dest=JSON_ERROR_ARG, + action="store_true", + help="Emit errors as JSON on stderr", + ) return parser @@ -81,7 +91,7 @@ def is_argument_valid(arg_name, arg_value, overwrite): return arg_value.endswith(".docx") and check_exists_ask_overwrite( arg_value, overwrite ) - elif arg_name in [OVERWRITE_ARG, QUIET_ARG]: + elif arg_name in [OVERWRITE_ARG, QUIET_ARG, JSON_ERROR_ARG]: return arg_value in [True, False] @@ -172,11 +182,16 @@ def main(): doc = render_docx(doc, json_data) save_file(doc, parsed_args) except TemplateError as e: - emit_cli_error(template_error_payload(e)) + emit_cli_error( + template_error_payload(e), json_error=parsed_args[JSON_ERROR_ARG] + ) except RuntimeError as e: - emit_cli_error({"error": "cli_error", "message": str(e)}) + emit_cli_error( + {"error": "cli_error", "message": str(e)}, + json_error=parsed_args[JSON_ERROR_ARG], + ) finally: - if not parsed_args[QUIET_ARG]: + if not parsed_args[QUIET_ARG] and not parsed_args[JSON_ERROR_ARG]: print("Exiting program!")