python3-commons 0.17.8__tar.gz → 0.18.0__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.17.8/src/python3_commons.egg-info → python3_commons-0.18.0}/PKG-INFO +8 -4
- {python3_commons-0.17.8 → python3_commons-0.18.0}/pyproject.toml +8 -3
- python3_commons-0.18.0/src/python3_commons/audit.py +22 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/auth.py +15 -15
- python3_commons-0.18.0/src/python3_commons/soap_client.py +310 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0/src/python3_commons.egg-info}/PKG-INFO +8 -4
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons.egg-info/SOURCES.txt +1 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons.egg-info/requires.txt +8 -3
- {python3_commons-0.17.8 → python3_commons-0.18.0}/uv.lock +71 -9
- python3_commons-0.17.8/src/python3_commons/audit.py +0 -67
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.coveragerc +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.devcontainer/Dockerfile +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.devcontainer/devcontainer.json +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.devcontainer/docker-compose.yml +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.env_template +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.github/workflows/checks.yml +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.github/workflows/python-publish.yaml +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.github/workflows/release-on-tag-push.yml +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.gitignore +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.pre-commit-config.yaml +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/.python-version +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/AUTHORS.rst +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/CHANGELOG.rst +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/LICENSE +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/README.md +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/README.rst +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/docs/Makefile +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/docs/_static/.gitignore +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/docs/authors.rst +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/docs/changelog.rst +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/docs/conf.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/docs/index.rst +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/docs/license.rst +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/setup.cfg +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/api_client.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/async_functools.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/cache.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/conf.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/db/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/db/helpers.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/db/models/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/db/models/auth.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/db/models/common.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/db/models/rbac.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/db/models/users.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/exceptions.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/fs.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/generators.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/helpers.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/log/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/log/filters.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/log/formatters.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/object_storage.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/permissions.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/common.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/json.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/msgpack.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/msgspec.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons.egg-info/dependency_links.txt +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons.egg-info/top_level.txt +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/integration/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/integration/test_cache.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/integration/test_osc.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/conftest.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/log/__init__.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/log/test_formatters.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/test_async_functools.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/test_audit.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/test_helpers.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/tests/unit/test_msgpack.py +0 -0
- {python3_commons-0.17.8 → python3_commons-0.18.0}/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.
|
|
3
|
+
Version: 0.18.0
|
|
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
|
|
@@ -16,15 +16,14 @@ Requires-Dist: msgpack~=1.1.2
|
|
|
16
16
|
Requires-Dist: msgspec==0.21.1
|
|
17
17
|
Requires-Dist: pydantic-settings~=2.14.0
|
|
18
18
|
Provides-Extra: all
|
|
19
|
-
Requires-Dist: python3_commons[api-client,audit,authn,authz,cache,database,object-storage]; extra == "all"
|
|
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
22
|
Requires-Dist: lxml~=6.1.0; extra == "api-client"
|
|
23
|
-
Requires-Dist: zeep~=4.3.2; extra == "api-client"
|
|
24
23
|
Requires-Dist: python3_commons[object-storage]; extra == "api-client"
|
|
25
24
|
Provides-Extra: audit
|
|
26
25
|
Requires-Dist: lxml~=6.1.0; extra == "audit"
|
|
27
|
-
Requires-Dist: zeep~=4.3.2; extra == "audit"
|
|
26
|
+
Requires-Dist: zeep[async]~=4.3.2; extra == "audit"
|
|
28
27
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
29
28
|
Provides-Extra: authn
|
|
30
29
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
|
|
@@ -40,6 +39,11 @@ Requires-Dist: SQLAlchemy[asyncio]~=2.0.49; extra == "database"
|
|
|
40
39
|
Provides-Extra: object-storage
|
|
41
40
|
Requires-Dist: aiobotocore~=3.5.0; extra == "object-storage"
|
|
42
41
|
Requires-Dist: object-storage-client==0.0.23; extra == "object-storage"
|
|
42
|
+
Provides-Extra: soap-client
|
|
43
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "soap-client"
|
|
44
|
+
Requires-Dist: lxml~=6.1.0; extra == "soap-client"
|
|
45
|
+
Requires-Dist: requests~=2.33.1; extra == "soap-client"
|
|
46
|
+
Requires-Dist: zeep[async]~=4.3.2; extra == "soap-client"
|
|
43
47
|
Dynamic: license-file
|
|
44
48
|
|
|
45
49
|
Re-usable Python3 code
|
|
@@ -25,17 +25,16 @@ dependencies = [
|
|
|
25
25
|
|
|
26
26
|
[project.optional-dependencies]
|
|
27
27
|
all = [
|
|
28
|
-
"python3_commons[api-client,audit,authn,authz,cache,database,object-storage]"
|
|
28
|
+
"python3_commons[api-client,audit,authn,authz,cache,database,object-storage,soap-client]"
|
|
29
29
|
]
|
|
30
30
|
api-client = [
|
|
31
31
|
"aiohttp[speedups]>=3.13.5,<3.15.0",
|
|
32
32
|
"lxml~=6.1.0",
|
|
33
|
-
"zeep~=4.3.2",
|
|
34
33
|
"python3_commons[object-storage]"
|
|
35
34
|
]
|
|
36
35
|
audit = [
|
|
37
36
|
"lxml~=6.1.0",
|
|
38
|
-
"zeep~=4.3.2",
|
|
37
|
+
"zeep[async]~=4.3.2",
|
|
39
38
|
"python3_commons[object-storage]"
|
|
40
39
|
]
|
|
41
40
|
authn = [
|
|
@@ -57,6 +56,12 @@ object-storage = [
|
|
|
57
56
|
"aiobotocore~=3.5.0",
|
|
58
57
|
"object-storage-client==0.0.23"
|
|
59
58
|
]
|
|
59
|
+
soap-client = [
|
|
60
|
+
"aiohttp[speedups]>=3.13.5,<3.15.0",
|
|
61
|
+
"lxml~=6.1.0",
|
|
62
|
+
"requests~=2.33.1",
|
|
63
|
+
"zeep[async]~=4.3.2",
|
|
64
|
+
]
|
|
60
65
|
|
|
61
66
|
|
|
62
67
|
[dependency-groups]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from python3_commons import object_storage
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from python3_commons.conf import S3Settings
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def write_audit_data(settings: S3Settings, key: str, data: bytes) -> None:
|
|
14
|
+
if settings.aws_secret_access_key:
|
|
15
|
+
try:
|
|
16
|
+
await object_storage.put_object(settings.s3_bucket, f'audit/{key}', io.BytesIO(data), len(data))
|
|
17
|
+
except Exception:
|
|
18
|
+
logger.exception('Failed storing object in storage.')
|
|
19
|
+
else:
|
|
20
|
+
logger.debug('Stored object in storage: %s', key)
|
|
21
|
+
else:
|
|
22
|
+
logger.debug('S3 is not configured, not storing object in storage: %s', key)
|
|
@@ -101,13 +101,13 @@ class OIDCAuthError(OIDCError):
|
|
|
101
101
|
# TODO: use api_client
|
|
102
102
|
class OIDCClient:
|
|
103
103
|
def __init__(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
self,
|
|
105
|
+
authority_url: str,
|
|
106
|
+
client_id: str,
|
|
107
|
+
client_secret: str | None = None,
|
|
108
|
+
*,
|
|
109
|
+
timeout: float = 10.0,
|
|
110
|
+
session: aiohttp.ClientSession | None = None,
|
|
111
111
|
) -> None:
|
|
112
112
|
self._token_url = f'{authority_url}/protocol/openid-connect/token' # TODO: get it from openid-configuration
|
|
113
113
|
self._client_id = client_id
|
|
@@ -126,11 +126,11 @@ class OIDCClient:
|
|
|
126
126
|
await self._session.close()
|
|
127
127
|
|
|
128
128
|
async def fetch_token(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
self,
|
|
130
|
+
*,
|
|
131
|
+
username: str,
|
|
132
|
+
password: str,
|
|
133
|
+
scope: str = 'openid profile email',
|
|
134
134
|
) -> OIDCTokenResponse:
|
|
135
135
|
if self._session is None:
|
|
136
136
|
msg = 'ClientSession not initialized'
|
|
@@ -150,9 +150,9 @@ class OIDCClient:
|
|
|
150
150
|
|
|
151
151
|
try:
|
|
152
152
|
async with self._session.post(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
self._token_url,
|
|
154
|
+
data=data,
|
|
155
|
+
headers={'Content-Type': 'application/x-www-form-urlencoded'},
|
|
156
156
|
) as resp:
|
|
157
157
|
payload = await resp.read()
|
|
158
158
|
decoder = msgspec.json.Decoder(type=OIDCTokenResponse)
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async SOAP client built on aiohttp + zeep.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from collections.abc import AsyncIterator, Sequence
|
|
10
|
+
from contextlib import asynccontextmanager
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
14
|
+
from uuid import uuid4
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import aiohttp
|
|
18
|
+
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
|
19
|
+
from lxml import etree
|
|
20
|
+
from requests import Response
|
|
21
|
+
from requests.cookies import RequestsCookieJar
|
|
22
|
+
from zeep import AsyncClient
|
|
23
|
+
from zeep.exceptions import TransportError
|
|
24
|
+
from zeep.plugins import HistoryPlugin, Plugin
|
|
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
|
+
from python3_commons.audit import write_audit_data
|
|
34
|
+
from python3_commons.conf import s3_settings
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from zeep.wsdl.definitions import AbstractOperation
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
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
|
+
@dataclass(frozen=True, slots=True)
|
|
77
|
+
class TransportConfig:
|
|
78
|
+
"""Immutable transport settings passed to AsyncTransport."""
|
|
79
|
+
|
|
80
|
+
timeout: int = 300
|
|
81
|
+
"""Total timeout in seconds for WSDL fetches."""
|
|
82
|
+
|
|
83
|
+
operation_timeout: int = 60
|
|
84
|
+
"""Total timeout in seconds for SOAP operation calls."""
|
|
85
|
+
|
|
86
|
+
verify_ssl: bool = True
|
|
87
|
+
proxy: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AsyncTransport(Transport):
|
|
91
|
+
"""
|
|
92
|
+
Async transport for zeep using aiohttp.
|
|
93
|
+
|
|
94
|
+
Usage::
|
|
95
|
+
|
|
96
|
+
async with AsyncTransport.from_config(config) as transport:
|
|
97
|
+
client = AsyncClient(wsdl_url, transport=transport)
|
|
98
|
+
result = await client.service.SomeOperation(...)
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
session: ClientSession,
|
|
105
|
+
wsdl_session: ClientSession,
|
|
106
|
+
config: TransportConfig,
|
|
107
|
+
_owns_session: bool = False,
|
|
108
|
+
_owns_wsdl_session: bool = False,
|
|
109
|
+
) -> None:
|
|
110
|
+
super().__init__()
|
|
111
|
+
self._session = session
|
|
112
|
+
self._wsdl_session = wsdl_session
|
|
113
|
+
self._config = config
|
|
114
|
+
self._owns_session = _owns_session
|
|
115
|
+
self._owns_wsdl_session = _owns_wsdl_session
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def from_config(
|
|
119
|
+
cls,
|
|
120
|
+
config: TransportConfig | None = None,
|
|
121
|
+
*,
|
|
122
|
+
session: ClientSession | None = None,
|
|
123
|
+
wsdl_session: ClientSession | None = None,
|
|
124
|
+
) -> AsyncTransport:
|
|
125
|
+
"""
|
|
126
|
+
Create a transport, optionally sharing an existing ClientSession.
|
|
127
|
+
|
|
128
|
+
If *session* / *wsdl_session* are omitted the transport owns (and
|
|
129
|
+
will close) the sessions it creates.
|
|
130
|
+
"""
|
|
131
|
+
config = config or TransportConfig()
|
|
132
|
+
connector = TCPConnector(ssl=config.verify_ssl)
|
|
133
|
+
user_agent = f'Zeep/{get_version()} (www.python-zeep.org)'
|
|
134
|
+
|
|
135
|
+
owns_session = session is None
|
|
136
|
+
owns_wsdl_session = wsdl_session is None
|
|
137
|
+
|
|
138
|
+
if owns_session:
|
|
139
|
+
session = ClientSession(
|
|
140
|
+
connector=connector,
|
|
141
|
+
timeout=ClientTimeout(total=config.operation_timeout),
|
|
142
|
+
headers={'User-Agent': user_agent},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if owns_wsdl_session:
|
|
146
|
+
wsdl_session = ClientSession(
|
|
147
|
+
connector=connector,
|
|
148
|
+
timeout=ClientTimeout(total=config.timeout),
|
|
149
|
+
headers={'User-Agent': user_agent},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return cls(
|
|
153
|
+
session=session,
|
|
154
|
+
wsdl_session=wsdl_session,
|
|
155
|
+
config=config,
|
|
156
|
+
_owns_session=owns_session,
|
|
157
|
+
_owns_wsdl_session=owns_wsdl_session,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def aclose(self) -> None:
|
|
161
|
+
if self._owns_session:
|
|
162
|
+
await self._session.close()
|
|
163
|
+
if self._owns_wsdl_session:
|
|
164
|
+
await self._wsdl_session.close()
|
|
165
|
+
|
|
166
|
+
async def __aenter__(self) -> Self:
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
async def __aexit__(self, *_: object) -> None:
|
|
170
|
+
await self.aclose()
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _build_response(response: aiohttp.ClientResponse, body: bytes) -> Response:
|
|
174
|
+
"""Convert an aiohttp response into a requests.Response for zeep."""
|
|
175
|
+
r = Response()
|
|
176
|
+
r.status_code = response.status
|
|
177
|
+
r._content = body # noqa: SLF001 (zeep reads this attribute directly)
|
|
178
|
+
r.headers = dict(response.headers)
|
|
179
|
+
r.encoding = response.charset
|
|
180
|
+
r.url = str(response.url)
|
|
181
|
+
|
|
182
|
+
# Bridge aiohttp SimpleCookie → RequestsCookieJar so zeep / requests
|
|
183
|
+
# cookie handling works correctly.
|
|
184
|
+
jar = RequestsCookieJar()
|
|
185
|
+
|
|
186
|
+
for name, morsel in response.cookies.items():
|
|
187
|
+
jar.set(name, morsel.value)
|
|
188
|
+
|
|
189
|
+
r.cookies = jar
|
|
190
|
+
|
|
191
|
+
return r
|
|
192
|
+
|
|
193
|
+
async def _load_remote_data(self, url: str) -> bytes:
|
|
194
|
+
"""Fetch WSDL / XSD documents (called by zeep during init)."""
|
|
195
|
+
|
|
196
|
+
async def _fetch() -> bytes:
|
|
197
|
+
async with self._wsdl_session.get(url, proxy=self._config.proxy) as resp:
|
|
198
|
+
content = await resp.read()
|
|
199
|
+
|
|
200
|
+
if resp.status >= 400:
|
|
201
|
+
raise TransportError(
|
|
202
|
+
status_code=resp.status,
|
|
203
|
+
message=content.decode(errors='ignore'),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return content
|
|
207
|
+
|
|
208
|
+
return await _fetch()
|
|
209
|
+
|
|
210
|
+
async def post(
|
|
211
|
+
self,
|
|
212
|
+
address: str,
|
|
213
|
+
message: bytes,
|
|
214
|
+
headers: dict[str, str],
|
|
215
|
+
*,
|
|
216
|
+
timeout: int | None = None,
|
|
217
|
+
) -> Response:
|
|
218
|
+
logger.debug('SOAP POST → %s\n%s', address, message)
|
|
219
|
+
|
|
220
|
+
request_timeout = ClientTimeout(total=timeout) if timeout is not None else None
|
|
221
|
+
|
|
222
|
+
async def _post() -> Response:
|
|
223
|
+
async with self._session.post(
|
|
224
|
+
address,
|
|
225
|
+
data=message,
|
|
226
|
+
headers=headers,
|
|
227
|
+
proxy=self._config.proxy,
|
|
228
|
+
timeout=request_timeout,
|
|
229
|
+
) as resp:
|
|
230
|
+
body = await resp.read()
|
|
231
|
+
logger.debug('SOAP ← %s (HTTP %d)\n%s', address, resp.status, body)
|
|
232
|
+
|
|
233
|
+
return self._build_response(resp, body)
|
|
234
|
+
|
|
235
|
+
return await _post()
|
|
236
|
+
|
|
237
|
+
async def post_xml(
|
|
238
|
+
self,
|
|
239
|
+
address: str,
|
|
240
|
+
envelope: Any,
|
|
241
|
+
headers: dict[str, str],
|
|
242
|
+
) -> Response:
|
|
243
|
+
message = etree_to_string(envelope)
|
|
244
|
+
|
|
245
|
+
return await self.post(address, message, headers)
|
|
246
|
+
|
|
247
|
+
async def get(
|
|
248
|
+
self,
|
|
249
|
+
address: str,
|
|
250
|
+
params: dict[str, str],
|
|
251
|
+
headers: dict[str, str],
|
|
252
|
+
) -> Response:
|
|
253
|
+
async with self._session.get(
|
|
254
|
+
address,
|
|
255
|
+
params=params,
|
|
256
|
+
headers=headers,
|
|
257
|
+
proxy=self._config.proxy,
|
|
258
|
+
) as resp:
|
|
259
|
+
body = await resp.read()
|
|
260
|
+
|
|
261
|
+
return self._build_response(resp, body)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def build_soap_client(
|
|
265
|
+
wsdl_url: str,
|
|
266
|
+
transport: AsyncTransport,
|
|
267
|
+
plugins: Sequence[Plugin] | None = None,
|
|
268
|
+
) -> AsyncClient:
|
|
269
|
+
"""
|
|
270
|
+
Construct a zeep AsyncClient with the supplied transport and plugins.
|
|
271
|
+
|
|
272
|
+
Raises ValueError if *wsdl_url* is empty or None.
|
|
273
|
+
"""
|
|
274
|
+
if not wsdl_url:
|
|
275
|
+
msg = 'wsdl_url must be a non-empty string.'
|
|
276
|
+
|
|
277
|
+
raise ValueError(msg)
|
|
278
|
+
|
|
279
|
+
return AsyncClient(wsdl_url, transport=transport, plugins=list(plugins or []))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@asynccontextmanager
|
|
283
|
+
async def soap_client(
|
|
284
|
+
wsdl_url: str,
|
|
285
|
+
*,
|
|
286
|
+
config: TransportConfig | None = None,
|
|
287
|
+
session: ClientSession | None = None,
|
|
288
|
+
plugins: Sequence[Plugin] | None = None,
|
|
289
|
+
) -> AsyncIterator[AsyncClient]:
|
|
290
|
+
"""
|
|
291
|
+
Async context manager that yields a ready-to-use zeep AsyncClient and
|
|
292
|
+
cleans up the transport on exit.
|
|
293
|
+
|
|
294
|
+
Example::
|
|
295
|
+
|
|
296
|
+
async with soap_client('https://example.com/service?wsdl') as client:
|
|
297
|
+
result = await client.service.GetData(id=42)
|
|
298
|
+
"""
|
|
299
|
+
transport = AsyncTransport.from_config(config, session=session)
|
|
300
|
+
|
|
301
|
+
async with transport:
|
|
302
|
+
yield build_soap_client(wsdl_url, transport, plugins)
|
|
303
|
+
|
|
304
|
+
|
|
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
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
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
|
|
@@ -16,15 +16,14 @@ Requires-Dist: msgpack~=1.1.2
|
|
|
16
16
|
Requires-Dist: msgspec==0.21.1
|
|
17
17
|
Requires-Dist: pydantic-settings~=2.14.0
|
|
18
18
|
Provides-Extra: all
|
|
19
|
-
Requires-Dist: python3_commons[api-client,audit,authn,authz,cache,database,object-storage]; extra == "all"
|
|
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
22
|
Requires-Dist: lxml~=6.1.0; extra == "api-client"
|
|
23
|
-
Requires-Dist: zeep~=4.3.2; extra == "api-client"
|
|
24
23
|
Requires-Dist: python3_commons[object-storage]; extra == "api-client"
|
|
25
24
|
Provides-Extra: audit
|
|
26
25
|
Requires-Dist: lxml~=6.1.0; extra == "audit"
|
|
27
|
-
Requires-Dist: zeep~=4.3.2; extra == "audit"
|
|
26
|
+
Requires-Dist: zeep[async]~=4.3.2; extra == "audit"
|
|
28
27
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
29
28
|
Provides-Extra: authn
|
|
30
29
|
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
|
|
@@ -40,6 +39,11 @@ Requires-Dist: SQLAlchemy[asyncio]~=2.0.49; extra == "database"
|
|
|
40
39
|
Provides-Extra: object-storage
|
|
41
40
|
Requires-Dist: aiobotocore~=3.5.0; extra == "object-storage"
|
|
42
41
|
Requires-Dist: object-storage-client==0.0.23; extra == "object-storage"
|
|
42
|
+
Provides-Extra: soap-client
|
|
43
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "soap-client"
|
|
44
|
+
Requires-Dist: lxml~=6.1.0; extra == "soap-client"
|
|
45
|
+
Requires-Dist: requests~=2.33.1; extra == "soap-client"
|
|
46
|
+
Requires-Dist: zeep[async]~=4.3.2; extra == "soap-client"
|
|
43
47
|
Dynamic: license-file
|
|
44
48
|
|
|
45
49
|
Re-usable Python3 code
|
|
@@ -36,6 +36,7 @@ src/python3_commons/generators.py
|
|
|
36
36
|
src/python3_commons/helpers.py
|
|
37
37
|
src/python3_commons/object_storage.py
|
|
38
38
|
src/python3_commons/permissions.py
|
|
39
|
+
src/python3_commons/soap_client.py
|
|
39
40
|
src/python3_commons.egg-info/PKG-INFO
|
|
40
41
|
src/python3_commons.egg-info/SOURCES.txt
|
|
41
42
|
src/python3_commons.egg-info/dependency_links.txt
|
|
@@ -3,17 +3,16 @@ msgspec==0.21.1
|
|
|
3
3
|
pydantic-settings~=2.14.0
|
|
4
4
|
|
|
5
5
|
[all]
|
|
6
|
-
python3_commons[api-client,audit,authn,authz,cache,database,object-storage]
|
|
6
|
+
python3_commons[api-client,audit,authn,authz,cache,database,object-storage,soap-client]
|
|
7
7
|
|
|
8
8
|
[api-client]
|
|
9
9
|
aiohttp[speedups]<3.15.0,>=3.13.5
|
|
10
10
|
lxml~=6.1.0
|
|
11
|
-
zeep~=4.3.2
|
|
12
11
|
python3_commons[object-storage]
|
|
13
12
|
|
|
14
13
|
[audit]
|
|
15
14
|
lxml~=6.1.0
|
|
16
|
-
zeep~=4.3.2
|
|
15
|
+
zeep[async]~=4.3.2
|
|
17
16
|
python3_commons[object-storage]
|
|
18
17
|
|
|
19
18
|
[authn]
|
|
@@ -34,3 +33,9 @@ SQLAlchemy[asyncio]~=2.0.49
|
|
|
34
33
|
[object-storage]
|
|
35
34
|
aiobotocore~=3.5.0
|
|
36
35
|
object-storage-client==0.0.23
|
|
36
|
+
|
|
37
|
+
[soap-client]
|
|
38
|
+
aiohttp[speedups]<3.15.0,>=3.13.5
|
|
39
|
+
lxml~=6.1.0
|
|
40
|
+
requests~=2.33.1
|
|
41
|
+
zeep[async]~=4.3.2
|
|
@@ -129,6 +129,18 @@ wheels = [
|
|
|
129
129
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
|
130
130
|
]
|
|
131
131
|
|
|
132
|
+
[[package]]
|
|
133
|
+
name = "anyio"
|
|
134
|
+
version = "4.13.0"
|
|
135
|
+
source = { registry = "https://pypi.org/simple" }
|
|
136
|
+
dependencies = [
|
|
137
|
+
{ name = "idna" },
|
|
138
|
+
]
|
|
139
|
+
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
|
140
|
+
wheels = [
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
|
142
|
+
]
|
|
143
|
+
|
|
132
144
|
[[package]]
|
|
133
145
|
name = "asyncpg"
|
|
134
146
|
version = "0.31.0"
|
|
@@ -467,6 +479,43 @@ wheels = [
|
|
|
467
479
|
{ url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" },
|
|
468
480
|
]
|
|
469
481
|
|
|
482
|
+
[[package]]
|
|
483
|
+
name = "h11"
|
|
484
|
+
version = "0.16.0"
|
|
485
|
+
source = { registry = "https://pypi.org/simple" }
|
|
486
|
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
|
487
|
+
wheels = [
|
|
488
|
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
[[package]]
|
|
492
|
+
name = "httpcore"
|
|
493
|
+
version = "1.0.9"
|
|
494
|
+
source = { registry = "https://pypi.org/simple" }
|
|
495
|
+
dependencies = [
|
|
496
|
+
{ name = "certifi" },
|
|
497
|
+
{ name = "h11" },
|
|
498
|
+
]
|
|
499
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
|
500
|
+
wheels = [
|
|
501
|
+
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
|
502
|
+
]
|
|
503
|
+
|
|
504
|
+
[[package]]
|
|
505
|
+
name = "httpx"
|
|
506
|
+
version = "0.28.1"
|
|
507
|
+
source = { registry = "https://pypi.org/simple" }
|
|
508
|
+
dependencies = [
|
|
509
|
+
{ name = "anyio" },
|
|
510
|
+
{ name = "certifi" },
|
|
511
|
+
{ name = "httpcore" },
|
|
512
|
+
{ name = "idna" },
|
|
513
|
+
]
|
|
514
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
|
515
|
+
wheels = [
|
|
516
|
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
|
517
|
+
]
|
|
518
|
+
|
|
470
519
|
[[package]]
|
|
471
520
|
name = "identify"
|
|
472
521
|
version = "2.6.19"
|
|
@@ -1004,29 +1053,28 @@ all = [
|
|
|
1004
1053
|
{ name = "asyncpg" },
|
|
1005
1054
|
{ name = "lxml" },
|
|
1006
1055
|
{ name = "object-storage-client" },
|
|
1056
|
+
{ name = "requests" },
|
|
1007
1057
|
{ name = "sqlalchemy", extra = ["asyncio"] },
|
|
1008
1058
|
{ name = "valkey", extra = ["libvalkey"] },
|
|
1009
|
-
{ name = "zeep" },
|
|
1059
|
+
{ name = "zeep", extra = ["async"] },
|
|
1010
1060
|
]
|
|
1011
1061
|
api-client = [
|
|
1012
1062
|
{ name = "aiobotocore" },
|
|
1013
1063
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1014
1064
|
{ name = "lxml" },
|
|
1015
1065
|
{ name = "object-storage-client" },
|
|
1016
|
-
{ name = "zeep" },
|
|
1017
1066
|
]
|
|
1018
1067
|
audit = [
|
|
1019
1068
|
{ name = "aiobotocore" },
|
|
1020
1069
|
{ name = "lxml" },
|
|
1021
1070
|
{ name = "object-storage-client" },
|
|
1022
|
-
{ name = "zeep" },
|
|
1071
|
+
{ name = "zeep", extra = ["async"] },
|
|
1023
1072
|
]
|
|
1024
1073
|
authn = [
|
|
1025
1074
|
{ name = "aiobotocore" },
|
|
1026
1075
|
{ name = "aiohttp", extra = ["speedups"] },
|
|
1027
1076
|
{ name = "lxml" },
|
|
1028
1077
|
{ name = "object-storage-client" },
|
|
1029
|
-
{ name = "zeep" },
|
|
1030
1078
|
]
|
|
1031
1079
|
authz = [
|
|
1032
1080
|
{ name = "aiobotocore" },
|
|
@@ -1035,7 +1083,6 @@ authz = [
|
|
|
1035
1083
|
{ name = "lxml" },
|
|
1036
1084
|
{ name = "object-storage-client" },
|
|
1037
1085
|
{ name = "sqlalchemy", extra = ["asyncio"] },
|
|
1038
|
-
{ name = "zeep" },
|
|
1039
1086
|
]
|
|
1040
1087
|
cache = [
|
|
1041
1088
|
{ name = "valkey", extra = ["libvalkey"] },
|
|
@@ -1048,6 +1095,12 @@ object-storage = [
|
|
|
1048
1095
|
{ name = "aiobotocore" },
|
|
1049
1096
|
{ name = "object-storage-client" },
|
|
1050
1097
|
]
|
|
1098
|
+
soap-client = [
|
|
1099
|
+
{ name = "aiohttp", extra = ["speedups"] },
|
|
1100
|
+
{ name = "lxml" },
|
|
1101
|
+
{ name = "requests" },
|
|
1102
|
+
{ name = "zeep", extra = ["async"] },
|
|
1103
|
+
]
|
|
1051
1104
|
|
|
1052
1105
|
[package.dev-dependencies]
|
|
1053
1106
|
dev = [
|
|
@@ -1071,25 +1124,28 @@ requires-dist = [
|
|
|
1071
1124
|
{ name = "aiobotocore", marker = "extra == 'object-storage'", specifier = "~=3.5.0" },
|
|
1072
1125
|
{ name = "aiohttp", extras = ["speedups"], marker = "extra == 'api-client'", specifier = ">=3.13.5,<3.15.0" },
|
|
1073
1126
|
{ name = "aiohttp", extras = ["speedups"], marker = "extra == 'authn'", specifier = ">=3.13.5,<3.15.0" },
|
|
1127
|
+
{ name = "aiohttp", extras = ["speedups"], marker = "extra == 'soap-client'", specifier = ">=3.13.5,<3.15.0" },
|
|
1074
1128
|
{ name = "asyncpg", marker = "extra == 'database'", specifier = "~=0.31.0" },
|
|
1075
1129
|
{ name = "lxml", marker = "extra == 'api-client'", specifier = "~=6.1.0" },
|
|
1076
1130
|
{ name = "lxml", marker = "extra == 'audit'", specifier = "~=6.1.0" },
|
|
1131
|
+
{ name = "lxml", marker = "extra == 'soap-client'", specifier = "~=6.1.0" },
|
|
1077
1132
|
{ name = "msgpack", specifier = "~=1.1.2" },
|
|
1078
1133
|
{ name = "msgspec", specifier = "==0.21.1" },
|
|
1079
1134
|
{ name = "object-storage-client", marker = "extra == 'object-storage'", specifier = "==0.0.23" },
|
|
1080
1135
|
{ name = "pydantic-settings", specifier = "~=2.14.0" },
|
|
1081
1136
|
{ name = "python3-commons", extras = ["api-client"], marker = "extra == 'authn'" },
|
|
1082
1137
|
{ name = "python3-commons", extras = ["api-client"], marker = "extra == 'authz'" },
|
|
1083
|
-
{ name = "python3-commons", extras = ["api-client", "audit", "authn", "authz", "cache", "database", "object-storage"], marker = "extra == 'all'" },
|
|
1138
|
+
{ name = "python3-commons", extras = ["api-client", "audit", "authn", "authz", "cache", "database", "object-storage", "soap-client"], marker = "extra == 'all'" },
|
|
1084
1139
|
{ name = "python3-commons", extras = ["database"], marker = "extra == 'authz'" },
|
|
1085
1140
|
{ name = "python3-commons", extras = ["object-storage"], marker = "extra == 'api-client'" },
|
|
1086
1141
|
{ name = "python3-commons", extras = ["object-storage"], marker = "extra == 'audit'" },
|
|
1142
|
+
{ name = "requests", marker = "extra == 'soap-client'", specifier = "~=2.33.1" },
|
|
1087
1143
|
{ name = "sqlalchemy", extras = ["asyncio"], marker = "extra == 'database'", specifier = "~=2.0.49" },
|
|
1088
1144
|
{ name = "valkey", extras = ["libvalkey"], marker = "extra == 'cache'", specifier = "~=6.1.1" },
|
|
1089
|
-
{ name = "zeep", marker = "extra == '
|
|
1090
|
-
{ name = "zeep", marker = "extra == '
|
|
1145
|
+
{ name = "zeep", extras = ["async"], marker = "extra == 'audit'", specifier = "~=4.3.2" },
|
|
1146
|
+
{ name = "zeep", extras = ["async"], marker = "extra == 'soap-client'", specifier = "~=4.3.2" },
|
|
1091
1147
|
]
|
|
1092
|
-
provides-extras = ["all", "api-client", "audit", "authn", "authz", "cache", "database", "object-storage"]
|
|
1148
|
+
provides-extras = ["all", "api-client", "audit", "authn", "authz", "cache", "database", "object-storage", "soap-client"]
|
|
1093
1149
|
|
|
1094
1150
|
[package.metadata.requires-dev]
|
|
1095
1151
|
dev = [
|
|
@@ -1462,3 +1518,9 @@ sdist = { url = "https://files.pythonhosted.org/packages/e8/06/4f1d3ff61e9301635
|
|
|
1462
1518
|
wheels = [
|
|
1463
1519
|
{ url = "https://files.pythonhosted.org/packages/cd/78/f43f3feb70d67cbe260ec5b682ecc3c1850c8f437f1df707495126e51817/zeep-4.3.2-py3-none-any.whl", hash = "sha256:ed08c3179709172bfaaa9b76a6a545f8a57043ec6218e64e9deb81ff1e0ff79b", size = 101853, upload-time = "2025-09-15T10:26:02.12Z" },
|
|
1464
1520
|
]
|
|
1521
|
+
|
|
1522
|
+
[package.optional-dependencies]
|
|
1523
|
+
async = [
|
|
1524
|
+
{ name = "httpx" },
|
|
1525
|
+
{ name = "packaging" },
|
|
1526
|
+
]
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import io
|
|
3
|
-
import logging
|
|
4
|
-
from datetime import UTC, datetime
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
from uuid import uuid4
|
|
7
|
-
|
|
8
|
-
try:
|
|
9
|
-
from lxml import etree
|
|
10
|
-
from zeep.plugins import Plugin
|
|
11
|
-
except ImportError as e:
|
|
12
|
-
msg = 'Install python3-commons[api-client] to use this feature'
|
|
13
|
-
raise RuntimeError(msg) from e
|
|
14
|
-
|
|
15
|
-
from python3_commons import object_storage
|
|
16
|
-
from python3_commons.conf import S3Settings, s3_settings
|
|
17
|
-
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
from zeep.wsdl.definitions import AbstractOperation
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async def write_audit_data(settings: S3Settings, key: str, data: bytes) -> None:
|
|
25
|
-
if settings.aws_secret_access_key:
|
|
26
|
-
try:
|
|
27
|
-
await object_storage.put_object(settings.s3_bucket, f'audit/{key}', io.BytesIO(data), len(data))
|
|
28
|
-
except Exception:
|
|
29
|
-
logger.exception('Failed storing object in storage.')
|
|
30
|
-
else:
|
|
31
|
-
logger.debug('Stored object in storage: %s', key)
|
|
32
|
-
else:
|
|
33
|
-
logger.debug('S3 is not configured, not storing object in storage: %s', key)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class ZeepAuditPlugin(Plugin):
|
|
37
|
-
def __init__(self, audit_name: str = 'zeep') -> None:
|
|
38
|
-
super().__init__()
|
|
39
|
-
self.audit_name = audit_name
|
|
40
|
-
|
|
41
|
-
def store_audit_in_s3(self, envelope, operation: AbstractOperation, direction: str) -> None:
|
|
42
|
-
xml = etree.tostring(envelope, encoding='UTF-8', pretty_print=True)
|
|
43
|
-
now = datetime.now(tz=UTC)
|
|
44
|
-
date_path = now.strftime('%Y/%m/%d')
|
|
45
|
-
timestamp = now.strftime('%H%M%S')
|
|
46
|
-
path = f'{date_path}/{self.audit_name}/{operation.name}/{timestamp}_{str(uuid4())[-12:]}_{direction}.xml'
|
|
47
|
-
coro = write_audit_data(s3_settings, path, xml)
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
loop = asyncio.get_running_loop()
|
|
51
|
-
except RuntimeError:
|
|
52
|
-
loop = None
|
|
53
|
-
|
|
54
|
-
if loop and loop.is_running():
|
|
55
|
-
loop.create_task(coro)
|
|
56
|
-
else:
|
|
57
|
-
asyncio.run(coro)
|
|
58
|
-
|
|
59
|
-
def ingress(self, envelope, http_headers, operation: AbstractOperation):
|
|
60
|
-
self.store_audit_in_s3(envelope, operation, 'ingress')
|
|
61
|
-
|
|
62
|
-
return envelope, http_headers
|
|
63
|
-
|
|
64
|
-
def egress(self, envelope, http_headers, operation: AbstractOperation, binding_options):
|
|
65
|
-
self.store_audit_in_s3(envelope, operation, 'egress')
|
|
66
|
-
|
|
67
|
-
return envelope, http_headers
|
|
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.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/msgpack.py
RENAMED
|
File without changes
|
{python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons/serializers/msgspec.py
RENAMED
|
File without changes
|
{python3_commons-0.17.8 → python3_commons-0.18.0}/src/python3_commons.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python3_commons-0.17.8 → python3_commons-0.18.0}/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
|