-
Notifications
You must be signed in to change notification settings - Fork 304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(auth): Add bulk get/delete methods #400
Conversation
6913eca
to
0e282f7
Compare
firebase_admin/_user_mgt.py
Outdated
last_refresh_at_millis = None | ||
last_refresh_at_iso8601 = self._data.get('lastRefreshAt', None) | ||
if last_refresh_at_iso8601 is not None: | ||
last_refresh_at_millis = iso8601.parse_date(last_refresh_at_iso8601).timestamp() * 1000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
datetime.timestamp()
is python 3.3. But I think that's ok now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good. I pointed out a few areas that can be improved.
Replace with a custom (ugh) rfc3339 module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. I think we just need to expose one of the new types in the public API.
firebase_admin/_rfc3339.py
Outdated
datestr_modified = re.sub(r'(\d\d):(\d\d)$', r'\1\2', datestr_modified) | ||
|
||
try: | ||
print("trying with micros") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop the print statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops.
Adding @egilmorez to review the documentation bits in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one suggestion about the public API.
I'm also ok with updating the docstring of ErrorInfo
to read some like "Error encountered while performing a batch operation such as importing users or deleting multiple user accounts" and then use it instead of the new BatchDeleteErrorInfo
.
firebase_admin/_user_mgt.py
Outdated
|
||
def __init__(self, errors=None): | ||
"""Constructs a BatchDeleteAccountsResponse instance, corresponseing to | ||
the json representing the BatchDeleteAccountsResponse proto. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest "JSON"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
firebase_admin/_user_mgt.py
Outdated
"""Represents the results of a delete_users() call.""" | ||
|
||
def __init__(self, errors=None): | ||
"""Constructs a BatchDeleteAccountsResponse instance, corresponseing to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest backticks for literal, here and just below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (also fixed spelling typo)
firebase_admin/_user_mgt.py
Outdated
|
||
Args: | ||
errors: List of dictionaries, with each dictionary representing a | ||
ErrorInfo instance as returned by the server. None implies an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest backticks for literal, and "an ErrorInfo
instance..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done/done.
firebase_admin/_user_mgt.py
Outdated
|
||
Returns: | ||
list[dict[string, string]]: List of dicts representing the json | ||
UserInfo responses from the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest backticks for literal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. (Also json->JSON)
firebase_admin/auth.py
Outdated
result list is not guaranteed to correspond to the nth entry in the input | ||
parameters list. | ||
|
||
Only a maximum of 100 identifiers may be supplied. If more than 100 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest "A maximum . . . "
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
firebase_admin/auth.py
Outdated
parameters list. | ||
|
||
Only a maximum of 100 identifiers may be supplied. If more than 100 | ||
identifiers are supplied, this method will immediately raise a ValueError. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest "method raises a ValueError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
firebase_admin/auth.py
Outdated
DeleteUserResult.success_count value. | ||
|
||
Only a maximum of 1000 identifiers may be supplied. If more than 1000 | ||
identifiers are supplied, this method will immediately raise a ValueError. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest "method raises a ValueError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
firebase_admin/auth.py
Outdated
"""Deletes the users specified by the given identifiers. | ||
|
||
Deleting a non-existing user won't generate an error. (i.e. this method is | ||
idempotent.) Non-existing users will be considered to be successfully |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest "are considered"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor style suggestions for parsed comments. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left small comment, otherwise LGTM. (Reviewing on behalf of egilmore@)
firebase_admin/_user_mgt.py
Outdated
|
||
@property | ||
def errors(self): | ||
"""A list of ``auth.ErrorInfo`` instances describing the errors that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checking, is this supposed to be in double back ticks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea! It seems both double and single backticks are used throughout. Single backticks are more common though, so I switched to that.
firebase_admin/auth.py
Outdated
Deleting a non-existing user won't generate an error. (i.e. this method is | ||
idempotent.) Non-existing users are considered to be successfully | ||
deleted, and will therefore be counted in the | ||
`DeleteUserResult.success_count` value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest:
"Deleting a non-existing user does not generate an error (the method is idempotent). Non-existing users are considered to be successfully deleted and are therefore included in the DeleteUserResult.success_count
value."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks quite good. I just made a few suggestions. See if those make sense for this PR.
firebase_admin/_user_identifier.py
Outdated
Args: | ||
uid: A user ID string. | ||
""" | ||
self.uid = uid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to validate these arguments? (Not empty, not None, string type etc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can't hurt, though implies we'll be doing some of the validation twice. (I don't think that's a problem.) Done. (Also, no longer doing validation twice; see other comment).
Note that we don't validate arguments to (eg) auth.get_user_by_email()
, instead, just deferring to the implementation function (user_mgt.get_user). This is a slightly different scenario, since for UserIdentifier, the user can create that ahead of time and possibly use it in some other context.
Another improvement (that I don't want to make in this PR) is to use type annotations. All of our supported python versions now support type annotations and they'd give you some of this for free (and at "compile" time too.)
firebase_admin/_user_mgt.py
Outdated
payload = {} | ||
for identifier in identifiers: | ||
if isinstance(identifier, _user_identifier.UidIdentifier): | ||
_auth_utils.validate_uid(identifier.uid, required=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we push this validation and the subsequent marshaling to the corresponding classes?
Each identifier class can expose something like:
def marshal(self, payload):
payload['localId'] = payload.get('localId', []).append(self.uid)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this (just now) but didn't really like the results. It makes this function shorter, but it alters the UserIdentifier
types such that they need to know how the json payload is constructed. (For instance, should it be ProviderIdentifier
s job to know that provider_uid
maps to rawId
on the wire?) It also spreads building the payload across 2 files and 5 classes, instead of having it in just one spot.
One possible improvement we could make here is to eliminate the validation (or at least, refactor it somewhat). As per one of your other comments, I've already moved some validation to the ctors, but that won't stop users from changing the values after construction, but before calling get_users(), so we should keep the validation here. But if we altered (eg) UidIdentifier.uid to be UidIdentifier._uid and added a getter (essentially making these objects immutable) I think that could allow us to skip validation here entirely, which would allow this block to focus solely on creating the payload. I've done that. PTAL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how validation turned out (and the immutability of the properties). I don't feel strongly about the marshalling part. When I first suggested it, I felt that having each class being in charge of marshalling was a good thing. If we add more implementations of the UserIdentifier interface in the future, that's when this will matter more. I'm ok with leaving this as is for now.
firebase_admin/_user_identifier.py
Outdated
@@ -30,7 +32,12 @@ def __init__(self, uid): | |||
Args: | |||
uid: A user ID string. | |||
""" | |||
self.uid = uid | |||
_auth_utils.validate_uid(uid, required=True) | |||
self._uid = uid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can collapse these into a single line:
self._uid = _auth_utils.validate_uid(...)
Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. LGTM with a suggestion.
Also fix multi-line Returns: doc statements. (See sphinx-contrib/napoleon#4)
This PR allows callers to retrieve a list of users by unique identifier (uid, email, phone, federated provider uid) as well as to delete a list of users.
RELEASE NOTE: Added
get_users()
anddelete_users()
APIs for retrieving and deleting user accounts in bulk.