|
6 | 6 | import logging
|
7 | 7 | import os.path
|
8 | 8 | import ssl
|
9 |
| -from typing import Any, Dict, Optional, Tuple |
| 9 | +from typing import IO, Any, Dict, Optional, Tuple, Union |
10 | 10 |
|
11 | 11 | import httpx
|
12 | 12 | from httpx import AsyncClient, BasicAuth, DigestAuth
|
13 | 13 | from zeep.cache import SqliteCache
|
14 | 14 | from zeep.client import AsyncClient as BaseZeepAsyncClient, Settings
|
15 | 15 | from zeep.exceptions import Fault
|
16 | 16 | import zeep.helpers
|
| 17 | +from zeep.loader import parse_xml |
17 | 18 | from zeep.proxy import AsyncServiceProxy
|
18 | 19 | from zeep.transports import AsyncTransport
|
19 | 20 | from zeep.wsa import WsAddressingPlugin
|
| 21 | +from zeep.wsdl import Document |
20 | 22 | from zeep.wsse.username import UsernameToken
|
21 | 23 |
|
22 | 24 | from onvif.definition import SERVICES
|
|
26 | 28 | logging.basicConfig(level=logging.INFO)
|
27 | 29 | logging.getLogger("zeep.client").setLevel(logging.CRITICAL)
|
28 | 30 |
|
| 31 | +_DEFAULT_SETTINGS = Settings() |
| 32 | +_DEFAULT_SETTINGS.strict = False |
| 33 | +_DEFAULT_SETTINGS.xml_huge_tree = True |
| 34 | + |
29 | 35 |
|
30 | 36 | def create_no_verify_ssl_context() -> ssl.SSLContext:
|
31 | 37 | """Return an SSL context that does not verify the server certificate.
|
@@ -85,6 +91,41 @@ def apply(self, envelope, headers):
|
85 | 91 | return result
|
86 | 92 |
|
87 | 93 |
|
| 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 | + |
88 | 129 | class ZeepAsyncClient(BaseZeepAsyncClient):
|
89 | 130 | """Overwrite create_service method to be async."""
|
90 | 131 |
|
@@ -174,21 +215,18 @@ def __init__(
|
174 | 215 | client=client, wsdl_client=wsdl_client, cache=SqliteCache()
|
175 | 216 | )
|
176 | 217 | )
|
177 |
| - settings = Settings() |
178 |
| - settings.strict = False |
179 |
| - settings.xml_huge_tree = True |
| 218 | + settings = _DEFAULT_SETTINGS |
180 | 219 | self.zeep_client_authless = ZeepAsyncClient(
|
181 |
| - wsdl=url, |
| 220 | + wsdl=DocumentWithCache(url, self.transport, settings=settings), |
182 | 221 | transport=self.transport,
|
183 | 222 | settings=settings,
|
184 | 223 | plugins=[WsAddressingPlugin()],
|
185 | 224 | )
|
186 | 225 | self.ws_client_authless = self.zeep_client_authless.create_service(
|
187 | 226 | binding_name, self.xaddr
|
188 | 227 | )
|
189 |
| - |
190 | 228 | self.zeep_client = ZeepAsyncClient(
|
191 |
| - wsdl=url, |
| 229 | + wsdl=DocumentWithCache(url, self.transport, settings=settings), |
192 | 230 | wsse=wsse,
|
193 | 231 | transport=self.transport,
|
194 | 232 | settings=settings,
|
|
0 commit comments