shwary-python 2.0.0__tar.gz → 2.0.2__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 (24) hide show
  1. {shwary_python-2.0.0 → shwary_python-2.0.2}/PKG-INFO +7 -6
  2. {shwary_python-2.0.0 → shwary_python-2.0.2}/README.md +5 -4
  3. {shwary_python-2.0.0 → shwary_python-2.0.2}/pyproject.toml +6 -3
  4. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/clients/async_client.py +5 -23
  5. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/clients/sync.py +5 -20
  6. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/core.py +21 -1
  7. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/schemas.py +5 -2
  8. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/tests/test_client_async.py +14 -3
  9. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/tests/test_schemas.py +4 -6
  10. shwary_python-2.0.0/shwary/logger.py +0 -32
  11. {shwary_python-2.0.0 → shwary_python-2.0.2}/.gitignore +0 -0
  12. {shwary_python-2.0.0 → shwary_python-2.0.2}/LICENSE +0 -0
  13. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/__init__.py +0 -0
  14. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/__version__.py +0 -0
  15. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/clients/__init__.py +0 -0
  16. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/clients/base.py +0 -0
  17. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/exceptions.py +0 -0
  18. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/logging_config.py +0 -0
  19. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/py.typed +0 -0
  20. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/tests/__init__.py +0 -0
  21. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/tests/test_advanced.py +0 -0
  22. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/tests/test_client.py +0 -0
  23. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/tests/test_validators.py +0 -0
  24. {shwary_python-2.0.0 → shwary_python-2.0.2/src}/shwary/validators.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shwary-python
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: SDK Python moderne (Async/Sync) pour l'API de paiement Shwary.
5
5
  Author-email: Josué Luis Panzu <josuepanzu8@gmail.com>
6
6
  License: MIT
7
7
  License-File: LICENSE
8
8
  Keywords: africa,fintech,kenya,mobile-money,payment,rdc,shwary,uganda
