{"id":21626943,"url":"https://github.com/komuw/naz","last_synced_at":"2025-10-19T07:32:28.905Z","repository":{"id":57445045,"uuid":"138318038","full_name":"komuw/naz","owner":"komuw","description":"naz is an async SMPP client.","archived":false,"fork":false,"pushed_at":"2020-10-02T18:52:39.000Z","size":13483,"stargazers_count":39,"open_issues_count":24,"forks_count":11,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-25T09:03:39.526Z","etag":null,"topics":["smpp","smpp-client","smpp-library","smpp-protocol","smpp-server","smpp-simulator"],"latest_commit_sha":null,"homepage":"https://komuw.github.io/naz/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/komuw.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-22T15:20:57.000Z","updated_at":"2024-07-16T13:06:51.000Z","dependencies_parsed_at":"2022-09-26T17:30:36.782Z","dependency_job_id":null,"html_url":"https://github.com/komuw/naz","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/komuw%2Fnaz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/komuw%2Fnaz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/komuw%2Fnaz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/komuw%2Fnaz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/komuw","download_url":"https://codeload.github.com/komuw/naz/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248034898,"owners_count":21037081,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["smpp","smpp-client","smpp-library","smpp-protocol","smpp-server","smpp-simulator"],"created_at":"2024-11-25T01:14:55.031Z","updated_at":"2025-10-19T07:32:28.845Z","avatar_url":"https://github.com/komuw.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## naz          \n\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/616e5c6664dd4c1abb26f34f0bf566ae)](https://www.codacy.com/app/komuw/naz)\n[![ci](https://github.com/komuw/naz/workflows/naz%20ci/badge.svg)](https://github.com/komuw/naz/actions)\n[![codecov](https://codecov.io/gh/komuw/naz/branch/master/graph/badge.svg)](https://codecov.io/gh/komuw/naz)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/komuw/naz)\n\n\nnaz is an async SMPP client.           \nIt's name is derived from Kenyan hip hop artiste, Nazizi.                             \n\n\u003e 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)\n\nnaz currently only supports SMPP version 3.4.       \nnaz has no third-party dependencies and it requires python version 3.7+\n\n\nnaz is in active development and it's API may change in backward incompatible ways.               \n[https://pypi.python.org/pypi/naz](https://pypi.python.org/pypi/naz)                 \n\n\nComprehensive documetion is available -\u003e [Documentation](https://komuw.github.io/naz)\n\n\n**Contents:**          \n[Installation](#installation)         \n[Usage](#usage)                  \n  + [As a library](#1-as-a-library)            \n  + [As cli app](#2-as-a-cli-app)            \n\n[Features](#features)               \n  + [async everywhere](#1-async-everywhere)            \n  + [monitoring-and-observability](#2-monitoring-and-observability)            \n    + [logging](#21-logging)            \n    + [hooks](#22-hooks)\n    + [integration with bug trackers(eg Sentry )](#23-integration-with-bug-trackers)\n  + [Rate limiting](#3-rate-limiting)            \n  + [Throttle handling](#4-throttle-handling)            \n  + [Broker](#5-broker)      \n      \n[Benchmarks](./benchmarks/README.md)\n\n\n## Installation\n\n```shell\npip install naz\n```           \n\n\n## Usage\n\n#### 1. As a library\n```python\nimport asyncio\nimport naz\n\nloop = asyncio.get_event_loop()\nbroker = naz.broker.SimpleBroker(maxsize=1000)\ncli = naz.Client(\n    smsc_host=\"127.0.0.1\",\n    smsc_port=2775,\n    system_id=\"smppclient1\",\n    password=\"password\",\n    broker=broker,\n)\n\n# queue messages to send\nfor i in range(0, 4):\n    print(\"submit_sm round:\", i)\n    msg = naz.protocol.SubmitSM(\n                short_message=\"Hello World-{0}\".format(str(i)),\n                log_id=\"myid12345\",\n                source_addr=\"254722111111\",\n                destination_addr=\"254722999999\",\n            )\n    loop.run_until_complete(\n          cli.send_message(msg)\n    )\n\n\ntry:\n    # 1. connect to the SMSC host\n    # 2. bind to the SMSC host\n    # 3. send any queued messages to SMSC\n    # 4. read any data from SMSC\n    # 5. continually check the state of the SMSC\n    tasks = asyncio.gather(\n        cli.connect(),\n        cli.tranceiver_bind(),\n        cli.dequeue_messages(),\n        cli.receive_data(),\n        cli.enquire_link(),\n    )\n    loop.run_until_complete(tasks)\nexcept Exception as e:\n    print(\"exception occured. error={0}\".format(str(e)))\nfinally:\n    loop.run_until_complete(cli.unbind())\n    loop.stop()\n```\n**NB:**      \n(a) For more information about all the parameters that `naz.Client` can take, consult the [documentation here](https://komuw.github.io/naz/client.html)            \n(b) More [examples can be found here](https://github.com/komuw/naz/tree/master/examples)         \n(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.        \nThat docker-compose file also has a redis and rabbitMQ container if you would like to use those as your broker.\n\n\n#### 2. As a cli app\nnaz also ships with a commandline interface app called `naz-cli`.            \ncreate a python config file, eg;            \n`/tmp/my_config.py`\n```python\nimport naz\nfrom myfile import ExampleBroker\n\nclient = naz.Client(\n    smsc_host=\"127.0.0.1\",\n    smsc_port=2775,\n    system_id=\"smppclient1\",\n    password=\"password\",\n    broker=ExampleBroker()\n)\n```\nand a python file, `myfile.py` (in the current working directory) with the contents:\n\n```python\nimport asyncio\nimport naz\n\nclass ExampleBroker(naz.broker.BaseBroker):\n    def __init__(self):\n        loop = asyncio.get_event_loop()\n        self.queue = asyncio.Queue(maxsize=1000, loop=loop)\n    async def enqueue(self,  message):\n        self.queue.put_nowait(message)\n    async def dequeue(self):\n        return await self.queue.get()\n```\nthen \nrun:                \n`naz-cli --client tmp.my_config.client`\n```shell\n\t Naz: the SMPP client.\n\n{'event': 'naz.Client.connect', 'stage': 'start', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}\n{'event': 'naz.Client.connect', 'stage': 'end', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}\n{'event': 'naz.Client.tranceiver_bind', 'stage': 'start', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}\n{'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'}\n{'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'}\n{'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'}\n{'event': 'naz.Client.tranceiver_bind', 'stage': 'end', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}\n{'event': 'naz.Client.dequeue_messages', 'stage': 'start', 'environment': 'production', 'release': 'canary', 'smsc_host': '127.0.0.1', 'system_id': 'smppclient1', 'client_id': '2VU55VT86KHWXTW7X'}\n```              \n             \n**NB:**      \n(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 \u003chttps://komuw.github.io/naz/client.html\u003e`_ declared in it.                \n(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`\n\nTo see help:\n\n`naz-cli --help`   \n```shell         \nnaz is an async SMPP client.     \nexample usage: naz-cli --client path.to.my_config.client\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --version             The currently installed naz version.\n  --client CLIENT       The config file to use. eg: --client path.to.my_config.client\n```\n\n\n\n## Features\n#### 1. async everywhere\nSMPP is an async protocol; the client can send a request and only get a response from SMSC/server 20mins later out of band.               \nIt thus makes sense to write your SMPP client in an async manner. We leverage python3's async/await to do so. \n```python\nimport naz\nimport asyncio\n\nloop = asyncio.get_event_loop()\nbroker = naz.broker.SimpleBroker(maxsize=1000)\ncli = naz.Client(\n    smsc_host=\"127.0.0.1\",\n    smsc_port=2775,\n    system_id=\"smppclient1\",\n    password=\"password\",\n    broker=broker,\n)\n```\n\n#### 2. monitoring and observability\nit's a loaded term, I know.                  \n\n##### 2.1 logging\nIn `naz` you have the ability to annotate all the log events that `naz` will generate with anything you want.        \nSo, for example if you wanted to annotate all log-events with a release version and your app's running environment.\n```python\nimport naz\n\nlogger = naz.log.SimpleLogger(\n                \"naz.client\",\n                log_metadata={ \"environment\": \"production\", \"release\": \"v5.6.8\"}\n            )\ncli = naz.Client(\n    ...\n    logger=logger,\n)\n```\nand then these will show up in all log events.             \nby default, `naz` annotates all log events with `smsc_host`, `system_id` and `client_id`\n\n##### 2.2 hooks\na 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).           \n`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.       \nthe default hook that `naz` uses is `naz.hooks.SimpleHook` which does nothing but logs.             \nIf you wanted, for example to keep metrics of all requests and responses to SMSC in your [prometheus](https://prometheus.io/) setup;\n```python\nimport naz\nfrom prometheus_client import Counter\n\nclass MyPrometheusHook(naz.hooks.BaseHook):\n    async def to_smsc(self, smpp_command, log_id, hook_metadata, pdu):\n        c = Counter('my_requests', 'Description of counter')\n        c.inc() # Increment by 1\n    async def from_smsc(self,\n                    smpp_command,\n                    log_id,\n                    hook_metadata,\n                    status,\n                    pdu):\n        c = Counter('my_responses', 'Description of counter')\n        c.inc() # Increment by 1\n\nmyHook = MyPrometheusHook()\ncli = naz.Client(\n    ...\n    hook=myHook,\n)\n```\nanother example is if you want to update a database record whenever you get a delivery notification event;\n```python\nimport sqlite3\nimport naz\n\nclass SetMessageStateHook(naz.hooks.BaseHook):\n    async def to_smsc(self, smpp_command, log_id, hook_metadata, pdu):\n        pass\n    async def from_smsc(self,\n                    smpp_command,\n                    log_id,\n                    hook_metadata,\n                    status,\n                    pdu):\n        if smpp_command == naz.SmppCommand.DELIVER_SM:\n            conn = sqlite3.connect('mySmsDB.db')\n            c = conn.cursor()\n            t = (log_id,)\n            # watch out for SQL injections!!\n            c.execute(\"UPDATE SmsTable SET State='delivered' WHERE CorrelatinID=?\", t)\n            conn.commit()\n            conn.close()\n\nstateHook = SetMessageStateHook()\ncli = naz.Client(\n    ...\n    hook=stateHook,\n)\n```\n\n\n#### 2.3 integration with bug trackers\nIf you want to integrate `naz` with your bug/issue tracker of choice, all you have to do is use their logging integrator.   \nAs 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;  \n`/tmp/my_config.py`\n```python\nimport naz\nfrom myfile import ExampleBroker\n\nimport sentry_sdk # import sentry SDK\nsentry_sdk.init(\"https://\u003cYOUR_SENTRY_PUBLIC_KEY\u003e@sentry.io/\u003cYOUR_SENTRY_PROJECT_ID\u003e\")\n\nmy_naz_client = naz.Client(\n    smsc_host=\"127.0.0.1\",\n    smsc_port=2775,\n    system_id=\"smppclient1\",\n    password=\"password\",\n    broker=ExampleBroker()\n)\n```\n\nthen run the `naz-cli` as usual:                \n`naz-cli --client tmp.my_config.my_naz_client`    \nAnd just like that you are good to go. This is what errors from `naz` will look like on sentry(sans the emojis, ofcourse):   \n\n![naz integration with sentry](https://raw.githubusercontent.com/komuw/naz/master/documentation/sphinx-docs/naz-sentry.png \"naz integration with sentry\")\n\n\n\n#### 3. Rate limiting\nSometimes 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.\nBy default, `naz` uses a simple token bucket rate limiting algorithm [implemented here](https://github.com/komuw/naz/blob/master/naz/ratelimiter.py).         \nYou 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) )            \nTo customize the default ratelimiter, for example to send at a rate of 35 requests per second.\n```python\nimport naz\n\nmyLimiter = naz.ratelimiter.SimpleRateLimiter(send_rate=35)\ncli = naz.Client(\n    ...\n    rate_limiter=myLimiter,\n)\n```\n\n#### 4. Throttle handling\nSometimes, when a client sends requests to an SMSC/server, the SMSC may reply with an `ESME_RTHROTTLED` status.           \nThis 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 ¯\\_(ツ)_/¯           \nThe way `naz` handles throtlling is via Throttle handlers.                \nA throttle handler is a class that implements the `BaseThrottleHandler` interface as [defined here](https://github.com/komuw/naz/blob/master/naz/throttle.py)            \n`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 \neverytime it gets a response from the SMSC and the response is NOT a throttled response.            \n`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 \nelse it should return `False` if requests should not be sent.                 \nBy default `naz` uses [`naz.throttle.SimpleThrottleHandler`](https://github.com/komuw/naz/blob/master/naz/throttle.py) to handle throttling.            \nThe 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.         \nAs 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;\n```python\nimport naz\n\nthrottler = naz.throttle.SimpleThrottleHandler(sampling_period=180,\n                                               sample_size=45,\n                                               deny_request_at=1.2)\ncli = naz.Client(\n    ...\n    throttle_handler=throttler,\n)\n```\n\n#### 5. Broker\n**How does your application and `naz` talk with each other?**         \nIt'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.       \nYou 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)             \nYour 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.         \n   \n\n`naz` ships with a simple broker implementation called [`naz.broker.SimpleBroker`](https://github.com/komuw/naz/blob/master/naz/broker.py).                     \nAn example of using that;\n```python\nimport asyncio\nimport naz\n\nloop = asyncio.get_event_loop()\nmy_broker = naz.broker.SimpleBroker(maxsize=1000,) # can hold upto 1000 items\ncli = naz.Client(\n    ...\n    broker=my_broker,\n)\n\ntry:\n    # 1. connect to the SMSC host\n    # 2. bind to the SMSC host\n    # 3. send any queued messages to SMSC\n    # 4. read any data from SMSC\n    # 5. continually check the state of the SMSC\n    tasks = asyncio.gather(\n        cli.connect(),\n        cli.tranceiver_bind(),\n        cli.dequeue_messages(),\n        cli.receive_data(),\n        cli.enquire_link(),\n    )\n    loop.run_until_complete(tasks)\nexcept Exception as e:\n    print(\"exception occured. error={0}\".format(str(e)))\nfinally:\n    loop.run_until_complete(cli.unbind())\n    loop.stop()\n```\nthen in your application, queue items to the queue;\n```python\n# queue messages to send\nfor i in range(0, 4):\n    msg = naz.protocol.SubmitSM(\n                short_message=\"Hello World-{0}\".format(str(i)),\n                log_id=\"myid12345\",\n                source_addr=\"254722111111\",\n                destination_addr=\"254722999999\",\n            )\n    loop.run_until_complete(\n          cli.send_message(msg)\n    )\n```                   \n                         \n                         \nHere is another example, but where we now use redis for our broker;\n```python\nimport json\nimport asyncio\nimport naz\nimport aioredis\n\nclass RedisExampleBroker(naz.broker.BaseBroker):\n    \"\"\"\n    use redis as our broker.\n    This implements a basic FIFO queue using redis.\n    Basically we use the redis command LPUSH to push messages onto the queue and BRPOP to pull them off.\n    https://redis.io/commands/lpush\n    https://redis.io/commands/brpop\n    You should use a non-blocking redis client eg https://github.com/aio-libs/aioredis\n    \"\"\"\n    def __init__(self):\n        self.queue_name = \"myqueue\"\n    async def enqueue(self, item):\n        _redis = await aioredis.create_redis_pool(address=(\"localhost\", 6379))\n        await _redis.lpush(self.queue_name, json.dumps(item))\n    async def dequeue(self):\n        _redis = await aioredis.create_redis_pool(address=(\"localhost\", 6379))\n        x = await _redis.brpop(self.queue_name)\n        dequed_item = json.loads(x[1].decode())\n        return dequed_item\n\nloop = asyncio.get_event_loop()\nbroker = RedisExampleBroker()\ncli = naz.Client(\n    smsc_host=\"127.0.0.1\",\n    smsc_port=2775,\n    system_id=\"smppclient1\",\n    password=\"password\",\n    broker=broker,\n)\n\ntry:\n    # 1. connect to the SMSC host\n    # 2. bind to the SMSC host\n    # 3. send any queued messages to SMSC\n    # 4. read any data from SMSC\n    # 5. continually check the state of the SMSC\n    tasks = asyncio.gather(\n        cli.connect(),\n        cli.tranceiver_bind(),\n        cli.dequeue_messages(),\n        cli.receive_data(),\n        cli.enquire_link(),\n    )\n    tasks = asyncio.gather(cli.dequeue_messages(), cli.receive_data(), cli.enquire_link())\n    loop.run_until_complete(tasks)\nexcept Exception as e:\n    print(\"error={0}\".format(str(e)))\nfinally:\n    loop.run_until_complete(cli.unbind())\n    loop.stop()\n```\nthen queue on your application side;\n```python\n# queue messages to send\nfor i in range(0, 5):\n    print(\"submit_sm round:\", i)\n    msg = naz.protocol.SubmitSM(\n                short_message=\"Hello World-{0}\".format(str(i)),\n                log_id=\"myid12345\",\n                source_addr=\"254722111111\",\n                destination_addr=\"254722999999\",\n            )\n    loop.run_until_complete(\n          cli.send_message(msg)\n    )\n```\n\n\n#### 6. Well written(if I have to say so myself):\n  - [Good test coverage](https://codecov.io/gh/komuw/naz)\n  - [Passing continous integration](https://github.com/komuw/naz/actions)\n  - [statically analyzed code](https://www.codacy.com/app/komuw/naz/dashboard)\n\n\n## Development setup\n- see [documentation on contributing](https://github.com/komuw/naz/blob/master/.github/CONTRIBUTING.md)\n- **NB:** I make no commitment of accepting your pull requests.                 \n\n\n## TODO\n- \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkomuw%2Fnaz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkomuw%2Fnaz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkomuw%2Fnaz/lists"}