Metadata-Version: 2.1
Name: naz
Version: 0.8.0
Summary: Naz is an async 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>`__
        `ci <https://github.com/komuw/naz/actions>`__
        `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.7+
        
        | naz is in active development and it’s API may change in backward
          incompatible ways.
        | https://pypi.python.org/pypi/naz
        
        Comprehensive documetion is available ->
        `Documentation <https://komuw.github.io/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>`__ + `integration with bug trackers(eg Sentry
          ) <#23-integration-with-bug-trackers>`__ + `Rate
          limiting <#3-rate-limiting>`__
        | + `Throttle handling <#4-throttle-handling>`__
        | + `Broker <#5-broker>`__
        
        `Benchmarks <./benchmarks/README.md>`__
        
        Installation
        ------------
        
        .. code:: shell
        
           pip install naz
        
        Usage
        -----
        
        1. As a library
        ^^^^^^^^^^^^^^^
        
        .. code:: python
        
           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:**
        | (a) For more information about all the parameters that ``naz.Client``
          can take, consult the `documentation
          here <https://komuw.github.io/naz/client.html>`__
        | (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 broker.
        
        2. As a cli app
        ^^^^^^^^^^^^^^^
        
        | naz also ships with a commandline interface app called ``naz-cli``.
        | create a python config file, eg;
        | ``/tmp/my_config.py``
        
        .. code:: python
        
           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:
        
        .. code:: python
        
           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_config.client``
        
        .. code:: shell
        
                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'}
        
        | **NB:**
        | (a) The \`\ ``naz`` config file(ie, the dotted path we pass in to
          ``naz-cli --client``) is any python file that has a
          ``naz.Client instance <https://komuw.github.io/naz/client.html>``\ \_
          declared in it.
        | (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 --client examples.example_config.client``
        
        To see help:
        
        ``naz-cli --help``
        
        .. code:: shell
        
           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
        ^^^^^^^^^^^^^^^^^^^
        
        | 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.
        
        .. code:: python
        
           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,
           )
        
        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
        
           logger = naz.log.SimpleLogger(
                           "naz.client",
                           log_metadata={ "environment": "production", "release": "v5.6.8"}
                       )
           cli = naz.Client(
               ...
               logger=logger,
           )
        
        | and then these will show up in all log events.
        | by default, ``naz`` annotates all log events with ``smsc_host``,
          ``system_id`` and ``client_id``
        
        2.2 hooks
        '''''''''
        
        | a hook is a class with two methods ``to_smsc`` and ``from_smsc``, ie
          it implements ``naz``\ ’s BaseHook interface as `defined
          here <https://github.com/komuw/naz/blob/master/naz/hooks.py>`__.
        | ``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.
        | 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 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;
        
        .. code:: python
        
           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
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        
        | If you want to integrate ``naz`` with your bug/issue tracker of
          choice, all you have to do is use their logging integrator.
        | As an example, to integrate ``naz`` with
          `sentry <https://sentry.io/>`__, 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``
        
        .. code:: python
        
           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()
           )
        
        | then run the ``naz-cli`` as usual:
        | ``naz-cli --client tmp.my_config.my_naz_client``
        | And just like that you are good to go. This is what errors from
          ``naz`` will look like on sentry(sans the emojis, ofcourse):
        
        .. figure:: https://raw.githubusercontent.com/komuw/naz/master/documentation/sphinx-docs/naz-sentry.png
           :alt: naz integration with sentry
        
           naz integration with sentry
        
        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 naz
        
           myLimiter = naz.ratelimiter.SimpleRateLimiter(send_rate=35)
           cli = naz.Client(
               ...
               rate_limiter=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. 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 ``BaseBroker`` interface as `defined
          here <https://github.com/komuw/naz/blob/master/naz/broker.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 broker.
        
        | ``naz`` ships with a simple broker implementation called
          ```naz.broker.SimpleBroker`` <https://github.com/komuw/naz/blob/master/naz/broker.py>`__.
        | An example of using that;
        
        .. code:: python
        
           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;
        
        .. code:: python
        
           # 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)
               )
        
        Here is another example, but where we now use redis for our broker;
        
        .. code:: python
        
           import json
           import asyncio
           import naz
           import aioredis
        
           class RedisExampleBroker(naz.broker.BaseBroker):
               """
               use redis as our broker.
               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()
           broker = RedisExampleBroker()
           cli = naz.Client(
               smsc_host="127.0.0.1",
               smsc_port=2775,
               system_id="smppclient1",
               password="password",
               broker=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(),
               )
               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;
        
        .. code:: python
        
           # queue messages to send
           for i in range(0, 5):
               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)
               )
        
        6. Well written(if I have to say so myself):
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        
        -  `Good test coverage <https://codecov.io/gh/komuw/naz>`__
        -  `Passing continous
           integration <https://github.com/komuw/naz/actions>`__
        -  `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.7
Provides-Extra: dev
Provides-Extra: test
Provides-Extra: benchmarks
