Skip to main content

Permissions made easy

Project description

Easy permissions

Define user permissions and resource access simply and efficiently.

Installation

pip install ezperm

Usage & examples

General case

Assume we are in charge of a superhero team. We have a list of heroes, and we want to define permissions for them. Our hero model might look like this:

import dataclasses


@dataclasses.dataclass
class Hero:
    name: str
    age: int
    permissions: list['Permissions']

After a while of working with our heroes, we realize that some of them are terrible cooks. We want to define a permission that will allow only some of them to cook or bake.

This can be done by extending the PermissionEnum class and defining a _has_perm method that will check if the hero has the permission.

Permission enums

import ezperm

class Permissions(ezperm.PermissionEnum):
    CAN_COOK = 'CAN_COOK'
    CAN_BAKE = 'CAN_BAKE'
    
    def _has_perm(self, hero: Hero) -> bool:
        return self.value in hero.permissions

Now lets use our permissions in the code:

# Define some heroes
ironman = Hero('Ironman', 45, [Permissions.CAN_COOK, Permissions.CAN_BAKE])
deadpool = Hero('Deadpool', 30, [Permissions.CAN_BAKE])
hulk = Hero('Hulk', 55, [])

# Check if the hero has a permission
Permissions.CAN_COOK(ironman)  # ➞ True
Permissions.CAN_COOK(deadpool)  # ➞ False
Permissions.CAN_COOK(hulk)  # ➞ False

It is possible to use & (logical and), | (logical or) or ~ (negation) operators to combine permissions:

(Permissions.CAN_COOK & Permissions.CAN_BAKE)(ironman)  # ➞ True
(Permissions.CAN_COOK & Permissions.CAN_BAKE)(deadpool)  # ➞ False
(Permissions.CAN_COOK | Permissions.CAN_BAKE)(hulk)  # ➞ False
~(Permissions.CAN_COOK | Permissions.CAN_BAKE)(hulk)  # ➞ True
(~Permissions.CAN_COOK & Permissions.CAN_BAKE)(deadpool)  # ➞ True

Dynamic permissions

Using the @permission decorator, we can also define dynamic permissions that will check if the hero has a permission based on some other condition, or define a permission as a combination of other permissions.

class Permissions(ezperm.PermissionEnum):
    CAN_COOK = 'CAN_COOK'
    CAN_BAKE = 'CAN_BAKE'
    
    def _has_perm(self, hero: Hero) -> bool:
        return self.value in hero.permissions
    
    ### ↓ NEW ↓ ###
    @ezperm.permission
    def is_worthy(hero: Hero) -> bool:
        return hero.name == 'Thor'
    
    @ezperm.permission
    def is_old(hero: Hero) -> bool:
        return hero.age > 50

    @ezperm.permission
    def can_use_oven(hero: Hero) -> bool:
        return (Permissions.CAN_COOK | Permissions.CAN_BAKE)(hero)

These permissions can be used in the same way as the static ones:

Permissions.is_worthy(ironman)  # ➞ False
(Permissions.CAN_COOK | PERMISSIONS.is_worthy)(ironman)  # ➞ True

Django integration

ezperm comes with a couple of tools to help with Django integration. Its use is entirely optional, and you can use ezperm and Django without it.

First, lets update our Permissions and Hero classes in our example:

import ezperm.django

from django.db import models
from django.contrib.postgres.fields import ArrayField


class Permissions(ezperm.PermissionEnum, models.TextChoices):
    ... # same as before


class Hero(ezperm.django.PermissionsMixin, models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()
    permissions = ArrayField(
        base_field=models.CharField(
            max_length=255,
            choices=Permissions.choices,
        ),
        default=list,
    )

Note, that we've inherited the Permissions from Django's TextChoices in order to use it as a choices argument for the permissions field. Moreover, we've added the PermissionsMixin to our Hero model, which overrides the has_perms and has_perm method.

Now lets define a view which will allow access only to worthy or old heroes:

from django.views.generic import View
from ezperm.django.views import PermissionRequiredMixin


class CookView(PermissionRequiredMixin, View):
    permission_required = Permissions.is_worthy | Permissions.is_old
    
    def get(self, request):
        return HttpResponse('You can cook!')

Contributing

Pull requests for any improvements are welcome.

Poetry is used to manage dependencies. To get started follow these steps:

git clone https://github.com/VojtechPetru/ezperm.git
cd ezperm
poetry install
poetry run pytest

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ezperm-0.1.2.tar.gz (5.1 kB view details)

Uploaded Source

Built Distribution

ezperm-0.1.2-py3-none-any.whl (6.9 kB view details)

Uploaded Python 3

File details

Details for the file ezperm-0.1.2.tar.gz.

File metadata

  • Download URL: ezperm-0.1.2.tar.gz
  • Upload date:
  • Size: 5.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.11.9 Linux/6.5.0-1025-azure

File hashes

Hashes for ezperm-0.1.2.tar.gz
Algorithm Hash digest
SHA256 e69524b91f11c32a949110b2c9b1abc01ff1f265e91e5aa625c0a62b1b5947bc
MD5 9eea6d6d5c521cc85ac9561e86c42802
BLAKE2b-256 ee489b04e5a51fd672cf27f455db1076550bbb3365a78d74bfa0c7b19cb1edb4

See more details on using hashes here.

File details

Details for the file ezperm-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: ezperm-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 6.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.11.9 Linux/6.5.0-1025-azure

File hashes

Hashes for ezperm-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 26581466195891d830519b9c6a719c3d295c53618a3eef68d7277a0a6e50edab
MD5 dd09aa07eeaa81449a79afe582e67d2b
BLAKE2b-256 57a4ea7a58a5e5c3e33460ba5eea48225fefa2324eae70dc3da23234d5e7bd5c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page