python3-commons 0.18.0__py3-none-any.whl → 0.18.2__py3-none-any.whl

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/audit.py CHANGED
@@ -1,10 +1,25 @@
1
+ import asyncio
1
2
  import io
2
3
  import logging
4
+ from datetime import UTC, datetime
3
5
  from typing import TYPE_CHECKING
6
+ from uuid import uuid4
7
+
8
+ try:
9
+ from lxml import etree
10
+ from zeep.plugins import HistoryPlugin, Plugin
11
+ except ImportError as e:
12
+ msg = 'Install python3-commons[audit] to use this feature'
13
+
14
+ raise RuntimeError(msg) from e
4
15
 
5
16
  from python3_commons import object_storage
17
+ from python3_commons.conf import s3_settings
6
18
 
7
19
  if TYPE_CHECKING:
20
+ from zeep import AsyncClient
21
+ from zeep.wsdl.definitions import AbstractOperation
22
+
8
23
  from python3_commons.conf import S3Settings
9
24
 
10
25
  logger = logging.getLogger(__name__)
@@ -20,3 +35,45 @@ async def write_audit_data(settings: S3Settings, key: str, data: bytes) -> None:
20
35
  logger.debug('Stored object in storage: %s', key)
21
36
  else:
22
37
  logger.debug('S3 is not configured, not storing object in storage: %s', key)
38
+
39
+
40
+ class ZeepAuditPlugin(Plugin):
41
+ def __init__(self, audit_name: str = 'zeep') -> None:
42
+ super().__init__()
43
+ self.audit_name = audit_name
44
+
45
+ def store_audit_in_s3(self, envelope, operation: AbstractOperation, direction: str) -> None:
46
+ xml = etree.tostring(envelope, encoding='UTF-8', pretty_print=True)
47
+ now = datetime.now(tz=UTC)
48
+ date_path = now.strftime('%Y/%m/%d')
49
+ timestamp = now.strftime('%H%M%S')
50
+ path = f'{date_path}/{self.audit_name}/{operation.name}/{timestamp}_{str(uuid4())[-12:]}_{direction}.xml'
51
+ coro = write_audit_data(s3_settings, path, xml)
52
+
53
+ try:
54
+ loop = asyncio.get_running_loop()
55
+ except RuntimeError:
56
+ loop = None
57
+
58
+ if loop and loop.is_running():
59
+ loop.create_task(coro)
60
+ else:
61
+ asyncio.run(coro)
62
+
63
+ def ingress(self, envelope, http_headers, operation: AbstractOperation):
64
+ self.store_audit_in_s3(envelope, operation, 'ingress')
65
+
66
+ return envelope, http_headers
67
+
68
+ def egress(self, envelope, http_headers, operation: AbstractOperation, binding_options):
69
+ self.store_audit_in_s3(envelope, operation, 'egress')
70
+
71
+ return envelope, http_headers
72
+
73
+
74
+ def get_history_plugin(client: AsyncClient) -> HistoryPlugin | None:
75
+ """Return the first HistoryPlugin attached to *client*, or None."""
76
+ return next(
77
+ (p for p in client.plugins if isinstance(p, HistoryPlugin)),
78
+ None,
79
+ )
@@ -5,23 +5,20 @@ Async SOAP client built on aiohttp + zeep.
5
5
  from __future__ import annotations
6
6
 
7
7
  import asyncio
8
+ import concurrent.futures
8
9
  import logging
9
10
  from collections.abc import AsyncIterator, Sequence
10
11
  from contextlib import asynccontextmanager
11
12
  from dataclasses import dataclass
12
- from datetime import UTC, datetime
13
13
  from typing import TYPE_CHECKING, Any, Self
14
- from uuid import uuid4
15
14
 
16
15
  try:
17
16
  import aiohttp
18
17
  from aiohttp import ClientSession, ClientTimeout, TCPConnector
19
- from lxml import etree
20
18
  from requests import Response
21
19
  from requests.cookies import RequestsCookieJar
22
20
  from zeep import AsyncClient
23
21
  from zeep.exceptions import TransportError
