Naz is an async SMPP client.
Project description
naz
SMPP is a protocol designed for the transfer of short message data between External Short Messaging Entities(ESMEs), Routing Entities(REs) and Short Message Service Center(SMSC). - Wikipedia
Installation
pip install naz
Usage
1. As a library
import asyncio
import naz
loop = asyncio.get_event_loop()
outboundqueue = naz.q.SimpleOutboundQueue(maxsize=1000, loop=loop)
cli = naz.Client(
async_loop=loop,
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
outboundqueue=outboundqueue,
)
# queue messages to send
for i in range(0, 4):
print("submit_sm round:", i)
item_to_enqueue = {
"version": "1",
"smpp_command": naz.SmppCommand.SUBMIT_SM,
"short_message": "Hello World-{0}".format(str(i)),
"log_id": "myid12345",
"source_addr": "254722111111",
"destination_addr": "254722999999",
}
loop.run_until_complete(outboundqueue.enqueue(item_to_enqueue))
# connect to the SMSC host
reader, writer = loop.run_until_complete(cli.connect())
# bind to SMSC as a tranceiver
loop.run_until_complete(cli.tranceiver_bind())
try:
# read any data from SMSC, send any queued messages to SMSC and continually check the state of the SMSC
tasks = asyncio.gather(cli.send_forever(), cli.receive_data(), cli.enquire_link())
loop.run_until_complete(tasks)
loop.run_forever()
except Exception as e:
print("exception occured. error={0}".format(str(e)))
finally:
loop.run_until_complete(cli.unbind())
loop.stop()
2. As a cli app
{ "smsc_host": "127.0.0.1", "smsc_port": 2775, "system_id": "smppclient1", "password": "password", "outboundqueue": "myfile.ExampleQueue" }
and a python file, myfile.py (in the current working directory) with the contents:
import asyncio
import naz
class ExampleQueue(naz.q.BaseOutboundQueue):
def __init__(self):
loop = asyncio.get_event_loop()
self.queue = asyncio.Queue(maxsize=1000, loop=loop)
async def enqueue(self, item):
self.queue.put_nowait(item)
async def dequeue(self):
return await self.queue.get()
Naz: the SMPP client.
{'event': 'naz.Client.connect', 'stage': 'start', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
{'event': 'naz.Client.connect', 'stage': 'end', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
{'event': 'naz.Client.tranceiver_bind', 'stage': 'start', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
{'event': 'naz.Client.send_data', 'stage': 'start', 'smpp_command': 'bind_transceiver', 'log_id': None, 'msg': 'hello', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
{'event': 'naz.SimpleHook.request', 'stage': 'start', 'smpp_command': 'bind_transceiver', 'log_id': None, 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
{'event': 'naz.Client.send_data', 'stage': 'end', 'smpp_command': 'bind_transceiver', 'log_id': None, 'msg': 'hello', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
{'event': 'naz.Client.tranceiver_bind', 'stage': 'end', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
{'event': 'naz.Client.send_forever', 'stage': 'start', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}
To see help:
naz-cli --help
naz is an async SMPP client.
example usage: naz-cli --config /path/to/my_config.json
optional arguments:
-h, --help show this help message and exit
--version The currently installed naz version.
--loglevel {DEBUG,INFO,WARNING,ERROR,CRITICAL}
The log level to output log messages at. eg: --loglevel DEBUG
--config CONFIG The config file to use. eg: --config /path/to/my_config.json
Features
1. async everywhere
import naz
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
loop = asyncio.get_event_loop()
outboundqueue = naz.q.SimpleOutboundQueue(maxsize=1000, loop=loop)
cli = naz.Client(
async_loop=loop,
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
outboundqueue=outboundqueue,
)
2. monitoring and observability
it’s a loaded term, I know.
2.1 logging
import naz
cli = naz.Client(
...
log_metadata={ "environment": "production", "release": "canary"},
)
2.2 hooks
import naz
from prometheus_client import Counter
class MyPrometheusHook(naz.hooks.BaseHook):
async def request(self, smpp_command, log_id, hook_metadata):
c = Counter('my_requests', 'Description of counter')
c.inc() # Increment by 1
async def response(self,
smpp_command,
log_id,
hook_metadata,
smsc_response):
c = Counter('my_responses', 'Description of counter')
c.inc() # Increment by 1
myHook = MyPrometheusHook()
cli = naz.Client(
...
hook=myHook,
)
another example is if you want to update a database record whenever you get a delivery notification event;
import sqlite3
import naz
class SetMessageStateHook(naz.hooks.BaseHook):
async def request(self, smpp_command, log_id, hook_metadata):
pass
async def response(self,
smpp_command,
log_id,
hook_metadata,
smsc_response):
if smpp_command == naz.SmppCommand.DELIVER_SM:
conn = sqlite3.connect('mySmsDB.db')
c = conn.cursor()
t = (log_id,)
# watch out for SQL injections!!
c.execute("UPDATE SmsTable SET State='delivered' WHERE CorrelatinID=?", t)
conn.commit()
conn.close()
stateHook = SetMessageStateHook()
cli = naz.Client(
...
hook=stateHook,
)
3. Rate limiting
import logging
import naz
logger = logging.getLogger("naz.rateLimiter")
myLimiter = naz.ratelimiter.SimpleRateLimiter(logger=logger, send_rate=35)
cli = naz.Client(
...
rateLimiter=myLimiter,
)
4. Throttle handling
import naz
throttler = naz.throttle.SimpleThrottleHandler(sampling_period=180,
sample_size=45,
deny_request_at=1.2)
cli = naz.Client(
...
throttle_handler=throttler,
)
5. Queuing
{
"version": "1",
"smpp_command": naz.SmppCommand.SUBMIT_SM,
"short_message": string,
"log_id": string,
"source_addr": string,
"destination_addr": string
}
For more information about all the parameters that are needed in the enqueued json object, consult the documentation here
import asyncio
import naz
loop = asyncio.get_event_loop()
my_queue = naz.q.SimpleOutboundQueue(maxsize=1000, loop=loop) # can hold upto 1000 items
cli = naz.Client(
...
async_loop=loop,
outboundqueue=my_queue,
)
# connect to the SMSC host
loop.run_until_complete(cli.connect())
# bind to SMSC as a tranceiver
loop.run_until_complete(cli.tranceiver_bind())
try:
# read any data from SMSC, send any queued messages to SMSC and continually check the state of the SMSC
tasks = asyncio.gather(cli.send_forever(), cli.receive_data(), cli.enquire_link())
loop.run_until_complete(tasks)
loop.run_forever()
except Exception as e:
print("exception occured. error={0}".format(str(e)))
finally:
loop.run_until_complete(cli.unbind())
loop.stop()
then in your application, queue items to the queue;
# queue messages to send
for i in range(0, 4):
item_to_enqueue = {
"version": "1",
"smpp_command": naz.SmppCommand.SUBMIT_SM,
"short_message": "Hello World-{0}".format(str(i)),
"log_id": "myid12345",
"source_addr": "254722111111",
"destination_addr": "254722999999",
}
loop.run_until_complete(outboundqueue.enqueue(item_to_enqueue))
Here is another example, but where we now use redis for our queue;
import json
import asyncio
import naz
import redis
class RedisExampleQueue(naz.q.BaseOutboundQueue):
"""
use redis as our queue.
This implements a basic FIFO queue using redis.
Basically we use the redis command LPUSH to push messages onto the queue and BRPOP to pull them off.
https://redis.io/commands/lpush
https://redis.io/commands/brpop
Note that in practice, you would probaly want to use a non-blocking redis
client eg https://github.com/aio-libs/aioredis
"""
def __init__(self):
self.redis_instance = redis.StrictRedis(host="localhost", port=6379, db=0)
self.queue_name = "myqueue"
async def enqueue(self, item):
self.redis_instance.lpush(self.queue_name, json.dumps(item))
async def dequeue(self):
x = self.redis_instance.brpop(self.queue_name)
dequed_item = json.loads(x[1].decode())
return dequed_item
loop = asyncio.get_event_loop()
outboundqueue = RedisExampleQueue()
cli = naz.Client(
async_loop=loop,
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
outboundqueue=outboundqueue,
)
# connect to the SMSC host
reader, writer = loop.run_until_complete(cli.connect())
# bind to SMSC as a tranceiver
loop.run_until_complete(cli.tranceiver_bind())
try:
# read any data from SMSC, send any queued messages to SMSC and continually check the state of the SMSC
tasks = asyncio.gather(cli.send_forever(), cli.receive_data(), cli.enquire_link())
loop.run_until_complete(tasks)
loop.run_forever()
except Exception as e:
print("error={0}".format(str(e)))
finally:
loop.run_until_complete(cli.unbind())
loop.stop()
then queue on your application side;
# queue messages to send
for i in range(0, 5):
print("submit_sm round:", i)
item_to_enqueue = {
"version": "1",
"smpp_command": naz.SmppCommand.SUBMIT_SM,
"short_message": "Hello World-{0}".format(str(i)),
"log_id": "myid12345",
"source_addr": "254722111111",
"destination_addr": "254722999999",
}
loop.run_until_complete(outboundqueue.enqueue(item_to_enqueue))
6. Well written(if I have to say so myself):
Development setup
NB: I make no commitment of accepting your pull requests.
## TODO
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.