Skip to content

Commit 4488f53

Browse files
authored
Introducing auth.DELETE_ATTRIBUTE sentinel value (#285)
* Introducing auth.DELETE_ATTRIBUTE sentinel value for updating users * Fixing a lint error
1 parent 7c413f6 commit 4488f53

File tree

4 files changed

+46
-13
lines changed

4 files changed

+46
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Unreleased
22

3-
-
3+
- [added] Added a new `auth.DELETE_ATTRIBUTE` sentinel value, which can be
4+
used to delete `phone_number`, `display_name`, `photo_url` and `custom_claims`
5+
attributes from a user account. It is now recommended to use this sentinel
6+
value over passing `None` for deleting attributes.
47

58
# v2.16.0
69

firebase_admin/_user_mgt.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,18 @@
3636
MAX_LIST_USERS_RESULTS = 1000
3737
MAX_IMPORT_USERS_SIZE = 1000
3838

39-
class _Unspecified(object):
40-
pass
39+
40+
class Sentinel(object):
41+
42+
def __init__(self, description):
43+
self.description = description
44+
4145

4246
# Use this internally, until sentinels are available in the public API.
43-
_UNSPECIFIED = _Unspecified()
47+
_UNSPECIFIED = Sentinel('No value specified')
48+
49+
50+
DELETE_ATTRIBUTE = Sentinel('Value used to delete an attribute from a user profile')
4451

4552

4653
class ApiCallError(Exception):
@@ -546,26 +553,26 @@ def update_user(self, uid, display_name=_UNSPECIFIED, email=None, phone_number=_
546553

547554
remove = []
548555
if display_name is not _UNSPECIFIED:
549-
if display_name is None:
556+
if display_name is None or display_name is DELETE_ATTRIBUTE:
550557
remove.append('DISPLAY_NAME')
551558
else:
552559
payload['displayName'] = _auth_utils.validate_display_name(display_name)
553560
if photo_url is not _UNSPECIFIED:
554-
if photo_url is None:
561+
if photo_url is None or photo_url is DELETE_ATTRIBUTE:
555562
remove.append('PHOTO_URL')
556563
else:
557564
payload['photoUrl'] = _auth_utils.validate_photo_url(photo_url)
558565
if remove:
559566
payload['deleteAttribute'] = remove
560567

561568
if phone_number is not _UNSPECIFIED:
562-
if phone_number is None:
569+
if phone_number is None or phone_number is DELETE_ATTRIBUTE:
563570
payload['deleteProvider'] = ['phone']
564571
else:
565572
payload['phoneNumber'] = _auth_utils.validate_phone(phone_number)
566573

567574
if custom_claims is not _UNSPECIFIED:
568-
if custom_claims is None:
575+
if custom_claims is None or custom_claims is DELETE_ATTRIBUTE:
569576
custom_claims = {}
570577
json_claims = json.dumps(custom_claims) if isinstance(
571578
custom_claims, dict) else custom_claims

firebase_admin/auth.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
__all__ = [
3838
'ActionCodeSettings',
3939
'AuthError',
40+
'DELETE_ATTRIBUTE',
4041
'ErrorInfo',
4142
'ExportedUserRecord',
4243
'ImportUserRecord',
@@ -68,6 +69,7 @@
6869
]
6970

7071
ActionCodeSettings = _user_mgt.ActionCodeSettings
72+
DELETE_ATTRIBUTE = _user_mgt.DELETE_ATTRIBUTE
7173
ErrorInfo = _user_import.ErrorInfo
7274
ExportedUserRecord = _user_mgt.ExportedUserRecord
7375
ListUsersPage = _user_mgt.ListUsersPage
@@ -359,17 +361,18 @@ def update_user(uid, **kwargs):
359361
360362
Keyword Args:
361363
display_name: The user's display name (optional). Can be removed by explicitly passing
362-
None.
364+
``auth.DELETE_ATTRIBUTE``.
363365
email: The user's primary email (optional).
364366
email_verified: A boolean indicating whether or not the user's primary email is
365367
verified (optional).
366368
phone_number: The user's primary phone number (optional). Can be removed by explicitly
367-
passing None.
368-
photo_url: The user's photo URL (optional). Can be removed by explicitly passing None.
369+
passing ``auth.DELETE_ATTRIBUTE``.
370+
photo_url: The user's photo URL (optional). Can be removed by explicitly passing
371+
``auth.DELETE_ATTRIBUTE``.
369372
password: The user's raw, unhashed password. (optional).
370373
disabled: A boolean indicating whether or not the user account is disabled (optional).
371374
custom_claims: A dictionary or a JSON string contining the custom claims to be set on the
372-
user account (optional).
375+
user account (optional). To remove all custom claims, pass ``auth.DELETE_ATTRIBUTE``.
373376
valid_since: An integer signifying the seconds since the epoch. This field is set by
374377
``revoke_refresh_tokens`` and it is discouraged to set this field directly.
375378

tests/test_user_mgt.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,13 @@ def test_update_user_custom_claims(self, user_mgt_app):
381381
request = json.loads(recorder[0].body.decode())
382382
assert request == {'localId' : 'testuser', 'customAttributes' : json.dumps(claims)}
383383

384-
def test_update_user_delete_fields(self, user_mgt_app):
384+
def test_delete_user_custom_claims(self, user_mgt_app):
385+
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
386+
user_mgt.update_user('testuser', custom_claims=auth.DELETE_ATTRIBUTE)
387+
request = json.loads(recorder[0].body.decode())
388+
assert request == {'localId' : 'testuser', 'customAttributes' : json.dumps({})}
389+
390+
def test_update_user_delete_fields_with_none(self, user_mgt_app):
385391
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
386392
user_mgt.update_user('testuser', display_name=None, photo_url=None, phone_number=None)
387393
request = json.loads(recorder[0].body.decode())
@@ -391,6 +397,20 @@ def test_update_user_delete_fields(self, user_mgt_app):
391397
'deleteProvider' : ['phone'],
392398
}
393399

400+
def test_update_user_delete_fields(self, user_mgt_app):
401+
user_mgt, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}')
402+
user_mgt.update_user(
403+
'testuser',
404+
display_name=auth.DELETE_ATTRIBUTE,
405+
photo_url=auth.DELETE_ATTRIBUTE,
406+
phone_number=auth.DELETE_ATTRIBUTE)
407+
request = json.loads(recorder[0].body.decode())
408+
assert request == {
409+
'localId' : 'testuser',
410+
'deleteAttribute' : ['DISPLAY_NAME', 'PHOTO_URL'],
411+
'deleteProvider' : ['phone'],
412+
}
413+
394414
def test_update_user_error(self, user_mgt_app):
395415
_instrument_user_manager(user_mgt_app, 500, '{"error":"test"}')
396416
with pytest.raises(auth.AuthError) as excinfo:

0 commit comments

Comments
 (0)