Skip to content

Commit 2931583

Browse files
authored
Merge pull request #9 from shiningflash/develop
Release: Improve Schema Definitions for Messages, Contacts, and Webhook Payloads
2 parents 114519a + 7e636ba commit 2931583

File tree

7 files changed

+162
-44
lines changed

7 files changed

+162
-44
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ The **Messaging SDK** is a Python library that allows developers to interact wit
122122
```
123123
124124
#### Comprehensive User Guide for **[SDK Usage](docs/sdk_usage.md)**
125-
For detailed usage and examples, please refer to the **[User Guide](docs/sdk_usage.md)**.
125+
For detailed usage and examples, please refer to the **[User Guide](docs/sdk_usage.md)** 👈.
126126
127127
### Webhook Setup
128128
1. Run the webhook server:
@@ -133,7 +133,7 @@ For detailed usage and examples, please refer to the **[User Guide](docs/sdk_usa
133133
2. Configure the API to send events to your webhook endpoint (e.g., `http://localhost:3010/webhooks`).
134134
135135
#### Comprehensive User Guide for **[Webhook](docs/webhook_guide.md)**
136-
For detailed usage and examples, please refer to the [User Guide](docs/webhook_guide.md).
136+
For detailed usage and examples, please refer to the **[User Guide](docs/webhook_guide.md)** 👈.
137137
138138
---
139139

src/sdk/schemas/contacts.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
from pydantic import BaseModel, Field, field_validator
2-
from typing import List, Optional
1+
from pydantic import BaseModel, Field, field_validator, ConfigDict
2+
from typing import List
33

44

55
class CreateContactRequest(BaseModel):
66
"""
77
Schema for creating a new contact.
8+
9+
Attributes:
10+
name (str): The name of the contact.
11+
phone (str): The phone number of the contact in E.164 format.
812
"""
9-
name: str = Field(..., description="The name of the contact.")
13+
name: str = Field(
14+
...,
15+
description="The name of the contact.",
16+
json_schema_extra={"example": "Amirul"}
17+
)
1018
phone: str = Field(
1119
...,
12-
pattern=r"^\+\d{1,15}$",
13-
description="Phone number in E.164 format."
20+
description="Phone number in E.164 format.",
21+
json_schema_extra={"example": "+1234567890"}
1422
)
1523

1624
@field_validator("name")
@@ -19,20 +27,47 @@ def validate_name(cls, value):
1927
raise ValueError("Name must be between 1 and 50 characters.")
2028
return value
2129

30+
@field_validator("phone")
31+
def validate_phone(cls, value):
32+
if not value.startswith("+"):
33+
raise ValueError("Phone numbers must start with a '+' prefix.")
34+
if not value[1:].isdigit():
35+
raise ValueError("Phone numbers must contain only digits after the '+' prefix.")
36+
return value
37+
2238

2339
class Contact(BaseModel):
2440
"""
2541
Schema for a contact resource.
42+
43+
Attributes:
44+
id (str): Unique identifier for the contact.
45+
name (str): The name of the contact.
46+
phone (str): The phone number of the contact in E.164 format.
2647
"""
27-
id: str
28-
name: str
29-
phone: str
48+
id: str = Field(..., description="Unique identifier for the contact.", json_schema_extra={"example": "contact123"})
49+
name: str = Field(..., description="The name of the contact.", json_schema_extra={"example": "Alice"})
50+
phone: str = Field(
51+
...,
52+
description="Phone number in E.164 format.",
53+
json_schema_extra={"example": "+1234567890"}
54+
)
3055

3156

3257
class ListContactsResponse(BaseModel):
3358
"""
3459
Schema for listing contacts with pagination support.
60+
61+
Attributes:
62+
contacts (List[Contact]): List of contact objects.
63+
page_number (int): Current page number.
64+
page_size (int): Number of contacts per page.
3565
"""
36-
contactsList: List[Contact]
37-
pageNumber: int
38-
pageSize: int
66+
contacts: List[Contact] = Field(..., alias="contactsList", description="List of contact objects.")
67+
page_number: int = Field(..., alias="pageNumber", description="Current page number.", json_schema_extra={"example": 1})
68+
page_size: int = Field(..., alias="pageSize", description="Number of contacts per page.", json_schema_extra={"example": 10})
69+
70+
model_config = ConfigDict(
71+
populate_by_name=True,
72+
arbitrary_types_allowed=True
73+
)

src/sdk/schemas/messages.py

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,102 @@
1-
from pydantic import BaseModel, Field, validator
2-
from typing import Optional, List
1+
from pydantic import BaseModel, Field, field_validator, ConfigDict
2+
from typing import List, Literal
3+
from datetime import datetime
34

