This release is a pre-release and may not be stable for production use.
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
Metadata
Release files for naz 0.3.0b1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| naz-0.3.0b1.tar.gz | 36.9 kB | Without provenance | Details for naz-0.3.0b1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| naz-0.3.0b1-py3-none-any.whl | Python 3 | none | any | Details for naz-0.3.0b1-py3-none-any.whl |
Total release size: 70.8 kB
Release files / naz-0.3.0b1.tar.gz
| Download URL | naz-0.3.0b1.tar.gz |
|---|---|
| Size | 36.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7852d5496794417c0c1e3093180ed03735b55288a5b1780336e74bfc31dce7db
|
|
BLAKE2b-256 checksum How to use checksums |
33169feb464278a9a9d7db0d8974797fa909cb47cfab51200c5e6722c1cdb4b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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
|
Release files / naz-0.3.0b1-py3-none-any.whl
| Download URL | naz-0.3.0b1-py3-none-any.whl |
|---|---|
| Size | 33.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
77c2854ab630ff3961d50f94e1acd44c74cfc5d43d7186e680689c0c925eafe0
|
|
BLAKE2b-256 checksum How to use checksums |
3ec739b6407b23fde6278e3be305be0f5cd6f471a9af5cef9dbc785e53c583d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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
|
Security for naz 0.3.0b1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.