python3-commons 0.18.6__tar.gz → 0.18.8__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.
Files changed (74) hide show
  1. {python3_commons-0.18.6/src/python3_commons.egg-info → python3_commons-0.18.8}/PKG-INFO +1 -1
  2. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/soap_client.py +96 -105
  3. {python3_commons-0.18.6 → python3_commons-0.18.8/src/python3_commons.egg-info}/PKG-INFO +1 -1
  4. {python3_commons-0.18.6 → python3_commons-0.18.8}/.coveragerc +0 -0
  5. {python3_commons-0.18.6 → python3_commons-0.18.8}/.devcontainer/Dockerfile +0 -0
  6. {python3_commons-0.18.6 → python3_commons-0.18.8}/.devcontainer/devcontainer.json +0 -0
  7. {python3_commons-0.18.6 → python3_commons-0.18.8}/.devcontainer/docker-compose.yml +0 -0
  8. {python3_commons-0.18.6 → python3_commons-0.18.8}/.env_template +0 -0
  9. {python3_commons-0.18.6 → python3_commons-0.18.8}/.github/workflows/checks.yml +0 -0
  10. {python3_commons-0.18.6 → python3_commons-0.18.8}/.github/workflows/python-publish.yaml +0 -0
  11. {python3_commons-0.18.6 → python3_commons-0.18.8}/.github/workflows/release-on-tag-push.yml +0 -0
  12. {python3_commons-0.18.6 → python3_commons-0.18.8}/.gitignore +0 -0
  13. {python3_commons-0.18.6 → python3_commons-0.18.8}/.pre-commit-config.yaml +0 -0
  14. {python3_commons-0.18.6 → python3_commons-0.18.8}/.python-version +0 -0
  15. {python3_commons-0.18.6 → python3_commons-0.18.8}/AUTHORS.rst +0 -0
  16. {python3_commons-0.18.6 → python3_commons-0.18.8}/CHANGELOG.rst +0 -0
  17. {python3_commons-0.18.6 → python3_commons-0.18.8}/LICENSE +0 -0
  18. {python3_commons-0.18.6 → python3_commons-0.18.8}/README.md +0 -0
  19. {python3_commons-0.18.6 → python3_commons-0.18.8}/README.rst +0 -0
  20. {python3_commons-0.18.6 → python3_commons-0.18.8}/docs/Makefile +0 -0
  21. {python3_commons-0.18.6 → python3_commons-0.18.8}/docs/_static/.gitignore +0 -0
  22. {python3_commons-0.18.6 → python3_commons-0.18.8}/docs/authors.rst +0 -0
  23. {python3_commons-0.18.6 → python3_commons-0.18.8}/docs/changelog.rst +0 -0
  24. {python3_commons-0.18.6 → python3_commons-0.18.8}/docs/conf.py +0 -0
  25. {python3_commons-0.18.6 → python3_commons-0.18.8}/docs/index.rst +0 -0
  26. {python3_commons-0.18.6 → python3_commons-0.18.8}/docs/license.rst +0 -0
  27. {python3_commons-0.18.6 → python3_commons-0.18.8}/pyproject.toml +0 -0
  28. {python3_commons-0.18.6 → python3_commons-0.18.8}/setup.cfg +0 -0
  29. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/__init__.py +0 -0
  30. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/api_client.py +0 -0
  31. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/async_functools.py +0 -0
  32. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/audit.py +0 -0
  33. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/auth.py +0 -0
  34. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/cache.py +0 -0
  35. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/conf.py +0 -0
  36. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/db/__init__.py +0 -0
  37. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/db/helpers.py +0 -0
  38. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/db/models/__init__.py +0 -0
  39. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/db/models/auth.py +0 -0
  40. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/db/models/common.py +0 -0
  41. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/db/models/rbac.py +0 -0
  42. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/db/models/users.py +0 -0
  43. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/exceptions.py +0 -0
  44. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/fs.py +0 -0
  45. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/generators.py +0 -0
  46. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/helpers.py +0 -0
  47. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/log/__init__.py +0 -0
  48. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/log/filters.py +0 -0
  49. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/log/formatters.py +0 -0
  50. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/object_storage.py +0 -0
  51. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/permissions.py +0 -0
  52. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/serializers/__init__.py +0 -0
  53. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/serializers/common.py +0 -0
  54. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/serializers/json.py +0 -0
  55. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/serializers/msgpack.py +0 -0
  56. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons/serializers/msgspec.py +0 -0
  57. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons.egg-info/SOURCES.txt +0 -0
  58. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  59. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons.egg-info/requires.txt +0 -0
  60. {python3_commons-0.18.6 → python3_commons-0.18.8}/src/python3_commons.egg-info/top_level.txt +0 -0
  61. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/__init__.py +0 -0
  62. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/integration/__init__.py +0 -0
  63. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/integration/test_cache.py +0 -0
  64. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/integration/test_osc.py +0 -0
  65. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/__init__.py +0 -0
  66. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/conftest.py +0 -0
  67. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/log/__init__.py +0 -0
  68. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/log/test_formatters.py +0 -0
  69. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/test_async_functools.py +0 -0
  70. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/test_audit.py +0 -0
  71. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/test_helpers.py +0 -0
  72. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/test_msgpack.py +0 -0
  73. {python3_commons-0.18.6 → python3_commons-0.18.8}/tests/unit/test_msgspec.py +0 -0
  74. {python3_commons-0.18.6 → python3_commons-0.18.8}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.18.6