24
- from zeep.plugins import HistoryPlugin, Plugin
25
22
  from zeep.transports import Transport
26
23
  from zeep.utils import get_version
27
24
  from zeep.wsdl.utils import etree_to_string
@@ -30,49 +27,12 @@ except ImportError as e:
30
27
 
31
28
  raise RuntimeError(msg) from e
32
29
 
33
- from python3_commons.audit import write_audit_data
34
- from python3_commons.conf import s3_settings
35
-
36
30
  if TYPE_CHECKING:
37
- from zeep.wsdl.definitions import AbstractOperation
31
+ from zeep.plugins import Plugin
38
32
 
39
33
  logger = logging.getLogger(__name__)
40
34
 
41
35
 
42
- class ZeepAuditPlugin(Plugin):
43
- def __init__(self, audit_name: str = 'zeep') -> None:
44
- super().__init__()
45
- self.audit_name = audit_name
46
-
47
- def store_audit_in_s3(self, envelope, operation: AbstractOperation, direction: str) -> None:
48
- xml = etree.tostring(envelope, encoding='UTF-8', pretty_print=True)
49
- now = datetime.now(tz=UTC)
50
- date_path = now.strftime('%Y/%m/%d')
51
- timestamp = now.strftime('%H%M%S')
52
- path = f'{date_path}/{self.audit_name}/{operation.name}/{timestamp}_{str(uuid4())[-12:]}_{direction}.xml'
53
- coro = write_audit_data(s3_settings, path, xml)
54
-
55
- try:
56
- loop = asyncio.get_running_loop()
57
- except RuntimeError:
58
- loop = None
59
-
60
- if loop and loop.is_running():
61
- loop.create_task(coro)
62
- else:
63
- asyncio.run(coro)
64
-
65
- def ingress(self, envelope, http_headers, operation: AbstractOperation):
66
- self.store_audit_in_s3(envelope, operation, 'ingress')
67
-
68
- return envelope, http_headers
69
-
70
- def egress(self, envelope, http_headers, operation: AbstractOperation, binding_options):
71
- self.store_audit_in_s3(envelope, operation, 'egress')
72
-
73
- return envelope, http_headers
74
-
75
-
76
36
  @dataclass(frozen=True, slots=True)
77
37
  class TransportConfig:
78
38
  """Immutable transport settings passed to AsyncTransport."""
@@ -207,6 +167,20 @@ class AsyncTransport(Transport):
207
167
 
208
168
  return await _fetch()
