Advanced Python Programming · May Assessment Arc
Branch:f1/mvp→ merged intomainvia Pull Request
East African merchant credit and remittance platform MVP. Provides REST endpoints for merchant onboarding, financing requests, lender-facing feeds, and background alert dispatch.
- Architecture overview
- Prerequisites
- Local setup — Docker (recommended)
- Local setup — bare metal (alternative)
- Environment variables
- Running database migrations
- Starting the Celery worker
- API documentation
- Example requests
- Project structure
- Design decisions
┌──────────────────────────────────────────────┐
│ Django REST API │
│ ┌──────────┐ ┌───────────┐ ┌───────────┐ │
│ │ merchants│ │ financing │ │ alerts │ │
│ └──────────┘ └───────────┘ └───────────┘ │
└──────────────────────────┬───────────────────┘
│ enqueue
┌────────▼────────┐
│ Redis (broker) │◄──── Django cache (fees)
└────────┬────────┘
│
┌────────▼────────┐
│ Celery worker │
│ dispatch_alert │
└────────┬────────┘
│ write
┌────────▼────────┐
│ AlertAttempt │
│ (Postgres) │
└─────────────────┘
- Django REST Framework handles synchronous request/response.
- Celery + Redis dispatches alerts asynchronously — clients are never blocked by alert delivery.
- Cursor pagination on lender feeds keeps response payloads small and queries cheap.
- Redis cache on the fee reference endpoint eliminates repeated DB reads.
| Tool | Version |
|---|---|
| Docker + Docker Compose | 24+ |
| Python | 3.11+ (bare-metal only) |
| Redis | 7+ (bare-metal only) |
| PostgreSQL | 15+ (bare-metal only) |
# 1. Clone the repository
git clone <your-repo-url>
cd advanced-python-imara-<username>
# 2. Copy environment file
cp .env.example .env
# Edit .env if needed — defaults work for Docker Compose
# 3. Build and start all services
docker-compose up --build
# 4. In a second terminal: run migrations
docker-compose exec web python manage.py migrate
# 5. Create a superuser (used as lender in examples below)
docker-compose exec web python manage.py createsuperuser
# API is now running at http://localhost:8000
# Swagger UI: http://localhost:8000/api/docs/The worker service starts automatically and begins consuming Celery tasks from Redis.
# 1. Create and activate a virtual environment
python3.11 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 2. Install dependencies
pip install -r requirements.txt
# 3. Configure environment
cp .env.example .env
# Edit .env — set DATABASE_URL and REDIS_URL for your local services
# 4. Apply migrations
python manage.py migrate
# 5. Create superuser
python manage.py createsuperuser
# 6. Start the development server
python manage.py runserver
# 7. In a separate terminal, start Celery
celery -A imara_project worker --loglevel=info| Variable | Required | Default | Description |
|---|---|---|---|
SECRET_KEY |
Yes | dev-secret-key-unsafe |
Django secret key — change in production |
DEBUG |
No | True |
Django debug mode |
DATABASE_URL |
Yes | SQLite fallback | Postgres DSN e.g. postgres://user:pass@host:5432/db |
REDIS_URL |
Yes | redis://localhost:6379/0 |
Redis DSN — used for Celery broker and Django cache |
ALERT_CHANNEL |
No | log |
Alert dispatch channel: log (dev) or sms |
AFRICASTALKING_USERNAME |
SMS only | — | Africa's Talking username |
AFRICASTALKING_API_KEY |
SMS only | — | Africa's Talking API key |
In local development,
ALERT_CHANNEL=logprints alert messages to the terminal. No SMS credits are consumed.
# Docker
docker-compose exec web python manage.py migrate
# Bare metal
python manage.py migrate# Docker — already started by docker-compose up
# To view logs:
docker-compose logs -f worker
# Bare metal
celery -A imara_project worker --loglevel=info --concurrency=2To verify the worker is running correctly:
# In Django shell
python manage.py shell
>>> from alerts.tasks import dispatch_financing_alert
>>> result = dispatch_financing_alert.delay("test-id-123", "created", "+254712345678")
>>> result.status
'SUCCESS'Interactive Swagger UI is available at:
http://localhost:8000/api/docs/
ReDoc (read-only, better for sharing):
http://localhost:8000/api/redoc/
Raw OpenAPI schema (JSON/YAML):
http://localhost:8000/api/schema/
All examples use curl. Replace <token> with your JWT access token.
curl -s -X POST http://localhost:8000/api/v1/auth/token/ \
-H "Content-Type: application/json" \
-d '{"username": "merchant1", "password": "testpass123"}' | python -m json.toolResponse:
{
"access": "eyJ...",
"refresh": "eyJ..."
}curl -s -X POST http://localhost:8000/api/v1/merchants/register/ \
-H "Content-Type: application/json" \
-d '{
"username": "merchant1",
"password": "testpass123",
"business_name": "Wanjiku Fresh Produce",
"business_type": "sole_trader",
"phone_number": "+254712345678",
"email": "wanjiku@example.com",
"country": "KE",
"town": "Nairobi",
"agent_code": "AGT-001"
}' | python -m json.toolcurl -s http://localhost:8000/api/v1/merchants/me/ \
-H "Authorization: Bearer <token>" | python -m json.toolcurl -s -X PATCH http://localhost:8000/api/v1/merchants/me/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"town": "Mombasa"}' | python -m json.toolcurl -s -X POST http://localhost:8000/api/v1/financing/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"amount_requested": "50000.00",
"currency": "KES",
"purpose": "Restock fresh produce inventory before Easter peak",
"repayment_period_days": 30
}' | python -m json.toolAn alert task is queued immediately. Watch the Celery worker terminal for the log output.
curl -s "http://localhost:8000/api/v1/financing/mine/?page_size=5" \
-H "Authorization: Bearer <token>" | python -m json.tool# Get token for the superuser you created
curl -s -X POST http://localhost:8000/api/v1/auth/token/ \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "adminpass"}' | python -m json.tool
curl -s "http://localhost:8000/api/v1/lender/requests/?status=pending" \
-H "Authorization: Bearer <lender-token>" | python -m json.toolcurl -s -X PATCH http://localhost:8000/api/v1/lender/requests/<request-uuid>/ \
-H "Authorization: Bearer <lender-token>" \
-H "Content-Type: application/json" \
-d '{
"status": "under_review",
"lender_notes": "Requesting 3-month bank statement."
}' | python -m json.toolcurl -s http://localhost:8000/api/v1/reference/fees/ \
-H "Authorization: Bearer <token>" | python -m json.toolFirst call returns "_cached": false; subsequent calls within 1 hour return "_cached": true.
.
├── ADR.md # Architecture Decision Record
├── README.md
├── Dockerfile
├── docker-compose.yml
├── manage.py
├── requirements.txt
├── .env.example
├── imara_project/ # Django project package
│ ├── settings.py
│ ├── urls.py
│ ├── celery.py # Celery app definition
│ ├── pagination.py # ImaraCursorPagination
│ └── exceptions.py # Custom error envelope handler
├── merchants/ # Merchant registration & profiles
│ ├── models.py
│ ├── serializers.py
│ ├── views.py
│ └── urls.py
├── financing/ # Financing request lifecycle
│ ├── models.py
│ ├── serializers.py
│ ├── views.py
│ └── urls.py
└── alerts/ # Async alert dispatch
├── models.py # AlertAttempt (audit log)
├── tasks.py # Celery task
└── services.py # Channel implementations
See ADR.md for the full Architecture Decision Record.
Key choices at a glance:
| Decision | Choice | Primary benefit |
|---|---|---|
| API framework | Django REST Framework | Ecosystem maturity; serializer-level validation |
| Async | Celery + Redis | Non-blocking client responses; retry on failure |
| Pagination | Cursor-based | Stable feeds; no COUNT(*); smaller payloads |
| Caching | Redis (fees endpoint) | Eliminates repeated DB reads for static reference data |
| Alert audit | AlertAttempt model |
Queryable history without broker access |