3
+ Version: 0.18.8
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
@@ -1,11 +1,6 @@
1
- """
2
- Async SOAP client built on aiohttp + zeep.
3
- """
4
-
5
1
  from __future__ import annotations
6
2
 
7
3
  import asyncio
8
- import concurrent.futures
9
4
  import logging
10
5
  import ssl
11
6
  from collections.abc import AsyncIterator, Sequence
@@ -13,10 +8,9 @@ from contextlib import asynccontextmanager
13
8
  from dataclasses import dataclass
14
9
  from typing import TYPE_CHECKING, Any, Self
15
10
 
16
- import certifi
17
-
18
11
  try:
19
12
  import aiohttp
13
+ import certifi
20
14
  from aiohttp import ClientSession, ClientTimeout, TCPConnector
21
15
  from requests import Response
22
16
  from requests.cookies import RequestsCookieJar
@@ -38,46 +32,38 @@ logger = logging.getLogger(__name__)
38
32
 
39
33
  def _make_ssl_context(*, verify: bool) -> ssl.SSLContext | bool:
40
34
  if not verify:
41
- return False # aiohttp accepts False to disable verification
35
+ return False
42
36
 
43
37
  return ssl.create_default_context(cafile=certifi.where())
44
38
 
45
39
 
46
40
  @dataclass(frozen=True, slots=True)
47
41
  class TransportConfig:
48
- """Immutable transport settings passed to AsyncTransport."""
49
-
50
42
  timeout: int = 300
51
- """Total timeout in seconds for WSDL fetches."""
52
-
53
43
  operation_timeout: int = 60
54
- """Total timeout in seconds for SOAP operation calls."""
55
-
56
44
  verify_ssl: bool = True
57
45
  proxy: str | None = None
58
46
 
59
47
 
60
- class AsyncTransport(Transport):
61
- """
62
- Async transport for zeep using aiohttp.
48
+ # -------------------------
49
+ # Transport
50
+ # -------------------------
63
51
 
64
- Usage::
65
-
66
- async with soap_client("https://example.com/service?wsdl") as client:
67
- result = await client.service.SomeOperation(...)
68
- """
69
52
 
53
+ class AsyncTransport(Transport):
70
54
  def __init__(
71
55
  self,
72
56
  *,
73
57
  session: ClientSession,
74
58
  config: TransportConfig,
75
- _owns_session: bool = False,
59
+ loop: asyncio.AbstractEventLoop,
60
+ owns_session: bool = False,
76
61
  ) -> None:
77
62
  super().__init__()
78
63
  self._session = session
79
64
  self._config = config
80
- self._owns_session = _owns_session
65
+ self._loop = loop
66
+ self._owns_session = owns_session
81
67
 
82
68
  @classmethod
83
69
  def from_config(
@@ -85,8 +71,9 @@ class AsyncTransport(Transport):
85
71
  config: TransportConfig | None = None,
86
72
  *,
87
73
  session: ClientSession | None = None,
88
- ) -> Self:
74
+ ) -> AsyncTransport:
89
75
  config = config or TransportConfig()
