{"id":37071217,"url":"https://github.com/somwaki/anysd","last_synced_at":"2026-01-14T08:19:24.685Z","repository":{"id":39637096,"uuid":"496248979","full_name":"somwaki/anysd","owner":"somwaki","description":"For building ussd applications faster, with navigation management out of the box","archived":false,"fork":false,"pushed_at":"2025-03-03T06:04:09.000Z","size":97,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-06T19:59:27.791Z","etag":null,"topics":["python-tree","ussd"],"latest_commit_sha":null,"homepage":"https://anysd.somwaki.cf","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/somwaki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-25T13:44:49.000Z","updated_at":"2025-05-06T01:35:53.000Z","dependencies_parsed_at":"2024-05-30T07:08:28.102Z","dependency_job_id":"dd6cb872-65d7-4f4f-a3b1-61657d13960a","html_url":"https://github.com/somwaki/anysd","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/somwaki/anysd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/somwaki%2Fanysd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/somwaki%2Fanysd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/somwaki%2Fanysd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/somwaki%2Fanysd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/somwaki","download_url":"https://codeload.github.com/somwaki/anysd/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/somwaki%2Fanysd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413765,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:16:59.381Z","status":"ssl_error","status_checked_at":"2026-01-14T08:13:45.490Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["python-tree","ussd"],"created_at":"2026-01-14T08:19:24.149Z","updated_at":"2026-01-14T08:19:24.673Z","avatar_url":"https://github.com/somwaki.png","language":"Python","readme":"# AnySD\n\n\nAnySD (Any USSD) is a package with classes to help you quickly write ussd (or ussd-like) applications.\n\n\n### Understanding Anysd ussd\n\n---\n#### Anysd\nAnysd uses [anytree](https://pypi.org/project/anytree/) to build a tree navigation, \nand [redis](https://pypi.org/project/redis/) for tracking navigation and session data (variables)\n*Therefore, you need redis to continue*\n\n#### ussds\nUssd applications have 2 main components\n\n1. **A Form** - for taking input from the user\n2. **Navigation** - How to reach the beginning of a form, by selecting options\n\n## Getting started\n\n---\n\n### Install anysd in virtual environment\nCreate and activate a virtualenv, then\nInstall `anysd` if you have not: \n```\n\u003e\u003e\u003e mkdir anysdtest \u0026\u0026 cd anysdtest\n\n\u003e\u003e\u003e virtualenv venv\ncreated virtual environment CPython3.8.10.final.0-64 in 4591ms\n  creator CPython3Posix(dest=/home/steven/workspace/anysdtest/venv, clear=False, no_vcs_ignore=False, global=False)\n  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/steven/.local/share/virtualenv)\n    added seed packages: pip==22.0.4, setuptools==62.1.0, wheel==0.37.1\n  activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator\n\n\u003e\u003e\u003e source venv/bin/activate\n(venv) \u003e\u003e\u003e\n(venv) \u003e\u003e\u003e pip install anysd\n\n...\n\n(venv) \u003e\u003e\u003e pip install Flask\n\n```\n\n### Building the ussd\n\nTo build a ussd application, we first need to define the menus, first a home screen (or starting point), \nthen the other menus descending from the home menu\n\nIn this example, we want a ussd to buy airtime and bundles. Then for each selection, we'll need to get the phone number\nof the receiver. For airtime, we need amount, and for bundles, we'll select a bundle option\n\n**step 1: Build the questions**\n\n```python\n# validator.py\n\nfrom anysd import FormFlow, ListInput\nairtime_questions = {\n    \"1\": {'name': 'AIRTIME_RECEIVER', 'menu': ListInput(items=['Buy for myself', 'Buy for other number'], title='Select Option')},\n    '2': {'name': 'AIRTIME_AMOUNT', 'menu': 'Enter amount'},\n    '3': {'name': 'CONFIRMATION', 'menu': 'You are about to buy {amount} airtime for {receiver}\\n1. Confirm\\n0. Cancel'}\n}\n\nbundle_packages = [\n    '100mb @15 valid 24 hours',\n    '1GB @40 valid 24 hours',\n    '5GB @100 valid 7 days'\n]\nbundle_questions = {\n    \"1\": {'name': 'BUNDLE_RECEIVER', 'menu': ListInput(items=['Buy for myself', 'Buy for other number'], title='Select Option')},\n    '2': {'name': 'BUNDLE_PACKAGE', 'menu': ListInput(items=bundle_packages, title='Select package')},\n    '3': {'name': 'CONFIRMATION', 'menu': 'You are about to buy {package} for {receiver}\\n1. Confirm\\n0. Cancel'}\n}\n\n\n```\n\n**step 2: create form validators**\n\nBy default, list inputs will be validated, but it's good you write another validator.\nIn validation, you specify what conditions make a user input invalid, and return either true or false on the user input.\nAlso, you can modify the input, if you need to\nNote: the validators should accept extra `kwargs` that may be passed \n\n```python\n# validator.py\n\ndef airtime_validator(current_step, last_input: str, **kwargs):\n    valid = True\n    validated = None  # in case we want to modify user input, \n    \n    if current_step == 2:\n        if not last_input.isnumeric():  # checking if amount is a numeric value\n            valid = False\n        elif int(last_input) \u003c 5:  # checking if is less than minimum \n            valid = False\n    \n    elif current_step == 3:\n        if last_input not in ['1', '0']:\n            valid = False\n    \n    return valid, validated\n\ndef bundle_validator(current_step, last_input: str, **kwargs):\n    valid = True\n    validated = None  # in case we want to modify user input, \n    \n    if current_step == 3:\n        if last_input not in ['1', '0']:\n            valid = False\n    \n    return valid, validated\n```\n*Note: Without step validators, all inputs except for List inputs(so far), will be assumed to be valid*\n\n**step 3: Build the navigation**\n\nLink the menu, with the forms\n```python\n# menu.py\n\nfrom anysd import NavigationMenu, FormFlow\nfrom validator import *\n\n\n# forms\nairtime_form = FormFlow(form_questions=airtime_questions, step_validator=airtime_validator)\nbundle_form = FormFlow(form_questions=bundle_questions, step_validator=bundle_validator)\n\n# menus\nhome = NavigationMenu(name=\"Home\", title=\"Main Menu\", show_title=True)\n\nbuy_airtime = NavigationMenu(name=\"airtime_home\", title=\"Buy Airtime\", parent=home, next_form=airtime_form)\nbuy_bundles = NavigationMenu(name=\"bundles_home\", title=\"Buy Bundles\", parent=home, next_form=bundle_form)\n```\n\n**step 4: Navigation controller**\n\nThe `NavigationController` object will be used to bind things together. It takes in `msisdn`, `session_id` and `ussd_string` plus your navigation `home`, then responds\nwith an appropriate response.\n\nWe will call this inside a simple flask application.\n\nIf you haven't, install flask: `pip install flask` in your virtualenv\n\n```python\nfrom flask import Flask, request\nfrom anysd import NavigationController\nfrom menu import home\n\napp = Flask(__name__)\n\n@app.get('/ussd')\ndef ussd_app():\n\n    msisdn = request.args.get('msisdn')\n    session_id = request.args.get('session_id')\n    ussd_string = request.args.get('ussd_string')\n\n    print(f\"{msisdn} :: {session_id} ::: {ussd_string}\")\n    navigator = NavigationController(home, msisdn, session_id, ussd_string)\n    msg = navigator.navigate()\n\n    return msg\n\n\nif __name__ == '__main__':\n    app.run()\n```\n\n**BEFORE WE RUN OUR BEAUTIFUL USSD, Anysd uses redis to store session data. We therefore need to specify the connection to redis in a config.yaml file**\n\n```yaml\n# config.yaml\n\nredis:\n    host: localhost\n    port: 6379\n    db: 4\n```\n\nNow we are ready to run the application:\n\n```\n(venv) \u003e\u003e\u003e flask run \n* Environment: production\n   WARNING: This is a development server. Do not use it in a production deployment.\n   Use a production WSGI server instead.\n * Debug mode: off\n2022-05-27 11:52:03,350 INFO   _log (on line 224 ) :  * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)\n\n```\n\nOur flask application will receive `GET` requests in this format: `http://host:port/ussd?msisdn=XXXXXXXXXXXX\u0026session_id=XXXXXXXXXXXXX\u0026ussd_string=XXX*XXX*XXX`\n\nNow Let's use postman to hit the endpoint\n\n![img.png](img.png)\n\nNow let's select option 1 and see what happens:\n\n![img_1.png](img_1.png)\n\n\n**Congratulations**. you have built a basic ussd application, with one level of navigation and a form.\nWe'll make more lessons on how to use anysd.\n\n### This is a new project, so many features are going to be added, progressively\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsomwaki%2Fanysd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsomwaki%2Fanysd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsomwaki%2Fanysd/lists"}