python3-commons 0.2.6__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 (81) 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.2.6 → python3_commons-0.13.5}/.github/workflows/python-publish.yaml +17 -9
  6. python3_commons-0.13.5/.github/workflows/release-on-tag-push.yml +52 -0
  7. python3_commons-0.13.5/.pre-commit-config.yaml +35 -0
  8. python3_commons-0.13.5/.python-version +1 -0
  9. python3_commons-0.13.5/PKG-INFO +30 -0
  10. python3_commons-0.13.5/pyproject.toml +154 -0
  11. python3_commons-0.13.5/setup.cfg +4 -0
  12. python3_commons-0.13.5/src/python3_commons/__init__.py +7 -0
  13. python3_commons-0.13.5/src/python3_commons/api_client.py +148 -0
  14. python3_commons-0.13.5/src/python3_commons/audit.py +185 -0
  15. python3_commons-0.13.5/src/python3_commons/auth.py +48 -0
  16. python3_commons-0.13.5/src/python3_commons/cache.py +259 -0
  17. python3_commons-0.13.5/src/python3_commons/conf.py +83 -0
  18. python3_commons-0.13.5/src/python3_commons/db/__init__.py +88 -0
  19. python3_commons-0.13.5/src/python3_commons/db/helpers.py +60 -0
  20. python3_commons-0.13.5/src/python3_commons/db/models/__init__.py +1 -0
  21. python3_commons-0.13.5/src/python3_commons/db/models/auth.py +11 -0
  22. python3_commons-0.13.5/src/python3_commons/db/models/common.py +41 -0
  23. {python3-commons-0.2.6 → python3_commons-0.13.5}/src/python3_commons/fs.py +2 -2
  24. python3_commons-0.13.5/src/python3_commons/generators.py +47 -0
  25. python3_commons-0.13.5/src/python3_commons/helpers.py +139 -0
  26. python3-commons-0.2.6/src/python3_commons/logging/formatter.py → python3_commons-0.13.5/src/python3_commons/log/formatters.py +6 -0
  27. python3_commons-0.13.5/src/python3_commons/object_storage.py +189 -0
  28. python3_commons-0.13.5/src/python3_commons/serializers/common.py +8 -0
  29. {python3-commons-0.2.6 → python3_commons-0.13.5}/src/python3_commons/serializers/json.py +6 -8
  30. python3_commons-0.13.5/src/python3_commons/serializers/msgpack.py +48 -0
  31. python3_commons-0.13.5/src/python3_commons/serializers/msgspec.py +96 -0
  32. python3_commons-0.13.5/src/python3_commons.egg-info/PKG-INFO +30 -0
  33. {python3-commons-0.2.6 → python3_commons-0.13.5}/src/python3_commons.egg-info/SOURCES.txt +26 -11
  34. python3_commons-0.13.5/src/python3_commons.egg-info/requires.txt +10 -0
  35. python3_commons-0.13.5/tests/__init__.py +0 -0
  36. python3_commons-0.13.5/tests/conftest.py +117 -0
  37. python3_commons-0.13.5/tests/test_audit.py +31 -0
  38. python3_commons-0.13.5/tests/test_cache.py +8 -0
  39. python3_commons-0.13.5/tests/test_helpers.py +10 -0
  40. {python3-commons-0.2.6 → python3_commons-0.13.5}/tests/test_msgpack.py +3 -3
  41. python3_commons-0.13.5/tests/test_msgspec.py +78 -0
  42. python3_commons-0.13.5/uv.lock +1704 -0
  43. python3-commons-0.2.6/PKG-INFO +0 -29
  44. python3-commons-0.2.6/pyproject.toml +0 -3
  45. python3-commons-0.2.6/requirements.txt +0 -5
  46. python3-commons-0.2.6/requirements_dev.txt +0 -4
  47. python3-commons-0.2.6/requirements_test.txt +0 -3
  48. python3-commons-0.2.6/setup.cfg +0 -87
  49. python3-commons-0.2.6/setup.py +0 -13
  50. python3-commons-0.2.6/src/python3_commons/__init__.py +0 -10
  51. python3-commons-0.2.6/src/python3_commons/conf.py +0 -20
  52. python3-commons-0.2.6/src/python3_commons/db.py +0 -28
  53. python3-commons-0.2.6/src/python3_commons/helpers.py +0 -49
  54. python3-commons-0.2.6/src/python3_commons/minio.py +0 -28
  55. python3-commons-0.2.6/src/python3_commons/object_storage.py +0 -127
  56. python3-commons-0.2.6/src/python3_commons/serializers/msgpack.py +0 -62
  57. python3-commons-0.2.6/src/python3_commons/serializers/msgspec.py +0 -67
  58. python3-commons-0.2.6/src/python3_commons.egg-info/PKG-INFO +0 -29
  59. python3-commons-0.2.6/src/python3_commons.egg-info/not-zip-safe +0 -1
  60. python3-commons-0.2.6/src/python3_commons.egg-info/requires.txt +0 -9
  61. python3-commons-0.2.6/tests/conftest.py +0 -61
  62. python3-commons-0.2.6/tests/test_msgspec.py +0 -26
  63. {python3-commons-0.2.6 → python3_commons-0.13.5}/.coveragerc +0 -0
  64. {python3-commons-0.2.6 → python3_commons-0.13.5}/.gitignore +0 -0
  65. {python3-commons-0.2.6 → python3_commons-0.13.5}/AUTHORS.rst +0 -0
  66. {python3-commons-0.2.6 → python3_commons-0.13.5}/CHANGELOG.rst +0 -0
  67. {python3-commons-0.2.6 → python3_commons-0.13.5}/LICENSE +0 -0
  68. {python3-commons-0.2.6 → python3_commons-0.13.5}/README.md +0 -0
  69. {python3-commons-0.2.6 → python3_commons-0.13.5}/README.rst +0 -0
  70. {python3-commons-0.2.6 → python3_commons-0.13.5}/docs/Makefile +0 -0
  71. {python3-commons-0.2.6 → python3_commons-0.13.5}/docs/_static/.gitignore +0 -0
  72. {python3-commons-0.2.6 → python3_commons-0.13.5}/docs/authors.rst +0 -0
  73. {python3-commons-0.2.6 → python3_commons-0.13.5}/docs/changelog.rst +0 -0
  74. {python3-commons-0.2.6 → python3_commons-0.13.5}/docs/conf.py +0 -0
  75. {python3-commons-0.2.6 → python3_commons-0.13.5}/docs/index.rst +0 -0
  76. {python3-commons-0.2.6 → python3_commons-0.13.5}/docs/license.rst +0 -0
  77. {python3-commons-0.2.6/src/python3_commons/logging → python3_commons-0.13.5/src/python3_commons/log}/__init__.py +0 -0
  78. {python3-commons-0.2.6/src/python3_commons/logging → python3_commons-0.13.5/src/python3_commons/log}/filters.py +0 -0
  79. {python3-commons-0.2.6 → python3_commons-0.13.5}/src/python3_commons/serializers/__init__.py +0 -0
  80. {python3-commons-0.2.6 → python3_commons-0.13.5}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  81. {python3-commons-0.2.6 → python3_commons-0.13.5}/src/python3_commons.egg-info/top_level.txt +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,19 +21,27 @@ jobs:
21
21
  runs-on: ubuntu-latest
22
22
 
23
23
  steps:
24
- - uses: actions/checkout@v3
25
- - name: Set up Python
26
- uses: actions/setup-python@v3
24
+ - uses: actions/checkout@v5
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v6
28
+ with:
29
+ enable-cache: true
30
+ cache-dependency-glob: "uv.lock"
31
+
32
+ - name: "Set up Python"
33
+ uses: actions/setup-python@v6
27
34
  with:
28
- python-version: '3.x'
29
- - name: Install dependencies
30
- run: |
31
- python -m pip install --upgrade pip
32
- pip install build
35
+ python-version-file: "pyproject.toml"
36
+
37
+ - name: Install build package
38
+ run: uv pip install --system build setuptools_scm
39
+
33
40
  - name: Build package
34
41
  run: python -m build
42
+
35
43
  - name: Publish package
36
- uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
44
+ uses: pypa/gh-action-pypi-publish@release/v1
37
45
  with:
38
46
  user: __token__
39
47
  password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,52 @@
1
+ name: Create Release on Tag
2
+ permissions:
3
+ contents: write
4
+
5
+ on:
6
+ push:
7
+ tags:
8
+ - 'v*'
9
+
10
+ jobs:
11
+ create-release:
12
+ runs-on: ubuntu-latest
13
+ if: ${{ startsWith(github.ref, 'refs/tags') }}
14
+ steps:
15
+ - name: Create GitHub Release
16
+ env:
17
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18
+ tag: ${{ github.ref_name }}
19
+ run: |
20
+ gh release create "$tag" \
21
+ --repo="$GITHUB_REPOSITORY" \
22
+ --title="v${tag#v}" \
23
+ --generate-notes
24
+ pypi-build-and-push:
25
+ name: Build and push wheel package
26
+ runs-on: ubuntu-latest
27
+
28
+ steps:
29
+ - uses: actions/checkout@v5
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v6
33
+ with:
34
+ enable-cache: true
35
+ cache-dependency-glob: "uv.lock"
36
+
37
+ - name: "Set up Python"
38
+ uses: actions/setup-python@v6
39
+ with:
40
+ python-version-file: "pyproject.toml"
41
+
42
+ - name: Install build package
43
+ run: uv pip install --system build setuptools_scm
44
+
45
+ - name: Build package
46
+ run: python -m build
47
+
48
+ - name: Publish package
49
+ uses: pypa/gh-action-pypi-publish@release/v1
50
+ with:
51
+ user: __token__
52
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,35 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/uv-pre-commit
3
+ rev: 0.9.9
4
+ hooks:
5
+ - id: uv-lock
6
+ # - id: uv-export
7
+
8
+ - repo: https://github.com/astral-sh/ruff-pre-commit
9
+ rev: v0.14.4
10
+ hooks:
11
+ # Run the linter.
12
+ - id: ruff-check
13
+ args: [ --fix ]
14
+ files: ^(migrations/|src/|tests/).*
15
+ types_or: [ python, pyi ]
16
+ # Run the import sort.
17
+ - id: ruff-check
18
+ args:
19
+ - --select
20
+ - I
21
+ - --fix
22
+ files: ^(migrations/|src/|tests/).*
23
+ types_or: [ python, pyi ]
24
+ # Run the formatter.
25
+ - id: ruff-format
26
+ files: ^(migrations/|src/|tests/).*
27
+ types_or: [ python, pyi ]
28
+
29
+ # - repo: local
30
+ # hooks:
31
+ # - id: pyright
32
+ # name: Run pyright via uvx
33
+ # entry: uvx pyright ./src ./tests
34
+ # language: system
35
+ # pass_filenames: false
@@ -0,0 +1 @@
1
+ >=3.13.9,<3.15.0
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: python3-commons
3
+ Version: 0.13.5
4
+ Summary: Re-usable Python3 code
5
+ Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
+ License-Expression: GPL-3.0
7
+ Project-URL: Homepage, https://github.com/kamikaze/python3-commons
8
+ Project-URL: Documentation, https://github.com/kamikaze/python3-commons/wiki
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Programming Language :: Python
11
+ Requires-Python: <3.15.0,>=3.13.9
12
+ Description-Content-Type: text/x-rst
13
+ License-File: LICENSE
14
+ License-File: AUTHORS.rst
15
+ Requires-Dist: aiobotocore~=2.25.2
16
+ Requires-Dist: aiohttp[speedups]<3.15.0,>=3.12.0
17
+ Requires-Dist: asyncpg~=0.30.0
18
+ Requires-Dist: lxml~=6.0.2
19
+ Requires-Dist: msgpack~=1.1.2
20
+ Requires-Dist: msgspec~=0.19.0
21
+ Requires-Dist: pydantic-settings~=2.12.0
22
+ Requires-Dist: SQLAlchemy[asyncio]~=2.0.44
23
+ Requires-Dist: valkey[libvalkey]~=6.1.1
24
+ Requires-Dist: zeep~=4.3.2
25
+ Dynamic: license-file
26
+
27
+ Re-usable Python3 code
28
+ ======================
29
+
30
+ Some description here
@@ -0,0 +1,154 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel", "setuptools_scm"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python3-commons"
7
+ dynamic = ["version"]
8
+ description = "Re-usable Python3 code"
9
+ authors = [
10
+ {name = "Oleg Korsak", email = "kamikaze.is.waiting.you@gmail.com"}
11
+ ]
12
+ license = "GPL-3.0"
13
+ readme = {file = "README.rst", content-type = "text/x-rst"}
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Programming Language :: Python"
17
+ ]
18
+ keywords = []
19
+ requires-python = ">=3.13.9,<3.15.0"
20
+ dependencies = [
21
+ "aiobotocore~=2.25.2",
22
+ "aiohttp[speedups]>=3.12.0,<3.15.0",
23
+ "asyncpg~=0.30.0",
24
+ "lxml~=6.0.2",
25
+ "msgpack~=1.1.2",
26
+ "msgspec~=0.19.0",
27
+ "pydantic-settings~=2.12.0",
28
+ "SQLAlchemy[asyncio]~=2.0.44",
29
+ "valkey[libvalkey]~=6.1.1",
30
+ "zeep~=4.3.2"
31
+ ]
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "build",
36
+ "pip",
37
+ "pre-commit",
38
+ "pyright",
39
+ "ruff",
40
+ "setuptools",
41
+ "setuptools_scm",
42
+ "types-aiobotocore[s3]",
43
+ "wheel",
44
+ ]
45
+ testing = [
46
+ "pytest",
47
+ "pytest-asyncio",
48
+ "pytest-cov",
49
+ "pytest-mock",
50
+ ]
51
+
52
+ [project.urls]
53
+ Homepage = "https://github.com/kamikaze/python3-commons"
54
+ Documentation = "https://github.com/kamikaze/python3-commons/wiki"
55
+
56
+ [tool.setuptools.packages.find]
57
+ where = ["src"]
58
+ exclude = ["tests"]
59
+
60
+ [tool.setuptools_scm]
61
+
62
+ [tool.bdist_wheel]
63
+ universal = true
64
+
65
+ [tool.ruff]
66
+ line-length = 120
67
+ indent-width = 4
68
+ target-version = "py313"
69
+
70
+ [tool.ruff.analyze]
71
+ detect-string-imports = true
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
+
133
+ [tool.ruff.lint.flake8-quotes]
134
+ docstring-quotes = "double"
135
+ inline-quotes = "single"
136
+
137
+ [tool.ruff.format]
138
+ exclude = ["*.pyi"]
139
+ indent-style = "space"
140
+ quote-style = "single"
141
+
142
+ [tool.pyright]
143
+ venvPath = "."
144
+ venv = ".venv"
145
+ reportMatchNotExhaustive = "error"
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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ import importlib.metadata
2
+
3
+ try:
4
+ dist_name = __name__
5
+ __version__ = importlib.metadata.version(dist_name)
6
+ except importlib.metadata.PackageNotFoundError:
7
+ __version__ = 'unknown'
@@ -0,0 +1,148 @@
1
+ import errno
2
+ import logging
3
+ from collections.abc import AsyncGenerator, Mapping, Sequence
4
+ from contextlib import asynccontextmanager
5
+ from datetime import UTC, datetime
6
+ from enum import Enum
7
+ from http import HTTPStatus
8
+ from json import dumps
9
+ from typing import Literal
10
+ from uuid import uuid4
11
+
12
+ from aiohttp import ClientResponse, ClientSession, ClientTimeout, client_exceptions
13
+ from aiohttp.abc import URL
14
+
15
+ from python3_commons import audit
16
+ from python3_commons.conf import s3_settings
17
+ from python3_commons.helpers import request_to_curl
18
+ from python3_commons.serializers.json import CustomJSONEncoder
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ async def _store_response_for_audit(
24
+ response: ClientResponse, audit_name: str, uri_path: str, method: str, request_id: str
25
+ ) -> None:
26
+ response_text = await response.text()
27
+
28
+ if response_text:
29
+ now = datetime.now(tz=UTC)
30
+ date_path = now.strftime('%Y/%m/%d')
31
+ timestamp = now.strftime('%H%M%S_%f')
32
+
33
+ await audit.write_audit_data(
34
+ s3_settings,
35
+ f'{date_path}/{audit_name}/{uri_path}/{method}_{timestamp}_{request_id}_response.txt',
36
+ response_text.encode('utf-8'),
37
+ )
38
+
39
+
40
+ @asynccontextmanager
41
+ async def request(
42
+ client: ClientSession,
43
+ base_url: str,
44
+ uri: str,
45
+ query: Mapping | None = None,
46
+ method: Literal['get', 'post', 'put', 'patch', 'options', 'head', 'delete'] = 'get',
47
+ headers: Mapping | None = None,
48
+ json: Mapping | Sequence | str | None = None,
49
+ data: bytes | None = None,
50
+ timeout: ClientTimeout | Enum | None = None,
51
+ audit_name: str | None = None,
52
+ ) -> AsyncGenerator[ClientResponse]:
53
+ now = datetime.now(tz=UTC)
54
+ date_path = now.strftime('%Y/%m/%d')
55
+ timestamp = now.strftime('%H%M%S_%f')
56
+ request_id = str(uuid4())[-12:]
57
+ uri_path = uri.removesuffix('/')
58
+ uri_path = uri_path.removeprefix('/')
59
+ url = f'{u[:-1] if (u := str(base_url)).endswith("/") else u}{uri}'
60
+
61
+ if audit_name:
62
+ curl_request = None
63
+ cookies = client.cookie_jar.filter_cookies(URL(base_url)) if base_url else None
64
+
65
+ if method == 'get':
66
+ if headers or query:
67
+ curl_request = request_to_curl(url=url, query=query, method=method, headers=headers, cookies=cookies)
68
+ else:
69
+ curl_request = request_to_curl(
70
+ url=url, query=query, method=method, headers=headers, cookies=cookies, json=json, data=data
71
+ )
72
+
73
+ if curl_request:
74
+ await audit.write_audit_data(
75
+ s3_settings,
76
+ f'{date_path}/{audit_name}/{uri_path}/{method}_{timestamp}_{request_id}_request.txt',
77
+ curl_request.encode('utf-8'),
78
+ )
79
+
80
+ client_method = getattr(client, method)
81
+
82
+ logger.debug(f'Requesting {method} {url}')
83
+
84
+ try:
85
+ if method == 'get':
86
+ async with client_method(url, params=query, headers=headers, timeout=timeout) as response:
87
+ if audit_name:
88
+ await _store_response_for_audit(response, audit_name, uri_path, method, request_id)
89
+
90
+ if response.ok:
91
+ yield response
92
+ else:
93
+ match response.status:
94
+ case HTTPStatus.UNAUTHORIZED:
95
+ msg = 'Unauthorized'
96
+
97
+ raise PermissionError(msg)
98
+ case HTTPStatus.FORBIDDEN:
99
+ msg = 'Forbidden'
100
+
101
+ raise PermissionError(msg)
102
+ case HTTPStatus.NOT_FOUND:
103
+ msg = 'Not found'
104
+
105
+ raise LookupError(msg)
106
+ case HTTPStatus.BAD_REQUEST:
107
+ msg = 'Bad request'
108
+
109
+ raise ValueError(msg)
110
+ case HTTPStatus.TOO_MANY_REQUESTS:
111
+ msg = 'Too many requests'
112
+
113
+ raise InterruptedError(msg)
114
+ case _:
115
+ response.raise_for_status()
116
+ else:
117
+ if json:
118
+ data = dumps(json, cls=CustomJSONEncoder).encode('utf-8')
119
+
120
+ if headers:
121
+ headers = {**headers, 'Content-Type': 'application/json'}
122
+ else:
123
+ headers = {'Content-Type': 'application/json'}
124
+
125
+ async with client_method(url, params=query, data=data, headers=headers, timeout=timeout) as response:
126
+ if audit_name:
127
+ await _store_response_for_audit(response, audit_name, uri_path, method, request_id)
128
+
129
+ yield response
130
+ except client_exceptions.ClientConnectorError as e:
131
+ msg = 'Cient connection error'
132
+
133
+ raise ConnectionRefusedError(msg) from e
134
+ except client_exceptions.ClientOSError as 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
143
+
144
+ raise
145
+ except client_exceptions.ServerDisconnectedError as e:
146
+ msg = 'Server disconnected'
147
+
148
+ raise ConnectionResetError(msg) from e