Skip to content

Commit e6ac6bb

Browse files
committed
fix types
1 parent e63f164 commit e6ac6bb

24 files changed

+148
-173
lines changed

.coverage

0 Bytes
Binary file not shown.

README.md

Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ A lightweight and robust Python SDK for interacting with the Wasender API ([http
1515

1616
- **Pydantic Models:** Leverages Pydantic for robust request/response validation and serialization, especially for the generic `send()` method and webhook event parsing.
1717
- **Message Sending:**
18-
- Simplified helper methods (e.g., `client.send_text(to=\"...\", text_body=\"...\")`, `client.send_image(to=\"...\", url=\"...\", caption=\"...\")`) that accept direct parameters for common message types.
18+
- Simplified helper methods (e.g., `client.send_text(to="...", text_body="...")`, `client.send_image(to="...", url="...", caption="...")`) that accept direct parameters for common message types.
1919
- Generic `client.send(payload: BaseMessage)` method for advanced use cases or less common message types, accepting a Pydantic model.
2020
- Support for text, image, video, document, audio, sticker, contact card, and location messages.
2121
- **Contact Management:** List, retrieve details, get profile pictures, block, and unblock contacts.
@@ -26,7 +26,7 @@ A lightweight and robust Python SDK for interacting with the Wasender API ([http
2626
- **Error Handling:** Comprehensive `WasenderAPIError` class with detailed error information.
2727
- **Rate Limiting:** Access to rate limit information on API responses.
2828
- **Retry Mechanism:** Optional automatic retries for rate-limited requests (HTTP 429) via `RetryConfig`.
29-
- **Customizable HTTP Client:** Allows providing a custom `fetch_implementation` (compatible with `requests.request` or an async equivalent) for specialized HTTP handling or testing.
29+
- **Customizable HTTP Client:** Allows providing a custom `httpx.AsyncClient` instance for the asynchronous client.
3030

3131
## Prerequisites
3232

@@ -172,38 +172,22 @@ async def send_async_text_message(client):
172172
# if __name__ == "__main__":
173173
# asyncio.run(main_async_send_example()) # Example of how to run it
174174
```
175-
# For this example, we'll use the default internal HTTP client.
176-
# custom_fetch_impl: Optional[FetchImplementation] = None
177-
178-
# This block seems like an old/outdated example of client initialization.
179-
# The create_sync_wasender and create_async_wasender are the preferred ways now.
180-
# It also uses WasenderClient which was the old name and personal_token instead of personal_access_token.
181-
# I will comment it out for now as it's confusing and outdated.
182-
# wasender_client_full = WasenderClient(
183-
# api_key=api_key,
184-
# # base_url="https://www.wasenderapi.com/api", # Defaults to this
185-
# # fetch_implementation=custom_fetch_impl,
186-
# retry_config=retry_config,
187-
# webhook_secret=webhook_secret,
188-
# personal_token=persona_token
189-
# )
190-
#
191-
# # Basic initialization (session-scoped endpoints only, no retries, no webhooks)
192-
# basic_wasender_client = WasenderClient(api_key=api_key)
193-
#
194-
# # Initialize with persona token (for account management, no retries, no webhooks)
195-
# account_wasender_client = WasenderClient(
196-
# api_key=api_key,
197-
# personal_token=persona_token
198-
# )
199-
#
200-
# # Initialize with webhook secret (for webhook handling, no retries, no account management)
201-
# webhook_handler_client = WasenderClient(
202-
# api_key=api_key,
203-
# webhook_secret=webhook_secret
204-
# )
205-
#
206-
# print("WasenderClient initialized.")
175+
176+
## Usage Overview
177+
178+
Using the SDK involves calling methods on the initialized client instance. For example, to send a message with the synchronous client:
179+
180+
```python
181+
from wasenderapi.errors import WasenderAPIError
182+
183+
try:
184+
response = sync_client.send_text(to="1234567890", text_body="Hello from Python SDK!")
185+
print(f"Message sent successfully: {response.response.data.message_id}")
186+
if response.rate_limit:
187+
print(f"Rate limit info: Remaining {response.rate_limit.remaining}")
188+
except WasenderAPIError as e:
189+
print(f"API Error: {e.message} (Status: {e.status_code})")
190+
```
207191

208192
## Authentication
209193

@@ -284,7 +268,7 @@ Send various types of messages including text, media (images, videos, documents,
284268

285269
- **Detailed Documentation & Examples:** [`docs/messages.md`](./docs/messages.md)
286270

287-
**Using the Synchronous Client (`WasenderSyncClient`)**
271+
#### Using the Synchronous Client (`WasenderSyncClient`)
288272

289273
```python
290274
import os
@@ -332,7 +316,7 @@ def send_sync_messages_example():
332316
# send_sync_messages_example()
333317
```
334318

335-
**Using the Asynchronous Client (`WasenderAsyncClient`)**
319+
#### Using the Asynchronous Client (`WasenderAsyncClient`)
336320

337321
```python
338322
import asyncio
@@ -728,45 +712,6 @@ async def manage_whatsapp_sessions_example():
728712

729713
## Advanced Topics
730714

731-
### Custom HTTP Client Fetch Implementation
732-
733-
For advanced scenarios, such as custom logging, request/response modification, or integration with specific HTTP libraries, you can provide your own HTTP fetch implementation.
734-
735-
**Synchronous Client:**
736-
737-
When initializing `WasenderSyncClient` (or using `create_sync_wasender`), the underlying HTTP calls are made using the `requests` library by default. If you need to customize this, you would typically do so by configuring the global `requests` behavior or by wrapping calls to the SDK if fine-grained control per request is needed (though the SDK doesn't directly expose a hook for a custom *sync* fetch function in its constructor anymore after recent refactoring focused on `httpx` for async and standard `requests` for sync internally).
738-
739-
**Asynchronous Client:**
740-
741-
The `WasenderAsyncClient` (and `create_async_wasender` factory) uses `httpx.AsyncClient` internally. You can pass your pre-configured `httpx.AsyncClient` instance during initialization:
742-
743-
```python
744-
import httpx
745-
from wasenderapi import create_async_wasender
746-
from wasenderapi.models import RetryConfig # Ensure RetryConfig is imported if used
747-
748-
# Example: Configure a custom httpx.AsyncClient with a timeout
749-
custom_http_client = httpx.AsyncClient(timeout=10.0)
750-
751-
# Example API key and retry config
752-
api_key = "YOUR_API_KEY"
753-
retry_conf = RetryConfig(enabled=True, max_retries=2)
754-
755-
async_client_custom = create_async_wasender(
756-
api_key=api_key,
757-
http_client=custom_http_client,
758-
retry_options=retry_conf
759-
)
760-
761-
async def use_custom_client():
762-
async with async_client_custom:
763-
# Make API calls
764-
# e.g., status = await async_client_custom.get_session_status("your_session_id")
765-
print("Using async client with custom httpx client")
766-
767-
# await use_custom_client()
768-
```
769-
770715
### Configuring Retries
771716

772717
The SDK supports automatic retries, primarily for handling HTTP 429 (Too Many Requests) errors. This is configured via the `RetryConfig` object passed to `retry_options` during client initialization.
-776 Bytes
Binary file not shown.

dist/wasenderapi-0.2.0.tar.gz

-1.31 KB
Binary file not shown.
-631 Bytes
Binary file not shown.
2.96 KB
Binary file not shown.
7.14 KB
Binary file not shown.

tests/test_contacts.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,12 @@ def mock_rate_limit_info_dict():
2727
@pytest.fixture
2828
def specific_contact_api_data_for_model_test(): # Renamed fixture
2929
return {
30-
30+
3131
"name": "Contact Name API Model Test",
3232
"notify": "Contact Display API Model Test",
3333
"verifiedName": "Verified Business API Model Test",
3434
"imgUrl": "https://profile.pic.url/model_test.jpg",
35-
"status": "API status: Model Test Active!",
36-
"exists": True
35+
"status": "API status: Model Test Active!"
3736
}
3837

3938
@pytest.fixture
@@ -42,7 +41,7 @@ def mock_multiple_contacts_api_data(specific_contact_api_data_for_model_test): #
4241
# Assuming it still uses a general form, let's define one or use the name that might be from conftest if needed by other tests.
4342
# For now, let it use the renamed one to see the effect. Or, create another general one if needed.
4443
contact2 = specific_contact_api_data_for_model_test.copy()
45-
contact2["jid"] = "[email protected]"
44+
contact2["id"] = "[email protected]"
4645
contact2["name"] = "Another Contact API Model Test"
4746
return [specific_contact_api_data_for_model_test, contact2]
4847

@@ -61,27 +60,26 @@ def test_contact_model_all_fields(self, specific_contact_api_data_for_model_test
6160
# Pydantic should use the aliases to populate the snake_case fields.
6261
contact = Contact(**specific_contact_api_data_for_model_test)
6362

64-
assert contact.jid == specific_contact_api_data_for_model_test["jid"]
63+
assert contact.id == specific_contact_api_data_for_model_test["id"]
6564
assert contact.name == specific_contact_api_data_for_model_test["name"]
6665
# Now check the model's snake_case attributes against the fixture's camelCase values
6766
assert contact.verified_name == specific_contact_api_data_for_model_test["verifiedName"]
6867
assert contact.img_url == specific_contact_api_data_for_model_test["imgUrl"]
6968
assert contact.status == specific_contact_api_data_for_model_test["status"]
70-
assert contact.exists == specific_contact_api_data_for_model_test["exists"]
7169

7270
dumped_contact = contact.model_dump(by_alias=True)
7371
assert dumped_contact["verifiedName"] == specific_contact_api_data_for_model_test["verifiedName"]
7472
assert dumped_contact["imgUrl"] == specific_contact_api_data_for_model_test["imgUrl"]
7573

7674
def test_contact_model_minimal_fields(self):
77-
minimal_data = {"jid": "[email protected]"}
75+
minimal_data = {"id": "[email protected]"}
7876
contact = Contact(**minimal_data)
79-
assert contact.jid == minimal_data["jid"]
77+
assert contact.id == minimal_data["id"]
8078
assert contact.name is None
8179
dumped_contact = contact.model_dump(by_alias=True, exclude_none=True)
8280
assert "name" not in dumped_contact
8381

84-
def test_contact_model_missing_jid_raises_error(self):
82+
def test_contact_model_missing_id_raises_error(self):
8583
with pytest.raises(ValidationError):
8684
Contact(name="Test Name")
8785

@@ -102,7 +100,7 @@ def test_get_all_contacts_result(self, mock_multiple_contacts_api_data, mock_rat
102100
assert result_model.response.message == "Fetched contacts successfully"
103101
assert len(result_model.response.data) == 2
104102
assert isinstance(result_model.response.data[0], Contact)
105-
assert result_model.response.data[0].jid == mock_multiple_contacts_api_data[0]["jid"]
103+
assert result_model.response.data[0].id == mock_multiple_contacts_api_data[0]["id"]
106104
assert result_model.response.data[0].name == mock_multiple_contacts_api_data[0]["name"]
107105
# Check aliasing for verifiedName and imgUrl from the first contact
108106
assert result_model.response.data[0].verified_name == mock_multiple_contacts_api_data[0]["verifiedName"]
@@ -126,7 +124,7 @@ def test_get_contact_info_result(self, specific_contact_api_data_for_model_test,
126124

127125
assert result_model.response.success is True
128126
assert isinstance(result_model.response.data, Contact)
129-
assert result_model.response.data.jid == specific_contact_api_data_for_model_test["jid"]
127+
assert result_model.response.data.id == specific_contact_api_data_for_model_test["id"]
130128
assert result_model.response.data.status == specific_contact_api_data_for_model_test["status"]
131129
assert result_model.rate_limit.remaining == mock_rate_limit_info_dict["remaining"]
132130

@@ -208,7 +206,7 @@ async def test_get_contact_info(self, async_client_contacts_mocked_internals, sp
208206

209207
client._get_internal.assert_called_once_with(f"/contacts/{contact_phone}")
210208
assert isinstance(result, GetContactInfoResult)
211-
assert result.response.data.jid == specific_contact_api_data_for_model_test["jid"]
209+
assert result.response.data.id == specific_contact_api_data_for_model_test["id"]
212210

213211
@pytest.mark.asyncio
214212
async def test_get_contact_profile_picture(self, async_client_contacts_mocked_internals, mock_rate_limit_info_dict):

0 commit comments

Comments
 (0)