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.
- python3_commons-0.13.5/.devcontainer/Dockerfile +15 -0
- python3_commons-0.13.5/.devcontainer/devcontainer.json +23 -0
- python3_commons-0.13.5/.devcontainer/docker-compose.yml +17 -0
- python3_commons-0.13.5/.github/workflows/checks.yml +63 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/.github/workflows/python-publish.yaml +3 -3
- {python3_commons-0.9.15 → python3_commons-0.13.5}/.github/workflows/release-on-tag-push.yml +3 -3
- {python3_commons-0.9.15 → python3_commons-0.13.5}/.pre-commit-config.yaml +2 -2
- python3_commons-0.13.5/.python-version +1 -0
- {python3_commons-0.9.15/src/python3_commons.egg-info → python3_commons-0.13.5}/PKG-INFO +9 -13
- {python3_commons-0.9.15 → python3_commons-0.13.5}/pyproject.toml +79 -22
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/api_client.py +35 -15
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/audit.py +6 -6
- python3_commons-0.13.5/src/python3_commons/auth.py +48 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/cache.py +44 -46
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/conf.py +4 -3
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/db/__init__.py +8 -9
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/db/helpers.py +5 -7
- python3_commons-0.13.5/src/python3_commons/db/models/__init__.py +1 -0
- python3_commons-0.13.5/src/python3_commons/db/models/auth.py +11 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/db/models/common.py +1 -1
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/fs.py +2 -2
- python3_commons-0.13.5/src/python3_commons/generators.py +47 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/helpers.py +8 -6
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/object_storage.py +43 -39
- python3_commons-0.13.5/src/python3_commons/serializers/common.py +8 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/serializers/json.py +5 -7
- python3_commons-0.13.5/src/python3_commons/serializers/msgpack.py +48 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/serializers/msgspec.py +31 -26
- {python3_commons-0.9.15 → python3_commons-0.13.5/src/python3_commons.egg-info}/PKG-INFO +9 -13
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons.egg-info/SOURCES.txt +7 -2
- python3_commons-0.13.5/src/python3_commons.egg-info/requires.txt +10 -0
- python3_commons-0.13.5/tests/__init__.py +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/conftest.py +7 -7
- {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_cache.py +1 -1
- {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_helpers.py +1 -1
- {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_msgpack.py +2 -2
- {python3_commons-0.9.15 → python3_commons-0.13.5}/tests/test_msgspec.py +15 -8
- python3_commons-0.13.5/uv.lock +1704 -0
- python3_commons-0.9.15/.python-version +0 -1
- python3_commons-0.9.15/src/python3_commons/auth.py +0 -89
- python3_commons-0.9.15/src/python3_commons/db/models/__init__.py +0 -8
- python3_commons-0.9.15/src/python3_commons/db/models/auth.py +0 -35
- python3_commons-0.9.15/src/python3_commons/db/models/rbac.py +0 -91
- python3_commons-0.9.15/src/python3_commons/permissions.py +0 -48
- python3_commons-0.9.15/src/python3_commons/serializers/msgpack.py +0 -50
- python3_commons-0.9.15/src/python3_commons.egg-info/requires.txt +0 -14
- python3_commons-0.9.15/uv.lock +0 -1561
- {python3_commons-0.9.15 → python3_commons-0.13.5}/.coveragerc +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/.gitignore +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/AUTHORS.rst +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/CHANGELOG.rst +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/LICENSE +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/README.md +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/README.rst +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/Makefile +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/_static/.gitignore +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/authors.rst +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/changelog.rst +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/conf.py +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/index.rst +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/docs/license.rst +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/setup.cfg +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/__init__.py +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/log/__init__.py +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/log/filters.py +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/log/formatters.py +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons/serializers/__init__.py +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons.egg-info/dependency_links.txt +0 -0
- {python3_commons-0.9.15 → python3_commons-0.13.5}/src/python3_commons.egg-info/top_level.txt +0 -0
- {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@
|
|
24
|
+
- uses: actions/checkout@v5
|
|
25
25
|
|
|
26
26
|
- name: Install uv
|
|
27
|
-
uses: astral-sh/setup-uv@
|
|
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@
|
|
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@
|
|
29
|
+
- uses: actions/checkout@v5
|
|
30
30
|
|
|
31
31
|
- name: Install uv
|
|
32
|
-
uses: astral-sh/setup-uv@
|
|
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@
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
16
|
-
Requires-Dist: aiohttp[speedups]
|
|
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:
|
|
19
|
-
Requires-Dist:
|
|
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
|
|
24
|
-
Requires-Dist:
|
|
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.
|
|
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 = "
|
|
20
|
-
|
|
19
|
+
requires-python = ">=3.13.9,<3.15.0"
|
|
21
20
|
dependencies = [
|
|
22
|
-
"aiobotocore~=2.
|
|
23
|
-
"aiohttp[speedups]
|
|
21
|
+
"aiobotocore~=2.25.2",
|
|
22
|
+
"aiohttp[speedups]>=3.12.0,<3.15.0",
|
|
24
23
|
"asyncpg~=0.30.0",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
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
|
|
31
|
-
"
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
56
|
-
uri_path = 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
|
-
|
|
95
|
+
msg = 'Unauthorized'
|
|
96
|
+
|
|
97
|
+
raise PermissionError(msg)
|
|
94
98
|
case HTTPStatus.FORBIDDEN:
|
|
95
|
-
|
|
99
|
+
msg = 'Forbidden'
|
|
100
|
+
|
|
101
|
+
raise PermissionError(msg)
|
|
96
102
|
case HTTPStatus.NOT_FOUND:
|
|
97
|
-
|
|
103
|
+
msg = 'Not found'
|
|
104
|
+
|
|
105
|
+
raise LookupError(msg)
|
|
98
106
|
case HTTPStatus.BAD_REQUEST:
|
|
99
|
-
|
|
107
|
+
msg = 'Bad request'
|
|
108
|
+
|
|
109
|
+
raise ValueError(msg)
|
|
100
110
|
case HTTPStatus.TOO_MANY_REQUESTS:
|
|
101
|
-
|
|
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
|
-
|
|
131
|
+
msg = 'Cient connection error'
|
|
132
|
+
|
|
133
|
+
raise ConnectionRefusedError(msg) from e
|
|
120
134
|
except client_exceptions.ClientOSError as e:
|
|
121
|
-
if e.errno ==
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
raise ConnectionResetError(
|
|
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
|
-
|
|
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.
|
|
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
|
|
147
|
-
logger.
|
|
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()
|