bazis-async-request 2.2.3__tar.gz → 2.3.0__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.
- bazis_async_request-2.3.0/.github/workflows/build-publish.yml +20 -0
- bazis_async_request-2.3.0/.github/workflows/release.yml +74 -0
- bazis_async_request-2.3.0/.github/workflows/tests.yml +131 -0
- bazis_async_request-2.3.0/CLAUDE.md +64 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/PKG-INFO +28 -20
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/README.md +20 -17
- bazis_async_request-2.3.0/bazis/contrib/async_request/_version.py +24 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/middleware.py +2 -5
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/schemas.py +7 -5
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/tasks.py +38 -23
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/utils.py +21 -13
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/PKG-INFO +28 -20
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/SOURCES.txt +10 -1
- bazis_async_request-2.3.0/bazis_async_request.egg-info/requires.txt +12 -0
- bazis_async_request-2.3.0/bazis_async_request.egg-info/scm_file_list.json +60 -0
- bazis_async_request-2.3.0/bazis_async_request.egg-info/scm_version.json +8 -0
- bazis_async_request-2.3.0/docs/releases/2.3.0.md +52 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/pyproject.toml +8 -2
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/pytest.ini +2 -1
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/routes.py +1 -1
- bazis_async_request-2.3.0/tests/__init__.py +14 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/conftest.py +36 -10
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_decorator.py +12 -8
- bazis_async_request-2.3.0/tests/test_internal.py +119 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_multiple_order_requests.py +2 -2
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_order_access.py +7 -5
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_order_patch.py +12 -9
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_order_view.py +11 -7
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_shop_calc_and_params.py +6 -3
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_shop_force_background_methods.py +2 -1
- bazis_async_request-2.3.0/tests/utils.py +35 -0
- bazis_async_request-2.2.3/.github/workflows/build-publish.yml +0 -16
- bazis_async_request-2.2.3/bazis/contrib/async_request/_version.py +0 -34
- bazis_async_request-2.2.3/bazis_async_request.egg-info/requires.txt +0 -7
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/.dockerignore +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/.github/workflows/update-licenses.yml +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/.gitignore +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/__init__.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/apps.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/dependency_links.txt +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/top_level.txt +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/Dockerfile +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/docker-compose.kafka.yml +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/docker-compose.test.yml +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/__init__.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/admin.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/migrations/0001_initial.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/migrations/0002_remove_order_fast_start__autogen_8f29b0_gin_and_more.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/migrations/__init__.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/models.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/router.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/schemas.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/manage.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/project.env +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/pyproject.toml +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/__init__.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/main.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/router.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/settings.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/urls.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/wsgi.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/test.env +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/__init__.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/admin.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/migrations/0001_initial.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/migrations/__init__.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/models.py +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/uv.lock +0 -0
- {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Build and Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*'
|
|
7
|
+
# started by the Release workflow on the tag it creates: a tag pushed with the
|
|
8
|
+
# workflow token does not trigger the push event above
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
17
|
+
uses: ecofuture-tech/.github/.github/workflows/reusable-build-publish.yml@main
|
|
18
|
+
secrets:
|
|
19
|
+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
20
|
+
TEST_PYPI_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Creates the release tag vX.Y.Z on the current main commit and starts Build and Publish
|
|
4
|
+
# for it. Run it from the Actions tab (or through the GitHub API) on the main branch.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
version:
|
|
10
|
+
description: 'Version without the "v" prefix, e.g. 2.3.0 or 2.4.0-rc1'
|
|
11
|
+
required: true
|
|
12
|
+
type: string
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
actions: write
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: release
|
|
20
|
+
cancel-in-progress: false
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
release:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
timeout-minutes: 10
|
|
26
|
+
if: github.ref == 'refs/heads/main'
|
|
27
|
+
env:
|
|
28
|
+
GH_TOKEN: ${{ github.token }}
|
|
29
|
+
VERSION: ${{ inputs.version }}
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
with:
|
|
33
|
+
fetch-depth: 0
|
|
34
|
+
|
|
35
|
+
- name: Validate the version
|
|
36
|
+
run: |
|
|
37
|
+
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)[0-9]+)?$ ]]; then
|
|
38
|
+
echo "::error::Invalid version '$VERSION', expected e.g. 2.3.0 or 2.4.0-rc1"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then
|
|
42
|
+
echo "::error::Tag v$VERSION already exists"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
LAST_TAG=$(git tag --list 'v*' --sort=-v:refname | head -n 1)
|
|
46
|
+
echo "Last release: ${LAST_TAG:-none}, new release: v$VERSION"
|
|
47
|
+
if [ -n "$LAST_TAG" ] && [ "$(printf '%s\n%s\n' "${LAST_TAG#v}" "$VERSION" | sort -V | tail -n 1)" != "$VERSION" ]; then
|
|
48
|
+
echo "::error::Version $VERSION is not newer than $LAST_TAG"
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
- name: Require green tests on this commit
|
|
53
|
+
run: |
|
|
54
|
+
CONCLUSION=$(gh run list --workflow tests.yml --commit "$GITHUB_SHA" --status completed \
|
|
55
|
+
--json conclusion --jq 'map(.conclusion) | first // "none"')
|
|
56
|
+
echo "Tests on $GITHUB_SHA: $CONCLUSION"
|
|
57
|
+
if [ "$CONCLUSION" != "success" ]; then
|
|
58
|
+
echo "::error::The Tests workflow has not succeeded on $GITHUB_SHA ($CONCLUSION)"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
- name: Create the tag
|
|
63
|
+
run: |
|
|
64
|
+
git config user.name 'github-actions[bot]'
|
|
65
|
+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
|
66
|
+
git tag -a "v$VERSION" -m "${{ github.event.repository.name }} $VERSION" "$GITHUB_SHA"
|
|
67
|
+
git push origin "v$VERSION"
|
|
68
|
+
|
|
69
|
+
- name: Start Build and Publish
|
|
70
|
+
run: gh workflow run build-publish.yml --ref "v$VERSION"
|
|
71
|
+
|
|
72
|
+
- name: Summary
|
|
73
|
+
run: |
|
|
74
|
+
echo "Tagged \`v$VERSION\` at \`$GITHUB_SHA\` and started Build and Publish." >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: tests-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
lint:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: astral-sh/setup-uv@v6
|
|
23
|
+
- run: uvx ruff check bazis tests
|
|
24
|
+
|
|
25
|
+
tests:
|
|
26
|
+
name: tests (python ${{ matrix.python }}, ${{ matrix.resolution }})
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
timeout-minutes: 30
|
|
29
|
+
strategy:
|
|
30
|
+
fail-fast: false
|
|
31
|
+
matrix:
|
|
32
|
+
python: ['3.12', '3.13', '3.14']
|
|
33
|
+
resolution: [highest]
|
|
34
|
+
include:
|
|
35
|
+
# the lowest versions allowed by pyproject.toml
|
|
36
|
+
- python: '3.12'
|
|
37
|
+
resolution: lowest-direct
|
|
38
|
+
|
|
39
|
+
services:
|
|
40
|
+
postgres:
|
|
41
|
+
image: postgis/postgis:16-3.4
|
|
42
|
+
env:
|
|
43
|
+
POSTGRES_USER: postgres
|
|
44
|
+
POSTGRES_PASSWORD: postgres
|
|
45
|
+
POSTGRES_DB: bazis
|
|
46
|
+
ports:
|
|
47
|
+
- 5432:5432
|
|
48
|
+
options: >-
|
|
49
|
+
--health-cmd pg_isready
|
|
50
|
+
--health-interval 5s
|
|
51
|
+
--health-timeout 5s
|
|
52
|
+
--health-retries 10
|
|
53
|
+
redis:
|
|
54
|
+
image: redis:7
|
|
55
|
+
ports:
|
|
56
|
+
- 6379:6379
|
|
57
|
+
options: >-
|
|
58
|
+
--health-cmd "redis-cli ping"
|
|
59
|
+
--health-interval 5s
|
|
60
|
+
--health-timeout 5s
|
|
61
|
+
--health-retries 10
|
|
62
|
+
kafka:
|
|
63
|
+
image: apache/kafka:3.9.1
|
|
64
|
+
ports:
|
|
65
|
+
- 9092:9092
|
|
66
|
+
options: >-
|
|
67
|
+
--health-cmd "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092"
|
|
68
|
+
--health-interval 10s
|
|
69
|
+
--health-timeout 10s
|
|
70
|
+
--health-retries 15
|
|
71
|
+
env:
|
|
72
|
+
BS_DEBUG: 'true'
|
|
73
|
+
BS_SECRET_KEY: ci-only-secret-key-that-is-long-enough-0123456789
|
|
74
|
+
BS_DATABASES__DEFAULT__HOST: localhost
|
|
75
|
+
BS_DATABASES__DEFAULT__PORT: '5432'
|
|
76
|
+
BS_DATABASES__DEFAULT__NAME: bazis
|
|
77
|
+
BS_DATABASES__DEFAULT__USER: postgres
|
|
78
|
+
BS_DATABASES__DEFAULT__PASSWORD: postgres
|
|
79
|
+
BS_CACHES__DEFAULT__LOCATION: redis://localhost:6379/1
|
|
80
|
+
BS_MEDIA_ROOT: /tmp/bazis/media
|
|
81
|
+
BS_STATIC_ROOT: /tmp/bazis/static
|
|
82
|
+
BS_WEBAPP_ROOT: /tmp/bazis/webapp
|
|
83
|
+
BS_KAFKA_BOOTSTRAP_SERVERS: localhost:9092
|
|
84
|
+
BS_KAFKA_TOPIC_ASYNC_BG: sample_local_async_request
|
|
85
|
+
BS_KAFKA_GROUP_ID: sample_local
|
|
86
|
+
BS_KAFKA_AUTO_OFFSET_RESET: earliest
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
|
|
90
|
+
- name: Install GDAL (required by the PostGIS database backend)
|
|
91
|
+
# a stalled Ubuntu mirror must fail the step quickly instead of hanging the job
|
|
92
|
+
timeout-minutes: 10
|
|
93
|
+
run: |
|
|
94
|
+
APT="sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30"
|
|
95
|
+
$APT update
|
|
96
|
+
$APT install -y --no-install-recommends gdal-bin
|
|
97
|
+
|
|
98
|
+
- uses: astral-sh/setup-uv@v6
|
|
99
|
+
with:
|
|
100
|
+
python-version: ${{ matrix.python }}
|
|
101
|
+
activate-environment: true
|
|
102
|
+
|
|
103
|
+
- name: Install dependencies
|
|
104
|
+
run: uv pip install --resolution ${{ matrix.resolution }} -e '.[test]'
|
|
105
|
+
|
|
106
|
+
- name: Check for missing migrations
|
|
107
|
+
working-directory: sample
|
|
108
|
+
run: python manage.py makemigrations --check --dry-run
|
|
109
|
+
|
|
110
|
+
- name: Migrate the database shared by the tests and the consumer
|
|
111
|
+
# sample/settings.py sets TEST.NAME to the database name, so that the consumer sees
|
|
112
|
+
# the data of the tests; pytest runs with --reuse-db
|
|
113
|
+
working-directory: sample
|
|
114
|
+
run: |
|
|
115
|
+
python manage.py migrate -v0
|
|
116
|
+
python manage.py pgtrigger install
|
|
117
|
+
|
|
118
|
+
- name: Start a Kafka consumer
|
|
119
|
+
working-directory: sample
|
|
120
|
+
run: |
|
|
121
|
+
nohup python manage.py kafka_consumer_single > "$RUNNER_TEMP/consumer.log" 2>&1 &
|
|
122
|
+
sleep 15
|
|
123
|
+
cat "$RUNNER_TEMP/consumer.log"
|
|
124
|
+
|
|
125
|
+
- name: Run tests
|
|
126
|
+
working-directory: sample
|
|
127
|
+
run: python -m pytest ../tests -o addopts="--reuse-db" -p no:cacheprovider
|
|
128
|
+
|
|
129
|
+
- name: Consumer log
|
|
130
|
+
if: failure()
|
|
131
|
+
run: cat "$RUNNER_TEMP/consumer.log"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# bazis-async-request
|
|
2
|
+
|
|
3
|
+
Background HTTP requests for Bazis: `AsyncRequestMiddleware` puts a request with
|
|
4
|
+
`X-Async-Background: true` into Kafka (bazis-async-background) and answers 202 with the task
|
|
5
|
+
id; the consumer (`tasks.py`) replays the request through the ASGI application, marked as
|
|
6
|
+
internal in the ASGI scope (`SCOPE_INTERNAL_KEY`), and stores the response for
|
|
7
|
+
`GET /async_background_response/{task_id}/`. `require_async` limits an endpoint to such
|
|
8
|
+
requests.
|
|
9
|
+
|
|
10
|
+
Never trust a header to recognize the requests of the consumer: clients can send any header.
|
|
11
|
+
|
|
12
|
+
The package code is in `bazis/contrib/async_request`, the sample project used by the tests is in `sample/`,
|
|
13
|
+
the tests are in `tests/`.
|
|
14
|
+
|
|
15
|
+
## Running the tests
|
|
16
|
+
|
|
17
|
+
The tests need PostgreSQL with PostGIS and Redis and Kafka (see `.github/workflows/tests.yml`).
|
|
18
|
+
Run them from the `sample` directory:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd sample
|
|
22
|
+
BS_DEBUG=true \
|
|
23
|
+
BS_SECRET_KEY=local-secret-key-that-is-long-enough-0123456789 \
|
|
24
|
+
BS_DATABASES__DEFAULT__HOST=localhost BS_DATABASES__DEFAULT__PORT=5432 \
|
|
25
|
+
BS_DATABASES__DEFAULT__NAME=bazis BS_DATABASES__DEFAULT__USER=postgres \
|
|
26
|
+
BS_DATABASES__DEFAULT__PASSWORD=postgres \
|
|
27
|
+
BS_CACHES__DEFAULT__LOCATION=redis://localhost:6379/1 \
|
|
28
|
+
BS_MEDIA_ROOT=/tmp/bazis/media BS_STATIC_ROOT=/tmp/bazis/static BS_WEBAPP_ROOT=/tmp/bazis/webapp \
|
|
29
|
+
BS_KAFKA_BOOTSTRAP_SERVERS=localhost:9092 BS_KAFKA_TOPIC_ASYNC_BG=sample_local_async_request \
|
|
30
|
+
BS_KAFKA_GROUP_ID=sample_local \
|
|
31
|
+
python -m pytest ../tests -o addopts="--reuse-db" -p no:cacheprovider
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The tests and the consumer share one database (`sample/sample/settings.py` sets `TEST.NAME`
|
|
35
|
+
to the database name): migrate it first (`python manage.py migrate` and
|
|
36
|
+
`python manage.py pgtrigger install` in `sample`), start a consumer with the same variables
|
|
37
|
+
(`python manage.py kafka_consumer_single &`) and run pytest with `-o addopts="--reuse-db"`.
|
|
38
|
+
The tests marked `run_with_consumer` are skipped without Kafka settings. A local Kafka
|
|
39
|
+
without Docker: download the Kafka binaries and start a single KRaft node.
|
|
40
|
+
|
|
41
|
+
Lint: `ruff check bazis tests`. CI also runs `python manage.py makemigrations --check
|
|
42
|
+
--dry-run` in `sample`: commit the migrations of model changes, including the sample apps.
|
|
43
|
+
|
|
44
|
+
## Releasing
|
|
45
|
+
|
|
46
|
+
A release is the tag `vX.Y.Z` on `main`: the Build and Publish workflow builds the package
|
|
47
|
+
(the version comes from the tag through setuptools-scm) and publishes it to PyPI
|
|
48
|
+
(pre-releases `-alphaN`/`-betaN`/`-rcN` go to Test PyPI) and creates the GitHub release.
|
|
49
|
+
|
|
50
|
+
Claude Code sessions cannot push tags. Release through the **Release** workflow instead:
|
|
51
|
+
|
|
52
|
+
1. Make sure the changes are merged into `main` and the Tests workflow is green on the
|
|
53
|
+
`main` head commit (the Release workflow checks this and refuses otherwise).
|
|
54
|
+
2. Add the release notes as `docs/releases/X.Y.Z.md` in the change being released.
|
|
55
|
+
3. Start the workflow `release.yml` on `ref: main` with the input `version: X.Y.Z`
|
|
56
|
+
(GitHub API: `POST /repos/ecofuture-tech/bazis-async-request/actions/workflows/release.yml/dispatches`;
|
|
57
|
+
with the GitHub MCP tools: `actions_run_trigger`, method `run_workflow`).
|
|
58
|
+
4. The Release run creates the annotated tag and starts Build and Publish on it. Check
|
|
59
|
+
that both runs succeed and that the version appears on https://pypi.org/project/bazis-async-request/.
|
|
60
|
+
|
|
61
|
+
Release the Bazis packages in dependency order: a package is tested in CI against the
|
|
62
|
+
versions of its Bazis dependencies published on PyPI. Pick the version by semver:
|
|
63
|
+
breaking changes (settings renamed or required, dependency removed, behavior changed)
|
|
64
|
+
bump the minor version while the project is below 3.0.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bazis-async-request
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Async Background Requests module for Bazis framework.
|
|
5
5
|
Author-email: Ilya Kharyn <ilya.tt07@gmail.com>
|
|
6
6
|
Maintainer-email: Ilya Kharyn <ilya.tt07@gmail.com>
|
|
@@ -17,9 +17,14 @@ Classifier: Framework :: Django
|
|
|
17
17
|
Classifier: Framework :: FastAPI
|
|
18
18
|
Requires-Python: >=3.12
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
|
-
Requires-Dist: bazis
|
|
20
|
+
Requires-Dist: bazis>=2.3.1
|
|
21
|
+
Requires-Dist: bazis-async-background>=2.3.0
|
|
21
22
|
Provides-Extra: test
|
|
22
|
-
Requires-Dist: bazis-test-utils>=2.
|
|
23
|
+
Requires-Dist: bazis-test-utils>=2.3.0; extra == "test"
|
|
24
|
+
Requires-Dist: bazis-users>=2.3.0; extra == "test"
|
|
25
|
+
Requires-Dist: bazis-author>=2.3.0; extra == "test"
|
|
26
|
+
Requires-Dist: bazis-permit>=2.3.0; extra == "test"
|
|
27
|
+
Requires-Dist: bazis-ws>=2.3.0; extra == "test"
|
|
23
28
|
Provides-Extra: dev
|
|
24
29
|
Requires-Dist: ruff; extra == "dev"
|
|
25
30
|
|
|
@@ -83,7 +88,7 @@ python manage.py kafka_consumer_multiple --consumers-count=5
|
|
|
83
88
|
- **WebSocket notifications** — automatic user notification about task status
|
|
84
89
|
- **API endpoint** — retrieving results by task_id
|
|
85
90
|
|
|
86
|
-
**How it works**: When sending a request with the `X-Async-
|
|
91
|
+
**How it works**: When sending a request with the `X-Async-Background: true` header, the request is not executed immediately but is placed in the Kafka queue. The consumer retrieves the task from the queue, executes it, saves the result in Redis, and sends a notification to the user via WebSocket.
|
|
87
92
|
|
|
88
93
|
**This package requires the installation of `bazis`, `bazis-users`, `bazis-ws` packages and running Kafka and Redis servers.**
|
|
89
94
|
|
|
@@ -127,7 +132,7 @@ This waits for the pytest container to finish and streams logs only from the Pyt
|
|
|
127
132
|
┌─────────────┐
|
|
128
133
|
│ Client │
|
|
129
134
|
└──────┬──────┘
|
|
130
|
-
│ POST + X-Async-
|
|
135
|
+
│ POST + X-Async-Background: true
|
|
131
136
|
▼
|
|
132
137
|
┌─────────────────────┐
|
|
133
138
|
│ API Endpoint │
|
|
@@ -232,23 +237,25 @@ This adds the endpoint: `GET /api/v1/async_background_response/{task_id}/`
|
|
|
232
237
|
### Project-Level Middleware
|
|
233
238
|
|
|
234
239
|
AsyncRequestMiddleware is registered automatically when `bazis.contrib.async_request` is loaded.
|
|
235
|
-
Any request can be moved to background using the `X-Async-
|
|
240
|
+
Any request can be moved to background using the `X-Async-Background: true` header.
|
|
236
241
|
|
|
237
242
|
**Location**: `bazis.contrib.async_request.middleware.AsyncRequestMiddleware`
|
|
238
243
|
|
|
239
244
|
### Endpoint-Only Async Request (Dependency)
|
|
240
245
|
|
|
241
246
|
Use a dependency to mark specific endpoints as async-only. Such routes will return `409 Conflict`
|
|
242
|
-
unless the request
|
|
243
|
-
`X-Async-
|
|
247
|
+
unless the request is executed in the background (the client sends it with
|
|
248
|
+
`X-Async-Background: true`, and the consumer executes it). The consumer marks its requests
|
|
249
|
+
in the ASGI scope, which clients cannot forge; the former `X-Async-Background-Internal`
|
|
250
|
+
header is ignored.
|
|
244
251
|
|
|
245
|
-
**Location**: `bazis.contrib.async_request.
|
|
252
|
+
**Location**: `bazis.contrib.async_request.utils.require_async`
|
|
246
253
|
|
|
247
254
|
#### Attach to a Single Route
|
|
248
255
|
|
|
249
256
|
```python
|
|
250
257
|
from fastapi import Depends
|
|
251
|
-
from bazis.contrib.async_request.
|
|
258
|
+
from bazis.contrib.async_request.utils import require_async
|
|
252
259
|
|
|
253
260
|
@router.post(
|
|
254
261
|
"/reports/generate/",
|
|
@@ -262,12 +269,12 @@ async def generate_report(...):
|
|
|
262
269
|
|
|
263
270
|
```python
|
|
264
271
|
from fastapi import Depends
|
|
265
|
-
from bazis.contrib.async_request.
|
|
272
|
+
from bazis.contrib.async_request.utils import require_async
|
|
266
273
|
|
|
267
274
|
@router.post("/reports/generate/")
|
|
268
275
|
async def generate_report(
|
|
269
276
|
...,
|
|
270
|
-
|
|
277
|
+
async_request: None = Depends(require_async),
|
|
271
278
|
):
|
|
272
279
|
...
|
|
273
280
|
```
|
|
@@ -297,14 +304,14 @@ Runs 5 consumers in separate processes. Suitable for local development or deploy
|
|
|
297
304
|
|
|
298
305
|
### Sending a Request
|
|
299
306
|
|
|
300
|
-
Add the `X-Async-
|
|
307
|
+
Add the `X-Async-Background: true` header to your request:
|
|
301
308
|
|
|
302
309
|
```bash
|
|
303
310
|
curl -X POST \
|
|
304
311
|
http://localhost/api/v1/orders/order/ \
|
|
305
312
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
306
313
|
-H "Content-Type: application/vnd.api+json" \
|
|
307
|
-
-H "X-Async-
|
|
314
|
+
-H "X-Async-Background: true" \
|
|
308
315
|
-d '{
|
|
309
316
|
"data": {
|
|
310
317
|
"type": "myapp.order",
|
|
@@ -327,11 +334,12 @@ curl -X POST \
|
|
|
327
334
|
}
|
|
328
335
|
```
|
|
329
336
|
|
|
330
|
-
|
|
337
|
+
A client without a user token sends an anonymous token it generated itself (16–128
|
|
338
|
+
characters `A-Z a-z 0-9 _ -`, e.g. a random UUID) as `Authorization: Bearer <token>`; the same
|
|
339
|
+
token subscribes to its WebSocket channel (bazis-ws) and reads the result.
|
|
331
340
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
```
|
|
341
|
+
The request is stored in the Kafka topic with all its headers, `Authorization` included, and
|
|
342
|
+
executed later with them: protect the topic accordingly.
|
|
335
343
|
|
|
336
344
|
Save the `async_request_id` — this is the task identifier for retrieving the result.
|
|
337
345
|
|
|
@@ -503,7 +511,7 @@ class AsyncReportClient {
|
|
|
503
511
|
headers: {
|
|
504
512
|
'Authorization': `Bearer ${this.token}`,
|
|
505
513
|
'Content-Type': 'application/vnd.api+json',
|
|
506
|
-
'X-Async-
|
|
514
|
+
'X-Async-Background': 'true'
|
|
507
515
|
},
|
|
508
516
|
body: JSON.stringify({
|
|
509
517
|
data: {
|
|
@@ -633,7 +641,7 @@ Content-Type: application/json
|
|
|
633
641
|
# Asynchronous request (returns task_id immediately)
|
|
634
642
|
POST /api/v1/generate-analytics/
|
|
635
643
|
Content-Type: application/json
|
|
636
|
-
X-Async-
|
|
644
|
+
X-Async-Background: true
|
|
637
645
|
|
|
638
646
|
{
|
|
639
647
|
"report_type": "sales",
|
|
@@ -58,7 +58,7 @@ python manage.py kafka_consumer_multiple --consumers-count=5
|
|
|
58
58
|
- **WebSocket notifications** — automatic user notification about task status
|
|
59
59
|
- **API endpoint** — retrieving results by task_id
|
|
60
60
|
|
|
61
|
-
**How it works**: When sending a request with the `X-Async-
|
|
61
|
+
**How it works**: When sending a request with the `X-Async-Background: true` header, the request is not executed immediately but is placed in the Kafka queue. The consumer retrieves the task from the queue, executes it, saves the result in Redis, and sends a notification to the user via WebSocket.
|
|
62
62
|
|
|
63
63
|
**This package requires the installation of `bazis`, `bazis-users`, `bazis-ws` packages and running Kafka and Redis servers.**
|
|
64
64
|
|
|
@@ -102,7 +102,7 @@ This waits for the pytest container to finish and streams logs only from the Pyt
|
|
|
102
102
|
┌─────────────┐
|
|
103
103
|
│ Client │
|
|
104
104
|
└──────┬──────┘
|
|
105
|
-
│ POST + X-Async-
|
|
105
|
+
│ POST + X-Async-Background: true
|
|
106
106
|
▼
|
|
107
107
|
┌─────────────────────┐
|
|
108
108
|
│ API Endpoint │
|
|
@@ -207,23 +207,25 @@ This adds the endpoint: `GET /api/v1/async_background_response/{task_id}/`
|
|
|
207
207
|
### Project-Level Middleware
|
|
208
208
|
|
|
209
209
|
AsyncRequestMiddleware is registered automatically when `bazis.contrib.async_request` is loaded.
|
|
210
|
-
Any request can be moved to background using the `X-Async-
|
|
210
|
+
Any request can be moved to background using the `X-Async-Background: true` header.
|
|
211
211
|
|
|
212
212
|
**Location**: `bazis.contrib.async_request.middleware.AsyncRequestMiddleware`
|
|
213
213
|
|
|
214
214
|
### Endpoint-Only Async Request (Dependency)
|
|
215
215
|
|
|
216
216
|
Use a dependency to mark specific endpoints as async-only. Such routes will return `409 Conflict`
|
|
217
|
-
unless the request
|
|
218
|
-
`X-Async-
|
|
217
|
+
unless the request is executed in the background (the client sends it with
|
|
218
|
+
`X-Async-Background: true`, and the consumer executes it). The consumer marks its requests
|
|
219
|
+
in the ASGI scope, which clients cannot forge; the former `X-Async-Background-Internal`
|
|
220
|
+
header is ignored.
|
|
219
221
|
|
|
220
|
-
**Location**: `bazis.contrib.async_request.
|
|
222
|
+
**Location**: `bazis.contrib.async_request.utils.require_async`
|
|
221
223
|
|
|
222
224
|
#### Attach to a Single Route
|
|
223
225
|
|
|
224
226
|
```python
|
|
225
227
|
from fastapi import Depends
|
|
226
|
-
from bazis.contrib.async_request.
|
|
228
|
+
from bazis.contrib.async_request.utils import require_async
|
|
227
229
|
|
|
228
230
|
@router.post(
|
|
229
231
|
"/reports/generate/",
|
|
@@ -237,12 +239,12 @@ async def generate_report(...):
|
|
|
237
239
|
|
|
238
240
|
```python
|
|
239
241
|
from fastapi import Depends
|
|
240
|
-
from bazis.contrib.async_request.
|
|
242
|
+
from bazis.contrib.async_request.utils import require_async
|
|
241
243
|
|
|
242
244
|
@router.post("/reports/generate/")
|
|
243
245
|
async def generate_report(
|
|
244
246
|
...,
|
|
245
|
-
|
|
247
|
+
async_request: None = Depends(require_async),
|
|
246
248
|
):
|
|
247
249
|
...
|
|
248
250
|
```
|
|
@@ -272,14 +274,14 @@ Runs 5 consumers in separate processes. Suitable for local development or deploy
|
|
|
272
274
|
|
|
273
275
|
### Sending a Request
|
|
274
276
|
|
|
275
|
-
Add the `X-Async-
|
|
277
|
+
Add the `X-Async-Background: true` header to your request:
|
|
276
278
|
|
|
277
279
|
```bash
|
|
278
280
|
curl -X POST \
|
|
279
281
|
http://localhost/api/v1/orders/order/ \
|
|
280
282
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
281
283
|
-H "Content-Type: application/vnd.api+json" \
|
|
282
|
-
-H "X-Async-
|
|
284
|
+
-H "X-Async-Background: true" \
|
|
283
285
|
-d '{
|
|
284
286
|
"data": {
|
|
285
287
|
"type": "myapp.order",
|
|
@@ -302,11 +304,12 @@ curl -X POST \
|
|
|
302
304
|
}
|
|
303
305
|
```
|
|
304
306
|
|
|
305
|
-
|
|
307
|
+
A client without a user token sends an anonymous token it generated itself (16–128
|
|
308
|
+
characters `A-Z a-z 0-9 _ -`, e.g. a random UUID) as `Authorization: Bearer <token>`; the same
|
|
309
|
+
token subscribes to its WebSocket channel (bazis-ws) and reads the result.
|
|
306
310
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
```
|
|
311
|
+
The request is stored in the Kafka topic with all its headers, `Authorization` included, and
|
|
312
|
+
executed later with them: protect the topic accordingly.
|
|
310
313
|
|
|
311
314
|
Save the `async_request_id` — this is the task identifier for retrieving the result.
|
|
312
315
|
|
|
@@ -478,7 +481,7 @@ class AsyncReportClient {
|
|
|
478
481
|
headers: {
|
|
479
482
|
'Authorization': `Bearer ${this.token}`,
|
|
480
483
|
'Content-Type': 'application/vnd.api+json',
|
|
481
|
-
'X-Async-
|
|
484
|
+
'X-Async-Background': 'true'
|
|
482
485
|
},
|
|
483
486
|
body: JSON.stringify({
|
|
484
487
|
data: {
|
|
@@ -608,7 +611,7 @@ Content-Type: application/json
|
|
|
608
611
|
# Asynchronous request (returns task_id immediately)
|
|
609
612
|
POST /api/v1/generate-analytics/
|
|
610
613
|
Content-Type: application/json
|
|
611
|
-
X-Async-
|
|
614
|
+
X-Async-Background: true
|
|
612
615
|
|
|
613
616
|
{
|
|
614
617
|
"report_type": "sales",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '2.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 3, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'g7f4029675'
|
{bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/middleware.py
RENAMED
|
@@ -27,7 +27,7 @@ from bazis.contrib.async_background.producer import enqueue_task_async
|
|
|
27
27
|
from bazis.contrib.async_background.routes import get_async_background_response
|
|
28
28
|
from bazis.contrib.async_background.utils import ChannelNameError, resolve_channel_name_async
|
|
29
29
|
|
|
30
|
-
from .utils import build_request_payload
|
|
30
|
+
from .utils import build_request_payload, is_internal_request
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
@@ -63,10 +63,7 @@ class AsyncRequestMiddleware:
|
|
|
63
63
|
return
|
|
64
64
|
|
|
65
65
|
headers = Headers(scope=scope)
|
|
66
|
-
if (
|
|
67
|
-
headers.get("X-Async-Background-Internal", "").lower() == "true"
|
|
68
|
-
or "X-Async-Background" not in headers
|
|
69
|
-
):
|
|
66
|
+
if is_internal_request(scope) or "X-Async-Background" not in headers:
|
|
70
67
|
await self.app(scope, receive, send)
|
|
71
68
|
return
|
|
72
69
|
|
{bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/schemas.py
RENAMED
|
@@ -13,14 +13,18 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
from pydantic import BaseModel, Field
|
|
16
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class AsyncRequestPayload(BaseModel):
|
|
20
20
|
"""Payload of a background HTTP request serialized for Kafka."""
|
|
21
21
|
|
|
22
|
-
path: str = Field(..., description="Request path")
|
|
22
|
+
path: str = Field(..., description="Request path (decoded, as in the ASGI scope)")
|
|
23
|
+
raw_path: str | None = Field(None, description="Request path as received (latin-1)")
|
|
24
|
+
root_path: str = Field("", description="ASGI root path of the application")
|
|
23
25
|
query_string: str = Field(..., description="Query string")
|
|
26
|
+
#: all headers of the request (Authorization included: the request is executed with
|
|
27
|
+
#: the credentials of the client), stored in the Kafka topic
|
|
24
28
|
headers: list[tuple[str | bytes, str | bytes]] = Field(
|
|
25
29
|
..., description="List of HTTP headers"
|
|
26
30
|
) # List of tuples (header_name, header_value)
|
|
@@ -31,6 +35,4 @@ class AsyncRequestPayload(BaseModel):
|
|
|
31
35
|
scheme: str = Field(..., description="Request scheme")
|
|
32
36
|
body: dict | list[dict] = Field(default_factory=dict, description="Request body")
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
json_encoders = {bytes: lambda v: v.decode("utf-8")}
|
|
36
|
-
use_enum_values = True
|
|
38
|
+
model_config = ConfigDict(use_enum_values=True)
|