Skip to content

Commit 21bf21e

Browse files
authored
Merge pull request #186 from mailersend/bugfix/issue-185/pydantic-attribute-error
fix: Fixed Pydantic validation issues
2 parents 50624c1 + 2da0a54 commit 21bf21e

26 files changed

+146
-118
lines changed

mailersend/builders/activity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def _convert_to_timestamp(self, value: Union[datetime, int]) -> int:
8585
def build_list_request(self) -> ActivityRequest:
8686
"""Build the ActivityRequest object for listing activities."""
8787
return self.build()
88-
88+
8989
def build(self) -> ActivityRequest:
9090
"""Build the ActivityRequest object."""
9191
# Convert dates to timestamps if needed
@@ -158,7 +158,7 @@ def reset(self) -> "SingleActivityBuilder":
158158
def build_get_request(self) -> SingleActivityRequest:
159159
"""Build the SingleActivityRequest object for getting a single activity."""
160160
return self.build()
161-
161+
162162
def build(self) -> SingleActivityRequest:
163163
"""
164164
Build the SingleActivityRequest object.

mailersend/builders/email.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
"""
88

99
import base64
10-
import mimetypes
1110
from datetime import datetime, timezone
1211
from pathlib import Path
13-
from typing import List, Dict, Any, Optional, Union, IO
12+
from typing import List, Dict, Any, Optional, Union
1413

1514
from ..models.email import (
1615
EmailRequest,
@@ -130,7 +129,9 @@ def to_many(self, recipients: List[Dict[str, str]]) -> "EmailBuilder":
130129
)
131130
return self
132131

133-
def cc(self, email: Union[str, List[Dict[str, str]]], name: Optional[str] = None) -> "EmailBuilder":
132+
def cc(
133+
self, email: Union[str, List[Dict[str, str]]], name: Optional[str] = None
134+
) -> "EmailBuilder":
134135
"""
135136
Add recipient(s) to the CC field.
136137
@@ -162,7 +163,9 @@ def cc(self, email: Union[str, List[Dict[str, str]]], name: Optional[str] = None
162163
raise ValidationError("Email must be a string or list of recipient objects")
163164
return self
164165

165-
def bcc(self, email: Union[str, List[Dict[str, str]]], name: Optional[str] = None) -> "EmailBuilder":
166+
def bcc(
167+
self, email: Union[str, List[Dict[str, str]]], name: Optional[str] = None
168+
) -> "EmailBuilder":
166169
"""
167170
Add recipient(s) to the BCC field.
168171

mailersend/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Data models used for communicating with the MailerSend API.
33
"""
4+
45
from .base import BaseModel
56
from .email import (
67
EmailContact,

mailersend/models/activity.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from typing import List, Optional, Any, Dict
2-
from pydantic import BaseModel, Field, EmailStr, ConfigDict
1+
"""Activity models."""
32

4-
from .base import BaseModel as BaseMailerSendModel
3+
from typing import List, Optional, Any
4+
from pydantic import Field, EmailStr, ConfigDict
5+
6+
from .base import BaseModel
57

68

79
class ActivityRecipient(BaseModel):

mailersend/models/analytics.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
"""Analytics models."""
2+
13
from typing import List, Optional, Literal
24
from pydantic import Field, field_validator, model_validator
35

4-
from .base import BaseModel as MailerSendBaseModel
6+
from .base import BaseModel
57

68

7-
class AnalyticsRequest(MailerSendBaseModel):
9+
class AnalyticsRequest(BaseModel):
810
"""
911
Request model for Analytics API endpoints.
1012
@@ -44,27 +46,30 @@ class AnalyticsRequest(MailerSendBaseModel):
4446
] = Field(None, alias="event[]")
4547

4648
@field_validator("date_from", "date_to")
49+
@classmethod
4750
def validate_timestamps(cls, v):
4851
"""Validate that timestamps are positive integers."""
4952
if v <= 0:
5053
raise ValueError("Timestamp must be a positive integer")
5154
return v
5255

5356
@model_validator(mode="after")
54-
def validate_date_range(cls, v):
57+
def validate_date_range(self):
5558
"""Validate that date_from is before date_to."""
56-
if v.date_from >= v.date_to:
59+
if self.date_from >= self.date_to:
5760
raise ValueError("date_from must be lower than date_to")
58-
return v
61+
return self
5962

6063
@field_validator("recipient_id")
64+
@classmethod
6165
def validate_recipient_count(cls, v):
6266
"""Validate recipient count limit."""
6367
if v and len(v) > 50:
6468
raise ValueError("Maximum 50 recipients are allowed")
6569
return v
6670

6771
@field_validator("tags")
72+
@classmethod
6873
def validate_tags(cls, v):
6974
"""Validate tags format."""
7075
if v:

mailersend/models/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from typing import List, Dict, Any, Generic, TypeVar, Optional, Union
1+
"""Base models."""
2+
3+
from typing import List, Dict, Any, Generic, TypeVar, Optional
24
from pydantic import BaseModel as PydanticBaseModel, ConfigDict
35
import json
46

mailersend/models/domains.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
"""Domains models."""
2+
13
from typing import Optional
24
from pydantic import Field, field_validator
35

4-
from .base import BaseModel as MailerSendBaseModel
6+
from .base import BaseModel
57

68

7-
class DomainListQueryParams(MailerSendBaseModel):
9+
class DomainListQueryParams(BaseModel):
810
"""Model for domain list query parameters with validation."""
911

