Official Python SDK for the Distru API - Cannabis supply chain management
Project description
Distru Python SDK
Official Python SDK for the Distru API - Cannabis supply chain management platform.
Installation
pip install distru-sdk
Quick Start
from distru_sdk import DistruClient
# Initialize the client with your API token
client = DistruClient(api_token="your_api_token_here")
# List products
products = client.products.list()
for product in products.auto_paginate():
print(f"{product['name']} - ${product['sale_price']}")
# Create an order
order = client.orders.create(
company_relationship_id=123,
order_date="2025-10-06T12:00:00Z",
order_items=[
{
"product_id": "prod-uuid-123",
"quantity": 10,
"unit_price": "15.00"
}
]
)
print(f"Order created: {order['order_number']}")
Authentication
The Distru API uses JWT Bearer token authentication. To get an API token:
- Log into your Distru account
- Navigate to Settings → API Keys
- Click Create API Key
- Copy the generated token
- Store it securely (never commit to source control!)
# Initialize client
client = DistruClient(api_token="your_api_token_here")
# Optional: Configure timeout and retries
client = DistruClient(
api_token="your_api_token_here",
timeout=60.0, # Request timeout in seconds
max_retries=5 # Maximum retry attempts
)
Usage
Products
# List all products
products = client.products.list()
# Auto-paginate through all results
for product in products.auto_paginate():
print(product['name'])
# Search products
products = client.products.list(search="Blue Dream")
# Get a specific product
product = client.products.get("prod-uuid-123")
# Create a product
product = client.products.create(
name="Blue Dream 1g",
sku="BD-1G",
unit_type_id=1,
inventory_tracking_method="BATCH",
sale_price="15.00",
wholesale_price="10.00"
)
# Update a product
product = client.products.update(
"prod-uuid-123",
sale_price="17.00",
wholesale_price="11.00"
)
# Delete a product
client.products.delete("prod-uuid-123")
Orders
# List orders
orders = client.orders.list()
# Filter orders
orders = client.orders.list(
status="Submitted",
customer_id=123,
from_date="2025-01-01",
to_date="2025-12-31"
)
# Get a specific order
order = client.orders.get("order-uuid-123")
# Create an order
order = client.orders.create(
company_relationship_id=123,
order_date="2025-10-06T12:00:00Z",
due_date="2025-10-20T12:00:00Z",
order_items=[
{
"product_id": "prod-uuid-1",
"quantity": 10,
"unit_price": "15.00"
},
{
"product_id": "prod-uuid-2",
"quantity": 5,
"unit_price": "25.00"
}
]
)
Invoices
# List invoices
invoices = client.invoices.list()
# Filter invoices
invoices = client.invoices.list(
status="Not Paid",
customer_id=123
)
# Get a specific invoice
invoice = client.invoices.get(456)
# Create an invoice
invoice = client.invoices.create(
order_id="order-uuid-123",
invoice_date="2025-10-06T12:00:00Z",
due_date="2025-10-20T12:00:00Z",
invoice_items=[
{
"order_item_id": "order-item-uuid-1",
"quantity": 10
}
]
)
# Add a payment to an invoice
payment = client.invoices.add_payment(
456,
amount="150.00",
payment_date="2025-10-06T12:00:00Z",
payment_method_id=1
)
Inventory
# Get current inventory
inventory = client.inventory.list()
# Include cost information
inventory = client.inventory.list(include_costs=True)
# Filter by location
inventory = client.inventory.list(location_id=1)
# Filter by product
inventory = client.inventory.list(product_id="prod-uuid-123")
Companies (Customers/Vendors)
# List all companies
companies = client.companies.list()
# Search companies
companies = client.companies.list(search="Acme")
# Filter by state
companies = client.companies.list(us_state="CA")
# Get a specific company
company = client.companies.get(123)
Batches
# List batches
batches = client.batches.list()
# Filter by product
batches = client.batches.list(product_id="prod-uuid-123")
# Create a batch
batch = client.batches.create(
product_id="prod-uuid-123",
batch_number="BATCH-001",
harvest_date="2025-09-01T00:00:00Z",
expiration_date="2026-09-01T00:00:00Z"
)
Purchases
# List purchases
purchases = client.purchases.list()
# Create a purchase order
purchase = client.purchases.create(
company_relationship_id=456,
purchase_date="2025-10-06T12:00:00Z",
purchase_items=[
{
"product_id": "prod-uuid-1",
"quantity": 100,
"unit_cost": "8.00"
}
]
)
# Add a payment
payment = client.purchases.add_payment(
789,
amount="800.00",
payment_date="2025-10-06T12:00:00Z"
)
Pagination
All list endpoints return paginated results. The SDK provides helper methods for easy pagination:
Auto-Pagination
# Automatically fetch all pages
products = client.products.list()
for product in products.auto_paginate():
print(product['name'])
Manual Pagination
# Iterate page by page
response = client.products.list()
for page in response.iter_pages():
print(f"Processing page with {len(page)} items")
for product in page:
print(product['name'])
Page-by-Page
# Fetch specific pages
page_1 = client.products.list(page=1, limit=100)
page_2 = client.products.list(page=2, limit=100)
Error Handling
The SDK provides specific exception classes for different error types:
from distru_sdk.exceptions import (
DistruAPIError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ValidationError,
RateLimitError,
ServerError,
NetworkError,
TimeoutError,
)
try:
order = client.orders.get("invalid-uuid")
except NotFoundError as e:
print(f"Order not found: {e.message}")
except AuthenticationError as e:
print(f"Invalid API token: {e.message}")
except RateLimitError as e:
print(f"Rate limited, retry after {e.retry_after} seconds")
except ValidationError as e:
print(f"Validation error: {e.message}")
print(f"Details: {e.details}")
except ServerError as e:
print(f"Server error: {e.message}")
except NetworkError as e:
print(f"Network error: {e.message}")
except TimeoutError as e:
print(f"Request timed out: {e.message}")
except DistruAPIError as e:
print(f"API error: {e.message}")
Webhooks (Beta)
Handle webhook events from Distru:
from distru_sdk.webhooks import WebhookHandler
# Create webhook handler
handler = WebhookHandler()
# Register event handlers
@handler.on('ORDER')
def handle_order_event(event):
if not event.before_changes: # New order
print(f"New order: {event.after_changes['order_number']}")
elif not event.after_changes: # Deleted order
print(f"Order deleted: {event.before_changes['order_number']}")
else: # Updated order
print(f"Order updated: {event.after_changes['order_number']}")
@handler.on('INVOICE')
def handle_invoice_event(event):
print(f"Invoice event: {event.type}")
# In your web framework (Flask, FastAPI, Django, etc.)
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhooks', methods=['POST'])
def webhook_endpoint():
payload = request.get_json()
handler.process(payload)
return {'status': 'ok'}
Data Consistency
The Distru API uses eventual consistency. Changes may take up to 1 second to propagate to GET endpoints.
Best Practices:
- Use the returned data from create/update operations immediately
- For critical operations, add a small delay before fetching updated data
- Implement idempotency using unique identifiers
import time
# Create an order
order = client.orders.create(...)
# The response contains the created order immediately
print(order['order_number']) # Available immediately
# If you need to fetch it again, wait briefly
time.sleep(1.5)
order = client.orders.get(order['id']) # Now guaranteed to be available
Rate Limiting
The SDK automatically handles rate limiting with exponential backoff:
- Automatic retry on 429 (rate limit) and 5xx errors
- Exponential backoff: 1s, 2s, 4s, 8s, 10s (max)
- Maximum 3 retries by default (configurable)
- Respects
Retry-Afterheaders
# Configure retry behavior
client = DistruClient(
api_token="your_token",
max_retries=5, # Increase max retries
timeout=60.0 # Increase timeout
)
Advanced Features
Async Client (New!)
For async/await support using httpx async:
import asyncio
from distru_sdk import AsyncDisruClient
async def main():
async with AsyncDisruClient(api_token="your_token") as client:
# Async requests
response = await client.request("GET", "/products")
data = response.json()
# Process data
for product in data.get("data", []):
print(product["name"])
asyncio.run(main())
Caching (New!)
Response caching to reduce API calls:
from distru_sdk import DistruClient, InMemoryCache, ResponseCache
# Create cache backend
cache_backend = InMemoryCache(default_ttl=300, max_size=1000)
response_cache = ResponseCache(backend=cache_backend)
# Cache GET requests automatically
# Note: Extend client to use response_cache for production
Batch Operations (New!)
Efficiently process multiple items in batches:
from distru_sdk import DistruClient, BatchOperations
client = DistruClient(api_token="your_token")
batch_ops = BatchOperations(client)
# Create multiple products
products = [
{"name": "Product 1", "sku": "SKU-001"},
{"name": "Product 2", "sku": "SKU-002"},
{"name": "Product 3", "sku": "SKU-003"},
]
results = batch_ops.create_multiple(
client.products,
products,
batch_size=10,
raise_on_error=False
)
# Update multiple items
updates = [
{"id": "prod-1", "name": "Updated 1"},
{"id": "prod-2", "name": "Updated 2"},
]
results = batch_ops.update_multiple(
client.products,
updates,
batch_size=10
)
# Delete multiple items
ids = ["prod-1", "prod-2", "prod-3"]
results = batch_ops.delete_multiple(client.products, ids)
Custom Retry Strategies (New!)
Configure retry behavior with custom strategies:
from distru_sdk.retry import ExponentialBackoff, LinearBackoff, FixedDelay, CustomRetry
# Exponential backoff (default)
strategy = ExponentialBackoff(
max_retries=5,
base_delay=1.0,
multiplier=2.0,
max_delay=30.0,
jitter=True
)
# Linear backoff
strategy = LinearBackoff(
max_retries=3,
base_delay=1.0,
increment=1.0
)
# Fixed delay
strategy = FixedDelay(
max_retries=3,
delay=2.0
)
# Use with decorator
@CustomRetry(strategy=strategy)
def fetch_products():
return client.products.list()
products = fetch_products()
Request/Response Logging (New!)
Debug and monitor API calls:
import logging
from distru_sdk.logging import RequestLogger, configure_logging
# Configure SDK logging
configure_logging(
level=logging.DEBUG,
log_file="distru_sdk.log"
)
# Or use custom logger
logger = RequestLogger(
level=logging.DEBUG,
log_request_body=True,
log_response_body=True,
max_body_length=1000
)
# Logs show:
# 2025-10-06 12:00:00 - distru_sdk - DEBUG - GET /products | params={"page": 1}
# 2025-10-06 12:00:01 - distru_sdk - DEBUG - 200 GET /products | duration=0.523s
Bulk Iterator (New!)
Process paginated results in controlled batches:
from distru_sdk.batch import BulkIterator
response = client.products.list()
# Process in batches of 50
for batch in BulkIterator(response.auto_paginate(), batch_size=50):
# Process batch (e.g., bulk insert to database)
print(f"Processing {len(batch)} items")
for item in batch:
# Process item
pass
Context Manager
# Automatically close connection
with DistruClient(api_token="your_token") as client:
products = client.products.list()
for product in products.auto_paginate():
print(product['name'])
# Connection closed automatically
Custom HTTP Client
import httpx
# Use custom httpx client
http_client = httpx.Client(
timeout=60.0,
limits=httpx.Limits(max_keepalive_connections=5)
)
client = DistruClient(
api_token="your_token",
http_client=http_client
)
Framework Integrations
Django
See examples/django_integration.py for full Django integration including:
- Custom cache backend using Django cache
- Error handling middleware
- Management commands
- View examples
Flask
See examples/flask_integration.py for Flask integration with:
- Request context client management
- Error handling decorator
- Flask-Caching integration
- CLI commands
FastAPI
See examples/fastapi_integration.py for async FastAPI integration featuring:
- Async client with lifespan management
- Pydantic models
- Dependency injection
- Background tasks
- WebSocket support
Requirements
- Python 3.8+
- httpx >= 0.25.0
- pydantic >= 2.0.0
Development
Setup
# Clone repository
git clone https://github.com/DistruApp/distru-api-sdk.git
cd distru-api-sdk/python
# Install in development mode
pip install -e ".[dev]"
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=distru_sdk --cov-report=html
# Run specific test file
pytest tests/test_products.py
# Run async tests
pytest tests/test_async_client.py
# Run with verbose output
pytest -v
The test suite includes:
- 128 tests covering all features
- 64% overall coverage (higher for new features)
- Unit tests for all advanced features (caching, batch ops, retry, logging, async)
- Integration test examples
Code Quality
# Format code
black distru_sdk tests
# Sort imports
isort distru_sdk tests
# Type checking
mypy distru_sdk
# Linting
flake8 distru_sdk tests
Support
- Documentation: GitHub Repository
- Issues: GitHub Issues
- Email: support@distru.com
License
MIT License - see LICENSE file for details.
Changelog
See CHANGELOG.md for version history and changes.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file distru_sdk-1.0.2.tar.gz.
File metadata
- Download URL: distru_sdk-1.0.2.tar.gz
- Upload date:
- Size: 52.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3f7052c8bf9a539d80459fa00ed2c22928a4794330c8ba4dd083f3c46f0d7b4
|
|
| MD5 |
c16f18b78e748e1aa7efee4068d06f8f
|
|
| BLAKE2b-256 |
cf8ef27d232203e36f4554cae294fa460d0ff3d7b40f2e418d8ad760b47977d3
|
File details
Details for the file distru_sdk-1.0.2-py3-none-any.whl.
File metadata
- Download URL: distru_sdk-1.0.2-py3-none-any.whl
- Upload date:
- Size: 46.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ada08cc6709ac74eefa4280558d6484a3519df38b3a392959fa6ad0dc4a01036
|
|
| MD5 |
3218ea37a52341f0c2a74f69ecefa12f
|
|
| BLAKE2b-256 |
40edfa026b89d3139a6c0007b6332b13dd5f12b6ce09ae5571a52cf191173c9a
|