Skip to content

Asyncio support #610

Open
Open

Description

@ugoi

Is your feature request related to a problem? Please describe.

I'm trying to use DocxTemplate inside my async FastAPI endpoint but since it's blocking I have to make my FastAPI endpoint synchronous which will create additional thread. I don't want to use up all my threads when getting many requests.

Describe the solution you'd like

If I could simply await the DocxTemplate and all it's methods this would be no problem.

Describe alternatives you've considered

Maybe use aiofiles or something / not sure about this

Additional context

Activity

  1. waketzheng commented on Sep 24, 2025

    @waketzheng
    Contributor

    @ugoi fastapi depends on anyio, you can do it like this:

    from io import BytesIO
    
    import anyio
    from docxtpl import DocxTemplate
    
    
    async def main() -> None:
        doc = DocxTemplate("my_word_template.docx")
        context = {"company_name": "World company"}
        doc.render(context)
        bio = BytesIO()
        doc.save(bio)
        filename = "generated_doc.docx"
        content = bio.getvalue()
        size = await anyio.Path(filename).write_bytes(content)
        print(f"Write to {filename} with {size=}")
    
    
    anyio.run(main)

    To control the concurrency number:

    import sys
    from io import BytesIO
    
    import anyio
    from asynctor import timeit  #  pip install asynctor
    from docxtpl import DocxTemplate
    
    LIMIT = 5
    
    
    async def new_docx(filepath: anyio.Path, limiter) -> None:
        doc = DocxTemplate("my_word_template.docx")
        context = {"company_name": "World company"}
        doc.render(context)
        bio = BytesIO()
        doc.save(bio)
        content = bio.getvalue()
        async with limiter:
            size = await filepath.write_bytes(content)
            await anyio.sleep(1)  # Sleep 1 second to verify the limiter result
        print(f"Write to {filepath.name} with {size=}")
    
    
    @timeit
    async def main() -> None:
        if sys.argv[1:] and sys.argv[1].isdigit():
            global LIMIT
            LIMIT = int(sys.argv[1])
            print(f"Changed LIMIT to {LIMIT}")
        filename = "generated_doc.docx"     
        async with anyio.create_task_group() as tg:
            f = anyio.Path(filename)
            limiter = anyio.CapacityLimiter(LIMIT)  # limiter must be defined outside the for loop
            for i in range(5):
                p = f.with_name(f.stem + f"_{i}" + f.suffix)
                tg.start_soon(new_docx, p, limiter)
    
    
    anyio.run(main)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions