kibonet-python-sdk 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. {kibonet_python_sdk-0.1.0/src/kibonet_python_sdk.egg-info → kibonet_python_sdk-0.1.1}/PKG-INFO +1 -1
  2. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/pyproject.toml +1 -1
  3. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1/src/kibonet_python_sdk.egg-info}/PKG-INFO +1 -1
  4. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/__init__.py +1 -1
  5. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/async_client.py +7 -6
  6. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/client.py +8 -9
  7. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/utils/async_http_client.py +1 -1
  8. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/utils/http_client.py +1 -1
  9. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/tests/test_async_client.py +15 -0
  10. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/tests/test_client.py +14 -0
  11. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/LICENSE +0 -0
  12. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/README.md +0 -0
  13. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/setup.cfg +0 -0
  14. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_python_sdk.egg-info/SOURCES.txt +0 -0
  15. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_python_sdk.egg-info/dependency_links.txt +0 -0
  16. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_python_sdk.egg-info/requires.txt +0 -0
  17. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_python_sdk.egg-info/top_level.txt +0 -0
  18. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/exceptions.py +0 -0
  19. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/utils/__init__.py +0 -0
  20. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/src/kibonet_sdk/utils/validators.py +0 -0
  21. {kibonet_python_sdk-0.1.0 → kibonet_python_sdk-0.1.1}/tests/test_validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kibonet-python-sdk
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Simple, secure Python SDK for the KiboNet SMS Gateway
5
5
  Author-email: Jackson Linus <jacksonlinus95@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kibonet-python-sdk"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Simple, secure Python SDK for the KiboNet SMS Gateway"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kibonet-python-sdk
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Simple, secure Python SDK for the KiboNet SMS Gateway
5
5
  Author-email: Jackson Linus <jacksonlinus95@gmail.com>
6
6
  License-Expression: MIT
@@ -58,4 +58,4 @@ __all__ = [
58
58
  "KiboNetServerError",
59
59
  ]
60
60
 
61
- __version__ = "0.1.0"
61
+ __version__ = "0.1.1"
@@ -214,21 +214,22 @@ class AsyncKiboNetClient:
214
214
  )
215
215
  if response.status_code == 429:
216
216
  raise KiboNetRateLimitError("Rate limit exceeded", code=429)
217
- if 500 <= response.status_code < 600:
218
- raise KiboNetServerError(
219
- f"KiboNet server error (HTTP {response.status_code})",
220
- code=response.status_code,
221
- )
222
217
 
218
+ # Always try to parse JSON first — KiboNet uses 5xx for business errors too
223
219
  try:
224
220
  data = response.json()
225
221
  except Exception:
222
+ if 500 <= response.status_code < 600:
223
+ raise KiboNetServerError(
224
+ f"KiboNet server error (HTTP {response.status_code})",
225
+ code=response.status_code,
226
+ )
226
227
  raise KiboNetAPIError(
227
228
  f"Unexpected non-JSON response (HTTP {response.status_code}): "
228
229
  f"{response.text[:200]}"
229
230
  )
230
231
 
231
- if response.status_code != 200 or not data.get("success", False):
232
+ if not data.get("success", False):
232
233
  raise KiboNetAPIError(
233
234
  data.get("message", f"API error (HTTP {response.status_code})"),
234
235
  code=data.get("code"),
@@ -252,24 +252,23 @@ class KiboNetClient:
252
252
  "Authentication failed — check your api_key and api_secret"
253
253
  )
254
254
  if response.status_code == 429:
255
- raise KiboNetRateLimitError(
256
- "Rate limit exceeded", code=429
257
- )
258
- if 500 <= response.status_code < 600:
259
- raise KiboNetServerError(
260
- f"KiboNet server error (HTTP {response.status_code})",
261
- code=response.status_code,
262
- )
255
+ raise KiboNetRateLimitError("Rate limit exceeded", code=429)
263
256
 
257
+ # Always try to parse JSON first — KiboNet uses 5xx for business errors too
264
258
  try:
265
259
  data = response.json()
266
260
  except Exception:
