Skip to content

Commit db679b1

Browse files
authored
feat(logs): add logs API support (#194)
1 parent 5f23893 commit db679b1

10 files changed

Lines changed: 558 additions & 1 deletion

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,3 +901,6 @@ FodyWeavers.xsd
901901
# End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows,python,jupyternotebooks,jetbrains,pycharm,vim,emacs,visualstudiocode,visualstudio
902902

903903
scratch/
904+
905+
# Allow resend/logs module (overrides [Ll]ogs/ rule above)
906+
!resend/logs/

examples/async/logs_async.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import asyncio
2+
import os
3+
4+
import resend
5+
6+
if not os.environ["RESEND_API_KEY"]:
7+
raise EnvironmentError("RESEND_API_KEY is missing")
8+
9+
10+
async def main() -> None:
11+
logs: resend.Logs.ListResponse = await resend.Logs.list_async()
12+
for log in logs["data"]:
13+
print(log["id"])
14+
print(log["endpoint"])
15+
print(log["method"])
16+
print(log["response_status"])
17+
print(log["created_at"])
18+
19+
print("\n--- Using pagination parameters ---")
20+
if logs["data"]:
21+
paginated_params: resend.Logs.ListParams = {
22+
"limit": 10,
23+
"after": logs["data"][0]["id"],
24+
}
25+
paginated_logs: resend.Logs.ListResponse = await resend.Logs.list_async(
26+
params=paginated_params
27+
)
28+
print(f"Retrieved {len(paginated_logs['data'])} logs with pagination")
29+
print(f"Has more logs: {paginated_logs['has_more']}")
30+
else:
31+
print("No logs available for pagination example")
32+
33+
print("\n--- Retrieve a single log ---")
34+
if logs["data"]:
35+
log_id = logs["data"][0]["id"]
36+
single_log: resend.Logs.GetResponse = await resend.Logs.get_async(log_id)
37+
print(f"Log ID: {single_log['id']}")
38+
print(f"Endpoint: {single_log['endpoint']}")
39+
print(f"Method: {single_log['method']}")
40+
print(f"Status: {single_log['response_status']}")
41+
print(f"Request body: {single_log['request_body']}")
42+
print(f"Response body: {single_log['response_body']}")
43+
44+
45+
asyncio.run(main())

examples/logs.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import os
2+
3+
import resend
4+
5+
if not os.environ["RESEND_API_KEY"]:
6+
raise EnvironmentError("RESEND_API_KEY is missing")
7+
8+
logs: resend.Logs.ListResponse = resend.Logs.list()
9+
for log in logs["data"]:
10+
print(log["id"])
11+
print(log["endpoint"])
12+
print(log["method"])
13+
print(log["response_status"])
14+
print(log["created_at"])
15+
16+
print("\n--- Using pagination parameters ---")
17+
if logs["data"]:
18+
paginated_params: resend.Logs.ListParams = {
19+
"limit": 10,
20+
"after": logs["data"][0]["id"],
21+
}
22+
paginated_logs: resend.Logs.ListResponse = resend.Logs.list(
23+
params=paginated_params
24+
)
25+
print(f"Retrieved {len(paginated_logs['data'])} logs with pagination")
26+
print(f"Has more logs: {paginated_logs['has_more']}")
27+
else:
28+
print("No logs available for pagination example")
29+
30+
print("\n--- Retrieve a single log ---")
31+
if logs["data"]:
32+
log_id = logs["data"][0]["id"]
33+
single_log: resend.Logs.GetResponse = resend.Logs.get(log_id)
34+
print(f"Log ID: {single_log['id']}")
35+
print(f"Endpoint: {single_log['endpoint']}")
36+
print(f"Method: {single_log['method']}")
37+
print(f"Status: {single_log['response_status']}")
38+
print(f"Request body: {single_log['request_body']}")
39+
print(f"Response body: {single_log['response_body']}")

resend/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from .contacts.segments._contact_segments import ContactSegments
1818
from .domains._domain import Domain
1919
from .domains._domains import Domains
20+
from .logs._log import Log
21+
from .logs._logs import Logs
2022
from .emails._attachment import Attachment, RemoteAttachment
2123
from .emails._attachments import Attachments as EmailAttachments
2224
from .emails._batch import Batch, BatchValidationError
@@ -74,6 +76,7 @@
7476
"Templates",
7577
"Webhooks",
7678
"Topics",
79+
"Logs",
7780
# Types
7881
"Audience",
7982
"Contact",
@@ -84,6 +87,7 @@
8487
"TopicSubscriptionUpdate",
8588
"Domain",
8689
"ApiKey",
90+
"Log",
8791
"Email",
8892
"Attachment",
8993
"RemoteAttachment",

resend/logs/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from resend.logs._log import Log
2+
from resend.logs._logs import Logs
3+
4+
__all__ = ["Log", "Logs"]

resend/logs/_log.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from typing import Any
2+
3+
from typing_extensions import NotRequired, TypedDict
4+
5+
6+
class Log(TypedDict):
7+
id: str
8+
"""
9+
The log ID
10+
"""
11+
created_at: str
12+
"""
13+
The date and time the log was created
14+
"""
15+
endpoint: str
16+
"""
17+
The API endpoint that was called
18+
"""
19+
method: str
20+
"""
21+
The HTTP method used
22+
"""
23+
response_status: int
24+
"""
25+
The HTTP response status code
26+
"""
27+
user_agent: str
28+
"""
29+
The user agent of the client
30+
"""
31+
request_body: NotRequired[Any]
32+
"""
33+
The original request body (only present when retrieving a single log)
34+
"""
35+
response_body: NotRequired[Any]
36+
"""
37+
The API response body (only present when retrieving a single log)
38+
"""

resend/logs/_logs.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
from typing import Any, Dict, List, Optional, cast
2+
3+
from typing_extensions import NotRequired, TypedDict
4+
5+
from resend import request
6+
from resend._base_response import BaseResponse
7+
from resend.logs._log import Log
8+
from resend.pagination_helper import PaginationHelper
9+
10+
# Async imports (optional - only available with pip install resend[async])
11+
try:
12+
from resend.async_request import AsyncRequest
13+
except ImportError:
14+
pass
15+
16+
17+
class Logs:
18+
19+
class GetResponse(BaseResponse):
20+
"""
21+
GetResponse type that wraps a single log object
22+
23+
Attributes:
24+
object (str): The object type, always "log"
25+
id (str): The log ID
26+
created_at (str): The date and time the log was created
27+
endpoint (str): The API endpoint that was called
28+
method (str): The HTTP method used
29+
response_status (int): The HTTP response status code
30+
user_agent (str): The user agent of the client
31+
request_body (Any): The original request body
32+
response_body (Any): The API response body
33+
"""
34+
35+
object: str
36+
"""
37+
The object type, always "log"
38+
"""
39+
id: str
40+
"""
41+
The log ID
42+
"""
43+
created_at: str
44+
"""
45+
The date and time the log was created
46+
"""
47+
endpoint: str
48+
"""
49+
The API endpoint that was called
50+
"""
51+
method: str
52+
"""
53+
The HTTP method used
54+
"""
55+
response_status: int
56+
"""
57+
The HTTP response status code
58+
"""
59+
user_agent: str
60+
"""
61+
The user agent of the client
62+
"""
63+
request_body: Any
64+
"""
65+
The original request body
66+
"""
67+
response_body: Any
68+
"""
69+
The API response body
70+
"""
71+
72+
class ListResponse(BaseResponse):
73+
"""
74+
ListResponse type that wraps a list of log objects with pagination metadata
75+
76+
Attributes:
77+
object (str): The object type, always "list"
78+
data (List[Log]): A list of log objects
79+
has_more (bool): Whether there are more results available
80+
"""
81+
82+
object: str
83+
"""
84+
The object type, always "list"
85+
"""
86+
data: List[Log]
87+
"""
88+
A list of log objects
89+
"""
90+
has_more: bool
91+
"""
92+
Whether there are more results available for pagination
93+
"""
94+
95+
class ListParams(TypedDict):
96+
limit: NotRequired[int]
97+
"""
98+
Number of logs to retrieve. Maximum is 100, and minimum is 1.
99+
"""
100+
after: NotRequired[str]
101+
"""
102+
The ID after which we'll retrieve more logs (for pagination).
103+
This ID will not be included in the returned list.
104+
Cannot be used with the before parameter.
105+
"""
106+
before: NotRequired[str]
107+
"""
108+
The ID before which we'll retrieve more logs (for pagination).
109+
This ID will not be included in the returned list.
110+
Cannot be used with the after parameter.
111+
"""
112+
113+
@classmethod
114+
def get(cls, log_id: str) -> GetResponse:
115+
"""
116+
Retrieve a single log by its ID.
117+
see more: https://resend.com/docs/api-reference/logs/retrieve-log
118+
119+
Args:
120+
log_id (str): The ID of the log to retrieve
121+
122+
Returns:
123+
GetResponse: The log object
124+
"""
125+
path = f"/logs/{log_id}"
126+
resp = request.Request[Logs.GetResponse](
127+
path=path, params={}, verb="get"
128+
).perform_with_content()
129+
return resp
130+
131+
@classmethod
132+
def list(cls, params: Optional[ListParams] = None) -> ListResponse:
133+
"""
134+
Retrieve a list of logs.
135+
see more: https://resend.com/docs/api-reference/logs/list-logs
136+
137+
Args:
138+
params (Optional[ListParams]): Optional pagination parameters
139+
- limit: Number of logs to retrieve (max 100, min 1)
140+
- after: ID after which to retrieve more logs
141+
- before: ID before which to retrieve more logs
142+
143+
Returns:
144+
ListResponse: A list of log objects
145+
"""
146+
base_path = "/logs"
147+
query_params = cast(Dict[Any, Any], params) if params else None
148+
path = PaginationHelper.build_paginated_path(base_path, query_params)
149+
resp = request.Request[Logs.ListResponse](
150+
path=path, params={}, verb="get"
151+
).perform_with_content()
152+
return resp
153+
154+
@classmethod
155+
async def get_async(cls, log_id: str) -> GetResponse:
156+
"""
157+
Retrieve a single log by its ID (async).
158+
see more: https://resend.com/docs/api-reference/logs/retrieve-log
159+
160+
Args:
161+
log_id (str): The ID of the log to retrieve
162+
163+
Returns:
164+
GetResponse: The log object
165+
"""
166+
path = f"/logs/{log_id}"
167+
resp = await AsyncRequest[Logs.GetResponse](
168+
path=path, params={}, verb="get"
169+
).perform_with_content()
170+
return resp
171+
172+
@classmethod
173+
async def list_async(cls, params: Optional[ListParams] = None) -> ListResponse:
174+
"""
175+
Retrieve a list of logs (async).
176+
see more: https://resend.com/docs/api-reference/logs/list-logs
177+
178+
Args:
179+
params (Optional[ListParams]): Optional pagination parameters
180+
- limit: Number of logs to retrieve (max 100, min 1)
181+
- after: ID after which to retrieve more logs
182+
- before: ID before which to retrieve more logs
183+
184+
Returns:
185+
ListResponse: A list of log objects
186+
"""
187+
base_path = "/logs"
188+
query_params = cast(Dict[Any, Any], params) if params else None
189+
path = PaginationHelper.build_paginated_path(base_path, query_params)
190+
resp = await AsyncRequest[Logs.ListResponse](
191+
path=path, params={}, verb="get"
192+
).perform_with_content()
193+
return resp

resend/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "2.26.0"
1+
__version__ = "2.27.0"
22

33

44
def get_version() -> str:

0 commit comments

Comments
 (0)