anysd


Nameanysd JSON
Version 1.2.1 PyPI version JSON
download
home_pageNone
SummaryFor building ussd applications faster, with navigation management out of the box
upload_time2024-11-12 11:51:49
maintainerNone
docs_urlNone
authorSomwaki
requires_python>=3.7
licenseMIT
keywords ussd navigation
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # AnySD


AnySD (Any USSD) is a package with classes to help you quickly write ussd (or ussd-like) applications.


### Understanding Anysd ussd

---
#### Anysd
Anysd uses [anytree](https://pypi.org/project/anytree/) to build a tree navigation, 
and [redis](https://pypi.org/project/redis/) for tracking navigation and session data (variables)
*Therefore, you need redis to continue*

#### ussds
Ussd applications have 2 main components

1. **A Form** - for taking input from the user
2. **Navigation** - How to reach the beginning of a form, by selecting options

## Getting started

---

### Install anysd in virtual environment
Create and activate a virtualenv, then
Install `anysd` if you have not: 
```
>>> mkdir anysdtest && cd anysdtest

>>> virtualenv venv
created virtual environment CPython3.8.10.final.0-64 in 4591ms
  creator CPython3Posix(dest=/home/steven/workspace/anysdtest/venv, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/steven/.local/share/virtualenv)
    added seed packages: pip==22.0.4, setuptools==62.1.0, wheel==0.37.1
  activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

>>> source venv/bin/activate
(venv) >>>
(venv) >>> pip install anysd

...

(venv) >>> pip install Flask

```

### Building the ussd

To build a ussd application, we first need to define the menus, first a home screen (or starting point), 
then the other menus descending from the home menu

In this example, we want a ussd to buy airtime and bundles. Then for each selection, we'll need to get the phone number
of the receiver. For airtime, we need amount, and for bundles, we'll select a bundle option

**step 1: Build the questions**

```python
# validator.py

from anysd import FormFlow, ListInput
airtime_questions = {
    "1": {'name': 'AIRTIME_RECEIVER', 'menu': ListInput(items=['Buy for myself', 'Buy for other number'], title='Select Option')},
    '2': {'name': 'AIRTIME_AMOUNT', 'menu': 'Enter amount'},
    '3': {'name': 'CONFIRMATION', 'menu': 'You are about to buy {amount} airtime for {receiver}\n1. Confirm\n0. Cancel'}
}

bundle_packages = [
    '100mb @15 valid 24 hours',
    '1GB @40 valid 24 hours',
    '5GB @100 valid 7 days'
]
bundle_questions = {
    "1": {'name': 'BUNDLE_RECEIVER', 'menu': ListInput(items=['Buy for myself', 'Buy for other number'], title='Select Option')},
    '2': {'name': 'BUNDLE_PACKAGE', 'menu': ListInput(items=bundle_packages, title='Select package')},
    '3': {'name': 'CONFIRMATION', 'menu': 'You are about to buy {package} for {receiver}\n1. Confirm\n0. Cancel'}
}


```

**step 2: create form validators**

By default, list inputs will be validated, but it's good you write another validator.
In validation, you specify what conditions make a user input invalid, and return either true or false on the user input.
Also, you can modify the input, if you need to
Note: the validators should accept extra `kwargs` that may be passed 

```python
# validator.py

def airtime_validator(current_step, last_input: str, **kwargs):
    valid = True
    validated = None  # in case we want to modify user input, 
    
    if current_step == 2:
        if not last_input.isnumeric():  # checking if amount is a numeric value
            valid = False
        elif int(last_input) < 5:  # checking if is less than minimum 
            valid = False
    
    elif current_step == 3:
        if last_input not in ['1', '0']:
            valid = False
    
    return valid, validated

def bundle_validator(current_step, last_input: str, **kwargs):
    valid = True
    validated = None  # in case we want to modify user input, 
    
    if current_step == 3:
        if last_input not in ['1', '0']:
            valid = False
    
    return valid, validated
```
*Note: Without step validators, all inputs except for List inputs(so far), will be assumed to be valid*

**step 3: Build the navigation**

Link the menu, with the forms
```python
# menu.py

from anysd import NavigationMenu, FormFlow
from validator import *


# forms
airtime_form = FormFlow(form_questions=airtime_questions, step_validator=airtime_validator)
bundle_form = FormFlow(form_questions=bundle_questions, step_validator=bundle_validator)

# menus
home = NavigationMenu(name="Home", title="Main Menu", show_title=True)

buy_airtime = NavigationMenu(name="airtime_home", title="Buy Airtime", parent=home, next_form=airtime_form)
buy_bundles = NavigationMenu(name="bundles_home", title="Buy Bundles", parent=home, next_form=bundle_form)
```

**step 4: Navigation controller**

The `NavigationController` object will be used to bind things together. It takes in `msisdn`, `session_id` and `ussd_string` plus your navigation `home`, then responds
with an appropriate response.

We will call this inside a simple flask application.

If you haven't, install flask: `pip install flask` in your virtualenv

```python
from flask import Flask, request
from anysd import NavigationController
from menu import home

app = Flask(__name__)

@app.get('/ussd')
def ussd_app():

    msisdn = request.args.get('msisdn')
    session_id = request.args.get('session_id')
    ussd_string = request.args.get('ussd_string')

    print(f"{msisdn} :: {session_id} ::: {ussd_string}")
    navigator = NavigationController(home, msisdn, session_id, ussd_string)
    msg = navigator.navigate()

    return msg


if __name__ == '__main__':
    app.run()
```

**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**

```yaml
# config.yaml

redis:
    host: localhost
    port: 6379
    db: 4
```

Now we are ready to run the application:

```
(venv) >>> flask run 
* Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
2022-05-27 11:52:03,350 INFO   _log (on line 224 ) :  * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

```

Our flask application will receive `GET` requests in this format: `http://host:port/ussd?msisdn=XXXXXXXXXXXX&session_id=XXXXXXXXXXXXX&ussd_string=XXX*XXX*XXX`

Now Let's use postman to hit the endpoint

![img.png](img.png)

Now let's select option 1 and see what happens:

![img_1.png](img_1.png)


**Congratulations**. you have built a basic ussd application, with one level of navigation and a form.
We'll make more lessons on how to use anysd.

### This is a new project, so many features are going to be added, progressively



            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "anysd",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "ussd, navigation",
    "author": "Somwaki",
    "author_email": "somwaki@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/d7/ac/774981c80fcfd558b15081df7832c95457b136327be6ee7e753aba09f75a/anysd-1.2.1.tar.gz",
    "platform": null,
    "description": "# 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>>> mkdir anysdtest && cd anysdtest\n\n>>> 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>>> source venv/bin/activate\n(venv) >>>\n(venv) >>> pip install anysd\n\n...\n\n(venv) >>> 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) < 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) >>> 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&session_id=XXXXXXXXXXXXX&ussd_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",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "For building ussd applications faster, with navigation management out of the box",
    "version": "1.2.1",
    "project_urls": {
        "Homepage": "https://github.com/somwaki/anysd"
    },
    "split_keywords": [
        "ussd",
        " navigation"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0a189d9270349fe04dc2c3bead17bb218444e73e400584be991829f278ca9263",
                "md5": "1a5d8dd1ff4ac99bc71a38ee4ee01dc7",
                "sha256": "0a8214ed533b3316d5bff6c99017c475d1430736df5781e97285b5ac9c87b08d"
            },
            "downloads": -1,
            "filename": "anysd-1.2.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "1a5d8dd1ff4ac99bc71a38ee4ee01dc7",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 13680,
            "upload_time": "2024-11-12T11:51:47",
            "upload_time_iso_8601": "2024-11-12T11:51:47.764834Z",
            "url": "https://files.pythonhosted.org/packages/0a/18/9d9270349fe04dc2c3bead17bb218444e73e400584be991829f278ca9263/anysd-1.2.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d7ac774981c80fcfd558b15081df7832c95457b136327be6ee7e753aba09f75a",
                "md5": "5bfe9062ffdf381b495e3075ee1d314b",
                "sha256": "7735559606034dc0674ca94e2a6e2ba5bdacfb382da140394563ccbbe3c70a9a"
            },
            "downloads": -1,
            "filename": "anysd-1.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "5bfe9062ffdf381b495e3075ee1d314b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 15247,
            "upload_time": "2024-11-12T11:51:49",
            "upload_time_iso_8601": "2024-11-12T11:51:49.970914Z",
            "url": "https://files.pythonhosted.org/packages/d7/ac/774981c80fcfd558b15081df7832c95457b136327be6ee7e753aba09f75a/anysd-1.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-12 11:51:49",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "somwaki",
    "github_project": "anysd",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "anysd"
}
        
Elapsed time: 0.34505s