AccessSubject
AccessSubject wraps a FastAPI dependency whose value comes from the request. Use it when a permission argument is not a fixed string but needs to be resolved dynamically at request time.
Import
from casbin_fastapi_decorator import AccessSubject
Definition
@dataclass(frozen=True, slots=True)
class AccessSubject:
val: Callable[..., Any]
selector: Callable[[Any], Any] = lambda x: x
val
A FastAPI dependency — a callable that FastAPI will resolve via dependency injection. It can use path parameters, query parameters, headers, or other dependencies.
selector
An optional transformation function applied to the resolved value before it is passed to the enforcer. Defaults to identity (lambda x: x).
Basic usage
from casbin_fastapi_decorator import AccessSubject
async def get_resource_owner(post_id: int) -> str:
post = await db.get_post(post_id)
return post.owner_id
@app.delete("/posts/{post_id}")
@guard.require_permission(
"post",
AccessSubject(val=get_resource_owner),
)
async def delete_post(post_id: int):
...
Here get_resource_owner receives post_id from the path parameter (FastAPI resolves it), fetches the post, and returns the owner ID. The enforcer then receives (user, "post", owner_id).
Using the selector
The selector transforms the resolved value. This is useful when the dependency returns a complex object but you only need one field:
from pydantic import BaseModel
class Post(BaseModel):
id: int
owner_id: str
title: str
async def get_post(post_id: int) -> Post:
return await db.get_post(post_id)
@app.put("/posts/{post_id}")
@guard.require_permission(
"post",
AccessSubject(
val=get_post,
selector=lambda post: post.owner_id, # extract just the owner_id
),
)
async def update_post(post_id: int, data: PostUpdate):
...
The enforcer receives (user, "post", post.owner_id) instead of the full Post object.
Combining static and dynamic arguments
You can mix plain values and AccessSubject in the same require_permission call:
@app.get("/posts/{post_id}/comments")
@guard.require_permission(
"comment", # static
"read", # static
AccessSubject(val=get_post_visibility), # dynamic
)
async def list_comments(post_id: int):
...
Arguments are resolved and passed to the enforcer in order:
enforcer.enforce(user, "comment", "read", post_visibility)
Immutability
AccessSubject is a frozen dataclass — it cannot be modified after creation. This makes it safe to define once at module level:
# Define once
post_owner = AccessSubject(val=get_post_owner, selector=lambda p: p.owner_id)
# Reuse across routes
@app.put("/posts/{post_id}")
@guard.require_permission("post", post_owner)
async def update_post(...): ...
@app.delete("/posts/{post_id}")
@guard.require_permission("post", post_owner)
async def delete_post(...): ...