Skip to content
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

Add type annotations and mypy config #754

Draft
wants to merge 3 commits into
base: devel
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class RunFlakesCommand(Command):
Runs pyflakes against guardian codebase.
"""
description = "Check sources with pyflakes"
user_options = []
user_options = [] # type: ignore

def initialize_options(self):
pass
Expand Down
2 changes: 1 addition & 1 deletion guardian/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class GuardianConfig(AppConfig):
name = 'guardian'
default_auto_field = 'django.db.models.AutoField'

def ready(self):
def ready(self) -> None:
# Must patch Group here since generic
# group permission model is definable
monkey_patch_group()
Expand Down
2 changes: 1 addition & 1 deletion guardian/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class ObjectPermissionBackend:
def authenticate(self, request, username=None, password=None):
return None

def has_perm(self, user_obj, perm, obj=None):
def has_perm(self, user_obj, perm, obj=None) -> bool:
"""
Returns ``True`` if given ``user_obj`` has ``perm`` for ``obj``. If no
``obj`` is given, ``False`` is returned.
Expand Down
7 changes: 4 additions & 3 deletions guardian/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from itertools import chain
from typing import List

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
Expand Down Expand Up @@ -60,7 +61,7 @@ def __init__(self, user_or_group=None):
self.user, self.group = get_identity(user_or_group)
self._obj_perms_cache = {}

def has_perm(self, perm, obj):
def has_perm(self, perm, obj) -> bool:
"""
Checks if user/group has given permission for object.

Expand Down Expand Up @@ -127,7 +128,7 @@ def get_user_perms(self, obj):

return user_perms

def get_group_perms(self, obj):
def get_group_perms(self, obj) -> List[str]:
ctype = get_content_type(obj)

perms_qs = Permission.objects.filter(content_type=ctype)
Expand All @@ -137,7 +138,7 @@ def get_group_perms(self, obj):

return group_perms

def get_perms(self, obj):
def get_perms(self, obj) -> List[str]:
"""
Returns list of ``codename``'s of all permissions for given ``obj``.

Expand Down
8 changes: 4 additions & 4 deletions guardian/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def get_obj_perms_field_choices(self):
choices = [(p.codename, p.name) for p in get_perms_for_model(self.obj)]
return choices

def get_obj_perms_field_initial(self):
def get_obj_perms_field_initial(self) -> list:
"""
Returns initial object permissions management field choices. Default:
``[]`` (empty list).
Expand All @@ -80,7 +80,7 @@ def get_obj_perms_field_widget(self):
"""
return forms.SelectMultiple

def are_obj_perms_required(self):
def are_obj_perms_required(self) -> bool:
"""
Indicates if at least one object permission should be required. Default:
``False``.
Expand Down Expand Up @@ -124,7 +124,7 @@ def get_obj_perms_field_initial(self):
perms = get_user_perms(self.user, self.obj)
return perms

def save_obj_perms(self):
def save_obj_perms(self) -> None:
"""
Saves selected object permissions by creating new ones and removing
those which were not selected but already exists.
Expand Down Expand Up @@ -172,7 +172,7 @@ def get_obj_perms_field_initial(self):
perms = get_group_perms(self.group, self.obj)
return perms

def save_obj_perms(self):
def save_obj_perms(self) -> None:
"""
Saves selected object permissions by creating new ones and removing
those which were not selected but already exists.
Expand Down
Empty file added guardian/forms.py:90
Empty file.
2 changes: 1 addition & 1 deletion guardian/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class SecureView(PermissionListMixin, ListView):

"""
permission_required = None
get_objects_for_user_extra_kwargs = {}
get_objects_for_user_extra_kwargs = {} # type: ignore

def get_required_permissions(self, request=None):
"""
Expand Down
46 changes: 39 additions & 7 deletions guardian/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
from collections import defaultdict
from functools import partial
from itertools import groupby
from typing import Union, List, Optional

from django.apps import apps
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.models import Group, Permission, AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.db import connection
from django.db.models import Count, Q, QuerySet
from django.db.models.manager import Manager
from django.shortcuts import _get_queryset
from django.db.models.expressions import Value
from django.db.models.functions import Cast, Replace
Expand All @@ -21,6 +23,7 @@
CharField,
ForeignKey,
IntegerField,
Model,
PositiveIntegerField,
PositiveSmallIntegerField,
SmallIntegerField,
Expand All @@ -33,8 +36,22 @@
GroupObjectPermission = get_group_obj_perms_model()
UserObjectPermission = get_user_obj_perms_model()


def assign_perm(perm, user_or_group, obj=None):
UserOrGroupType = Union[
User,
AnonymousUser,
Group,
List[User],
List[Group],
QuerySet
]
PermsType = Union[str, List[str]]


def assign_perm(
perm: Union[str, Permission],
user_or_group: UserOrGroupType,
obj: Union[None, Model, QuerySet, List[Model]] = None
):
"""
Assigns permission to user/group and object pair.