261
+ if 500 <= response.status_code < 600:
262
+ raise KiboNetServerError(
263
+ f"KiboNet server error (HTTP {response.status_code})",
264
+ code=response.status_code,
265
+ )
267
266
  raise KiboNetAPIError(
268
267
  f"Unexpected non-JSON response (HTTP {response.status_code}): "
269
268
  f"{response.text[:200]}"
270
269
  )
271
270
 
272
- if response.status_code != 200 or not data.get("success", False):
271
+ if not data.get("success", False):
273
272
  raise KiboNetAPIError(
274
273
  data.get("message", f"API error (HTTP {response.status_code})"),
275
274
  code=data.get("code"),
@@ -12,7 +12,7 @@ from ..exceptions import KiboNetConnectionError
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
14
  BASE_URL = "https://sms.kibonet.co.tz"
15
- SDK_VERSION = "0.1.0"
15
+ SDK_VERSION = "0.1.1"
16
16
 
17
17
 
18
18
  class AsyncHTTPClient:
@@ -12,7 +12,7 @@ from ..exceptions import KiboNetConnectionError
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
14
  BASE_URL = "https://sms.kibonet.co.tz"
15
- SDK_VERSION = "0.1.0"
15
+ SDK_VERSION = "0.1.1"
16
16
 
17
17
 
18
18
  class HTTPClient:
@@ -130,6 +130,7 @@ class TestAsyncSendSMS:
130
130
  @pytest.mark.asyncio
131
131
  @respx.mock
132
132
  async def test_send_sms_server_error(self, async_client):
133
+ # Non-JSON 5xx → KiboNetServerError
133
134
  respx.post(f"{BASE_URL}/api/v1/vendor/message/send").mock(
134
135
  return_value=httpx.Response(500, text="Internal Server Error")
135
136
  )
@@ -138,6 +139,20 @@ class TestAsyncSendSMS:
138
139
  sender_id="Sender", message="Hi", contacts="255712345678"
139
140
  )
140
141
 
142
+ @pytest.mark.asyncio
143
+ @respx.mock
144
+ async def test_send_sms_business_error_as_5xx(self, async_client):
145
+ # KiboNet returns 5xx with JSON success=false for business errors
146
+ respx.post(f"{BASE_URL}/api/v1/vendor/message/send").mock(
147
+ return_value=httpx.Response(
148
+ 501, json={"code": 501, "success": False, "message": "Sender id does not exists to your account.", "data": None}
149
+ )
150
+ )
151
+ with pytest.raises(KiboNetAPIError, match="Sender id does not exists"):
152
+ await async_client.send_sms(
153
+ sender_id="INVALID", message="Hi", contacts="255712345678"
154
+ )
155
+
141
156
  @pytest.mark.asyncio
142
157
  @respx.mock
143
158
  async def test_send_sms_api_error_success_false(self, async_client):
@@ -139,6 +139,7 @@ class TestSendSMS:
139
139
 
140
140
  @respx.mock
141
141
  def test_send_sms_server_error(self, client):
142
+ # Non-JSON 5xx → KiboNetServerError
142
143
  respx.post(f"{BASE_URL}/api/v1/vendor/message/send").mock(
143
144
  return_value=httpx.Response(500, text="Internal Server Error")
144
145
  )
@@ -147,6 +148,19 @@ class TestSendSMS:
147
148
  sender_id="Sender", message="Hi", contacts="255712345678"
148
149
  )
149
150
 
151
+ @respx.mock
152
+ def test_send_sms_business_error_as_5xx(self, client):
153
+ # KiboNet returns 5xx with JSON success=false for business errors
154
+ respx.post(f"{BASE_URL}/api/v1/vendor/message/send").mock(
155
+ return_value=httpx.Response(
156
+ 501, json={"code": 501, "success": False, "message": "Sender id does not exists to your account.", "data": None}
157
+ )
158
+ )
159
+ with pytest.raises(KiboNetAPIError, match="Sender id does not exists"):
160
+ client.send_sms(
161
+ sender_id="INVALID", message="Hi", contacts="255712345678"
162
+ )
163
+
150
164
  @respx.mock
151
165
  def test_send_sms_api_error_success_false(self, client):
152
166
  respx.post(f"{BASE_URL}/api/v1/vendor/message/send").mock(