9
- Classifier: Development Status :: 4 - Beta
9
+ Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3
@@ -24,9 +24,9 @@ Description-Content-Type: text/markdown
24
24
  [![Python versions](https://img.shields.io/pypi/pyversions/shwary-python.svg)](https://pypi.org/project/shwary-python/)
25
25
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
26
26
 
27
- **Shwary Python** est une bibliothèque cliente moderne, asynchrone et performante (non officielle) pour l'intégration de l'API [Shwary](https://shwary.com). Elle permet d'initier des paiements Mobile Money en **RDC**, au **Kenya** et en **Ouganda** avec une validation stricte des données avant l'envoi.
27
+ **Shwary Python** est une bibliothèque cliente moderne, asynchrone et performante pour l'intégration de l'API [Shwary](https://shwary.com). Elle permet d'initier des paiements Mobile Money en **RDC**, au **Kenya** et en **Ouganda** avec une validation stricte des données avant l'envoi.
28
28
 
29
- ## Quoi de neuf dans la v2.0.0
29
+ ## Quoi de neuf dans la v2.0.2
30
30
 
31
31
  - **Retry automatique** : Les erreurs réseau transitoires (timeout, connexion) sont automatiquement retentées avec backoff exponentiel
32
32
  - **Types stricts** : TypedDict pour les réponses (`PaymentResponse`, `TransactionResponse`, `WebhookPayload`)
@@ -36,6 +36,7 @@ Description-Content-Type: text/markdown
36
36
  - **Docstrings améliorées** : Documentation complète avec exemples d'utilisation
37
37
  - **Tests étendus** : Couverture complète des retries, erreurs et validations
38
38
  - **Modèles de réponse** : Schemas Pydantic pour les webhooks et transactions
39
+ - **Correction des bugs** : Correction des imports et des bugs mineurs
39
40
 
40
41
  ## Caractéristiques
41
42
 
@@ -170,12 +171,12 @@ async def handle_webhook(payload: WebhookPayload):
170
171
  """Shwary envoie une notification de changement d'état."""
171
172
 
172
173
  if payload.status == "completed":
173
- # Transaction réussie
174
+ # Transaction réussie
174
175
  print(f"Paiement {payload.id} reçu ({payload.amount})")
175
176
  # La livrez le service ici
176
177
 
177
178
  elif payload.status == "failed":
178
- # Transaction échouée
179
+ # Transaction échouée
179
180
  print(f"Paiement {payload.id} échoué")
180
181
  # Notifiez le client
181
182
 
@@ -4,9 +4,9 @@
4
4
  [![Python versions](https://img.shields.io/pypi/pyversions/shwary-python.svg)](https://pypi.org/project/shwary-python/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- **Shwary Python** est une bibliothèque cliente moderne, asynchrone et performante (non officielle) pour l'intégration de l'API [Shwary](https://shwary.com). Elle permet d'initier des paiements Mobile Money en **RDC**, au **Kenya** et en **Ouganda** avec une validation stricte des données avant l'envoi.
7
+ **Shwary Python** est une bibliothèque cliente moderne, asynchrone et performante pour l'intégration de l'API [Shwary](https://shwary.com). Elle permet d'initier des paiements Mobile Money en **RDC**, au **Kenya** et en **Ouganda** avec une validation stricte des données avant l'envoi.
8
8
 
9
- ## Quoi de neuf dans la v2.0.0
9
+ ## Quoi de neuf dans la v2.0.2
10
10
 
11
11
  - **Retry automatique** : Les erreurs réseau transitoires (timeout, connexion) sont automatiquement retentées avec backoff exponentiel
12
12
  - **Types stricts** : TypedDict pour les réponses (`PaymentResponse`, `TransactionResponse`, `WebhookPayload`)
@@ -16,6 +16,7 @@
16
16
  - **Docstrings améliorées** : Documentation complète avec exemples d'utilisation
17
17
  - **Tests étendus** : Couverture complète des retries, erreurs et validations
18
18
  - **Modèles de réponse** : Schemas Pydantic pour les webhooks et transactions
19
+ - **Correction des bugs** : Correction des imports et des bugs mineurs
19
20
 
20
21
  ## Caractéristiques
21
22
 
@@ -150,12 +151,12 @@ async def handle_webhook(payload: WebhookPayload):
150
151
  """Shwary envoie une notification de changement d'état."""
151
152
 
152
153
  if payload.status == "completed":
153
- # Transaction réussie
154
+ # Transaction réussie
154
155
  print(f"Paiement {payload.id} reçu ({payload.amount})")
155
156
  # La livrez le service ici
156
157
 
157
158
  elif payload.status == "failed":
158
- # Transaction échouée
159
+ # Transaction échouée
159
160
  print(f"Paiement {payload.id} échoué")
160
161
  # Notifiez le client
161
162
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shwary-python"
3
- version = "2.0.0"
3
+ version = "2.0.2"
4
4
  description = "SDK Python moderne (Async/Sync) pour l'API de paiement Shwary."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "License :: OSI Approved :: MIT License",
21
21
  "Operating System :: OS Independent",
22
22
  "Topic :: Software Development :: Libraries :: Python Modules",
23
- "Development Status :: 4 - Beta",
23
+ "Development Status :: 5 - Production/Stable",
24
24
  ]
25
25
 
26
26
  [build-system]
@@ -45,7 +45,10 @@ line-length = 88
45
45
  select = ["E", "F", "I"] # Erreurs, Flakes, et tris d'Imports
46
46
 
47
47
  [tool.hatch.build.targets.sdist]
48
- packages = ["src/shwary"]
48
+ only-include = ["src/shwary"]
49
49
 
50
50
  [tool.hatch.build.targets.wheel]
51
51
  packages = ["src/shwary"]
52
+
53
+ [tool.hatch.build.targets.wheel.sources]
54
+ "src" = ""
@@ -1,14 +1,7 @@
1
- from typing import Any
2
-
3
1
  import httpx
4
- from tenacity import (
5
- AsyncRetrying,
6
- retry_if_exception_type,
7
- stop_after_attempt,
8
- wait_exponential,
9
- )
10
-
11
- from ..core import prepare_payment_request
2
+ from tenacity import AsyncRetrying
3
+
4
+ from ..core import get_retrying_options, prepare_payment_request
12
5
  from ..exceptions import raise_from_response
13
6
  from ..schemas import PaymentResponse, TransactionResponse
14
7
  from .base import BaseShwaryClient
@@ -76,17 +69,6 @@ class ShwaryAsync(BaseShwaryClient):
76
69
  """Sortie du context manager asynchrone."""
77
70
  await self.close()
78
71
 
79
- @property
80
- def _retrying_options(self) -> dict[str, Any]:
81
- return {
82
- "retry": retry_if_exception_type(
83
- (httpx.TimeoutException, httpx.ConnectError)
84
- ),
85
- "stop": stop_after_attempt(3),
86
- "wait": wait_exponential(multiplier=1, min=2, max=10),
87
- "reraise": True,
88
- }
89
-
90
72
  async def initiate_payment(
91
73
  self,
92
74
  country: str,
@@ -129,7 +111,7 @@ class ShwaryAsync(BaseShwaryClient):
129
111
 
130
112
  self._log_request(endpoint, json_data)
131
113
 
132
- async for attempt in AsyncRetrying(**self._retrying_options):
114
+ async for attempt in AsyncRetrying(**get_retrying_options()):
133
115
  with attempt:
134
116
  try:
135
117
  response = await self._client.post(endpoint, json=json_data)
@@ -166,7 +148,7 @@ class ShwaryAsync(BaseShwaryClient):
166
148
 
167
149
  self.logger.debug(f"Fetching transaction: {transaction_id}")
168
150
 
169
- async for attempt in AsyncRetrying(**self._retrying_options):
151
+ async for attempt in AsyncRetrying(**get_retrying_options()):
170
152
  with attempt:
171
153
  try:
172
154
  response = await self._client.get(endpoint)
@@ -1,12 +1,7 @@
1
1
  import httpx
2
- from tenacity import (
3
- retry,
4
- retry_if_exception_type,
5
- stop_after_attempt,
6
- wait_exponential,
7
- )
8
-
9
- from ..core import prepare_payment_request
2
+ from tenacity import retry
3
+
4
+ from ..core import get_retrying_options, prepare_payment_request
10
5
  from ..exceptions import raise_from_response
11
6
  from ..schemas import PaymentResponse, TransactionResponse
12
7
  from .base import BaseShwaryClient
@@ -74,12 +69,7 @@ class Shwary(BaseShwaryClient):
74
69
  """Sortie du context manager."""
75
70
  self.close()
76
71
 
77
- @retry(
78
- retry=retry_if_exception_type((httpx.TimeoutException, httpx.ConnectError)),
79
- stop=stop_after_attempt(3),
80
- wait=wait_exponential(multiplier=1, min=2, max=10),
81
- reraise=True,
82
- )
72
+ @retry(**get_retrying_options())
83
73
  def initiate_payment(
84
74
  self,
85
75
  country: str,
@@ -133,12 +123,7 @@ class Shwary(BaseShwaryClient):
133
123
  self._log_error(endpoint, e)
134
124
  raise
135
125
 
136
- @retry(
137
- retry=retry_if_exception_type((httpx.TimeoutException, httpx.ConnectError)),
138
- stop=stop_after_attempt(3),
139
- wait=wait_exponential(multiplier=1, min=2, max=10),
140
- reraise=True,
141
- )
126
+ @retry(**get_retrying_options())
142
127
  def get_transaction(self, transaction_id: str) -> TransactionResponse:
143
128
  """
144
129
  Récupère les détails d'une transaction par son ID.
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Logique métier partagée entre les clients Sync et Async.
3
3
 
4
- Ce module contient les fonctions de validation et de préparation des requêtes
4
+ Ce module contient notamment les fonctions de validation et de préparation des requêtes
5
5
  utilisées par tous les clients Shwary pour éviter la duplication de code.
6
6
 
7
7
  Fonctions principales:
@@ -20,6 +20,15 @@ Exemple d'utilisation directe:
20
20
  # payload = {"amount": 5000, "clientPhoneNumber": "+243972345678"}
21
21
  """
22
22
 
23
+ from typing import Any
24
+
25
+ import httpx
26
+ from tenacity import (
27
+ retry_if_exception_type,
28
+ stop_after_attempt,
29
+ wait_exponential,
30
+ )
31
+
23
32
  from .exceptions import ValidationError
24
33
  from .schemas import CountryCode, PaymentPayload
25
34
 
@@ -166,3 +175,14 @@ def prepare_payment_request(
166
175
  endpoint: str = f"/payment/{env_path}{country_enum.value}"
167
176
 
168
177
  return endpoint, payload.model_dump(exclude={'country_target'}, exclude_none=True)
178
+
179
+
180
+ def get_retrying_options() -> dict[str, Any]:
181
+ return {
182
+ "retry": retry_if_exception_type(
183
+ (httpx.TimeoutException, httpx.ConnectError)
184
+ ),
185
+ "stop": stop_after_attempt(3),
186
+ "wait": wait_exponential(multiplier=1, min=2, max=10),
187
+ "reraise": True,
188
+ }
@@ -122,6 +122,9 @@ class PaymentResponse(BaseModel):
122
122
 
123
123
  id: str = Field(..., description="Identifiant unique de la transaction (UUID)")
124
124
  status: str = Field(..., description="État de la transaction")
125
+ isSandbox: bool = Field(
126
+ ..., description="Indique si la transaction est en mode sandbox"
127
+ )
125
128
 
126
129
  model_config = {"extra": "allow"}
127
130
 
@@ -134,7 +137,7 @@ class TransactionResponse(BaseModel):
134
137
  id: Identifiant unique de la transaction
135
138
  status: État actualisé de la transaction
136
139
  amount: Montant de la transaction
137
- clientPhoneNumber: Numéro du client (peut être partiellement masqué)
140
+ recipientPhoneNumber: Numéro du receveur
138
141
  createdAt: Timestamp de création
139
142
  updatedAt: Timestamp de dernière mise à jour
140
143
  metadata: Données métier additionnelles
@@ -145,7 +148,7 @@ class TransactionResponse(BaseModel):
145
148
  ..., description="État actualisé (pending, completed, failed, etc.)"
146
149
  )
147
150
  amount: float = Field(..., description="Montant de la transaction")
148
- clientPhoneNumber: Optional[str] = Field(None, description="Numéro du client")
151
+ recipientPhoneNumber: Optional[str] = Field(None, description="Numéro du receveur")
149
152
  createdAt: Optional[datetime] = Field(None, description="Timestamp de création")
150
153
  updatedAt: Optional[datetime] = Field(
151
154
  None, description="Timestamp de dernière mise à jour"
@@ -146,18 +146,29 @@ async def test_async_retry_on_timeout(async_client):
146
146
  async def test_async_parallel_payments(async_client):
147
147
  """Teste la capacité à gérer plusieurs requêtes simultanées."""
148
148
  respx.post("https://api.shwary.com/api/v1/merchants/payment/sandbox/DRC").mock(
149
- return_value=Response(200, json={"id": "p1", "status": "pending"})
149
+ return_value=Response(
150
+ 200, json={"id": "p1", "status": "pending", "isSandbox": True}
151
+ )
150
152
  )
151
153
  respx.post("https://api.shwary.com/api/v1/merchants/payment/sandbox/KE").mock(
152
- return_value=Response(200, json={"id": "p2", "status": "pending"})
154
+ return_value=Response(
155
+ 200, json={"id": "p2", "status": "pending", "isSandbox": True}
156
+ )
157
+ )
158
+ respx.post("https://api.shwary.com/api/v1/merchants/payment/sandbox/UG").mock(
159
+ return_value=Response(
160
+ 200, json={"id": "p3", "status": "pending", "isSandbox": True}
161
+ )
153
162
  )
154
163
 
155
164
  async with async_client as c:
156
- # On lance les deux en même temps
165
+ # On lance les trois en même temps
157
166
  results = await asyncio.gather(
158
167
  c.initiate_payment("DRC", 5000, "+243972345678"),
159
168
  c.initiate_payment("KE", 10, "+254700000000"),
169
+ c.initiate_payment("UG", 50, "+256700000000"),
160
170
  )
161
171
 
162
172
  assert results[0].id == "p1"
163
173
  assert results[1].id == "p2"
174
+ assert results[2].id == "p3"
@@ -144,7 +144,7 @@ class TestPaymentResponse:
144
144
  id="trans-123",
145
145
  status="pending",
146
146
  isSandbox=True,
147
- extra_field="extra_value", # Accepté grâce à extra="allow"
147
+ extra_field="extra_value",
148
148
  )
149
149
 
150
150
  assert response.id == "trans-123"
@@ -160,7 +160,7 @@ class TestTransactionResponse:
160
160
  id="trans-456",
161
161
  status="completed",
162
162
  amount=5000,
163
- clientPhoneNumber="+243972345678",
163
+ recipientPhoneNumber="+243972345678",
164
164
  )
165
165
 
166
166
  assert response.id == "trans-456"
@@ -174,10 +174,10 @@ class TestTransactionResponse:
174
174
  status="pending",
175
175
  amount=1000,
176
176
  # Les champs suivants sont optionnels:
177
- # clientPhoneNumber, createdAt, updatedAt, metadata
177
+ # recipientPhoneNumber, createdAt, updatedAt, metadata
178
178
  )
179
179
 
180
- assert response.clientPhoneNumber is None
180
+ assert response.recipientPhoneNumber is None
181
181
 
182
182
 
183
183
  class TestWebhookPayload:
@@ -185,12 +185,10 @@ class TestWebhookPayload:
185
185
 
186
186
  def test_valid_webhook_payload(self):
187
187
  """Teste la création d'une charge webhook valide."""
188
- now = datetime.now()
189
188
  payload = WebhookPayload(
190
189
  id="trans-webhook",
191
190
  status="completed",
192
191
  amount=5000,
193
- timestamp=now,
194
192
  )
195
193
 
196
194
  assert payload.id == "trans-webhook"
@@ -1,32 +0,0 @@
1
- import logging
2
- import sys
3
-
4
- # Create a custom logger
5
- logger = logging.getLogger(__name__)
6
-
7
- # Create handlers
8
- console_handler = logging.StreamHandler(sys.stdout)
9
- file_handler = logging.FileHandler('app.log')
10
-
11
- # Create formatters and add them to the handlers
12
- console_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
13
- file_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
14
- console_handler.setFormatter(console_format)
15
- file_handler.setFormatter(file_format)
16
-
17
- # Set level for handlers
18
- console_handler.setLevel(logging.DEBUG)
19
- file_handler.setLevel(logging.ERROR)
20
-
21
- # Add the handlers to the logger
22
- logger.addHandler(console_handler)
23
- logger.addHandler(file_handler)
24
-
25
- # Set the default logging level
26
- def set_log_level(level):
27
- logger.setLevel(level)
28
-
29
- # Example usage
30
- if __name__ == '__main__':
31
- logger.info('This is an info message')
32
- logger.error('This is an error message')
File without changes
File without changes