45

56
class CreateMessageRequest(BaseModel):
67
"""
78
Schema for creating a new message.
9+
10+
Attributes:
11+
to (str): Recipient's phone number in E.164 format.
12+
content (str): Message content, with a maximum length of 160 characters.
13+
sender (str): Sender's phone number in E.164 format.
814
"""
915
to: str = Field(
1016
...,
11-
pattern=r"^\+\d{1,15}$",
12-
description="Recipient's phone number in E.164 format."
17+
description="Recipient's phone number in E.164 format.",
18+
json_schema_extra={"example": "+1234567890"}
1319
)
1420
content: str = Field(
1521
...,
1622
min_length=1,
1723
max_length=160,
18-
description="Message content with a maximum of 160 characters."
24+
description="Message content, limited to 160 characters.",
25+
json_schema_extra={"example": "Hello, World!"}
1926
)
2027
sender: str = Field(
2128
...,
22-
pattern=r"^\+\d{1,15}$",
23-
description="Sender's phone number in E.164 format."
29+
description="Sender's phone number in E.164 format.",
30+
json_schema_extra={"example": "+0987654321"}
2431
)
2532

33+
@field_validator("to", "sender")
34+
def validate_phone_number(cls, value):
35+
if not value.startswith("+"):
36+
raise ValueError("Phone numbers must include the '+' prefix.")
37+
if not value[1:].isdigit():
38+
raise ValueError("Phone numbers must contain only digits after the '+' prefix.")
39+
return value
40+
2641

2742
class Message(BaseModel):
2843
"""
2944
Schema for a message resource.
45+
46+
Attributes:
47+
id (str): Unique identifier for the message.
48+
from_ (str): Sender's phone number.
49+
to (str): Recipient's phone number.
50+
content (str): Message content.
51+
status (str): Message status, one of 'queued', 'delivered', or 'failed'.
52+
created_at (datetime): Timestamp when the message was created.
3053
"""
31-
id: str
32-
from_: str = Field(..., alias="from", description="Sender's phone number.")
33-
to: str
34-
content: str
35-
status: str # queued, delivered, or failed
36-
createdAt: str
54+
id: str = Field(..., description="Unique identifier for the message.", json_schema_extra={"example": "msg123"})
55+
from_: str = Field(
56+
...,
57+
alias="from",
58+
description="Sender's phone number.",
59+
json_schema_extra={"example": "+0987654321"}
60+
)
61+
to: str = Field(..., description="Recipient's phone number.", json_schema_extra={"example": "+1234567890"})
62+
content: str = Field(..., description="Message content.", json_schema_extra={"example": "Hello, World!"})
63+
status: Literal["queued", "delivered", "failed"] = Field(
64+
...,
65+
description="Message status.",
66+
json_schema_extra={"example": "delivered"}
67+
)
68+
created_at: datetime = Field(
69+
...,
70+
alias="createdAt",
71+
description="Timestamp when the message was created.",
72+
json_schema_extra={"example": "2024-12-01T12:00:00Z"}
73+
)
74+
75+
model_config = ConfigDict(
76+
populate_by_name=True,
77+
arbitrary_types_allowed=True
78+
)
3779

3880

3981
class ListMessagesResponse(BaseModel):
4082
"""
4183
Schema for listing messages with pagination support.
84+
85+
Attributes:
86+
messages (List[Message]): List of message objects.
87+
page (int): Current page number.
88+
quantity_per_page (int): Number of messages per page.
4289
"""
43-
messages: List[Message]
44-
page: int
45-
quantityPerPage: int
90+
messages: List[Message] = Field(..., description="List of message objects.")
91+
page: int = Field(..., description="Current page number.", json_schema_extra={"example": 1})
92+
quantity_per_page: int = Field(
93+
...,
94+
alias="quantityPerPage",
95+
description="Number of messages per page.",
96+
json_schema_extra={"example": 10}
97+
)
98+
99+
model_config = ConfigDict(
100+
populate_by_name=True,
101+
arbitrary_types_allowed=True
102+
)

src/server/schemas.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,35 @@
1-
from pydantic import BaseModel, Field
2-
from typing import Optional
3-
from typing import Literal
1+
from pydantic import BaseModel, Field, ConfigDict
2+
from typing import Optional, Literal
3+
from datetime import datetime
44

55

