Naz is an SMPP client.
Project description
naz
Codacy Badge Build Status codecov Code style: black
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 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):
c = Counter('my_requests', 'Description of counter')
c.inc() # Increment by 1
async def response(self, smpp_command, log_id):
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):
pass
async def response(self, smpp_command, log_id):
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.
Source Distribution
Built Distribution
File details
Details for the file naz-0.3.0b1.tar.gz
.
File metadata
- Download URL: naz-0.3.0b1.tar.gz
- Upload date:
- Size: 36.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.20.1 setuptools/40.6.2 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 |
7852d5496794417c0c1e3093180ed03735b55288a5b1780336e74bfc31dce7db
|
|
MD5 |
c87cd20c5fb0cf0210adb5dfbf394a06
|
|
BLAKE2b-256 |
33169feb464278a9a9d7db0d8974797fa909cb47cfab51200c5e6722c1cdb4b6
|
File details
Details for the file naz-0.3.0b1-py3-none-any.whl
.
File metadata
- Download URL: naz-0.3.0b1-py3-none-any.whl
- Upload date:
- Size: 33.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.20.1 setuptools/40.6.2 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 |
77c2854ab630ff3961d50f94e1acd44c74cfc5d43d7186e680689c0c925eafe0
|
|
MD5 |
272c38ea06825e9b4945f5f6b0968ba0
|
|
BLAKE2b-256 |
3ec739b6407b23fde6278e3be305be0f5cd6f471a9af5cef9dbc785e53c583d3
|