Skip to content

Commit af968f8

Browse files
authored
Merge branch 'main' into fix/verbose-put-last-response-capture
2 parents c2d5e9c + ef91f56 commit af968f8

10 files changed

Lines changed: 50 additions & 50 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repos:
1010
- id: end-of-file-fixer
1111
- id: trailing-whitespace
1212
- repo: https://github.com/astral-sh/uv-pre-commit
13-
rev: 0.11.32
13+
rev: 0.12.0
1414
hooks:
1515
- id: uv-lock
1616
- repo: https://github.com/astral-sh/ruff-pre-commit

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.11.2"
2+
".": "2.12.0"
33
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [2.12.0](https://github.com/descope/python-sdk/compare/descope-2.11.2...descope-2.12.0) (2026-08-18)
4+
5+
6+
### Features
7+
8+
* support status field on user create, document invite caveat ([#1665](https://github.com/descope/python-sdk/issues/1665)) ([bfcb192](https://github.com/descope/python-sdk/commit/bfcb19275070fadc4b5cf07d3471896cead563be))
9+
310
## [2.11.2](https://github.com/descope/python-sdk/compare/descope-2.11.1...descope-2.11.2) (2026-08-10)
411

512

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,11 @@ descope_client.mgmt.user.invite(
700700
invite_url="invite.me"
701701
)
702702

703+
# NOTE: `invite` only controls whether an invitation message is sent - it does NOT
704+
# change the user's status. A newly invited user starts out in the "invited" status,
705+
# not "enabled". If your use case requires the user to be active immediately, set
706+
# `status="enabled"` explicitly via `create`/`patch`, or call `activate()` afterwards.
707+
703708
# Batch invite
704709
descope_client.mgmt.user.invite_batch(
705710
users=[

descope/management/_user_base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
)
1010
from descope.management.user_pwd import UserPassword
1111

12+
VALID_USER_STATUSES = ["enabled", "disabled", "invited", "expired"]
13+
1214

1315
class UserObj:
1416
def __init__(
@@ -71,6 +73,16 @@ def __init__(
7173

7274

7375
class UserBase:
76+
@staticmethod
77+
def _validate_status(status: Optional[str], login_id: Optional[str] = None) -> None:
78+
if status is not None and status not in VALID_USER_STATUSES:
79+
suffix = f" for user {login_id}" if login_id is not None else ""
80+
raise AuthException(
81+
400,
82+
ERROR_TYPE_INVALID_ARGUMENT,
83+
f"Invalid status value: {status}{suffix}. Must be one of: {', '.join(VALID_USER_STATUSES)}",
84+
)
85+
7486
@staticmethod
7587
def _compose_create_body(
7688
login_id: str,
@@ -95,6 +107,7 @@ def _compose_create_body(
95107
sso_app_ids: Optional[List[str]] = None,
96108
template_id: str = "",
97109
locale: Optional[str] = None,
110+
status: Optional[str] = None,
98111
) -> dict:
99112
body = UserBase._compose_update_body(
100113
login_id=login_id,
@@ -127,6 +140,8 @@ def _compose_create_body(
127140
body["templateId"] = template_id
128141
if locale is not None:
129142
body["locale"] = locale
143+
if status is not None:
144+
body["status"] = status
130145
return body
131146

132147
@staticmethod

descope/management/user.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def create(
3232
invite_url: Optional[str] = None,
3333
additional_login_ids: Optional[List[str]] = None,
3434
sso_app_ids: Optional[List[str]] = None,
35+
status: Optional[str] = None,
3536
) -> dict:
3637
"""
3738
Create a new user. Users can have any number of optional fields, including email, phone number and authorization.
@@ -48,6 +49,7 @@ def create(
4849
picture (str): Optional url for user picture
4950
custom_attributes (dict): Optional, set the different custom attributes values of the keys that were previously configured in Descope console app
5051
sso_app_ids (List[str]): Optional, list of SSO applications IDs to be associated with the user.
52+
status (str): Optional status field. Can be one of: "enabled", "disabled", "invited", "expired".
5153
5254
Return value (dict):
5355
Return dict in the format
@@ -57,6 +59,7 @@ def create(
5759
Raise:
5860
AuthException: raised if create operation fails
5961
"""
62+
UserBase._validate_status(status)
6063
role_names = [] if role_names is None else role_names
6164
user_tenants = [] if user_tenants is None else user_tenants
6265

@@ -83,6 +86,7 @@ def create(
8386
None,
8487
additional_login_ids,
8588
sso_app_ids,
89+
status=status,
8690
),
8791
)
8892
return response.json()
@@ -389,17 +393,7 @@ def patch(
389393
Raise:
390394
AuthException: raised if patch operation fails
391395
"""
392-
if status is not None and status not in [
393-
"enabled",
394-
"disabled",
395-
"invited",
396-
"expired",
397-
]:
398-
raise AuthException(
399-
400,
400-
ERROR_TYPE_INVALID_ARGUMENT,
401-
f"Invalid status value: {status}. Must be one of: enabled, disabled, invited, expired",
402-
)
396+
UserBase._validate_status(status)
403397
response = self._http.patch(
404398
MgmtV1.user_patch_path,
405399
body=UserBase._compose_patch_body(
@@ -445,19 +439,8 @@ def patch_batch(
445439
Raise:
446440
AuthException: raised if patch batch operation fails
447441
"""
448-
# Validate status fields for all users
449442
for user in users:
450-
if user.status is not None and user.status not in [
451-
"enabled",
452-
"disabled",
453-
"invited",
454-
"expired",
455-
]:
456-
raise AuthException(
457-
400,
458-
ERROR_TYPE_INVALID_ARGUMENT,
459-
f"Invalid status value: {user.status} for user {user.login_id}. Must be one of: enabled, disabled, invited, expired",
460-
)
443+
UserBase._validate_status(user.status, user.login_id)
461444

462445
response = self._http.patch(
463446
MgmtV1.user_patch_batch_path,

descope/management/user_async.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ async def create(
3636
invite_url: Optional[str] = None,
3737
additional_login_ids: Optional[List[str]] = None,
3838
sso_app_ids: Optional[List[str]] = None,
39+
status: Optional[str] = None,
3940
) -> dict:
4041
"""
4142
Create a new user. Users can have any number of optional fields, including email, phone number and authorization.
@@ -52,6 +53,7 @@ async def create(
5253
picture (str): Optional url for user picture
5354
custom_attributes (dict): Optional, set the different custom attributes values of the keys that were previously configured in Descope console app
5455
sso_app_ids (List[str]): Optional, list of SSO applications IDs to be associated with the user.
56+
status (str): Optional status field. Can be one of: "enabled", "disabled", "invited", "expired".
5557
5658
Return value (dict):
5759
Return dict in the format
@@ -61,6 +63,7 @@ async def create(
6163
Raise:
6264
AuthException: raised if create operation fails
6365
"""
66+
UserBase._validate_status(status)
6467
role_names = [] if role_names is None else role_names
6568
user_tenants = [] if user_tenants is None else user_tenants
6669

@@ -87,6 +90,7 @@ async def create(
8790
None,
8891
additional_login_ids,
8992
sso_app_ids,
93+
status=status,
9094
),
9195
)
9296
return response.json()
@@ -393,17 +397,7 @@ async def patch(
393397
Raise:
394398
AuthException: raised if patch operation fails
395399
"""
396-
if status is not None and status not in [
397-
"enabled",
398-
"disabled",
399-
"invited",
400-
"expired",
401-
]:
402-
raise AuthException(
403-
400,
404-
ERROR_TYPE_INVALID_ARGUMENT,
405-
f"Invalid status value: {status}. Must be one of: enabled, disabled, invited, expired",
406-
)
400+
UserBase._validate_status(status)
407401
response = await self._http.patch(
408402
MgmtV1.user_patch_path,
409403
body=UserBase._compose_patch_body(
@@ -451,17 +445,7 @@ async def patch_batch(
451445
"""
452446
# Validate status fields for all users
453447
for user in users:
454-
if user.status is not None and user.status not in [
455-
"enabled",
456-
"disabled",
457-
"invited",
458-
"expired",
459-
]:
460-
raise AuthException(
461-
400,
462-
ERROR_TYPE_INVALID_ARGUMENT,
463-
f"Invalid status value: {user.status} for user {user.login_id}. Must be one of: enabled, disabled, invited, expired",
464-
)
448+
UserBase._validate_status(user.status, user.login_id)
465449

466450
response = await self._http.patch(
467451
MgmtV1.user_patch_batch_path,

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "descope"
3-
version = "2.11.2"
3+
version = "2.12.0"
44
description = "Descope Python SDK"
55
authors = [{ name = "Descope", email = "info@descope.com" }]
66
requires-python = ">=3.9,<4.0"
@@ -79,7 +79,7 @@ module-name = "descope"
7979
module-root = ""
8080

8181
[build-system]
82-
requires = ["uv_build>=0.11.7,<0.12.0"]
82+
requires = ["uv_build>=0.12.0,<0.13.0"]
8383
build-backend = "uv_build"
8484

8585
[tool.pytest.ini_options]

tests/management/test_user.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ async def test_create(self, client_factory):
2525
with pytest.raises(AuthException):
2626
await client.invoke(client.mgmt.user.create("valid-id"))
2727

28+
with pytest.raises(AuthException) as exc_info:
29+
await client.invoke(client.mgmt.user.create("valid-id", status="invalid_status"))
30+
assert "Invalid status value: invalid_status" in str(exc_info.value)
31+
2832
# Test success flow
2933
with client.mock_mgmt_post(make_response({"user": {"id": "u1"}})) as mock_post:
3034
resp = await client.invoke(
@@ -40,6 +44,7 @@ async def test_create(self, client_factory):
4044
custom_attributes={"ak": "av"},
4145
additional_login_ids=["id-1", "id-2"],
4246
sso_app_ids=["app1", "app2"],
47+
status="disabled",
4348
)
4449
)
4550
user = resp["user"]
@@ -70,6 +75,7 @@ async def test_create(self, client_factory):
7075
"invite": False,
7176
"additionalLoginIds": ["id-1", "id-2"],
7277
"ssoAppIDs": ["app1", "app2"],
78+
"status": "disabled",
7379
},
7480
follow_redirects=False,
7581
)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)