Skip to content

Commit 5f604ce

Browse files
authored
Fix WSDL files being loaded multiple times and blocking the event loop (#21)
1 parent ca225c9 commit 5f604ce

File tree

1 file changed

+45
-7
lines changed

1 file changed

+45
-7
lines changed

onvif/client.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@
66
import logging
77
import os.path
88
import ssl
9-
from typing import Any, Dict, Optional, Tuple
9+
from typing import IO, Any, Dict, Optional, Tuple, Union
1010

1111
import httpx
1212
from httpx import AsyncClient, BasicAuth, DigestAuth
1313
from zeep.cache import SqliteCache
1414
from zeep.client import AsyncClient as BaseZeepAsyncClient, Settings
1515
from zeep.exceptions import Fault
1616
import zeep.helpers
17+
from zeep.loader import parse_xml
1718
from zeep.proxy import AsyncServiceProxy
1819
from zeep.transports import AsyncTransport
1920
from zeep.wsa import WsAddressingPlugin
21+
from zeep.wsdl import Document
2022
from zeep.wsse.username import UsernameToken
2123

2224
from onvif.definition import SERVICES
@@ -26,6 +28,10 @@
2628
logging.basicConfig(level=logging.INFO)
2729
logging.getLogger("zeep.client").setLevel(logging.CRITICAL)
2830

31+
_DEFAULT_SETTINGS = Settings()
32+
_DEFAULT_SETTINGS.strict = False
33+
_DEFAULT_SETTINGS.xml_huge_tree = True
34+
2935

3036
def create_no_verify_ssl_context() -> ssl.SSLContext:
3137
"""Return an SSL context that does not verify the server certificate.
@@ -85,6 +91,41 @@ def apply(self, envelope, headers):
8591
return result
8692

8793

94+
class AsyncSafeTransport:
95+
"""A transport that blocks all I/O for zeep."""
96+
97+
def load(self, *args: Any, **kwargs: Any) -> None:
98+
"""Load the given XML document.
99+
100+
This should never be called, but we want to raise
101+
an error if it is so we know we're doing something wrong
102+
and do not accidentally block the event loop.
103+
"""
104+
raise RuntimeError("Loading is not supported in async mode")
105+
106+
107+
_ASYNC_TRANSPORT = AsyncSafeTransport()
108+
109+
110+
@lru_cache(maxsize=128)
111+
def _cached_parse_xml(path: str) -> Any:
112+
"""Load external XML document from disk."""
113+
with open(os.path.expanduser(path), "rb") as fh:
114+
return parse_xml(fh.read(), _ASYNC_TRANSPORT, settings=_DEFAULT_SETTINGS)
115+
116+
117+
class DocumentWithCache(Document):
118+
"""A WSDL document that supports caching."""
119+
120+
def _get_xml_document(self, url: Union[IO, str]) -> Any:
121+
"""Load external XML document from a file-like object or URL."""
122+
if _path_isfile(url):
123+
return _cached_parse_xml(url)
124+
raise RuntimeError(
125+
f"Cannot fetch {url} in async mode because it would block the event loop"
126+
)
127+
128+
88129
class ZeepAsyncClient(BaseZeepAsyncClient):
89130
"""Overwrite create_service method to be async."""
90131

@@ -174,21 +215,18 @@ def __init__(
174215
client=client, wsdl_client=wsdl_client, cache=SqliteCache()
175216
)
176217
)
177-
settings = Settings()
178-
settings.strict = False
179-
settings.xml_huge_tree = True
218+
settings = _DEFAULT_SETTINGS
180219
self.zeep_client_authless = ZeepAsyncClient(
181-
wsdl=url,
220+
wsdl=DocumentWithCache(url, self.transport, settings=settings),
182221
transport=self.transport,
183222
settings=settings,
184223
plugins=[WsAddressingPlugin()],
185224
)
186225
self.ws_client_authless = self.zeep_client_authless.create_service(
187226
binding_name, self.xaddr
188227
)
189-
190228
self.zeep_client = ZeepAsyncClient(
191-
wsdl=url,
229+
wsdl=DocumentWithCache(url, self.transport, settings=settings),
192230
wsse=wsse,
193231
transport=self.transport,
194232
settings=settings,

0 commit comments

Comments
 (0)