Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2a98234
Fix mypy complaints
waketzheng Jul 18, 2025
1ba5a7c
Save stage
waketzheng Jul 19, 2025
9abc053
add type checking section
waketzheng Jul 20, 2025
e5c1440
Rollback string format changes
waketzheng Jul 20, 2025
d4f04da
Success to pass mypy check
waketzheng Jul 21, 2025
722baa1
Merge branch 'type-hints' of github.com:waketzheng/python-docx-templa…
waketzheng Jul 21, 2025
4450a9f
refactor: reorder imports by ruff
waketzheng Jul 21, 2025
243c719
fix flake8 issue
waketzheng Jul 22, 2025
8e33b4f
ci: add test workflow
waketzheng Jul 22, 2025
992f190
ci: separate py3.7 test to a single job
waketzheng Jul 22, 2025
35c4844
ci: fix typo
waketzheng Jul 22, 2025
1561476
ci: remove 3.14 from py list as it failed to install lxml
waketzheng Jul 22, 2025
f6bab5a
tests: resolve `__file__` to be abs path
waketzheng Jul 22, 2025
3bbedea
refactor: move docxcompose to optional dependencies
waketzheng Jul 22, 2025
d4340de
feat: support pdm and uv
waketzheng Jul 22, 2025
e2cb9da
refactor: remove subdoc import from __init__ file
waketzheng Jul 23, 2025
5136bba
ci: add exclude parameter to flake8
waketzheng Jul 23, 2025
c82d691
ci: run python by uv
waketzheng Jul 23, 2025
346ffa5
ci: fix py3.7 install deps error
waketzheng Jul 23, 2025
5596c5b
chore: upgrade deps and github actions
waketzheng Sep 19, 2025
29bd23a
refactor: default to imports subdoc and ignore importerror
waketzheng Sep 24, 2025
be3a0eb
Use poetry plugin as build system backend
waketzheng Sep 24, 2025
f5922dc
feat: add type hints
waketzheng Sep 24, 2025
ae134ab
refactor: type hints for `__main__.py`
waketzheng Oct 2, 2025
1948f04
Fix flake8 complaint
waketzheng Oct 2, 2025
b40de05
Fix ci error
waketzheng Oct 2, 2025
f548b6e
Merge branch 'type-hints' into feat-uv-pdm
waketzheng Oct 2, 2025
54e5e50
Fix merge conflicts
waketzheng Oct 2, 2025
345eae2
chore: update lock files
waketzheng Oct 2, 2025
ac78169
Merge branch 'feat-uv-pdm' of github.com:waketzheng/python-docx-templ…
waketzheng Oct 2, 2025
9305bca
chore: remove pdm lock file
waketzheng Oct 3, 2025
2782c47
chore: upgrade deps
waketzheng Oct 3, 2025
93242da
Merge branch 'feat-uv-pdm' of github.com:waketzheng/python-docx-templ…
waketzheng Oct 3, 2025
48a026e
Use poetry for test ci
waketzheng Oct 3, 2025
d7f2fc9
Fix ci error
waketzheng Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/codestyle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ jobs:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand All @@ -23,3 +24,11 @@ jobs:
pip install flake8
# stop the build if there are code styling problems. The GitHub editor is 127 chars wide.
flake8 . --count --max-line-length=127 --show-source --statistics
- name: Check type hints
run: |
pip install mypy lxml-stubs
mypy .
- name: Ensure library work without docxcompose
run: |
pip uninstall -y docxcompose
python -c "from docxtpl import *"
58 changes: 58 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Test
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.13"
- "3.12"
- "3.11"
- "3.10"
- "3.9"
- "3.8"
fail-fast: false
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install and configure Poetry
run: |
python3 -m pip install -U pip poetry
poetry env use ${{ matrix.python-version }}
- name: Install dependencies
run: poetry install --all-extras --all-groups
- name: Test
run: poetry run python tests/runtests.py
- name: Build
run: poetry build