Expand Down Expand Up @@ -143,7 +160,7 @@ def assign(perm, user_or_group, obj=None):
return assign_perm(perm, user_or_group, obj)


def remove_perm(perm, user_or_group=None, obj=None):
def remove_perm(perm: Union[str, Permission], user_or_group=None, obj=None):
"""
Removes permission from user/group and object pair.

Expand Down Expand Up @@ -392,8 +409,15 @@ def get_groups_with_perms(obj, attach_perms=False):
return dict(group_perms_mapping)


def get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=False,
with_superuser=True, accept_global_perms=True):
def get_objects_for_user(
user: Union[User, AnonymousUser],
perms: PermsType,
klass=None,
use_groups: bool = True,
any_perm: bool = False,
with_superuser: bool = True,
accept_global_perms: bool = True
):
"""
Returns queryset of objects for which a given ``user`` has *all*
permissions present at ``perms``.
Expand Down Expand Up @@ -530,6 +554,7 @@ def get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=Fals
raise WrongAppError("Cannot determine content type")
else:
queryset = _get_queryset(klass)
assert ctype is not None # TODO: is this guaranteed at this point?
if ctype.model_class() != queryset.model:
raise MixedContentTypeError("Content type for given perms and "
"klass differs")
Expand Down Expand Up @@ -652,7 +677,13 @@ def get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=Fals
return queryset.filter(q)


def get_objects_for_group(group, perms, klass=None, any_perm=False, accept_global_perms=True):
def get_objects_for_group(
group: Group,
perms: PermsType,
klass: Union[None, Model, Manager, QuerySet] = None,
any_perm: bool = False,
accept_global_perms: bool = True
):
"""
Returns queryset of objects for which a given ``group`` has *all*
permissions present at ``perms``.
Expand Down Expand Up @@ -746,6 +777,7 @@ def get_objects_for_group(group, perms, klass=None, any_perm=False, accept_globa
raise WrongAppError("Cannot determine content type")
else:
queryset = _get_queryset(klass)
assert ctype is not None # TODO: is this guaranteed at this point?
if ctype.model_class() != queryset.model:
raise MixedContentTypeError("Content type for given perms and "
"klass differs")
Expand Down
11 changes: 6 additions & 5 deletions guardian/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import logging
import os
from itertools import chain
from typing import Union

from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
from django.contrib.auth.models import AnonymousUser, Group
from django.contrib.auth.models import AnonymousUser, Group, User
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Model, QuerySet
from django.http import HttpResponseForbidden, HttpResponseNotFound
Expand All @@ -34,7 +35,7 @@ def get_anonymous_user():
return User.objects.get(**lookup)


def get_identity(identity):
def get_identity(identity: Union[User, Group]):
"""
Returns (user_obj, None) or (None, group_obj) tuple depending on what is
given. Also accepts AnonymousUser instance but would return ``User``
Expand Down Expand Up @@ -141,7 +142,7 @@ def get_40x_or_None(request, perms, obj=None, login_url=None,
from django.apps import apps as django_apps
from django.core.exceptions import ImproperlyConfigured

def get_obj_perm_model_by_conf(setting_name):
def get_obj_perm_model_by_conf(setting_name: str):
"""
Return the model that matches the guardian settings.
"""
Expand All @@ -156,7 +157,7 @@ def get_obj_perm_model_by_conf(setting_name):
) from e


def clean_orphan_obj_perms():
def clean_orphan_obj_perms() -> int:
"""
Seeks and removes all object permissions entries pointing at non-existing
targets.
Expand Down Expand Up @@ -235,7 +236,7 @@ def get_group_obj_perms_model(obj = None):
return get_obj_perms_model(obj, GroupObjectPermissionBase, GroupObjectPermission)


def evict_obj_perms_cache(obj):
def evict_obj_perms_cache(obj) -> bool:
if hasattr(obj, '_guardian_perms_cache'):
delattr(obj, '_guardian_perms_cache')
return True
Expand Down
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ replace = version = '{new_version}'
[bumpversion:file:guardian/__init__.py]
search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'

[mypy]
show_error_codes = true
ignore_missing_imports = true
exclude=example/*|guardian/testapp/*|benchmarks/*