python-zeep icon indicating copy to clipboard operation
python-zeep copied to clipboard

UserWarning: Unclosed httpx.AsyncClient

Open bitsofinfo opened this issue 3 years ago • 5 comments

When using zeep.AsyncClient() how should it be cleaned up on exit?

  history_plugin = HistoryPlugin()
  wsdl = os.environ.get("mywsdl")
  settings = Settings(strict=False, xml_huge_tree=True)
  client = zeep.AsyncClient(wsdl, settings=settings, plugins=[history_plugin])

When the program exits:

/me/lib/python3.8/site-packages/httpx/_client.py:1914: UserWarning: Unclosed <httpx.AsyncClient object at 0x1045b8610>. See https://www.python-httpx.org/async/#opening-and-closing-clients for details.

bitsofinfo avatar May 06 '21 14:05 bitsofinfo

async def async_finalize(self) -> None:
    log.debug('start async_finalize')
    # closes properly the transport, which in turn properly closes the async clients
    await self.client.transport.aclose()
    log.debug('end async_finalize')

AndrewMagerman avatar May 21 '21 12:05 AndrewMagerman

what would call that method @AndrewMagerman

bitsofinfo avatar Jun 25 '21 21:06 bitsofinfo

you'd call it at the very end of your program i.e.

    def exit(self) -> None:
        if self.run_asynchroneously:
            loop = asyncio.get_event_loop()
            loop.run_until_complete(self.async_finalize())

AndrewMagerman avatar Jul 09 '21 07:07 AndrewMagerman

@bitsofinfo I had the same problem. The solution is that if you are using the AsyncClient you should be using it as a context manager like this:

EDIT -- this implementation closes the client prematurely, see the latest response for a better implementation

class Provider:

    def __init__(self):
        self.client = zeep.AsyncClient(wsdl=wsdl_path, **zeep_client_kwargs)

    async def do_something(self):
        async with self.client as client:
            response = client.service.someSoapMethod(
            ..
         )

        serialized_response = zeep.helpers.serialize_object(response)

My problem was that I was using it like this, incorrectly:

class Provider:

    def __init__(self):
        self.client = zeep.AsyncClient(wsdl=wsdl_path, **zeep_client_kwargs)

    async def do_something(self):
        response = self.client.service.someSoapMethod(
            ..
         )

        serialized_response = zeep.helpers.serialize_object(response)

The AsyncClient implements __aenter__ and __aexit__ which closes the transport:

https://github.com/mvantellingen/python-zeep/blob/c7d916cbac8817ba4be81751ee590c6fe52b3f8a/src/zeep/client.py#L237-L241

And the AsyncTransport.aclose() closes the httpx client:

https://github.com/mvantellingen/python-zeep/blob/c7d916cbac8817ba4be81751ee590c6fe52b3f8a/src/zeep/transports.py#L205-L206

phillipuniverse avatar Dec 23 '21 16:12 phillipuniverse

My suggestion above with the context manager has a problem where the httpx client is closed and it cannot be reused, thus eliminating the advantage of using a Provider class. In order to tie the lifecycle of the zeep.AsyncClient (and thus the httpx.AsyncClient) to the Provider, you have to close the instance yourself.

The suggestion @AndrewMagerman had is a good one but this is what I did to manage the lifecycle in the Provider class:

class Provider:

    def __init__(self):
        self.client = zeep.AsyncClient(wsdl=wsdl_path, **zeep_client_kwargs)

    async def do_something(self):
        response = self.client.service.someSoapMethod(
            ..
         )

        serialized_response = zeep.helpers.serialize_object(response)
        ...

    def __del__(self):
        """
        Ensures that the client is closed on exit. More information at https://github.com/mvantellingen/python-zeep/issues/1224#issuecomment-1000442879
        """
        loop = asyncio.get_event_loop()
        if loop.is_running():
            loop.create_task(self._close_client())
        else:
            loop.run_until_complete(self._close_client())

    async def _close_client(self):
        await self.client.__aexit__()

I can't take all the credit from this, most of this comes from https://stackoverflow.com/a/67577364/629263. More information on the non-context-managed version of httpx AsyncClient at https://github.com/encode/httpx/issues/769#issuecomment-601636292

phillipuniverse avatar Dec 23 '21 17:12 phillipuniverse