# Easy permissions
Define user permissions and resource access simply and efficiently.
## Installation
```bash
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:
```python
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
```python
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:
```python
# 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:
```python
(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.
```python
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:
```python
Permissions.is_worthy(ironman) # ➞ False
(Permissions.CAN_COOK | PERMISSIONS.is_worthy)(ironman) # ➞ True
```
### Django integration
#### Installation
```bash
pip install ezperm[django]
```
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:
```python
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:
```python
from django.views.generic import View
from ezperm.django.views import PermissionRequiredMixin
class CookView(PermissionRequiredMixin, View):
permission_required = Permissions.CAN_COOK | Permissions.is_worthy
def get(self, request):
return HttpResponse('You can cook!')
```
## Contributing
Pull requests for any improvements are welcome.
[Poetry](https://github.com/sdispater/poetry) is used to manage dependencies.
To get started follow these steps:
```shell
git clone https://github.com/VojtechPetru/ezperm.git
cd ezperm
poetry install
poetry run pytest
```
## Links
- Repository: https://github.com/VojtechPetru/ezperm
- Issue tracker: https://github.com/VojtechPetru/ezperm/issues.
In case of sensitive bugs (e.g. security vulnerabilities) please contact me at _petru.vojtech@gmail.com_ directly.
Raw data
{
"_id": null,
"home_page": "https://github.com/VojtechPetru/ezperm",
"name": "ezperm",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8,<4.0",
"maintainer_email": "",
"keywords": "permissions,django",
"author": "vojtech",
"author_email": "petru.vojtech@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/ab/39/cd5d91f21db2c184864ffde734e0287f6a2dc013ba6aae5c322b3f9a55d1/ezperm-0.1.1.tar.gz",
"platform": null,
"description": "# Easy permissions\nDefine user permissions and resource access simply and efficiently.\n\n## Installation\n```bash\npip install ezperm\n```\n\n## Usage & examples\n### General case\nAssume we are in charge of a superhero team. We have a list of heroes, and we want to define permissions for them.\nOur hero model might look like this:\n```python\nimport dataclasses\n\n\n@dataclasses.dataclass\nclass Hero:\n name: str\n age: int\n permissions: list['Permissions']\n```\nAfter a while of working with our heroes, we realize that some of them are terrible cooks. We want to define a permission\nthat will allow only some of them to cook or bake. \n\nThis can be done by extending the `PermissionEnum` class and defining a `_has_perm`\nmethod that will check if the hero has the permission.\n#### Permission enums\n```python\nimport ezperm\n\nclass Permissions(ezperm.PermissionEnum):\n CAN_COOK = 'CAN_COOK'\n CAN_BAKE = 'CAN_BAKE'\n \n def _has_perm(self, hero: Hero) -> bool:\n return self.value in hero.permissions\n```\nNow lets use our permissions in the code:\n```python\n# Define some heroes\nironman = Hero('Ironman', 45, [Permissions.CAN_COOK, Permissions.CAN_BAKE])\ndeadpool = Hero('Deadpool', 30, [Permissions.CAN_BAKE])\nhulk = Hero('Hulk', 55, [])\n\n# Check if the hero has a permission\nPermissions.CAN_COOK(ironman) # \u279e True\nPermissions.CAN_COOK(deadpool) # \u279e False\nPermissions.CAN_COOK(hulk) # \u279e False\n```\nIt is possible to use `&` _(logical and)_, `|` _(logical or)_ or `~` _(negation)_ operators to combine permissions:\n```python\n(Permissions.CAN_COOK & Permissions.CAN_BAKE)(ironman) # \u279e True\n(Permissions.CAN_COOK & Permissions.CAN_BAKE)(deadpool) # \u279e False\n(Permissions.CAN_COOK | Permissions.CAN_BAKE)(hulk) # \u279e False\n~(Permissions.CAN_COOK | Permissions.CAN_BAKE)(hulk) # \u279e True\n(~Permissions.CAN_COOK & Permissions.CAN_BAKE)(deadpool) # \u279e True\n```\n\n#### Dynamic permissions\nUsing 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.\n```python\nclass Permissions(ezperm.PermissionEnum):\n CAN_COOK = 'CAN_COOK'\n CAN_BAKE = 'CAN_BAKE'\n \n def _has_perm(self, hero: Hero) -> bool:\n return self.value in hero.permissions\n \n ### \u2193 NEW \u2193 ###\n @ezperm.permission\n def is_worthy(hero: Hero) -> bool:\n return hero.name == 'Thor'\n \n @ezperm.permission\n def is_old(hero: Hero) -> bool:\n return hero.age > 50\n\n @ezperm.permission\n def can_use_oven(hero: Hero) -> bool:\n return (Permissions.CAN_COOK | Permissions.CAN_BAKE)(hero)\n```\nThese permissions can be used in the same way as the static ones:\n```python\nPermissions.is_worthy(ironman) # \u279e False\n(Permissions.CAN_COOK | PERMISSIONS.is_worthy)(ironman) # \u279e True\n```\n\n\n### Django integration\n#### Installation\n```bash\npip install ezperm[django]\n```\n\nezperm 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.\n\nFirst, lets update our `Permissions` and `Hero` classes in our example:\n```python\nimport ezperm.django\n\nfrom django.db import models\nfrom django.contrib.postgres.fields import ArrayField\n\n\nclass Permissions(ezperm.PermissionEnum, models.TextChoices):\n ... # same as before\n\n\nclass Hero(ezperm.django.PermissionsMixin, models.Model):\n name = models.CharField(max_length=100)\n age = models.IntegerField()\n permissions = ArrayField(\n base_field=models.CharField(\n max_length=255,\n choices=Permissions.choices,\n ),\n default=list,\n )\n```\nNote, that we've inherited the `Permissions` from Django's `TextChoices` in order to use it as a `choices` argument for the `permissions` field.\nMoreover, we've added the `PermissionsMixin` to our `Hero` model, which overrides the `has_perms` and `has_perm` method.\n\nNow lets define a view which will allow access only to worthy or old heroes:\n```python\nfrom django.views.generic import View\nfrom ezperm.django.views import PermissionRequiredMixin\n\n\nclass CookView(PermissionRequiredMixin, View):\n permission_required = Permissions.CAN_COOK | Permissions.is_worthy\n \n def get(self, request):\n return HttpResponse('You can cook!')\n```\n\n\n## Contributing\nPull requests for any improvements are welcome.\n\n[Poetry](https://github.com/sdispater/poetry) is used to manage dependencies.\nTo get started follow these steps:\n\n```shell\ngit clone https://github.com/VojtechPetru/ezperm.git\ncd ezperm\npoetry install\npoetry run pytest\n```\n\n## Links\n- Repository: https://github.com/VojtechPetru/ezperm\n- Issue tracker: https://github.com/VojtechPetru/ezperm/issues. \nIn case of sensitive bugs (e.g. security vulnerabilities) please contact me at _petru.vojtech@gmail.com_ directly.\n\n\n",
"bugtrack_url": null,
"license": "LICENSE.md",
"summary": "Permissions made easy",
"version": "0.1.1",
"split_keywords": [
"permissions",
"django"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "846b532955016f800e81f6b022842570cc1105cca979e782a1536fe8471e382b",
"md5": "7080005c1c506503aa64745449835dff",
"sha256": "7951db573e1575fe83b54cf55699bda8ebb2ebda34b0a7e8ef3a4a3ac5b7960e"
},
"downloads": -1,
"filename": "ezperm-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7080005c1c506503aa64745449835dff",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8,<4.0",
"size": 6912,
"upload_time": "2023-03-29T03:47:26",
"upload_time_iso_8601": "2023-03-29T03:47:26.987331Z",
"url": "https://files.pythonhosted.org/packages/84/6b/532955016f800e81f6b022842570cc1105cca979e782a1536fe8471e382b/ezperm-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "ab39cd5d91f21db2c184864ffde734e0287f6a2dc013ba6aae5c322b3f9a55d1",
"md5": "fa8fc11f135db89fd4aaf5722bcde509",
"sha256": "bbbdad4773bf011b919d5d07a6c29ed75322f82e83b1cc717e8c5330fd6f663c"
},
"downloads": -1,
"filename": "ezperm-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "fa8fc11f135db89fd4aaf5722bcde509",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8,<4.0",
"size": 5197,
"upload_time": "2023-03-29T03:47:28",
"upload_time_iso_8601": "2023-03-29T03:47:28.664988Z",
"url": "https://files.pythonhosted.org/packages/ab/39/cd5d91f21db2c184864ffde734e0287f6a2dc013ba6aae5c322b3f9a55d1/ezperm-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-03-29 03:47:28",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "VojtechPetru",
"github_project": "ezperm",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "ezperm"
}