test_py37:
runs-on: ubuntu-latest
container: python:3.7-slim
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.x"
- name: Install and configure Poetry
run: |
python3 -m pip install -U pip poetry
poetry env use 3.7
- name: Install dependencies
run: poetry install --all-extras --all-groups
- name: Test
run: |
poetry run python tests/runtests.py
poetry run python -V
- name: Build
run: poetry build
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var/
.installed.cfg
*.egg
tests/output/*
.venv

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down Expand Up @@ -61,4 +62,11 @@ target/
.project

#Pycharm
.idea
.idea

# Uv/Pdm
.pdm-python
.python-version

# In Project Virtual Environment
.venv/
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ name = "pypi"
[dev-packages]
docxtpl = {editable = true, path = "."}
flake8 = "*"
mypy = "*"
lxml-stubs = "*"

[requires]
python_version = "3"
633 changes: 303 additions & 330 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docxtpl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
try:
from .subdoc import Subdoc
except ImportError:
pass
...
23 changes: 13 additions & 10 deletions docxtpl/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import argparse
import json
import os
Expand All @@ -11,7 +13,7 @@
QUIET_ARG = "quiet"


def make_arg_parser():
def make_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage="python -m docxtpl [-h] [-o] [-q] {} {} {}".format(
TEMPLATE_ARG, JSON_ARG, OUTPUT_ARG
Expand Down Expand Up @@ -42,7 +44,7 @@ def make_arg_parser():
return parser


def get_args(parser):
def get_args(parser) -> dict:
try:
parsed_args = vars(parser.parse_args())
return parsed_args
Expand All @@ -57,7 +59,7 @@ def get_args(parser):
)


def is_argument_valid(arg_name, arg_value, overwrite):
def is_argument_valid(arg_name: str, arg_value: str, overwrite: bool) -> bool | None:
# Basic checks for the arguments
if arg_name == TEMPLATE_ARG:
return os.path.isfile(arg_value) and arg_value.endswith(".docx")
Expand All @@ -69,9 +71,10 @@ def is_argument_valid(arg_name, arg_value, overwrite):
)
elif arg_name in [OVERWRITE_ARG, QUIET_ARG]:
return arg_value in [True, False]
return None


def check_exists_ask_overwrite(arg_value, overwrite):
def check_exists_ask_overwrite(arg_value: str, overwrite: bool) -> bool:
# If output file does not exist or command was run with overwrite option,
# returns True, else asks for overwrite confirmation. If overwrite is
# confirmed returns True, else raises OSError.
Expand All @@ -93,7 +96,7 @@ def check_exists_ask_overwrite(arg_value, overwrite):
return True


def validate_all_args(parsed_args):
def validate_all_args(parsed_args: dict) -> None:
overwrite = parsed_args[OVERWRITE_ARG]
# Raises AssertionError if any of the arguments is not validated
try:
Expand All @@ -108,7 +111,7 @@ def validate_all_args(parsed_args):
)


def get_json_data(json_path):
def get_json_data(json_path) -> dict:
with open(json_path) as file:
try:
json_data = json.load(file)
Expand All @@ -121,22 +124,22 @@ def get_json_data(json_path):
raise RuntimeError("Failed to get json data.")


def make_docxtemplate(template_path):
def make_docxtemplate(template_path: str) -> DocxTemplate:
try:
return DocxTemplate(template_path)
except TemplateError:
raise RuntimeError("Could not create docx template.")


def render_docx(doc, json_data):
def render_docx(doc: DocxTemplate, json_data: dict) -> DocxTemplate:
try:
doc.render(json_data)
return doc
except TemplateError:
raise RuntimeError("An error ocurred while trying to render the docx")


def save_file(doc, parsed_args):
def save_file(doc: DocxTemplate, parsed_args: dict) -> None:
try:
output_path = parsed_args[OUTPUT_ARG]
doc.save(output_path)
Expand All @@ -151,7 +154,7 @@ def save_file(doc, parsed_args):
raise RuntimeError("Failed to save file.")


def main():
def main() -> None:
parser = make_arg_parser()
# Everything is in a try-except block that catches a RuntimeError that is
# raised if any of the individual functions called cause an error
Expand Down
9 changes: 9 additions & 0 deletions docxtpl/_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
try:
from html import escape
except ImportError:
# cgi.escape is deprecated in python 3.7
from cgi import escape # type:ignore[attr-defined,no-redef]


__all__ = ("escape",)
28 changes: 22 additions & 6 deletions docxtpl/inline_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,39 @@

@author: Eric Lapouyade
"""

from __future__ import annotations

from typing import IO, TYPE_CHECKING

from docx.oxml import OxmlElement, parse_xml
from docx.oxml.ns import qn

if TYPE_CHECKING:
from docx.shared import Length
from .template import DocxTemplate


class InlineImage(object):
"""Class to generate an inline image

This is much faster than using Subdoc class.
"""

tpl = None
image_descriptor = None
width = None
height = None
tpl: DocxTemplate = None # type:ignore[assignment]
image_descriptor: str | IO[bytes] = None # type:ignore[assignment]
width: int | Length | None = None
height: int | Length | None = None
anchor = None

def __init__(self, tpl, image_descriptor, width=None, height=None, anchor=None):
def __init__(
self,
tpl: DocxTemplate,
image_descriptor: str | IO[bytes],
width: int | Length | None = None,
height: int | Length | None = None,
anchor=None,
) -> None:
self.tpl, self.image_descriptor = tpl, image_descriptor
self.width, self.height = width, height
self.anchor = anchor
Expand Down Expand Up @@ -49,7 +65,7 @@ def _add_hyperlink(self, run, url, part):

return run

def _insert_image(self):
def _insert_image(self) -> str:
pic = self.tpl.current_rendering_part.new_pic_inline(
self.image_descriptor,
self.width,
Expand Down
7 changes: 2 additions & 5 deletions docxtpl/listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@

@author: Eric Lapouyade
"""
try:
from html import escape
except ImportError:
# cgi.escape is deprecated in python 3.7
from cgi import escape

from ._compat import escape


class Listing(object):
Expand Down
Empty file added docxtpl/py.typed
Empty file.
7 changes: 2 additions & 5 deletions docxtpl/richtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@

@author: Eric Lapouyade
"""
try:
from html import escape
except ImportError:
# cgi.escape is deprecated in python 3.7
from cgi import escape

from ._compat import escape


class RichText(object):
Expand Down
Loading
Loading