A modern, intuitive Python framework for building USSD applications with FastAPI
Project description
Cardisk — USSD Framework for Python
A modern, intuitive Python framework for building USSD applications with FastAPI. Cardisk provides a clean, decorator-based API for creating interactive USSD menus and flows.
Features
- Simple API - Decorator-based routing similar to Flask/FastAPI
- Menu System - Built-in support for interactive menus with automatic navigation
- Session Management - Automatic session handling with in-memory or Redis storage
- Monitoring - Built-in Prometheus metrics endpoint
- Error Handling - Robust error handling middleware
- Type Safe - Full type hints support
- Fast - Built on FastAPI and Uvicorn
Installation
Development Install
pip install -e .
Production Install
pip install cardisk
Quick Start
Create a file app.py:
from cardisk import Cardisk, ussd
app = Cardisk()
@app.route("/")
def main_menu():
ussd.menu("Welcome to Wolke Services", [
("Loans", "/loans"),
("Savings", "/savings"),
("Insurance", "/insurance")
])
@app.route("/loans")
def loans():
ussd.menu("Loans Menu", [
("Apply for loan", "/loan/apply"),
("Check loan status", "/loan/status"),
("Repay loan", "/loan/repay")
])
@app.route("/loan/apply")
def loan_apply():
ussd.screen(f"Enter loan amount for {ussd.msisdn}")
ussd.next("/loan/confirm")
@app.route("/loan/confirm")
def loan_confirm():
amount = ussd.input
ussd.screen(f"Loan of ${amount} submitted successfully for {ussd.msisdn}")
ussd.end()
Run the application:
cardisk runserver app.py
Or with uvicorn directly:
uvicorn app:app.app --reload --port 8000
API Reference
Application Setup
from cardisk import Cardisk, ussd
app = Cardisk()
Route Decorator
Define routes using the @app.route() decorator:
@app.route("/path")
def handler():
# Your logic here
pass
- Routes should start with
/ - Use
/for the main menu (entry point)
USSD Object
The ussd object provides access to request context and response methods:
Properties
ussd.msisdn- User's phone number (str)ussd.session- Current session ID (str)ussd.input- User's input from the previous screen (str)
Methods
ussd.menu(title, options)
Display a numbered menu to the user:
ussd.menu("Main Menu", [
("Option 1", "/route1"),
("Option 2", "/route2"),
("Option 3", "/route3")
])
title(str): Menu headingoptions(List[Tuple[str, str]]): List of (label, route) tuples
The menu automatically handles user input (1, 2, 3, etc.) and routes to the corresponding path.
ussd.screen(text)
Display a text screen:
ussd.screen(f"Hello {ussd.msisdn}!")
ussd.next(route)
Set the next route to navigate to after user input:
ussd.screen("Enter your PIN:")
ussd.next("/verify-pin")
ussd.end()
End the USSD session:
ussd.screen("Thank you!")
ussd.end()
USSD XML Protocol
Cardisk uses XML for USSD communication:
Request Format
<ussd>
<type>1</type> <!-- 1=new session, 2=continue -->
<msisdn>254712345678</msisdn>
<sessionid>unique-session-id</sessionid>
<msg>user-input</msg>
</ussd>
Response Format
<ussd>
<type>2</type> <!-- 2=continue, 3=end -->
<msg>Response text</msg>
</ussd>
Testing Your USSD App
Using cURL
1. Start a new session:
curl -X POST http://localhost:8000/ \
-H "Content-Type: application/xml" \
-d '<ussd><type>1</type><msisdn>254712345678</msisdn><sessionid>test123</sessionid><msg></msg></ussd>'
2. Select a menu option (e.g., option 1):
curl -X POST http://localhost:8000/ \
-H "Content-Type: application/xml" \
-d '<ussd><type>2</type><msisdn>254712345678</msisdn><sessionid>test123</sessionid><msg>1</msg></ussd>'
3. Enter input:
curl -X POST http://localhost:8000/ \
-H "Content-Type: application/xml" \
-d '<ussd><type>2</type><msisdn>254712345678</msisdn><sessionid>test123</sessionid><msg>500</msg></ussd>'
Using Python
import httpx
client = httpx.Client(base_url="http://localhost:8000")
# Start session
response = client.post("/", content="""
<ussd>
<type>1</type>
<msisdn>254712345678</msisdn>
<sessionid>test123</sessionid>
<msg></msg>
</ussd>
""", headers={"Content-Type": "application/xml"})
print(response.text)
# Continue session
response = client.post("/", content="""
<ussd>
<type>2</type>
<msisdn>254712345678</msisdn>
<sessionid>test123</sessionid>
<msg>1</msg>
</ussd>
""", headers={"Content-Type": "application/xml"})
print(response.text)
Example Application
Check out the full example in examples/mybank/main.py:
cd examples/mybank
cardisk runserver main.py
Or:
cd examples/mybank
uvicorn main:app.app --reload
Session Management
In-Memory Sessions (Default)
By default, Cardisk uses in-memory session storage, suitable for development and single-instance deployments.
Redis Sessions
For production with multiple instances, use Redis:
export CARDISK_USE_REDIS=true
export CARDISK_REDIS_URL=redis://localhost:6379/0
cardisk runserver app.py
Monitoring
Prometheus Metrics
Cardisk exposes metrics at /metrics:
curl http://localhost:8000/metrics
Available metrics:
cardisk_requests_total- Total USSD requests by type
Docker
Build
docker build -t cardisk-app .
Run
docker run -p 8000:8000 cardisk-app
With Redis
docker run -p 8000:8000 \
-e CARDISK_USE_REDIS=true \
-e CARDISK_REDIS_URL=redis://redis:6379/0 \
cardisk-app
CLI Commands
Create a new project
cardisk new myproject
Run the server
cardisk runserver app.py --host 0.0.0.0 --port 8000
Advanced Usage
Custom Error Handling
@app.route("/transfer")
def transfer():
try:
amount = float(ussd.input)
if amount <= 0:
ussd.screen("Invalid amount. Please try again.")
ussd.next("/transfer")
else:
# Process transfer
ussd.screen(f"Transfer of ${amount} successful!")
ussd.end()
except ValueError:
ussd.screen("Invalid input. Please enter a number.")
ussd.next("/transfer")
Multi-Step Forms
@app.route("/register")
def register_name():
ussd.screen("Enter your name:")
ussd.next("/register/age")
@app.route("/register/age")
def register_age():
name = ussd.input
ussd.screen(f"Hello {name}! Enter your age:")
ussd.next("/register/confirm")
@app.route("/register/confirm")
def register_confirm():
age = ussd.input
ussd.screen(f"Registration complete! Age: {age}")
ussd.end()
Testing
Run Tests
pytest
Run with Coverage
pytest --cov=cardisk --cov-report=html
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License
Support
For issues and questions, please open an issue on GitHub.
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 cardisk-0.4.7.tar.gz.
File metadata
- Download URL: cardisk-0.4.7.tar.gz
- Upload date:
- Size: 19.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6d82dee7277ecf94ca0d5b10c38c8a7e16568ee6795213e906b9c040d5e1471
|
|
| MD5 |
7cfe7158a6033775246316d6dfc37fd7
|
|
| BLAKE2b-256 |
2186e1d264ef2cfb7d4754c8b2ee65c2c43638787c9cb589d7d01a7fe481eee4
|
File details
Details for the file cardisk-0.4.7-py3-none-any.whl.
File metadata
- Download URL: cardisk-0.4.7-py3-none-any.whl
- Upload date:
- Size: 18.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6bef572765e628fd1d72f21c89796a4552efa22edd114125021979ce7d3cfcc4
|
|
| MD5 |
179ccba3b5f0f5a6b0f609669634e45e
|
|
| BLAKE2b-256 |
16810fa1f85638fd63abcbb95a9df8f7561c39e694862fd5ba59dab20f0331b8
|