Introduction to naz¶
naz is an async SMPP client. It’s name is derived from Kenyan hip hop artiste, Nazizi.
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
naz is in active development and it’s API may change in backward incompatible ways.
Table of Contents
1 Installation¶
pip install naz
2.1 As a library¶
import asyncio
import naz
loop = asyncio.get_event_loop()
broker = naz.broker.SimpleBroker(maxsize=1000)
cli = naz.Client(
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
broker=broker,
)
# queue messages to send
for i in range(0, 4):
print("submit_sm round:", i)
msg = naz.protocol.SubmitSM(
short_message="Hello World-{0}".format(str(i)),
log_id="myid12345",
source_addr="254722111111",
destination_addr="254722999999",
)
loop.run_until_complete(
cli.send_message(msg)
)
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()
NB:
For more information about all the parameters that naz.Client can take, consult the docs
More examples can be found here
if you need an SMSC server/gateway to test with, you can use the docker-compose file in the
naz
repo to bring up an SMSC simulator. That docker-compose file also has a redis and rabbitMQ container if you would like to use those as your naz.broker.BaseBroker.
2.2 As a cli app¶
naz
also ships with a commandline interface app called naz-cli
(it is also installed by default when you pip install naz).
create a python config file, eg; /tmp/my_app.py
import naz
from myfile import ExampleBroker
client = naz.Client(
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
broker=ExampleBroker()
)
and a python file, myfile.py (in the current working directory) with the contents:
import asyncio
import naz
class ExampleBroker(naz.broker.BaseBroker):
def __init__(self):
loop = asyncio.get_event_loop()
self.queue = asyncio.Queue(maxsize=1000, loop=loop)
async def enqueue(self, message):
self.queue.put_nowait(message)
async def dequeue(self):
return await self.queue.get()
then run:
naz-cli --client tmp.my_app.client
NB:
The
naz
config file(ie, the dotted path we pass in tonaz-cli --client
) is any python file that has a naz.Client instance declared in it.
More examples can be found; examples As an example, start the SMSC simulator(
docker-compose up
) then in another terminal run,naz-cli --client examples.example_config.client
3.1 async everywhere¶
import naz
import asyncio
loop = asyncio.get_event_loop()
broker = naz.broker.SimpleBroker(maxsize=1000)
cli = naz.Client(
smsc_host="127.0.0.1",
smsc_port=2775,
system_id="smppclient1",
password="password",
broker=broker,
)
3.2.1 logging¶
naz
you have the ability to annotate all the log events that naz will generate with anything you want.import naz
logger = naz.log.SimpleLogger(
"naz.client",
log_metadata={ "environment": "production", "release": "v5.6.8"}
)
cli = naz.Client(
...
logger=logger,
)
naz
also gives you the ability to supply your own logger. All you have to do is pass in a python logging.Loggernaz
to use key=value style of logging, then just create a logger that does just that:import naz
class KVlogger(logging.Logger):
"""
A simple implementation of a key=value
log renderer.
"""
def __init__(self, name, level=logging.INFO):
super(KVlogger, self).__init__(name, level)
handler = logging.StreamHandler()
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
self.addHandler(handler)
self.setLevel("DEBUG")
def log(self, level, msg, *args, **kwargs):
new_msg = self._process_msg(msg)
return super(KVlogger, self).log(level, new_msg, *args, **kwargs)
def _process_msg(self, message):
# implementation of key=value log renderer
new_msg = ", ".join("{0}={1}".format(k, v) for k, v in message.items())
return new_msg
kvLog = KVlogger()
cli = naz.Client(
...
logger=kvLog,
)
3.2.2 hooks¶
naz
will call the to_smsc method just before sending data to SMSC and also call the from_smsc method just after getting data from SMSC.naz.hooks.SimpleHook
which just logs the request and response.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,
)
3.2.3 integration with bug trackers¶
naz
with sentry, all you have to do is import and init the sentry sdk. A good place to do that would be in the naz config file, ie;/tmp/my_config.py
import naz
from myfile import ExampleBroker
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",
broker=ExampleBroker()
)
naz-cli --client tmp.my_config.my_naz_client
3.3 Rate limiting¶
naz
lets you do this, by allowing you to specify a custom rate limiter.naz.ratelimiter.SimpleRateLimiter
import naz
myLimiter = naz.ratelimiter.SimpleRateLimiter(send_rate=35)
cli = naz.Client(
...
rate_limiter=myLimiter,
)
3.4 Throttle handling¶
The way naz handles throtlling is via Throttle handlers. A throttle handler is a class that implements the naz.throttle.BaseThrottleHandler interface
By default naz uses naz.throttle.SimpleThrottleHandler
to handle throttling.
As an example if you want to deny outgoing requests if the percentage of throttles is above 1.2% over a period of 180 seconds and the total number of responses from SMSC is greater than 45, then;
from naz.throttle import SimpleThrottleHandler
throttler = SimpleThrottleHandler(sampling_period=180,
sample_size=45,
deny_request_at=1.2)
cli = naz.Client(
...
throttle_handler=throttler,
)
3.5 Broker¶
How does your application and naz talk with each other?
It’s via a broker interface. Your application queues messages to a broker, naz
consumes from that broker and then naz sends those messages to SMSC/server.
You can implement the broker mechanism any way you like, so long as it satisfies the naz.broker.BaseBroker interface
naz.broker.SimpleBroker
naz.broker.SimpleBroker
should only be used for demo/test purposes.An example of using that broker;
import asyncio
import naz
loop = asyncio.get_event_loop()
my_broker = naz.broker.SimpleBroker(maxsize=1000) # can hold upto 1000 items
cli = naz.Client(
...
broker=my_broker,
)
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):
msg = naz.protocol.SubmitSM(
short_message="Hello World-{0}".format(str(i)),
log_id="myid12345",
source_addr="254722111111",
destination_addr="254722999999",
)
loop.run_until_complete(
cli.send_message(msg)
)
then in your application, queue items to the queue;
# queue messages to send
for i in range(0, 4):
msg = naz.protocol.SubmitSM(
short_message="Hello World-{0}".format(str(i)),
log_id="myid12345",
source_addr="254722111111",
destination_addr="254722999999",
)
loop.run_until_complete(
cli.send_message(msg)
)
4 Benchmarks¶
Benchmarks can be found; benchmarks