Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UserWarning: Unclosed httpx.AsyncClient #1224

Open
bitsofinfo opened this issue May 6, 2021 · 5 comments
Open

UserWarning: Unclosed httpx.AsyncClient #1224

bitsofinfo opened this issue May 6, 2021 · 5 comments

Comments

@bitsofinfo
Copy link

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.
@AndrewMagerman
Copy link

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')

@bitsofinfo
Copy link
Author

what would call that method @AndrewMagerman

@AndrewMagerman
Copy link

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())

@phillipuniverse
Copy link

phillipuniverse commented Dec 23, 2021

@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:

async def __aenter__(self):
return self
async def __aexit__(self, exc_type=None, exc_value=None, traceback=None) -> None:
await self.transport.aclose()

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

async def aclose(self):
await self.client.aclose()

@phillipuniverse
Copy link

phillipuniverse commented Dec 23, 2021

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 encode/httpx#769 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants