python-zeep
python-zeep copied to clipboard
UserWarning: Unclosed httpx.AsyncClient
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.
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')
what would call that method @AndrewMagerman
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())
@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
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