Metadata-Version: 2.1
Name: naz
Version: 0.0.6
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| |Build Status| |codecov| |Code style: black|
        
        | 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 = {
                    "version": "1",
                    "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("naz.rateLimiter")
        
            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/json object with any
          parameters but the following are mandatory:
        
        .. code:: bash
        
            {
                "version": "1",
                "smpp_event": "submit_sm",
                "short_message": string,
                "correlation_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 <https://github.com/komuw/naz/blob/master/docs/config.md>`__
        
        | ``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 = {
                    "version": "1",
                    "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 = {
                    "version": "1",
                    "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
        -------
        
        .. |Codacy Badge| image:: https://api.codacy.com/project/badge/Grade/616e5c6664dd4c1abb26f34f0bf566ae
           :target: https://www.codacy.com/app/komuw/naz
        .. |Build Status| image:: https://travis-ci.com/komuw/naz.svg?branch=master
           :target: https://travis-ci.com/komuw/naz
        .. |codecov| image:: https://codecov.io/gh/komuw/naz/branch/master/graph/badge.svg
           :target: https://codecov.io/gh/komuw/naz
        .. |Code style: black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
           :target: https://github.com/komuw/naz
        
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: test
Provides-Extra: dev
