Metadata-Version: 2.1
Name: naz
Version: 0.0.2b0
Summary: Naz is an SMPP client.
Home-page: https://github.com/komuw/naz
Author: komuW
Author-email: komuw05@gmail.com
License: MIT
Description: naz
        ---
        
        `Codacy Badge <https://www.codacy.com/app/komuw/naz>`__ `Build
        Status <https://travis-ci.com/komuw/naz>`__
        `codecov <https://codecov.io/gh/komuw/naz>`__ `Code style:
        black <https://github.com/komuw/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 <https://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer>`__
        
        | naz currently only supports SMPP version 3.4.
        | naz has no third-party dependencies and it requires python version
          3.6+
        
        | naz is in active development and it’s API may change in backward
          incompatible ways.
        | https://pypi.python.org/pypi/naz
        
        | **Contents:**
        | `Installation <#installation>`__
        | `Usage <#usage>`__
        | + `As a library <#1-as-a-library>`__
        | + `As cli app <#2-as-a-cli-app>`__
        
        | `Features <#features>`__
        | + `async everywhere <#1-async-everywhere>`__
        | + `monitoring-and-observability <#2-monitoring-and-observability>`__
        | + `logging <#21-logging>`__
        | + `hooks <#22-hooks>`__ + `Rate limiting <#3-rate-limiting>`__
        | + `Throttle handling <#4-throttle-handling>`__
        | + `Queuing <#5-queuing>`__
        
        Installation
        ------------
        
        .. code:: shell
        
           pip install naz
        
        Usage
        -----
        
        1. As a library
        ^^^^^^^^^^^^^^^
        
        .. code:: python
        
           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 = {
                   "smpp_event": "submit_sm",
                   "short_message": "Hello World-{0}".format(str(i)),
                   "correlation_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.close()
        
        | **NB:**
        | (a) For more information about all the parameters that ``naz.Client``
          can take, consult the `documentation
          here <https://github.com/komuw/naz/blob/master/docs/config.md>`__
        | (b) More `examples can be found
          here <https://github.com/komuw/naz/tree/master/examples>`__
        | (c) if you need a SMSC server/gateway to test with, you can use the
          `docker-compose file in this
          repo <https://github.com/komuw/naz/blob/master/docker-compose.yml>`__
          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 outboundqueue.
        
        2. As a cli app
        ^^^^^^^^^^^^^^^
        
        | naz also ships with a commandline interface app called ``naz-cli``.
        | create a json config file, eg;
        | ``/tmp/my_config.json``
        
        ::
        
           {
             "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:
        
        .. code:: python
        
           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()
        
        | then run:
        | ``naz-cli --config /tmp/my_config.json``
        
        .. code:: shell
        
                Naz: the SMPP client.
        
           {'event': 'connect', 'stage': 'start'} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
           {'event': 'connect', 'stage': 'end'} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
           {'event': 'tranceiver_bind', 'stage': 'start'} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
           {'event': 'send_data', 'stage': 'start', 'smpp_command': 'bind_transceiver', 'correlation_id': None} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
           {'event': 'SimpleHook.request', 'stage': 'start', 'correlation_id': None} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
           {'event': 'send_data', 'stage': 'end', 'smpp_command': 'bind_transceiver', 'correlation_id': None} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
           {'event': 'tranceiver_bind', 'stage': 'end'} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
           {'event': 'send_forever', 'stage': 'start'} {'smsc_host': '127.0.0.1', 'system_id': 'smppclient1'}
        
        | **NB:**
        | (a) For more information about the ``naz`` config file, consult the
          `documentation
          here <https://github.com/komuw/naz/blob/master/docs/config.md>`__
        | (b) More `examples can be found
          here <https://github.com/komuw/naz/tree/master/examples>`__. As an
          example, start the SMSC simulator(\ ``docker-compose up``) then in
          another terminal run,
          ``naz-cli --config examples/example_config.json``
        
        To see help:
        
        ``naz-cli --help``
        
        .. code:: shell
        
           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
        ^^^^^^^^^^^^^^^^^^^
        
        | SMPP is an async protocol; the client can send a request and only get
          a response from SMSC/server 20mins later out of band.
        | It thus makes sense to write your SMPP client in an async manner. We
          leverage python3’s async/await to do so. And if you do not like
          python’s inbuilt event loop, you can bring your own. eg; to use
          `uvloop <https://github.com/MagicStack/uvloop>`__;
        
        .. code:: python
        
           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
        '''''''''''
        
        | In ``naz`` you have the ability to annotate all the log events that
          ``naz`` will generate with anything you want.
        | So, for example if you wanted to annotate all log-events with a
          release version and your app’s running environment.
        
        .. code:: python
        
           import naz
        
           cli = naz.Client(
               ...
               log_metadata={ "environment": "production", "release": "canary"},
           )
        
        | and then these will show up in all log events.
        | by default, ``naz`` annotates all log events with ``smsc_host`` and
          ``system_id``
        
        2.2 hooks
        '''''''''
        
        | a hook is a class with two methods ``request`` and ``response``, ie it
          implements ``naz``\ ’s BaseHook interface as `defined
          here <https://github.com/komuw/naz/blob/master/naz/hooks.py>`__.
        | ``naz`` will call the ``request`` method just before sending request
          to SMSC and also call the ``response`` method just after getting
          response from SMSC.
        | the default hook that ``naz`` uses is ``naz.hooks.SimpleHook`` which
          does nothing but logs.
        | If you wanted, for example to keep metrics of all requests and
          responses to SMSC in your `prometheus <https://prometheus.io/>`__
          setup;
        
        .. code:: python
        
           import naz
           from prometheus_client import Counter
        
           class MyPrometheusHook(naz.hooks.BaseHook):
               async def request(self, smpp_event, correlation_id):
                   c = Counter('my_requests', 'Description of counter')
                   c.inc() # Increment by 1
               async def response(self, smpp_event, correlation_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;
        
        .. code:: python
        
           import sqlite3
           import naz
        
           class SetMessageStateHook(naz.hooks.BaseHook):
               async def request(self, smpp_event, correlation_id):
                   pass
               async def response(self, smpp_event, correlation_id):
                   if smpp_event == "deliver_sm":
                       conn = sqlite3.connect('mySmsDB.db')
                       c = conn.cursor()
                       t = (correlation_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
        ^^^^^^^^^^^^^^^^
        
        | Sometimes you want to control the rate at which the client sends
          requests to an SMSC/server. ``naz`` lets you do this, by allowing you
          to specify a custom rate limiter. By default, ``naz`` uses a simple
          token bucket rate limiting algorithm `implemented
          here <https://github.com/komuw/naz/blob/master/naz/ratelimiter.py>`__.
        | You can customize ``naz``\ ’s ratelimiter or even write your own
          ratelimiter (if you decide to write your own, you just have to satisfy
          the ``BaseRateLimiter`` interface `found
          here <https://github.com/komuw/naz/blob/master/naz/ratelimiter.py>`__
          )
        | To customize the default ratelimiter, for example to send at a rate of
          35 requests per second.
        
        .. code:: python
        
           import logging
           import naz
           logger = logging.getLogger()
        
           myLimiter = naz.ratelimiter.SimpleRateLimiter(logger=logger, send_rate=35)
           cli = naz.Client(
               ...
               rateLimiter=myLimiter,
           )
        
        4. Throttle handling
        ^^^^^^^^^^^^^^^^^^^^
        
        | Sometimes, when a client sends requests to an SMSC/server, the SMSC
          may reply with an ``ESME_RTHROTTLED`` status.
        | This can happen, say if the client has surpassed the rate at which it
          is supposed to send requests at, or the SMSC is under load or for
          whatever reason ¯_(ツ)_/¯
        | The way ``naz`` handles throtlling is via Throttle handlers.
        | A throttle handler is a class that implements the
          ``BaseThrottleHandler`` interface as `defined
          here <https://github.com/komuw/naz/blob/master/naz/throttle.py>`__
        | ``naz`` calls that class’s ``throttled`` method everytime it gets a
          throttled(\ ``ESME_RTHROTTLED``) response from the SMSC and it also
          calls that class’s ``not_throttled`` method everytime it gets a
          response from the SMSC and the response is NOT a throttled response.
        | ``naz`` will also call that class’s ``allow_request`` method just
          before sending a request to SMSC. the ``allow_request`` method should
          return ``True`` if requests should be allowed to SMSC else it should
          return ``False`` if requests should not be sent.
        | By default ``naz`` uses
          ```naz.throttle.SimpleThrottleHandler`` <https://github.com/komuw/naz/blob/master/naz/throttle.py>`__
          to handle throttling.
        | The way ``SimpleThrottleHandler`` works is, it calculates the
          percentage of responses that are throttle responses and then denies
          outgoing requests(towards SMSC) if percentage of responses that are
          throttles goes above a certain metric.
        | 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;
        
        .. code:: python
        
           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
        ^^^^^^^^^^
        
        | **How does your application and ``naz`` talk with each other?**
        | It’s via a queuing interface. Your application queues messages to a
          queue, ``naz`` consumes from that queue and then ``naz`` sends those
          messages to SMSC/server.
        | You can implement the queuing mechanism any way you like, so long as
          it satisfies the ``BaseOutboundQueue`` interface as `defined
          here <https://github.com/komuw/naz/blob/master/naz/q.py>`__
        | Your application should call that class’s ``enqueue`` method to -you
          guessed it- enqueue messages to the queue while ``naz`` will call the
          class’s ``dequeue`` method to consume from the queue.
        | Your application should enqueue a dictionary object with any
          parameters but the following are mandatory:
        
        .. code:: bash
        
           {
               "smpp_event": "submit_sm",
               "short_message": string,
               "correlation_id": string,
               "source_addr": string,
               "destination_addr": string
           }
        
        | ``naz`` ships with a simple queue implementation called
          ```naz.q.SimpleOutboundQueue`` <https://github.com/komuw/naz/blob/master/naz/q.py>`__.
        | An example of using that;
        
        .. code:: python
        
           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.close()
        
        then in your application, queue items to the queue;
        
        .. code:: python
        
           # queue messages to send
           for i in range(0, 4):
               item_to_enqueue = {
                   "smpp_event": "submit_sm",
                   "short_message": "Hello World-{0}".format(str(i)),
                   "correlation_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;
        
        .. code:: python
        
           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.close()
        
        then queue on your application side;
        
        .. code:: python
        
           # queue messages to send
           for i in range(0, 5):
               print("submit_sm round:", i)
               item_to_enqueue = {
                   "smpp_event": "submit_sm",
                   "short_message": "Hello World-{0}".format(str(i)),
                   "correlation_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):
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        
        -  `Good test coverage <https://codecov.io/gh/komuw/naz>`__
        -  `Passing continous
           integration <https://travis-ci.com/komuw/naz/builds>`__
        -  `statically analyzed
           code <https://www.codacy.com/app/komuw/naz/dashboard>`__
        
        Development setup
        -----------------
        
        -  see `documentation on
           contributing <https://github.com/komuw/naz/blob/master/.github/CONTRIBUTING.md>`__
        -  **NB:** I make no commitment of accepting your pull requests.
        
        ## TODO
        -------
        
Keywords: naz,smpp,smpp-client,smpp-protocol,smpp-library
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Security
Classifier: Topic :: System :: Installation/Setup
Classifier: Topic :: System :: Networking
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.6
Provides-Extra: dev
Provides-Extra: test
