mvantellingen / python-zeep

A Python SOAP client
http://docs.python-zeep.org
Other
1.87k stars 578 forks source link

AsyncClient has a blocking constructor #1409

Open Thalisson-Alves opened 4 months ago

Thalisson-Alves commented 4 months ago

Problem

AsyncClient makes a blocking call in its constructor, likely occurring in the Document.load method. This can cause the entire application to freeze until the client finishes this initial request.

Example script

This script includes both a function that blocks the event loop (make_request_blocking) and a workaround for that (make_request_non_blocking).

import asyncio

from zeep.client import AsyncClient

async def get_client_non_blocking():
    return await asyncio.get_event_loop().run_in_executor(
        None, AsyncClient, "http://172.16.1.45"
    )

async def get_client_blocking():
    """ I usually use the following pattern to create a client:

    async with AsyncClient("http://172.16.1.45") as client:
        return await client.service.serviceName(args="args")
    """
    return AsyncClient("http://172.16.1.45")

async def counter():
    for i in range(10):
        print(i, flush=True)
        await asyncio.sleep(1)

async def main():
    # This one will block the event loop, and you should see
    # only the first number printed
    # await asyncio.gather(counter(), get_client_blocking())

    # This one works as expected
    await asyncio.gather(counter(), get_client_non_blocking())

if __name__ == "__main__":
    asyncio.run(main())

Possible solution

Expose a def load() -> None function on Client and an async def load() -> None function on AsyncClient. Those functions could be called inside the __enter__ and __exit__ magic methods if they haven't been called yet.