1012
page: Optional[int] = Field(default=1, ge=1)
@@ -14,15 +16,15 @@ class DomainListQueryParams(MailerSendBaseModel):
1416
def to_query_params(self) -> dict:
1517
"""Convert to query parameters for API request."""
1618
params = {"page": self.page, "limit": self.limit}
17-
19+
1820
# Convert boolean to lowercase string for API compatibility
1921
if self.verified is not None:
2022
params["verified"] = str(self.verified).lower()
2123

2224
return {k: v for k, v in params.items() if v is not None}
2325

2426

25-
class DomainListRequest(MailerSendBaseModel):
27+
class DomainListRequest(BaseModel):
2628
"""Request model for listing domains."""
2729

2830
query_params: DomainListQueryParams
@@ -32,7 +34,7 @@ def to_query_params(self) -> dict:
3234
return self.query_params.to_query_params()
3335

3436

35-
class DomainRecipientsQueryParams(MailerSendBaseModel):
37+
class DomainRecipientsQueryParams(BaseModel):
3638
"""Model for domain recipients query parameters with validation."""
3739

3840
page: Optional[int] = Field(default=1, ge=1)
@@ -45,7 +47,7 @@ def to_query_params(self) -> dict:
4547
return {k: v for k, v in params.items() if v is not None}
4648

4749

48-
class DomainRecipientsRequest(MailerSendBaseModel):
50+
class DomainRecipientsRequest(BaseModel):
4951
"""Request model for getting domain recipients."""
5052

5153
domain_id: str # Path parameter
@@ -63,7 +65,7 @@ def to_query_params(self) -> dict:
6365
return self.query_params.to_query_params()
6466

6567

66-
class DomainCreateRequest(MailerSendBaseModel):
68+
class DomainCreateRequest(BaseModel):
6769
"""Request model for creating a new domain."""
6870

6971
name: str
@@ -100,7 +102,7 @@ def validate_subdomains(cls, v):
100102
return v
101103

102104

103-
class DomainDeleteRequest(MailerSendBaseModel):
105+
class DomainDeleteRequest(BaseModel):
104106
"""Request model for deleting a domain."""
105107

106108
domain_id: str
@@ -113,7 +115,7 @@ def validate_domain_id(cls, v):
113115
return v.strip()
114116

115117

116-
class DomainGetRequest(MailerSendBaseModel):
118+
class DomainGetRequest(BaseModel):
117119
"""Request model for getting a single domain."""
118120

119121
domain_id: str
@@ -126,7 +128,7 @@ def validate_domain_id(cls, v):
126128
return v.strip()
127129

128130

129-
class DomainSettings(MailerSendBaseModel):
131+
class DomainSettings(BaseModel):
130132
"""Model for domain settings."""
131133

132134
send_paused: bool = False
@@ -149,7 +151,7 @@ class DomainSettings(MailerSendBaseModel):
149151
ignore_duplicated_recipients: bool = False
150152

151153

152-
class DomainUpdateSettingsRequest(MailerSendBaseModel):
154+
class DomainUpdateSettingsRequest(BaseModel):
153155
"""Request model for updating domain settings."""
154156

155157
domain_id: str
@@ -180,7 +182,7 @@ def validate_custom_tracking_subdomain(cls, v):
180182
return v
181183

182184

183-
class DomainDnsRecordsRequest(MailerSendBaseModel):
185+
class DomainDnsRecordsRequest(BaseModel):
184186
"""Request model for getting domain DNS records."""
185187

186188
domain_id: str
@@ -193,7 +195,7 @@ def validate_domain_id(cls, v):
193195
return v.strip()
194196

195197

196-
class DomainVerificationRequest(MailerSendBaseModel):
198+
class DomainVerificationRequest(BaseModel):
197199
"""Request model for getting domain verification status."""
198200

199201
domain_id: str

mailersend/models/email.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
from typing_extensions import Self
1+
"""Email models."""
2+
23
from typing import List, Dict, Optional, Any
34
from pydantic import (
4-
BaseModel,
55
Field,
66
EmailStr,
77
ConfigDict,
88
field_validator,
99
model_validator,
1010
)
11+
from .base import BaseModel
1112
import time
1213

1314

@@ -74,12 +75,12 @@ class EmailRequest(BaseModel):
7475
model_config = ConfigDict(validate_by_name=True)
7576

7677
@model_validator(mode="after")
77-
def validate_from_email(cls, v):
78-
if v.from_email is None and v.template_id is None:
78+
def validate_from_email(self):
79+
if self.from_email is None and self.template_id is None:
7980
raise ValueError(
8081
"At least one of 'from_email' or 'template_id' is required"
8182
)
82-
return v
83+
return self
8384

8485
@field_validator("subject")
8586
def validate_subject_length(cls, v):
@@ -88,12 +89,12 @@ def validate_subject_length(cls, v):
8889
return v
8990

9091
@model_validator(mode="after")
91-
def validate_content_exists(cls, v):
92-
if v.html is None and v.text is None and v.template_id is None:
92+
def validate_content_exists(self):
93+
if self.html is None and self.text is None and self.template_id is None:
9394
raise ValueError(
9495
"At least one of 'text', 'html' or 'template_id' must be provided"
9596
)
96-
return v
97+
return self
9798

9899
@field_validator("tags")
99100
def validate_tags_count(cls, v):

mailersend/models/email_verification.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Email Verification API models for MailerSend SDK."""
22

3-
from datetime import datetime
43
from typing import List, Optional, Dict, Any
54

6-
from pydantic import BaseModel, Field, field_validator
5+
from pydantic import Field, field_validator
6+
from .base import BaseModel
77

88

99
# Query Parameters Models

mailersend/models/identities.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Identities models."""
2+
13
from typing import Optional, List, Any
24
from pydantic import field_validator, Field
35

0 commit comments

Comments
 (0)