Skip to content

Commit

Permalink
fix: Async connections sessions were being closed
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobsvante committed Jun 2, 2022
1 parent c0074a0 commit 28e2b8e
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 53 deletions.
11 changes: 8 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,18 @@ ns = NetSuite(config)


async def async_main():
# NOTE: SOAP needs `pip install netsuite[soap_api]`
soap_api_results = await ns.soap_api.getList('customer', internalIds=[1337])

rest_api_results = await ns.rest_api.get("/record/v1/salesOrder")

restlet_results = await ns.restlet.get(987, deploy=2)

# NOTE: SOAP needs `pip install netsuite[soap_api]`
soap_api_results = await ns.soap_api.getList('customer', internalIds=[1337])

# Multiple requests, using the same underlying connection
async with ns.soap_api:
customers = await ns.soap_api.getList('customer', internalIds=[1, 2, 3])
sales_orders = await ns.soap_api.getList('salesOrder', internalIds=[1, 2])

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

Expand Down
77 changes: 27 additions & 50 deletions netsuite/soap_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,6 @@
__all__ = ("NetSuiteSoapApi",)


# TODO: Submit PR for the following changes (1170 uses a different method)
# This avoids the following warning on asyncio loop shutdown:
#
# UserWarning: Unclosed <httpx.AsyncClient object at 0x10e431be0>.
# See https://www.python-httpx.org/async/#opening-and-closing-clients
# for details.
#
class _AsyncClient(zeep.client.AsyncClient):
async def __aenter__(self):
await self.transport.client.__aenter__()
return self

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


class NetSuiteSoapApi:
version = "2021.1.0"
wsdl_url_tmpl = "https://{account_slug}.suitetalk.api.netsuite.com/wsdl/v{underscored_version}/netsuite.wsdl"
Expand All @@ -44,16 +26,15 @@ def __init__(
version: str = None,
wsdl_url: str = None,
cache: zeep.cache.Base = None,
session: zeep.requests.Session = None,
) -> None:
self._ensure_required_dependencies()
if version is not None:
assert re.match(r"\d+\.\d+\.\d+", version)
self.version = version
self.config = config
self.__wsdl_url = wsdl_url
self.__cache = cache
self.__session = session
self.config: Config = config
self._wsdl_url: Optional[str] = wsdl_url
self._cache: Optional[zeep.cache.Base] = cache
self._client: Optional[zeep.client.AsyncClient] = None

def __repr__(self) -> str:
return f"<{self.__class__.__name__} {self.hostname}({self.version})>"
Expand All @@ -66,26 +47,30 @@ async def __aexit__(self, exc_type=None, exc_value=None, traceback=None) -> None
await self.client.__aexit__(
exc_type=exc_type, exc_value=exc_value, traceback=traceback
)
# Connection is now closed by zeep. Generate a new one
self._client = self._generate_client()

@cached_property
@property
def wsdl_url(self) -> str:
return self.__wsdl_url or self._generate_wsdl_url()
if self._wsdl_url is None:
self._wsdl_url = self._generate_wsdl_url()
return self._wsdl_url

@cached_property
@property
def cache(self) -> zeep.cache.Base:
return self.__cache or self._generate_cache()
if self._cache is None:
self._cache = self._generate_cache()
return self._cache

@cached_property
def session(self) -> zeep.requests.Session:
return self.__session or self._generate_session()

@cached_property
def client(self) -> _AsyncClient:
return self._generate_client()
@property
def client(self) -> zeep.client.AsyncClient:
if self._client is None:
self._client = self._generate_client()
return self._client

@cached_property
@property
def transport(self):
return self._generate_transport()
return self.client.transport

@cached_property
def hostname(self) -> str:
Expand Down Expand Up @@ -117,8 +102,8 @@ def _generate_session(self) -> zeep.requests.Session:

def _generate_transport(self) -> zeep.transports.AsyncTransport:
return AsyncNetSuiteTransport(
self._generate_wsdl_url(),
session=self.session,
self.wsdl_url,
session=self._generate_session(),
cache=self.cache,
)

Expand All @@ -135,10 +120,10 @@ def with_timeout(self, timeout: int):
with self.transport.settings(timeout=timeout):
yield

def _generate_client(self) -> _AsyncClient:
return _AsyncClient(
def _generate_client(self) -> zeep.client.AsyncClient:
return zeep.client.AsyncClient(
self.wsdl_url,
transport=self.transport,
transport=self._generate_transport(),
)

def _get_namespace(self, name: str, sub_namespace: str) -> str:
Expand Down Expand Up @@ -358,15 +343,7 @@ async def request(self, service_name: str, *args, **kw):
The response from NetSuite
"""
svc = getattr(self.service, service_name)
# NOTE: Using httpx context manager here
# This avoids the following error on asyncio close:
#
# UserWarning: Unclosed <httpx.AsyncClient object at 0x10e431be0>.
# See https://www.python-httpx.org/async/#opening-and-closing-clients
# for details.
#
async with self:
return await svc(*args, _soapheaders=self.generate_passport(), **kw)
return await svc(*args, _soapheaders=self.generate_passport(), **kw)

@WebServiceCall(
"body.readResponseList.readResponse",
Expand Down

0 comments on commit 28e2b8e

Please sign in to comment.