python3-commons 0.9.15__tar.gz → 0.13.5__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 (70) hide show
  1. python3_commons-0.13.5/.devcontainer/Dockerfile +15 -0
  2. python3_commons-0.13.5/.devcontainer/devcontainer.json +23 -0
  3. python3_commons-0.13.5/.devcontainer/docker-compose.yml +17 -0
  4. python3_commons-0.13.5/.github/workflows/checks.yml +63 -0
  5. {python3_commons-0.9.15 → python3_commons-0.13.5}/.github/workflows/python-publish.yaml +3 -3
  6. {python3_commons-0.9.15 → python3_commons-0.13.5}/.github/workflows/release-on-tag-push.yml +3 -3
  7. {python3_commons-0.9.15 → python3_commons-0.13.5}/.pre-commit-config.yaml +2 -2
  8. python3_commons-0.13.5/.python-version +1 -0
  9. {python3_commons-0.9.15/src/python3_commons.egg-info → python3_commons-0.13.5}/PKG-INFO +9 -13
  10. {python3_commons-0.9.15 → python3_commons-0.13.5}/pyproject.toml +79 -22
  11. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/api_client.py +35 -15
  12. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/audit.py +6 -6
  13. python3_commons-0.13.5/src/python3_commons/auth.py +48 -0
  14. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/cache.py +44 -46
  15. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/conf.py +4 -3
  16. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/db/__init__.py +8 -9
  17. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/db/helpers.py +5 -7
  18. python3_commons-0.13.5/src/python3_commons/db/models/__init__.py +1 -0
  19. python3_commons-0.13.5/src/python3_commons/db/models/auth.py +11 -0
  20. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/db/models/common.py +1 -1
  21. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/fs.py +2 -2
  22. python3_commons-0.13.5/src/python3_commons/generators.py +47 -0
  23. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/helpers.py +8 -6
  24. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/object_storage.py +43 -39
  25. python3_commons-0.13.5/src/python3_commons/serializers/common.py +8 -0
  26. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/serializers/json.py +5 -7
  27. python3_commons-0.13.5/src/python3_commons/serializers/msgpack.py +48 -0
  28. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/serializers/msgspec.py +31 -26
  29. {python3_commons-0.9.15 → python3_commons-0.13.5/src/python3_commons.egg-info}/PKG-INFO +9 -13
  30. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons.egg-info/SOURCES.txt +7 -2
  31. python3_commons-0.13.5/src/python3_commons.egg-info/requires.txt +10 -0
  32. python3_commons-0.13.5/tests/__init__.py +0 -0
  33. {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/conftest.py +7 -7
  34. {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_cache.py +1 -1
  35. {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_helpers.py +1 -1
  36. {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_msgpack.py +2 -2
  37. {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_msgspec.py +15 -8
  38. python3_commons-0.13.5/uv.lock +1704 -0
  39. python3_commons-0.9.15/.python-version +0 -1
  40. python3_commons-0.9.15/src/python3_commons/auth.py +0 -89
  41. python3_commons-0.9.15/src/python3_commons/db/models/__init__.py +0 -8
  42. python3_commons-0.9.15/src/python3_commons/db/models/auth.py +0 -35
  43. python3_commons-0.9.15/src/python3_commons/db/models/rbac.py +0 -91
  44. python3_commons-0.9.15/src/python3_commons/permissions.py +0 -48
  45. python3_commons-0.9.15/src/python3_commons/serializers/msgpack.py +0 -50
  46. python3_commons-0.9.15/src/python3_commons.egg-info/requires.txt +0 -14
  47. python3_commons-0.9.15/uv.lock +0 -1561
  48. {python3_commons-0.9.15 → python3_commons-0.13.5}/.coveragerc +0 -0
  49. {python3_commons-0.9.15 → python3_commons-0.13.5}/.gitignore +0 -0
  50. {python3_commons-0.9.15 → python3_commons-0.13.5}/AUTHORS.rst +0 -0
  51. {python3_commons-0.9.15 → python3_commons-0.13.5}/CHANGELOG.rst +0 -0
  52. {python3_commons-0.9.15 → python3_commons-0.13.5}/LICENSE +0 -0
  53. {python3_commons-0.9.15 → python3_commons-0.13.5}/README.md +0 -0
  54. {python3_commons-0.9.15 → python3_commons-0.13.5}/README.rst +0 -0
  55. {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/Makefile +0 -0
  56. {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/_static/.gitignore +0 -0
  57. {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/authors.rst +0 -0
  58. {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/changelog.rst +0 -0
  59. {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/conf.py +0 -0
  60. {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/index.rst +0 -0
  61. {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/license.rst +0 -0
  62. {python3_commons-0.9.15 → python3_commons-0.13.5}/setup.cfg +0 -0
  63. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/__init__.py +0 -0
  64. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/log/__init__.py +0 -0
  65. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/log/filters.py +0 -0
  66. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/log/formatters.py +0 -0
  67. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/serializers/__init__.py +0 -0
  68. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  69. {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons.egg-info/top_level.txt +0 -0
  70. {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_audit.py +0 -0
@@ -0,0 +1,15 @@
1
+ FROM python:3.13.9-slim-trixie
2
+
3
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
4
+
5
+ ENV PYTHONDONTWRITEBYTECODE=1
6
+ ENV PYTHONUNBUFFERED=1
7
+
8
+ RUN apt update && \
9
+ apt install -y curl ca-certificates apt-transport-https wget gnupg && \
10
+ wget -O- https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg && \
11
+ echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ trixie-pgdg main | tee /etc/apt/sources.list.d/postgresql.list && \
12
+ apt update && \
13
+ apt install -y --no-install-recommends libpq-dev gcc g++ make postgresql-server-dev-18 libffi-dev git cargo pkg-config ssh && \
14
+ apt clean && \
15
+ rm -rf /var/lib/{apt,dpkg,cache,log}/
@@ -0,0 +1,23 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/postgres
3
+ {
4
+ "name": "python3-commons",
5
+ "dockerComposeFile": "docker-compose.yml",
6
+ "service": "python3-commons",
7
+ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
8
+ // Features to add to the dev container. More info: https://containers.dev/features.
9
+ // "features": {},
10
+
11
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
12
+ // This can be used to network with other containers or the host.
13
+ "forwardPorts": [8080, 5432],
14
+
15
+ "postCreateCommand": "uv sync --all-groups --all-extras",
16
+ "customizations": {
17
+ "jetbrains": {
18
+ "backend": "PyCharm"
19
+ }
20
+ },
21
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
22
+ "remoteUser": "root"
23
+ }
@@ -0,0 +1,17 @@
1
+ services:
2
+ db:
3
+ image: postgres:18-alpine
4
+ container_name: db
5
+ ports:
6
+ - "5432:5432"
7
+ environment:
8
+ POSTGRES_DB: "${POSTGRES_DB:-python3-commons}"
9
+ POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-dev}"
10
+ POSTGRES_USER: "${POSTGRES_USER:-dev}"
11
+ restart: unless-stopped
12
+
13
+ # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
14
+ # (Adding the "ports" property to this file will not forward from a Codespace.)
15
+
16
+ volumes:
17
+ postgres-data:
@@ -0,0 +1,63 @@
1
+ name: Checks
2
+
3
+ on:
4
+ push:
5
+
6
+ concurrency:
7
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8
+ cancel-in-progress: true
9
+
10
+ jobs:
11
+ lint:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Check Out Repo
15
+ uses: actions/checkout@v5
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v6
19
+ with:
20
+ enable-cache: true
21
+ cache-dependency-glob: "uv.lock"
22
+
23
+ - name: "Set up Python"
24
+ uses: actions/setup-python@v6
25
+ with:
26
+ python-version-file: "pyproject.toml"
27
+
28
+ - name: Install the project
29
+ run: uv sync --locked --all-extras --dev
30
+
31
+ - name: Ruff format check
32
+ run: |
33
+ uvx ruff format --diff ./src/ ./tests/
34
+
35
+ - name: Ruff default rules and import check
36
+ run: |
37
+ uvx ruff check --extend-select I ./src/ ./tests/
38
+
39
+ # - name: Pyright check
40
+ # run: |
41
+ # uvx pyright ./src/
42
+ # tests:
43
+ # runs-on: ubuntu-latest
44
+ # steps:
45
+ # - name: Check Out Repo
46
+ # uses: actions/checkout@v5
47
+ #
48
+ # - name: Install uv
49
+ # uses: astral-sh/setup-uv@v6
50
+ # with:
51
+ # enable-cache: true
52
+ # cache-dependency-glob: "uv.lock"
53
+ #
54
+ # - name: "Set up Python"
55
+ # uses: actions/setup-python@v6
56
+ # with:
57
+ # python-version-file: "pyproject.toml"
58
+ #
59
+ # - name: Install the project
60
+ # run: uv sync --locked --all-extras --all-groups --dev
61
+ #
62
+ # - name: Run unittests
63
+ # run: uv run pytest tests/
@@ -21,16 +21,16 @@ jobs:
21
21
  runs-on: ubuntu-latest
22
22
 
23
23
  steps:
24
- - uses: actions/checkout@v4
24
+ - uses: actions/checkout@v5
25
25
 
26
26
  - name: Install uv
27
- uses: astral-sh/setup-uv@v5
27
+ uses: astral-sh/setup-uv@v6
28
28
  with:
29
29
  enable-cache: true
30
30
  cache-dependency-glob: "uv.lock"
31
31
 
32
32
  - name: "Set up Python"
33
- uses: actions/setup-python@v5
33
+ uses: actions/setup-python@v6
34
34
  with:
35
35
  python-version-file: "pyproject.toml"
36
36
 
@@ -26,16 +26,16 @@ jobs:
26
26
  runs-on: ubuntu-latest
27
27
 
28
28
  steps:
29
- - uses: actions/checkout@v4
29
+ - uses: actions/checkout@v5
30
30
 
31
31
  - name: Install uv
32
- uses: astral-sh/setup-uv@v5
32
+ uses: astral-sh/setup-uv@v6
33
33
  with:
34
34
  enable-cache: true
35
35
  cache-dependency-glob: "uv.lock"
36
36
 
37
37
  - name: "Set up Python"
38
- uses: actions/setup-python@v5
38
+ uses: actions/setup-python@v6
39
39
  with:
40
40
  python-version-file: "pyproject.toml"
41
41
 
@@ -1,12 +1,12 @@
1
1
  repos:
2
2
  - repo: https://github.com/astral-sh/uv-pre-commit
3
- rev: 0.8.15
3
+ rev: 0.9.9
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.12.12
9
+ rev: v0.14.4
10
10
  hooks:
11
11
  # Run the linter.
12
12
  - id: ruff-check
@@ -0,0 +1 @@
1
+ >=3.13.9,<3.15.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.9.15
3
+ Version: 0.13.5
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
@@ -8,24 +8,20 @@ Project-URL: Homepage, https://github.com/kamikaze/python3-commons
8
8
  Project-URL: Documentation, https://github.com/kamikaze/python3-commons/wiki
9
9
  Classifier: Development Status :: 4 - Beta
10
10
  Classifier: Programming Language :: Python
11
- Requires-Python: ==3.13.*
11
+ Requires-Python: <3.15.0,>=3.13.9
12
12
  Description-Content-Type: text/x-rst
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.rst
15
- Requires-Dist: aiobotocore~=2.24.2
16
- Requires-Dist: aiohttp[speedups]~=3.12.15
15
+ Requires-Dist: aiobotocore~=2.25.2
16
+ Requires-Dist: aiohttp[speedups]<3.15.0,>=3.12.0
17
17
  Requires-Dist: asyncpg~=0.30.0
18
- Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
19
- Requires-Dist: fastapi-users[sqlalchemy]~=14.0.1
20
- Requires-Dist: lxml~=6.0.1
21
- Requires-Dist: msgpack~=1.1.1
18
+ Requires-Dist: lxml~=6.0.2
19
+ Requires-Dist: msgpack~=1.1.2
22
20
  Requires-Dist: msgspec~=0.19.0
23
- Requires-Dist: pydantic[email]~=2.11.7
24
- Requires-Dist: pydantic-settings~=2.10.1
25
- Requires-Dist: python-jose==3.5.0
26
- Requires-Dist: SQLAlchemy[asyncio]~=2.0.43
21
+ Requires-Dist: pydantic-settings~=2.12.0
22
+ Requires-Dist: SQLAlchemy[asyncio]~=2.0.44
27
23
  Requires-Dist: valkey[libvalkey]~=6.1.1
28
- Requires-Dist: zeep~=4.3.1
24
+ Requires-Dist: zeep~=4.3.2
29
25
  Dynamic: license-file
30
26
 
31
27
  Re-usable Python3 code
@@ -16,23 +16,18 @@ classifiers = [
16
16
  "Programming Language :: Python"
17
17
  ]
18
18
  keywords = []
19
- requires-python = "==3.13.*"
20
-
19
+ requires-python = ">=3.13.9,<3.15.0"
21
20
  dependencies = [
22
- "aiobotocore~=2.24.2",
23
- "aiohttp[speedups]~=3.12.15",
21
+ "aiobotocore~=2.25.2",
22
+ "aiohttp[speedups]>=3.12.0,<3.15.0",
24
23
  "asyncpg~=0.30.0",
25
- "fastapi-users-db-sqlalchemy~=7.0.0",
26
- "fastapi-users[sqlalchemy]~=14.0.1",
27
- "lxml~=6.0.1",
28
- "msgpack~=1.1.1",
24
+ "lxml~=6.0.2",
25
+ "msgpack~=1.1.2",
29
26
  "msgspec~=0.19.0",
30
- "pydantic[email]~=2.11.7",
31
- "pydantic-settings~=2.10.1",
32
- "python-jose==3.5.0",
33
- "SQLAlchemy[asyncio]~=2.0.43",
27
+ "pydantic-settings~=2.12.0",
28
+ "SQLAlchemy[asyncio]~=2.0.44",
34
29
  "valkey[libvalkey]~=6.1.1",
35
- "zeep~=4.3.1"
30
+ "zeep~=4.3.2"
36
31
  ]
37
32
 
38
33
  [dependency-groups]
@@ -44,14 +39,14 @@ dev = [
44
39
  "ruff",
45
40
  "setuptools",
46
41
  "setuptools_scm",
47
- "types-aiobotocore-s3",
42
+ "types-aiobotocore[s3]",
48
43
  "wheel",
49
44
  ]
50
45
  testing = [
51
46
  "pytest",
52
47
  "pytest-asyncio",
53
48
  "pytest-cov",
54
- "pytest-mock"
49
+ "pytest-mock",
55
50
  ]
56
51
 
57
52
  [project.urls]
@@ -67,13 +62,6 @@ exclude = ["tests"]
67
62
  [tool.bdist_wheel]
68
63
  universal = true
69
64
 
70
- [tool.pytest.ini_options]
71
- addopts = [
72
- "--verbose"
73
- ]
74
- norecursedirs = ["dist", "build", ".tox"]
75
- testpaths = ["tests"]
76
-
77
65
  [tool.ruff]
78
66
  line-length = 120
79
67
  indent-width = 4
@@ -82,8 +70,69 @@ target-version = "py313"
82
70
  [tool.ruff.analyze]
83
71
  detect-string-imports = true
84
72
 
73
+ [tool.ruff.lint]
74
+ select = [
75
+ "FAST",
76
+ "YTT",
77
+ # "ANN",
78
+ "I",
79
+ "E",
80
+ "W",
81
+ "PYI",
82
+
83
+ "A",
84
+ # "ARG",
85
+ "ASYNC",
86
+ "B",
87
+ "C4",
88
+ # "DTZ",
89
+ "EM",
90
+ "EXE",
91
+ "F",
92
+ "FA",
93
+ "FBT",
94
+ "FLY",
95
+ "FURB",
96
+ # "G",
97
+ "ICN",
98
+ "INP",
99
+ "ISC",
100
+ "LOG",
101
+ "N",
102
+ "NPY",
103
+ "PD",
104
+ "PERF",
105
+ "PIE",
106
+ "PLC",
107
+ "PLE",
108
+ # "PLR",
109
+ "PLW",
110
+ "PT",
111
+ "PTH",
112
+ "Q",
113
+ "RET",
114
+ "RSE",
115
+ "RUF",
116
+ "S",
117
+ "SIM",
118
+ "SLF",
119
+ "SLOT",
120
+ "T20",
121
+ "TC",
122
+ "TID",
123
+ "TRY",
124
+ "UP",
125
+ ]
126
+ ignore = [
127
+ "ASYNC109",
128
+ ]
129
+
130
+ [tool.ruff.lint.per-file-ignores]
131
+ "tests/*.py" = ["DTZ", "S101"]
132
+
85
133
  [tool.ruff.lint.flake8-quotes]
86
134
  docstring-quotes = "double"
135
+ inline-quotes = "single"
87
136
 
88
137
  [tool.ruff.format]
89
138
  exclude = ["*.pyi"]
@@ -95,3 +144,11 @@ venvPath = "."
95
144
  venv = ".venv"
96
145
  reportMatchNotExhaustive = "error"
97
146
  reportUnnecessaryComparison = "error"
147
+
148
+ [tool.pytest.ini_options]
149
+ pythonpath = ["src"]
150
+ asyncio_mode = "strict"
151
+ asyncio_default_fixture_loop_scope = "function"
152
+ addopts = "--cov python3_commons --cov-fail-under=20 --cov-report term-missing --verbose"
153
+ norecursedirs = ["dist", "build", ".tox"]
154
+ testpaths = ["tests"]
@@ -1,10 +1,12 @@
1
+ import errno
1
2
  import logging
3
+ from collections.abc import AsyncGenerator, Mapping, Sequence
2
4
  from contextlib import asynccontextmanager
3
5
  from datetime import UTC, datetime
4
6
  from enum import Enum
5
7
  from http import HTTPStatus
6
8
  from json import dumps
7
- from typing import AsyncGenerator, Literal, Mapping, Sequence
9
+ from typing import Literal
8
10
  from uuid import uuid4
9
11
 
10
12
  from aiohttp import ClientResponse, ClientSession, ClientTimeout, client_exceptions
@@ -20,7 +22,7 @@ logger = logging.getLogger(__name__)
20
22
 
21
23
  async def _store_response_for_audit(
22
24
  response: ClientResponse, audit_name: str, uri_path: str, method: str, request_id: str
23
- ):
25
+ ) -> None:
24
26
  response_text = await response.text()
25
27
 
26
28
  if response_text:
@@ -52,8 +54,8 @@ async def request(
52
54
  date_path = now.strftime('%Y/%m/%d')
53
55
  timestamp = now.strftime('%H%M%S_%f')
54
56
  request_id = str(uuid4())[-12:]
55
- uri_path = uri[:-1] if uri.endswith('/') else uri
56
- uri_path = uri_path[1:] if uri_path.startswith('/') else uri_path
57
+ uri_path = uri.removesuffix('/')
58
+ uri_path = uri_path.removeprefix('/')
57
59
  url = f'{u[:-1] if (u := str(base_url)).endswith("/") else u}{uri}'
58
60
 
59
61
  if audit_name:
@@ -90,15 +92,25 @@ async def request(
90
92
  else:
91
93
  match response.status:
92
94
  case HTTPStatus.UNAUTHORIZED:
93
- raise PermissionError('Unauthorized')
95
+ msg = 'Unauthorized'
96
+
97
+ raise PermissionError(msg)
94
98
  case HTTPStatus.FORBIDDEN:
95
- raise PermissionError('Forbidden')
99
+ msg = 'Forbidden'
100
+
101
+ raise PermissionError(msg)
96
102
  case HTTPStatus.NOT_FOUND:
97
- raise LookupError('Not found')
103
+ msg = 'Not found'
104
+
105
+ raise LookupError(msg)
98
106
  case HTTPStatus.BAD_REQUEST:
99
- raise ValueError('Bad request')
107
+ msg = 'Bad request'
108
+
109
+ raise ValueError(msg)
100
110
  case HTTPStatus.TOO_MANY_REQUESTS:
101
- raise InterruptedError('Too many requests')
111
+ msg = 'Too many requests'
112
+
113
+ raise InterruptedError(msg)
102
114
  case _:
103
115
  response.raise_for_status()
104
116
  else:
@@ -116,13 +128,21 @@ async def request(
116
128
 
117
129
  yield response
118
130
  except client_exceptions.ClientConnectorError as e:
119
- raise ConnectionRefusedError('Cient connection error') from e
131
+ msg = 'Cient connection error'
132
+
133
+ raise ConnectionRefusedError(msg) from e
120
134
  except client_exceptions.ClientOSError as e:
121
- if e.errno == 32:
122
- raise ConnectionResetError('Broken pipe') from e
123
- elif e.errno == 104:
124
- raise ConnectionResetError('Connection reset by peer') from e
135
+ if e.errno == errno.EPIPE:
136
+ msg = 'Broken pipe'
137
+
138
+ raise ConnectionResetError(msg) from e
139
+ elif e.errno == errno.ECONNRESET:
140
+ msg = 'Connection reset by peer'
141
+
142
+ raise ConnectionResetError(msg) from e
125
143
 
126
144
  raise
127
145
  except client_exceptions.ServerDisconnectedError as e:
128
- raise ConnectionResetError('Server disconnected') from e
146
+ msg = 'Server disconnected'
147
+
148
+ raise ConnectionResetError(msg) from e
@@ -137,14 +137,14 @@ logger = logging.getLogger(__name__)
137
137
  # logger.error(f'Failed to delete object in {bucket_name=}: {error}')
138
138
 
139
139
 
140
- async def write_audit_data(settings: S3Settings, key: str, data: bytes):
141
- if settings.s3_secret_access_key:
140
+ async def write_audit_data(settings: S3Settings, key: str, data: bytes) -> None:
141
+ if settings.aws_secret_access_key:
142
142
  try:
143
143
  absolute_path = object_storage.get_absolute_path(f'audit/{key}')
144
144
 
145
145
  await object_storage.put_object(settings.s3_bucket, absolute_path, io.BytesIO(data), len(data))
146
- except Exception as e:
147
- logger.error(f'Failed storing object in storage: {e}')
146
+ except Exception:
147
+ logger.exception('Failed storing object in storage.')
148
148
  else:
149
149
  logger.debug(f'Stored object in storage: {key}')
150
150
  else:
@@ -152,11 +152,11 @@ async def write_audit_data(settings: S3Settings, key: str, data: bytes):
152
152
 
153
153
 
154
154
  class ZeepAuditPlugin(Plugin):
155
- def __init__(self, audit_name: str = 'zeep'):
155
+ def __init__(self, audit_name: str = 'zeep') -> None:
156
156
  super().__init__()
157
157
  self.audit_name = audit_name
158
158
 
159
- def store_audit_in_s3(self, envelope, operation: AbstractOperation, direction: str):
159
+ def store_audit_in_s3(self, envelope, operation: AbstractOperation, direction: str) -> None:
160
160
  xml = etree.tostring(envelope, encoding='UTF-8', pretty_print=True)
161
161
  now = datetime.now(tz=UTC)
162
162
  date_path = now.strftime('%Y/%m/%d')
@@ -0,0 +1,48 @@
1
+ import logging
2
+ from collections.abc import Sequence
3
+ from http import HTTPStatus
4
+ from typing import TypeVar
5
+
6
+ import aiohttp
7
+ import msgspec
8
+
9
+ from python3_commons.conf import oidc_settings
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class TokenData(msgspec.Struct):
15
+ sub: str
16
+ aud: str | Sequence[str]
17
+ exp: int
18
+ iss: str
19
+
20
+
21
+ T = TypeVar('T', bound=TokenData)
22
+ OIDC_CONFIG_URL = f'{oidc_settings.authority_url}/.well-known/openid-configuration'
23
+
24
+
25
+ async def fetch_openid_config() -> dict:
26
+ """
27
+ Fetch the OpenID configuration (including JWKS URI) from OIDC authority.
28
+ """
29
+ async with aiohttp.ClientSession() as session, session.get(OIDC_CONFIG_URL) as response:
30
+ if response.status != HTTPStatus.OK:
31
+ msg = 'Failed to fetch OpenID configuration'
32
+
33
+ raise RuntimeError(msg)
34
+
35
+ return await response.json()
36
+
37
+
38
+ async def fetch_jwks(jwks_uri: str) -> dict:
39
+ """
40
+ Fetch the JSON Web Key Set (JWKS) for validating the token's signature.
41
+ """
42
+ async with aiohttp.ClientSession() as session, session.get(jwks_uri) as response:
43
+ if response.status != HTTPStatus.OK:
44
+ msg = 'Failed to fetch JWKS'
45
+
46
+ raise RuntimeError(msg)
47
+
48
+ return await response.json()