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
Comprehensive documetion is available -> Documentation
Installation
pip install naz
Usage
1. As a library
import asyncio
import naz
loop = asyncio.get_event_loop()
outboundqueue = naz.q.SimpleOutboundQueue(maxsize=1000)
cli = naz.Client(
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)
loop.run_until_complete(
cli.submit_sm(
short_message="Hello World-{0}".format(str(i)),
log_id="myid12345",
source_addr="254722111111",
destination_addr="254722999999",
)
)
try:
# 1. connect to the SMSC host
# 2. bind to the SMSC host
# 3. send any queued messages to SMSC
# 4. read any data from SMSC
# 5. continually check the state of the SMSC
tasks = asyncio.gather(
cli.connect(),
cli.tranceiver_bind(),
cli.dequeue_messages(),
cli.receive_data(),
cli.enquire_link(),
)
loop.run_until_complete(tasks)
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
import naz
from myfile import ExampleQueue
client = naz.Client(
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
outboundqueue=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.to_smsc', '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.dequeue_messages', '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 --client path.to.my_config.client
optional arguments:
-h, --help show this help message and exit
--version The currently installed naz version.
--client CLIENT The config file to use. eg: --client path.to.my_config.client
Features
1. async everywhere
import naz
import asyncio
loop = asyncio.get_event_loop()
outboundqueue = naz.q.SimpleOutboundQueue(maxsize=1000)
cli = naz.Client(
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 to_smsc(self, smpp_command, log_id, hook_metadata, pdu):
c = Counter('my_requests', 'Description of counter')
c.inc() # Increment by 1
async def from_smsc(self,
smpp_command,
log_id,
hook_metadata,
status,
pdu):
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 to_smsc(self, smpp_command, log_id, hook_metadata, pdu):
pass
async def from_smsc(self,
smpp_command,
log_id,
hook_metadata,
status,
pdu):
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,
)
2.3 integration with bug trackers
import naz
from myfile import ExampleQueue
import sentry_sdk # import sentry SDK
sentry_sdk.init("https://<YOUR_SENTRY_PUBLIC_KEY>@sentry.io/<YOUR_SENTRY_PROJECT_ID>")
my_naz_client = naz.Client(
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
outboundqueue=ExampleQueue()
)
3. Rate limiting
import naz
myLimiter = naz.ratelimiter.SimpleRateLimiter(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,) # can hold upto 1000 items
cli = naz.Client(
...
outboundqueue=my_queue,
)
try:
# 1. connect to the SMSC host
# 2. bind to the SMSC host
# 3. send any queued messages to SMSC
# 4. read any data from SMSC
# 5. continually check the state of the SMSC
tasks = asyncio.gather(
cli.connect(),
cli.tranceiver_bind(),
cli.dequeue_messages(),
cli.receive_data(),
cli.enquire_link(),
)
loop.run_until_complete(tasks)
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):
loop.run_until_complete(
cli.submit_sm(
short_message="Hello World-{0}".format(str(i)),
log_id="myid12345",
source_addr="254722111111",
destination_addr="254722999999",
)
)
Here is another example, but where we now use redis for our queue;
import json
import asyncio
import naz
import aioredis
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
You should use a non-blocking redis client eg https://github.com/aio-libs/aioredis
"""
def __init__(self):
self.queue_name = "myqueue"
async def enqueue(self, item):
_redis = await aioredis.create_redis_pool(address=("localhost", 6379))
await _redis.lpush(self.queue_name, json.dumps(item))
async def dequeue(self):
_redis = await aioredis.create_redis_pool(address=("localhost", 6379))
x = await _redis.brpop(self.queue_name)
dequed_item = json.loads(x[1].decode())
return dequed_item
loop = asyncio.get_event_loop()
outboundqueue = RedisExampleQueue()
cli = naz.Client(
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
outboundqueue=outboundqueue,
)
try:
# 1. connect to the SMSC host
# 2. bind to the SMSC host
# 3. send any queued messages to SMSC
# 4. read any data from SMSC
# 5. continually check the state of the SMSC
tasks = asyncio.gather(
cli.connect(),
cli.tranceiver_bind(),
cli.dequeue_messages(),
cli.receive_data(),
cli.enquire_link(),
)
tasks = asyncio.gather(cli.dequeue_messages(), cli.receive_data(), cli.enquire_link())
loop.run_until_complete(tasks)
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)
loop.run_until_complete(
cli.submit_sm(
short_message="Hello World-{0}".format(str(i)),
log_id="myid12345",
source_addr="254722111111",
destination_addr="254722999999",
)
)
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.