Skip to content

Commit d84206e

Browse files
authored
feat(api): add team RBAC and complete sync and async email options (#19)
* Sync SDK with current OpenAPI specifications * Complete email settings, attachments, and batch idempotency
1 parent f668d78 commit d84206e

5 files changed

Lines changed: 387 additions & 185 deletions

File tree

‎src/lettermint/endpoints/api.py‎

Lines changed: 40 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -138,44 +138,6 @@ def rotate_token(self, project_id: str) -> lm_types.ProjectRotateTokenResponse:
138138
),
139139
)
140140

141-
def update_members(
142-
self, project_id: str, data: lm_types.ProjectUpdateMembersRequest
143-
) -> lm_types.ProjectUpdateMembersResponse:
144-
return cast(
145-
lm_types.ProjectUpdateMembersResponse,
146-
self._client.put(
147-
self._path("/projects/{projectId}/members", projectId=project_id),
148-
data=data,
149-
),
150-
)
151-
152-
def add_member(self, project_id: str, team_member_id: str) -> lm_types.ProjectAddMemberResponse:
153-
return cast(
154-
lm_types.ProjectAddMemberResponse,
155-
self._client.post(
156-
self._path(
157-
"/projects/{projectId}/members/{teamMemberId}",
158-
projectId=project_id,
159-
teamMemberId=team_member_id,
160-
),
161-
data={},
162-
),
163-
)
164-
165-
def remove_member(
166-
self, project_id: str, team_member_id: str
167-
) -> lm_types.ProjectRemoveMemberResponse:
168-
return cast(
169-
lm_types.ProjectRemoveMemberResponse,
170-
self._client.delete(
171-
self._path(
172-
"/projects/{projectId}/members/{teamMemberId}",
173-
projectId=project_id,
174-
teamMemberId=team_member_id,
175-
)
176-
),
177-
)
178-
179141
def routes(self, project_id: str, query: Query | None = None) -> lm_types.RouteIndexResponse:
180142
return cast(
181143
lm_types.RouteIndexResponse,
@@ -263,9 +225,29 @@ def update(self, data: lm_types.TeamUpdateRequest) -> lm_types.TeamUpdateRespons
263225
def usage(self) -> lm_types.TeamUsageResponse:
264226
return cast(lm_types.TeamUsageResponse, self._client.get("/team/usage"))
265227

228+
def roles(self) -> lm_types.TeamRolesResponse:
229+
return cast(lm_types.TeamRolesResponse, self._client.get("/team/roles"))
230+
266231
def members(self, query: Query | None = None) -> lm_types.TeamMembersResponse:
267232
return cast(lm_types.TeamMembersResponse, self._client.get("/team/members", params=query))
268233

234+
def member(self, user_id: str) -> lm_types.TeamMembersShowResponse:
235+
return cast(
236+
lm_types.TeamMembersShowResponse,
237+
self._client.get(self._path("/team/members/{userId}", userId=user_id)),
238+
)
239+
240+
def update_member_assignment(
241+
self, user_id: str, data: lm_types.TeamMembersAssignmentUpdateRequest
242+
) -> lm_types.TeamMembersAssignmentUpdateResponse:
243+
return cast(
244+
lm_types.TeamMembersAssignmentUpdateResponse,
245+
self._client.put(
246+
self._path("/team/members/{userId}/assignment", userId=user_id),
247+
data=data,
248+
),
249+
)
250+
269251

270252
class WebhooksEndpoint(Endpoint):
271253
def list(self, query: Query | None = None) -> lm_types.WebhookIndexResponse:
@@ -481,45 +463,6 @@ async def rotate_token(self, project_id: str) -> lm_types.ProjectRotateTokenResp
481463
),
482464
)
483465

484-
async def update_members(
485-
self, project_id: str, data: lm_types.ProjectUpdateMembersRequest
486-
) -> lm_types.ProjectUpdateMembersResponse:
487-
return cast(
488-
lm_types.ProjectUpdateMembersResponse,
489-
await self._client.put(
490-
self._path("/projects/{projectId}/members", projectId=project_id), data=data
491-
),
492-
)
493-
494-
async def add_member(
495-
self, project_id: str, team_member_id: str
496-
) -> lm_types.ProjectAddMemberResponse:
497-
return cast(
498-
lm_types.ProjectAddMemberResponse,
499-
await self._client.post(
500-
self._path(
501-
"/projects/{projectId}/members/{teamMemberId}",
502-
projectId=project_id,
503-
teamMemberId=team_member_id,
504-
),
505-
data={},
506-
),
507-
)
508-
509-
async def remove_member(
510-
self, project_id: str, team_member_id: str
511-
) -> lm_types.ProjectRemoveMemberResponse:
512-
return cast(
513-
lm_types.ProjectRemoveMemberResponse,
514-
await self._client.delete(
515-
self._path(
516-
"/projects/{projectId}/members/{teamMemberId}",
517-
projectId=project_id,
518-
teamMemberId=team_member_id,
519-
)
520-
),
521-
)
522-
523466
async def routes(
524467
self, project_id: str, query: Query | None = None
525468
) -> lm_types.RouteIndexResponse:
@@ -612,11 +555,31 @@ async def update(self, data: lm_types.TeamUpdateRequest) -> lm_types.TeamUpdateR
612555
async def usage(self) -> lm_types.TeamUsageResponse:
613556
return cast(lm_types.TeamUsageResponse, await self._client.get("/team/usage"))
614557

