Skip to content

Commit 2656703

Browse files
authored
Added support for overriding various fields of outgoing requests (#363)
This PR adds support for adding a new `RequestOverride` object which can be built with fields that can override: * API URI * API Key * Timeout * and, adding additional headers for outgoing requests.
1 parent a4f3bad commit 2656703

38 files changed

+842
-207
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ nylas-python Changelog
44
Unreleased
55
----------------
66
* Added support for adding custom headers to outgoing requests
7+
* Added support for overriding various fields of outgoing requests
78
* Added support for `provider` field in code exchange response
89
* Added clean messages support
910
* Added additional webhook triggers

nylas/config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from enum import Enum
2+
from typing import TypedDict
3+
4+
from typing_extensions import NotRequired
25

36

47
class Region(str, Enum):
@@ -10,6 +13,23 @@ class Region(str, Enum):
1013
EU = "eu"
1114

1215

16+
class RequestOverrides(TypedDict):
17+
"""
18+
Overrides to use for an outgoing request to the Nylas API
19+
20+
Attributes:
21+
api_key: The API key to use for the request.
22+
api_uri: The API URI to use for the request.
23+
timeout: The timeout to use for the request.
24+
headers: Additional headers to include in the request.
25+
"""
26+
27+
api_key: NotRequired[str]
28+
api_uri: NotRequired[str]
29+
timeout: NotRequired[int]
30+
headers: NotRequired[dict]
31+
32+
1333
DEFAULT_REGION = Region.US
1434
""" The default Nylas API region. """
1535

nylas/handler/api_resources.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,50 @@
88

99
class ListableApiResource(Resource):
1010
def list(
11-
self, path, response_type, headers=None, query_params=None, request_body=None
11+
self,
12+
path,
13+
response_type,
14+
headers=None,
15+
query_params=None,
16+
request_body=None,
17+
overrides=None,
1218
) -> ListResponse:
1319
response_json = self._http_client._execute(
14-
"GET", path, headers, query_params, request_body
20+
"GET", path, headers, query_params, request_body, overrides=overrides
1521
)
1622

1723
return ListResponse.from_dict(response_json, response_type)
1824

1925

2026
class FindableApiResource(Resource):
2127
def find(
22-
self, path, response_type, headers=None, query_params=None, request_body=None
28+
self,
29+
path,
30+
response_type,
31+
headers=None,
32+
query_params=None,
33+
request_body=None,
34+
overrides=None,
2335
) -> Response:
2436
response_json = self._http_client._execute(
25-
"GET", path, headers, query_params, request_body
37+
"GET", path, headers, query_params, request_body, overrides=overrides
2638
)
2739

2840
return Response.from_dict(response_json, response_type)
2941

3042

3143
class CreatableApiResource(Resource):
3244
def create(
33-
self, path, response_type, headers=None, query_params=None, request_body=None
45+
self,
46+
path,
47+
response_type,
48+
headers=None,
49+
query_params=None,
50+
request_body=None,
51+
overrides=None,
3452
) -> Response:
3553
response_json = self._http_client._execute(
36-
"POST", path, headers, query_params, request_body
54+
"POST", path, headers, query_params, request_body, overrides=overrides
3755
)
3856

3957
return Response.from_dict(response_json, response_type)
@@ -48,9 +66,10 @@ def update(
4866
query_params=None,
4967
request_body=None,
5068
method="PUT",
69+
overrides=None,
5170
):
5271
response_json = self._http_client._execute(
53-
method, path, headers, query_params, request_body
72+
method, path, headers, query_params, request_body, overrides=overrides
5473
)
5574

5675
return Response.from_dict(response_json, response_type)
@@ -64,11 +83,12 @@ def destroy(
6483
headers=None,
6584
query_params=None,
6685
request_body=None,
86+
overrides=None,
6787
):
6888
if response_type is None:
6989
response_type = DeleteResponse
7090

7191
response_json = self._http_client._execute(
72-
"DELETE", path, headers, query_params, request_body
92+
"DELETE", path, headers, query_params, request_body, overrides=overrides
7393
)
7494
return response_type.from_dict(response_json)

nylas/handler/http_client.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,26 @@ def _execute(
8080
query_params=None,
8181
request_body=None,
8282
data=None,
83+
overrides=None,
8384
) -> dict:
8485
request = self._build_request(
85-
method, path, headers, query_params, request_body, data
86+
method, path, headers, query_params, request_body, data, overrides
8687
)
88+
89+
timeout = self.timeout
90+
if overrides and overrides.get("timeout"):
91+
timeout = overrides["timeout"]
8792
try:
8893
response = self.session.request(
8994
request["method"],
9095
request["url"],
9196
headers=request["headers"],
9297
json=request_body,
93-
timeout=self.timeout,
98+
timeout=timeout,
9499
data=data,
95100
)
96101
except requests.exceptions.Timeout as exc:
97-
raise NylasSdkTimeoutError(
98-
url=request["url"], timeout=self.timeout
99-
) from exc
102+
raise NylasSdkTimeoutError(url=request["url"], timeout=timeout) from exc
100103

101104
return _validate_response(response)
102105

@@ -106,14 +109,19 @@ def _execute_download_request(
106109
headers=None,
107110
query_params=None,
108111
stream=False,
112+
overrides=None,
109113
) -> Union[bytes, Response]:
110-
request = self._build_request("GET", path, headers, query_params)
114+
request = self._build_request("GET", path, headers, query_params, overrides)
115+
116+
timeout = self.timeout
117+
if overrides and overrides.get("timeout"):
118+
timeout = overrides["timeout"]
111119
try:
112120
response = self.session.request(
113121
request["method"],
114122
request["url"],
115123
headers=request["headers"],
116-
timeout=self.timeout,
124+
timeout=timeout,
117125
stream=stream,
118126
)
119127

@@ -123,9 +131,7 @@ def _execute_download_request(
123131

124132
return response.content if response.content else None
125133
except requests.exceptions.Timeout as exc:
126-
raise NylasSdkTimeoutError(
127-
url=request["url"], timeout=self.timeout
128-
) from exc
134+
raise NylasSdkTimeoutError(url=request["url"], timeout=timeout) from exc
129135

130136
def _build_request(
131137
self,
@@ -135,10 +141,15 @@ def _build_request(
135141
query_params: dict = None,
136142
request_body=None,
137143
data=None,
144+
overrides=None,
138145
) -> dict:
139-
base_url = f"{self.api_server}{path}"
146+
api_server = self.api_server
147+
if overrides and overrides.get("api_uri"):
148+
api_server = overrides["api_uri"]
149+
150+
base_url = f"{api_server}{path}"
140151
url = _build_query_params(base_url, query_params) if query_params else base_url
141-
headers = self._build_headers(headers, request_body, data)
152+
headers = self._build_headers(headers, request_body, data, overrides)
142153

143154
return {
144155
"method": method,
@@ -147,23 +158,32 @@ def _build_request(
147158
}
148159

149160
def _build_headers(
150-
self, extra_headers: dict = None, response_body=None, data=None
161+
self, extra_headers: dict = None, response_body=None, data=None, overrides=None
151162
) -> dict:
163+
override_headers = {}
164+
if overrides and overrides.get("headers"):
165+
override_headers = overrides["headers"]
166+
152167
if extra_headers is None:
153168
extra_headers = {}
154169

155170
major, minor, revision, _, __ = sys.version_info
156171
user_agent_header = (
157172
f"Nylas Python SDK {__VERSION__} - {major}.{minor}.{revision}"
158173
)
174+
175+
api_key = self.api_key
176+
if overrides and overrides.get("api_key"):
177+
api_key = overrides["api_key"]
178+
159179
headers = {
160180
"X-Nylas-API-Wrapper": "python",
161181
"User-Agent": user_agent_header,
162-
"Authorization": f"Bearer {self.api_key}",
182+
"Authorization": f"Bearer {api_key}",
163183
}
164184
if data is not None and data.content_type is not None:
165185
headers["Content-type"] = data.content_type
166186
elif response_body is not None:
167187
headers["Content-type"] = "application/json"
168188

169-
return {**headers, **extra_headers}
189+
return {**headers, **extra_headers, **override_headers}

nylas/resources/applications.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from nylas.config import RequestOverrides
12
from nylas.models.application_details import ApplicationDetails
23
from nylas.models.response import Response
34
from nylas.resources.redirect_uris import RedirectUris
@@ -22,15 +23,18 @@ def redirect_uris(self) -> RedirectUris:
2223
"""
2324
return RedirectUris(self._http_client)
2425

25-
def info(self) -> Response[ApplicationDetails]:
26+
def info(self, overrides: RequestOverrides = None) -> Response[ApplicationDetails]:
2627
"""
2728
Get the application information.
2829
30+
Args:
31+
overrides: The query parameters to include in the request.
32+
2933
Returns:
3034
Response: The application information.
3135
"""
3236

3337
json_response = self._http_client._execute(
34-
method="GET", path="/v3/applications"
38+
method="GET", path="/v3/applications", overrides=overrides
3539
)
3640
return Response.from_dict(json_response, ApplicationDetails)

nylas/resources/attachments.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from requests import Response
22

3+
from nylas.config import RequestOverrides
34
from nylas.handler.api_resources import (
45
FindableApiResource,
56
)
@@ -21,6 +22,7 @@ def find(
2122
identifier: str,
2223
attachment_id: str,
2324
query_params: FindAttachmentQueryParams,
25+
overrides: RequestOverrides = None,
2426
) -> NylasResponse[Attachment]:
2527
"""
2628
Return metadata of an attachment.
@@ -29,6 +31,7 @@ def find(
2931
identifier: The identifier of the Grant to act upon.
3032
attachment_id: The id of the attachment to retrieve.
3133
query_params: The query parameters to include in the request.
34+
overrides: The request overrides to use for the request.
3235
3336
Returns:
3437
The attachment metadata.
@@ -37,13 +40,15 @@ def find(
3740
path=f"/v3/grants/{identifier}/attachments/{attachment_id}",
3841
response_type=Attachment,
3942
query_params=query_params,
43+
overrides=overrides,
4044
)
4145

4246
def download(
4347
self,
4448
identifier: str,
4549
attachment_id: str,
4650
query_params: FindAttachmentQueryParams,
51+
overrides: RequestOverrides = None,
4752
) -> Response:
4853
"""
4954
Download the attachment data.
@@ -56,6 +61,7 @@ def download(
5661
identifier: The identifier of the Grant to act upon.
5762
attachment_id: The id of the attachment to download.
5863
query_params: The query parameters to include in the request.
64+
overrides: The request overrides to use for the request.
5965
6066
Returns:
6167
The Response object containing the file data.
@@ -78,13 +84,15 @@ def download(
7884
path=f"/v3/grants/{identifier}/attachments/{attachment_id}/download",
7985
query_params=query_params,
8086
stream=True,
87+
overrides=overrides,
8188
)
8289

8390
def download_bytes(
8491
self,
8592
identifier: str,
8693
attachment_id: str,
8794
query_params: FindAttachmentQueryParams,
95+
overrides: RequestOverrides = None,
8896
) -> bytes:
8997
"""
9098
Download the attachment as a byte array.
@@ -93,6 +101,7 @@ def download_bytes(
93101
identifier: The identifier of the Grant to act upon.
94102
attachment_id: The id of the attachment to download.
95103
query_params: The query parameters to include in the request.
104+
overrides: The request overrides to use for the request.
96105
97106
Returns:
98107
The raw file data.
@@ -101,4 +110,5 @@ def download_bytes(
101110
path=f"/v3/grants/{identifier}/attachments/{attachment_id}/download",
102111
query_params=query_params,
103112
stream=False,
113+
overrides=overrides,
104114
)

0 commit comments

Comments
 (0)