From 9f710fba58f9cec3f712fc16e0081b445b22c499 Mon Sep 17 00:00:00 2001 From: aviadl Date: Tue, 2 Dec 2025 21:45:35 +0200 Subject: [PATCH 1/8] Load users by ID + tests related to https://github.com/descope/etc/issues/12741 --- .vscode/settings.json | 6 ++++- README.md | 6 +++++ descope/management/common.py | 1 + descope/management/user.py | 38 +++++++++++++++++++++++++++ tests/management/test_user.py | 49 +++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e5b872c6..006ab76ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,9 @@ "mypy-type-checker.importStrategy": "fromEnvironment", "isort.importStrategy": "fromEnvironment", "black-formatter.importStrategy": "fromEnvironment", - "workbench.colorCustomizations": { /* do not change please... */} + "workbench.colorCustomizations": { + "activityBar.background": "#4D1C3B", + "titleBar.activeBackground": "#6B2752", + "titleBar.activeForeground": "#FDF8FB" + } } \ No newline at end of file diff --git a/README.md b/README.md index 917b59da5..79c581366 100644 --- a/README.md +++ b/README.md @@ -706,6 +706,12 @@ descope_client.mgmt.user.logout_user("") # Logout user from all devices by user ID descope_client.mgmt.user.logout_user_by_user_id("") +# Load users by their user id +users_resp = descope_client.mgmt.user.load_users(user_ids=[""]) +users = users_resp["users"] + for user in users: + # Do something + # Search all users, optionally according to tenant and/or role filter # results can be paginated using the limit and page parameters, as well as by time with the from_created_time, to_created_time, from_modified_time, and to_modified_time users_resp = descope_client.mgmt.user.search_all(tenant_ids=["my-tenant-id"]) diff --git a/descope/management/common.py b/descope/management/common.py index ebdc45dc9..151bef478 100644 --- a/descope/management/common.py +++ b/descope/management/common.py @@ -87,6 +87,7 @@ class MgmtV1: user_logout_path = "/v1/mgmt/user/logout" user_delete_all_test_users_path = "/v1/mgmt/user/test/delete/all" user_load_path = "/v1/mgmt/user" + users_load_path = "/v1/mgmt/users/load" users_search_path = "/v2/mgmt/user/search" test_users_search_path = "/v2/mgmt/user/search/test" user_get_provider_token = "/v1/mgmt/user/provider/token" diff --git a/descope/management/user.py b/descope/management/user.py index 24cfd6c2b..df8ec2f09 100644 --- a/descope/management/user.py +++ b/descope/management/user.py @@ -653,6 +653,44 @@ def logout_user_by_user_id( body={"userId": user_id}, ) + def load_users( + self, + user_ids: List[str] = None, + include_invalid_users: Optional[bool] = None, + ) -> dict: + """ + Search all users. + + Args: + user_ids (List[str]): Optional list of user IDs to filter by + include_invalid_users (bool): Optional flag to include invalid users in the response + + Return value (dict): + Return dict in the format + {"users": []} + "users" contains a list of all of the found users and their information + + Raise: + AuthException: raised if search operation fails + """ + if user_ids is None or len(user_ids) == 0: + raise AuthException( + 400, ERROR_TYPE_INVALID_ARGUMENT, "At list one user id needs to be supplied" + ) + + body: dict[str, Union[List[str], bool]] = { + "userIds": user_ids, + } + + if include_invalid_users is not None: + body["includeInvalidUsers"] = include_invalid_users + + response = self._http.post( + MgmtV1.users_load_path, + body=body, + ) + return response.json() + def search_all( self, tenant_ids: Optional[List[str]] = None, diff --git a/tests/management/test_user.py b/tests/management/test_user.py index 0af438ac6..e8ff7aa00 100644 --- a/tests/management/test_user.py +++ b/tests/management/test_user.py @@ -1013,6 +1013,55 @@ def test_load_by_user_id(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) + def test_load_users(self): + # Test failed flows + with patch("requests.post") as mock_post: + mock_post.return_value.ok = False + self.assertRaises( + AuthException, + self.client.mgmt.user.load_users, + [""], + ) + + with patch("requests.post") as mock_post: + mock_post.return_value.ok = True + self.assertRaises( + AuthException, self.client.mgmt.user.load_users, None, False + ) + + # Test success flow + with patch("requests.post") as mock_post: + network_resp = mock.Mock() + network_resp.ok = True + network_resp.json.return_value = json.loads( + """{"users": [{"id": "u1"}, {"id": "u2"}]}""" + ) + mock_post.return_value = network_resp + resp = self.client.mgmt.user.load_users( + ["uid"], + include_invalid_users=True, + ) + users = resp["users"] + self.assertEqual(len(users), 2) + self.assertEqual(users[0]["id"], "u1") + self.assertEqual(users[1]["id"], "u2") + mock_post.assert_called_with( + f"{common.DEFAULT_BASE_URL}{MgmtV1.users_load_path}", + headers={ + **common.default_headers, + "Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}", + "x-descope-project-id": self.dummy_project_id, + }, + params=None, + json={ + "userIds": ["uid"], + "includeInvalidUsers": True, + }, + allow_redirects=False, + verify=True, + timeout=DEFAULT_TIMEOUT_SECONDS, + ) + def test_search_all(self): # Test failed flows with patch("requests.post") as mock_post: From 2caf0b6976aecf5cf72abfcaf92aeab76d6e03d0 Mon Sep 17 00:00:00 2001 From: Aviad Lichtenstadt Date: Tue, 2 Dec 2025 21:47:15 +0200 Subject: [PATCH 2/8] Update descope/management/user.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- descope/management/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/descope/management/user.py b/descope/management/user.py index df8ec2f09..930002a86 100644 --- a/descope/management/user.py +++ b/descope/management/user.py @@ -659,7 +659,7 @@ def load_users( include_invalid_users: Optional[bool] = None, ) -> dict: """ - Search all users. + Load users by their user IDs. Args: user_ids (List[str]): Optional list of user IDs to filter by From ec51641f29be27ef7ac3f3eed5de316a58b57db9 Mon Sep 17 00:00:00 2001 From: Aviad Lichtenstadt Date: Tue, 2 Dec 2025 21:47:31 +0200 Subject: [PATCH 3/8] Update descope/management/user.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- descope/management/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/descope/management/user.py b/descope/management/user.py index 930002a86..f44ad8efc 100644 --- a/descope/management/user.py +++ b/descope/management/user.py @@ -675,7 +675,7 @@ def load_users( """ if user_ids is None or len(user_ids) == 0: raise AuthException( - 400, ERROR_TYPE_INVALID_ARGUMENT, "At list one user id needs to be supplied" + 400, ERROR_TYPE_INVALID_ARGUMENT, "At least one user id needs to be supplied" ) body: dict[str, Union[List[str], bool]] = { From c1ede44c91753ced6a502dd26a67141bc4c5c7ff Mon Sep 17 00:00:00 2001 From: Omer Cohen Date: Tue, 2 Dec 2025 21:53:44 +0200 Subject: [PATCH 4/8] Apply suggestion from @omercnet --- .vscode/settings.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 006ab76ba..62eb56eb1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,9 +7,5 @@ "mypy-type-checker.importStrategy": "fromEnvironment", "isort.importStrategy": "fromEnvironment", "black-formatter.importStrategy": "fromEnvironment", - "workbench.colorCustomizations": { - "activityBar.background": "#4D1C3B", - "titleBar.activeBackground": "#6B2752", - "titleBar.activeForeground": "#FDF8FB" - } + "workbench.colorCustomizations": {} } \ No newline at end of file From e8c44aff02659a8374dc5b17b53b0064faaa8cc6 Mon Sep 17 00:00:00 2001 From: aviadl Date: Tue, 2 Dec 2025 21:53:54 +0200 Subject: [PATCH 5/8] Revert settings.json --- .vscode/settings.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 006ab76ba..4e5b872c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,9 +7,5 @@ "mypy-type-checker.importStrategy": "fromEnvironment", "isort.importStrategy": "fromEnvironment", "black-formatter.importStrategy": "fromEnvironment", - "workbench.colorCustomizations": { - "activityBar.background": "#4D1C3B", - "titleBar.activeBackground": "#6B2752", - "titleBar.activeForeground": "#FDF8FB" - } + "workbench.colorCustomizations": { /* do not change please... */} } \ No newline at end of file From c9a4d9fb536125b0b354763b6291b24be1d210de Mon Sep 17 00:00:00 2001 From: Omer Cohen Date: Tue, 2 Dec 2025 21:54:04 +0200 Subject: [PATCH 6/8] Apply suggestion from @omercnet --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 62eb56eb1..4e5b872c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,5 @@ "mypy-type-checker.importStrategy": "fromEnvironment", "isort.importStrategy": "fromEnvironment", "black-formatter.importStrategy": "fromEnvironment", - "workbench.colorCustomizations": {} + "workbench.colorCustomizations": { /* do not change please... */} } \ No newline at end of file From 3b6d3af080a3ae2bd0018bd751413eb701977b4a Mon Sep 17 00:00:00 2001 From: aviadl Date: Tue, 2 Dec 2025 21:57:52 +0200 Subject: [PATCH 7/8] Linting --- descope/management/user.py | 4 +++- tests/test_descope_client.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/descope/management/user.py b/descope/management/user.py index f44ad8efc..31a403349 100644 --- a/descope/management/user.py +++ b/descope/management/user.py @@ -675,7 +675,9 @@ def load_users( """ if user_ids is None or len(user_ids) == 0: raise AuthException( - 400, ERROR_TYPE_INVALID_ARGUMENT, "At least one user id needs to be supplied" + 400, + ERROR_TYPE_INVALID_ARGUMENT, + "At least one user id needs to be supplied", ) body: dict[str, Union[List[str], bool]] = { diff --git a/tests/test_descope_client.py b/tests/test_descope_client.py index d63ab83d4..c91ac87f4 100644 --- a/tests/test_descope_client.py +++ b/tests/test_descope_client.py @@ -1076,7 +1076,7 @@ def test_base_url_none(self): public_key=self.public_key_dict, ) - expected_base_url = common.DEFAULT_BASE_URL + expected_base_url = common.DEFAULT_BASE_URL self.assertEqual(client._auth.http_client.base_url, expected_base_url) self.assertEqual(client._mgmt._http.base_url, expected_base_url) From 131e77191c7a6ffecdd102ed6424861a9975fed0 Mon Sep 17 00:00:00 2001 From: aviadl Date: Tue, 2 Dec 2025 22:05:19 +0200 Subject: [PATCH 8/8] Fix typing --- descope/management/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/descope/management/user.py b/descope/management/user.py index 31a403349..a49179a5b 100644 --- a/descope/management/user.py +++ b/descope/management/user.py @@ -655,7 +655,7 @@ def logout_user_by_user_id( def load_users( self, - user_ids: List[str] = None, + user_ids: List[str], include_invalid_users: Optional[bool] = None, ) -> dict: """