Basics

Client

The Client object is designed to act as the source of truth and decision-making point of your application.

The Client object is designed to act as the source of truth and decision-making point of your application.

As a general rule, you should endeavor to use that Client object to make any authorization decision, not going through specific policies or rules.

Accessing the Client

The Client class takes a single optional parameters : a path to the folder containing your policies files.

You may place all of your policies in a single file or split them into multiple files in the same folder, it doesn't matter. Upon initialization, if the path parameter is provided, the Client will inspect that folder and register any Policy instance it finds.

You may also manually load policies after the initialization of the client, by calling load_policies() on it with a path parameter.

Using the Client

The Client class exposes the same evaluation functions as the Policy class, with one little twist: These function must now receive a resource positional argument that may be an instance of a class, or the class itself.

This argument will first be used by the client to determine which policy should be used for this resource, then will be passed down all the way to the actual rule evaluation. As such, passing a class when the rule would expect an instance will cause Entitled to correctly determine which rule should be evaluated, but the evaluation itself will fail:

policy = Policy[Post]()

@rule.policy
async def view(actor: Actor, post: Post):
    ...

client = Client()
await client.authorize(actor, Post) # The client will resolve this to the proper policy, but the evaluation itself will fail

Aside from this, the authorize, allows, denies, inspect and grants functions work like their counterpart on Policy and in fact defer to these functions under the hood.

!WARNING A potential (and temporary) foot-gun with Client

As we discussed in the "rules" section, the flexibility allowed when defining Rules and when calling evaluation function is still a bit unrefined and may come with a few potholes.

In particular, one such pothole that arises from the current implementation is that, when defining rules that don't make use of the 'resource' field but require additional fields, Client functions will pass the resource object as an argument to the rule evaluation, and if no precaution is taken, will pass it down as the additional parameter. Consider this:

policy = Policy[Post]()

@policy.rule
async def update(user: User, context: dict[str, Any]):
    return "some_user_list" in context and user in context["some_user_list"]:

client.authorize("update", post, context) # current implementation raises an error in this case.

A possible workaround in this case to define such rules like this:

@policy.rule
async def update(user: User, *, context: dict[str, Any]):
    return "some_user_list" in context and user in context["some_user_list"]:

This will ensure that the rule only accepts context as a keyword argument. You may then call you method like this:

client.authorize("update", post, context=context) # This will evaluate correctly

Copyright © 2025