Skip to content

Commit cf7bb3f

Browse files
authored
Move document construction cache higher in the stack (#22)
1 parent 20881ad commit cf7bb3f

File tree

1 file changed

+21
-30
lines changed

1 file changed

+21
-30
lines changed

onvif/client.py

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@
66
import logging
77
import os.path
88
import ssl
9-
from typing import IO, Any, Dict, Optional, Tuple, Union
9+
from typing import Any, Dict, Optional, Tuple
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
1817
from zeep.proxy import AsyncServiceProxy
19-
from zeep.transports import AsyncTransport
18+
from zeep.transports import AsyncTransport, Transport
2019
from zeep.wsa import WsAddressingPlugin
2120
from zeep.wsdl import Document
2221
from zeep.wsse.username import UsernameToken
@@ -91,39 +90,30 @@ def apply(self, envelope, headers):
9190
return result
9291

9392

94-
class AsyncSafeTransport:
95-
"""A transport that blocks all I/O for zeep."""
93+
class AsyncSafeTransport(Transport):
94+
"""A transport that blocks all remote I/O for zeep."""
9695

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")
96+
def load(self, url: str) -> None:
97+
"""Load the given XML document."""
98+
if not _path_isfile(url):
99+
raise RuntimeError(f"Loading {url} is not supported in async mode")
100+
# Ideally this would happen in the executor but the library
101+
# does not call this from a coroutine so the best we can do
102+
# without a major refactor is to cache this so it only happens
103+
# once per process at startup. Previously it would happen once
104+
# per service per camera per setup which is a lot of blocking
105+
# I/O in the event loop so this is a major improvement.
106+
with open(os.path.expanduser(url), "rb") as fh:
107+
return fh.read()
105108

106109

107110
_ASYNC_TRANSPORT = AsyncSafeTransport()
108111

109112

110113
@lru_cache(maxsize=128)
111-
def _cached_parse_xml(path: str) -> Any:
114+
def _cached_document(url: str) -> Document:
112115
"""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-
)
116+
return Document(url, _ASYNC_TRANSPORT, settings=_DEFAULT_SETTINGS)
127117

128118

129119
class ZeepAsyncClient(BaseZeepAsyncClient):
@@ -216,8 +206,9 @@ def __init__(
216206
)
217207
)
218208
settings = _DEFAULT_SETTINGS
209+
document = _cached_document(url)
219210
self.zeep_client_authless = ZeepAsyncClient(
220-
wsdl=DocumentWithCache(url, self.transport, settings=settings),
211+
wsdl=document,
221212
transport=self.transport,
222213
settings=settings,
223214
plugins=[WsAddressingPlugin()],
@@ -226,7 +217,7 @@ def __init__(
226217
binding_name, self.xaddr
227218
)
228219
self.zeep_client = ZeepAsyncClient(
229-
wsdl=DocumentWithCache(url, self.transport, settings=settings),
220+
wsdl=document,
230221
wsse=wsse,
231222
transport=self.transport,
232223
settings=settings,

0 commit comments

Comments
 (0)