558+
async def roles(self) -> lm_types.TeamRolesResponse:
559+
return cast(lm_types.TeamRolesResponse, await self._client.get("/team/roles"))
560+
615561
async def members(self, query: Query | None = None) -> lm_types.TeamMembersResponse:
616562
return cast(
617563
lm_types.TeamMembersResponse, await self._client.get("/team/members", params=query)
618564
)
619565

566+
async def member(self, user_id: str) -> lm_types.TeamMembersShowResponse:
567+
return cast(
568+
lm_types.TeamMembersShowResponse,
569+
await self._client.get(self._path("/team/members/{userId}", userId=user_id)),
570+
)
571+
572+
async def update_member_assignment(
573+
self, user_id: str, data: lm_types.TeamMembersAssignmentUpdateRequest
574+
) -> lm_types.TeamMembersAssignmentUpdateResponse:
575+
return cast(
576+
lm_types.TeamMembersAssignmentUpdateResponse,
577+
await self._client.put(
578+
self._path("/team/members/{userId}/assignment", userId=user_id),
579+
data=data,
580+
),
581+
)
582+
620583

621584
class AsyncWebhooksEndpoint(AsyncEndpoint):
622585
async def list(self, query: Query | None = None) -> lm_types.WebhookIndexResponse:

‎src/lettermint/endpoints/email.py‎

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
else:
1313
from typing_extensions import Self
1414

15-
from ..types import SendBatchEmailResponse, SendEmailResponse
15+
from ..types import SendBatchEmailResponse, SendBatchMailRequest, SendEmailResponse, TlsPolicy
1616
from .endpoint import AsyncEndpoint, Endpoint
1717

1818
if TYPE_CHECKING:
@@ -207,13 +207,15 @@ def attach(
207207
filename: str,
208208
content: str,
209209
content_id: str | None = None,
210+
content_type: str | None = None,
210211
) -> Self:
211212
"""Attach a file to the email.
212213
213214
Args:
214215
filename: The attachment filename.
215216
content: The base64-encoded file content.
216217
content_id: Optional Content-ID for inline attachments.
218+
content_type: Optional MIME type for the attachment.
217219
218220
Returns:
219221
The current instance for method chaining.
@@ -233,10 +235,17 @@ def attach(
233235
}
234236
if content_id is not None:
235237
attachment["content_id"] = content_id
238+
if content_type is not None:
239+
attachment["content_type"] = content_type
236240

237241
self._payload["attachments"].append(attachment)
238242
return self
239243

244+
def settings(self, settings: dict[str, bool | TlsPolicy]) -> Self:
245+
"""Set per-email settings that override the selected route."""
246+
self._payload["settings"] = settings
247+
return self
248+
240249
def metadata(self, metadata: dict[str, str]) -> Self:
241250
"""Set metadata for the email.
242251
@@ -297,10 +306,21 @@ def send(self) -> SendEmailResponse:
297306
finally:
298307
self._reset()
299308

300-
def send_batch(self, payload: list[dict[str, Any]]) -> SendBatchEmailResponse:
309+
def send_batch(self, payload: SendBatchMailRequest) -> SendBatchEmailResponse:
301310
"""Send multiple emails in one batch."""
302-
response: SendBatchEmailResponse = self._client.post("/send/batch", data=payload)
303-
return response
311+
headers: dict[str, str] | None = None
312+
if self._idempotency_key is not None:
313+
headers = {"Idempotency-Key": self._idempotency_key}
314+
315+
try:
316+
response: SendBatchEmailResponse = self._client.post(
317+
"/send/batch",
318+
data=payload,
319+
headers=headers,
320+
)
321+
return response
322+
finally:
323+
self._reset()
304324

305325
def ping(self) -> str:
306326
"""Ping the Sending API."""
@@ -478,13 +498,15 @@ def attach(
478498
filename: str,
479499
content: str,
480500
content_id: str | None = None,
501+
content_type: str | None = None,
481502
) -> Self:
482503
"""Attach a file to the email.
483504
484505
Args:
485506
filename: The attachment filename.
486507
content: The base64-encoded file content.
487508
content_id: Optional Content-ID for inline attachments.
509+
content_type: Optional MIME type for the attachment.
488510
489511
Returns:
490512
The current instance for method chaining.
@@ -498,10 +520,17 @@ def attach(
498520
}
499521
if content_id is not None:
500522
attachment["content_id"] = content_id
523+
if content_type is not None:
524+
attachment["content_type"] = content_type
501525

502526
self._payload["attachments"].append(attachment)
503527
return self
504528

529+
def settings(self, settings: dict[str, bool | TlsPolicy]) -> Self:
530+
"""Set per-email settings that override the selected route."""
531+
self._payload["settings"] = settings
532+
return self
533+
505534
def metadata(self, metadata: dict[str, str]) -> Self:
506535
"""Set metadata for the email.
507536
@@ -554,9 +583,18 @@ async def _send() -> SendEmailResponse:
554583

555584
return _send()
556585

557-
async def send_batch(self, payload: list[dict[str, Any]]) -> SendBatchEmailResponse:
586+
async def send_batch(self, payload: SendBatchMailRequest) -> SendBatchEmailResponse:
558587
"""Send multiple emails in one batch asynchronously."""
559-
response: SendBatchEmailResponse = await self._client.post("/send/batch", data=payload)
588+
headers: dict[str, str] | None = None
589+
if self._idempotency_key is not None:
590+
headers = {"Idempotency-Key": self._idempotency_key}
591+
self._reset()
592+
593+
response: SendBatchEmailResponse = await self._client.post(
594+
"/send/batch",
595+
data=payload,
596+
headers=headers,
597+
)
560598
return response
561599

562600
async def ping(self) -> str:

0 commit comments

Comments
 (0)