66
class WebhookPayload(BaseModel):
7-
id: str = Field(..., description="Unique message identifier")
8-
status: Literal["queued", "delivered", "failed"]
9-
deliveredAt: Optional[str] = Field(None, description="Delivery timestamp if delivered")
7+
"""
8+
Schema for webhook payloads received from the API server.
9+
10+
Attributes:
11+
id (str): Unique identifier for the message.
12+
status (str): The status of the message, one of 'queued', 'delivered', or 'failed'.
13+
delivered_at (datetime, optional): Timestamp when the message was delivered, if applicable.
14+
"""
15+
id: str = Field(
16+
...,
17+
description="Unique identifier for the message.",
18+
json_schema_extra={"example": "msg123"}
19+
)
20+
status: Literal["queued", "delivered", "failed"] = Field(
21+
...,
22+
description="The status of the message.",
23+
json_schema_extra={"example": "delivered"}
24+
)
25+
delivered_at: Optional[datetime] = Field(
26+
None,
27+
alias="deliveredAt",
28+
description="Timestamp when the message was delivered, if applicable.",
29+
json_schema_extra={"example": "2024-12-01T12:00:00Z"}
30+
)
31+
32+
model_config = ConfigDict(
33+
populate_by_name=True,
34+
arbitrary_types_allowed=True
35+
)

tests/e2e/test_messages_e2e.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_send_message_and_verify(mock_api_client, messages):
1212
"to": "+123456789",
1313
"from": "+987654321",
1414
"content": "Hello, World!",
15-
"status": "sent",
15+
"status": "delivered",
1616
"createdAt": "2024-12-01T00:00:00Z",
1717
}
1818
mock_api_client.request.return_value = send_response
@@ -28,7 +28,7 @@ def test_send_message_and_verify(mock_api_client, messages):
2828
"to": "+123456789",
2929
"from": "+987654321",
3030
"content": "Hello, World!",
31-
"status": "sent",
31+
"status": "delivered",
3232
"createdAt": "2024-12-01T00:00:00Z",
3333
}
3434
mock_api_client.request.return_value = retrieve_response
@@ -55,7 +55,7 @@ def test_list_messages_with_pagination(mock_api_client, messages):
5555
"to": "+123456789",
5656
"from": "+987654321",
5757
"content": "Hello, World!",
58-
"status": "sent",
58+
"status": "delivered",
5959
"createdAt": "2024-12-01T00:00:00Z",
6060
},
6161
{
@@ -109,7 +109,7 @@ def test_resend_failed_message(mock_api_client, messages):
109109
"to": "+123456789",
110110
"from": "+987654321",
111111
"content": "Hello, World!",
112-
"status": "sent",
112+
"status": "delivered",
113113
"createdAt": "2024-12-01T00:00:00Z",
114114
}
115115
mock_api_client.request.return_value = resend_response

tests/integration/test_end_to_end_workflows.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def test_send_and_check_message_workflow(messages, mock_api_client):
1313
"from": "+987654321",
1414
"content": "Hello, World!",
1515
"sender": "+987654321",
16-
"status": "sent",
16+
"status": "delivered",
1717
"createdAt": "2024-12-01T00:00:00Z",
1818
}
1919
mock_api_client.request.return_value = sent_message_response
@@ -64,7 +64,7 @@ def test_list_contacts_and_messages_workflow(contacts, messages, mock_api_client
6464
"to": "+123456789",
6565
"from": "+987654321",
6666
"content": "Hello, World!",
67-
"status": "sent",
67+
"status": "delivered",
6868
"createdAt": "2024-12-01T00:00:00Z",
6969
}
7070
],

tests/integration/test_messages_integration.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_send_message_success(messages, mock_api_client):
1111
"from": "+987654321",
1212
"content": "Hello, World!",
1313
"sender": "Sender123",
14-
"status": "sent",
14+
"status": "delivered",
1515
"createdAt": "2024-12-01T00:00:00Z"
1616
}
1717
mock_api_client.request.return_value = mock_response
@@ -63,7 +63,7 @@ def test_list_messages_success(messages, mock_api_client):
6363
"from": "+987654321",
6464
"content": "Hello, World!",
6565
"sender": "Sender123",
66-
"status": "sent",
66+
"status": "delivered",
6767
"createdAt": "2024-12-01T00:00:00Z"
6868
}
6969
]
@@ -98,7 +98,7 @@ def test_get_message_success(messages, mock_api_client):
9898
"from": "+987654321",
9999
"content": "Hello, World!",
100100
"sender": "Sender123",
101-
"status": "sent",
101+
"status": "delivered",
102102
"createdAt": "2024-12-01T00:00:00Z"
103103
}
104104
mock_api_client.request.return_value = mock_response

0 commit comments

Comments
 (0)