Skip to content

Commit 0011932

Browse files
Merge pull request #458 from supertokens/feat/network-interceptor-hook
feat: added network interceptor hook
2 parents 4d73959 + 2961c44 commit 0011932

File tree

36 files changed

+718
-152
lines changed

36 files changed

+718
-152
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [unreleased]
1010

11+
## [0.16.8] - 2023-11-7
12+
13+
### Added
14+
15+
- Added `network_interceptor` to the `supertokens_config` in `init`.
16+
- This can be used to capture/modify all the HTTP requests sent to the core.
17+
- Solves the issue - https://github.com/supertokens/supertokens-core/issues/865
18+
19+
### Fixes
20+
- The sync functions `create_user_id_mapping` and `delete_user_id_mapping` now take the `force` parameter as an optional argument, just like their async counterparts.
21+
- Functions `get_users_oldest_first`, `get_users_newest_first`, `get_user_count`, `delete_user`, `create_user_id_mapping`, `get_user_id_mapping`, `delete_user_id_mapping` and `update_or_delete_user_id_mapping_info` now accept `user_context` as an optional argument.
22+
- Fixed the dependencies in the example apps
23+
- Example apps will now fetch the latest version of the frameworks
24+
1125
## [0.16.7] - 2023-11-2
1226

1327
- Added `debug` flag in `init()`. If set to `True`, debug logs will be printed.
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
Django==4.0.4
2-
django-cors-headers==3.12.0
3-
python-dotenv==0.19.2
4-
supertokens-python
1+
django-cors-headers
2+
python-dotenv
3+
supertokens-python
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
fastapi==0.68.1
2-
uvicorn==0.16.0
3-
python-dotenv==0.19.2
1+
fastapi
2+
uvicorn
3+
python-dotenv
44
supertokens-python
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
flask==2.0.1
2-
flask_cors==3.0.10
3-
python-dotenv==0.19.2
1+
flask
2+
flask_cors
3+
python-dotenv
44
supertokens-python

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070

