A Python client for interacting with Mercately WhatsApp service from Django applications
Project description
Mercately WhatsApp Client
A Python client library for integrating Django applications with the Mercately WhatsApp service. This package provides both synchronous and asynchronous clients for easy WhatsApp template message sending.
Features
- 🚀 Easy Integration: Simple API for sending WhatsApp messages
- 🔄 Sync & Async: Both synchronous and asynchronous clients
- 📱 Template Messages: Send WhatsApp template messages with parameters
- 👥 Customer Management: Create and manage customers
- 🔒 Error Handling: Comprehensive exception hierarchy
- 📊 Logging: Structured logging with privacy protection
- 🔧 Auto-Configuration: Automatic discovery from Django settings
Installation
pip install mb-mercately-client
Quick Start
1. Configuration
Add to your Django settings.py:
MERCATELY_CLIENT = {
'SERVICE_URL': 'https://your-mercately-service-url',
'AUTH_TOKEN': 'your-auth-token',
'TIMEOUT': 30,
'RETRY_ATTEMPTS': 3,
'RETRY_DELAY': 1,
}
Or use environment variables:
export MERCATELY_SERVICE_URL="https://your-mercately-service-url"
export MERCATELY_SERVICE_TOKEN="your-auth-token"
2. Basic Usage
from mb_mercately_client import MercatelyClient
# Create client (auto-discovers configuration)
client = MercatelyClient()
# Send a template message
response = client.send_template(
phone="+1234567890",
template_id="welcome_template",
template_params=["John", "Doe"]
)
# Get message identifier (falls back to created_time if message_id is null)
message_id = response.get_message_identifier()
print(f"Message sent! ID: {message_id}")
print(f"Status: {response.get_message_status()}")
print(f"Destination: {response.get_destination()}")
3. Async Usage
from mercately_client import AsyncMercatelyClient
async def send_message():
async with AsyncMercatelyClient() as client:
response = await client.send_template(
phone="+1234567890",
template_id="welcome_template",
template_params=["John", "Doe"]
)
print(f"Message sent! ID: {response.message_id}")
Django Integration Examples
Synchronous View
from django.http import JsonResponse
from mb_mercately_client import MercatelyClient
from mb_mercately_client.exceptions import MercatelyClientError
def send_welcome(request):
try:
client = MercatelyClient()
response = client.send_template(
phone=request.POST['phone'],
template_id='welcome_template',
template_params=[request.user.first_name]
)
return JsonResponse({'message_id': response.message_id})
except MercatelyClientError as e:
return JsonResponse({'error': str(e)}, status=500)
API Reference
MercatelyClient
The main synchronous client for the Mercately API.
Methods
send_template(phone, template_id, template_params=None, auto_create_customer=True)
Send a WhatsApp template message.
Parameters:
phone(str): Phone number with country code (e.g., '+1234567890')template_id(str): Template identifiertemplate_params(List[str], optional): Template parameters in orderauto_create_customer(bool): Whether to create customer if not exists
Returns: TemplateResponse with message details
Raises:
InvalidPhoneNumberError: If phone number format is invalidTemplateNotFoundError: If template is not foundMercatelyAPIError: If API request fails
get_templates()
Get available WhatsApp templates.
Returns: List of Template objects
create_customer(phone, first_name=None, last_name=None, email=None, metadata=None)
Create a new customer.
Parameters:
phone(str): Phone number with country codefirst_name(str, optional): Customer first namelast_name(str, optional): Customer last nameemail(str, optional): Customer emailmetadata(Dict, optional): Additional customer metadata
Returns: Customer object
get_customer(phone)
Get customer by phone number.
Parameters:
phone(str): Phone number with country code
Returns: Customer object
Raises:
CustomerNotFoundError: If customer is not found
health_check()
Check service health.
Returns: HealthResponse with service status
AsyncMercatelyClient
Asynchronous version of the client with the same methods as MercatelyClient, but all methods are async and return awaitable objects.
async with AsyncMercatelyClient() as client:
response = await client.send_template(...)
Error Handling
The package provides a comprehensive exception hierarchy:
from mb_mercately_client.exceptions import (
MercatelyClientError, # Base exception
MercatelyAPIError, # API-specific errors
MercatelyTimeoutError, # Timeout errors
MercatelyAuthenticationError, # Authentication errors
MercatelyConnectionError, # Connection errors
InvalidPhoneNumberError, # Phone validation errors
TemplateNotFoundError, # Template not found
CustomerNotFoundError, # Customer not found
RateLimitError, # Rate limiting
ConfigurationError, # Configuration errors
ValidationError, # Data validation errors
)
try:
response = client.send_template(...)
except InvalidPhoneNumberError:
print("Invalid phone number format")
except TemplateNotFoundError:
print("Template not found")
except MercatelyAPIError as e:
print(f"API Error {e.status_code}: {e.message}")
except MercatelyTimeoutError:
print("Request timed out")
except MercatelyClientError as e:
print(f"General error: {e.message}")
Configuration Options
Django Settings
MERCATELY_CLIENT = {
'SERVICE_URL': 'https://your-mercately-service-url',
'AUTH_TOKEN': 'your-auth-token',
'TIMEOUT': 30, # Request timeout in seconds
'RETRY_ATTEMPTS': 3, # Number of retry attempts
'RETRY_DELAY': 1, # Base delay between retries
}
Environment Variables
MERCATELY_SERVICE_URL="https://your-mercately-service-url"
MERCATELY_SERVICE_TOKEN="your-auth-token"
Explicit Configuration
client = MercatelyClient(
service_url="https://api.example.com",
auth_token="your-token",
timeout=30,
retry_attempts=3,
retry_delay=1.0
)
Testing
For testing your Django application, you can mock the client:
from unittest.mock import patch, Mock
# Mock the client in tests
with patch('mb_mercately_client.MercatelyClient') as mock_client:
mock_instance = Mock()
mock_response = Mock()
mock_response.get_message_identifier.return_value = "test_msg_123"
mock_instance.send_template.return_value = mock_response
mock_client.return_value = mock_instance
# Your test code here
response = self.client.post('/send-message/', {
'phone': '+1234567890',
'template_id': 'welcome'
})
self.assertEqual(response.status_code, 200)
Development
Running Tests
# Install development dependencies
pip install -e .[dev]
# Run tests
pytest
# Run tests with coverage
pytest --cov=mercately_client --cov-report=html
# Run type checking
mypy src/mercately_client
# Format code
black src tests examples
isort src tests examples
Project Structure
mb-mercately-client/
├── src/
│ └── mb_mercately_client/
│ ├── __init__.py # Package exports
│ ├── client.py # Synchronous client
│ ├── async_client.py # Asynchronous client
│ ├── models.py # Pydantic models
│ └── exceptions.py # Custom exceptions
├── tests/ # Test suite
├── examples/ # Usage examples
├── pyproject.toml # Project configuration
└── README.md # This file
Requirements
- Python 3.8+
- requests>=2.28.0
- pydantic>=2.0.0
- httpx>=0.24.0 (for async client)
API Endpoints
The client communicates with these Mercately API endpoints:
POST /whatsapp/template- Send WhatsApp template messagesGET /templates- Get available templatesPOST /customers- Create customerGET /customers/{phone}- Get customer by phoneGET /health- Health check (no authentication required)
Phone Number Format
Phone numbers must include the country code and start with '+':
✅ Correct: +1234567890, +521234567890
❌ Incorrect: 1234567890, 234567890
Logging
The client provides structured logging with privacy protection:
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('mercately_client')
# Phone numbers are automatically masked in logs
# +1234567890 becomes +12***890
Publishing & Updates
Initial PyPI Setup
- Install build tools:
uv add --dev build twine
-
Create PyPI account:
- Sign up at PyPI
- Create API token at PyPI Token Management
-
Configure authentication:
# Create ~/.pypirc file
cat > ~/.pypirc << EOF
[distutils]
index-servers = pypi
[pypi]
repository = https://upload.pypi.org/legacy/
username = __token__
password = pypi-YOUR_API_TOKEN_HERE
EOF
First Release
- Ensure tests pass:
uv pip install -e ".[dev]"
pytest
- Build the package:
python -m build
- Upload to PyPI:
twine upload dist/*
Version Updates & New Releases
When adding new features or fixes:
- Update version in
pyproject.toml:
[project]
version = "0.2.0" # Increment version
-
Follow semantic versioning:
0.1.0→0.1.1(patch: bug fixes)0.1.0→0.2.0(minor: new features, backward compatible)0.1.0→1.0.0(major: breaking changes)
-
Update changelog (optional but recommended):
# Add to CHANGELOG.md or include in commit messages
## [0.2.0] - 2024-01-15
### Added
- New feature X
### Fixed
- Bug Y
- Clean previous builds:
rm -rf dist/ build/
- Build and upload:
python -m build
twine upload dist/*
Automated Release Workflow (Recommended)
Create .github/workflows/release.yml for automatic releases:
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
Quick Release Checklist
- All tests pass (
pytest) - Version updated in
pyproject.toml - Changes documented
- Clean build (
rm -rf dist/ build/) - Build package (
python -m build) - Upload to PyPI (
twine upload dist/*)
Testing Before Release
Test your package locally before publishing:
# Build the package
python -m build
# Install locally from wheel
pip install dist/mb_mercately_client-*.whl
# Test in another environment
python -c "from mb_mercately_client import MercatelyClient; print('Import successful')"
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Run the test suite (
pytest) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- Report issues: GitHub Issues
- Documentation: API Documentation
- Email: dev@yourcompany.com
Project details
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 mb_mercately_client-0.3.0.tar.gz.
File metadata
- Download URL: mb_mercately_client-0.3.0.tar.gz
- Upload date:
- Size: 21.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84f68c62e1ee6dc80c739ba79996b77b7cd08c6b828a08331df22589c9e508a0
|
|
| MD5 |
b13758a7ad823414da71de6ca02dccfa
|
|
| BLAKE2b-256 |
b1165c80aa8fd933e70c45d3bea891ea97ee1e44f18ecbf60c2960ae4e0f8f0f
|
File details
Details for the file mb_mercately_client-0.3.0-py3-none-any.whl.
File metadata
- Download URL: mb_mercately_client-0.3.0-py3-none-any.whl
- Upload date:
- Size: 18.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e92a555a3a2de04ae870b99c9b459f5e2ee727cc588b2a0260763b38e089a694
|
|
| MD5 |
7906c492831ffe2da4429e30e44b92af
|
|
| BLAKE2b-256 |
5f6e407a7127bcc46159089bf02e34adbd754a881e9b374774d5bf385b0a52c9
|