76
+ loop = asyncio.get_running_loop()
90
77
  owns_session = session is None
91
78
 
92
79
  if owns_session:
@@ -96,7 +83,12 @@ class AsyncTransport(Transport):
96
83
  headers={'User-Agent': f'Zeep/{get_version()} (www.python-zeep.org)'},
97
84
  )
98
85
 
99
- return cls(session=session, config=config, _owns_session=owns_session)
86
+ return cls(
87
+ session=session,
88
+ config=config,
89
+ loop=loop,
90
+ owns_session=owns_session,
91
+ )
100
92
 
101
93
  async def aclose(self) -> None:
102
94
  if self._owns_session:
@@ -110,7 +102,6 @@ class AsyncTransport(Transport):
110
102
 
111
103
  @staticmethod
112
104
  def _build_response(response: aiohttp.ClientResponse, body: bytes) -> Response:
113
- """Convert an aiohttp response into a requests.Response for zeep."""
114
105
  r = Response()
115
106
  r.status_code = response.status
116
107
  r._content = body # noqa: SLF001
@@ -127,90 +118,86 @@ class AsyncTransport(Transport):
127
118
 
128
119
  return r
129
120
 
121
+ async def _fetch(self, url: str) -> bytes:
122
+ async with self._session.get(
123
+ url,
124
+ proxy=self._config.proxy,
125
+ timeout=ClientTimeout(total=self._config.timeout),
126
+ ) as resp:
127
+ body = await resp.read()
128
+
129
+ if resp.status >= 400:
130
+ raise TransportError(
131
+ status_code=resp.status,
132
+ message=body.decode(errors='ignore'),
133
+ )
134
+
135
+ return body
136
+
130
137
  def load(self, url: str) -> bytes:
131
138
  """
132
- Sync entry-point zeep calls during WSDL document init.
139
+ Called synchronously by zeep during WSDL parsing.
133
140
 
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.
141
+ We safely hop into the main loop.
136
142
  """
137
143
  if not url:
138
144
  return b''
139
145
 
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)
146
+ future = asyncio.run_coroutine_threadsafe(
147
+ self._fetch(url),
148
+ self._loop,
149
+ )
150
+
151
+ return future.result()
152
+
153
+ def post_xml(
154
+ self,
155
+ address: str,
156
+ envelope: Any,
157
+ headers: dict[str, str],
158
+ ) -> Response:
159
+ future = asyncio.run_coroutine_threadsafe(
160
+ self.post(address, etree_to_string(envelope), headers),
161
+ self._loop,
162
+ )
163
+
164
+ return future.result()
165
+
166
+ async def post(
167
+ self,
168
+ address: str,
169
+ message: bytes,
170
+ headers: dict[str, str],
171
+ *,
172
+ timeout: int | None = None,
173
+ ) -> Response:
174
+ logger.debug('SOAP POST → %s', address)
175
+
176
+ async with self._session.post(
177
+ address,
178
+ data=message,
179
+ headers=headers,
180
+ proxy=self._config.proxy,
181
+ timeout=ClientTimeout(total=timeout) if timeout is not None else None,
182
+ ) as resp:
183
+ body = await resp.read()
184
+ logger.debug('SOAP ← %s (%d)', address, resp.status)
185
+ return self._build_response(resp, body)
186
+
187
+ async def get(
188
+ self,
189
+ address: str,
190
+ params: dict[str, str],
191
+ headers: dict[str, str],
192
+ ) -> Response:
193
+ async with self._session.get(
194
+ address,
195
+ params=params,
196
+ headers=headers,
197
+ proxy=self._config.proxy,
198
+ ) as resp:
199
+ body = await resp.read()
200
+ return self._build_response(resp, body)
214
201
 
215
202
 
216
203
  def build_soap_client(
@@ -223,7 +210,11 @@ def build_soap_client(
223
210
 
224
211
  raise ValueError(msg)
225
212
 
226
- return AsyncClient(wsdl_url, transport=transport, plugins=list(plugins or []))
213
+ return AsyncClient(
214
+ wsdl_url,
215
+ transport=transport,
216
+ plugins=list(plugins or []),
217
+ )
227
218
 
228
219
 
229
220
  @asynccontextmanager
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.18.6
3
+ Version: 0.18.8
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