Skip to main content

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(...): ...