Bazis Bulk
An extension package for Bazis, providing batch request processing with transaction support and asynchronous execution.
Quick Start
# Install package
uv add bazis-bulk
# Register route
# router.py
from bazis.core.routing import BazisRouter
router = BazisRouter(prefix='/api/v1')
router.register('bazis.contrib.bulk.router')
# Usage example
curl -X POST http://localhost/api/v1/bulk/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '[
{
"endpoint": "/api/v1/entity/parent_entity/",
"method": "POST",
"body": {
"data": {
"type": "entity.parent_entity",
"bs:action": "add",
"attributes": {"name": "Parent 1"}
}
}
},
{
"endpoint": "/api/v1/entity/child_entity/",
"method": "POST",
"body": {
"data": {
"type": "entity.child_entity",
"bs:action": "add",
"attributes": {"child_name": "Child 1"}
}
}
}
]'
Table of Contents
Description
Bazis Bulk is an extension package for the Bazis framework that allows executing multiple API requests in a single HTTP request. The package includes:
- Batch request execution — send multiple operations in one request
- Transactional mode — all operations execute within a single transaction (atomicity)
- Non-transactional mode — operations execute independently
- Support for all HTTP methods — GET, POST, PATCH, PUT, DELETE
- JSON:API support — work with
includedresources and relationships - Dedicated thread for transactions — guaranteed transaction isolation
Typical use cases:
- Creating related entities in one request
- Bulk record updates
- Atomic operations on multiple resources
- Reducing HTTP request count (lower latency)
This package requires the base bazis package to be installed.
Requirements
- Python: 3.12+
- bazis: latest version
- PostgreSQL: 12+
Installation
Using uv (recommended)
uv add bazis-bulk
Using pip
pip install bazis-bulk
Usage
Route Registration
Add the route to your main router.py:
from bazis.core.routing import BazisRouter
router = BazisRouter(prefix='/api/v1')
# Register bulk route
router.register('bazis.contrib.bulk.router')
This creates the endpoint: POST /api/v1/bulk/
Request Format
A bulk request is an array of objects, where each object describes a separate HTTP request.
Single element structure:
{
"endpoint": string, // Endpoint path (required)
"method": string, // GET (default), POST, PUT, PATCH, DELETE, HEAD or OPTIONS
"body": object, // Request body in JSON:API format (optional)
"headers": array // [name, value] pairs added to the headers of the bulk request (optional)
}
Every request receives the headers of the bulk request (e.g. Authorization), except
Content-Length, Content-Type, Transfer-Encoding and Expect, plus its own headers.
A request item cannot set connection headers (Host, Content-Length, Connection, ...)
or the headers of the reverse proxy (Forwarded, X-Forwarded-*, X-Real-IP); header
values must be latin-1 without line breaks. Otherwise the bulk request fails with 422.
A bulk request can contain at most BAZIS_BULK_MAX_ITEMS requests (1000 by default) and
cannot contain bulk requests; otherwise it fails with 400 and the error code ERR_BULK.
A request that fails with an unhandled exception gets the status 500 and does not stop
the following ones.
Example:
[
{
"endpoint": "/api/v1/entity/parent_entity/",
"method": "POST",
"body": {
"data": {
"type": "entity.parent_entity",
"bs:action": "add",
"attributes": {
"name": "New Parent"
}
}
}
}
]
Request Parameters
is_atomic (query parameter)
Defines the request execution mode:
-
is_atomic=true(default) — transactional mode- All operations execute within a single transaction
- Any operation error rolls back the entire transaction
- Response status: 400 if errors occur
-
is_atomic=false— non-transactional mode- Operations execute independently
- Error in one operation doesn't affect others
- Response status: 200 even with errors in individual operations
Examples:
# Transactional mode (default)
POST /api/v1/bulk/
POST /api/v1/bulk/?is_atomic=true
# Non-transactional mode
POST /api/v1/bulk/?is_atomic=false
Response Format
Each response item contains the original endpoint, HTTP status, headers, and the parsed body.
Single element structure:
{
"endpoint": string, // Endpoint path (required)
"status": number, // HTTP status code (required)
"headers": array, // ASGI response headers as [name, value] pairs
"response": object // Parsed JSON for JSON responses, raw body otherwise (may be null)
}
Headers are returned as emitted by the ASGI app. A non-JSON body is returned as text.
Transactional Mode
In transactional mode, all operations execute in a dedicated thread with a single database transaction.
While the bulk request runs, the synchronous code of its operations (endpoints and dependencies,
which FastAPI runs through anyio.to_thread.run_sync) is sent to this thread; other requests
keep using the shared thread pool.
Features:
- All operations either succeed completely or rollback entirely
- Any operation error causes transaction rollback
- Overall response status: 400 if errors occur
Request example:
POST /api/v1/bulk/?is_atomic=true
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
[
{
"endpoint": "/api/v1/orders/order/",
"method": "POST",
"body": {
"data": {
"type": "myapp.order",
"bs:action": "add",
"attributes": {
"description": "Order 1",
"amount": 1000
}
}
}
},
{
"endpoint": "/api/v1/orders/order/",
"method": "POST",
"body": {
"data": {
"type": "myapp.order",
"bs:action": "add",
"attributes": {
"description": "Order 2",
"amount": 2000
}
}
}
}
]
Success response (status 200):
[
{
"endpoint": "/api/v1/orders/order/",
"status": 201,
"headers": [
["content-type", "application/vnd.api+json"]
],
"response": {
"data": {
"type": "myapp.order",
"id": "123e4567-e89b-12d3-a456-426614174000",
"attributes": {
"description": "Order 1",
"amount": 1000
}
}
}
},
{
"endpoint": "/api/v1/orders/order/",
"status": 201,
"response": {
"data": {
"type": "myapp.order",
"id": "987e6543-e21b-32d1-b654-426614174001",
"attributes": {
"description": "Order 2",
"amount": 2000
}
}
}
}
]
Error response (status 400, all operations rolled back):
[
{
"endpoint": "/api/v1/orders/order/",
"status": 201,
"response": {
"data": {
"type": "myapp.order",
"id": "123e4567-e89b-12d3-a456-426614174000",
"attributes": {
"description": "Order 1"
}
}
}
},
{
"endpoint": "/api/v1/orders/order/",
"status": 403,
"response": {
"errors": [
{
"status": 403,
"detail": "Permission denied"
}
]
}
}
]
Non-transactional Mode
In non-transactional mode, each operation executes independently in a thread pool.
Features:
- Operations execute independently
- Error in one operation doesn't affect others
- Overall response status: always 200
Request example:
POST /api/v1/bulk/?is_atomic=false
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
[
{
"endpoint": "/api/v1/orders/order/",
"method": "POST",
"body": {
"data": {
"type": "myapp.order",
"bs:action": "add",
"attributes": {"description": "Order 1"}
}
}
},
{
"endpoint": "/api/v1/orders/order/999/",
"method": "DELETE",
"body": {}
}
]
Response (status 200, even with errors):
[
{
"endpoint": "/api/v1/orders/order/",
"status": 201,
"response": {
"data": {
"type": "myapp.order",
"id": "123e4567-e89b-12d3-a456-426614174000"
}
}
},
{
"endpoint": "/api/v1/orders/order/999/",
"status": 404,
"response": {
"errors": [
{
"status": 404,
"detail": "Not found"
}
]
}
}
]
Examples
Example 1: Creating Related Entities
Creating a parent entity and two child entities in one transaction:
[
{
"endpoint": "/api/v1/entity/parent_entity/",
"method": "POST",
"body": {
"data": {
"type": "entity.parent_entity",
"bs:action": "add",
"attributes": {
"name": "Parent Entity"
}
}
}
},
{
"endpoint": "/api/v1/entity/child_entity/",
"method": "POST",
"body": {
"data": {
"type": "entity.child_entity",
"bs:action": "add",
"attributes": {
"child_name": "Child 1"
}
}
}
},
{
"endpoint": "/api/v1/entity/child_entity/",
"method": "POST",
"body": {
"data": {
"type": "entity.child_entity",
"bs:action": "add",
"attributes": {
"child_name": "Child 2"
}
}
}
}
]
Example 2: Update with Included Resources
Updating a parent entity and its related children:
[
{
"endpoint": "/api/v1/entity/parent_entity/123/?include=extended_entity,dependent_entities",
"method": "PATCH",
"body": {
"data": {
"id": "123",
"type": "entity.parent_entity",
"bs:action": "change",
"attributes": {
"name": "Updated Parent"
}
},
"included": [
{
"id": "456",
"type": "entity.extended_entity",
"bs:action": "change",
"attributes": {
"extended_name": "Updated Extended"
}
},
{
"type": "entity.dependent_entity",
"bs:action": "add",
"attributes": {
"dependent_name": "New Dependent"
},
"relationships": {
"parent_entity": {
"data": {
"id": "123",
"type": "entity.parent_entity"
}
}
}
}
]
}
}
]
Example 3: Bulk Update
Updating multiple records simultaneously:
[
{
"endpoint": "/api/v1/entity/child_entity/child-1/",
"method": "PATCH",
"body": {
"data": {
"id": "child-1",
"type": "entity.child_entity",
"bs:action": "change",
"attributes": {
"child_name": "Updated Child 1"
}
}
}
},
{
"endpoint": "/api/v1/entity/child_entity/child-2/",
"method": "PATCH",
"body": {
"data": {
"id": "child-2",
"type": "entity.child_entity",
"bs:action": "change",
"attributes": {
"child_name": "Updated Child 2"
}
}
}
},
{
"endpoint": "/api/v1/entity/child_entity/child-3/",
"method": "PATCH",
"body": {
"data": {
"id": "child-3",
"type": "entity.child_entity",
"bs:action": "change",
"attributes": {
"child_name": "Updated Child 3"
}
}
}
}
]
Example 4: Mixed Operations
Create, update, and delete in one request:
[
{
"endpoint": "/api/v1/entity/parent_entity/",
"method": "POST",
"body": {
"data": {
"type": "entity.parent_entity",
"bs:action": "add",
"attributes": {"name": "New Parent"}
}
}
},
{
"endpoint": "/api/v1/entity/parent_entity/existing-id/",
"method": "PATCH",
"body": {
"data": {
"id": "existing-id",
"type": "entity.parent_entity",
"bs:action": "change",
"attributes": {"price": "845.42"}
}
}
},
{
"endpoint": "/api/v1/entity/child_entity/old-id/",
"method": "DELETE",
"body": {}
}
]
Example 5: JavaScript Client
class BulkClient {
constructor(apiUrl, token) {
this.apiUrl = apiUrl;
this.token = token;
}
async executeBulk(operations, isAtomic = true) {
const url = `${this.apiUrl}/bulk/?is_atomic=${isAtomic}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(operations)
});
if (!response.ok) {
throw new Error(`Bulk request failed: ${response.status}`);
}
return await response.json();
}
async createMultiple(entities, isAtomic = true) {
const operations = entities.map(entity => ({
endpoint: entity.endpoint,
method: 'POST',
body: {
data: {
type: entity.type,
'bs:action': 'add',
attributes: entity.attributes,
relationships: entity.relationships
}
}
}));
return await this.executeBulk(operations, isAtomic);
}
async updateMultiple(updates, isAtomic = true) {
const operations = updates.map(update => ({
endpoint: `${update.endpoint}/${update.id}/`,
method: 'PATCH',
body: {
data: {
id: update.id,
type: update.type,
'bs:action': 'change',
attributes: update.attributes
}
}
}));
return await this.executeBulk(operations, isAtomic);
}
}
// Usage
const bulk = new BulkClient('http://api.example.com/api/v1', jwtToken);
// Create multiple entities atomically
const results = await bulk.createMultiple([
{
endpoint: '/api/v1/orders/order',
type: 'myapp.order',
attributes: { description: 'Order 1', amount: 1000 }
},
{
endpoint: '/api/v1/orders/order',
type: 'myapp.order',
attributes: { description: 'Order 2', amount: 2000 }
}
], true);
console.log('Created:', results);
// Bulk update without transaction
await bulk.updateMultiple([
{
endpoint: '/api/v1/orders/order',
id: 'order-1',
type: 'myapp.order',
attributes: { status: 'completed' }
},
{
endpoint: '/api/v1/orders/order',
id: 'order-2',
type: 'myapp.order',
attributes: { status: 'completed' }
}
], false);
Example 6: Python Client
import requests
from typing import List, Dict, Any
class BulkClient:
def __init__(self, api_url: str, token: str):
self.api_url = api_url
self.token = token
self.headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
def execute_bulk(
self,
operations: List[Dict[str, Any]],
is_atomic: bool = True
) -> List[Dict[str, Any]]:
"""Execute bulk request"""
url = f"{self.api_url}/bulk/?is_atomic={str(is_atomic).lower()}"
response = requests.post(
url,
headers=self.headers,
json=operations
)
response.raise_for_status()
return response.json()
def create_with_related(
self,
parent_data: Dict[str, Any],
children_data: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""Create parent entity with children in one transaction"""
operations = [
{
'endpoint': parent_data['endpoint'],
'method': 'POST',
'body': {
'data': {
'type': parent_data['type'],
'bs:action': 'add',
'attributes': parent_data['attributes']
}
}
}
]
for child in children_data:
operations.append({
'endpoint': child['endpoint'],
'method': 'POST',
'body': {
'data': {
'type': child['type'],
'bs:action': 'add',
'attributes': child['attributes'],
'relationships': child.get('relationships', {})
}
}
})
return self.execute_bulk(operations, is_atomic=True)
# Usage
bulk = BulkClient('http://api.example.com/api/v1', jwt_token)
# Create order with items
results = bulk.create_with_related(
parent_data={
'endpoint': '/api/v1/orders/order',
'type': 'myapp.order',
'attributes': {
'description': 'New Order',
'customer': 'John Doe'
}
},
children_data=[
{
'endpoint': '/api/v1/orders/orderitem',
'type': 'myapp.orderitem',
'attributes': {
'product': 'Product 1',
'quantity': 2,
'price': '100.00'
}
},
{
'endpoint': '/api/v1/orders/orderitem',
'type': 'myapp.orderitem',
'attributes': {
'product': 'Product 2',
'quantity': 1,
'price': '50.00'
}
}
]
)
print(f"Created order with {len(results) - 1} items")
License
Apache License 2.0
See LICENSE file for details.
Links
- Bazis Documentation — main repository
- Bazis Bulk Repository — package repository
- Issue Tracker — report bugs or request features
Support
If you have questions or issues:
- Review the Bazis documentation
- Search through existing issues
- Create a new issue with detailed information
Made with ❤️ by the Bazis team
Metadata
Release files for bazis-bulk 2.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| bazis_bulk-2.3.1.tar.gz | 86.6 kB | Without provenance | Details for bazis_bulk-2.3.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bazis_bulk-2.3.1-py3-none-any.whl | Python 3 | none | any | Details for bazis_bulk-2.3.1-py3-none-any.whl |
Total release size: 104.6 kB
Release files / bazis_bulk-2.3.1.tar.gz
| Download URL | bazis_bulk-2.3.1.tar.gz |
|---|---|
| Size | 86.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6ade1a418f6fe6155deb6c88c85eb87f31e21c907a32270f83b962c0ee7f0f15
|
|
BLAKE2b-256 checksum How to use checksums |
03793c3a4657aaf8c958a3394645d82c06d6ef8c437ed491aad52595731f9ce6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / bazis_bulk-2.3.1-py3-none-any.whl
| Download URL | bazis_bulk-2.3.1-py3-none-any.whl |
|---|---|
| Size | 18.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
538b106093bed2f3b82a538ea6317f1c63733574722b2feedcb7ec6ee96ce33e
|
|
BLAKE2b-256 checksum How to use checksums |
f2190a45bb52e39ee3cb367377dd9a59b88396f1fa299fe7f44cbe6e05f0b5a7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for bazis-bulk 2.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.