python3-commons 0.18.4__tar.gz → 0.18.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python3_commons-0.18.4/src/python3_commons.egg-info → python3_commons-0.18.6}/PKG-INFO +4 -1
- {python3_commons-0.18.4 → python3_commons-0.18.6}/pyproject.toml +3 -0
- python3_commons-0.18.6/src/python3_commons/soap_client.py +250 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6/src/python3_commons.egg-info}/PKG-INFO +4 -1
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons.egg-info/requires.txt +3 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/uv.lock +8 -4
- python3_commons-0.18.4/src/python3_commons/soap_client.py +0 -299
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.coveragerc +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.devcontainer/Dockerfile +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.devcontainer/devcontainer.json +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.devcontainer/docker-compose.yml +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.env_template +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.github/workflows/checks.yml +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.github/workflows/python-publish.yaml +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.github/workflows/release-on-tag-push.yml +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.gitignore +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.pre-commit-config.yaml +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/.python-version +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/AUTHORS.rst +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/CHANGELOG.rst +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/LICENSE +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/README.md +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/README.rst +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/docs/Makefile +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/docs/_static/.gitignore +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/docs/authors.rst +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/docs/changelog.rst +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/docs/conf.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/docs/index.rst +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/docs/license.rst +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/setup.cfg +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/api_client.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/async_functools.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/audit.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/auth.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/cache.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/conf.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/db/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/db/helpers.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/db/models/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/db/models/auth.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/db/models/common.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/db/models/rbac.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/db/models/users.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/exceptions.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/fs.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/generators.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/helpers.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/log/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/log/filters.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/log/formatters.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/object_storage.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/permissions.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/common.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/json.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/msgpack.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/msgspec.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons.egg-info/SOURCES.txt +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons.egg-info/dependency_links.txt +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons.egg-info/top_level.txt +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/integration/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/integration/test_cache.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/integration/test_osc.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/conftest.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/log/__init__.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/log/test_formatters.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/test_async_functools.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/test_audit.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/test_helpers.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/test_msgpack.py +0 -0
- {python3_commons-0.18.4 → python3_commons-0.18.6}/tests/unit/test_msgspec.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.6
|
|
4
4
|
Summary: Re-usable Python3 code
|
|
5
5
|
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0
|
|
@@ -19,6 +19,7 @@ Provides-Extra: all
|
|
|
19
19
|
Requires-Dist: python3_commons[api-client,audit,authn,authz,cache,database,object-storage,soap-client]; extra == "all"
|
|
20
20
|
Provides-Extra: api-client
|
|
21
21
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "api-client"
|
|
22
|
+
Requires-Dist: certifi==2026.4.22; extra == "api-client"
|
|
22
23
|
Requires-Dist: python3_commons[object-storage]; extra == "api-client"
|
|
23
24
|
Provides-Extra: audit
|
|
24
25
|
Requires-Dist: lxml~=6.1.0; extra == "audit"
|
|
@@ -26,6 +27,7 @@ Requires-Dist: zeep[async]~=4.3.2; extra == "audit"
|
|
|
26
27
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
27
28
|
Provides-Extra: authn
|
|
28
29
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
|
|
30
|
+
Requires-Dist: certifi==2026.4.22; extra == "authn"
|
|
29
31
|
Requires-Dist: python3_commons[api-client]; extra == "authn"
|
|
30
32
|
Provides-Extra: authz
|
|
31
33
|
Requires-Dist: python3_commons[database]; extra == "authz"
|
|
@@ -40,6 +42,7 @@ Requires-Dist: aiobotocore~=3.5.0; extra == "object-storage"
|
|
|
40
42
|
Requires-Dist: object-storage-client==0.0.23; extra == "object-storage"
|
|
41
43
|
Provides-Extra: soap-client
|
|
42
44
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "soap-client"
|
|
45
|
+
Requires-Dist: certifi==2026.4.22; extra == "soap-client"
|
|
43
46
|
Requires-Dist: lxml~=6.1.0; extra == "soap-client"
|
|
44
47
|
Requires-Dist: requests~=2.33.1; extra == "soap-client"
|
|
45
48
|
Requires-Dist: zeep[async]~=4.3.2; extra == "soap-client"
|
|
@@ -29,6 +29,7 @@ all = [
|
|
|
29
29
|
]
|
|
30
30
|
api-client = [
|
|
31
31
|
"aiohttp[speedups]>=3.13.5,<3.15.0",
|
|
32
|
+
"certifi==2026.4.22",
|
|
32
33
|
"python3_commons[object-storage]"
|
|
33
34
|
]
|
|
34
35
|
audit = [
|
|
@@ -38,6 +39,7 @@ audit = [
|
|
|
38
39
|
]
|
|
39
40
|
authn = [
|
|
40
41
|
"aiohttp[speedups]>=3.13.5,<3.15.0",
|
|
42
|
+
"certifi==2026.4.22",
|
|
41
43
|
"python3_commons[api-client]"
|
|
42
44
|
]
|
|
43
45
|
authz = [
|
|
@@ -57,6 +59,7 @@ object-storage = [
|
|
|
57
59
|
]
|
|
58
60
|
soap-client = [
|
|
59
61
|
"aiohttp[speedups]>=3.13.5,<3.15.0",
|
|
62
|
+
"certifi==2026.4.22",
|
|
60
63
|
"lxml~=6.1.0",
|
|
61
64
|
"requests~=2.33.1",
|
|
62
65
|
"zeep[async]~=4.3.2",
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async SOAP client built on aiohttp + zeep.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import concurrent.futures
|
|
9
|
+
import logging
|
|
10
|
+
import ssl
|
|
11
|
+
from collections.abc import AsyncIterator, Sequence
|
|
12
|
+
from contextlib import asynccontextmanager
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
15
|
+
|
|
16
|
+
import certifi
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import aiohttp
|
|
20
|
+
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
|
21
|
+
from requests import Response
|
|
22
|
+
from requests.cookies import RequestsCookieJar
|
|
23
|
+
from zeep import AsyncClient
|
|
24
|
+
from zeep.exceptions import TransportError
|
|
25
|
+
from zeep.transports import Transport
|
|
26
|
+
from zeep.utils import get_version
|
|
27
|
+
from zeep.wsdl.utils import etree_to_string
|
|
28
|
+
except ImportError as e:
|
|
29
|
+
msg = 'Install python3-commons[soap-client] to use this feature'
|
|
30
|
+
|
|
31
|
+
raise RuntimeError(msg) from e
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from zeep.plugins import Plugin
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _make_ssl_context(*, verify: bool) -> ssl.SSLContext | bool:
|
|
40
|
+
if not verify:
|
|
41
|
+
return False # aiohttp accepts False to disable verification
|
|
42
|
+
|
|
43
|
+
return ssl.create_default_context(cafile=certifi.where())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True)
|
|
47
|
+
class TransportConfig:
|
|
48
|
+
"""Immutable transport settings passed to AsyncTransport."""
|
|
49
|
+
|
|
50
|
+
timeout: int = 300
|
|
51
|
+
"""Total timeout in seconds for WSDL fetches."""
|
|
52
|
+
|
|
53
|
+
operation_timeout: int = 60
|
|
54
|
+
"""Total timeout in seconds for SOAP operation calls."""
|
|
55
|
+
|
|
56
|
+
verify_ssl: bool = True
|
|
57
|
+
proxy: str | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AsyncTransport(Transport):
|
|
61
|
+
"""
|
|
62
|
+
Async transport for zeep using aiohttp.
|
|
63
|
+
|
|
64
|
+
Usage::
|
|
65
|
+
|
|
66
|
+
async with soap_client("https://example.com/service?wsdl") as client:
|
|
67
|
+
result = await client.service.SomeOperation(...)
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
session: ClientSession,
|
|
74
|
+
config: TransportConfig,
|
|
75
|
+
_owns_session: bool = False,
|
|
76
|
+
) -> None:
|
|
77
|
+
super().__init__()
|
|
78
|
+
self._session = session
|
|
79
|
+
self._config = config
|
|
80
|
+
self._owns_session = _owns_session
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_config(
|
|
84
|
+
cls,
|
|
85
|
+
config: TransportConfig | None = None,
|
|
86
|
+
*,
|
|
87
|
+
session: ClientSession | None = None,
|
|
88
|
+
) -> Self:
|
|
89
|
+
config = config or TransportConfig()
|
|
90
|
+
owns_session = session is None
|
|
91
|
+
|
|
92
|
+
if owns_session:
|
|
93
|
+
session = ClientSession(
|
|
94
|
+
connector=TCPConnector(ssl=_make_ssl_context(verify=config.verify_ssl)),
|
|
95
|
+
timeout=ClientTimeout(total=config.operation_timeout),
|
|
96
|
+
headers={'User-Agent': f'Zeep/{get_version()} (www.python-zeep.org)'},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return cls(session=session, config=config, _owns_session=owns_session)
|
|
100
|
+
|
|
101
|
+
async def aclose(self) -> None:
|
|
102
|
+
if self._owns_session:
|
|
103
|
+
await self._session.close()
|
|
104
|
+
|
|
105
|
+
async def __aenter__(self) -> Self:
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
async def __aexit__(self, *_: object) -> None:
|
|
109
|
+
await self.aclose()
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _build_response(response: aiohttp.ClientResponse, body: bytes) -> Response:
|
|
113
|
+
"""Convert an aiohttp response into a requests.Response for zeep."""
|
|
114
|
+
r = Response()
|
|
115
|
+
r.status_code = response.status
|
|
116
|
+
r._content = body # noqa: SLF001
|
|
117
|
+
r.headers = dict(response.headers)
|
|
118
|
+
r.encoding = response.charset
|
|
119
|
+
r.url = str(response.url)
|
|
120
|
+
|
|
121
|
+
jar = RequestsCookieJar()
|
|
122
|
+
|
|
123
|
+
for name, morsel in response.cookies.items():
|
|
124
|
+
jar.set(name, morsel.value)
|
|
125
|
+
|
|
126
|
+
r.cookies = jar
|
|
127
|
+
|
|
128
|
+
return r
|
|
129
|
+
|
|
130
|
+
def load(self, url: str) -> bytes:
|
|
131
|
+
"""
|
|
132
|
+
Sync entry-point zeep calls during WSDL document init.
|
|
133
|
+
|
|
134
|
+
Creates a short-lived session confined to its own event loop so there
|
|
135
|
+
is no cross-loop session sharing with the operational session.
|
|
136
|
+
"""
|
|
137
|
+
if not url:
|
|
138
|
+
return b''
|
|
139
|
+
|
|
140
|
+
async def _fetch() -> bytes:
|
|
141
|
+
async with (
|
|
142
|
+
ClientSession(
|
|
143
|
+
connector=TCPConnector(ssl=_make_ssl_context(verify=self._config.verify_ssl)),
|
|
144
|
+
timeout=ClientTimeout(total=self._config.timeout),
|
|
145
|
+
headers={'User-Agent': f'Zeep/{get_version()} (www.python-zeep.org)'},
|
|
146
|
+
) as session,
|
|
147
|
+
session.get(url, proxy=self._config.proxy) as resp,
|
|
148
|
+
):
|
|
149
|
+
content = await resp.read()
|
|
150
|
+
|
|
151
|
+
if resp.status >= 400:
|
|
152
|
+
raise TransportError(
|
|
153
|
+
status_code=resp.status,
|
|
154
|
+
message=content.decode(errors='ignore'),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return content
|
|
158
|
+
|
|
159
|
+
# load() is always called from within a running event loop (during
|
|
160
|
+
# AsyncClient.__init__ inside the soap_client context manager), so
|
|
161
|
+
# run_until_complete on the running loop would raise. Always delegate
|
|
162
|
+
# to a thread with its own fresh event loop.
|
|
163
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
164
|
+
return pool.submit(asyncio.run, _fetch()).result()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
async def post(
|
|
168
|
+
self,
|
|
169
|
+
address: str,
|
|
170
|
+
message: bytes,
|
|
171
|
+
headers: dict[str, str],
|
|
172
|
+
*,
|
|
173
|
+
timeout: int | None = None,
|
|
174
|
+
) -> Response:
|
|
175
|
+
logger.debug('SOAP POST → %s\n%s', address, message)
|
|
176
|
+
|
|
177
|
+
async with self._session.post(
|
|
178
|
+
address,
|
|
179
|
+
data=message,
|
|
180
|
+
headers=headers,
|
|
181
|
+
proxy=self._config.proxy,
|
|
182
|
+
timeout=ClientTimeout(total=timeout) if timeout is not None else None,
|
|
183
|
+
) as resp:
|
|
184
|
+
body = await resp.read()
|
|
185
|
+
logger.debug('SOAP ← %s (HTTP %d)\n%s', address, resp.status, body)
|
|
186
|
+
|
|
187
|
+
return self._build_response(resp, body)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def post_xml(
|
|
191
|
+
self,
|
|
192
|
+
address: str,
|
|
193
|
+
envelope: Any,
|
|
194
|
+
headers: dict[str, str],
|
|
195
|
+
) -> Response:
|
|
196
|
+
return await self.post(address, etree_to_string(envelope), headers)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def get(
|
|
200
|
+
self,
|
|
201
|
+
address: str,
|
|
202
|
+
params: dict[str, str],
|
|
203
|
+
headers: dict[str, str],
|
|
204
|
+
) -> Response:
|
|
205
|
+
async with self._session.get(
|
|
206
|
+
address,
|
|
207
|
+
params=params,
|
|
208
|
+
headers=headers,
|
|
209
|
+
proxy=self._config.proxy,
|
|
210
|
+
) as resp:
|
|
211
|
+
body = await resp.read()
|
|
212
|
+
|
|
213
|
+
return self._build_response(resp, body)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def build_soap_client(
|
|
217
|
+
wsdl_url: str,
|
|
218
|
+
transport: AsyncTransport,
|
|
219
|
+
plugins: Sequence[Plugin] | None = None,
|
|
220
|
+
) -> AsyncClient:
|
|
221
|
+
if not wsdl_url:
|
|
222
|
+
msg = 'wsdl_url must be a non-empty string.'
|
|
223
|
+
|
|
224
|
+
raise ValueError(msg)
|
|
225
|
+
|
|
226
|
+
return AsyncClient(wsdl_url, transport=transport, plugins=list(plugins or []))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@asynccontextmanager
|
|
230
|
+
async def soap_client(
|
|
231
|
+
wsdl_url: str,
|
|
232
|
+
*,
|
|
233
|
+
config: TransportConfig | None = None,
|
|
234
|
+
session: ClientSession | None = None,
|
|
235
|
+
plugins: Sequence[Plugin] | None = None,
|
|
236
|
+
) -> AsyncIterator[AsyncClient]:
|
|
237
|
+
"""
|
|
238
|
+
Async context manager yielding a ready-to-use zeep AsyncClient.
|
|
239
|
+
|
|
240
|
+
Example::
|
|
241
|
+
|
|
242
|
+
async with soap_client("https://example.com/service?wsdl") as client:
|
|
243
|
+
result = await client.service.GetData(id=42)
|
|
244
|
+
"""
|
|
245
|
+
transport = AsyncTransport.from_config(config, session=session)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
yield build_soap_client(wsdl_url, transport, plugins)
|
|
249
|
+
finally:
|
|
250
|
+
await transport.aclose()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.6
|
|
4
4
|
Summary: Re-usable Python3 code
|
|
5
5
|
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0
|
|
@@ -19,6 +19,7 @@ Provides-Extra: all
|
|
|
19
19
|
Requires-Dist: python3_commons[api-client,audit,authn,authz,cache,database,object-storage,soap-client]; extra == "all"
|
|
20
20
|
Provides-Extra: api-client
|
|
21
21
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "api-client"
|
|
22
|
+
Requires-Dist: certifi==2026.4.22; extra == "api-client"
|
|
22
23
|
Requires-Dist: python3_commons[object-storage]; extra == "api-client"
|
|
23
24
|
Provides-Extra: audit
|
|
24
25
|
Requires-Dist: lxml~=6.1.0; extra == "audit"
|
|
@@ -26,6 +27,7 @@ Requires-Dist: zeep[async]~=4.3.2; extra == "audit"
|
|
|
26
27
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
27
28
|
Provides-Extra: authn
|
|
28
29
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
|
|
30
|
+
Requires-Dist: certifi==2026.4.22; extra == "authn"
|
|
29
31
|
Requires-Dist: python3_commons[api-client]; extra == "authn"
|
|
30
32
|
Provides-Extra: authz
|
|
31
33
|
Requires-Dist: python3_commons[database]; extra == "authz"
|
|
@@ -40,6 +42,7 @@ Requires-Dist: aiobotocore~=3.5.0; extra == "object-storage"
|
|
|
40
42
|
Requires-Dist: object-storage-client==0.0.23; extra == "object-storage"
|
|
41
43
|
Provides-Extra: soap-client
|
|
42
44
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "soap-client"
|
|
45
|
+
Requires-Dist: certifi==2026.4.22; extra == "soap-client"
|
|
43
46
|
Requires-Dist: lxml~=6.1.0; extra == "soap-client"
|
|
44
47
|
Requires-Dist: requests~=2.33.1; extra == "soap-client"
|
|
45
48
|
Requires-Dist: zeep[async]~=4.3.2; extra == "soap-client"
|
|
@@ -7,6 +7,7 @@ python3_commons[api-client,audit,authn,authz,cache,database,object-storage,soap-
|
|
|
7
7
|
|
|
8
8
|
[api-client]
|
|
9
9
|
aiohttp[speedups]<3.15.0,>=3.13.5
|
|
10
|
+
certifi==2026.4.22
|
|
10
11
|
python3_commons[object-storage]
|
|
11
12
|
|
|
12
13
|
[audit]
|
|
@@ -16,6 +17,7 @@ python3_commons[object-storage]
|
|
|
16
17
|
|
|
17
18
|
[authn]
|
|
18
19
|
aiohttp[speedups]<3.15.0,>=3.13.5
|
|
20
|
+
certifi==2026.4.22
|
|
19
21
|
python3_commons[api-client]
|
|
20
22
|
|
|
21
23
|
[authz]
|
|
@@ -35,6 +37,7 @@ object-storage-client==0.0.23
|
|
|
35
37
|
|
|
36
38
|
[soap-client]
|
|
37
39
|
aiohttp[speedups]<3.15.0,>=3.13.5
|
|
40
|
+
certifi==2026.4.22
|
|
38
41
|
lxml~=6.1.0
|
|
39
42
|
requests~=2.33.1
|
|
40
43
|
zeep[async]~=4.3.2
|
|
@@ -1051,6 +1051,7 @@ all = [
|
|
|
1051
1051
|
{ name = "aiobotocore" },
|
|
1052
1052
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1053
1053
|
{ name = "asyncpg" },
|
|
1054
|
+
{ name = "certifi" },
|
|
1054
1055
|
{ name = "lxml" },
|
|
1055
1056
|
{ name = "object-storage-client" },
|
|
1056
1057
|
{ name = "requests" },
|
|
@@ -1061,7 +1062,7 @@ all = [
|
|
|
1061
1062
|
api-client = [
|
|
1062
1063
|
{ name = "aiobotocore" },
|
|
1063
1064
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1064
|
-
{ name = "
|
|
1065
|
+
{ name = "certifi" },
|
|
1065
1066
|
{ name = "object-storage-client" },
|
|
1066
1067
|
]
|
|
1067
1068
|
audit = [
|
|
@@ -1073,14 +1074,14 @@ audit = [
|
|
|
1073
1074
|
authn = [
|
|
1074
1075
|
{ name = "aiobotocore" },
|
|
1075
1076
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1076
|
-
{ name = "
|
|
1077
|
+
{ name = "certifi" },
|
|
1077
1078
|
{ name = "object-storage-client" },
|
|
1078
1079
|
]
|
|
1079
1080
|
authz = [
|
|
1080
1081
|
{ name = "aiobotocore" },
|
|
1081
1082
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1082
1083
|
{ name = "asyncpg" },
|
|
1083
|
-
{ name = "
|
|
1084
|
+
{ name = "certifi" },
|
|
1084
1085
|
{ name = "object-storage-client" },
|
|
1085
1086
|
{ name = "sqlalchemy", extra = ["asyncio"] },
|
|
1086
1087
|
]
|
|
@@ -1097,6 +1098,7 @@ object-storage = [
|
|
|
1097
1098
|
]
|
|
1098
1099
|
soap-client = [
|
|
1099
1100
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1101
|
+
{ name = "certifi" },
|
|
1100
1102
|
{ name = "lxml" },
|
|
1101
1103
|
{ name = "requests" },
|
|
1102
1104
|
{ name = "zeep", extra = ["async"] },
|
|
@@ -1126,7 +1128,9 @@ requires-dist = [
|
|
|
1126
1128
|
{ name = "aiohttp", extras = ["speedups"], marker = "extra == 'authn'", specifier = ">=3.13.5,<3.15.0" },
|
|
1127
1129
|
{ name = "aiohttp", extras = ["speedups"], marker = "extra == 'soap-client'", specifier = ">=3.13.5,<3.15.0" },
|
|
1128
1130
|
{ name = "asyncpg", marker = "extra == 'database'", specifier = "~=0.31.0" },
|
|
1129
|
-
{ name = "
|
|
1131
|
+
{ name = "certifi", marker = "extra == 'api-client'", specifier = "==2026.4.22" },
|
|
1132
|
+
{ name = "certifi", marker = "extra == 'authn'", specifier = "==2026.4.22" },
|
|
1133
|
+
{ name = "certifi", marker = "extra == 'soap-client'", specifier = "==2026.4.22" },
|
|
1130
1134
|
{ name = "lxml", marker = "extra == 'audit'", specifier = "~=6.1.0" },
|
|
1131
1135
|
{ name = "lxml", marker = "extra == 'soap-client'", specifier = "~=6.1.0" },
|
|
1132
1136
|
{ name = "msgpack", specifier = "~=1.1.2" },
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Async SOAP client built on aiohttp + zeep.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
import concurrent.futures
|
|
9
|
-
import logging
|
|
10
|
-
from collections.abc import AsyncIterator, Sequence
|
|
11
|
-
from contextlib import asynccontextmanager
|
|
12
|
-
from dataclasses import dataclass
|
|
13
|
-
from typing import TYPE_CHECKING, Any, Self
|
|
14
|
-
|
|
15
|
-
try:
|
|
16
|
-
import aiohttp
|
|
17
|
-
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
|
18
|
-
from requests import Response
|
|
19
|
-
from requests.cookies import RequestsCookieJar
|
|
20
|
-
from zeep import AsyncClient
|
|
21
|
-
from zeep.exceptions import TransportError
|
|
22
|
-
from zeep.transports import Transport
|
|
23
|
-
from zeep.utils import get_version
|
|
24
|
-
from zeep.wsdl.utils import etree_to_string
|
|
25
|
-
except ImportError as e:
|
|
26
|
-
msg = 'Install python3-commons[soap-client] to use this feature'
|
|
27
|
-
|
|
28
|
-
raise RuntimeError(msg) from e
|
|
29
|
-
|
|
30
|
-
if TYPE_CHECKING:
|
|
31
|
-
from zeep.plugins import Plugin
|
|
32
|
-
|
|
33
|
-
logger = logging.getLogger(__name__)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@dataclass(frozen=True, slots=True)
|
|
37
|
-
class TransportConfig:
|
|
38
|
-
"""Immutable transport settings passed to AsyncTransport."""
|
|
39
|
-
|
|
40
|
-
timeout: int = 300
|
|
41
|
-
"""Total timeout in seconds for WSDL fetches."""
|
|
42
|
-
|
|
43
|
-
operation_timeout: int = 60
|
|
44
|
-
"""Total timeout in seconds for SOAP operation calls."""
|
|
45
|
-
|
|
46
|
-
verify_ssl: bool = True
|
|
47
|
-
proxy: str | None = None
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class AsyncTransport(Transport):
|
|
51
|
-
"""
|
|
52
|
-
Async transport for zeep using aiohttp.
|
|
53
|
-
|
|
54
|
-
Usage::
|
|
55
|
-
|
|
56
|
-
async with AsyncTransport.from_config(config) as transport:
|
|
57
|
-
client = AsyncClient(wsdl_url, transport=transport)
|
|
58
|
-
result = await client.service.SomeOperation(...)
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
def __init__(
|
|
62
|
-
self,
|
|
63
|
-
*,
|
|
64
|
-
session: ClientSession,
|
|
65
|
-
wsdl_session: ClientSession,
|
|
66
|
-
config: TransportConfig,
|
|
67
|
-
_owns_session: bool = False,
|
|
68
|
-
_owns_wsdl_session: bool = False,
|
|
69
|
-
) -> None:
|
|
70
|
-
super().__init__()
|
|
71
|
-
self._session = session
|
|
72
|
-
self._wsdl_session = wsdl_session
|
|
73
|
-
self._config = config
|
|
74
|
-
self._owns_session = _owns_session
|
|
75
|
-
self._owns_wsdl_session = _owns_wsdl_session
|
|
76
|
-
|
|
77
|
-
@classmethod
|
|
78
|
-
def from_config(
|
|
79
|
-
cls,
|
|
80
|
-
config: TransportConfig | None = None,
|
|
81
|
-
*,
|
|
82
|
-
session: ClientSession | None = None,
|
|
83
|
-
wsdl_session: ClientSession | None = None,
|
|
84
|
-
) -> AsyncTransport:
|
|
85
|
-
"""
|
|
86
|
-
Create a transport, optionally sharing an existing ClientSession.
|
|
87
|
-
|
|
88
|
-
If *session* / *wsdl_session* are omitted the transport owns (and
|
|
89
|
-
will close) the sessions it creates.
|
|
90
|
-
"""
|
|
91
|
-
config = config or TransportConfig()
|
|
92
|
-
connector = TCPConnector(ssl=config.verify_ssl)
|
|
93
|
-
user_agent = f'Zeep/{get_version()} (www.python-zeep.org)'
|
|
94
|
-
|
|
95
|
-
owns_session = session is None
|
|
96
|
-
owns_wsdl_session = wsdl_session is None
|
|
97
|
-
|
|
98
|
-
if owns_session:
|
|
99
|
-
session = ClientSession(
|
|
100
|
-
connector=connector,
|
|
101
|
-
timeout=ClientTimeout(total=config.operation_timeout),
|
|
102
|
-
headers={'User-Agent': user_agent},
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
if owns_wsdl_session:
|
|
106
|
-
wsdl_session = ClientSession(
|
|
107
|
-
connector=connector,
|
|
108
|
-
timeout=ClientTimeout(total=config.timeout),
|
|
109
|
-
headers={'User-Agent': user_agent},
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
return cls(
|
|
113
|
-
session=session,
|
|
114
|
-
wsdl_session=wsdl_session,
|
|
115
|
-
config=config,
|
|
116
|
-
_owns_session=owns_session,
|
|
117
|
-
_owns_wsdl_session=owns_wsdl_session,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
async def aclose(self) -> None:
|
|
121
|
-
if self._owns_session:
|
|
122
|
-
await self._session.close()
|
|
123
|
-
if self._owns_wsdl_session:
|
|
124
|
-
await self._wsdl_session.close()
|
|
125
|
-
|
|
126
|
-
async def __aenter__(self) -> Self:
|
|
127
|
-
return self
|
|
128
|
-
|
|
129
|
-
async def __aexit__(self, *_: object) -> None:
|
|
130
|
-
await self.aclose()
|
|
131
|
-
|
|
132
|
-
@staticmethod
|
|
133
|
-
def _build_response(response: aiohttp.ClientResponse, body: bytes) -> Response:
|
|
134
|
-
"""Convert an aiohttp response into a requests.Response for zeep."""
|
|
135
|
-
r = Response()
|
|
136
|
-
r.status_code = response.status
|
|
137
|
-
r._content = body # noqa: SLF001 (zeep reads this attribute directly)
|
|
138
|
-
r.headers = dict(response.headers)
|
|
139
|
-
r.encoding = response.charset
|
|
140
|
-
r.url = str(response.url)
|
|
141
|
-
|
|
142
|
-
# Bridge aiohttp SimpleCookie → RequestsCookieJar so zeep / requests
|
|
143
|
-
# cookie handling works correctly.
|
|
144
|
-
jar = RequestsCookieJar()
|
|
145
|
-
|
|
146
|
-
for name, morsel in response.cookies.items():
|
|
147
|
-
jar.set(name, morsel.value)
|
|
148
|
-
|
|
149
|
-
r.cookies = jar
|
|
150
|
-
|
|
151
|
-
return r
|
|
152
|
-
|
|
153
|
-
async def _load_remote_data(self, url: str) -> bytes:
|
|
154
|
-
"""Fetch WSDL / XSD documents (called by zeep during init)."""
|
|
155
|
-
|
|
156
|
-
async def _fetch() -> bytes:
|
|
157
|
-
async with self._wsdl_session.get(url, proxy=self._config.proxy) as resp:
|
|
158
|
-
content = await resp.read()
|
|
159
|
-
|
|
160
|
-
if resp.status >= 400:
|
|
161
|
-
raise TransportError(
|
|
162
|
-
status_code=resp.status,
|
|
163
|
-
message=content.decode(errors='ignore'),
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
return content
|
|
167
|
-
|
|
168
|
-
return await _fetch()
|
|
169
|
-
|
|
170
|
-
def load(self, url: str) -> bytes:
|
|
171
|
-
"""Sync entry-point zeep calls during WSDL document init."""
|
|
172
|
-
if not url:
|
|
173
|
-
return b''
|
|
174
|
-
|
|
175
|
-
async def _fetch() -> bytes:
|
|
176
|
-
connector = TCPConnector(ssl=self._config.verify_ssl)
|
|
177
|
-
|
|
178
|
-
async with ClientSession(
|
|
179
|
-
connector=connector,
|
|
180
|
-
timeout=ClientTimeout(total=self._config.timeout),
|
|
181
|
-
headers={'User-Agent': f'Zeep/{get_version()} (www.python-zeep.org)'},
|
|
182
|
-
) as session, session.get(url, proxy=self._config.proxy) as resp:
|
|
183
|
-
content = await resp.read()
|
|
184
|
-
|
|
185
|
-
if resp.status >= 400:
|
|
186
|
-
raise TransportError(
|
|
187
|
-
status_code=resp.status,
|
|
188
|
-
message=content.decode(errors='ignore'),
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
return content
|
|
192
|
-
|
|
193
|
-
try:
|
|
194
|
-
loop = asyncio.get_event_loop()
|
|
195
|
-
|
|
196
|
-
return loop.run_until_complete(_fetch())
|
|
197
|
-
except RuntimeError:
|
|
198
|
-
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
199
|
-
future = pool.submit(asyncio.run, _fetch())
|
|
200
|
-
|
|
201
|
-
return future.result()
|
|
202
|
-
|
|
203
|
-
async def post(
|
|
204
|
-
self,
|
|
205
|
-
address: str,
|
|
206
|
-
message: bytes,
|
|
207
|
-
headers: dict[str, str],
|
|
208
|
-
*,
|
|
209
|
-
timeout: int | None = None,
|
|
210
|
-
) -> Response:
|
|
211
|
-
logger.debug('SOAP POST → %s\n%s', address, message)
|
|
212
|
-
|
|
213
|
-
request_timeout = ClientTimeout(total=timeout) if timeout is not None else None
|
|
214
|
-
|
|
215
|
-
async def _post() -> Response:
|
|
216
|
-
async with self._session.post(
|
|
217
|
-
address,
|
|
218
|
-
data=message,
|
|
219
|
-
headers=headers,
|
|
220
|
-
proxy=self._config.proxy,
|
|
221
|
-
timeout=request_timeout,
|
|
222
|
-
) as resp:
|
|
223
|
-
body = await resp.read()
|
|
224
|
-
logger.debug('SOAP ← %s (HTTP %d)\n%s', address, resp.status, body)
|
|
225
|
-
|
|
226
|
-
return self._build_response(resp, body)
|
|
227
|
-
|
|
228
|
-
return await _post()
|
|
229
|
-
|
|
230
|
-
async def post_xml(
|
|
231
|
-
self,
|
|
232
|
-
address: str,
|
|
233
|
-
envelope: Any,
|
|
234
|
-
headers: dict[str, str],
|
|
235
|
-
) -> Response:
|
|
236
|
-
message = etree_to_string(envelope)
|
|
237
|
-
|
|
238
|
-
return await self.post(address, message, headers)
|
|
239
|
-
|
|
240
|
-
async def get(
|
|
241
|
-
self,
|
|
242
|
-
address: str,
|
|
243
|
-
params: dict[str, str],
|
|
244
|
-
headers: dict[str, str],
|
|
245
|
-
) -> Response:
|
|
246
|
-
async with self._session.get(
|
|
247
|
-
address,
|
|
248
|
-
params=params,
|
|
249
|
-
headers=headers,
|
|
250
|
-
proxy=self._config.proxy,
|
|
251
|
-
) as resp:
|
|
252
|
-
body = await resp.read()
|
|
253
|
-
|
|
254
|
-
return self._build_response(resp, body)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def build_soap_client(
|
|
258
|
-
wsdl_url: str,
|
|
259
|
-
transport: AsyncTransport,
|
|
260
|
-
plugins: Sequence[Plugin] | None = None,
|
|
261
|
-
) -> AsyncClient:
|
|
262
|
-
"""
|
|
263
|
-
Construct a zeep AsyncClient with the supplied transport and plugins.
|
|
264
|
-
|
|
265
|
-
Raises ValueError if *wsdl_url* is empty or None.
|
|
266
|
-
"""
|
|
267
|
-
if not wsdl_url:
|
|
268
|
-
msg = 'wsdl_url must be a non-empty string.'
|
|
269
|
-
|
|
270
|
-
raise ValueError(msg)
|
|
271
|
-
|
|
272
|
-
return AsyncClient(wsdl_url, transport=transport, plugins=list(plugins or []))
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
@asynccontextmanager
|
|
276
|
-
async def soap_client(
|
|
277
|
-
wsdl_url: str,
|
|
278
|
-
*,
|
|
279
|
-
config: TransportConfig | None = None,
|
|
280
|
-
session: ClientSession | None = None,
|
|
281
|
-
plugins: Sequence[Plugin] | None = None,
|
|
282
|
-
) -> AsyncIterator[AsyncClient]:
|
|
283
|
-
"""
|
|
284
|
-
Async context manager that yields a ready-to-use zeep AsyncClient and
|
|
285
|
-
cleans up the transport on exit.
|
|
286
|
-
|
|
287
|
-
Example::
|
|
288
|
-
|
|
289
|
-
async with soap_client('https://example.com/service?wsdl') as client:
|
|
290
|
-
result = await client.service.GetData(id=42)
|
|
291
|
-
"""
|
|
292
|
-
transport = AsyncTransport.from_config(config, session=session)
|
|
293
|
-
|
|
294
|
-
try:
|
|
295
|
-
client = build_soap_client(wsdl_url, transport, plugins) # WSDL loaded here (sync via load())
|
|
296
|
-
|
|
297
|
-
yield client
|
|
298
|
-
finally:
|
|
299
|
-
await transport.aclose()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/msgpack.py
RENAMED
|
File without changes
|
{python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons/serializers/msgspec.py
RENAMED
|
File without changes
|
|
File without changes
|
{python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python3_commons-0.18.4 → python3_commons-0.18.6}/src/python3_commons.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|