python3-commons 0.25.0__tar.gz → 0.25.2__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.25.0 → python3_commons-0.25.2}/.pre-commit-config.yaml +2 -2
- python3_commons-0.25.2/.python-version +1 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/PKG-INFO +13 -13
- {python3_commons-0.25.0 → python3_commons-0.25.2}/README.md +5 -5
- {python3_commons-0.25.0 → python3_commons-0.25.2}/pyproject.toml +7 -7
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/object_storage.py +2 -2
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/PKG-INFO +13 -13
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/requires.txt +7 -7
- python3_commons-0.25.2/src/python3_commons.egg-info/scm_version.json +8 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_api_client.py +50 -1
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_audit.py +20 -1
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_object_storage.py +2 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/uv.lock +301 -287
- python3_commons-0.25.0/.python-version +0 -1
- python3_commons-0.25.0/src/python3_commons.egg-info/scm_version.json +0 -8
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.coveragerc +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.devcontainer/Dockerfile +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.devcontainer/devcontainer.json +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.devcontainer/docker-compose.yml +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.env_template +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.github/workflows/checks.yml +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.github/workflows/python-publish.yaml +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.github/workflows/release-on-tag-push.yml +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/.gitignore +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/AUTHORS.rst +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/CHANGELOG.rst +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/LICENSE +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/README.rst +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/Makefile +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/_static/.gitignore +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/authors.rst +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/changelog.rst +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/conf.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/index.rst +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/license.rst +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/setup.cfg +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/api_client.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/async_functools.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/audit.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/auth.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/cache.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/conf.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/helpers.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/auth.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/common.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/rbac.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/users.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/exceptions.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/fs.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/generators.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/helpers.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/log/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/log/filters.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/log/formatters.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/permissions.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/common.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/json.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/msgpack.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/msgspec.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/soap_client.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/SOURCES.txt +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/dependency_links.txt +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/scm_file_list.json +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/top_level.txt +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/conftest.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/test_auth.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/test_cache.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/test_osc.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/conftest.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/db/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/db/test_async_session_manager.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/log/__init__.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/log/test_formatters.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_async_functools.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_auth.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_helpers.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_msgpack.py +0 -0
- {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_msgspec.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
repos:
|
|
2
2
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
3
|
-
rev: 0.12.
|
|
3
|
+
rev: 0.12.23
|
|
4
4
|
hooks:
|
|
5
5
|
- id: uv-lock
|
|
6
6
|
# - id: uv-export
|
|
7
7
|
|
|
8
8
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
9
|
-
rev: v0.16.
|
|
9
|
+
rev: v0.16.10
|
|
10
10
|
hooks:
|
|
11
11
|
# Run the linter.
|
|
12
12
|
- id: ruff-check
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
==3.14.8
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.25.
|
|
3
|
+
Version: 0.25.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-only
|
|
@@ -12,13 +12,13 @@ Requires-Python: <3.15.0,>=3.14.0
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
License-File: AUTHORS.rst
|
|
15
|
-
Requires-Dist: msgpack~=1.2.
|
|
16
|
-
Requires-Dist: msgspec~=0.
|
|
15
|
+
Requires-Dist: msgpack~=1.2.3
|
|
16
|
+
Requires-Dist: msgspec~=0.22.0
|
|
17
17
|
Requires-Dist: pydantic-settings~=2.15.0
|
|
18
18
|
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
|
-
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.
|
|
21
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.4; extra == "api-client"
|
|
22
22
|
Requires-Dist: certifi~=2026.7.22; extra == "api-client"
|
|
23
23
|
Requires-Dist: python3_commons[audit,object-storage]; extra == "api-client"
|
|
24
24
|
Provides-Extra: audit
|
|
@@ -26,7 +26,7 @@ Requires-Dist: lxml~=6.1.3; extra == "audit"
|
|
|
26
26
|
Requires-Dist: zeep[async]~=4.3.3; extra == "audit"
|
|
27
27
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
28
28
|
Provides-Extra: authn
|
|
29
|
-
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.
|
|
29
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.4; extra == "authn"
|
|
30
30
|
Requires-Dist: certifi~=2026.7.22; extra == "authn"
|
|
31
31
|
Requires-Dist: python3_commons[api-client]; extra == "authn"
|
|
32
32
|
Provides-Extra: authz
|
|
@@ -35,12 +35,12 @@ Requires-Dist: python3_commons[api-client]; extra == "authz"
|
|
|
35
35
|
Provides-Extra: cache
|
|
36
36
|
Requires-Dist: valkey[libvalkey]~=6.1.1; extra == "cache"
|
|
37
37
|
Provides-Extra: database
|
|
38
|
-
Requires-Dist: asyncpg~=0.
|
|
39
|
-
Requires-Dist: SQLAlchemy[asyncio]~=2.1.
|
|
38
|
+
Requires-Dist: asyncpg~=0.32.0; extra == "database"
|
|
39
|
+
Requires-Dist: SQLAlchemy[asyncio]~=2.1.4; extra == "database"
|
|
40
40
|
Provides-Extra: object-storage
|
|
41
41
|
Requires-Dist: object-storage-client~=0.1.12; extra == "object-storage"
|
|
42
42
|
Provides-Extra: soap-client
|
|
43
|
-
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.
|
|
43
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.4; extra == "soap-client"
|
|
44
44
|
Requires-Dist: certifi~=2026.7.22; extra == "soap-client"
|
|
45
45
|
Requires-Dist: lxml~=6.1.3; extra == "soap-client"
|
|
46
46
|
Requires-Dist: requests~=2.34.2; extra == "soap-client"
|
|
@@ -113,7 +113,7 @@ HTTP error status codes and network failures are mapped to built-in Python excep
|
|
|
113
113
|
Audit logging captures the outgoing request (formatted as an executable `curl` command) and the incoming response body, writing them asynchronously to Object Storage.
|
|
114
114
|
|
|
115
115
|
The destination path is resolved as follows:
|
|
116
|
-
- If `ApiClientSettings.audit_path` is configured (via `API_CLIENT_AUDIT_PATH`), it is used directly as the audit root for `api_client` (without prepending `"audit/"`)
|
|
116
|
+
- If `ApiClientSettings.audit_path` is configured (via `API_CLIENT_AUDIT_PATH`), it is used directly as the audit root for `api_client` (without prepending `"audit/"`). Note that `API_CLIENT_AUDIT_PATH` is a full object storage path like `s3://bucket/audit`:
|
|
117
117
|
`<audit_path>/<YYYY/MM/DD>/<audit_name>/<uri>/<method>_<timestamp>_<request_id>_{request,response}.txt`
|
|
118
118
|
- Otherwise, if `ObjectStorageSettings.work_path` is configured (via `OBJECT_STORAGE_WORK_PATH`), it is used as the root with `"audit/"` prepended:
|
|
119
119
|
`<work_path>/audit/<YYYY/MM/DD>/<audit_name>/<uri>/<method>_<timestamp>_<request_id>_{request,response}.txt`
|
|
@@ -123,8 +123,8 @@ The destination path is resolved as follows:
|
|
|
123
123
|
To enable audit logging:
|
|
124
124
|
1. **Configure Object Storage & S3 credentials**:
|
|
125
125
|
```bash
|
|
126
|
-
# API client audit path or Object storage root
|
|
127
|
-
export API_CLIENT_AUDIT_PATH="
|
|
126
|
+
# API client audit path (full object storage path) or Object storage root
|
|
127
|
+
export API_CLIENT_AUDIT_PATH="s3://bucket/audit"
|
|
128
128
|
# or export OBJECT_STORAGE_WORK_PATH="my-audit-bucket"
|
|
129
129
|
|
|
130
130
|
# S3 credentials
|
|
@@ -141,7 +141,7 @@ To enable audit logging:
|
|
|
141
141
|
from python3_commons.conf import api_client_settings, object_storage_settings, s3_settings
|
|
142
142
|
from pydantic import SecretStr
|
|
143
143
|
|
|
144
|
-
api_client_settings.audit_path = "
|
|
144
|
+
api_client_settings.audit_path = "s3://bucket/audit"
|
|
145
145
|
# or object_storage_settings.work_path = "my-audit-bucket"
|
|
146
146
|
|
|
147
147
|
s3_settings.access_key_id = SecretStr("your-access-key-id")
|
|
@@ -373,7 +373,7 @@ Typed application settings powered by Pydantic Settings:
|
|
|
373
373
|
- `DBSettings` / `db_settings`: PostgreSQL database settings (`DB_` environment prefix, with `DB_PASS` alias for password).
|
|
374
374
|
- `S3Settings` / `s3_settings`: S3 credentials and connection configuration (`S3_` environment prefix: `ACCESS_KEY_ID`, `SECRET_ACCESS_KEY`, `REGION`).
|
|
375
375
|
- `ObjectStorageSettings` / `object_storage_settings`: Object storage root configuration (`OBJECT_STORAGE_` environment prefix: `WORK_PATH`).
|
|
376
|
-
- `ApiClientSettings` / `api_client_settings`: API client configuration (`API_CLIENT_` environment prefix: `AUDIT_PATH`).
|
|
376
|
+
- `ApiClientSettings` / `api_client_settings`: API client configuration (`API_CLIENT_` environment prefix: `AUDIT_PATH` full object storage path like `s3://bucket/audit`).
|
|
377
377
|
- `OIDCSettings`: OpenID Connect configuration (`OIDC_` environment prefix).
|
|
378
378
|
- `ValkeySettings` / `valkey_settings`: Valkey/Redis cache configuration (`VALKEY_` environment prefix).
|
|
379
379
|
|
|
@@ -64,7 +64,7 @@ HTTP error status codes and network failures are mapped to built-in Python excep
|
|
|
64
64
|
Audit logging captures the outgoing request (formatted as an executable `curl` command) and the incoming response body, writing them asynchronously to Object Storage.
|
|
65
65
|
|
|
66
66
|
The destination path is resolved as follows:
|
|
67
|
-
- If `ApiClientSettings.audit_path` is configured (via `API_CLIENT_AUDIT_PATH`), it is used directly as the audit root for `api_client` (without prepending `"audit/"`)
|
|
67
|
+
- If `ApiClientSettings.audit_path` is configured (via `API_CLIENT_AUDIT_PATH`), it is used directly as the audit root for `api_client` (without prepending `"audit/"`). Note that `API_CLIENT_AUDIT_PATH` is a full object storage path like `s3://bucket/audit`:
|
|
68
68
|
`<audit_path>/<YYYY/MM/DD>/<audit_name>/<uri>/<method>_<timestamp>_<request_id>_{request,response}.txt`
|
|
69
69
|
- Otherwise, if `ObjectStorageSettings.work_path` is configured (via `OBJECT_STORAGE_WORK_PATH`), it is used as the root with `"audit/"` prepended:
|
|
70
70
|
`<work_path>/audit/<YYYY/MM/DD>/<audit_name>/<uri>/<method>_<timestamp>_<request_id>_{request,response}.txt`
|
|
@@ -74,8 +74,8 @@ The destination path is resolved as follows:
|
|
|
74
74
|
To enable audit logging:
|
|
75
75
|
1. **Configure Object Storage & S3 credentials**:
|
|
76
76
|
```bash
|
|
77
|
-
# API client audit path or Object storage root
|
|
78
|
-
export API_CLIENT_AUDIT_PATH="
|
|
77
|
+
# API client audit path (full object storage path) or Object storage root
|
|
78
|
+
export API_CLIENT_AUDIT_PATH="s3://bucket/audit"
|
|
79
79
|
# or export OBJECT_STORAGE_WORK_PATH="my-audit-bucket"
|
|
80
80
|
|
|
81
81
|
# S3 credentials
|
|
@@ -92,7 +92,7 @@ To enable audit logging:
|
|
|
92
92
|
from python3_commons.conf import api_client_settings, object_storage_settings, s3_settings
|
|
93
93
|
from pydantic import SecretStr
|
|
94
94
|
|
|
95
|
-
api_client_settings.audit_path = "
|
|
95
|
+
api_client_settings.audit_path = "s3://bucket/audit"
|
|
96
96
|
# or object_storage_settings.work_path = "my-audit-bucket"
|
|
97
97
|
|
|
98
98
|
s3_settings.access_key_id = SecretStr("your-access-key-id")
|
|
@@ -324,7 +324,7 @@ Typed application settings powered by Pydantic Settings:
|
|
|
324
324
|
- `DBSettings` / `db_settings`: PostgreSQL database settings (`DB_` environment prefix, with `DB_PASS` alias for password).
|
|
325
325
|
- `S3Settings` / `s3_settings`: S3 credentials and connection configuration (`S3_` environment prefix: `ACCESS_KEY_ID`, `SECRET_ACCESS_KEY`, `REGION`).
|
|
326
326
|
- `ObjectStorageSettings` / `object_storage_settings`: Object storage root configuration (`OBJECT_STORAGE_` environment prefix: `WORK_PATH`).
|
|
327
|
-
- `ApiClientSettings` / `api_client_settings`: API client configuration (`API_CLIENT_` environment prefix: `AUDIT_PATH`).
|
|
327
|
+
- `ApiClientSettings` / `api_client_settings`: API client configuration (`API_CLIENT_` environment prefix: `AUDIT_PATH` full object storage path like `s3://bucket/audit`).
|
|
328
328
|
- `OIDCSettings`: OpenID Connect configuration (`OIDC_` environment prefix).
|
|
329
329
|
- `ValkeySettings` / `valkey_settings`: Valkey/Redis cache configuration (`VALKEY_` environment prefix).
|
|
330
330
|
|
|
@@ -18,8 +18,8 @@ classifiers = [
|
|
|
18
18
|
keywords = []
|
|
19
19
|
requires-python = ">=3.14.0,<3.15.0"
|
|
20
20
|
dependencies = [
|
|
21
|
-
"msgpack~=1.2.
|
|
22
|
-
"msgspec~=0.
|
|
21
|
+
"msgpack~=1.2.3",
|
|
22
|
+
"msgspec~=0.22.0",
|
|
23
23
|
"pydantic-settings~=2.15.0"
|
|
24
24
|
]
|
|
25
25
|
|
|
@@ -28,7 +28,7 @@ all = [
|
|
|
28
28
|
"python3_commons[api-client,audit,authn,authz,cache,database,object-storage,soap-client]"
|
|
29
29
|
]
|
|
30
30
|
api-client = [
|
|
31
|
-
"aiohttp[speedups]>=3.14.
|
|
31
|
+
"aiohttp[speedups]>=3.14.4,<3.15.0",
|
|
32
32
|
"certifi~=2026.7.22",
|
|
33
33
|
"python3_commons[audit,object-storage]"
|
|
34
34
|
]
|
|
@@ -38,7 +38,7 @@ audit = [
|
|
|
38
38
|
"python3_commons[object-storage]"
|
|
39
39
|
]
|
|
40
40
|
authn = [
|
|
41
|
-
"aiohttp[speedups]>=3.14.
|
|
41
|
+
"aiohttp[speedups]>=3.14.4,<3.15.0",
|
|
42
42
|
"certifi~=2026.7.22",
|
|
43
43
|
"python3_commons[api-client]"
|
|
44
44
|
]
|
|
@@ -50,14 +50,14 @@ cache = [
|
|
|
50
50
|
"valkey[libvalkey]~=6.1.1"
|
|
51
51
|
]
|
|
52
52
|
database = [
|
|
53
|
-
"asyncpg~=0.
|
|
54
|
-
"SQLAlchemy[asyncio]~=2.1.
|
|
53
|
+
"asyncpg~=0.32.0",
|
|
54
|
+
"SQLAlchemy[asyncio]~=2.1.4",
|
|
55
55
|
]
|
|
56
56
|
object-storage = [
|
|
57
57
|
"object-storage-client~=0.1.12",
|
|
58
58
|
]
|
|
59
59
|
soap-client = [
|
|
60
|
-
"aiohttp[speedups]>=3.14.
|
|
60
|
+
"aiohttp[speedups]>=3.14.4,<3.15.0",
|
|
61
61
|
"certifi~=2026.7.22",
|
|
62
62
|
"lxml~=6.1.3",
|
|
63
63
|
"requests~=2.34.2",
|
|
@@ -78,9 +78,9 @@ def build_url(work_path: str, path: str = '') -> str:
|
|
|
78
78
|
base_no_scheme = base.split('://', 1)[-1].lstrip('/')
|
|
79
79
|
|
|
80
80
|
if path.startswith(f'{base_no_scheme}/') or path == base_no_scheme:
|
|
81
|
-
|
|
81
|
+
prefix = base[: len(base) - len(base_no_scheme)]
|
|
82
82
|
|
|
83
|
-
return f'{
|
|
83
|
+
return f'{prefix}{path}'
|
|
84
84
|
|
|
85
85
|
parsed = urlparse(base)
|
|
86
86
|
base_path = parsed.path.lstrip('/')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.25.
|
|
3
|
+
Version: 0.25.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-only
|
|
@@ -12,13 +12,13 @@ Requires-Python: <3.15.0,>=3.14.0
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
License-File: AUTHORS.rst
|
|
15
|
-
Requires-Dist: msgpack~=1.2.
|
|
16
|
-
Requires-Dist: msgspec~=0.
|
|
15
|
+
Requires-Dist: msgpack~=1.2.3
|
|
16
|
+
Requires-Dist: msgspec~=0.22.0
|
|
17
17
|
Requires-Dist: pydantic-settings~=2.15.0
|
|
18
18
|
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
|
-
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.
|
|
21
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.4; extra == "api-client"
|
|
22
22
|
Requires-Dist: certifi~=2026.7.22; extra == "api-client"
|
|
23
23
|
Requires-Dist: python3_commons[audit,object-storage]; extra == "api-client"
|
|
24
24
|
Provides-Extra: audit
|
|
@@ -26,7 +26,7 @@ Requires-Dist: lxml~=6.1.3; extra == "audit"
|
|
|
26
26
|
Requires-Dist: zeep[async]~=4.3.3; extra == "audit"
|
|
27
27
|
Requires-Dist: python3_commons[object-storage]; extra == "audit"
|
|
28
28
|
Provides-Extra: authn
|
|
29
|
-
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.
|
|
29
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.4; extra == "authn"
|
|
30
30
|
Requires-Dist: certifi~=2026.7.22; extra == "authn"
|
|
31
31
|
Requires-Dist: python3_commons[api-client]; extra == "authn"
|
|
32
32
|
Provides-Extra: authz
|
|
@@ -35,12 +35,12 @@ Requires-Dist: python3_commons[api-client]; extra == "authz"
|
|
|
35
35
|
Provides-Extra: cache
|
|
36
36
|
Requires-Dist: valkey[libvalkey]~=6.1.1; extra == "cache"
|
|
37
37
|
Provides-Extra: database
|
|
38
|
-
Requires-Dist: asyncpg~=0.
|
|
39
|
-
Requires-Dist: SQLAlchemy[asyncio]~=2.1.
|
|
38
|
+
Requires-Dist: asyncpg~=0.32.0; extra == "database"
|
|
39
|
+
Requires-Dist: SQLAlchemy[asyncio]~=2.1.4; extra == "database"
|
|
40
40
|
Provides-Extra: object-storage
|
|
41
41
|
Requires-Dist: object-storage-client~=0.1.12; extra == "object-storage"
|
|
42
42
|
Provides-Extra: soap-client
|
|
43
|
-
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.
|
|
43
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.14.4; extra == "soap-client"
|
|
44
44
|
Requires-Dist: certifi~=2026.7.22; extra == "soap-client"
|
|
45
45
|
Requires-Dist: lxml~=6.1.3; extra == "soap-client"
|
|
46
46
|
Requires-Dist: requests~=2.34.2; extra == "soap-client"
|
|
@@ -113,7 +113,7 @@ HTTP error status codes and network failures are mapped to built-in Python excep
|
|
|
113
113
|
Audit logging captures the outgoing request (formatted as an executable `curl` command) and the incoming response body, writing them asynchronously to Object Storage.
|
|
114
114
|
|
|
115
115
|
The destination path is resolved as follows:
|
|
116
|
-
- If `ApiClientSettings.audit_path` is configured (via `API_CLIENT_AUDIT_PATH`), it is used directly as the audit root for `api_client` (without prepending `"audit/"`)
|
|
116
|
+
- If `ApiClientSettings.audit_path` is configured (via `API_CLIENT_AUDIT_PATH`), it is used directly as the audit root for `api_client` (without prepending `"audit/"`). Note that `API_CLIENT_AUDIT_PATH` is a full object storage path like `s3://bucket/audit`:
|
|
117
117
|
`<audit_path>/<YYYY/MM/DD>/<audit_name>/<uri>/<method>_<timestamp>_<request_id>_{request,response}.txt`
|
|
118
118
|
- Otherwise, if `ObjectStorageSettings.work_path` is configured (via `OBJECT_STORAGE_WORK_PATH`), it is used as the root with `"audit/"` prepended:
|
|
119
119
|
`<work_path>/audit/<YYYY/MM/DD>/<audit_name>/<uri>/<method>_<timestamp>_<request_id>_{request,response}.txt`
|
|
@@ -123,8 +123,8 @@ The destination path is resolved as follows:
|
|
|
123
123
|
To enable audit logging:
|
|
124
124
|
1. **Configure Object Storage & S3 credentials**:
|
|
125
125
|
```bash
|
|
126
|
-
# API client audit path or Object storage root
|
|
127
|
-
export API_CLIENT_AUDIT_PATH="
|
|
126
|
+
# API client audit path (full object storage path) or Object storage root
|
|
127
|
+
export API_CLIENT_AUDIT_PATH="s3://bucket/audit"
|
|
128
128
|
# or export OBJECT_STORAGE_WORK_PATH="my-audit-bucket"
|
|
129
129
|
|
|
130
130
|
# S3 credentials
|
|
@@ -141,7 +141,7 @@ To enable audit logging:
|
|
|
141
141
|
from python3_commons.conf import api_client_settings, object_storage_settings, s3_settings
|
|
142
142
|
from pydantic import SecretStr
|
|
143
143
|
|
|
144
|
-
api_client_settings.audit_path = "
|
|
144
|
+
api_client_settings.audit_path = "s3://bucket/audit"
|
|
145
145
|
# or object_storage_settings.work_path = "my-audit-bucket"
|
|
146
146
|
|
|
147
147
|
s3_settings.access_key_id = SecretStr("your-access-key-id")
|
|
@@ -373,7 +373,7 @@ Typed application settings powered by Pydantic Settings:
|
|
|
373
373
|
- `DBSettings` / `db_settings`: PostgreSQL database settings (`DB_` environment prefix, with `DB_PASS` alias for password).
|
|
374
374
|
- `S3Settings` / `s3_settings`: S3 credentials and connection configuration (`S3_` environment prefix: `ACCESS_KEY_ID`, `SECRET_ACCESS_KEY`, `REGION`).
|
|
375
375
|
- `ObjectStorageSettings` / `object_storage_settings`: Object storage root configuration (`OBJECT_STORAGE_` environment prefix: `WORK_PATH`).
|
|
376
|
-
- `ApiClientSettings` / `api_client_settings`: API client configuration (`API_CLIENT_` environment prefix: `AUDIT_PATH`).
|
|
376
|
+
- `ApiClientSettings` / `api_client_settings`: API client configuration (`API_CLIENT_` environment prefix: `AUDIT_PATH` full object storage path like `s3://bucket/audit`).
|
|
377
377
|
- `OIDCSettings`: OpenID Connect configuration (`OIDC_` environment prefix).
|
|
378
378
|
- `ValkeySettings` / `valkey_settings`: Valkey/Redis cache configuration (`VALKEY_` environment prefix).
|
|
379
379
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
msgpack~=1.2.
|
|
2
|
-
msgspec~=0.
|
|
1
|
+
msgpack~=1.2.3
|
|
2
|
+
msgspec~=0.22.0
|
|
3
3
|
pydantic-settings~=2.15.0
|
|
4
4
|
|
|
5
5
|
[all]
|
|
6
6
|
python3_commons[api-client,audit,authn,authz,cache,database,object-storage,soap-client]
|
|
7
7
|
|
|
8
8
|
[api-client]
|
|
9
|
-
aiohttp[speedups]<3.15.0,>=3.14.
|
|
9
|
+
aiohttp[speedups]<3.15.0,>=3.14.4
|
|
10
10
|
certifi~=2026.7.22
|
|
11
11
|
python3_commons[audit,object-storage]
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ zeep[async]~=4.3.3
|
|
|
16
16
|
python3_commons[object-storage]
|
|
17
17
|
|
|
18
18
|
[authn]
|
|
19
|
-
aiohttp[speedups]<3.15.0,>=3.14.
|
|
19
|
+
aiohttp[speedups]<3.15.0,>=3.14.4
|
|
20
20
|
certifi~=2026.7.22
|
|
21
21
|
python3_commons[api-client]
|
|
22
22
|
|
|
@@ -28,14 +28,14 @@ python3_commons[api-client]
|
|
|
28
28
|
valkey[libvalkey]~=6.1.1
|
|
29
29
|
|
|
30
30
|
[database]
|
|
31
|
-
asyncpg~=0.
|
|
32
|
-
SQLAlchemy[asyncio]~=2.1.
|
|
31
|
+
asyncpg~=0.32.0
|
|
32
|
+
SQLAlchemy[asyncio]~=2.1.4
|
|
33
33
|
|
|
34
34
|
[object-storage]
|
|
35
35
|
object-storage-client~=0.1.12
|
|
36
36
|
|
|
37
37
|
[soap-client]
|
|
38
|
-
aiohttp[speedups]<3.15.0,>=3.14.
|
|
38
|
+
aiohttp[speedups]<3.15.0,>=3.14.4
|
|
39
39
|
certifi~=2026.7.22
|
|
40
40
|
lxml~=6.1.3
|
|
41
41
|
requests~=2.34.2
|
|
@@ -11,7 +11,8 @@ from python3_commons.api_client import request
|
|
|
11
11
|
from python3_commons.conf import ApiClientSettings, api_client_settings
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def test_api_client_settings():
|
|
14
|
+
def test_api_client_settings(monkeypatch):
|
|
15
|
+
monkeypatch.delenv('API_CLIENT_AUDIT_PATH', raising=False)
|
|
15
16
|
settings = ApiClientSettings()
|
|
16
17
|
assert settings.audit_path is None
|
|
17
18
|
|
|
@@ -19,6 +20,10 @@ def test_api_client_settings():
|
|
|
19
20
|
assert custom.audit_path == 'custom-audit-path'
|
|
20
21
|
assert hasattr(api_client_settings, 'audit_path')
|
|
21
22
|
|
|
23
|
+
monkeypatch.setenv('API_CLIENT_AUDIT_PATH', 'env-audit-path')
|
|
24
|
+
env_settings = ApiClientSettings()
|
|
25
|
+
assert env_settings.audit_path == 'env-audit-path'
|
|
26
|
+
|
|
22
27
|
|
|
23
28
|
@pytest.mark.asyncio
|
|
24
29
|
async def test_api_client_request_success(mocker):
|
|
@@ -130,6 +135,50 @@ async def test_api_client_audit_logging_with_audit_path(mocker, monkeypatch):
|
|
|
130
135
|
assert resp_call[0][1].endswith('_response.txt')
|
|
131
136
|
|
|
132
137
|
|
|
138
|
+
@pytest.mark.asyncio
|
|
139
|
+
async def test_api_client_audit_logging_with_s3_audit_path(mocker, monkeypatch):
|
|
140
|
+
monkeypatch.setattr('python3_commons.api_client.api_client_settings.audit_path', 's3://bucket/audit')
|
|
141
|
+
|
|
142
|
+
mock_response = mocker.MagicMock(spec=ClientResponse)
|
|
143
|
+
mock_response.ok = True
|
|
144
|
+
mock_response.status = HTTPStatus.OK
|
|
145
|
+
mock_response.text = AsyncMock(return_value='{"result": "success"}')
|
|
146
|
+
|
|
147
|
+
mock_ctx = AsyncMock()
|
|
148
|
+
mock_ctx.__aenter__.return_value = mock_response
|
|
149
|
+
mock_ctx.__aexit__.return_value = None
|
|
150
|
+
|
|
151
|
+
session = mocker.MagicMock(spec=ClientSession)
|
|
152
|
+
session.get.return_value = mock_ctx
|
|
153
|
+
session.cookie_jar.filter_cookies.return_value = {}
|
|
154
|
+
|
|
155
|
+
mock_write_audit = mocker.patch('python3_commons.api_client.audit.write_audit_data', new_callable=AsyncMock)
|
|
156
|
+
|
|
157
|
+
async with request(
|
|
158
|
+
session,
|
|
159
|
+
base_url='https://api.example.com',
|
|
160
|
+
uri='/data',
|
|
161
|
+
method='get',
|
|
162
|
+
headers={'Authorization': 'Bearer token'},
|
|
163
|
+
audit_name='test_service',
|
|
164
|
+
) as resp:
|
|
165
|
+
assert resp == mock_response
|
|
166
|
+
|
|
167
|
+
assert mock_write_audit.call_count == 2
|
|
168
|
+
|
|
169
|
+
req_call = mock_write_audit.call_args_list[0]
|
|
170
|
+
assert req_call[0][1].startswith('s3://bucket/audit/')
|
|
171
|
+
assert not req_call[0][1].startswith('audit/')
|
|
172
|
+
assert 'test_service/data/get_' in req_call[0][1]
|
|
173
|
+
assert req_call[0][1].endswith('_request.txt')
|
|
174
|
+
|
|
175
|
+
resp_call = mock_write_audit.call_args_list[1]
|
|
176
|
+
assert resp_call[0][1].startswith('s3://bucket/audit/')
|
|
177
|
+
assert not resp_call[0][1].startswith('audit/')
|
|
178
|
+
assert 'test_service/data/get_' in resp_call[0][1]
|
|
179
|
+
assert resp_call[0][1].endswith('_response.txt')
|
|
180
|
+
|
|
181
|
+
|
|
133
182
|
@pytest.mark.asyncio
|
|
134
183
|
async def test_api_client_audit_logging_disabled_by_default(mocker):
|
|
135
184
|
mock_response = mocker.MagicMock(spec=ClientResponse)
|
|
@@ -34,11 +34,15 @@ def test_object_storage_settings_fields():
|
|
|
34
34
|
assert not hasattr(object_storage_settings, 'audit_path')
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def test_api_client_settings_fields():
|
|
37
|
+
def test_api_client_settings_fields(monkeypatch):
|
|
38
38
|
settings = ApiClientSettings(audit_path='audit-dir')
|
|
39
39
|
assert settings.audit_path == 'audit-dir'
|
|
40
40
|
assert hasattr(api_client_settings, 'audit_path')
|
|
41
41
|
|
|
42
|
+
monkeypatch.setenv('API_CLIENT_AUDIT_PATH', 'env-audit-dir')
|
|
43
|
+
env_settings = ApiClientSettings()
|
|
44
|
+
assert env_settings.audit_path == 'env-audit-dir'
|
|
45
|
+
|
|
42
46
|
|
|
43
47
|
@pytest.mark.asyncio
|
|
44
48
|
async def test_write_audit_data_with_work_path(mocker, monkeypatch):
|
|
@@ -88,6 +92,21 @@ async def test_write_audit_data_key_starts_with_audit_path(mocker, monkeypatch):
|
|
|
88
92
|
assert call_kwargs['path'] == 'my-audit-root/2026/01/01/request.txt'
|
|
89
93
|
|
|
90
94
|
|
|
95
|
+
@pytest.mark.asyncio
|
|
96
|
+
async def test_write_audit_data_with_full_s3_audit_path(mocker, monkeypatch):
|
|
97
|
+
monkeypatch.setattr(object_storage_settings, 'work_path', None)
|
|
98
|
+
monkeypatch.setattr(api_client_settings, 'audit_path', 's3://bucket/audit')
|
|
99
|
+
mock_put = mocker.patch('python3_commons.audit.object_storage.put_object', new_callable=AsyncMock)
|
|
100
|
+
|
|
101
|
+
settings = S3Settings(secret_access_key=SecretStr('secret'))
|
|
102
|
+
await audit.write_audit_data(settings, 's3://bucket/audit/2026/01/01/request.txt', b'test-data')
|
|
103
|
+
|
|
104
|
+
assert mock_put.call_count == 1
|
|
105
|
+
call_kwargs = mock_put.call_args[1]
|
|
106
|
+
assert call_kwargs['work_path'] == 's3://bucket/audit'
|
|
107
|
+
assert call_kwargs['path'] == 's3://bucket/audit/2026/01/01/request.txt'
|
|
108
|
+
|
|
109
|
+
|
|
91
110
|
@pytest.mark.asyncio
|
|
92
111
|
async def test_write_audit_data_default_work_path_audit(mocker, monkeypatch):
|
|
93
112
|
monkeypatch.setattr(object_storage_settings, 'work_path', None)
|
|
@@ -28,6 +28,8 @@ def test_build_url():
|
|
|
28
28
|
# Local paths
|
|
29
29
|
assert object_storage.build_url('file:///var/data', 'file.txt') == 'file:///var/data/file.txt'
|
|
30
30
|
assert object_storage.build_url('/var/data', 'file.txt') == '/var/data/file.txt'
|
|
31
|
+
assert object_storage.build_url('/var/data', 'var/data/file.txt') == '/var/data/file.txt'
|
|
32
|
+
assert object_storage.build_url('file:///var/data', 'var/data/file.txt') == 'file:///var/data/file.txt'
|
|
31
33
|
# Empty path
|
|
32
34
|
assert object_storage.build_url('s3://my-bucket', '') == 's3://my-bucket'
|
|
33
35
|
|