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.
Files changed (69) hide show
  1. bazis_async_request-2.3.0/.github/workflows/build-publish.yml +20 -0
  2. bazis_async_request-2.3.0/.github/workflows/release.yml +74 -0
  3. bazis_async_request-2.3.0/.github/workflows/tests.yml +131 -0
  4. bazis_async_request-2.3.0/CLAUDE.md +64 -0
  5. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/PKG-INFO +28 -20
  6. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/README.md +20 -17
  7. bazis_async_request-2.3.0/bazis/contrib/async_request/_version.py +24 -0
  8. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/middleware.py +2 -5
  9. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/schemas.py +7 -5
  10. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/tasks.py +38 -23
  11. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/utils.py +21 -13
  12. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/PKG-INFO +28 -20
  13. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/SOURCES.txt +10 -1
  14. bazis_async_request-2.3.0/bazis_async_request.egg-info/requires.txt +12 -0
  15. bazis_async_request-2.3.0/bazis_async_request.egg-info/scm_file_list.json +60 -0
  16. bazis_async_request-2.3.0/bazis_async_request.egg-info/scm_version.json +8 -0
  17. bazis_async_request-2.3.0/docs/releases/2.3.0.md +52 -0
  18. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/pyproject.toml +8 -2
  19. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/pytest.ini +2 -1
  20. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/routes.py +1 -1
  21. bazis_async_request-2.3.0/tests/__init__.py +14 -0
  22. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/conftest.py +36 -10
  23. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_decorator.py +12 -8
  24. bazis_async_request-2.3.0/tests/test_internal.py +119 -0
  25. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_multiple_order_requests.py +2 -2
  26. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_order_access.py +7 -5
  27. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_order_patch.py +12 -9
  28. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_order_view.py +11 -7
  29. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_shop_calc_and_params.py +6 -3
  30. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/tests/test_shop_force_background_methods.py +2 -1
  31. bazis_async_request-2.3.0/tests/utils.py +35 -0
  32. bazis_async_request-2.2.3/.github/workflows/build-publish.yml +0 -16
  33. bazis_async_request-2.2.3/bazis/contrib/async_request/_version.py +0 -34
  34. bazis_async_request-2.2.3/bazis_async_request.egg-info/requires.txt +0 -7
  35. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/.dockerignore +0 -0
  36. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/.github/workflows/update-licenses.yml +0 -0
  37. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/.gitignore +0 -0
  38. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/__init__.py +0 -0
  39. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis/contrib/async_request/apps.py +0 -0
  40. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/dependency_links.txt +0 -0
  41. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/bazis_async_request.egg-info/top_level.txt +0 -0
  42. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/Dockerfile +0 -0
  43. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/docker-compose.kafka.yml +0 -0
  44. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/docker-compose.test.yml +0 -0
  45. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/__init__.py +0 -0
  46. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/admin.py +0 -0
  47. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/migrations/0001_initial.py +0 -0
  48. {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
  49. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/migrations/__init__.py +0 -0
  50. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/models.py +0 -0
  51. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/router.py +0 -0
  52. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/fast_start/schemas.py +0 -0
  53. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/manage.py +0 -0
  54. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/project.env +0 -0
  55. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/pyproject.toml +0 -0
  56. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/__init__.py +0 -0
  57. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/main.py +0 -0
  58. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/router.py +0 -0
  59. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/settings.py +0 -0
  60. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/urls.py +0 -0
  61. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/sample/wsgi.py +0 -0
  62. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/test.env +0 -0
  63. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/__init__.py +0 -0
  64. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/admin.py +0 -0
  65. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/migrations/0001_initial.py +0 -0
  66. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/migrations/__init__.py +0 -0
  67. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/users/models.py +0 -0
  68. {bazis_async_request-2.2.3 → bazis_async_request-2.3.0}/sample/uv.lock +0 -0
  69. {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.2.3
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-async-background
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.2.0; extra == "test"
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-Request: 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.
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-Request: true
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-Request: true` header.
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 includes `X-Async-Request` (or an internal background call with
243
- `X-Async-Request-Internal: true`).
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.dependencies.require_async`
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.dependencies import require_async
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.dependencies import require_async
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
- _async_request: None = Depends(require_async),
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-Request: true` header to your request:
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-Request: true" \
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
- If the request has no `Authorization` header, pass a channel name directly:
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
- ```bash
333
- X-Async-Request: <channel_name>
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-Request': 'true'
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-Request: true
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-Request: 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.
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-Request: true
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-Request: true` header.
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 includes `X-Async-Request` (or an internal background call with
218
- `X-Async-Request-Internal: true`).
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.dependencies.require_async`
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.dependencies import require_async
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.dependencies import require_async
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
- _async_request: None = Depends(require_async),
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-Request: true` header to your request:
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-Request: true" \
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
- If the request has no `Authorization` header, pass a channel name directly:
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
- ```bash
308
- X-Async-Request: <channel_name>
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-Request': 'true'
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-Request: true
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'
@@ -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
 
@@ -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
- class Config:
35
- json_encoders = {bytes: lambda v: v.decode("utf-8")}
36
- use_enum_values = True
38
+ model_config = ConfigDict(use_enum_values=True)