Skip to main content

File Hot-Reload Example

A focused example for casbin-fastapi-decorator-file. It keeps the same simple Bearer-token auth as the basic example, but adds a /policy endpoint so you can verify file changes while the app is running.

Install​

pip install "casbin-fastapi-decorator[file]" uvicorn python-multipart

Project structure​

my-app/
├── casbin/
│ ├── model.conf
│ └── policy.csv
├── auth.py
├── authz.py
├── model.py
└── main.py

casbin/model.conf​

Same as the basic example.

casbin/policy.csv​

Same as the basic example.

model.py​

Same as the basic example.

auth.py​

Same as the basic example.

authz.py​

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

from auth import get_current_user
from fastapi import FastAPI, HTTPException

from casbin_fastapi_decorator import PermissionGuard
from casbin_fastapi_decorator_file import CachedFileEnforcerProvider

enforcer_provider = CachedFileEnforcerProvider(
model_path="casbin/model.conf",
policy_path="casbin/policy.csv",
)


@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
async with enforcer_provider:
yield


guard = PermissionGuard(
user_provider=get_current_user,
enforcer_provider=enforcer_provider,
error_factory=lambda *_: HTTPException(403, "Forbidden"),
)

main.py​

from pathlib import Path
from typing import Annotated

from auth import get_current_user
from authz import guard, lifespan
from fastapi import Depends, FastAPI, Form
from model import Permission, PostCreateSchema, PostSchema, Resource, Role, UserSchema

app = FastAPI(title="Core + File Hot-Reload Example", lifespan=lifespan)

MOCK_DB = [
PostSchema(id=1, title="First Post"),
PostSchema(id=2, title="Second Post"),
]

@app.post("/login")
async def login(role: Role) -> str:
return role

@app.get("/me")
@guard.auth_required()
async def me(user: Annotated[UserSchema, Depends(get_current_user)]) -> UserSchema:
return user

@app.get("/articles")
@guard.require_permission(Resource.POST, Permission.READ)
async def list_posts() -> list[PostSchema]:
return MOCK_DB

@app.post("/articles")
@guard.require_permission(Resource.POST, Permission.WRITE)
async def create_post(data: Annotated[PostCreateSchema, Form]) -> PostSchema:
pk = sorted(MOCK_DB, key=lambda p: p.id)[-1].id + 1
post = PostSchema(id=pk, title=data.title)
MOCK_DB.append(post)
return post

@app.delete("/articles/{post_id}")
@guard.require_permission(Resource.POST, Permission.DELETE)
async def delete_post(post_id: int) -> dict:
return {"id": post_id, "deleted": True}

@app.get("/policy")
async def current_policy() -> dict:
return {"policy": Path("casbin/policy.csv").read_text()}

Running​

uvicorn main:app --reload

Hot-reload demo​

Inspect the current policy:

curl http://localhost:8000/policy

Then edit casbin/policy.csv while the app is running:

cat > casbin/policy.csv <<'EOF'
p, admin, post, read
p, admin, post, write
p, admin, post, delete
p, editor, post, read
p, editor, post, write
EOF

The next request uses the updated policy without restarting the app:

curl -H "Authorization: Bearer viewer" http://localhost:8000/articles
# -> {"detail": "Forbidden"}