209
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
+ try:
176
+ loop = asyncio.get_event_loop()
177
+
178
+ return loop.run_until_complete(self._load_remote_data(url))
179
+ except RuntimeError:
180
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
181
+ future = pool.submit(asyncio.run, self._load_remote_data(url))
182
+ return future.result()
183
+
210
184
  async def post(
211
185
  self,
212
186
  address: str,
@@ -298,13 +272,9 @@ async def soap_client(
298
272
  """
299
273
  transport = AsyncTransport.from_config(config, session=session)
300
274
 
301
- async with transport:
302
- yield build_soap_client(wsdl_url, transport, plugins)
303
-
275
+ try:
276
+ client = build_soap_client(wsdl_url, transport, plugins) # WSDL loaded here (sync via load())
304
277
 
305
- def get_history_plugin(client: AsyncClient) -> HistoryPlugin | None:
306
- """Return the first HistoryPlugin attached to *client*, or None."""
307
- return next(
308
- (p for p in client.plugins if isinstance(p, HistoryPlugin)),
309
- None,
310
- )
278
+ yield client
279
+ finally:
280
+ await transport.aclose()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.18.0
3
+ Version: 0.18.2
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,7 +19,6 @@ 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: lxml~=6.1.0; extra == "api-client"
23
22
  Requires-Dist: python3_commons[object-storage]; extra == "api-client"
24
23
  Provides-Extra: audit
25
24
  Requires-Dist: lxml~=6.1.0; extra == "audit"
@@ -1,7 +1,7 @@
1
1
  python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
2
2
  python3_commons/api_client.py,sha256=WuP2SDikWvdrJ6yCLUVrtUBxv0UDfcFKxLZwj-It6R8,5418
3
3
  python3_commons/async_functools.py,sha256=A2HvwFzZHxOWTp4IQM5UiBY2yg1S_0U1CWra5BWK0gk,9101
4
- python3_commons/audit.py,sha256=FdQ6GhDJuBE9L4YEqscAhHAN74FfcBuaHI_GAWOzpgw,714
4
+ python3_commons/audit.py,sha256=uGoCwenDJ0Gdwbr_VNOZm5scT8luxW1weprJbbMoHo0,2608
5
5
  python3_commons/auth.py,sha256=oihQ1sNnK_bi9JhQf_NCu4JVWmWrwVfSHD_zrHwRfzA,5044
6
6
  python3_commons/cache.py,sha256=lowiXJqFgFy1Yg86wi9IhuoNqIUGP6nc5eNibmf0dfY,8018
7
7
  python3_commons/conf.py,sha256=qy5asXYVaquo_qPnA3L7Hcqk9a4wZukEVK3xmCuF-LU,2867
@@ -11,7 +11,7 @@ python3_commons/generators.py,sha256=P6sKdCFhHT79-DZgzPU-qmwZvHg3wU8a75UFIwrycOY
11
11
  python3_commons/helpers.py,sha256=2U0XyiBhylPrPIdPkZ16jrJRDZBE-yKv9GjRIY4C-EA,4541
12
12
  python3_commons/object_storage.py,sha256=JB39Wb6rluAHa9oprsmB8d9UlY3sehsjX-aFmY_A-us,7219
13
13
  python3_commons/permissions.py,sha256=gaMKSWg0MgPQTdP1voll4ItXcblXku9BlD0Lq3Xv64U,1724
14
- python3_commons/soap_client.py,sha256=yF0S-lIb3UJ4WF9HyeUKbqsrnK4XW5aO8gjGhqf0C-M,9400
14
+ python3_commons/soap_client.py,sha256=dZZWkg2RxFWr5yHfzcDK7SCy21FMfxctuDCoYiRBJII,8296
15
15
  python3_commons/db/__init__.py,sha256=i60JvWiUY8kz8nEaVjc5MA1IN_P5g3tK7jX8KgcUd0Y,3105
16
16
  python3_commons/db/helpers.py,sha256=xRpWs4aVkBge6HCLjb6OLSnWc1nlV6rKGyaVhZ_x12w,2001
17
17
  python3_commons/db/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -27,9 +27,9 @@ python3_commons/serializers/common.py,sha256=VkA7C6wODvHk0QBXVX_x2JieDstihx3U__U
27
27
  python3_commons/serializers/json.py,sha256=UPkC3ps13x2C_NxwVV-K7Ewp4VjkVHSSUkJVw5k7Wiw,712
28
28
  python3_commons/serializers/msgpack.py,sha256=zESFBX34GsZ8rDu6Zk5V6CLT6P0mPilU0r04Ka6TblI,1474
29
29
  python3_commons/serializers/msgspec.py,sha256=upy5CBmK66-8hYnK5bAM_sZvZY5CAqZmzCw9GIF346I,2988
30
- python3_commons-0.18.0.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
31
- python3_commons-0.18.0.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
32
- python3_commons-0.18.0.dist-info/METADATA,sha256=_zsYYWNFNss7noCUZjiL1EZXUDL4rBl-erUL17uz0BM,2210
33
- python3_commons-0.18.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
34
- python3_commons-0.18.0.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
35
- python3_commons-0.18.0.dist-info/RECORD,,
30
+ python3_commons-0.18.2.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
31
+ python3_commons-0.18.2.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
32
+ python3_commons-0.18.2.dist-info/METADATA,sha256=kIEA0rAgEB2EWJF1BCMXFPhRmxKK4oVasgPbNyZQAoo,2160
33
+ python3_commons-0.18.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
34
+ python3_commons-0.18.2.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
35
+ python3_commons-0.18.2.dist-info/RECORD,,