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.
Files changed (85) hide show
  1. {python3_commons-0.25.0 → python3_commons-0.25.2}/.pre-commit-config.yaml +2 -2
  2. python3_commons-0.25.2/.python-version +1 -0
  3. {python3_commons-0.25.0 → python3_commons-0.25.2}/PKG-INFO +13 -13
  4. {python3_commons-0.25.0 → python3_commons-0.25.2}/README.md +5 -5
  5. {python3_commons-0.25.0 → python3_commons-0.25.2}/pyproject.toml +7 -7
  6. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/object_storage.py +2 -2
  7. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/PKG-INFO +13 -13
  8. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/requires.txt +7 -7
  9. python3_commons-0.25.2/src/python3_commons.egg-info/scm_version.json +8 -0
  10. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_api_client.py +50 -1
  11. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_audit.py +20 -1
  12. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_object_storage.py +2 -0
  13. {python3_commons-0.25.0 → python3_commons-0.25.2}/uv.lock +301 -287
  14. python3_commons-0.25.0/.python-version +0 -1
  15. python3_commons-0.25.0/src/python3_commons.egg-info/scm_version.json +0 -8
  16. {python3_commons-0.25.0 → python3_commons-0.25.2}/.coveragerc +0 -0
  17. {python3_commons-0.25.0 → python3_commons-0.25.2}/.devcontainer/Dockerfile +0 -0
  18. {python3_commons-0.25.0 → python3_commons-0.25.2}/.devcontainer/devcontainer.json +0 -0
  19. {python3_commons-0.25.0 → python3_commons-0.25.2}/.devcontainer/docker-compose.yml +0 -0
  20. {python3_commons-0.25.0 → python3_commons-0.25.2}/.env_template +0 -0
  21. {python3_commons-0.25.0 → python3_commons-0.25.2}/.github/workflows/checks.yml +0 -0
  22. {python3_commons-0.25.0 → python3_commons-0.25.2}/.github/workflows/python-publish.yaml +0 -0
  23. {python3_commons-0.25.0 → python3_commons-0.25.2}/.github/workflows/release-on-tag-push.yml +0 -0
  24. {python3_commons-0.25.0 → python3_commons-0.25.2}/.gitignore +0 -0
  25. {python3_commons-0.25.0 → python3_commons-0.25.2}/AUTHORS.rst +0 -0
  26. {python3_commons-0.25.0 → python3_commons-0.25.2}/CHANGELOG.rst +0 -0
  27. {python3_commons-0.25.0 → python3_commons-0.25.2}/LICENSE +0 -0
  28. {python3_commons-0.25.0 → python3_commons-0.25.2}/README.rst +0 -0
  29. {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/Makefile +0 -0
  30. {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/_static/.gitignore +0 -0
  31. {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/authors.rst +0 -0
  32. {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/changelog.rst +0 -0
  33. {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/conf.py +0 -0
  34. {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/index.rst +0 -0
  35. {python3_commons-0.25.0 → python3_commons-0.25.2}/docs/license.rst +0 -0
  36. {python3_commons-0.25.0 → python3_commons-0.25.2}/setup.cfg +0 -0
  37. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/__init__.py +0 -0
  38. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/api_client.py +0 -0
  39. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/async_functools.py +0 -0
  40. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/audit.py +0 -0
  41. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/auth.py +0 -0
  42. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/cache.py +0 -0
  43. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/conf.py +0 -0
  44. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/__init__.py +0 -0
  45. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/helpers.py +0 -0
  46. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/__init__.py +0 -0
  47. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/auth.py +0 -0
  48. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/common.py +0 -0
  49. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/rbac.py +0 -0
  50. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/db/models/users.py +0 -0
  51. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/exceptions.py +0 -0
  52. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/fs.py +0 -0
  53. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/generators.py +0 -0
  54. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/helpers.py +0 -0
  55. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/log/__init__.py +0 -0
  56. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/log/filters.py +0 -0
  57. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/log/formatters.py +0 -0
  58. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/permissions.py +0 -0
  59. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/__init__.py +0 -0
  60. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/common.py +0 -0
  61. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/json.py +0 -0
  62. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/msgpack.py +0 -0
  63. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/serializers/msgspec.py +0 -0
  64. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons/soap_client.py +0 -0
  65. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/SOURCES.txt +0 -0
  66. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  67. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/scm_file_list.json +0 -0
  68. {python3_commons-0.25.0 → python3_commons-0.25.2}/src/python3_commons.egg-info/top_level.txt +0 -0
  69. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/__init__.py +0 -0
  70. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/__init__.py +0 -0
  71. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/conftest.py +0 -0
  72. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/test_auth.py +0 -0
  73. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/test_cache.py +0 -0
  74. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/integration/test_osc.py +0 -0
  75. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/__init__.py +0 -0
  76. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/conftest.py +0 -0
  77. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/db/__init__.py +0 -0
  78. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/db/test_async_session_manager.py +0 -0
  79. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/log/__init__.py +0 -0
  80. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/log/test_formatters.py +0 -0
  81. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_async_functools.py +0 -0
  82. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_auth.py +0 -0
  83. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_helpers.py +0 -0
  84. {python3_commons-0.25.0 → python3_commons-0.25.2}/tests/unit/test_msgpack.py +0 -0
  85. {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.10
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.6
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.0
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.2
16
- Requires-Dist: msgspec~=0.21.1
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.3; extra == "api-client"
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.3; extra == "authn"
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.31.0; extra == "database"
39
- Requires-Dist: SQLAlchemy[asyncio]~=2.1.1; extra == "database"
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.3; extra == "soap-client"
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="my-audit-bucket/audit-logs"
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 = "my-audit-bucket/audit-logs"
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="my-audit-bucket/audit-logs"
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 = "my-audit-bucket/audit-logs"
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.2",
22
- "msgspec~=0.21.1",
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.3,<3.15.0",
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.3,<3.15.0",
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.31.0",
54
- "SQLAlchemy[asyncio]~=2.1.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.3,<3.15.0",
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
- scheme = base.split('://', 1)[0] + '://' if '://' in base else ''
81
+ prefix = base[: len(base) - len(base_no_scheme)]
82
82
 
83
- return f'{scheme}{path}'
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.0
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.2
16
- Requires-Dist: msgspec~=0.21.1
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.3; extra == "api-client"
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.3; extra == "authn"
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.31.0; extra == "database"
39
- Requires-Dist: SQLAlchemy[asyncio]~=2.1.1; extra == "database"
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.3; extra == "soap-client"
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="my-audit-bucket/audit-logs"
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 = "my-audit-bucket/audit-logs"
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
2
- msgspec~=0.21.1
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.3
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.3
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.31.0
32
- SQLAlchemy[asyncio]~=2.1.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.3
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.25.2",
3
+ "distance": 0,
4
+ "node": "g54b958326632a148dc22f407b5f2bec9db2e1ccb",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-07"
8
+ }
@@ -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