Skip to content

Commit 7be0001

Browse files
committed
feat(openfgaclient): add support for BatchCheck API
1 parent 28353fa commit 7be0001

11 files changed

Lines changed: 1294 additions & 70 deletions

File tree

README.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -724,17 +724,19 @@ If 429s or 5xxs are encountered, the underlying check will retry up to 3 times b
724724

725725
```python
726726
# from openfga_sdk import OpenFgaClient
727-
# from openfga_sdk.client import ClientCheckRequest
728-
# from openfga_sdk.client.models import ClientTuple
729-
727+
# from openfga_sdk.client.models import (
728+
# ClientTuple,
729+
# ClientBatchCheckItem,
730+
# ClientBatchCheckRequest,
731+
# )
730732
# Initialize the fga_client
731733
# fga_client = OpenFgaClient(configuration)
732734

733735
options = {
734736
# You can rely on the model id set in the configuration or override it for this specific request
735737
"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"
736738
}
737-
body = [ClientCheckRequest(
739+
checks = [ClientBatchCheckItem(
738740
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
739741
relation="viewer",
740742
object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
@@ -748,7 +750,7 @@ body = [ClientCheckRequest(
748750
context=dict(
749751
ViewCount=100
750752
)
751-
), ClientCheckRequest(
753+
), ClientBatchCheckItem(
752754
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
753755
relation="admin",
754756
object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
@@ -759,20 +761,21 @@ body = [ClientCheckRequest(
759761
object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
760762
),
761763
]
762-
), ClientCheckRequest(
764+
), ClientBatchCheckItem(
763765
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
764766
relation="creator",
765767
object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
766-
), ClientCheckRequest(
768+
), ClientBatchCheckItem(
767769
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
768770
relation="deleter",
769771
object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
770772
)]
771773

772-
response = await fga_client.batch_check(body, options)
773-
# response.responses = [{
774+
response = await fga_client.batch_check(ClientBatchCheckRequest(checks=checks), options)
775+
# response.result = [{
774776
# allowed: false,
775-
# request: {
777+
# correlation_id: "de3630c2-f9be-4ee5-9441-cb1fbd82ce75",
778+
# tuple: {
776779
# user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
777780
# relation: "viewer",
778781
# object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
@@ -787,7 +790,8 @@ response = await fga_client.batch_check(body, options)
787790
# }
788791
# }, {
789792
# allowed: false,
790-
# request: {
793+
# correlation_id: "6d7c7129-9607-480e-bfd0-17c16e46b9ec",
794+
# tuple: {
791795
# user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
792796
# relation: "admin",
793797
# object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
@@ -799,14 +803,19 @@ response = await fga_client.batch_check(body, options)
799803
# }
800804
# }, {
801805
# allowed: false,
802-
# request: {
806+
# correlation_id: "210899b9-6bc3-4491-bdd1-d3d79780aa31",
807+
# tuple: {
803808
# user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
804809
# relation: "creator",
805810
# object: "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
806811
# },
807-
# error: <FgaError ...>
812+
# error: {
813+
# input_error: "validation_error",
814+
# message: "relation 'document#creator' not found"
815+
# }
808816
# }, {
809817
# allowed: true,
818+
# correlation_id: "55cc1946-9fc3-4710-bd40-8fe2687ed8da",
810819
# request: {
811820
# user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
812821
# relation: "deleter",

example/example1/example1.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import os
3+
import uuid
34

45
from openfga_sdk import (
56
ClientConfiguration,
@@ -21,6 +22,8 @@
2122
)
2223
from openfga_sdk.client.models import (
2324
ClientAssertion,
25+
ClientBatchCheckItem,
26+
ClientBatchCheckRequest,
2427
ClientCheckRequest,
2528
ClientListObjectsRequest,
2629
ClientListRelationsRequest,
@@ -268,6 +271,32 @@ async def main():
268271
)
269272
print(f"Allowed: {response.allowed}")
270273

274+
# Performing a BatchCheck
275+
print("Checking for access via BatchCheck")
276+
277+
anne_cor_id = str(uuid.uuid4())
278+
response = await fga_client.batch_check(
279+
ClientBatchCheckRequest(
280+
checks=[
281+
ClientBatchCheckItem(
282+
user="user:anne",
283+
relation="viewer",
284+
object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
285+
context=dict(ViewCount=100),
286+
correlation_id=anne_cor_id,
287+
),
288+
ClientBatchCheckItem(
289+
user="user:bob",
290+
relation="viewer",
291+
object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a",
292+
context=dict(ViewCount=100),
293+
),
294+
]
295+
)
296+
)
297+
print(f"Anne allowed: {response.result[0].allowed}")
298+
print(f"Bob allowed: {response.result[1].allowed}")
299+
271300
# List objects with context
272301
print("Listing objects for access with context")
273302

openfga_sdk/client/client.py

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,24 @@
1717
from openfga_sdk.api_client import ApiClient
1818
from openfga_sdk.client.configuration import ClientConfiguration
1919
from openfga_sdk.client.models.assertion import ClientAssertion
20-
from openfga_sdk.client.models.batch_check_response import BatchCheckResponse
20+
from openfga_sdk.client.models.batch_check_item import (
21+
ClientBatchCheckItem,
22+
construct_batch_item,
23+
)
24+
from openfga_sdk.client.models.batch_check_request import ClientBatchCheckRequest
25+
from openfga_sdk.client.models.batch_check_response import (
26+
ClientBatchCheckResponse,
27+
)
28+
from openfga_sdk.client.models.batch_check_single_response import (
29+
ClientBatchCheckSingleResponse,
30+
)
2131
from openfga_sdk.client.models.check_request import (
2232
ClientCheckRequest,
2333
construct_check_request,
2434
)
25-
from openfga_sdk.client.models.client_batch_check_response import ClientBatchCheckClientResponse
35+
from openfga_sdk.client.models.client_batch_check_response import (
36+
ClientBatchCheckClientResponse,
37+
)
2638
from openfga_sdk.client.models.expand_request import ClientExpandRequest
2739
from openfga_sdk.client.models.list_objects_request import ClientListObjectsRequest
2840
from openfga_sdk.client.models.list_relations_request import ClientListRelationsRequest
@@ -41,6 +53,7 @@
4153
UnauthorizedException,
4254
)
4355
from openfga_sdk.models.assertion import Assertion
56+
from openfga_sdk.models.batch_check_request import BatchCheckRequest
4457
from openfga_sdk.models.check_request import CheckRequest
4558
from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
4659
from openfga_sdk.models.create_store_request import CreateStoreRequest
@@ -637,6 +650,118 @@ async def client_batch_check(
637650

638651
return batch_check_response
639652

653+
async def _single_batch_check(
654+
self,
655+
body: BatchCheckRequest,
656+
semaphore: asyncio.Semaphore,
657+
options: dict[str, str] = None,
658+
):
659+
"""
660+
Run a single BatchCheck request
661+
:param body - list[ClientCheckRequest] defining check request
662+
:param authorization_model_id(options) - Overrides the authorization model id in the configuration
663+
"""
664+
await semaphore.acquire()
665+
try:
666+
kwargs = options_to_kwargs(options)
667+
api_response = await self._api.batch_check(body, **kwargs)
668+
return api_response
669+
except Exception as err:
670+
raise err
671+
finally:
672+
semaphore.release()
673+
674+
async def batch_check(self, body: ClientBatchCheckRequest, options=None):
675+
"""
676+
Run a batchcheck request
677+
:param body - BatchCheck request
678+
:param authorization_model_id(options) - Overrides the authorization model id in the configuration
679+
:param max_parallel_requests(options) - Max number of requests to issue in parallel. Defaults to 10
680+
:param max_batch_size(options) - Max number of checks to include in a request. Defaults to 50
681+
:param header(options) - Custom headers to send alongside the request
682+
:param retryParams(options) - Override the retry parameters for this request
683+
:param retryParams.maxRetry(options) - Override the max number of retries on each API request
684+
:param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated
685+
"""
686+
options = set_heading_if_not_set(
687+
options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4())
688+
)
689+
690+
max_parallel_requests = 10
691+
if options is not None and "max_parallel_requests" in options:
692+
if (
693+
isinstance(options["max_parallel_requests"], str)
694+
and options["max_parallel_requests"].isdigit()
695+
):
696+
max_parallel_requests = int(options["max_parallel_requests"])
697+
elif isinstance(options["max_parallel_requests"], int):
698+
max_parallel_requests = options["max_parallel_requests"]
699+
700+
max_batch_size = 50
701+
if options is not None and "max_batch_size" in options:
702+
if (
703+
isinstance(options["max_batch_size"], str)
704+
and options["max_batch_size"].isdigit()
705+
):
706+
max_batch_size = int(options["max_batch_size"])
707+
elif isinstance(options["max_batch_size"], int):
708+
max_batch_size = options["max_batch_size"]
709+
710+
check_to_id: dict[str, ClientBatchCheckItem] = {}
711+
712+
def track_and_transform(checks):
713+
transformed = []
714+
for check in checks:
715+
if check.correlation_id is None:
716+
check.correlation_id = str(uuid.uuid4())
717+
718+
if check.correlation_id in check_to_id:
719+
raise FgaValidationException("Duplicate correlation_id provided")
720+
721+
check_to_id[check.correlation_id] = check
722+
723+
transformed.append(construct_batch_item(check))
724+
return transformed
725+
726+
checks = [
727+
track_and_transform(
728+
body.checks[i * max_batch_size : (i + 1) * max_batch_size]
729+
)
730+
for i in range((len(body.checks) + max_batch_size - 1) // max_batch_size)
731+
]
732+
733+
result = []
734+
sem = asyncio.Semaphore(max_parallel_requests)
735+
736+
def map_response(id, result):
737+
check = check_to_id[id]
738+
return ClientBatchCheckSingleResponse(
739+
allowed=result.allowed,
740+
request=check,
741+
correlation_id=id,
742+
error=result.error,
743+
)
744+
745+
async def coro(checks):
746+
res = await self._single_batch_check(
747+
BatchCheckRequest(
748+
checks=checks,
749+
authorization_model_id=self._get_authorization_model_id(options),
750+
consistency=self._get_consistency(options),
751+
),
752+
sem,
753+
options,
754+
)
755+
756+
result.extend(
757+
[map_response(c_id, c_result) for c_id, c_result in res.result.items()]
758+
)
759+
760+
batch_check_coros = [coro(request) for request in checks]
761+
await asyncio.gather(*batch_check_coros)
762+
763+
return ClientBatchCheckResponse(result)
764+
640765
async def expand(self, body: ClientExpandRequest, options: dict[str, str] = None):
641766
"""
642767
Run expand request

openfga_sdk/client/models/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@
1111
"""
1212

1313
from openfga_sdk.client.models.assertion import ClientAssertion
14-
from openfga_sdk.client.models.batch_check_response import BatchCheckResponse
14+
from openfga_sdk.client.models.batch_check_item import ClientBatchCheckItem
15+
from openfga_sdk.client.models.batch_check_request import ClientBatchCheckRequest
16+
from openfga_sdk.client.models.batch_check_response import ClientBatchCheckResponse
17+
from openfga_sdk.client.models.batch_check_single_response import (
18+
ClientBatchCheckSingleResponse,
19+
)
1520
from openfga_sdk.client.models.check_request import ClientCheckRequest
16-
from openfga_sdk.client.models.client_batch_check_response import ClientBatchCheckClientResponse
21+
from openfga_sdk.client.models.client_batch_check_response import (
22+
ClientBatchCheckClientResponse,
23+
)
1724
from openfga_sdk.client.models.expand_request import ClientExpandRequest
1825
from openfga_sdk.client.models.list_objects_request import ClientListObjectsRequest
1926
from openfga_sdk.client.models.list_relations_request import ClientListRelationsRequest

0 commit comments

Comments
 (0)