Metadata-Version: 2.1
Name: naz
Version: 0.7.5
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| |ci| |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.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)
                loop.run_until_complete(
                    cli.submit_sm(
                        short_message="Hello World-{0}".format(str(i)),
                        log_id="myid12345",
                        source_addr="254722111111",
                        destination_addr="254722999999",
                    )
                )
        
            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(
                ...
                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. 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):
                loop.run_until_complete(
                    cli.submit_sm(
                        short_message="Hello World-{0}".format(str(i)),
                        log_id="myid12345",
                        source_addr="254722111111",
                        destination_addr="254722999999",
                    )
                )
        
        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)
                loop.run_until_complete(
                    cli.submit_sm(
                        short_message="Hello World-{0}".format(str(i)),
                        log_id="myid12345",
                        source_addr="254722111111",
                        destination_addr="254722999999",
                    )
                )
        
        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
        -------
        
        .. |Codacy Badge| image:: https://api.codacy.com/project/badge/Grade/616e5c6664dd4c1abb26f34f0bf566ae
           :target: https://www.codacy.com/app/komuw/naz
        .. |ci| image:: https://github.com/komuw/naz/workflows/naz%20ci/badge.svg
           :target: https://github.com/komuw/naz/actions
        .. |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.7
Provides-Extra: dev
Provides-Extra: test
Provides-Extra: benchmarks
