diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..f42e9bd --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,38 @@ +# NOTE: This file is auto generated by OpenAPI Generator. +# URL: https://openapi-generator.tech +# +# ref: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: openfga_sdk Python package + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore index e277ddc..9a1c489 100644 --- a/.openapi-generator-ignore +++ b/.openapi-generator-ignore @@ -4,6 +4,10 @@ test/* !test/test_open_fga_api.py !test/test_credentials.py !test/test_client.py +!test/test_client_sync.py +!test/test_open_fga_api_sync.py +!test/test_validation.py +!test/test_credentials_sync.py .gitlab-ci.yml .travis.yml tox.ini diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index 8d1a02b..dad4d85 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -70,6 +70,7 @@ docs/WriteRequest.md openfga_sdk/__init__.py openfga_sdk/api/__init__.py openfga_sdk/api/open_fga_api.py +openfga_sdk/api/open_fga_api_sync.py openfga_sdk/api_client.py openfga_sdk/client/__init__.py openfga_sdk/client/client.py @@ -146,7 +147,7 @@ openfga_sdk/models/write_authorization_model_request.py openfga_sdk/models/write_authorization_model_response.py openfga_sdk/models/write_request.py openfga_sdk/rest.py -openfga_sdk/sync/api.py +openfga_sdk/sync/__init__.py openfga_sdk/sync/api_client.py openfga_sdk/sync/client/client.py openfga_sdk/sync/credentials.py @@ -158,5 +159,9 @@ setup.py test-requirements.txt test/__init__.py test/test_client.py +test/test_client_sync.py test/test_credentials.py +test/test_credentials_sync.py test/test_open_fga_api.py +test/test_open_fga_api_sync.py +test/test_validation.py diff --git a/openfga_sdk/sync/__init__.py b/openfga_sdk/sync/__init__.py new file mode 100644 index 0000000..27b1b75 --- /dev/null +++ b/openfga_sdk/sync/__init__.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +# flake8: noqa +""" + Python SDK for OpenFGA + + API version: 0.1 + Website: https://openfga.dev + Documentation: https://openfga.dev/docs + Support: https://discord.gg/8naAwJfWN6 + License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) + + NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +""" + +from openfga_sdk.sync.client.client import OpenFgaClient +from openfga_sdk.sync.api_client import ApiClient + +from openfga_sdk.client.configuration import ClientConfiguration +import openfga_sdk.configuration as configuration +from openfga_sdk.configuration import Configuration + +from openfga_sdk.exceptions import OpenApiException +from openfga_sdk.exceptions import FgaValidationException +from openfga_sdk.exceptions import ApiValueError +from openfga_sdk.exceptions import ApiAttributeError +from openfga_sdk.exceptions import ApiKeyError +from openfga_sdk.exceptions import ApiException +from openfga_sdk.exceptions import NotFoundException +from openfga_sdk.exceptions import UnauthorizedException +from openfga_sdk.exceptions import ForbiddenException +from openfga_sdk.exceptions import ServiceException +from openfga_sdk.exceptions import ValidationException +from openfga_sdk.exceptions import AuthenticationError +from openfga_sdk.exceptions import RateLimitExceededError diff --git a/openfga_sdk/sync/api.py b/openfga_sdk/sync/api.py deleted file mode 100644 index 473d71e..0000000 --- a/openfga_sdk/sync/api.py +++ /dev/null @@ -1,25 +0,0 @@ -# coding: utf-8 - -""" - Python SDK for OpenFGA - - API version: 0.1 - Website: https://openfga.dev - Documentation: https://openfga.dev/docs - Support: https://discord.gg/8naAwJfWN6 - License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) - - NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. -""" - - -import re # noqa: F401 - -# python 2 and python 3 compatibility library -import six - -from openfga_sdk.sync.api_client import ApiClient -from openfga_sdk.exceptions import ( # noqa: F401 - FgaValidationException, - ApiValueError -) diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index ed1871b..c6241ba 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -12,7 +12,7 @@ """ from openfga_sdk.sync.api_client import ApiClient -from openfga_sdk.sync.api.open_fga_api import OpenFgaApi +from openfga_sdk.sync.open_fga_api import OpenFgaApi from openfga_sdk.client.configuration import ClientConfiguration from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.check_request import ClientCheckRequest, construct_check_request @@ -40,7 +40,7 @@ from openfga_sdk.models.write_request import WriteRequest from openfga_sdk.validation import is_well_formed_ulid_string -import asyncio +import time import uuid from typing import List @@ -113,14 +113,14 @@ def __init__(self, configuration: ClientConfiguration): self._api_client = ApiClient(configuration) self._api = OpenFgaApi(self._api_client) - async def __aenter__(self): + def __enter__(self): return self - async def __aexit__(self, exc_type, exc_value, traceback): - await self.close() + def __exit__(self, exc_type, exc_value, traceback): + self.close() - async def close(self): - await self._api.close() + def close(self): + self._api.close() def _get_authorization_model_id(self, options: object) -> str | None: """ @@ -161,21 +161,21 @@ def get_authorization_model_id(self): """ return self._client_configuration.authorization_model_id - async def _check_valid_api_connection(self, options: dict[str, int | str]): + def _check_valid_api_connection(self, options: dict[str, int | str]): """ Checks that a connection with the given configuration can be established """ authorization_model_id = self._get_authorization_model_id(options) if authorization_model_id is not None and authorization_model_id != "": - await self.read_authorization_model(options) + self.read_authorization_model(options) else: - await self.read_latest_authorization_model(options) + self.read_latest_authorization_model(options) ################# # Stores ################# - async def list_stores(self, options: dict[str, int | str] = None): + def list_stores(self, options: dict[str, int | str] = None): """ List the stores in the system :param page_size(options) - Number of items returned per request @@ -188,12 +188,12 @@ async def list_stores(self, options: dict[str, int | str] = None): # convert options to kargs options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "ListStores") kwargs = options_to_kwargs(options) - api_response = await self._api.list_stores( + api_response = self._api.list_stores( **kwargs, ) return api_response - async def create_store(self, body: CreateStoreRequest, options: dict[str, int | str] = None): + def create_store(self, body: CreateStoreRequest, options: dict[str, int | str] = None): """ Create the stores in the system :param header(options) - Custom headers to send alongside the request @@ -203,13 +203,13 @@ async def create_store(self, body: CreateStoreRequest, options: dict[str, int | """ options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "CreateStore") kwargs = options_to_kwargs(options) - api_response = await self._api.create_store( + api_response = self._api.create_store( body, **kwargs ) return api_response - async def get_store(self, options: dict[str, int | str] = None): + def get_store(self, options: dict[str, int | str] = None): """ Get the store info in the system. Store id is from the configuration. :param header(options) - Custom headers to send alongside the request @@ -219,12 +219,12 @@ async def get_store(self, options: dict[str, int | str] = None): """ options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "GetStore") kwargs = options_to_kwargs(options) - api_response = await self._api.get_store( + api_response = self._api.get_store( **kwargs, ) return api_response - async def delete_store(self, options: dict[str, int | str] = None): + def delete_store(self, options: dict[str, int | str] = None): """ Delete the store from the system. Store id is from the configuration. :param header(options) - Custom headers to send alongside the request @@ -234,7 +234,7 @@ async def delete_store(self, options: dict[str, int | str] = None): """ options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "DeleteStore") kwargs = options_to_kwargs(options) - api_response = await self._api.delete_store( + api_response = self._api.delete_store( **kwargs, ) return api_response @@ -243,7 +243,7 @@ async def delete_store(self, options: dict[str, int | str] = None): # Authorization Models ####################### - async def read_authorization_models(self, options: dict[str, int | str] = None): + def read_authorization_models(self, options: dict[str, int | str] = None): """ Return all the authorization models for a particular store. :param header(options) - Custom headers to send alongside the request @@ -253,12 +253,12 @@ async def read_authorization_models(self, options: dict[str, int | str] = None): """ options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "ReadAuthorizationModels") kwargs = options_to_kwargs(options) - api_response = await self._api.read_authorization_models( + api_response = self._api.read_authorization_models( **kwargs, ) return api_response - async def write_authorization_model(self, body: WriteAuthorizationModelRequest, options: dict[str, int | str] = None): + def write_authorization_model(self, body: WriteAuthorizationModelRequest, options: dict[str, int | str] = None): """ Write authorization model. :param body - WriteAuthorizationModelRequest @@ -269,13 +269,13 @@ async def write_authorization_model(self, body: WriteAuthorizationModelRequest, """ options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "WriteAuthorizationModel") kwargs = options_to_kwargs(options) - api_response = await self._api.write_authorization_model( + api_response = self._api.write_authorization_model( body, **kwargs, ) return api_response - async def read_authorization_model(self, options: dict[str, int | str] = None): + def read_authorization_model(self, options: dict[str, int | str] = None): """ Read an authorization model. :param header(options) - Custom headers to send alongside the request @@ -286,13 +286,13 @@ async def read_authorization_model(self, options: dict[str, int | str] = None): options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "ReadAuthorizationModel") kwargs = options_to_kwargs(options) authorization_model_id = self._get_authorization_model_id(options) - api_response = await self._api.read_authorization_model( + api_response = self._api.read_authorization_model( authorization_model_id, **kwargs, ) return api_response - async def read_latest_authorization_model(self, options: dict[str, int | str] = None): + def read_latest_authorization_model(self, options: dict[str, int | str] = None): """ Convenient method of reading the latest authorizaiton model :param header(options) - Custom headers to send alongside the request @@ -303,14 +303,14 @@ async def read_latest_authorization_model(self, options: dict[str, int | str] = options = set_heading_if_not_set( options, CLIENT_METHOD_HEADER, "ReadLatestAuthoriationModel") options["page_size"] = 1 - api_response = await self.read_authorization_models(options) + api_response = self.read_authorization_models(options) return ReadAuthorizationModelResponse(api_response.authorization_models[0]) ####################### # Relationship Tuples ####################### - async def read_changes(self, body: ClientReadChangesRequest, options: dict[str, str] = None): + def read_changes(self, body: ClientReadChangesRequest, options: dict[str, str] = None): """ Read changes for specified type :param body - the type we want to look for change @@ -324,12 +324,12 @@ async def read_changes(self, body: ClientReadChangesRequest, options: dict[str, options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "ReadChanges") kwargs = options_to_kwargs(options) kwargs["type"] = body.type - api_response = await self._api.read_changes( + api_response = self._api.read_changes( **kwargs, ) return api_response - async def read(self, body: TupleKey, options: dict[str, str] = None): + def read(self, body: TupleKey, options: dict[str, str] = None): """ Read changes for specified type :param body - the tuples we want to read @@ -352,7 +352,7 @@ async def read(self, body: TupleKey, options: dict[str, str] = None): options.pop("continuation_token") kwargs = options_to_kwargs(options) - api_response = await self._api.read( + api_response = self._api.read( ReadRequest( tuple_key=body, page_size=page_size, @@ -362,7 +362,7 @@ async def read(self, body: TupleKey, options: dict[str, str] = None): ) return api_response - async def _write_single_batch(self, batch: List[ClientTuple], is_write: bool, options: dict[str, str] = None): + def _write_single_batch(self, batch: List[ClientTuple], is_write: bool, options: dict[str, str] = None): try: write_batch = None delete_batch = None @@ -370,12 +370,13 @@ async def _write_single_batch(self, batch: List[ClientTuple], is_write: bool, op write_batch = batch else: delete_batch = batch - await self._write_with_transaction(ClientWriteRequest(writes=write_batch, deletes=delete_batch), options) + self._write_with_transaction(ClientWriteRequest( + writes=write_batch, deletes=delete_batch), options) return [construct_write_single_response(i, True, None) for i in batch] except Exception as err: return [construct_write_single_response(i, False, err) for i in batch] - async def _write_batches(self, tuple_keys: List[ClientTuple], transaction: WriteTransactionOpts, is_write: bool, options: dict[str, str] = None): + def _write_batches(self, tuple_keys: List[ClientTuple], transaction: WriteTransactionOpts, is_write: bool, options: dict[str, str] = None): """ Internal function for write/delete batches """ @@ -384,15 +385,14 @@ async def _write_batches(self, tuple_keys: List[ClientTuple], transaction: Write write_batches = _chuck_array(chunks, transaction.max_parallel_requests) batch_write_responses = [] for write_batch in write_batches: - request = [self._write_single_batch(i, is_write, options) for i in write_batch] - response = await asyncio.gather(*request) + response = [self._write_single_batch(i, is_write, options) for i in write_batch] flatten_list = [ item for batch_single_response in response for item in batch_single_response] batch_write_responses.extend(flatten_list) return batch_write_responses - async def _write_with_transaction(self, body: ClientWriteRequest, options: dict[str, str] = None): + def _write_with_transaction(self, body: ClientWriteRequest, options: dict[str, str] = None): """ Write or deletes tuples """ @@ -404,7 +404,7 @@ async def _write_with_transaction(self, body: ClientWriteRequest, options: dict[ if body.deletes_tuple_keys: deletes_tuple_keys = body.deletes_tuple_keys - await self._api.write( + self._api.write( WriteRequest( writes=writes_tuple_keys, deletes=deletes_tuple_keys, @@ -422,7 +422,7 @@ async def _write_with_transaction(self, body: ClientWriteRequest, options: dict[ i, True, None) for i in body.deletes] return ClientWriteResponse(writes=writes_response, deletes=deletes_response) - async def write(self, body: ClientWriteRequest, options: dict[str, str] = None): + def write(self, body: ClientWriteRequest, options: dict[str, str] = None): """ Write or deletes tuples :param body - the write request @@ -434,23 +434,23 @@ async def write(self, body: ClientWriteRequest, options: dict[str, str] = None): options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "Writes") transaction = options_to_transaction_info(options) if not transaction.disabled: - results = await self._write_with_transaction(body, options) + results = self._write_with_transaction(body, options) return results options = set_heading_if_not_set(options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4())) # TODO: this should be run in parallel - await self._check_valid_api_connection(options) + self._check_valid_api_connection(options) # otherwise, it is not a transaction and it is a batch write requests writes_response = None if body.writes: - writes_response = await self._write_batches(body.writes, transaction, True, options) + writes_response = self._write_batches(body.writes, transaction, True, options) deletes_response = None if body.deletes: - deletes_response = await self._write_batches(body.deletes, transaction, False, options) + deletes_response = self._write_batches(body.deletes, transaction, False, options) return ClientWriteResponse(writes=writes_response, deletes=deletes_response) - async def write_tuples(self, body: List[ClientTuple], options: dict[str, str] = None): + def write_tuples(self, body: List[ClientTuple], options: dict[str, str] = None): """ Convenient method for writing tuples :param body - the list of tuples we want to write @@ -460,10 +460,10 @@ async def write_tuples(self, body: List[ClientTuple], options: dict[str, str] = :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "WriteTuples") - result = await self.write(ClientWriteRequest(body, None), options) + result = self.write(ClientWriteRequest(body, None), options) return result - async def delete_tuples(self, body: List[ClientTuple], options: dict[str, str] = None): + def delete_tuples(self, body: List[ClientTuple], options: dict[str, str] = None): """ Convenient method for deleteing tuples :param body - the list of tuples we want to delete @@ -473,13 +473,13 @@ async def delete_tuples(self, body: List[ClientTuple], options: dict[str, str] = :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "DeleteTuples") - result = await self.write(ClientWriteRequest(None, body), options) + result = self.write(ClientWriteRequest(None, body), options) return result ####################### # Relationship Queries ####################### - async def check(self, body: ClientCheckRequest, options: dict[str, str] = None): # noqa: E501 + def check(self, body: ClientCheckRequest, options: dict[str, str] = None): # noqa: E501 """ Check whether a user is authorized to access an object :param body - ClientCheckRequest defining check request @@ -505,25 +505,25 @@ async def check(self, body: ClientCheckRequest, options: dict[str, str] = None): req_body.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = await self._api.check( + api_response = self._api.check( body=req_body, **kwargs ) return api_response - async def _single_batch_check(self, body: ClientCheckRequest, options: dict[str, str] = None): # noqa: E501 + def _single_batch_check(self, body: ClientCheckRequest, options: dict[str, str] = None): # noqa: E501 """ Run a single batch request and return body in a SingleBatchCheckResponse :param body - ClientCheckRequest defining check request :param authorization_model_id(options) - Overrides the authorization model id in the configuration """ try: - api_response = await self.check(body, options) + api_response = self.check(body, options) return BatchCheckResponse(allowed=api_response.allowed, request=body, response=api_response, error=None) except Exception as err: return BatchCheckResponse(allowed=False, request=body, response=None, error=err) - async def batch_check(self, body: List[ClientCheckRequest], options: dict[str, str] = None): # noqa: E501 + def batch_check(self, body: List[ClientCheckRequest], options: dict[str, str] = None): # noqa: E501 """ Run a set of checks :param body - list of ClientCheckRequest defining check request @@ -538,7 +538,7 @@ async def batch_check(self, body: List[ClientCheckRequest], options: dict[str, s options = set_heading_if_not_set(options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4())) # TODO: this should be run in parallel - await self._check_valid_api_connection(options) + self._check_valid_api_connection(options) max_parallel_requests = 10 if options is not None and "max_parallel_requests" in options: @@ -547,12 +547,11 @@ async def batch_check(self, body: List[ClientCheckRequest], options: dict[str, s request_batches = _chuck_array(body, max_parallel_requests) batch_check_response = [] for request_batch in request_batches: - request = [self._single_batch_check(i, options) for i in request_batch] - response = await asyncio.gather(*request) + response = [self._single_batch_check(i, options) for i in request_batch] batch_check_response.extend(response) return batch_check_response - async def expand(self, body: ClientExpandRequest, options: dict[str, str] = None): # noqa: E501 + def expand(self, body: ClientExpandRequest, options: dict[str, str] = None): # noqa: E501 """ Run expand request :param body - list of ClientExpandRequest defining expand request @@ -572,13 +571,13 @@ async def expand(self, body: ClientExpandRequest, options: dict[str, str] = None ), authorization_model_id=self._get_authorization_model_id(options), ) - api_response = await self._api.expand( + api_response = self._api.expand( body=req_body, **kwargs ) return api_response - async def list_objects(self, body: ClientListObjectsRequest, options: dict[str, str] = None): # noqa: E501 + def list_objects(self, body: ClientListObjectsRequest, options: dict[str, str] = None): # noqa: E501 """ Run list object request :param body - list object parameters @@ -601,13 +600,13 @@ async def list_objects(self, body: ClientListObjectsRequest, options: dict[str, req_body.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = await self._api.list_objects( + api_response = self._api.list_objects( body=req_body, **kwargs ) return api_response - async def list_relations(self, body: ClientListObjectsRequest, options: dict[str, str] = None): # noqa: E501 + def list_relations(self, body: ClientListObjectsRequest, options: dict[str, str] = None): # noqa: E501 """ Return all the relations for which user has a relationship with the object :param body - list relation request @@ -622,7 +621,7 @@ async def list_relations(self, body: ClientListObjectsRequest, options: dict[str request_body = [construct_check_request( user=body.user, relation=i, object=body.object, contextual_tuples=body.contextual_tuples) for i in body.relations] - result = await self.batch_check(request_body, options) + result = self.batch_check(request_body, options) # need to filter with the allowed response result_iterator = filter(_check_allowed, result) result_list = list(result_iterator) @@ -632,7 +631,7 @@ async def list_relations(self, body: ClientListObjectsRequest, options: dict[str # Assertions ####################### - async def read_assertions(self, options: dict[str, str] = None): # noqa: E501 + def read_assertions(self, options: dict[str, str] = None): # noqa: E501 """ Return the assertions :param authorization_model_id(options) - Overrides the authorization model id in the configuration @@ -645,10 +644,10 @@ async def read_assertions(self, options: dict[str, str] = None): # noqa: E501 kwargs = options_to_kwargs(options) authorization_model_id = self._get_authorization_model_id(options) - api_response = await self._api.read_assertions(authorization_model_id, **kwargs) + api_response = self._api.read_assertions(authorization_model_id, **kwargs) return api_response - async def write_assertions(self, body: List[ClientAssertion], options: dict[str, str] = None): # noqa: E501 + def write_assertions(self, body: List[ClientAssertion], options: dict[str, str] = None): # noqa: E501 """ Upsert the assertions :param body - Write assertion request @@ -671,5 +670,6 @@ def map_to_assertion(client_assertion: ClientAssertion): api_request_body = WriteAssertionsRequest( [map_to_assertion(client_assertion) for client_assertion in body]) - api_response = await self._api.write_assertions(authorization_model_id, api_request_body, **kwargs) + api_response = self._api.write_assertions( + authorization_model_id, api_request_body, **kwargs) return api_response diff --git a/openfga_sdk/sync/open_fga_api.py b/openfga_sdk/sync/open_fga_api.py new file mode 100644 index 0000000..4da863d --- /dev/null +++ b/openfga_sdk/sync/open_fga_api.py @@ -0,0 +1,2221 @@ +# coding: utf-8 + +""" + Python SDK for OpenFGA + + API version: 0.1 + Website: https://openfga.dev + Documentation: https://openfga.dev/docs + Support: https://discord.gg/8naAwJfWN6 + License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) + + NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +""" + + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from openfga_sdk.sync.api_client import ApiClient +from openfga_sdk.exceptions import ( # noqa: F401 + FgaValidationException, + ApiValueError +) + + +class OpenFgaApi(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def __enter__(self): + return self + + def __exit__(self): + self.close() + + def close(self): + self.api_client.close() + + def check(self, body, **kwargs): # noqa: E501 + """Check whether a user is authorized to access an object # noqa: E501 + + The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn't. # noqa: E501 + + >>> thread = api.check(body) + + :param body: (required) + :type body: CheckRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: CheckResponse + """ + kwargs['_return_http_data_only'] = True + return self.check_with_http_info(body, **kwargs) # noqa: E501 + + def check_with_http_info(self, body, **kwargs): # noqa: E501 + """Check whether a user is authorized to access an object # noqa: E501 + + The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn't. # noqa: E501 + + >>> thread = api.check_with_http_info(body) + + :param body: (required) + :type body: CheckRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(CheckResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method check" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'body' is set + if self.api_client.client_side_validation and local_var_params.get('body') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `body` when calling `check`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `check`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'POST', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "CheckResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/check'.replace('{store_id}', store_id), 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def create_store(self, body, **kwargs): # noqa: E501 + """Create a store # noqa: E501 + + Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. # noqa: E501 + + >>> thread = api.create_store(body) + + :param body: (required) + :type body: CreateStoreRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: CreateStoreResponse + """ + kwargs['_return_http_data_only'] = True + return self.create_store_with_http_info(body, **kwargs) # noqa: E501 + + def create_store_with_http_info(self, body, **kwargs): # noqa: E501 + """Create a store # noqa: E501 + + Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. # noqa: E501 + + >>> thread = api.create_store_with_http_info(body) + + :param body: (required) + :type body: CreateStoreRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(CreateStoreResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method create_store" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'POST', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 201: "CreateStoreResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def delete_store(self, **kwargs): # noqa: E501 + """Delete a store # noqa: E501 + + Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. # noqa: E501 + + >>> thread = api.delete_store() + + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + kwargs['_return_http_data_only'] = True + return self.delete_store_with_http_info(**kwargs) # noqa: E501 + + def delete_store_with_http_info(self, **kwargs): # noqa: E501 + """Delete a store # noqa: E501 + + Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. # noqa: E501 + + >>> thread = api.delete_store_with_http_info() + + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + + local_var_params = locals() + + all_params = [ + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method delete_store" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `delete_store`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = {} + + return self.api_client.call_api( + '/stores/{store_id}'.replace('{store_id}', store_id), 'DELETE', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def expand(self, body, **kwargs): # noqa: E501 + """Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship # noqa: E501 + + The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. # noqa: E501 + + >>> thread = api.expand(body) + + :param body: (required) + :type body: ExpandRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ExpandResponse + """ + kwargs['_return_http_data_only'] = True + return self.expand_with_http_info(body, **kwargs) # noqa: E501 + + def expand_with_http_info(self, body, **kwargs): # noqa: E501 + """Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship # noqa: E501 + + The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. # noqa: E501 + + >>> thread = api.expand_with_http_info(body) + + :param body: (required) + :type body: ExpandRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ExpandResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method expand" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'body' is set + if self.api_client.client_side_validation and local_var_params.get('body') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `body` when calling `expand`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `expand`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'POST', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ExpandResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/expand'.replace('{store_id}', store_id), 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def get_store(self, **kwargs): # noqa: E501 + """Get a store # noqa: E501 + + Returns an OpenFGA store by its identifier # noqa: E501 + + >>> thread = api.get_store() + + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: GetStoreResponse + """ + kwargs['_return_http_data_only'] = True + return self.get_store_with_http_info(**kwargs) # noqa: E501 + + def get_store_with_http_info(self, **kwargs): # noqa: E501 + """Get a store # noqa: E501 + + Returns an OpenFGA store by its identifier # noqa: E501 + + >>> thread = api.get_store_with_http_info() + + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(GetStoreResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method get_store" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `get_store`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "GetStoreResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}'.replace('{store_id}', store_id), 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def list_objects(self, body, **kwargs): # noqa: E501 + """List all objects of the given type that the user has a relation with # noqa: E501 + + The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. # noqa: E501 + + >>> thread = api.list_objects(body) + + :param body: (required) + :type body: ListObjectsRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ListObjectsResponse + """ + kwargs['_return_http_data_only'] = True + return self.list_objects_with_http_info(body, **kwargs) # noqa: E501 + + def list_objects_with_http_info(self, body, **kwargs): # noqa: E501 + """List all objects of the given type that the user has a relation with # noqa: E501 + + The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. # noqa: E501 + + >>> thread = api.list_objects_with_http_info(body) + + :param body: (required) + :type body: ListObjectsRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ListObjectsResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method list_objects" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'body' is set + if self.api_client.client_side_validation and local_var_params.get('body') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `body` when calling `list_objects`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `list_objects`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'POST', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ListObjectsResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/list-objects'.replace('{store_id}', store_id), 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def list_stores(self, **kwargs): # noqa: E501 + """List all stores # noqa: E501 + + Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores. # noqa: E501 + + >>> thread = api.list_stores() + + :param page_size:(optional) + :type page_size: int, optional + :param continuation_token:(optional) + :type continuation_token: str, optional + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ListStoresResponse + """ + kwargs['_return_http_data_only'] = True + return self.list_stores_with_http_info(**kwargs) # noqa: E501 + + def list_stores_with_http_info(self, **kwargs): # noqa: E501 + """List all stores # noqa: E501 + + Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores. # noqa: E501 + + >>> thread = api.list_stores_with_http_info() + + :param page_size:(optional) + :type page_size: int, optional + :param continuation_token:(optional) + :type continuation_token: str, optional + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ListStoresResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'page_size', + 'continuation_token' + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method list_stores" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + query_params = [] + if local_var_params.get('page_size') is not None: # noqa: E501 + query_params.append(('page_size', local_var_params['page_size'])) # noqa: E501 + if local_var_params.get('continuation_token') is not None: # noqa: E501 + query_params.append(('continuation_token', local_var_params['continuation_token'])) # noqa: E501 + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ListStoresResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def read(self, body, **kwargs): # noqa: E501 + """Get tuples from the store that matches a query, without following userset rewrite rules # noqa: E501 + + The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). # noqa: E501 + + >>> thread = api.read(body) + + :param body: (required) + :type body: ReadRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ReadResponse + """ + kwargs['_return_http_data_only'] = True + return self.read_with_http_info(body, **kwargs) # noqa: E501 + + def read_with_http_info(self, body, **kwargs): # noqa: E501 + """Get tuples from the store that matches a query, without following userset rewrite rules # noqa: E501 + + The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). # noqa: E501 + + >>> thread = api.read_with_http_info(body) + + :param body: (required) + :type body: ReadRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ReadResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method read" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'body' is set + if self.api_client.client_side_validation and local_var_params.get('body') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `body` when calling `read`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `read`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'POST', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ReadResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/read'.replace('{store_id}', store_id), 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def read_assertions(self, authorization_model_id, **kwargs): # noqa: E501 + """Read assertions for an authorization model ID # noqa: E501 + + The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. # noqa: E501 + + >>> thread = api.read_assertions(authorization_model_id) + + :param authorization_model_id: (required) + :type authorization_model_id: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ReadAssertionsResponse + """ + kwargs['_return_http_data_only'] = True + return self.read_assertions_with_http_info(authorization_model_id, **kwargs) # noqa: E501 + + def read_assertions_with_http_info(self, authorization_model_id, **kwargs): # noqa: E501 + """Read assertions for an authorization model ID # noqa: E501 + + The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. # noqa: E501 + + >>> thread = api.read_assertions_with_http_info(authorization_model_id) + + :param authorization_model_id: (required) + :type authorization_model_id: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ReadAssertionsResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'authorization_model_id' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method read_assertions" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'authorization_model_id' is set + if self.api_client.client_side_validation and local_var_params.get('authorization_model_id') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `authorization_model_id` when calling `read_assertions`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `read_assertions`") # noqa: E501 + store_id = self.api_client._get_store_id() + + if 'authorization_model_id' in local_var_params: + path_params['authorization_model_id'] = local_var_params['authorization_model_id'] # noqa: E501 + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ReadAssertionsResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/assertions/{authorization_model_id}'.replace( + '{store_id}', store_id), 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def read_authorization_model(self, id, **kwargs): # noqa: E501 + """Return a particular version of an authorization model # noqa: E501 + + The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). # noqa: E501 + + >>> thread = api.read_authorization_model(id) + + :param id: (required) + :type id: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ReadAuthorizationModelResponse + """ + kwargs['_return_http_data_only'] = True + return self.read_authorization_model_with_http_info(id, **kwargs) # noqa: E501 + + def read_authorization_model_with_http_info(self, id, **kwargs): # noqa: E501 + """Return a particular version of an authorization model # noqa: E501 + + The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). # noqa: E501 + + >>> thread = api.read_authorization_model_with_http_info(id) + + :param id: (required) + :type id: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ReadAuthorizationModelResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'id' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method read_authorization_model" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'id' is set + if self.api_client.client_side_validation and local_var_params.get('id') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `id` when calling `read_authorization_model`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `read_authorization_model`") # noqa: E501 + store_id = self.api_client._get_store_id() + + if 'id' in local_var_params: + path_params['id'] = local_var_params['id'] # noqa: E501 + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ReadAuthorizationModelResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/authorization-models/{id}'.replace('{store_id}', store_id), 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def read_authorization_models(self, **kwargs): # noqa: E501 + """Return all the authorization models for a particular store # noqa: E501 + + The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA's response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store's authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` # noqa: E501 + + >>> thread = api.read_authorization_models() + + :param page_size:(optional) + :type page_size: int, optional + :param continuation_token:(optional) + :type continuation_token: str, optional + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ReadAuthorizationModelsResponse + """ + kwargs['_return_http_data_only'] = True + return self.read_authorization_models_with_http_info(**kwargs) # noqa: E501 + + def read_authorization_models_with_http_info(self, **kwargs): # noqa: E501 + """Return all the authorization models for a particular store # noqa: E501 + + The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA's response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store's authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` # noqa: E501 + + >>> thread = api.read_authorization_models_with_http_info() + + :param page_size:(optional) + :type page_size: int, optional + :param continuation_token:(optional) + :type continuation_token: str, optional + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ReadAuthorizationModelsResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'page_size', + 'continuation_token' + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method read_authorization_models" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `read_authorization_models`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + if local_var_params.get('page_size') is not None: # noqa: E501 + query_params.append(('page_size', local_var_params['page_size'])) # noqa: E501 + if local_var_params.get('continuation_token') is not None: # noqa: E501 + query_params.append(('continuation_token', local_var_params['continuation_token'])) # noqa: E501 + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ReadAuthorizationModelsResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/authorization-models'.replace('{store_id}', store_id), 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def read_changes(self, **kwargs): # noqa: E501 + """Return a list of all the tuple changes # noqa: E501 + + The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. # noqa: E501 + + >>> thread = api.read_changes() + + :param type:(optional) + :type type: str, optional + :param page_size:(optional) + :type page_size: int, optional + :param continuation_token:(optional) + :type continuation_token: str, optional + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: ReadChangesResponse + """ + kwargs['_return_http_data_only'] = True + return self.read_changes_with_http_info(**kwargs) # noqa: E501 + + def read_changes_with_http_info(self, **kwargs): # noqa: E501 + """Return a list of all the tuple changes # noqa: E501 + + The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. # noqa: E501 + + >>> thread = api.read_changes_with_http_info() + + :param type:(optional) + :type type: str, optional + :param page_size:(optional) + :type page_size: int, optional + :param continuation_token:(optional) + :type continuation_token: str, optional + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(ReadChangesResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'type', + 'page_size', + 'continuation_token' + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method read_changes" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `read_changes`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + if local_var_params.get('type') is not None: # noqa: E501 + query_params.append(('type', local_var_params['type'])) # noqa: E501 + if local_var_params.get('page_size') is not None: # noqa: E501 + query_params.append(('page_size', local_var_params['page_size'])) # noqa: E501 + if local_var_params.get('continuation_token') is not None: # noqa: E501 + query_params.append(('continuation_token', local_var_params['continuation_token'])) # noqa: E501 + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "ReadChangesResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/changes'.replace('{store_id}', store_id), 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def write(self, body, **kwargs): # noqa: E501 + """Add or delete tuples from the store # noqa: E501 + + The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples while `deletes` removes existing tuples. The API is not idempotent: if, later on, you try to add the same tuple, or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` # noqa: E501 + + >>> thread = api.write(body) + + :param body: (required) + :type body: WriteRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: object + """ + kwargs['_return_http_data_only'] = True + return self.write_with_http_info(body, **kwargs) # noqa: E501 + + def write_with_http_info(self, body, **kwargs): # noqa: E501 + """Add or delete tuples from the store # noqa: E501 + + The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples while `deletes` removes existing tuples. The API is not idempotent: if, later on, you try to add the same tuple, or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` # noqa: E501 + + >>> thread = api.write_with_http_info(body) + + :param body: (required) + :type body: WriteRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(object, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method write" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'body' is set + if self.api_client.client_side_validation and local_var_params.get('body') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `body` when calling `write`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `write`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'POST', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 200: "object", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/write'.replace('{store_id}', store_id), 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def write_assertions(self, authorization_model_id, body, **kwargs): # noqa: E501 + """Upsert assertions for an authorization model ID # noqa: E501 + + The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. # noqa: E501 + + >>> thread = api.write_assertions(authorization_model_id, body) + + :param authorization_model_id: (required) + :type authorization_model_id: str + :param body: (required) + :type body: WriteAssertionsRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + kwargs['_return_http_data_only'] = True + return self.write_assertions_with_http_info(authorization_model_id, body, **kwargs) # noqa: E501 + + def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs): # noqa: E501 + """Upsert assertions for an authorization model ID # noqa: E501 + + The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. # noqa: E501 + + >>> thread = api.write_assertions_with_http_info(authorization_model_id, body) + + :param authorization_model_id: (required) + :type authorization_model_id: str + :param body: (required) + :type body: WriteAssertionsRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + + local_var_params = locals() + + all_params = [ + + 'authorization_model_id', + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method write_assertions" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'authorization_model_id' is set + if self.api_client.client_side_validation and local_var_params.get('authorization_model_id') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `authorization_model_id` when calling `write_assertions`") # noqa: E501 + # verify the required parameter 'body' is set + if self.api_client.client_side_validation and local_var_params.get('body') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `body` when calling `write_assertions`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `write_assertions`") # noqa: E501 + store_id = self.api_client._get_store_id() + + if 'authorization_model_id' in local_var_params: + path_params['authorization_model_id'] = local_var_params['authorization_model_id'] # noqa: E501 + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'PUT', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = {} + + return self.api_client.call_api( + '/stores/{store_id}/assertions/{authorization_model_id}'.replace( + '{store_id}', store_id), 'PUT', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) + + def write_authorization_model(self, body, **kwargs): # noqa: E501 + """Create a new authorization model # noqa: E501 + + The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model's ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA's response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` # noqa: E501 + + >>> thread = api.write_authorization_model(body) + + :param body: (required) + :type body: WriteAuthorizationModelRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: WriteAuthorizationModelResponse + """ + kwargs['_return_http_data_only'] = True + return self.write_authorization_model_with_http_info(body, **kwargs) # noqa: E501 + + def write_authorization_model_with_http_info(self, body, **kwargs): # noqa: E501 + """Create a new authorization model # noqa: E501 + + The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model's ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA's response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` # noqa: E501 + + >>> thread = api.write_authorization_model_with_http_info(body) + + :param body: (required) + :type body: WriteAuthorizationModelRequest + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _return_http_data_only: response data without head status code + and headers + :type _return_http_data_only: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :type _preload_content: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :param _retry_param: if specified, override the retry parameters specified in configuration + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(WriteAuthorizationModelResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + local_var_params = locals() + + all_params = [ + + 'body' + + ] + all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers', + '_retry_parms' + ] + ) + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise FgaValidationException( + "Got an unexpected keyword argument '%s'" + " to method write_authorization_model" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'body' is set + if self.api_client.client_side_validation and local_var_params.get('body') is None: # noqa: E501 + raise ApiValueError("Missing the required parameter `body` when calling `write_authorization_model`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + if self.api_client._get_store_id() is None: + raise ApiValueError("Store ID expected in api_client's configuration when calling `write_authorization_model`") # noqa: E501 + store_id = self.api_client._get_store_id() + + query_params = [] + + header_params = dict(local_var_params.get('_headers', {})) + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type(['application/json'], 'POST', body_params)) # noqa: E501 + if content_types_list: + header_params['Content-Type'] = content_types_list + + # Authentication setting + auth_settings = [] # noqa: E501 + + response_types_map = { + 201: "WriteAuthorizationModelResponse", + 400: "ValidationErrorMessageResponse", + 404: "PathUnknownErrorMessageResponse", + 500: "InternalErrorMessageResponse", + } + + return self.api_client.call_api( + '/stores/{store_id}/authorization-models'.replace('{store_id}', store_id), 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_types_map=response_types_map, + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + _retry_params=local_var_params.get('_retry_params'), + collection_formats=collection_formats, + _request_auth=local_var_params.get('_request_auth')) diff --git a/test/test_client_sync.py b/test/test_client_sync.py new file mode 100644 index 0000000..829b336 --- /dev/null +++ b/test/test_client_sync.py @@ -0,0 +1,2191 @@ +# coding: utf-8 +""" + Python SDK for OpenFGA + + API version: 0.1 + Website: https://openfga.dev + Documentation: https://openfga.dev/docs + Support: https://discord.gg/8naAwJfWN6 + License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) + + NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +""" + +from unittest.mock import ANY +from unittest import IsolatedAsyncioTestCase +from mock import patch +from datetime import datetime + +import urllib3 + +from openfga_sdk.sync.client.client import OpenFgaClient +from openfga_sdk.sync import rest +from openfga_sdk.client import ClientConfiguration +from openfga_sdk.client.models.assertion import ClientAssertion +from openfga_sdk.client.models.check_request import ClientCheckRequest +from openfga_sdk.client.models.tuple import ClientTuple +from openfga_sdk.client.models.write_request import ClientWriteRequest +from openfga_sdk.client.models.expand_request import ClientExpandRequest +from openfga_sdk.client.models.list_objects_request import ClientListObjectsRequest +from openfga_sdk.client.models.list_relations_request import ClientListRelationsRequest +from openfga_sdk.client.models.read_changes_request import ClientReadChangesRequest +from openfga_sdk.client.models.write_single_response import ClientWriteSingleResponse +from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts +from openfga_sdk.exceptions import ValidationException, FgaValidationException, UnauthorizedException +from openfga_sdk.models.assertion import Assertion +from openfga_sdk.models.authorization_model import AuthorizationModel +from openfga_sdk.models.check_response import CheckResponse +from openfga_sdk.models.create_store_request import CreateStoreRequest +from openfga_sdk.models.create_store_response import CreateStoreResponse +from openfga_sdk.models.expand_response import ExpandResponse +from openfga_sdk.models.get_store_response import GetStoreResponse +from openfga_sdk.models.leaf import Leaf +from openfga_sdk.models.list_objects_response import ListObjectsResponse +from openfga_sdk.models.list_stores_response import ListStoresResponse +from openfga_sdk.models.node import Node +from openfga_sdk.models.object_relation import ObjectRelation +from openfga_sdk.models.read_assertions_response import ReadAssertionsResponse +from openfga_sdk.models.read_authorization_model_response import ReadAuthorizationModelResponse +from openfga_sdk.models.read_authorization_models_response import ReadAuthorizationModelsResponse +from openfga_sdk.models.read_changes_response import ReadChangesResponse +from openfga_sdk.models.read_response import ReadResponse +from openfga_sdk.models.store import Store +from openfga_sdk.models.tuple import Tuple +from openfga_sdk.models.tuple_change import TupleChange +from openfga_sdk.models.tuple_key import TupleKey +from openfga_sdk.models.tuple_operation import TupleOperation +from openfga_sdk.models.type_definition import TypeDefinition +from openfga_sdk.models.users import Users +from openfga_sdk.models.userset import Userset +from openfga_sdk.models.usersets import Usersets +from openfga_sdk.models.userset_tree import UsersetTree +from openfga_sdk.models.validation_error_message_response import ValidationErrorMessageResponse +from openfga_sdk.models.write_authorization_model_request import WriteAuthorizationModelRequest +from openfga_sdk.models.write_authorization_model_response import WriteAuthorizationModelResponse + + +store_id = '01YCP46JKYM8FJCQ37NMBYHE5X' +request_id = 'x1y2z3' + +# Helper function to construct mock response + + +def http_mock_response(body, status): + headers = urllib3.response.HTTPHeaderDict({ + 'content-type': 'application/json', + 'Fga-Request-Id': request_id + }) + return urllib3.HTTPResponse( + body.encode('utf-8'), + headers, + status, + preload_content=False + ) + + +def mock_response(body, status): + obj = http_mock_response(body, status) + return rest.RESTResponse(obj, obj.data) + + +class TestOpenFgaClient(IsolatedAsyncioTestCase): + """Test for OpenFGA Client""" + + def setUp(self): + self.configuration = ClientConfiguration( + api_scheme='http', + api_host="api.fga.example", + ) + + def tearDown(self): + pass + + @patch.object(rest.RESTClientObject, 'request') + def test_list_stores(self, mock_request): + """Test case for list_stores + + Get all stores # noqa: E501 + """ + response_body = ''' +{ + "stores": [ + { + "id": "01YCP46JKYM8FJCQ37NMBYHE5X", + "name": "store1", + "created_at": "2022-07-25T21:15:37.524Z", + "updated_at": "2022-07-25T21:15:37.524Z", + "deleted_at": "2022-07-25T21:15:37.524Z" + }, + { + "id": "01YCP46JKYM8FJCQ37NMBYHE6X", + "name": "store2", + "created_at": "2022-07-25T21:15:37.524Z", + "updated_at": "2022-07-25T21:15:37.524Z", + "deleted_at": "2022-07-25T21:15:37.524Z" + } + ], + "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + with OpenFgaClient(configuration) as api_client: + api_response = api_client.list_stores( + options={"page_size": 1, "continuation_token": "continuation_token_example"} + ) + self.assertIsInstance(api_response, ListStoresResponse) + self.assertEqual(api_response.continuation_token, + "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==") + store1 = Store( + id="01YCP46JKYM8FJCQ37NMBYHE5X", + name="store1", + created_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + updated_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + deleted_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + ) + store2 = Store( + id="01YCP46JKYM8FJCQ37NMBYHE6X", + name="store2", + created_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + updated_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + deleted_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + ) + + stores = [store1, store2] + self.assertEqual(api_response.stores, stores) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores', + headers=ANY, + query_params=[('page_size', 1), ('continuation_token', + 'continuation_token_example')], + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_create_store(self, mock_request): + """Test case for create_store + + Create a store # noqa: E501 + """ + response_body = '''{ + "id": "01YCP46JKYM8FJCQ37NMBYHE5X", + "name": "test_store", + "created_at": "2022-07-25T17:41:26.607Z", + "updated_at": "2022-07-25T17:41:26.607Z"} + ''' + mock_request.return_value = mock_response(response_body, 201) + configuration = self.configuration + with OpenFgaClient(configuration) as api_client: + api_response = api_client.create_store( + CreateStoreRequest(name="test-store"), + options={} + ) + self.assertIsInstance(api_response, CreateStoreResponse) + self.assertEqual(api_response.id, '01YCP46JKYM8FJCQ37NMBYHE5X') + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores', + headers=ANY, + query_params=[], + post_params=[], + body={"name": "test-store"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_get_store(self, mock_request): + """Test case for get_store + + Get all stores # noqa: E501 + """ + response_body = ''' +{ + "id": "01YCP46JKYM8FJCQ37NMBYHE5X", + "name": "store1", + "created_at": "2022-07-25T21:15:37.524Z", + "updated_at": "2022-07-25T21:15:37.524Z" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_response = api_client.get_store( + options={} + ) + self.assertIsInstance(api_response, GetStoreResponse) + self.assertEqual(api_response.id, "01YCP46JKYM8FJCQ37NMBYHE5X") + self.assertEqual(api_response.name, "store1") + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_delete_store(self, mock_request): + """Test case for delete_store + + Get all stores # noqa: E501 + """ + mock_request.return_value = mock_response('', 201) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_client.delete_store( + options={} + ) + mock_request.assert_called_once_with( + 'DELETE', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X', + headers=ANY, + query_params=[], + body=None, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_read_authorization_models(self, mock_request): + """Test case for read_authorization_models + + Return all authorization models configured for the store # noqa: E501 + """ + response_body = ''' +{ + "authorization_models": [{ + "id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "schema_version":"1.1", + "type_definitions": [ + { + "type": "document", + "relations": { + "reader": { + "union": { + "child": [ + { + "this": {} + }, + { + "computedUserset": { + "object": "", + "relation": "writer" + } + } + ] + } + }, + "writer": { + "this": {} + } + } + } + ] + }], + "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with OpenFgaClient(configuration) as api_client: + + # Return a particular version of an authorization model + api_response = api_client.read_authorization_models( + options={} + ) + self.assertIsInstance(api_response, ReadAuthorizationModelsResponse) + type_definitions = [ + TypeDefinition( + type="document", + relations=dict( + reader=Userset( + union=Usersets( + child=[ + Userset(this=dict()), + Userset(computed_userset=ObjectRelation( + object="", + relation="writer", + )), + ], + ), + ), + writer=Userset( + this=dict(), + ), + ) + ) + ] + authorization_model = AuthorizationModel(id='01G5JAVJ41T49E9TT3SKVS7X1J', schema_version="1.1", + type_definitions=type_definitions) + self.assertEqual(api_response.authorization_models, [authorization_model]) + self.assertEqual(api_response.continuation_token, + "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==") + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_authorization_model(self, mock_request): + """Test case for write_authorization_model + + Create a new authorization model # noqa: E501 + """ + response_body = '{"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}' + mock_request.return_value = mock_response(response_body, 201) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + # example passing only required values which don't have defaults set + body = WriteAuthorizationModelRequest( + schema_version="1.1", + type_definitions=[ + TypeDefinition( + type="document", + relations=dict( + writer=Userset( + this=dict(), + ), + reader=Userset( + union=Usersets( + child=[ + Userset(this=dict()), + Userset(computed_userset=ObjectRelation( + object="", + relation="writer", + )), + ], + ), + ), + ) + ), + ], + ) + # Create a new authorization model + api_response = api_client.write_authorization_model( + body, + options={} + ) + self.assertIsInstance(api_response, WriteAuthorizationModelResponse) + expected_response = WriteAuthorizationModelResponse( + authorization_model_id='01G5JAVJ41T49E9TT3SKVS7X1J' + ) + self.assertEqual(api_response, expected_response) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models', + headers=ANY, + query_params=[], + post_params=[], + body={"schema_version": "1.1", "type_definitions": [{"type": "document", "relations": {"writer": {"this": { + }}, "reader": {"union": {"child": [{"this": {}}, {"computedUserset": {"object": "", "relation": "writer"}}]}}}}]}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_read_authorization_model(self, mock_request): + """Test case for read_authorization_model + + Return a particular version of an authorization model # noqa: E501 + """ + response_body = ''' +{ + "authorization_model": { + "id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "schema_version":"1.1", + "type_definitions": [ + { + "type": "document", + "relations": { + "reader": { + "union": { + "child": [ + { + "this": {} + }, + { + "computedUserset": { + "object": "", + "relation": "writer" + } + } + ] + } + }, + "writer": { + "this": {} + } + } + } + ] + } +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with OpenFgaClient(configuration) as api_client: + + # Return a particular version of an authorization model + api_response = api_client.read_authorization_model( + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + self.assertIsInstance(api_response, ReadAuthorizationModelResponse) + type_definitions = [ + TypeDefinition( + type="document", + relations=dict( + reader=Userset( + union=Usersets( + child=[ + Userset(this=dict()), + Userset(computed_userset=ObjectRelation( + object="", + relation="writer", + )), + ], + ), + ), + writer=Userset( + this=dict(), + ), + ) + ) + ] + authorization_model = AuthorizationModel(id='01G5JAVJ41T49E9TT3SKVS7X1J', schema_version="1.1", + type_definitions=type_definitions) + self.assertEqual(api_response.authorization_model, authorization_model) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_read_latest_authorization_model(self, mock_request): + """Test case for read_latest_authorization_model + + Return the latest authorization models configured for the store # noqa: E501 + """ + response_body = ''' +{ + "authorization_models": [{ + "id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "schema_version":"1.1", + "type_definitions": [ + { + "type": "document", + "relations": { + "reader": { + "union": { + "child": [ + { + "this": {} + }, + { + "computedUserset": { + "object": "", + "relation": "writer" + } + } + ] + } + }, + "writer": { + "this": {} + } + } + } + ] + }], + "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with OpenFgaClient(configuration) as api_client: + + # Return a particular version of an authorization model + api_response = api_client.read_latest_authorization_model( + options={} + ) + self.assertIsInstance(api_response, ReadAuthorizationModelResponse) + type_definitions = [ + TypeDefinition( + type="document", + relations=dict( + reader=Userset( + union=Usersets( + child=[ + Userset(this=dict()), + Userset(computed_userset=ObjectRelation( + object="", + relation="writer", + )), + ], + ), + ), + writer=Userset( + this=dict(), + ), + ) + ) + ] + authorization_model = AuthorizationModel(id='01G5JAVJ41T49E9TT3SKVS7X1J', schema_version="1.1", + type_definitions=type_definitions) + self.assertEqual(api_response.authorization_model, authorization_model) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models', + headers=ANY, + query_params=[('page_size', 1)], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_read_changes(self, mock_request): + """Test case for read_changes + + Return a list of all the tuple changes # noqa: E501 + """ + response_body = ''' +{ + "changes": [ + { + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b" + }, + "operation": "TUPLE_OPERATION_WRITE", + "timestamp": "2022-07-26T15:55:55.809Z" + } + ], + "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with OpenFgaClient(configuration) as api_client: + + # Return a particular version of an authorization model + api_response = api_client.read_changes( + ClientReadChangesRequest("document"), + options={"page_size": 1, "continuation_token": "abcdefg"} + ) + + self.assertIsInstance(api_response, ReadChangesResponse) + changes = TupleChange( + tuple_key=TupleKey(object="document:2021-budget", relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b"), + operation=TupleOperation.WRITE, + timestamp=datetime.fromisoformat("2022-07-26T15:55:55.809+00:00")) + read_changes = ReadChangesResponse( + continuation_token='eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==', + changes=[changes]) + self.assertEqual(api_response, read_changes) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/changes', + headers=ANY, + query_params=[('type', 'document'), ('page_size', 1), + ('continuation_token', 'abcdefg')], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_read(self, mock_request): + """Test case for read + + Get tuples from the store that matches a query, without following userset rewrite rules # noqa: E501 + """ + response_body = ''' + { + "tuples": [ + { + "key": { + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + "relation": "reader", + "object": "document:2021-budget" + }, + "timestamp": "2021-10-06T15:32:11.128Z" + } + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with OpenFgaClient(configuration) as api_client: + body = TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + api_response = api_client.read( + body=body, + options={"page_size": 50, "continuation_token": + "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="} + ) + self.assertIsInstance(api_response, ReadResponse) + key = TupleKey(user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation="reader", object="document:2021-budget") + timestamp = datetime.fromisoformat("2021-10-06T15:32:11.128+00:00") + expected_data = ReadResponse(tuples=[Tuple(key=key, timestamp=timestamp)]) + self.assertEqual(api_response, expected_data) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, + "page_size": 50, "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_read_empty_options(self, mock_request): + """Test case for read with empty options + + Get tuples from the store that matches a query, without following userset rewrite rules # noqa: E501 + """ + response_body = ''' + { + "tuples": [ + { + "key": { + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + "relation": "reader", + "object": "document:2021-budget" + }, + "timestamp": "2021-10-06T15:32:11.128Z" + } + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with OpenFgaClient(configuration) as api_client: + body = TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + api_response = api_client.read( + body=body, + options={} + ) + self.assertIsInstance(api_response, ReadResponse) + key = TupleKey(user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation="reader", object="document:2021-budget") + timestamp = datetime.fromisoformat("2021-10-06T15:32:11.128+00:00") + expected_data = ReadResponse(tuples=[Tuple(key=key, timestamp=timestamp)]) + self.assertEqual(api_response, expected_data) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write(self, mock_request): + """Test case for write + + Add tuples from the store with transaction enabled + """ + response_body = '{}' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + ], + ) + api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, {"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}, {"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_delete(self, mock_request): + """Test case for delete + + Delete tuples from the store with transaction enabled + """ + response_body = '{}' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + body = ClientWriteRequest( + deletes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + ], + ) + api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"deletes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_batch(self, mock_request): + """Test case for write + + Add tuples from the store with transaction disabled + """ + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{}', 200), + mock_response('{}', 200), + mock_response('{}', 200), + ] + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + ], + ) + transaction = WriteTransactionOpts( + disabled=True, max_per_chunk=1, max_parallel_requests=10) + response = api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "transaction": transaction} + ) + + self.assertEqual(response.deletes, None) + self.assertEqual(response.writes, + [ + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + success=True, + error=None), + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + success=True, + error=None), + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ), + success=True, + error=None) + ] + ) + self.assertEqual(mock_request.call_count, 4) + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_batch_min_parallel(self, mock_request): + """Test case for write + + Add tuples from the store with transaction disabled and minimum parallel request + """ + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{}', 200), + mock_response('{}', 200), + mock_response('{}', 200), + ] + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + ], + ) + transaction = WriteTransactionOpts( + disabled=True, max_per_chunk=1, max_parallel_requests=1) + response = api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "transaction": transaction} + ) + + self.assertEqual(response.deletes, None) + self.assertEqual(response.writes, + [ + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + success=True, + error=None), + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + success=True, + error=None), + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ), + success=True, + error=None) + ] + ) + self.assertEqual(mock_request.call_count, 4) + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_batch_larger_chunk(self, mock_request): + """Test case for write + + Add tuples from the store with transaction disabled and minimum parallel request + """ + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{}', 200), + mock_response('{}', 200), + ] + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + ], + ) + transaction = WriteTransactionOpts( + disabled=True, max_per_chunk=2, max_parallel_requests=2) + response = api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "transaction": transaction} + ) + + self.assertEqual(response.deletes, None) + self.assertEqual(response.writes, + [ + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + success=True, + error=None), + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + success=True, + error=None), + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ), + success=True, + error=None) + ] + ) + self.assertEqual(mock_request.call_count, 3) + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, { + "object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_batch_failed(self, mock_request): + """Test case for write + + Add tuples from the store with transaction disabled where one of the request failed + """ + response_body = ''' +{ + "code": "validation_error", + "message": "Generic validation error" +} + ''' + + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{}', 200), + ValidationException(http_resp=http_mock_response(response_body, 400)), + mock_response('{}', 200), + ] + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + ], + ) + transaction = WriteTransactionOpts( + disabled=True, max_per_chunk=1, max_parallel_requests=10) + response = api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "transaction": transaction} + ) + + self.assertEqual(response.deletes, None) + self.assertEqual(len(response.writes), 3) + self.assertEqual(response.writes[0], + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + success=True, + error=None)) + self.assertEqual(response.writes[1].tuple_key, + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + )) + self.assertFalse(response.writes[1].success) + self.assertIsInstance(response.writes[1].error, ValidationException) + self.assertIsInstance( + response.writes[1].error.parsed_exception, ValidationErrorMessageResponse) + self.assertEqual(response.writes[2], + ClientWriteSingleResponse( + tuple_key=ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ), + success=True, + error=None)) + self.assertEqual(mock_request.call_count, 4) + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_delete_batch(self, mock_request): + """Test case for delete + + Delete tuples from the store with transaction disabled but there is only 1 relationship tuple + """ + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{}', 200), + ] + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + body = ClientWriteRequest( + deletes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + ], + ) + transaction = WriteTransactionOpts( + disabled=True, max_per_chunk=1, max_parallel_requests=10) + api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "transaction": transaction} + ) + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"deletes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_tuples(self, mock_request): + """Test case for write tuples + + Add tuples from the store with transaction enabled + """ + response_body = '{}' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + api_client.write_tuples( + [ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + ], + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, {"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}, {"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_delete_tuples(self, mock_request): + """Test case for delete tuples + + Add tuples from the store with transaction enabled + """ + response_body = '{}' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + + api_client.delete_tuples( + [ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ), + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + ], + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write', + headers=ANY, + query_params=[], + post_params=[], + body={"deletes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, {"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}, {"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_batch_unauthorized(self, mock_request): + """Test case for write with 401 response + """ + + mock_request.side_effect = UnauthorizedException( + http_resp=http_mock_response('{}', 401) + ) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + with self.assertRaises(UnauthorizedException) as api_exception: + body = ClientWriteRequest( + writes=[ + ClientTuple( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + ], + ) + transaction = WriteTransactionOpts( + disabled=True, max_per_chunk=1, max_parallel_requests=10) + api_client.write( + body, + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "transaction": transaction} + ) + + self.assertIsInstance(api_exception.exception, UnauthorizedException) + mock_request.assert_called() + self.assertEqual(mock_request.call_count, 1) + + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_check(self, mock_request): + """Test case for check + + Check whether a user is authorized to access an object # noqa: E501 + """ + + # First, mock the response + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.return_value = mock_response(response_body, 200) + body = ClientCheckRequest( + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation="reader", + object="document:budget", + contextual_tuples=[ + ClientTuple( + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation="writer", + object="document:budget", + ), + ], + ) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_response = api_client.check( + body=body, + options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"} + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the right data + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", "relation": "reader", "object": "document:budget"}, + "contextual_tuples": {"tuple_keys": [{"user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", "relation": "writer", "object": "document:budget"}]}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_check_config_auth_model(self, mock_request): + """Test case for check + + Check whether a user is authorized to access an object and the auth model is already encoded in store # noqa: E501 + """ + + # First, mock the response + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.return_value = mock_response(response_body, 200) + body = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01GXSA8YR785C4FYS3C0RTG7B1" + with OpenFgaClient(configuration) as api_client: + api_response = api_client.check( + body=body, + options={} + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the right data + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_batch_check_single_request(self, mock_request): + """Test case for check with single request + + Check whether a user is authorized to access an object # noqa: E501 + """ + + # First, mock the response + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response(response_body, 200), + ] + body = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_response = api_client.batch_check( + body=[body], + options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"} + ) + self.assertIsInstance(api_response, list) + self.assertEqual(len(api_response), 1) + self.assertEqual(api_response[0].error, None) + self.assertTrue(api_response[0].allowed) + self.assertEqual(api_response[0].request, body) + # Make sure the API was called with the right data + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01GXSA8YR785C4FYS3C0RTG7B1', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_batch_check_multiple_request(self, mock_request): + """Test case for check with multiple request + + Check whether a user is authorized to access an object # noqa: E501 + """ + + # First, mock the response + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{"allowed": true, "resolution": "1234"}', 200), + mock_response('{"allowed": false, "resolution": "1234"}', 200), + mock_response('{"allowed": true, "resolution": "1234"}', 200), + ] + body1 = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + body2 = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ) + body3 = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_response = api_client.batch_check( + body=[body1, body2, body3], + options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + "max_parallel_requests": 2} + ) + self.assertIsInstance(api_response, list) + self.assertEqual(len(api_response), 3) + self.assertEqual(api_response[0].error, None) + self.assertTrue(api_response[0].allowed) + self.assertEqual(api_response[0].request, body1) + self.assertEqual(api_response[1].error, None) + self.assertFalse(api_response[1].allowed) + self.assertEqual(api_response[1].request, body2) + self.assertEqual(api_response[2].error, None) + self.assertTrue(api_response[2].allowed) + self.assertEqual(api_response[2].request, body3) + # Make sure the API was called with the right data + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01GXSA8YR785C4FYS3C0RTG7B1', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_batch_check_multiple_request_fail(self, mock_request): + """Test case for check with multiple request with one request failed + + Check whether a user is authorized to access an object # noqa: E501 + """ + response_body = ''' +{ + "code": "validation_error", + "message": "Generic validation error" +} + ''' + + # First, mock the response + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{"allowed": true, "resolution": "1234"}', 200), + ValidationException(http_resp=http_mock_response(response_body, 400)), + mock_response('{"allowed": false, "resolution": "1234"}', 200), + ] + body1 = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + body2 = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", + ) + body3 = ClientCheckRequest( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", + ) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_response = api_client.batch_check( + body=[body1, body2, body3], + options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + "max_parallel_requests": 2} + ) + self.assertIsInstance(api_response, list) + self.assertEqual(len(api_response), 3) + self.assertEqual(api_response[0].error, None) + self.assertTrue(api_response[0].allowed) + self.assertEqual(api_response[0].request, body1) + self.assertFalse(api_response[1].allowed) + self.assertEqual(api_response[1].request, body2) + self.assertIsInstance(api_response[1].error, ValidationException) + self.assertIsInstance( + api_response[1].error.parsed_exception, ValidationErrorMessageResponse) + self.assertEqual(api_response[2].error, None) + self.assertFalse(api_response[2].allowed) + self.assertEqual(api_response[2].request, body3) + # Make sure the API was called with the right data + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01GXSA8YR785C4FYS3C0RTG7B1', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_expand(self, mock_request): + """Test case for expand + + Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship # noqa: E501 + """ + response_body = '''{ + "tree": {"root": {"name": "document:budget#reader", "leaf": {"users": {"users": ["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]}}}}} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + body = ClientExpandRequest( + object="document:budget", + relation="reader", + ) + api_response = api_client.expand( + body=body, + options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"} + ) + self.assertIsInstance(api_response, ExpandResponse) + curUsers = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]) + leaf = Leaf(users=curUsers) + node = Node(name="document:budget#reader", leaf=leaf) + userTree = UsersetTree(node) + expected_response = ExpandResponse(userTree) + self.assertEqual(api_response, expected_response) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/expand', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:budget", "relation": "reader"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_list_objects(self, mock_request): + """Test case for list_objects + + List objects # noqa: E501 + """ + response_body = ''' +{ + "objects": [ + "document:abcd1234" + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + body = ClientListObjectsRequest( + type="document", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + # Get all stores + api_response = api_client.list_objects( + body, options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}) + self.assertIsInstance(api_response, ListObjectsResponse) + self.assertEqual(api_response.objects, ['document:abcd1234']) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-objects', + headers=ANY, + query_params=[], + post_params=[], + body={'authorization_model_id': '01GXSA8YR785C4FYS3C0RTG7B1', + 'type': 'document', 'relation': 'reader', 'user': 'user:81684243-9356-4421-8fbf-a4f8d36aa31b'}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_list_objects_contextual_tuples(self, mock_request): + """Test case for list_objects + + List objects # noqa: E501 + """ + response_body = ''' +{ + "objects": [ + "document:abcd1234" + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + body = ClientListObjectsRequest( + type="document", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + contextual_tuples=[ + ClientTuple( + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation="writer", + object="document:budget", + ), + ], + ) + # Get all stores + api_response = api_client.list_objects( + body, options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}) + self.assertIsInstance(api_response, ListObjectsResponse) + self.assertEqual(api_response.objects, ['document:abcd1234']) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-objects', + headers=ANY, + query_params=[], + post_params=[], + body={'authorization_model_id': '01GXSA8YR785C4FYS3C0RTG7B1', + 'type': 'document', 'relation': 'reader', 'user': 'user:81684243-9356-4421-8fbf-a4f8d36aa31b', + 'contextual_tuples': {'tuple_keys': [{'object': 'document:budget', 'relation': 'writer', 'user': 'user:81684243-9356-4421-8fbf-a4f8d36aa31b'}]}}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_list_relations(self, mock_request): + """Test case for list relations + + Check whether a user is authorized to access an object # noqa: E501 + """ + + # First, mock the response + mock_request.side_effect = [ + mock_response('{}', 200), + mock_response('{"allowed": true, "resolution": "1234"}', 200), + mock_response('{"allowed": false, "resolution": "1234"}', 200), + mock_response('{"allowed": true, "resolution": "1234"}', 200), + ] + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_response = api_client.list_relations( + body=ClientListRelationsRequest(user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relations=["reader", "owner", "viewer"], + object="document:2021-budget"), + options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"} + ) + self.assertEqual(api_response, ["reader", "viewer"]) + + # Make sure the API was called with the right data + mock_request.assert_any_call( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01GXSA8YR785C4FYS3C0RTG7B1', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "owner", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + mock_request.assert_any_call( + 'POST', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "viewer", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_list_relations_unauthorized(self, mock_request): + """Test case for list relations with 401 response + """ + + mock_request.side_effect = UnauthorizedException( + http_resp=http_mock_response('{}', 401) + ) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + with self.assertRaises(UnauthorizedException) as api_exception: + api_client.list_relations( + body=ClientListRelationsRequest(user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relations=["reader", "owner", "viewer"], + object="document:2021-budget"), + options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"} + ) + + self.assertIsInstance(api_exception.exception, UnauthorizedException) + mock_request.assert_called() + self.assertEqual(mock_request.call_count, 1) + + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01GXSA8YR785C4FYS3C0RTG7B1', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_read_assertions(self, mock_request): + """Test case for read assertions + + """ + response_body = ''' +{ + "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "assertions": [ + { + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:anne" + }, + "expectation": true + } + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with OpenFgaClient(configuration) as api_client: + api_response = api_client.read_assertions( + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + self.assertEqual(api_response, ReadAssertionsResponse( + authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", + assertions=[Assertion( + tuple_key=TupleKey(object="document:2021-budget", relation="reader", + user="user:anne"), + expectation=True, + )] + )) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_write_assertions(self, mock_request): + """Test case for write assertions + + Get all stores # noqa: E501 + """ + mock_request.return_value = mock_response('', 204) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_client.write_assertions( + [ClientAssertion(user="user:anne", relation="reader", + object="document:2021-budget", expectation=True)], + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + mock_request.assert_called_once_with( + 'PUT', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + body={"assertions": [{"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:anne"}, "expectation": True}]}, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_set_store_id(self, mock_request): + """Test case for write assertions + + Get all stores # noqa: E501 + """ + mock_request.return_value = mock_response('', 204) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_client.set_store_id("01YCP46JKYM8FJCQ37NMBYHE5Y") + + api_client.write_assertions( + [ClientAssertion(user="user:anne", relation="reader", + object="document:2021-budget", expectation=True)], + options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} + ) + self.assertEqual(api_client.get_store_id(), "01YCP46JKYM8FJCQ37NMBYHE5Y") + mock_request.assert_called_once_with( + 'PUT', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5Y/assertions/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + body={"assertions": [{"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:anne"}, "expectation": True}]}, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_config_auth_model(self, mock_request): + """Test case for write assertions + + Get all stores # noqa: E501 + """ + mock_request.return_value = mock_response('', 204) + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01G5JAVJ41T49E9TT3SKVS7X1J" + with OpenFgaClient(configuration) as api_client: + api_client.write_assertions( + [ClientAssertion(user="user:anne", relation="reader", + object="document:2021-budget", expectation=True)], + options={} + ) + self.assertEqual(api_client.get_authorization_model_id(), "01G5JAVJ41T49E9TT3SKVS7X1J") + mock_request.assert_called_once_with( + 'PUT', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + body={"assertions": [{"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:anne"}, "expectation": True}]}, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + def test_update_auth_model(self, mock_request): + """Test case for write assertions + + Get all stores # noqa: E501 + """ + mock_request.return_value = mock_response('', 204) + configuration = self.configuration + configuration.store_id = store_id + configuration.authorization_model_id = "01G5JAVJ41T49E9TT3SKVS7X1J" + with OpenFgaClient(configuration) as api_client: + api_client.set_authorization_model_id("01G5JAVJ41T49E9TT3SKVS7X2J") + + api_client.write_assertions( + [ClientAssertion(user="user:anne", relation="reader", + object="document:2021-budget", expectation=True)], + options={} + ) + mock_request.assert_called_once_with( + 'PUT', + 'http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X2J', + headers=ANY, + body={"assertions": [{"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:anne"}, "expectation": True}]}, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + def test_configuration_store_id_invalid(self): + """ + Test whether ApiValueError is raised if host has query + """ + configuration = ClientConfiguration( + api_host='localhost', + api_scheme='http', + store_id="abcd" + ) + self.assertRaises(FgaValidationException, configuration.is_valid) + + def test_configuration_authorization_model_id_invalid(self): + """ + Test whether ApiValueError is raised if host has query + """ + configuration = ClientConfiguration( + api_host='localhost', + api_scheme='http', + store_id="01H15K9J85050XTEDPVM8DJM78", + authorization_model_id="abcd" + ) + self.assertRaises(FgaValidationException, configuration.is_valid) diff --git a/test/test_credentials_sync.py b/test/test_credentials_sync.py new file mode 100644 index 0000000..b69a23b --- /dev/null +++ b/test/test_credentials_sync.py @@ -0,0 +1,251 @@ +# coding: utf-8 + +""" + Python SDK for OpenFGA + + API version: 0.1 + Website: https://openfga.dev + Documentation: https://openfga.dev/docs + Support: https://discord.gg/8naAwJfWN6 + License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) + + NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +""" + +from unittest import IsolatedAsyncioTestCase + +from mock import patch +from datetime import datetime, timedelta + +import openfga_sdk.sync as openfga_sdk +import urllib3 + +from openfga_sdk.sync import rest +from openfga_sdk.sync.credentials import CredentialConfiguration, Credentials +from openfga_sdk.configuration import Configuration +from openfga_sdk.exceptions import AuthenticationError + + +# Helper function to construct mock response +def mock_response(body, status): + headers = urllib3.response.HTTPHeaderDict({ + 'content-type': 'application/json' + }) + obj = urllib3.HTTPResponse( + body, + headers, + status, + preload_content=False + ) + return rest.RESTResponse(obj, obj.data) + + +class TestCredentials(IsolatedAsyncioTestCase): + """Credentials unit test""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_bad_method(self): + """ + Check whether assertion is raised if method is not allowed + """ + credential = Credentials("bad") + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_method_none(self): + """ + Test credential with method none is valid + """ + credential = Credentials("none") + credential.validate_credentials_config() + self.assertEqual(credential.method, 'none') + + def test_method_default(self): + """ + Test credential with not method is default to none + """ + credential = Credentials() + credential.validate_credentials_config() + self.assertEqual(credential.method, 'none') + + def test_configuration_api_token(self): + """ + Test credential with method api_token and appropriate configuration is valid + """ + credential = Credentials( + method="api_token", configuration=CredentialConfiguration(api_token='ABCDEFG')) + credential.validate_credentials_config() + self.assertEqual(credential.method, 'api_token') + self.assertEqual(credential.configuration.api_token, 'ABCDEFG') + + def test_configuration_api_token_missing_configuration(self): + """ + Test credential with method api_token but configuration is not specified + """ + credential = Credentials(method="api_token") + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_configuration_api_token_missing_token(self): + """ + Test credential with method api_token but configuration is missing token + """ + credential = Credentials(method="api_token", configuration=CredentialConfiguration()) + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_configuration_api_token_empty_token(self): + """ + Test credential with method api_token but configuration has empty token + """ + credential = Credentials( + method="api_token", configuration=CredentialConfiguration(api_token='')) + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_configuration_client_credentials(self): + """ + Test credential with method client_credentials and appropriate configuration is valid + """ + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration(client_id='myclientid', + client_secret='mysecret', api_issuer='www.testme.com', api_audience='myaudience')) + credential.validate_credentials_config() + self.assertEqual(credential.method, 'client_credentials') + + def test_configuration_client_credentials_missing_config(self): + """ + Test credential with method client_credentials and configuration is missing + """ + credential = Credentials(method="client_credentials") + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_configuration_client_credentials_missing_client_id(self): + """ + Test credential with method client_credentials and configuration is missing client id + """ + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration( + client_secret='mysecret', api_issuer='www.testme.com', api_audience='myaudience')) + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_configuration_client_credentials_missing_client_secret(self): + """ + Test credential with method client_credentials and configuration is missing client secret + """ + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration(client_id='myclientid', + api_issuer='www.testme.com', api_audience='myaudience')) + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_configuration_client_credentials_missing_api_issuer(self): + """ + Test credential with method client_credentials and configuration is missing api issuer + """ + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration(client_id='myclientid', + client_secret='mysecret', api_audience='myaudience')) + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_configuration_client_credentials_missing_api_audience(self): + """ + Test credential with method client_credentials and configuration is missing api audience + """ + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration(client_id='myclientid', + client_secret='mysecret', api_issuer='www.testme.com')) + with self.assertRaises(openfga_sdk.ApiValueError): + credential.validate_credentials_config() + + def test_get_authentication_header(self): + """ + Test getting authentication header when method is none + """ + credential = Credentials() + auth_header = credential.get_authentication_header(None) + self.assertEqual(auth_header, {}) + + def test_get_authentication_api_token(self): + """ + Test getting authentication header when method is api token + """ + credential = Credentials( + method="api_token", configuration=CredentialConfiguration(api_token='ABCDEFG')) + auth_header = credential.get_authentication_header(None) + self.assertEqual(auth_header, {'Authorization': 'Bearer ABCDEFG'}) + + def test_get_authentication_valid_client_credentials(self): + """ + Test getting authentication header when method is client credentials + """ + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration(client_id='myclientid', + client_secret='mysecret', api_issuer='www.testme.com', api_audience='myaudience')) + credential._access_token = 'XYZ123' + credential._access_expiry_time = datetime.now() + timedelta(seconds=60) + auth_header = credential.get_authentication_header(None) + self.assertEqual(auth_header, {'Authorization': 'Bearer XYZ123'}) + + @patch.object(rest.RESTClientObject, 'request') + def test_get_authentication_obtain_client_credentials(self, mock_request): + """ + Test getting authentication header when method is client credential and we need to obtain token + """ + response_body = ''' +{ + "expires_in": 120, + "access_token": "AABBCCDD" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration(client_id='myclientid', + client_secret='mysecret', api_issuer='www.testme.com', api_audience='myaudience')) + client = rest.RESTClientObject(Configuration()) + current_time = datetime.now() + auth_header = credential.get_authentication_header(client) + self.assertEqual(auth_header, {'Authorization': 'Bearer AABBCCDD'}) + self.assertEqual(credential._access_token, 'AABBCCDD') + self.assertGreaterEqual(credential._access_expiry_time, + current_time + timedelta(seconds=int(120))) + expected_header = urllib3.response.HTTPHeaderDict( + {'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk (python) 0.2.1'}) + mock_request.assert_called_once_with( + 'POST', + 'https://www.testme.com/oauth/token', + headers=expected_header, + query_params=None, post_params=None, _preload_content=True, _request_timeout=None, + body={"client_id": "myclientid", "client_secret": "mysecret", + "audience": "myaudience", "grant_type": "client_credentials"} + ) + client.close() + + @patch.object(rest.RESTClientObject, 'request') + def test_get_authentication_obtain_client_credentials_failed(self, mock_request): + """ + Test getting authentication header when method is client credential and we fail to obtain token + """ + response_body = ''' +{ + "reason": "Unauthorized" +} + ''' + mock_request.return_value = mock_response(response_body, 403) + + credential = Credentials(method="client_credentials", + configuration=CredentialConfiguration(client_id='myclientid', + client_secret='mysecret', api_issuer='www.testme.com', api_audience='myaudience')) + client = rest.RESTClientObject(Configuration()) + with self.assertRaises(AuthenticationError): + credential.get_authentication_header(client) + client.close() diff --git a/test/test_open_fga_api_sync.py b/test/test_open_fga_api_sync.py new file mode 100644 index 0000000..ebf9752 --- /dev/null +++ b/test/test_open_fga_api_sync.py @@ -0,0 +1,1235 @@ +# coding: utf-8 + +""" + Python SDK for OpenFGA + + API version: 0.1 + Website: https://openfga.dev + Documentation: https://openfga.dev/docs + Support: https://discord.gg/8naAwJfWN6 + License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) + + NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +""" + +import unittest +from unittest.mock import ANY +from unittest import IsolatedAsyncioTestCase +from mock import patch +from datetime import datetime + +import urllib3 + +import openfga_sdk.sync as openfga_sdk +from openfga_sdk.sync import rest +from openfga_sdk.sync import open_fga_api +from openfga_sdk.sync.credentials import CredentialConfiguration, Credentials +from openfga_sdk.exceptions import FgaValidationException, ApiValueError, NotFoundException, RateLimitExceededError, ServiceException, ValidationException, FGA_REQUEST_ID +from openfga_sdk.models.assertion import Assertion +from openfga_sdk.models.authorization_model import AuthorizationModel +from openfga_sdk.models.check_request import CheckRequest +from openfga_sdk.models.check_response import CheckResponse +from openfga_sdk.models.create_store_request import CreateStoreRequest +from openfga_sdk.models.create_store_response import CreateStoreResponse +from openfga_sdk.models.error_code import ErrorCode +from openfga_sdk.models.expand_request import ExpandRequest +from openfga_sdk.models.expand_response import ExpandResponse +from openfga_sdk.models.get_store_response import GetStoreResponse +from openfga_sdk.models.internal_error_code import InternalErrorCode +from openfga_sdk.models.internal_error_message_response import InternalErrorMessageResponse +from openfga_sdk.models.leaf import Leaf +from openfga_sdk.models.list_objects_request import ListObjectsRequest +from openfga_sdk.models.list_objects_response import ListObjectsResponse +from openfga_sdk.models.list_stores_response import ListStoresResponse +from openfga_sdk.models.node import Node +from openfga_sdk.models.not_found_error_code import NotFoundErrorCode +from openfga_sdk.models.object_relation import ObjectRelation +from openfga_sdk.models.path_unknown_error_message_response import PathUnknownErrorMessageResponse +from openfga_sdk.models.read_assertions_response import ReadAssertionsResponse +from openfga_sdk.models.read_authorization_model_response import ReadAuthorizationModelResponse +from openfga_sdk.models.read_changes_response import ReadChangesResponse +from openfga_sdk.models.read_request import ReadRequest +from openfga_sdk.models.read_response import ReadResponse +from openfga_sdk.models.store import Store +from openfga_sdk.models.tuple import Tuple +from openfga_sdk.models.tuple_change import TupleChange +from openfga_sdk.models.tuple_key import TupleKey +from openfga_sdk.models.tuple_keys import TupleKeys +from openfga_sdk.models.tuple_operation import TupleOperation +from openfga_sdk.models.type_definition import TypeDefinition +from openfga_sdk.models.users import Users +from openfga_sdk.models.userset import Userset +from openfga_sdk.models.userset_tree import UsersetTree +from openfga_sdk.models.usersets import Usersets +from openfga_sdk.models.validation_error_message_response import ValidationErrorMessageResponse +from openfga_sdk.models.write_assertions_request import WriteAssertionsRequest +from openfga_sdk.models.write_authorization_model_request import WriteAuthorizationModelRequest +from openfga_sdk.models.write_authorization_model_response import WriteAuthorizationModelResponse +from openfga_sdk.models.write_request import WriteRequest + +store_id = '01H0H015178Y2V4CX10C2KGHF4' +request_id = 'x1y2z3' + +# Helper function to construct mock response + + +def http_mock_response(body, status): + headers = urllib3.response.HTTPHeaderDict({ + 'content-type': 'application/json', + 'Fga-Request-Id': request_id + }) + return urllib3.HTTPResponse( + body.encode('utf-8'), + headers, + status, + preload_content=False + ) + + +def mock_response(body, status): + obj = http_mock_response(body, status) + return rest.RESTResponse(obj, obj.data) + + +class TestOpenFgaApiSync(IsolatedAsyncioTestCase): + """openfga_sdk.sync.OpenFgaApi unit test stubs""" + + def setUp(self): + self.configuration = openfga_sdk.Configuration( + api_scheme='http', + api_host="api.fga.example", + ) + + def tearDown(self): + pass + + @patch.object(rest.RESTClientObject, 'request') + async def test_check(self, mock_request): + """Test case for check + + Check whether a user is authorized to access an object # noqa: E501 + """ + + # First, mock the response + response_body = '{"allowed": true, "resolution": "1234"}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + authorization_model_id="01GXSA8YR785C4FYS3C0RTG7B1", + ) + api_response = api_instance.check( + body=body, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the right data + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", + "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + async def test_create_store(self, mock_request): + """Test case for create_store + + Create a store # noqa: E501 + """ + response_body = '''{ + "id": "01YCP46JKYM8FJCQ37NMBYHE5X", + "name": "test_store", + "created_at": "2022-07-25T17:41:26.607Z", + "updated_at": "2022-07-25T17:41:26.607Z"} + ''' + mock_request.return_value = mock_response(response_body, 201) + + configuration = self.configuration + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CreateStoreRequest( + name="test-store", + ) + api_response = api_instance.create_store( + body=body, + ) + self.assertIsInstance(api_response, CreateStoreResponse) + self.assertEqual(api_response.id, '01YCP46JKYM8FJCQ37NMBYHE5X') + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores', + headers=ANY, + query_params=[], + post_params=[], + body={"name": "test-store"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + async def test_delete_store(self, mock_request): + """Test case for delete_store + + Delete a store # noqa: E501 + """ + response_body = '' + mock_request.return_value = mock_response(response_body, 201) + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + api_instance.delete_store() + mock_request.assert_called_once_with( + 'DELETE', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4', + headers=ANY, + query_params=[], + body=None, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + async def test_expand(self, mock_request): + """Test case for expand + + Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship # noqa: E501 + """ + response_body = '''{ + "tree": {"root": {"name": "document:budget#reader", "leaf": {"users": {"users": ["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]}}}}} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = ExpandRequest( + tuple_key=TupleKey( + object="document:budget", + relation="reader", + ), + authorization_model_id="01GXSA8YR785C4FYS3C0RTG7B1", + ) + api_response = api_instance.expand( + body=body, + ) + self.assertIsInstance(api_response, ExpandResponse) + curUsers = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]) + leaf = Leaf(users=curUsers) + node = Node(name="document:budget#reader", leaf=leaf) + userTree = UsersetTree(node) + expected_response = ExpandResponse(userTree) + self.assertEqual(api_response, expected_response) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/expand', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:budget", "relation": "reader"}, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + async def test_get_store(self, mock_request): + """Test case for get_store + + Get a store # noqa: E501 + """ + response_body = '''{ + "id": "01H0H015178Y2V4CX10C2KGHF4", + "name": "test_store", + "created_at": "2022-07-25T20:45:10.485Z", + "updated_at": "2022-07-25T20:45:10.485Z" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + # Get a store + api_response = api_instance.get_store() + self.assertIsInstance(api_response, GetStoreResponse) + self.assertEqual(api_response.id, '01H0H015178Y2V4CX10C2KGHF4') + self.assertEqual(api_response.name, 'test_store') + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + async def test_list_objects(self, mock_request): + """Test case for list_objects + + List objects # noqa: E501 + """ + response_body = ''' +{ + "objects": [ + "document:abcd1234" + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = ListObjectsRequest( + authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", + type="document", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + # Get all stores + api_response = api_instance.list_objects(body) + self.assertIsInstance(api_response, ListObjectsResponse) + self.assertEqual(api_response.objects, ['document:abcd1234']) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/list-objects', + headers=ANY, + query_params=[], + post_params=[], + body={'authorization_model_id': '01G5JAVJ41T49E9TT3SKVS7X1J', + 'type': 'document', 'relation': 'reader', 'user': 'user:81684243-9356-4421-8fbf-a4f8d36aa31b'}, + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + async def test_list_stores(self, mock_request): + """Test case for list_stores + + Get all stores # noqa: E501 + """ + response_body = ''' +{ + "stores": [ + { + "id": "01YCP46JKYM8FJCQ37NMBYHE5X", + "name": "store1", + "created_at": "2022-07-25T21:15:37.524Z", + "updated_at": "2022-07-25T21:15:37.524Z", + "deleted_at": "2022-07-25T21:15:37.524Z" + }, + { + "id": "01YCP46JKYM8FJCQ37NMBYHE6X", + "name": "store2", + "created_at": "2022-07-25T21:15:37.524Z", + "updated_at": "2022-07-25T21:15:37.524Z", + "deleted_at": "2022-07-25T21:15:37.524Z" + } + ], + "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + # Get all stores + api_response = api_instance.list_stores( + page_size=1, + continuation_token="continuation_token_example", + ) + self.assertIsInstance(api_response, ListStoresResponse) + self.assertEqual(api_response.continuation_token, + "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==") + store1 = Store( + id="01YCP46JKYM8FJCQ37NMBYHE5X", + name="store1", + created_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + updated_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + deleted_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + ) + store2 = Store( + id="01YCP46JKYM8FJCQ37NMBYHE6X", + name="store2", + created_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + updated_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + deleted_at=datetime.fromisoformat("2022-07-25T21:15:37.524+00:00"), + ) + + stores = [store1, store2] + self.assertEqual(api_response.stores, stores) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores', + headers=ANY, + query_params=[('page_size', 1), ('continuation_token', + 'continuation_token_example')], + _preload_content=ANY, + _request_timeout=None + ) + api_client.close() + + @patch.object(rest.RESTClientObject, 'request') + async def test_read(self, mock_request): + """Test case for read + + Get tuples from the store that matches a query, without following userset rewrite rules # noqa: E501 + """ + response_body = ''' + { + "tuples": [ + { + "key": { + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + "relation": "reader", + "object": "document:2021-budget" + }, + "timestamp": "2021-10-06T15:32:11.128Z" + } + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = ReadRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + page_size=50, + continuation_token="eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", + ) + api_response = api_instance.read( + body=body, + ) + self.assertIsInstance(api_response, ReadResponse) + key = TupleKey(user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation="reader", object="document:2021-budget") + timestamp = datetime.fromisoformat("2021-10-06T15:32:11.128+00:00") + expected_data = ReadResponse(tuples=[Tuple(key=key, timestamp=timestamp)]) + self.assertEqual(api_response, expected_data) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/read', + headers=ANY, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}, + "page_size": 50, "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_read_assertions(self, mock_request): + """Test case for read_assertions + + Read assertions for an authorization model ID # noqa: E501 + """ + response_body = ''' +{ + "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "assertions": [ + { + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b" + }, + "expectation": true + } + ] +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + api_response = api_instance.read_assertions( + "01G5JAVJ41T49E9TT3SKVS7X1J", + ) + self.assertIsInstance(api_response, ReadAssertionsResponse) + self.assertEqual(api_response.authorization_model_id, '01G5JAVJ41T49E9TT3SKVS7X1J') + assertion = Assertion( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + expectation=True, + ) + self.assertEqual(api_response.assertions, [assertion]) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/assertions/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_read_authorization_model(self, mock_request): + """Test case for read_authorization_model + + Return a particular version of an authorization model # noqa: E501 + """ + response_body = ''' +{ + "authorization_model": { + "id": "01G5JAVJ41T49E9TT3SKVS7X1J", + "schema_version":"1.1", + "type_definitions": [ + { + "type": "document", + "relations": { + "reader": { + "union": { + "child": [ + { + "this": {} + }, + { + "computedUserset": { + "object": "", + "relation": "writer" + } + } + ] + } + }, + "writer": { + "this": {} + } + } + } + ] + } +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # Return a particular version of an authorization model + api_response = api_instance.read_authorization_model( + "01G5JAVJ41T49E9TT3SKVS7X1J", + ) + self.assertIsInstance(api_response, ReadAuthorizationModelResponse) + type_definitions = [ + TypeDefinition( + type="document", + relations=dict( + reader=Userset( + union=Usersets( + child=[ + Userset(this=dict()), + Userset(computed_userset=ObjectRelation( + object="", + relation="writer", + )), + ], + ), + ), + writer=Userset( + this=dict(), + ), + ) + ) + ] + authorization_model = AuthorizationModel(id='01G5JAVJ41T49E9TT3SKVS7X1J', schema_version="1.1", + type_definitions=type_definitions) + self.assertEqual(api_response.authorization_model, authorization_model) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J', + headers=ANY, + query_params=[], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_read_changes(self, mock_request): + """Test case for read_changes + + Return a list of all the tuple changes # noqa: E501 + """ + response_body = ''' +{ + "changes": [ + { + "tuple_key": { + "object": "document:2021-budget", + "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b" + }, + "operation": "TUPLE_OPERATION_WRITE", + "timestamp": "2022-07-26T15:55:55.809Z" + } + ], + "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==" +} + ''' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # Return a particular version of an authorization model + api_response = api_instance.read_changes( + page_size=1, + continuation_token="abcdefg", + type="document" + ) + self.assertIsInstance(api_response, ReadChangesResponse) + changes = TupleChange( + tuple_key=TupleKey(object="document:2021-budget", relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b"), + operation=TupleOperation.WRITE, + timestamp=datetime.fromisoformat("2022-07-26T15:55:55.809+00:00")) + read_changes = ReadChangesResponse( + continuation_token='eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==', + changes=[changes]) + self.assertEqual(api_response, read_changes) + mock_request.assert_called_once_with( + 'GET', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/changes', + headers=ANY, + query_params=[('type', 'document'), ('page_size', 1), + ('continuation_token', 'abcdefg')], + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_write(self, mock_request): + """Test case for write + + Add tuples from the store # noqa: E501 + """ + response_body = '{}' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # example passing only required values which don't have defaults set + + body = WriteRequest( + writes=TupleKeys( + tuple_keys=[ + TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + ], + ), + authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", + ) + api_instance.write( + body, + ) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/write', + headers=ANY, + query_params=[], + post_params=[], + body={"writes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_write_delete(self, mock_request): + """Test case for write + + Delete tuples from the store # noqa: E501 + """ + response_body = '{}' + mock_request.return_value = mock_response(response_body, 200) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # example passing only required values which don't have defaults set + + body = WriteRequest( + deletes=TupleKeys( + tuple_keys=[ + TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ) + ], + ), + authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", + ) + api_instance.write( + body, + ) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/write', + headers=ANY, + query_params=[], + post_params=[], + body={"deletes": {"tuple_keys": [{"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}]}, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_write_assertions(self, mock_request): + """Test case for write_assertions + + Upsert assertions for an authorization model ID # noqa: E501 + """ + response_body = '' + mock_request.return_value = mock_response(response_body, 204) + configuration = self.configuration + configuration.store_id = store_id + # Enter a context with an instance of the API client + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # example passing only required values which don't have defaults set + body = WriteAssertionsRequest( + assertions=[ + Assertion( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + expectation=True, + ) + ], + ) + # Upsert assertions for an authorization model ID + api_instance.write_assertions( + authorization_model_id="xyz0123", + body=body, + ) + mock_request.assert_called_once_with( + 'PUT', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/assertions/xyz0123', + headers=ANY, + query_params=[], + post_params=[], + body={"assertions": [{"expectation": True, "tuple_key": { + "object": "document:2021-budget", "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}}]}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_write_authorization_model(self, mock_request): + """Test case for write_authorization_model + + Create a new authorization model # noqa: E501 + """ + response_body = '{"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}' + mock_request.return_value = mock_response(response_body, 201) + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # example passing only required values which don't have defaults set + body = WriteAuthorizationModelRequest( + schema_version="1.1", + type_definitions=[ + TypeDefinition( + type="document", + relations=dict( + writer=Userset( + this=dict(), + ), + reader=Userset( + union=Usersets( + child=[ + Userset(this=dict()), + Userset(computed_userset=ObjectRelation( + object="", + relation="writer", + )), + ], + ), + ), + ) + ), + ], + ) + # Create a new authorization model + api_response = api_instance.write_authorization_model( + body + ) + self.assertIsInstance(api_response, WriteAuthorizationModelResponse) + expected_response = WriteAuthorizationModelResponse( + authorization_model_id='01G5JAVJ41T49E9TT3SKVS7X1J' + ) + self.assertEqual(api_response, expected_response) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/authorization-models', + headers=ANY, + query_params=[], + post_params=[], + body={"schema_version": "1.1", "type_definitions": [{"type": "document", "relations": {"writer": {"this": { + }}, "reader": {"union": {"child": [{"this": {}}, {"computedUserset": {"object": "", "relation": "writer"}}]}}}}]}, + _preload_content=ANY, + _request_timeout=None + ) + + def test_default_scheme(self): + """ + Ensure default scheme is https + """ + configuration = openfga_sdk.Configuration( + api_host='localhost' + ) + self.assertEqual(configuration.api_scheme, 'https') + + def test_host_port(self): + """ + Ensure host has port will not raise error + """ + configuration = openfga_sdk.Configuration( + api_host='localhost:3000' + ) + self.assertEqual(configuration.api_host, 'localhost:3000') + + def test_configuration_missing_host(self): + """ + Test whether FgaValidationException is raised if configuration does not have host specified + """ + configuration = openfga_sdk.Configuration( + api_scheme='http' + ) + self.assertRaises(FgaValidationException, configuration.is_valid) + + def test_configuration_missing_scheme(self): + """ + Test whether FgaValidationException is raised if configuration does not have scheme specified + """ + configuration = openfga_sdk.Configuration( + api_host='localhost' + ) + configuration.api_scheme = None + self.assertRaises(FgaValidationException, configuration.is_valid) + + def test_configuration_bad_scheme(self): + """ + Test whether ApiValueError is raised if scheme is bad + """ + configuration = openfga_sdk.Configuration( + api_host='localhost', + api_scheme='foo' + ) + self.assertRaises(ApiValueError, configuration.is_valid) + + def test_configuration_bad_host(self): + """ + Test whether ApiValueError is raised if host is bad + """ + configuration = openfga_sdk.Configuration( + api_host='/', + api_scheme='foo' + ) + self.assertRaises(ApiValueError, configuration.is_valid) + + def test_configuration_has_path(self): + """ + Test whether ApiValueError is raised if host has path + """ + configuration = openfga_sdk.Configuration( + api_host='localhost/mypath', + api_scheme='http' + ) + self.assertRaises(ApiValueError, configuration.is_valid) + + def test_configuration_has_query(self): + """ + Test whether ApiValueError is raised if host has query + """ + configuration = openfga_sdk.Configuration( + api_host='localhost?mypath=foo', + api_scheme='http' + ) + self.assertRaises(ApiValueError, configuration.is_valid) + + def test_configuration_store_id_invalid(self): + """ + Test whether ApiValueError is raised if host has query + """ + configuration = openfga_sdk.Configuration( + api_host='localhost', + api_scheme='http', + store_id="abcd" + ) + self.assertRaises(FgaValidationException, configuration.is_valid) + + async def test_bad_configuration_read_authorization_model(self): + """ + Test whether FgaValidationException is raised for API (reading authorization models) + with configuration is having incorrect API scheme + """ + configuration = openfga_sdk.Configuration( + api_scheme='bad', + api_host="api.fga.example", + ) + configuration.store_id = 'xyz123' + # Enter a context with an instance of the API client + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # expects FgaValidationException to be thrown because api_scheme is bad + with self.assertRaises(ApiValueError): + api_instance.read_authorization_models( + page_size=1, + continuation_token="abcdefg" + ) + + async def test_configuration_missing_storeid(self): + """ + Test whether FgaValidationException is raised for API (reading authorization models) + required store ID but configuration is missing store ID + """ + configuration = openfga_sdk.Configuration( + api_scheme='http', + api_host="api.fga.example", + ) + # Notice the store_id is not set + # Enter a context with an instance of the API client + with openfga_sdk.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = open_fga_api.OpenFgaApi(api_client) + + # expects FgaValidationException to be thrown because store_id is not specified + with self.assertRaises(FgaValidationException): + api_instance.read_authorization_models( + page_size=1, + continuation_token="abcdefg" + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_400_error(self, mock_request): + """ + Test to ensure 400 errors are handled properly + """ + response_body = ''' +{ + "code": "validation_error", + "message": "Generic validation error" +} + ''' + mock_request.side_effect = ValidationException( + http_resp=http_mock_response(response_body, 400)) + + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + with self.assertRaises(ValidationException) as api_exception: + api_instance.check( + body=body, + ) + self.assertIsInstance(api_exception.exception.parsed_exception, + ValidationErrorMessageResponse) + self.assertEqual(api_exception.exception.parsed_exception.code, + ErrorCode.VALIDATION_ERROR) + self.assertEqual(api_exception.exception.parsed_exception.message, + "Generic validation error") + self.assertEqual(api_exception.exception.header.get(FGA_REQUEST_ID), request_id) + + @patch.object(rest.RESTClientObject, 'request') + async def test_404_error(self, mock_request): + """ + Test to ensure 404 errors are handled properly + """ + response_body = ''' +{ + "code": "undefined_endpoint", + "message": "Endpoint not enabled" +} + ''' + mock_request.side_effect = NotFoundException( + http_resp=http_mock_response(response_body, 404)) + + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + with self.assertRaises(NotFoundException) as api_exception: + api_instance.check( + body=body, + ) + self.assertIsInstance(api_exception.exception.parsed_exception, + PathUnknownErrorMessageResponse) + self.assertEqual(api_exception.exception.parsed_exception.code, + NotFoundErrorCode.UNDEFINED_ENDPOINT) + self.assertEqual(api_exception.exception.parsed_exception.message, + "Endpoint not enabled") + + @patch.object(rest.RESTClientObject, 'request') + async def test_429_error_no_retry(self, mock_request): + """ + Test to ensure 429 errors are handled properly. + For this case, there is no retry configured + """ + response_body = ''' +{ + "code": "rate_limit_exceeded", + "message": "Rate Limit exceeded" +} + ''' + mock_request.side_effect = RateLimitExceededError( + http_resp=http_mock_response(response_body, 429)) + + retry = openfga_sdk.configuration.RetryParams(0, 10) + configuration = self.configuration + configuration.store_id = store_id + configuration.retry_params = retry + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + with self.assertRaises(RateLimitExceededError) as api_exception: + api_instance.check( + body=body, + ) + self.assertIsInstance(api_exception.exception, RateLimitExceededError) + mock_request.assert_called() + self.assertEqual(mock_request.call_count, 1) + + @patch.object(rest.RESTClientObject, 'request') + async def test_429_error_first_error(self, mock_request): + """ + Test to ensure 429 errors are handled properly. + For this case, retry is configured and only the first time has error + """ + response_body = '{"allowed": true, "resolution": "1234"}' + error_response_body = ''' +{ + "code": "rate_limit_exceeded", + "message": "Rate Limit exceeded" +} + ''' + mock_request.side_effect = [RateLimitExceededError(http_resp=http_mock_response( + error_response_body, 429)), mock_response(response_body, 200)] + + retry = openfga_sdk.configuration.RetryParams(1, 10) + configuration = self.configuration + configuration.store_id = store_id + configuration.retry_params = retry + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + api_response = api_instance.check( + body=body, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + mock_request.assert_called() + self.assertEqual(mock_request.call_count, 2) + + @patch.object(rest.RESTClientObject, 'request') + async def test_500_error(self, mock_request): + """ + Test to ensure 500 errors are handled properly + """ + response_body = ''' +{ + "code": "internal_error", + "message": "Internal Server Error" +} + ''' + mock_request.side_effect = ServiceException( + http_resp=http_mock_response(response_body, 500)) + + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + with self.assertRaises(ServiceException) as api_exception: + api_instance.check( + body=body, + ) + self.assertIsInstance(api_exception.exception.parsed_exception, + InternalErrorMessageResponse) + self.assertEqual(api_exception.exception.parsed_exception.code, + InternalErrorCode.INTERNAL_ERROR) + self.assertEqual(api_exception.exception.parsed_exception.message, + "Internal Server Error") + + @patch.object(rest.RESTClientObject, 'request') + async def test_check_api_token(self, mock_request): + """Test case for API token + + Check whether API token is send when configuration specifies credential method as api_token + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + configuration.credentials = Credentials( + method='api_token', configuration=CredentialConfiguration(api_token='TOKEN1')) + with openfga_sdk.ApiClient(configuration) as api_client: + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + api_response = api_instance.check( + body=body, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the right data + expectedHeader = urllib3.response.HTTPHeaderDict( + {'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk python/0.2.1', 'Authorization': 'Bearer TOKEN1'}) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check', + headers=expectedHeader, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}}, + _preload_content=ANY, + _request_timeout=None + ) + + @patch.object(rest.RESTClientObject, 'request') + async def test_check_custom_header(self, mock_request): + """Test case for custom header + + Check whether custom header can be added + """ + + # First, mock the response + response_body = '{"allowed": true}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with openfga_sdk.ApiClient(configuration) as api_client: + api_client.set_default_header("Custom Header", "custom value") + api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( + tuple_key=TupleKey( + object="document:2021-budget", + relation="reader", + user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", + ), + ) + api_response = api_instance.check( + body=body, + ) + self.assertIsInstance(api_response, CheckResponse) + self.assertTrue(api_response.allowed) + # Make sure the API was called with the right data + expectedHeader = urllib3.response.HTTPHeaderDict( + {'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk python/0.2.1', 'Custom Header': 'custom value'}) + mock_request.assert_called_once_with( + 'POST', + 'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check', + headers=expectedHeader, + query_params=[], + post_params=[], + body={"tuple_key": {"object": "document:2021-budget", "relation": "reader", + "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b"}}, + _preload_content=ANY, + _request_timeout=None + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_validation.py b/test/test_validation.py new file mode 100644 index 0000000..196281d --- /dev/null +++ b/test/test_validation.py @@ -0,0 +1,48 @@ +# coding: utf-8 + +""" + Python SDK for OpenFGA + + API version: 0.1 + Website: https://openfga.dev + Documentation: https://openfga.dev/docs + Support: https://discord.gg/8naAwJfWN6 + License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) + + NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +""" + +import unittest + +from openfga_sdk.validation import is_well_formed_ulid_string + + +class TestValidation(unittest.TestCase): + """Test for validation""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_is_well_formed_ulid_string_valid_ulids(self): + self.assertEqual(is_well_formed_ulid_string( + "01H0GVCS1HCQM6SJRJ4A026FZ9"), True, "Should be True") + self.assertEqual(is_well_formed_ulid_string( + "01H0GVD9ACPFKGMWJV0Y93ZM7H"), True, "Should be True") + self.assertEqual(is_well_formed_ulid_string( + "01H0GVDH0FRZ4WAFED6T9KZYZR"), True, "Should be True") + self.assertEqual(is_well_formed_ulid_string( + "01H0GVDSW72AZ8QV3R0HJ91QBX"), True, "Should be True") + + def test_is_well_formed_ulid_string_invalid_ulids(self): + self.assertEqual(is_well_formed_ulid_string("abc"), False, "Should be False") + self.assertEqual(is_well_formed_ulid_string(123), False, "Should be False") + self.assertEqual(is_well_formed_ulid_string(None), False, "Should be False") + self.assertEqual(is_well_formed_ulid_string( + "01H0GVDSW72AZ8QV3R0HJ91QBXa"), False, "Should be False") + self.assertEqual(is_well_formed_ulid_string( + "b523ad13-8adb-4803-a6db-013ac50197ca"), False, "Should be False") + self.assertEqual(is_well_formed_ulid_string( + "9240BFC0-DA00-457B-A328-FC370A598D60"), False, "Should be False")