Skip to main content

FastAPI permissions system

Project description

fastapi-has-permissions

license test codecov pypi downloads

Introduction

fastapi-has-permissions is a Python library that provides a declarative permissions system for FastAPI applications. It allows you to define permission checks as classes or functions and compose them using boolean operators (&, |, ~) into complex permission expressions. These composed permissions integrate directly with FastAPI's dependency injection system.

Features:

  • Class-based permissions -- subclass Permission and override check_permissions()
  • Function-based permissions -- use the @permission decorator to wrap async functions
  • Boolean composition -- combine permissions with & (AND), | (OR), ~ (NOT)
  • Lazy evaluation -- defer dependency resolution to request time with lazy()
  • Skip mechanism -- conditionally bypass permission checks using SkipPermissionCheck
  • Customizable error responses -- override default 403 status code and error messages
  • Full FastAPI DI integration -- permission check functions support all FastAPI dependency injection features
  • Compatible with Python 3.10 and higher

Installation

pip install fastapi-has-permissions

Quickstart

Define a permission by subclassing Permission and use it as a FastAPI dependency:

from dataclasses import dataclass

from fastapi import Depends, FastAPI, Request

from fastapi_has_permissions import Permission


class HasAuthorizationHeader(Permission):
    async def check_permissions(self, request: Request) -> bool:
        return "Authorization" in request.headers


app = FastAPI()


@app.get(
    "/protected",
    dependencies=[Depends(HasAuthorizationHeader())],
)
async def protected():
    return {"message": "You have access!"}

Boolean Composition

Permissions can be combined using & (AND), | (OR), and ~ (NOT) operators:

from dataclasses import dataclass

from fastapi import Depends, Request

from fastapi_has_permissions import Permission


class HasAuthorizationHeader(Permission):
    async def check_permissions(self, request: Request) -> bool:
        return "Authorization" in request.headers


@dataclass
class HasRole(Permission):
    role: str

    async def check_permissions(self, request: Request) -> bool:
        return request.headers.get("role") == self.role


# All permissions must pass
@app.get(
    "/admin",
    dependencies=[Depends(HasAuthorizationHeader() & HasRole("admin"))],
)
async def admin_only():
    return {"message": "Admin access granted"}


# Any permission must pass
@app.get(
    "/flexible",
    dependencies=[Depends(HasAuthorizationHeader() | HasRole("admin"))],
)
async def flexible_access():
    return {"message": "Access granted"}


# Negated permission
@app.get(
    "/no-auth",
    dependencies=[Depends(~HasAuthorizationHeader())],
)
async def no_auth_required():
    return {"message": "No auth header present"}

Function-Based Permissions

Use the @permission decorator to create permissions from async functions:

from typing import Annotated

from fastapi import Depends, Header

from fastapi_has_permissions import permission


@permission
async def has_admin_role(role: Annotated[str, Header()]) -> bool:
    return role == "admin"


@app.get(
    "/admin",
    dependencies=[Depends(has_admin_role)],
)
async def admin_endpoint():
    return {"message": "Admin access granted"}

Function-based permissions also support boolean composition:

@permission
async def has_authorization(request: Request) -> bool:
    return "Authorization" in request.headers


# Combine function-based permissions
@app.get(
    "/combined",
    dependencies=[Depends(has_authorization & has_admin_role)],
)
async def combined():
    return {"message": "Access granted"}

Lazy Permissions

Use lazy() to defer dependency resolution to request time. This is useful when a permission's dependencies may not always be available:

from dataclasses import dataclass
from typing import Annotated

from fastapi import Depends, Header
from fastapi.exceptions import RequestValidationError

from fastapi_has_permissions import Permission, lazy


@dataclass
class AgeIsMoreThan(Permission):
    age: int

    async def check_permissions(self, age: Annotated[int, Header()]) -> bool:
        return age > self.age


# If the "age" header is missing/invalid, skip the check instead of failing
@app.get(
    "/age-restricted",
    dependencies=[
        Depends(lazy(AgeIsMoreThan(age=18), skip_on_exc=(RequestValidationError,))),
    ],
)
async def age_restricted():
    return {"message": "Access granted"}

Custom Error Responses

Override the default 403 response by setting class variables or overriding methods:

class CustomPermission(Permission):
    default_exc_message = "Custom error message"
    default_exc_status_code = 401

    async def check_permissions(self, request: Request) -> bool:
        return "Authorization" in request.headers

For dynamic error messages, override get_exc_message() or get_exc_status_code():

@dataclass
class HasRole(Permission):
    role: str

    async def check_permissions(self, request: Request) -> bool:
        return request.headers.get("role") == self.role

    def get_exc_message(self) -> str:
        return f"Role '{self.role}' is required"

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

fastapi_has_permissions-0.1.0.tar.gz (56.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_has_permissions-0.1.0-py3-none-any.whl (13.1 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_has_permissions-0.1.0.tar.gz.

File metadata

  • Download URL: fastapi_has_permissions-0.1.0.tar.gz
  • Upload date:
  • Size: 56.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fastapi_has_permissions-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3d30cebf2d1efffca4516051435ecf9f3b6dfa6ba09b784ae2e17bf650a1e3b9
MD5 0b7034e694c2c0093ebf649db7c70f00
BLAKE2b-256 84575a8a43ad8b5f0bd9b624534fed8d75b642aff58635f7f5f38f6e334a2b68

See more details on using hashes here.

File details

Details for the file fastapi_has_permissions-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fastapi_has_permissions-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fastapi_has_permissions-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a0f0cd5c80d7eba860f53c32e07253d21988fa47cfeea1dd2b4e7bc864bf6fcb
MD5 cf29f7fbd11d69c082e9de5a88d22f1a
BLAKE2b-256 168bded93043cbf2497f103bb4e0440a30f7a2fdac913b45d8a314f63b09be9c

See more details on using hashes here.

Supported by

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