[PATCH 1 of 2] model: check for invalid users centrally

Mads Kiilerich mads at kiilerich.com
Tue Jun 23 09:59:42 EDT 2015


On 06/22/2015 10:09 PM, Thomas De Schampheleire wrote:
> On Mon, Jun 22, 2015 at 7:05 PM, Mads Kiilerich <mads at kiilerich.com> wrote:
>> On 06/22/2015 06:45 PM, Thomas De Schampheleire wrote:
>>> # HG changeset patch
>>> # User Thomas De Schampheleire <thomas.de.schampheleire at gmail.com>
>>> # Date 1434984724 -7200
>>> #      Mon Jun 22 16:52:04 2015 +0200
>>> # Node ID 68032f8bd7f52c676bb5143a03b0dfd5df242f0c
>>> # Parent  7303d0035d82a20f93df1c78b3358403b7ff9827
>>> model: check for invalid users centrally
>>>
>>> Commit 9a23b444a7fefe5b67e57a42d26343787d992874 introduced
>>> UserInvalidException. The raising of this exception, however, was done
>>> only
>>> in pullrequest context, while it could have been done in
>>> BaseModel._get_user().
>>
>> Why not be more consistent and do it in _get_instance (using a more generic
>> EntityNotFound exception (or something like that)? Is that the end goal and
>> this just an intermediate step?
> Would you translate the EntityNotFound in some other more specific
> exception class, for example from _get_user, or would EntityNotFound
> be the final exception?

I think that would be fine.

In some cases, there will be multiple users involved anyway and it will 
not be possible to distinguish _which_ of them failed. The description 
should of course explain what it was that failed.

> This user part is an evolution from my initial introduction of
> UserInvalidException, but as I hinted below I think it makes sense to
> expand it. Doing it directly in get_instance is indeed more logical, I
> can do that at the same time.

Ok - so you will rework this patch to do that?

>>> Note: the same 'use-exceptions-rather-than-explicit-checks-for-None'
>>> approach could actually be introduced for the other _get_X methods in
>>> model, like _get_repo.
>>
>> Why do you think exceptions are better than None? Is it often one extra line
>> of code and usually slower (not that matters in most of these cases). (I
>> might agree but it would be nice to have reasoning/assumption stated
>> explicitly.)
> One important reason why exceptions seem better to me in getObject(x),
> is that you're sure that the possible nonexistence of the object will
> not go unnoticed.

I agree that makes sense. That reasoning will be nice to have in the 
commit message ... and perhaps also elsewhere, to establish a general 
style in the code base.

> When getObject(x) returns None but the caller does
> not check the return value but simply start using it, at best the
> caller itself will cause another exception because it accesses an
> element of None. But worse, the caller may just need to pass the
> object to another function, which also passes it through, and so on,
> until at some point a real exception is thrown and the immediate cause
> is no longer obvious.
> With an exception, either the caller envisions a possible nonexistence
> of the object and catches the exception, or the caller thinks/assumes
> that the object does really exist -- the opposite will immediately
> cause an exception and stop further code execution for that request.
>
> The code below in lib/base.py, which is always executed even if there
> is no authenticated user, is one example where Kallithea is apparently
> juggling with invalid data. The exception code I added made the issue
> clear in the tests.
>
> Other reasons are that it can make the core logic more apparent
> (moving out the exceptions a bit further down, although there are
> advocates in making the try-block as short as possible and using the
> else clause for the rest of the non-throwing code) and that it is more
> pythonic ("Easier to Ask for Forgiveness than Permission." versus
> "Look Before You Leap")
>
>>
>>> diff --git a/kallithea/controllers/api/api.py
>>> b/kallithea/controllers/api/api.py
>>> --- a/kallithea/controllers/api/api.py
>>> +++ b/kallithea/controllers/api/api.py
>>> @@ -37,6 +37,7 @@ from kallithea.lib.auth import (
>>>        PasswordGenerator, AuthUser, HasPermissionAllDecorator,
>>>        HasPermissionAnyDecorator, HasPermissionAnyApi,
>>> HasRepoPermissionAnyApi,
>>>        HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAny)
>>> +from kallithea.lib.exceptions import UserInvalidException
>>>    from kallithea.lib.utils import map_groups, repo2db_mapper
>>>    from kallithea.lib.utils2 import (
>>>        str2bool, time_to_datetime, safe_int, Optional, OAttr)
>>> @@ -72,8 +73,9 @@ def get_user_or_error(userid):
>>>          :param userid:
>>>        """
>>> -    user = UserModel().get_user(userid)
>>> -    if user is None:
>>> +    try:
>>> +        user = UserModel().get_user(userid)
>>> +    except UserInvalidException, u:
>>>            raise JSONRPCError("user `%s` does not exist" % (userid,))
>>>        return user
>>>    diff --git a/kallithea/lib/base.py b/kallithea/lib/base.py
>>> --- a/kallithea/lib/base.py
>>> +++ b/kallithea/lib/base.py
>>> @@ -50,7 +50,7 @@ from kallithea.lib.utils2 import str2boo
>>>    from kallithea.lib import auth_modules
>>>    from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware,
>>> CookieStoreWrapper
>>>    from kallithea.lib.utils import get_repo_slug
>>> -from kallithea.lib.exceptions import UserCreationError
>>> +from kallithea.lib.exceptions import UserCreationError,
>>> UserInvalidException
>>>    from kallithea.lib.vcs.exceptions import RepositoryError,
>>> EmptyRepositoryError, ChangesetDoesNotExistError
>>>    from kallithea.model import meta
>>>    @@ -331,12 +331,16 @@ class BaseController(WSGIController):
>>>              c.repo_name = get_repo_slug(request)  # can be empty
>>>            c.backends = BACKENDS.keys()
>>> -        c.unread_notifications = NotificationModel()\
>>> -                        .get_unread_cnt_for_user(c.authuser.user_id)
>>>              self.cut_off_limit = safe_int(config.get('cut_off_limit'))
>>>    -        c.my_pr_count =
>>> PullRequestModel().get_pullrequest_cnt_for_user(c.authuser.user_id)
>>> +        try:
>>> +            c.unread_notifications = NotificationModel()\
>>> +                            .get_unread_cnt_for_user(c.authuser.user_id)
>>> +            c.my_pr_count =
>>> PullRequestModel().get_pullrequest_cnt_for_user(c.authuser.user_id)
>>> +        except UserInvalidException, u:
>>> +            c.unread_notifications = 0
>>> +            c.my_pr_count = 0
>>
>> It seems like NotificationModel already quietly ignored invalid user IDs.
>> That seems weird and wrong ... but perhaps a consequence of the "feature" of
>> being able to delete users? It seems like the weirdness propagates a bit
>> here ...
> You mean that this should be handled in get_unread_cnt_for_user and
> get_pullrequest_cnt_for_user instead?
> That makes sense.
> If you mean something else, then please clarify. I don't think the
> above changes propagates the weirdness _more_ than before, rather it
> is the same amount of propagation.

Ok, agreed.

This new location for the weirdness might however deserve a comment, 
admitting it is weird (if it is or why not), why it is there, and 
perhaps what should be done to make it less weird.

/Mads


More information about the kallithea-general mailing list