7171
setup(
7272
name="supertokens_python",
73-
version="0.16.7",
73+
version="0.16.8",
7474
author="SuperTokens",
7575
license="Apache 2.0",
7676
author_email="[email protected]",

supertokens_python/asyncio/__init__.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
from typing import Dict, List, Optional, Union
14+
from typing import Dict, List, Optional, Union, Any
1515

1616
from supertokens_python import Supertokens
1717
from supertokens_python.interfaces import (
@@ -33,9 +33,16 @@ async def get_users_oldest_first(
3333
pagination_token: Union[str, None] = None,
3434
include_recipe_ids: Union[None, List[str]] = None,
3535
query: Union[None, Dict[str, str]] = None,
36+
user_context: Optional[Dict[str, Any]] = None,
3637
) -> UsersResponse:
3738
return await Supertokens.get_instance().get_users(
38-
tenant_id, "ASC", limit, pagination_token, include_recipe_ids, query
39+
tenant_id,
40+
"ASC",
41+
limit,
42+
pagination_token,
43+
include_recipe_ids,
44+
query,
45+
user_context,
3946
)
4047

4148

@@ -45,61 +52,82 @@ async def get_users_newest_first(
4552
pagination_token: Union[str, None] = None,
4653
include_recipe_ids: Union[None, List[str]] = None,
4754
query: Union[None, Dict[str, str]] = None,
55+
user_context: Optional[Dict[str, Any]] = None,
4856
) -> UsersResponse:
4957
return await Supertokens.get_instance().get_users(
50-
tenant_id, "DESC", limit, pagination_token, include_recipe_ids, query
58+
tenant_id,
59+
"DESC",
60+
limit,
61+
pagination_token,
62+
include_recipe_ids,
63+
query,
64+
user_context,
5165
)
5266

5367

5468
async def get_user_count(
55-
include_recipe_ids: Union[None, List[str]] = None, tenant_id: Optional[str] = None
69+
include_recipe_ids: Union[None, List[str]] = None,
70+
tenant_id: Optional[str] = None,
71+
user_context: Optional[Dict[str, Any]] = None,
5672
) -> int:
5773
return await Supertokens.get_instance().get_user_count(
58-
include_recipe_ids, tenant_id
74+
include_recipe_ids, tenant_id, user_context
5975
)
6076

6177

62-
async def delete_user(user_id: str) -> None:
63-
return await Supertokens.get_instance().delete_user(user_id)
78+
async def delete_user(
79+
user_id: str, user_context: Optional[Dict[str, Any]] = None
80+
) -> None:
81+
return await Supertokens.get_instance().delete_user(user_id, user_context)
6482

6583

6684
async def create_user_id_mapping(
6785
supertokens_user_id: str,
6886
external_user_id: str,
6987
external_user_id_info: Optional[str] = None,
7088
force: Optional[bool] = None,
89+
user_context: Optional[Dict[str, Any]] = None,
7190
) -> Union[
7291
CreateUserIdMappingOkResult,
7392
UnknownSupertokensUserIDError,
7493
UserIdMappingAlreadyExistsError,
7594
]:
7695
return await Supertokens.get_instance().create_user_id_mapping(
77-
supertokens_user_id, external_user_id, external_user_id_info, force
96+
supertokens_user_id,
97+
external_user_id,
98+
external_user_id_info,
99+
force,
100+
user_context,
78101
)
79102

80103

81104
async def get_user_id_mapping(
82105
user_id: str,
83106
user_id_type: Optional[UserIDTypes] = None,
107+
user_context: Optional[Dict[str, Any]] = None,
84108
) -> Union[GetUserIdMappingOkResult, UnknownMappingError]:
85-
return await Supertokens.get_instance().get_user_id_mapping(user_id, user_id_type)
109+
return await Supertokens.get_instance().get_user_id_mapping(
110+
user_id, user_id_type, user_context
111+
)
86112

87113

88114
async def delete_user_id_mapping(
89115
user_id: str,
90116
user_id_type: Optional[UserIDTypes] = None,
91117
force: Optional[bool] = None,
118+
user_context: Optional[Dict[str, Any]] = None,
92119
) -> DeleteUserIdMappingOkResult:
93120
return await Supertokens.get_instance().delete_user_id_mapping(
94-
user_id, user_id_type, force
121+
user_id, user_id_type, force, user_context
95122
)
96123

97124

98125
async def update_or_delete_user_id_mapping_info(
99126
user_id: str,
100127
user_id_type: Optional[UserIDTypes] = None,
101128
external_user_id_info: Optional[str] = None,
129+
user_context: Optional[Dict[str, Any]] = None,
102130
) -> Union[UpdateOrDeleteUserIdMappingInfoOkResult, UnknownMappingError]:
103131
return await Supertokens.get_instance().update_or_delete_user_id_mapping_info(
104-
user_id, user_id_type, external_user_id_info
132+
user_id, user_id_type, external_user_id_info, user_context
105133
)

supertokens_python/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from __future__ import annotations
1515

1616
SUPPORTED_CDI_VERSIONS = ["3.0"]
17-
VERSION = "0.16.7"
17+
VERSION = "0.16.8"
1818
TELEMETRY = "/telemetry"
1919
USER_COUNT = "/users/count"
2020
USER_DELETE = "/user/remove"

supertokens_python/querier.py

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414
from __future__ import annotations
1515

1616
import asyncio
17-
1817
from json import JSONDecodeError
1918
from os import environ
20-
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional
19+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Tuple
2120

2221
from httpx import AsyncClient, ConnectTimeout, NetworkError, Response
2322

@@ -50,6 +49,25 @@ class Querier:
5049
api_version = None
5150
__last_tried_index: int = 0
5251
__hosts_alive_for_testing: Set[str] = set()
52+
network_interceptor: Optional[
53+
Callable[
54+
[
55+
str,
56+
str,
57+
Dict[str, Any],
58+
Optional[Dict[str, Any]],
59+
Optional[Dict[str, Any]],
60+
Optional[Dict[str, Any]],
61+
],
62+
Tuple[
63+
str,
64+
str,
65+
Dict[str, Any],
66+
Optional[Dict[str, Any]],
67+
Optional[Dict[str, Any]],
68+
],
69+
]
70+
] = None
5371

5472
def __init__(self, hosts: List[Host], rid_to_core: Union[None, str] = None):
5573
self.__hosts = hosts
@@ -141,14 +159,37 @@ def get_instance(rid_to_core: Union[str, None] = None):
141159
return Querier(Querier.__hosts, rid_to_core)
142160

143161
@staticmethod
144-
def init(hosts: List[Host], api_key: Union[str, None] = None):
162+
def init(
163+
hosts: List[Host],
164+
api_key: Union[str, None] = None,
165+
network_interceptor: Optional[
166+
Callable[
167+
[
168+
str,
169+
str,
170+
Dict[str, Any],
171+
Optional[Dict[str, Any]],
172+
Optional[Dict[str, Any]],
173+
Optional[Dict[str, Any]],
174+
],
175+
Tuple[
176+
str,
177+
str,
178+
Dict[str, Any],
179+
Optional[Dict[str, Any]],
180+
Optional[Dict[str, Any]],
181+
],
182+
]
183+
] = None,
184+
):
145185
if not Querier.__init_called:
146186
Querier.__init_called = True
147187
Querier.__hosts = hosts
148188
Querier.__api_key = api_key
149189
Querier.api_version = None
150190
Querier.__last_tried_index = 0
151191
Querier.__hosts_alive_for_testing = set()
192+
Querier.network_interceptor = network_interceptor
152193

153194
async def __get_headers_with_api_version(self, path: NormalisedURLPath):
154195
headers = {API_VERSION_HEADER: await self.get_api_version()}
@@ -159,17 +200,33 @@ async def __get_headers_with_api_version(self, path: NormalisedURLPath):
159200
return headers
160201

161202
async def send_get_request(
162-
self, path: NormalisedURLPath, params: Union[Dict[str, Any], None] = None
203+
self,
204+
path: NormalisedURLPath,
205+
params: Union[Dict[str, Any], None],
206+
user_context: Union[Dict[str, Any], None],
163207
) -> Dict[str, Any]:
164208
if params is None:
165209
params = {}
166210

167211
async def f(url: str, method: str) -> Response:
212+
headers = await self.__get_headers_with_api_version(path)
213+
nonlocal params
214+
if Querier.network_interceptor is not None:
215+
(
216+
url,
217+
method,
218+
headers,
219+
params,
220+
_,
221+
) = Querier.network_interceptor( # pylint:disable=not-callable
222+
url, method, headers, params, {}, user_context
223+
)
224+
168225
return await self.api_request(
169226
url,
170227
method,
171228
2,
172-
headers=await self.__get_headers_with_api_version(path),
229+
headers=headers,
173230
params=params,
174231
)
175232

@@ -178,7 +235,8 @@ async def f(url: str, method: str) -> Response:
178235
async def send_post_request(
179236
self,
180237
path: NormalisedURLPath,
181-
data: Union[Dict[str, Any], None] = None,
238+
data: Union[Dict[str, Any], None],
239+
user_context: Union[Dict[str, Any], None],
182240
test: bool = False,
183241
) -> Dict[str, Any]:
184242
if data is None:
@@ -195,35 +253,64 @@ async def send_post_request(
195253
headers["content-type"] = "application/json; charset=utf-8"
196254

197255
async def f(url: str, method: str) -> Response:
256+
nonlocal headers, data
257+
if Querier.network_interceptor is not None:
258+
(
259+
url,
260+
method,
261+
headers,
262+
_,
263+
data,
264+
) = Querier.network_interceptor( # pylint:disable=not-callable
265+
url, method, headers, {}, data, user_context
266+
)
198267
return await self.api_request(
199268
url,
200269
method,
201270
2,
202-
headers=await self.__get_headers_with_api_version(path),
271+
headers=headers,
203272
json=data,
204273
)
205274

206275
return await self.__send_request_helper(path, "POST", f, len(self.__hosts))
207276

208277
async def send_delete_request(
209-
self, path: NormalisedURLPath, params: Union[Dict[str, Any], None] = None
278+
self,
279+
path: NormalisedURLPath,
280+
params: Union[Dict[str, Any], None],
281+
user_context: Union[Dict[str, Any], None],
210282
) -> Dict[str, Any]:
211283
if params is None:
212284
params = {}
213285

214286
async def f(url: str, method: str) -> Response:
287+
headers = await self.__get_headers_with_api_version(path)
288+
nonlocal params
289+
if Querier.network_interceptor is not None:
290+
(
291+
url,
292+
method,
293+
headers,
294+
params,
295+
_,
296+
) = Querier.network_interceptor( # pylint:disable=not-callable
297+
url, method, headers, params, {}, user_context
298+
)
215299
return await self.api_request(
216300
url,
217301
method,
218302
2,
219-
headers=await self.__get_headers_with_api_version(path),
303+
headers=headers,
220304
params=params,
221305
)
222306

223307
return await self.__send_request_helper(path, "DELETE", f, len(self.__hosts))
224308

225309
async def send_put_request(
226-
self, path: NormalisedURLPath, data: Union[Dict[str, Any], None] = None
310+
self,
311+
path: NormalisedURLPath,
312+
data: Union[Dict[str, Any], None],
313+
user_context: Union[Dict[str, Any], None],
227314
) -> Dict[str, Any]:
228315
if data is None:
229316
data = {}
@@ -232,6 +319,17 @@ async def send_put_request(
232319
headers["content-type"] = "application/json; charset=utf-8"
233320

234321
async def f(url: str, method: str) -> Response:
322+
nonlocal headers, data
323+
if Querier.network_interceptor is not None:
324+
(
325+
url,
326+
method,
327+
headers,
328+
_,
329+
data,
330+
) = Querier.network_interceptor( # pylint:disable=not-callable
331+
url, method, headers, {}, data, user_context
332+
)
235333
return await self.api_request(url, method, 2, headers=headers, json=data)
236334

237335
return await self.__send_request_helper(path, "PUT", f, len(self.__hosts))

0 commit comments

Comments
 (0)