diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a7103b32 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.10 +WORKDIR /app/tests +COPY . /app +RUN pip install -r test-requirements.txt +CMD ["sh", "-c", "pytest"] \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt deleted file mode 100644 index f374f666..00000000 --- a/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -0.9.1 diff --git a/openfga_sdk/api/open_fga_api.py b/openfga_sdk/api/open_fga_api.py index 7a9e6460..983ef025 100644 --- a/openfga_sdk/api/open_fga_api.py +++ b/openfga_sdk/api/open_fga_api.py @@ -10,93 +10,279 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ -from openfga_sdk.api_client import ApiClient +from openfga_sdk.common.cookies import HttpCookies +from openfga_sdk.common.headers import HttpHeaders +from openfga_sdk.common.open_fga_api import ApiResponse, OpenFgaApiBase +from openfga_sdk.common.options import ( + CreateStoreRequestOptions, + DeleteStoreRequestOptions, + GetStoreRequestOptions, + ListStoresRequestOptions, + ReadAuthorizationModelRequestOptions, + ReadAuthorizationModelsRequestOptions, + WriteAuthorizationModelRequestOptions, + ReadChangesRequestOptions, + ReadRequestOptions, + WriteRequestOptions, +) +from openfga_sdk.common.rest import ( + RestClientRequestBody, + RestClientRequestFieldParameters, + RestClientRequestMethod, + RestClientRequestQueryParameters, +) from openfga_sdk.exceptions import ApiValueError, FgaValidationException -from openfga_sdk.oauth2 import OAuth2Client -from openfga_sdk.telemetry import Telemetry +from openfga_sdk.models.batch_check_request import BatchCheckRequest +from openfga_sdk.models.check_request import CheckRequest +from openfga_sdk.models.read_request import ReadRequest +from openfga_sdk.models.create_store_request import CreateStoreRequest +from openfga_sdk.models.write_authorization_model_request import ( + WriteAuthorizationModelRequest, +) +from openfga_sdk.models.write_request import WriteRequest +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + FactoryProtocol, + StoreRequestOptionsProtocol, + TelemetryAttributeProtocol, +) from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes -class OpenFgaApi: - """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech +class OpenFgaApi(OpenFgaApiBase): + async def __aenter__(self) -> "OpenFgaApi": + return self - Do not edit the class manually. - """ + async def __aexit__(self, exc_type, exc_value, traceback) -> None: + await self.close() - def __init__(self, api_client=None): - if api_client is None: - api_client = ApiClient() - self.api_client: ApiClient = api_client + async def close(self) -> None: + self.api_client.close() - self._oauth2_client = None - if api_client.configuration is not None: - credentials = api_client.configuration.credentials - if credentials is not None and credentials.method == "client_credentials": - self._oauth2_client = OAuth2Client( - credentials, api_client.configuration - ) + @property + def _factory(self) -> FactoryProtocol: + if self.factory is None: + return self._factory_async - self._telemetry = Telemetry() + return self.factory - async def __aenter__(self): - return self + async def batch_check( + self, + body: BatchCheckRequest | None = None, + options: StoreRequestOptionsProtocol | None = None, + ): + response_types = self.build_response_types(tuple([200, "BatchCheckResponse"])) - async def __aexit__(self, exc_type, exc_value, traceback): - await self.close() + body: RestClientRequestBody = body or RestClientRequestBody() + headers = HttpHeaders.for_json() + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + cookies = HttpCookies.from_options(options) + timeout = options.timeout + streaming = False + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "batch_check", + TelemetryAttributes.fga_client_request_store_id: self.configuration.store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + return await self.api_client.request( + path=f"/stores/{store_id}/batch-check", + method=RestClientRequestMethod("POST"), + body=body, + headers=headers, + query=query, + fields=fields, + cookies=cookies, + timeout=timeout, + response_types=response_types, + attributes=attributes, + streaming=streaming, + ) + + async def check( + self, + store_id: str, + body: CheckRequest | None = None, + options: StoreRequestOptionsProtocol | None = None, + ): + response_types = self.build_response_types(tuple([200, "CheckResponse"])) + + body: RestClientRequestBody = body or RestClientRequestBody() + headers = HttpHeaders.for_json() + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + cookies = HttpCookies.from_options(options) + timeout = options.timeout + streaming = False + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "check", + TelemetryAttributes.fga_client_request_store_id: self.configuration.store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + return await self.api_client.request( + path=f"/stores/{store_id}/check", + method=RestClientRequestMethod("POST"), + body=body, + headers=headers, + query=query, + fields=fields, + cookies=cookies, + timeout=timeout, + response_types=response_types, + attributes=attributes, + streaming=streaming, + ) + + async def create_store( + self, + body: CreateStoreRequest, + options: CreateStoreRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: CreateStoreRequestOptions = CreateStoreRequestOptions() | ( + options or CreateStoreRequestOptions() + ) + response_types = self.build_response_types(tuple([200, "CreateStoreResponse"])) + + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id + + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "create_store", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores", + method=RestClientRequestMethod("POST"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) + + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types + ) + + return api_response + + async def delete_store( + self, + options: DeleteStoreRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: DeleteStoreRequestOptions = DeleteStoreRequestOptions() | ( + options or DeleteStoreRequestOptions() + ) + response_types = self.build_response_types() + + body = None + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id + + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "delete_store", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}", + method=RestClientRequestMethod("DELETE"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) + + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types + ) - async def close(self): - await self.api_client.close() + return api_response - async def batch_check(self, body, **kwargs): - """Send a list of `check` operations in a single request + async def expand(self, body, **kwargs): + """Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship - The `BatchCheck` API functions nearly identically to `Check`, but instead of checking a single user-object relationship BatchCheck accepts a list of relationships to check and returns a map containing `BatchCheckItem` response for each check it received. An associated `correlation_id` is required for each check in the batch. This ID is used to correlate a check to the appropriate response. It is a string consisting of only alphanumeric characters or hyphens with a maximum length of 36 characters. This `correlation_id` is used to map the result of each check to the item which was checked, so it must be unique for each item in the batch. We recommend using a UUID or ULID as the `correlation_id`, but you can use whatever unique identifier you need as long as it matches this regex pattern: `^[\\w\\d-]{1,36}$` For more details on how `Check` functions, see the docs for `/check`. ### Examples #### A BatchCheckRequest ```json { \"checks\": [ { \"tuple_key\": { \"object\": \"document:2021-budget\" \"relation\": \"reader\", \"user\": \"user:anne\", }, \"contextual_tuples\": {...} \"context\": {} \"correlation_id\": \"01JA8PM3QM7VBPGB8KMPK8SBD5\" }, { \"tuple_key\": { \"object\": \"document:2021-budget\" \"relation\": \"reader\", \"user\": \"user:bob\", }, \"contextual_tuples\": {...} \"context\": {} \"correlation_id\": \"01JA8PMM6A90NV5ET0F28CYSZQ\" } ] } ``` Below is a possible response to the above request. Note that the result map's keys are the `correlation_id` values from the checked items in the request: ```json { \"result\": { \"01JA8PMM6A90NV5ET0F28CYSZQ\": { \"allowed\": false, \"error\": {\"message\": \"\"} }, \"01JA8PM3QM7VBPGB8KMPK8SBD5\": { \"allowed\": true, \"error\": {\"message\": \"\"} } } ``` + 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. 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. Each of these tuples may have an associated `condition`. 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`. ### Expand Request with Contextual Tuples Given the model ```python model schema 1.1 type user type folder relations define owner: [user] type document relations define parent: [folder] define viewer: [user] or writer define writer: [user] or owner from parent ``` and the initial tuples ```json [{ \"user\": \"user:bob\", \"relation\": \"owner\", \"object\": \"folder:1\" }] ``` To expand all `writers` of `document:1` when `document:1` is put in `folder:1`, the first call could be ```json { \"tuple_key\": { \"object\": \"document:1\", \"relation\": \"writer\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"folder:1\", \"relation\": \"parent\", \"object\": \"document:1\" } ] } } ``` this returns: ```json { \"tree\": { \"root\": { \"name\": \"document:1#writer\", \"union\": { \"nodes\": [ { \"name\": \"document:1#writer\", \"leaf\": { \"users\": { \"users\": [] } } }, { \"name\": \"document:1#writer\", \"leaf\": { \"tupleToUserset\": { \"tupleset\": \"document:1#parent\", \"computed\": [ { \"userset\": \"folder:1#owner\" } ] } } } ] } } } } ``` This tells us that the `owner` of `folder:1` may also be a writer. So our next call could be to find the `owners` of `folder:1` ```json { \"tuple_key\": { \"object\": \"folder:1\", \"relation\": \"owner\" } } ``` which gives ```json { \"tree\": { \"root\": { \"name\": \"folder:1#owner\", \"leaf\": { \"users\": { \"users\": [ \"user:bob\" ] } } } } } ``` - >>> thread = await api.batch_check(body) + >>> thread = await api.expand(body) :param body: (required) - :type body: BatchCheckRequest + :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: BatchCheckResponse + :rtype: ExpandResponse """ kwargs["_return_http_data_only"] = True - return await self.batch_check_with_http_info(body, **kwargs) + return await self.expand_with_http_info(body, **kwargs) - async def batch_check_with_http_info(self, body, **kwargs): - """Send a list of `check` operations in a single request + async def expand_with_http_info(self, body, **kwargs): + """Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship - The `BatchCheck` API functions nearly identically to `Check`, but instead of checking a single user-object relationship BatchCheck accepts a list of relationships to check and returns a map containing `BatchCheckItem` response for each check it received. An associated `correlation_id` is required for each check in the batch. This ID is used to correlate a check to the appropriate response. It is a string consisting of only alphanumeric characters or hyphens with a maximum length of 36 characters. This `correlation_id` is used to map the result of each check to the item which was checked, so it must be unique for each item in the batch. We recommend using a UUID or ULID as the `correlation_id`, but you can use whatever unique identifier you need as long as it matches this regex pattern: `^[\\w\\d-]{1,36}$` For more details on how `Check` functions, see the docs for `/check`. ### Examples #### A BatchCheckRequest ```json { \"checks\": [ { \"tuple_key\": { \"object\": \"document:2021-budget\" \"relation\": \"reader\", \"user\": \"user:anne\", }, \"contextual_tuples\": {...} \"context\": {} \"correlation_id\": \"01JA8PM3QM7VBPGB8KMPK8SBD5\" }, { \"tuple_key\": { \"object\": \"document:2021-budget\" \"relation\": \"reader\", \"user\": \"user:bob\", }, \"contextual_tuples\": {...} \"context\": {} \"correlation_id\": \"01JA8PMM6A90NV5ET0F28CYSZQ\" } ] } ``` Below is a possible response to the above request. Note that the result map's keys are the `correlation_id` values from the checked items in the request: ```json { \"result\": { \"01JA8PMM6A90NV5ET0F28CYSZQ\": { \"allowed\": false, \"error\": {\"message\": \"\"} }, \"01JA8PM3QM7VBPGB8KMPK8SBD5\": { \"allowed\": true, \"error\": {\"message\": \"\"} } } ``` + 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. 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. Each of these tuples may have an associated `condition`. 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`. ### Expand Request with Contextual Tuples Given the model ```python model schema 1.1 type user type folder relations define owner: [user] type document relations define parent: [folder] define viewer: [user] or writer define writer: [user] or owner from parent ``` and the initial tuples ```json [{ \"user\": \"user:bob\", \"relation\": \"owner\", \"object\": \"folder:1\" }] ``` To expand all `writers` of `document:1` when `document:1` is put in `folder:1`, the first call could be ```json { \"tuple_key\": { \"object\": \"document:1\", \"relation\": \"writer\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"folder:1\", \"relation\": \"parent\", \"object\": \"document:1\" } ] } } ``` this returns: ```json { \"tree\": { \"root\": { \"name\": \"document:1#writer\", \"union\": { \"nodes\": [ { \"name\": \"document:1#writer\", \"leaf\": { \"users\": { \"users\": [] } } }, { \"name\": \"document:1#writer\", \"leaf\": { \"tupleToUserset\": { \"tupleset\": \"document:1#parent\", \"computed\": [ { \"userset\": \"folder:1#owner\" } ] } } } ] } } } } ``` This tells us that the `owner` of `folder:1` may also be a writer. So our next call could be to find the `owners` of `folder:1` ```json { \"tuple_key\": { \"object\": \"folder:1\", \"relation\": \"owner\" } } ``` which gives ```json { \"tree\": { \"root\": { \"name\": \"folder:1#owner\", \"leaf\": { \"users\": { \"users\": [ \"user:bob\" ] } } } } } ``` - >>> thread = api.batch_check_with_http_info(body) + >>> thread = api.expand_with_http_info(body) :param body: (required) - :type body: BatchCheckRequest + :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. @@ -106,7 +292,7 @@ async def batch_check_with_http_info(self, body, **kwargs): :return: Returns the result object. If the method is called asynchronously, returns the request thread. - :rtype: tuple(BatchCheckResponse, status_code(int), headers(HTTPHeaderDict)) + :rtype: tuple(ExpandResponse, status_code(int), headers(HTTPHeaderDict)) """ local_var_params = locals() @@ -116,8 +302,6 @@ async def batch_check_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -129,17 +313,17 @@ async def batch_check_with_http_info(self, body, **kwargs): for key, val in local_var_params["kwargs"].items(): if key not in all_params: raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method batch_check" + f"Got an unexpected keyword argument '{key}' to method expand" ) local_var_params[key] = val del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( - "Missing the required parameter `body` when calling `batch_check`" + "Missing the required parameter `body` when calling `expand`" ) collection_formats = {} @@ -150,7 +334,7 @@ async def batch_check_with_http_info(self, body, **kwargs): if self.api_client._get_store_id() is None: raise ApiValueError( - "Store ID expected in api_client's configuration when calling `batch_check`" + "Store ID expected in api_client's configuration when calling `expand`" ) store_id = self.api_client._get_store_id() @@ -183,7 +367,7 @@ async def batch_check_with_http_info(self, body, **kwargs): auth_settings = [] response_types_map = { - 200: "BatchCheckResponse", + 200: "ExpandResponse", 400: "ValidationErrorMessageResponse", 401: "UnauthenticatedResponse", 403: "ForbiddenResponse", @@ -194,7 +378,7 @@ async def batch_check_with_http_info(self, body, **kwargs): } telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "batch_check", + TelemetryAttributes.fga_client_request_method: "expand", TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), TelemetryAttributes.fga_client_request_model_id: local_var_params.get( "authorization_model_id", "" @@ -207,7 +391,7 @@ async def batch_check_with_http_info(self, body, **kwargs): ) return await self.api_client.call_api( - "/stores/{store_id}/batch-check".replace("{store_id}", store_id), + "/stores/{store_id}/expand".replace("{store_id}", store_id), "POST", path_params, query_params, @@ -219,65 +403,100 @@ async def batch_check_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) - async def check(self, body, **kwargs): - """Check whether a user is authorized to access an object + async def get_store( + self, + options: GetStoreRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: GetStoreRequestOptions = GetStoreRequestOptions() | ( + options or GetStoreRequestOptions() + ) + response_types = self.build_response_types(tuple([200, "GetStoreResponse"])) + + body = None + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id + + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "get_store", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}", + method=RestClientRequestMethod("GET"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) + + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types + ) + + return api_response + + async def list_objects(self, body, **kwargs): + """List all objects of the given type that the user has a relation with - The Check API returns whether a given user has a relationship with a given object in a given store. The `user` field of the request can be a specific target, such as `user:anne`, or a userset (set of users) such as `group:marketing#member` or a type-bound public access `user:*`. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. 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. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. By default, the Check API caches results for a short time to optimize performance. You may specify a value of `HIGHER_CONSISTENCY` for the optional `consistency` parameter in the body to inform the server that higher conisistency is preferred at the expense of increased latency. Consideration should be given to the increased latency if requesting higher consistency. The response will return whether the relationship exists in the field `allowed`. Some exceptions apply, but in general, if a Check API responds with `{allowed: true}`, then you can expect the equivalent ListObjects query to return the object, and viceversa. For example, if `Check(user:anne, reader, document:2021-budget)` responds with `{allowed: true}`, then `ListObjects(user:anne, reader, document)` may include `document:2021-budget` in the response. ## Examples ### Querying with contextual tuples 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\" } ``` ### Querying usersets Some Checks will always return `true`, even without any tuples. For example, for the following authorization model ```python model schema 1.1 type user type document relations define reader: [user] ``` the following query ```json { \"tuple_key\": { \"user\": \"document:2021-budget#reader\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } } ``` will always return `{ \"allowed\": true }`. This is because usersets are self-defining: the userset `document:2021-budget#reader` will always have the `reader` relation with `document:2021-budget`. ### Querying usersets with difference in the model A Check for a userset can yield results that must be treated carefully if the model involves difference. For example, for the following authorization model ```python model schema 1.1 type user type group relations define member: [user] type document relations define blocked: [user] define reader: [group#member] but not blocked ``` the following query ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"group:finance\" }, { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, { \"user\": \"user:anne\", \"relation\": \"blocked\", \"object\": \"document:2021-budget\" } ] }, } ``` will return `{ \"allowed\": true }`, even though a specific user of the userset `group:finance#member` does not have the `reader` relationship with the given object. ### Requesting higher consistency By default, the Check API caches results for a short time to optimize performance. You may request higher consistency to inform the server that higher consistency should be preferred at the expense of increased latency. Care should be taken when requesting higher consistency due to the increased latency. ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"consistency\": \"HIGHER_CONSISTENCY\" } ``` + The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. By default, the Check API caches results for a short time to optimize performance. You may specify a value of `HIGHER_CONSISTENCY` for the optional `consistency` parameter in the body to inform the server that higher conisistency is preferred at the expense of increased latency. Consideration should be given to the increased latency if requesting higher consistency. 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. - >>> thread = await api.check(body) + >>> thread = await api.list_objects(body) :param body: (required) - :type body: CheckRequest + :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: CheckResponse + :rtype: ListObjectsResponse """ kwargs["_return_http_data_only"] = True - return await self.check_with_http_info(body, **kwargs) + return await self.list_objects_with_http_info(body, **kwargs) - async def check_with_http_info(self, body, **kwargs): - """Check whether a user is authorized to access an object + async def list_objects_with_http_info(self, body, **kwargs): + """List all objects of the given type that the user has a relation with - The Check API returns whether a given user has a relationship with a given object in a given store. The `user` field of the request can be a specific target, such as `user:anne`, or a userset (set of users) such as `group:marketing#member` or a type-bound public access `user:*`. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. 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. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. By default, the Check API caches results for a short time to optimize performance. You may specify a value of `HIGHER_CONSISTENCY` for the optional `consistency` parameter in the body to inform the server that higher conisistency is preferred at the expense of increased latency. Consideration should be given to the increased latency if requesting higher consistency. The response will return whether the relationship exists in the field `allowed`. Some exceptions apply, but in general, if a Check API responds with `{allowed: true}`, then you can expect the equivalent ListObjects query to return the object, and viceversa. For example, if `Check(user:anne, reader, document:2021-budget)` responds with `{allowed: true}`, then `ListObjects(user:anne, reader, document)` may include `document:2021-budget` in the response. ## Examples ### Querying with contextual tuples 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\" } ``` ### Querying usersets Some Checks will always return `true`, even without any tuples. For example, for the following authorization model ```python model schema 1.1 type user type document relations define reader: [user] ``` the following query ```json { \"tuple_key\": { \"user\": \"document:2021-budget#reader\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } } ``` will always return `{ \"allowed\": true }`. This is because usersets are self-defining: the userset `document:2021-budget#reader` will always have the `reader` relation with `document:2021-budget`. ### Querying usersets with difference in the model A Check for a userset can yield results that must be treated carefully if the model involves difference. For example, for the following authorization model ```python model schema 1.1 type user type group relations define member: [user] type document relations define blocked: [user] define reader: [group#member] but not blocked ``` the following query ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"group:finance\" }, { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, { \"user\": \"user:anne\", \"relation\": \"blocked\", \"object\": \"document:2021-budget\" } ] }, } ``` will return `{ \"allowed\": true }`, even though a specific user of the userset `group:finance#member` does not have the `reader` relationship with the given object. ### Requesting higher consistency By default, the Check API caches results for a short time to optimize performance. You may request higher consistency to inform the server that higher consistency should be preferred at the expense of increased latency. Care should be taken when requesting higher consistency due to the increased latency. ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"consistency\": \"HIGHER_CONSISTENCY\" } ``` + The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. By default, the Check API caches results for a short time to optimize performance. You may specify a value of `HIGHER_CONSISTENCY` for the optional `consistency` parameter in the body to inform the server that higher conisistency is preferred at the expense of increased latency. Consideration should be given to the increased latency if requesting higher consistency. 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. - >>> thread = api.check_with_http_info(body) + >>> thread = api.list_objects_with_http_info(body) :param body: (required) - :type body: CheckRequest + :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. @@ -287,7 +506,7 @@ async def check_with_http_info(self, body, **kwargs): :return: Returns the result object. If the method is called asynchronously, returns the request thread. - :rtype: tuple(CheckResponse, status_code(int), headers(HTTPHeaderDict)) + :rtype: tuple(ListObjectsResponse, status_code(int), headers(HTTPHeaderDict)) """ local_var_params = locals() @@ -297,8 +516,6 @@ async def check_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -310,17 +527,17 @@ async def check_with_http_info(self, body, **kwargs): for key, val in local_var_params["kwargs"].items(): if key not in all_params: raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method check" + f"Got an unexpected keyword argument '{key}' to method list_objects" ) local_var_params[key] = val del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( - "Missing the required parameter `body` when calling `check`" + "Missing the required parameter `body` when calling `list_objects`" ) collection_formats = {} @@ -331,7 +548,7 @@ async def check_with_http_info(self, body, **kwargs): if self.api_client._get_store_id() is None: raise ApiValueError( - "Store ID expected in api_client's configuration when calling `check`" + "Store ID expected in api_client's configuration when calling `list_objects`" ) store_id = self.api_client._get_store_id() @@ -364,7 +581,7 @@ async def check_with_http_info(self, body, **kwargs): auth_settings = [] response_types_map = { - 200: "CheckResponse", + 200: "ListObjectsResponse", 400: "ValidationErrorMessageResponse", 401: "UnauthenticatedResponse", 403: "ForbiddenResponse", @@ -375,7 +592,7 @@ async def check_with_http_info(self, body, **kwargs): } telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "check", + TelemetryAttributes.fga_client_request_method: "list_objects", TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), TelemetryAttributes.fga_client_request_model_id: local_var_params.get( "authorization_model_id", "" @@ -388,7 +605,7 @@ async def check_with_http_info(self, body, **kwargs): ) return await self.api_client.call_api( - "/stores/{store_id}/check".replace("{store_id}", store_id), + "/stores/{store_id}/list-objects".replace("{store_id}", store_id), "POST", path_params, query_params, @@ -400,65 +617,100 @@ async def check_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) - async def create_store(self, body, **kwargs): - """Create a store + async def list_stores( + self, + options: ListStoresRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: ListStoresRequestOptions = ListStoresRequestOptions() | ( + options or ListStoresRequestOptions() + ) + response_types = self.build_response_types(tuple([200, "ListStoresResponse"])) + + body = None + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id + + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "list_stores", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores", + method=RestClientRequestMethod("GET"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) + + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types + ) + + return api_response + + async def list_users(self, body, **kwargs): + """List the users matching the provided filter who have a certain relation to a particular type. - Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. + The ListUsers API returns a list of all the users of a specific type that have a relation to a given object. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related users in an array in the \"users\" field of the response. These results may include specific objects, usersets or type-bound public access. Each of these types of results is encoded in its own type and not represented as a string.In cases where a type-bound public access result is returned (e.g. `user:*`), it cannot be inferred that all subjects of that type have a relation to the object; it is possible that negations exist and checks should still be queried on individual subjects to ensure access to that document.The number of users in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_USERS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_USERS_MAX_RESULTS, whichever is hit first. The returned users will not be sorted, and therefore two identical calls may yield different sets of users. - >>> thread = await api.create_store(body) + >>> thread = await api.list_users(body) :param body: (required) - :type body: CreateStoreRequest + :type body: ListUsersRequest :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 + :rtype: ListUsersResponse """ kwargs["_return_http_data_only"] = True - return await self.create_store_with_http_info(body, **kwargs) + return await self.list_users_with_http_info(body, **kwargs) - async def create_store_with_http_info(self, body, **kwargs): - """Create a store + async def list_users_with_http_info(self, body, **kwargs): + """List the users matching the provided filter who have a certain relation to a particular type. - Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. + The ListUsers API returns a list of all the users of a specific type that have a relation to a given object. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related users in an array in the \"users\" field of the response. These results may include specific objects, usersets or type-bound public access. Each of these types of results is encoded in its own type and not represented as a string.In cases where a type-bound public access result is returned (e.g. `user:*`), it cannot be inferred that all subjects of that type have a relation to the object; it is possible that negations exist and checks should still be queried on individual subjects to ensure access to that document.The number of users in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_USERS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_USERS_MAX_RESULTS, whichever is hit first. The returned users will not be sorted, and therefore two identical calls may yield different sets of users. - >>> thread = api.create_store_with_http_info(body) + >>> thread = api.list_users_with_http_info(body) :param body: (required) - :type body: CreateStoreRequest + :type body: ListUsersRequest :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. @@ -468,7 +720,7 @@ async def create_store_with_http_info(self, body, **kwargs): :return: Returns the result object. If the method is called asynchronously, returns the request thread. - :rtype: tuple(CreateStoreResponse, status_code(int), headers(HTTPHeaderDict)) + :rtype: tuple(ListUsersResponse, status_code(int), headers(HTTPHeaderDict)) """ local_var_params = locals() @@ -478,8 +730,6 @@ async def create_store_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -491,10 +741,18 @@ async def create_store_with_http_info(self, body, **kwargs): for key, val in local_var_params["kwargs"].items(): if key not in all_params: raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method create_store" + f"Got an unexpected keyword argument '{key}' to method list_users" ) local_var_params[key] = val del local_var_params["kwargs"] + # verify the required parameter 'body' is set + if ( + self.configuration.client_side_validation + and local_var_params.get("body") is None + ): + raise ApiValueError( + "Missing the required parameter `body` when calling `list_users`" + ) collection_formats = {} @@ -502,6 +760,12 @@ async def create_store_with_http_info(self, body, **kwargs): store_id = None + if self.api_client._get_store_id() is None: + raise ApiValueError( + "Store ID expected in api_client's configuration when calling `list_users`" + ) + store_id = self.api_client._get_store_id() + query_params = [] header_params = dict(local_var_params.get("_headers", {})) @@ -531,7 +795,7 @@ async def create_store_with_http_info(self, body, **kwargs): auth_settings = [] response_types_map = { - 201: "CreateStoreResponse", + 200: "ListUsersResponse", 400: "ValidationErrorMessageResponse", 401: "UnauthenticatedResponse", 403: "ForbiddenResponse", @@ -542,7 +806,7 @@ async def create_store_with_http_info(self, body, **kwargs): } telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "create_store", + TelemetryAttributes.fga_client_request_method: "list_users", TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), TelemetryAttributes.fga_client_request_model_id: local_var_params.get( "authorization_model_id", "" @@ -555,7 +819,7 @@ async def create_store_with_http_info(self, body, **kwargs): ) return await self.api_client.call_api( - "/stores", + "/stores/{store_id}/list-users".replace("{store_id}", store_id), "POST", path_params, query_params, @@ -567,61 +831,103 @@ async def create_store_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) - async def delete_store(self, **kwargs): - """Delete a store + async def read( + self, + body: ReadRequest, + options: ReadRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: ReadRequestOptions = ReadRequestOptions() | ( + options or ReadRequestOptions() + ) + response_types = self.build_response_types(tuple([200, "ReadResponse"])) + + body = None + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id + + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "read", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}/read", + method=RestClientRequestMethod("GET"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) + + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types + ) + + return api_response + + async def read_assertions(self, authorization_model_id, **kwargs): + """Read assertions for an authorization model ID - Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. + The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. - >>> thread = await api.delete_store() + >>> thread = await 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: None + :rtype: ReadAssertionsResponse """ kwargs["_return_http_data_only"] = True - return await self.delete_store_with_http_info(**kwargs) + return await self.read_assertions_with_http_info( + authorization_model_id, **kwargs + ) - async def delete_store_with_http_info(self, **kwargs): - """Delete a store + async def read_assertions_with_http_info(self, authorization_model_id, **kwargs): + """Read assertions for an authorization model ID - Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. + The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. - >>> thread = api.delete_store_with_http_info() + >>> 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. @@ -631,18 +937,16 @@ async def delete_store_with_http_info(self, **kwargs): :return: Returns the result object. If the method is called asynchronously, returns the request thread. - :rtype: None + :rtype: tuple(ReadAssertionsResponse, status_code(int), headers(HTTPHeaderDict)) """ local_var_params = locals() - all_params = [] + all_params = ["authorization_model_id"] all_params.extend( [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -654,10 +958,18 @@ async def delete_store_with_http_info(self, **kwargs): for key, val in local_var_params["kwargs"].items(): if key not in all_params: raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method delete_store" + f"Got an unexpected keyword argument '{key}' to method read_assertions" ) local_var_params[key] = val del local_var_params["kwargs"] + # verify the required parameter 'authorization_model_id' is set + if ( + self.configuration.client_side_validation + and local_var_params.get("authorization_model_id") is None + ): + raise ApiValueError( + "Missing the required parameter `authorization_model_id` when calling `read_assertions`" + ) collection_formats = {} @@ -667,10 +979,15 @@ async def delete_store_with_http_info(self, **kwargs): if self.api_client._get_store_id() is None: raise ApiValueError( - "Store ID expected in api_client's configuration when calling `delete_store`" + "Store ID expected in api_client's configuration when calling `read_assertions`" ) 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" + ] + query_params = [] header_params = dict(local_var_params.get("_headers", {})) @@ -687,10 +1004,19 @@ async def delete_store_with_http_info(self, **kwargs): # Authentication setting auth_settings = [] - response_types_map = {} + response_types_map = { + 200: "ReadAssertionsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + } telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "delete_store", + TelemetryAttributes.fga_client_request_method: "read_assertions", TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), TelemetryAttributes.fga_client_request_model_id: local_var_params.get( "authorization_model_id", "" @@ -703,8 +1029,10 @@ async def delete_store_with_http_info(self, **kwargs): ) return await self.api_client.call_api( - "/stores/{store_id}".replace("{store_id}", store_id), - "DELETE", + "/stores/{store_id}/assertions/{authorization_model_id}".replace( + "{store_id}", store_id + ), + "GET", path_params, query_params, header_params, @@ -715,1998 +1043,205 @@ async def delete_store_with_http_info(self, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) - async def expand(self, body, **kwargs): - """Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship + async def read_authorization_model( + self, + authorization_model_id: str, + options: ReadAuthorizationModelRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: ReadAuthorizationModelRequestOptions = ( + ReadAuthorizationModelRequestOptions() + | (options or ReadAuthorizationModelRequestOptions()) + ) + response_types = self.build_response_types( + tuple([200, "ReadAuthorizationModelResponse"]) + ) - 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. 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. Each of these tuples may have an associated `condition`. 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`. ### Expand Request with Contextual Tuples Given the model ```python model schema 1.1 type user type folder relations define owner: [user] type document relations define parent: [folder] define viewer: [user] or writer define writer: [user] or owner from parent ``` and the initial tuples ```json [{ \"user\": \"user:bob\", \"relation\": \"owner\", \"object\": \"folder:1\" }] ``` To expand all `writers` of `document:1` when `document:1` is put in `folder:1`, the first call could be ```json { \"tuple_key\": { \"object\": \"document:1\", \"relation\": \"writer\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"folder:1\", \"relation\": \"parent\", \"object\": \"document:1\" } ] } } ``` this returns: ```json { \"tree\": { \"root\": { \"name\": \"document:1#writer\", \"union\": { \"nodes\": [ { \"name\": \"document:1#writer\", \"leaf\": { \"users\": { \"users\": [] } } }, { \"name\": \"document:1#writer\", \"leaf\": { \"tupleToUserset\": { \"tupleset\": \"document:1#parent\", \"computed\": [ { \"userset\": \"folder:1#owner\" } ] } } } ] } } } } ``` This tells us that the `owner` of `folder:1` may also be a writer. So our next call could be to find the `owners` of `folder:1` ```json { \"tuple_key\": { \"object\": \"folder:1\", \"relation\": \"owner\" } } ``` which gives ```json { \"tree\": { \"root\": { \"name\": \"folder:1#owner\", \"leaf\": { \"users\": { \"users\": [ \"user:bob\" ] } } } } } ``` + body = None + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id - >>> thread = await api.expand(body) + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") - :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 await self.expand_with_http_info(body, **kwargs) + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "read_authorization_model", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } - async def expand_with_http_info(self, body, **kwargs): - """Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) - 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. 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. Each of these tuples may have an associated `condition`. 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`. ### Expand Request with Contextual Tuples Given the model ```python model schema 1.1 type user type folder relations define owner: [user] type document relations define parent: [folder] define viewer: [user] or writer define writer: [user] or owner from parent ``` and the initial tuples ```json [{ \"user\": \"user:bob\", \"relation\": \"owner\", \"object\": \"folder:1\" }] ``` To expand all `writers` of `document:1` when `document:1` is put in `folder:1`, the first call could be ```json { \"tuple_key\": { \"object\": \"document:1\", \"relation\": \"writer\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"folder:1\", \"relation\": \"parent\", \"object\": \"document:1\" } ] } } ``` this returns: ```json { \"tree\": { \"root\": { \"name\": \"document:1#writer\", \"union\": { \"nodes\": [ { \"name\": \"document:1#writer\", \"leaf\": { \"users\": { \"users\": [] } } }, { \"name\": \"document:1#writer\", \"leaf\": { \"tupleToUserset\": { \"tupleset\": \"document:1#parent\", \"computed\": [ { \"userset\": \"folder:1#owner\" } ] } } } ] } } } } ``` This tells us that the `owner` of `folder:1` may also be a writer. So our next call could be to find the `owners` of `folder:1` ```json { \"tuple_key\": { \"object\": \"folder:1\", \"relation\": \"owner\" } } ``` which gives ```json { \"tree\": { \"root\": { \"name\": \"folder:1#owner\", \"leaf\": { \"users\": { \"users\": [ \"user:bob\" ] } } } } } ``` + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}/authorization-models/{authorization_model_id}", + method=RestClientRequestMethod("GET"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) - >>> thread = api.expand_with_http_info(body) + api_response: ApiResponseProtocol = ApiResponse() | api_client_response - :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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method expand" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `body` when calling `expand`" - ) - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `expand`" - ) - 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"] - ) - - # 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 - ), - ) - if content_types_list: - header_params["Content-Type"] = content_types_list - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ExpandResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "expand", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def get_store(self, **kwargs): - """Get a store - - Returns an OpenFGA store by its identifier - - >>> thread = await 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 await self.get_store_with_http_info(**kwargs) - - async def get_store_with_http_info(self, **kwargs): - """Get a store - - Returns an OpenFGA store by its identifier - - >>> 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method get_store" - ) - local_var_params[key] = val - del local_var_params["kwargs"] - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `get_store`" - ) - 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"] - ) - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "GetStoreResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "get_store", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def list_objects(self, body, **kwargs): - """List all objects of the given type that the user has a relation with - - The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. By default, the Check API caches results for a short time to optimize performance. You may specify a value of `HIGHER_CONSISTENCY` for the optional `consistency` parameter in the body to inform the server that higher conisistency is preferred at the expense of increased latency. Consideration should be given to the increased latency if requesting higher consistency. 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. - - >>> thread = await 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 await self.list_objects_with_http_info(body, **kwargs) - - async def list_objects_with_http_info(self, body, **kwargs): - """List all objects of the given type that the user has a relation with - - The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. By default, the Check API caches results for a short time to optimize performance. You may specify a value of `HIGHER_CONSISTENCY` for the optional `consistency` parameter in the body to inform the server that higher conisistency is preferred at the expense of increased latency. Consideration should be given to the increased latency if requesting higher consistency. 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. - - >>> 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method list_objects" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `body` when calling `list_objects`" - ) - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `list_objects`" - ) - 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"] - ) - - # 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 - ), - ) - if content_types_list: - header_params["Content-Type"] = content_types_list - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ListObjectsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "list_objects", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def list_stores(self, **kwargs): - """List all stores - - 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. - - >>> thread = await 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 await self.list_stores_with_http_info(**kwargs) - - async def list_stores_with_http_info(self, **kwargs): - """List all stores - - 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. - - >>> 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method list_stores" - ) - local_var_params[key] = val - del local_var_params["kwargs"] - - collection_formats = {} - - path_params = {} - - store_id = None - - query_params = [] - if local_var_params.get("page_size") is not None: - query_params.append(("page_size", local_var_params["page_size"])) - if local_var_params.get("continuation_token") is not None: - query_params.append( - ("continuation_token", local_var_params["continuation_token"]) - ) - - 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"] - ) - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ListStoresResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "list_stores", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def list_users(self, body, **kwargs): - """List the users matching the provided filter who have a certain relation to a particular type. - - The ListUsers API returns a list of all the users of a specific type that have a relation to a given object. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related users in an array in the \"users\" field of the response. These results may include specific objects, usersets or type-bound public access. Each of these types of results is encoded in its own type and not represented as a string.In cases where a type-bound public access result is returned (e.g. `user:*`), it cannot be inferred that all subjects of that type have a relation to the object; it is possible that negations exist and checks should still be queried on individual subjects to ensure access to that document.The number of users in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_USERS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_USERS_MAX_RESULTS, whichever is hit first. The returned users will not be sorted, and therefore two identical calls may yield different sets of users. - - >>> thread = await api.list_users(body) - - :param body: (required) - :type body: ListUsersRequest - :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: ListUsersResponse - """ - kwargs["_return_http_data_only"] = True - return await self.list_users_with_http_info(body, **kwargs) - - async def list_users_with_http_info(self, body, **kwargs): - """List the users matching the provided filter who have a certain relation to a particular type. - - The ListUsers API returns a list of all the users of a specific type that have a relation to a given object. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). 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. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related users in an array in the \"users\" field of the response. These results may include specific objects, usersets or type-bound public access. Each of these types of results is encoded in its own type and not represented as a string.In cases where a type-bound public access result is returned (e.g. `user:*`), it cannot be inferred that all subjects of that type have a relation to the object; it is possible that negations exist and checks should still be queried on individual subjects to ensure access to that document.The number of users in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_USERS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_USERS_MAX_RESULTS, whichever is hit first. The returned users will not be sorted, and therefore two identical calls may yield different sets of users. - - >>> thread = api.list_users_with_http_info(body) - - :param body: (required) - :type body: ListUsersRequest - :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(ListUsersResponse, 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method list_users" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `body` when calling `list_users`" - ) - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `list_users`" - ) - 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"] - ) - - # 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 - ), - ) - if content_types_list: - header_params["Content-Type"] = content_types_list - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ListUsersResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "list_users", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await self.api_client.call_api( - "/stores/{store_id}/list-users".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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def read(self, body, **kwargs): - """Get tuples from the store that matches a query, without following userset rewrite rules - - The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. The API doesn't guarantee order by any field. 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`). - - >>> thread = await 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 await self.read_with_http_info(body, **kwargs) - - async def read_with_http_info(self, body, **kwargs): - """Get tuples from the store that matches a query, without following userset rewrite rules - - The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. The API doesn't guarantee order by any field. 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`). - - >>> 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method read" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `body` when calling `read`" - ) - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `read`" - ) - 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"] - ) - - # 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 - ), - ) - if content_types_list: - header_params["Content-Type"] = content_types_list - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ReadResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "read", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def read_assertions(self, authorization_model_id, **kwargs): - """Read assertions for an authorization model ID - - The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. - - >>> thread = await 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 await self.read_assertions_with_http_info( - authorization_model_id, **kwargs - ) - - async def read_assertions_with_http_info(self, authorization_model_id, **kwargs): - """Read assertions for an authorization model ID - - The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. - - >>> 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method read_assertions" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `authorization_model_id` when calling `read_assertions`" - ) - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `read_assertions`" - ) - 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" - ] - - 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"] - ) - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ReadAssertionsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "read_assertions", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def read_authorization_model(self, id, **kwargs): - """Return a particular version of an authorization model - - 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`). - - >>> thread = await 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 await self.read_authorization_model_with_http_info(id, **kwargs) - - async def read_authorization_model_with_http_info(self, id, **kwargs): - """Return a particular version of an authorization model - - 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`). - - >>> 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method read_authorization_model" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `id` when calling `read_authorization_model`" - ) - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `read_authorization_model`" - ) - store_id = self.api_client._get_store_id() - - if "id" in local_var_params: - path_params["id"] = local_var_params["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"] - ) - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ReadAuthorizationModelResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "read_authorization_model", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def read_authorization_models(self, **kwargs): - """Return all the authorization models for a particular store - - 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\": \"\" } ``` - - >>> thread = await 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 await self.read_authorization_models_with_http_info(**kwargs) - - async def read_authorization_models_with_http_info(self, **kwargs): - """Return all the authorization models for a particular store - - 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\": \"\" } ``` - - >>> 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method read_authorization_models" - ) - local_var_params[key] = val - del local_var_params["kwargs"] - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `read_authorization_models`" - ) - store_id = self.api_client._get_store_id() - - query_params = [] - if local_var_params.get("page_size") is not None: - query_params.append(("page_size", local_var_params["page_size"])) - if local_var_params.get("continuation_token") is not None: - query_params.append( - ("continuation_token", local_var_params["continuation_token"]) - ) - - 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"] - ) - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ReadAuthorizationModelsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "read_authorization_models", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def read_changes(self, **kwargs): - """Return a list of all the tuple changes - - 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. When reading a write tuple change, if it was conditioned, the condition will be returned. When reading a delete tuple change, the condition will NOT be returned regardless of whether it was originally conditioned or not. - - >>> thread = await 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 start_time: Start date and time of changes to read. Format: ISO 8601 timestamp (e.g., 2022-01-01T00:00:00Z) If a continuation_token is provided along side start_time, the continuation_token will take precedence over start_time.(optional) - :type start_time: datetime, 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 await self.read_changes_with_http_info(**kwargs) - - async def read_changes_with_http_info(self, **kwargs): - """Return a list of all the tuple changes - - 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. When reading a write tuple change, if it was conditioned, the condition will be returned. When reading a delete tuple change, the condition will NOT be returned regardless of whether it was originally conditioned or not. - - >>> 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 start_time: Start date and time of changes to read. Format: ISO 8601 timestamp (e.g., 2022-01-01T00:00:00Z) If a continuation_token is provided along side start_time, the continuation_token will take precedence over start_time.(optional) - :type start_time: datetime, 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", "start_time"] - all_params.extend( - [ - "async_req", - "_return_http_data_only", - "_preload_content", - "_request_timeout", - "_request_auth", - "_content_type", - "_headers", - "_retry_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method read_changes" - ) - local_var_params[key] = val - del local_var_params["kwargs"] - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `read_changes`" - ) - store_id = self.api_client._get_store_id() - - query_params = [] - if local_var_params.get("type") is not None: - query_params.append(("type", local_var_params["type"])) - if local_var_params.get("page_size") is not None: - query_params.append(("page_size", local_var_params["page_size"])) - if local_var_params.get("continuation_token") is not None: - query_params.append( - ("continuation_token", local_var_params["continuation_token"]) - ) - if local_var_params.get("start_time") is not None: - query_params.append(("start_time", local_var_params["start_time"])) - - 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"] - ) - - # Authentication setting - auth_settings = [] - - response_types_map = { - 200: "ReadChangesResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "read_changes", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) - - async def streamed_list_objects(self, body, **kwargs): - """Stream all objects of the given type that the user has a relation with - - The Streamed ListObjects API is very similar to the the ListObjects API, with two differences: 1. Instead of collecting all objects before returning a response, it streams them to the client as they are collected. 2. The number of results returned is only limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE. - - >>> thread = await api.streamed_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: StreamResultOfStreamedListObjectsResponse - """ - kwargs["_return_http_data_only"] = True - return await self.streamed_list_objects_with_http_info(body, **kwargs) - - async def streamed_list_objects_with_http_info(self, body, **kwargs): - """Stream all objects of the given type that the user has a relation with - - The Streamed ListObjects API is very similar to the the ListObjects API, with two differences: 1. Instead of collecting all objects before returning a response, it streams them to the client as they are collected. 2. The number of results returned is only limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE. - - >>> thread = api.streamed_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(StreamResultOfStreamedListObjectsResponse, 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_params", - "_streaming", - ] - ) - - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method streamed_list_objects" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `body` when calling `streamed_list_objects`" - ) + api_response.deserialized = self.deserialize( + api_response.response, response_types + ) - collection_formats = {} + return api_response - path_params = {} + async def read_authorization_models( + self, + options: ReadAuthorizationModelsRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: ReadAuthorizationModelsRequestOptions = ( + ReadAuthorizationModelsRequestOptions() + | (options or ReadAuthorizationModelsRequestOptions()) + ) + response_types = self.build_response_types( + tuple([200, "ReadAuthorizationModelsResponse"]) + ) - store_id = None + body = None + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `streamed_list_objects`" - ) - store_id = self.api_client._get_store_id() + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") - query_params = [] + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "read_authorization_models", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } - header_params = dict(local_var_params.get("_headers", {})) + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) - form_params = [] - local_var_files = {} + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}/authorization-models", + method=RestClientRequestMethod("GET"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) - 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"] + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types ) - # 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 - ), + return api_response + + async def read_changes( + self, + options: ReadChangesRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: ReadChangesRequestOptions = ReadChangesRequestOptions() | ( + options or ReadChangesRequestOptions() ) - if content_types_list: - header_params["Content-Type"] = content_types_list + response_types = self.build_response_types(tuple([200, "ReadChangesResponse"])) - # Authentication setting - auth_settings = [] + body = None + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id - response_types_map = { - 200: "StreamResultOfStreamedListObjectsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "streamed_list_objects", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "read_changes", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, } - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, ) - return await self.api_client.call_api( - "/stores/{store_id}/streamed-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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}/changes", + method=RestClientRequestMethod("GET"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) + + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types ) - async def write(self, body, **kwargs): - """Add or delete tuples from the store + return api_response + + async def streamed_list_objects(self, body, **kwargs): + """Stream all objects of the given type that the user has a relation with - The Write API will transactionally 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 and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. 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\" } ] } } ``` + The Streamed ListObjects API is very similar to the the ListObjects API, with two differences: 1. Instead of collecting all objects before returning a response, it streams them to the client as they are collected. 2. The number of results returned is only limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE. - >>> thread = await api.write(body) + >>> thread = await api.streamed_list_objects(body) :param body: (required) - :type body: WriteRequest + :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: object + :rtype: StreamResultOfStreamedListObjectsResponse """ kwargs["_return_http_data_only"] = True - return await self.write_with_http_info(body, **kwargs) + return await self.streamed_list_objects_with_http_info(body, **kwargs) - async def write_with_http_info(self, body, **kwargs): - """Add or delete tuples from the store + async def streamed_list_objects_with_http_info(self, body, **kwargs): + """Stream all objects of the given type that the user has a relation with - The Write API will transactionally 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 and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. 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\" } ] } } ``` + The Streamed ListObjects API is very similar to the the ListObjects API, with two differences: 1. Instead of collecting all objects before returning a response, it streams them to the client as they are collected. 2. The number of results returned is only limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE. - >>> thread = api.write_with_http_info(body) + >>> thread = api.streamed_list_objects_with_http_info(body) :param body: (required) - :type body: WriteRequest + :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. @@ -2716,7 +1251,7 @@ async def write_with_http_info(self, body, **kwargs): :return: Returns the result object. If the method is called asynchronously, returns the request thread. - :rtype: tuple(object, status_code(int), headers(HTTPHeaderDict)) + :rtype: tuple(StreamResultOfStreamedListObjectsResponse, status_code(int), headers(HTTPHeaderDict)) """ local_var_params = locals() @@ -2726,8 +1261,6 @@ async def write_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2739,17 +1272,17 @@ async def write_with_http_info(self, body, **kwargs): for key, val in local_var_params["kwargs"].items(): if key not in all_params: raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method write" + f"Got an unexpected keyword argument '{key}' to method streamed_list_objects" ) local_var_params[key] = val del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( - "Missing the required parameter `body` when calling `write`" + "Missing the required parameter `body` when calling `streamed_list_objects`" ) collection_formats = {} @@ -2760,7 +1293,7 @@ async def write_with_http_info(self, body, **kwargs): if self.api_client._get_store_id() is None: raise ApiValueError( - "Store ID expected in api_client's configuration when calling `write`" + "Store ID expected in api_client's configuration when calling `streamed_list_objects`" ) store_id = self.api_client._get_store_id() @@ -2793,7 +1326,7 @@ async def write_with_http_info(self, body, **kwargs): auth_settings = [] response_types_map = { - 200: "object", + 200: "StreamResultOfStreamedListObjectsResponse", 400: "ValidationErrorMessageResponse", 401: "UnauthenticatedResponse", 403: "ForbiddenResponse", @@ -2804,7 +1337,7 @@ async def write_with_http_info(self, body, **kwargs): } telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "write", + TelemetryAttributes.fga_client_request_method: "streamed_list_objects", TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), TelemetryAttributes.fga_client_request_model_id: local_var_params.get( "authorization_model_id", "" @@ -2817,7 +1350,7 @@ async def write_with_http_info(self, body, **kwargs): ) return await self.api_client.call_api( - "/stores/{store_id}/write".replace("{store_id}", store_id), + "/stores/{store_id}/streamed-list-objects".replace("{store_id}", store_id), "POST", path_params, query_params, @@ -2829,16 +1362,63 @@ async def write_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) + async def write( + self, + body: WriteRequest, + options: WriteRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: WriteRequestOptions = WriteRequestOptions() | ( + options or WriteRequestOptions() + ) + response_types = self.build_response_types(tuple([200, "object"])) + + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id + + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") + + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "write", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } + + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, + ) + + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}/write", + method=RestClientRequestMethod("GET"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, + ) + + api_response: ApiResponseProtocol = ApiResponse() | api_client_response + + api_response.deserialized = self.deserialize( + api_response.response, response_types + ) + + return api_response + async def write_assertions(self, authorization_model_id, body, **kwargs): """Upsert assertions for an authorization model ID @@ -2852,14 +1432,8 @@ async def write_assertions(self, authorization_model_id, body, **kwargs): :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. @@ -2888,14 +1462,8 @@ async def write_assertions_with_http_info( :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. @@ -2915,8 +1483,6 @@ async def write_assertions_with_http_info( [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2934,7 +1500,7 @@ async def write_assertions_with_http_info( del local_var_params["kwargs"] # verify the required parameter 'authorization_model_id' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("authorization_model_id") is None ): raise ApiValueError( @@ -2942,7 +1508,7 @@ async def write_assertions_with_http_info( ) # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -3024,193 +1590,63 @@ async def write_assertions_with_http_info( auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) - async def write_authorization_model(self, body, **kwargs): - """Create a new authorization model - - 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\"} ``` - - >>> thread = await 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 await self.write_authorization_model_with_http_info(body, **kwargs) - - async def write_authorization_model_with_http_info(self, body, **kwargs): - """Create a new authorization model - - 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\"} ``` - - >>> 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_params", - "_streaming", - ] + async def write_authorization_model( + self, + body: WriteAuthorizationModelRequest, + options: WriteAuthorizationModelRequestOptions | None = None, + ) -> ApiResponseProtocol: + options: WriteAuthorizationModelRequestOptions = ( + WriteAuthorizationModelRequestOptions() + | (options or WriteAuthorizationModelRequestOptions()) ) - for key, val in local_var_params["kwargs"].items(): - if key not in all_params: - raise FgaValidationException( - f"Got an unexpected keyword argument '{key}' to method write_authorization_model" - ) - 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 - ): - raise ApiValueError( - "Missing the required parameter `body` when calling `write_authorization_model`" - ) - - collection_formats = {} - - path_params = {} - - store_id = None - - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `write_authorization_model`" - ) - store_id = self.api_client._get_store_id() + response_types = self.build_response_types( + tuple([200, "WriteAuthorizationModelResponse"]) + ) - query_params = [] + query = RestClientRequestQueryParameters.from_options(options) + fields = RestClientRequestFieldParameters() + streaming = False + store_id = options.store_id or self.configuration.store_id - header_params = dict(local_var_params.get("_headers", {})) + headers = HttpHeaders() + headers.add_header("Accept", "application/json") + headers.add_header("Content-Type", "application/json") - form_params = [] - local_var_files = {} + attributes: dict[TelemetryAttributeProtocol, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: "write_authorization_model", + TelemetryAttributes.fga_client_request_store_id: store_id, + TelemetryAttributes.fga_client_request_model_id: self.configuration.authorization_model_id, + } - 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"] + attributes = TelemetryAttributes.fromBody( + body=body, + attributes=attributes, ) - # 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 - ), + api_client_response: ApiClientResponseProtocol = await self.api_client.request( + path=f"/stores/{store_id}/authorization-models", + method=RestClientRequestMethod("POST"), + body=body, + headers=headers, + query=query, + fields=fields, + attributes=attributes, + streaming=streaming, ) - if content_types_list: - header_params["Content-Type"] = content_types_list - - # Authentication setting - auth_settings = [] - - response_types_map = { - 201: "WriteAuthorizationModelResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - } - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "write_authorization_model", - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( - "authorization_model_id", "" - ), - } + api_response: ApiResponseProtocol = ApiResponse() | api_client_response - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, + api_response.deserialized = self.deserialize( + api_response.response, response_types ) - return await 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"), - _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"), - _oauth2_client=self._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=local_var_params.get("_streaming", False), - ) + return api_response diff --git a/openfga_sdk/api_client.py b/openfga_sdk/api_client.py index 1dc831c9..d28a9a32 100644 --- a/openfga_sdk/api_client.py +++ b/openfga_sdk/api_client.py @@ -10,880 +10,194 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ -import asyncio -import atexit -import datetime -import json -import math -import random -import re import time -import urllib - -from multiprocessing.pool import ThreadPool from dateutil.parser import parse # type: ignore[import-untyped] -import openfga_sdk.models - -from openfga_sdk import oauth2, rest -from openfga_sdk.configuration import Configuration -from openfga_sdk.exceptions import ( - ApiException, - ApiValueError, - FgaValidationException, - RateLimitExceededError, - ServiceException, +from openfga_sdk.common.api_client import ApiClientBase, ApiClientResponse +from openfga_sdk.common.cookies import HttpCookies +from openfga_sdk.common.headers import HttpHeaders +from openfga_sdk.common.math import Math +from openfga_sdk.common.rest import ( + RestClientRequestBody, + RestClientRequestFieldParameters, + RestClientRequestMethod, + RestClientRequestQueryParameters, +) +from openfga_sdk.protocols import ( + FactoryProtocol, + HttpCookiesProtocol, + HttpHeadersProtocol, + RestClientRequestBodyProtocol, + RestClientRequestFieldParametersProtocol, + RestClientRequestQueryParametersProtocol, + RestClientResponseProtocol, + RetryParamsProtocol, + TelemetryAttributeProtocol, ) -from openfga_sdk.telemetry import Telemetry from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes -DEFAULT_USER_AGENT = "openfga-sdk python/0.9.1" - - -def random_time(loop_count, min_wait_in_ms): - """ - Helper function to return the time (in s) to wait before retry - """ - minimum = math.ceil(2**loop_count * min_wait_in_ms) - maximum = math.ceil(2 ** (loop_count + 1) * min_wait_in_ms) - return random.randrange(minimum, maximum) / 1000 - - -class ApiClient: - """Generic API client for OpenAPI client library builds. - - OpenAPI generic API client. This client handles the client- - server communication, and is invariant across implementations. Specifics of - the methods and models for each application are generated from the OpenAPI - templates. - - NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - Do not edit the class manually. - - :param configuration: .Configuration object for this client - :param header_name: a header to pass when making calls to the API. - :param header_value: a header value to pass when making calls to - the API. - :param cookie: a cookie to include in the header when making calls - to the API - :param pool_threads: The number of threads to use for async requests - to the API. More threads means more concurrent API requests. - """ - - PRIMITIVE_TYPES = (float, bool, bytes, str, int) - NATIVE_TYPES_MAPPING = { - "int": int, - "long": int, - "float": float, - "str": str, - "bool": bool, - "date": datetime.date, - "datetime": datetime.datetime, - "object": object, - } - _pool = None - - def __init__( - self, - configuration=None, - header_name=None, - header_value=None, - cookie=None, - pool_threads=1, - ): - if configuration is None: - configuration = Configuration.get_default_copy() - - self.configuration = configuration - self.pool_threads = pool_threads - - self.rest_client = rest.RESTClientObject(configuration) +class ApiClient(ApiClientBase): + async def __aenter__(self) -> "ApiClient": + return self - self.default_headers = {} - if header_name is not None: - self.default_headers[header_name] = header_value + async def __aexit__(self, exc_type, exc_value, traceback) -> None: + self.close() - self.cookie = cookie + @property + def _factory(self) -> FactoryProtocol: + if self.factory is None: + return self._factory_async - self.user_agent = DEFAULT_USER_AGENT + return self.factory - self.client_side_validation = configuration.client_side_validation - self._telemetry = Telemetry() + async def request( + self, + path: str, + method: RestClientRequestMethod, + body: RestClientRequestBodyProtocol | None = None, + headers: HttpHeadersProtocol | None = None, + query: RestClientRequestQueryParametersProtocol | None = None, + fields: RestClientRequestFieldParametersProtocol | None = None, + cookies: HttpCookiesProtocol | None = None, + timeout: int | None = None, + attributes: ( + dict[TelemetryAttributeProtocol, str | bool | int | float] | None + ) = None, + streaming: bool = False, + ) -> ApiClientResponse: + start = float(time.time()) - async def __aenter__(self): - return self + # Assemble request URL + url = self.configuration.api_url + path - async def __aexit__(self, exc_type, exc_value, traceback): - await self.close() + # Prepare query parameters + query: RestClientRequestQueryParameters = ( + query or RestClientRequestQueryParameters() + ) - async def close(self): - await self.rest_client.close() - if self._pool: - self._pool.close() - self._pool.join() - self._pool = None - if hasattr(atexit, "unregister"): - atexit.unregister(self.close) + # Prepare POST parameters + fields: RestClientRequestFieldParameters = ( + fields or RestClientRequestFieldParameters() + ) - @property - def pool(self): - """Create thread pool on first request - avoids instantiating unused threadpool for blocking clients. - """ - if self._pool is None: - atexit.register(self.close) - self._pool = ThreadPool(self.pool_threads) - return self._pool + # Prepare request body + body: RestClientRequestBody = body or RestClientRequestBody() - @property - def user_agent(self): - """User agent for this API client""" - return self.default_headers["User-Agent"] + # Prepare telemetry attributes + attributes: dict[TelemetryAttribute, str | bool | int | float] = ( + attributes or {} + ) - @user_agent.setter - def user_agent(self, value): - self.default_headers["User-Agent"] = value + # Prepare cookies + cookies: HttpCookies = cookies or HttpCookies() + cookies.merge(self.cookies) - def set_default_header(self, header_name, header_value): - self.default_headers[header_name] = header_value + # Prepare headers + headers: HttpHeaders = headers or HttpHeaders() + headers.merge(self.headers) - async def __call_api( - self, - resource_path, - method, - path_params=None, - query_params=None, - header_params=None, - body=None, - post_params=None, - response_types_map=None, - auth_settings=None, - _return_http_data_only=None, - collection_formats=None, - _preload_content=True, - _request_timeout=None, - _host=None, - _request_auth=None, - _retry_params=None, - _oauth2_client=None, - _telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] - | None = None, - _streaming: bool = False, - ): - self.configuration.is_valid() - config = self.configuration - start = float(time.time()) + # Add cookie headers to the request, if any + if cookies: + headers.add_header("Cookie", cookies.as_header()) - # header parameters - header_params = header_params or {} - header_params.update(self.default_headers) - if self.cookie: - header_params["Cookie"] = self.cookie - if header_params: - header_params = self.sanitize_for_serialization(header_params) - header_params = dict( - self.parameters_to_tuples(header_params, collection_formats) + # Perform credentials exchange if necessary + if ( + self.configuration.credentials is not None + and self.configuration.credentials.method == "client_credentials" + ): + headers.insert_header( + self.auth_client.get_authentication_header(self.rest_client) ) - # path parameters - if path_params: - path_params = self.sanitize_for_serialization(path_params) - path_params = self.parameters_to_tuples(path_params, collection_formats) - for k, v in path_params: - # specified safe chars, encode everything - _k = urllib.parse.quote(str(k), safe=config.safe_chars_for_path_param) - _v = urllib.parse.quote(str(v), safe=config.safe_chars_for_path_param) - resource_path = resource_path.replace("{" + str(k) + "}", _v) - - # query parameters - if query_params: - query_params = self.sanitize_for_serialization(query_params) - query_params = self.parameters_to_tuples(query_params, collection_formats) - - # post parameters - if post_params: - post_params = post_params if post_params else [] - post_params = self.sanitize_for_serialization(post_params) - post_params = self.parameters_to_tuples(post_params, collection_formats) - - # auth setting - await self.update_params_for_auth( - header_params, - query_params, - auth_settings, - request_auth=_request_auth, - oauth2_client=_oauth2_client, + # Collect automatic retry conditions + max_retries = ( + self.configuration.retry_params.max_retries + or RetryParamsProtocol.DEFAULT_MAX_RETRIES ) - # body - if body: - body = self.sanitize_for_serialization(body) - - # request url - if _host is None: - if self.configuration.api_url is not None: - url = self.configuration.api_url + resource_path - else: - url = ( - self.configuration.api_scheme - + "://" - + self.configuration.api_host - + resource_path - ) - else: - # use server/host defined in path or operation instead - url = self.configuration.api_scheme + "://" + _host + resource_path - - max_retry = ( - self.configuration.retry_params.max_retry - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.max_retry is not None - ) - else 0 - ) min_wait_in_ms = ( self.configuration.retry_params.min_wait_in_ms - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.min_wait_in_ms is not None - ) - else 0 + or RetryParamsProtocol.DEFAULT_MIN_WAIT ) - if _retry_params is not None: - if _retry_params.max_retry is not None: - max_retry = _retry_params.max_retry - if _retry_params.min_wait_in_ms is not None: - max_retry = _retry_params.min_wait_in_ms - _telemetry_attributes = TelemetryAttributes.fromRequest( + # Build the request object + request = self.rest_client.build_request( + method=method, + url=url, + body=body, + headers=headers, + query=query, + fields=fields, + ) + + # Seed the telemetry attributes with the request data + attributes = TelemetryAttributes.fromRequest( user_agent=self.user_agent, - fga_method=resource_path, + fga_method=path, http_method=method, url=url, resend_count=0, start=start, credentials=self.configuration.credentials, - attributes=_telemetry_attributes, + attributes=attributes, ) - for retry in range(max_retry + 1): - _telemetry_attributes[TelemetryAttributes.http_request_resend_count] = retry - - try: - # perform request and return response - response_data = await self.request( - method, - url, - query_params=query_params, - headers=header_params, - post_params=post_params, - body=body, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - _streaming=_streaming, - ) - except (RateLimitExceededError, ServiceException) as e: - if retry < max_retry and e.status != 501: - _telemetry_attributes = TelemetryAttributes.fromResponse( - response=e.body.decode("utf-8"), - credentials=self.configuration.credentials, - attributes=_telemetry_attributes, - ) - - self._telemetry.metrics.request( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) - - await asyncio.sleep(random_time(retry, min_wait_in_ms)) - - continue - e.body = e.body.decode("utf-8") - response_type = response_types_map.get(e.status, None) - if response_type is not None: - e.parsed_exception = self.__deserialize( - json.loads(e.body), response_type - ) - e.body = None - raise e - except ApiException as e: - e.body = e.body.decode("utf-8") - response_type = response_types_map.get(e.status, None) - if response_type is not None: - e.parsed_exception = self.__deserialize( - json.loads(e.body), response_type - ) - e.body = None - - _telemetry_attributes = TelemetryAttributes.fromResponse( - response=e, - credentials=self.configuration.credentials, - attributes=_telemetry_attributes, - ) - - self._telemetry.metrics.request( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) - - self._telemetry.metrics.queryDuration( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) - - self._telemetry.metrics.requestDuration( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) - raise e - - self.last_response = response_data - - return_data = response_data - - _telemetry_attributes = TelemetryAttributes.fromResponse( - response=response_data, - credentials=self.configuration.credentials, - attributes=_telemetry_attributes, - ) - - self._telemetry.metrics.request( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) + for retry in range(max_retries + 1): + attributes[TelemetryAttributes.http_request_resend_count] = retry + response: RestClientResponseProtocol | None = None + exception: Exception | None = None - self._telemetry.metrics.queryDuration( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, + # try: + response = await ( + self.rest_client.request(request) + if not streaming + else self.rest_client.stream(request) ) - self._telemetry.metrics.requestDuration( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, + response_status: int = ( + exception.status + if exception is not None + else response.status if response is not None else 500 ) - if not _preload_content or _streaming: - return return_data - - response_type = response_types_map.get(response_data.status, None) - - if response_type not in ["file", "bytes"]: - match = None - content_type = response_data.getheader("content-type") - if content_type is not None: - match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) - encoding = match.group(1) if match else "utf-8" - if response_data.data is not None: - response_data.data = response_data.data.decode(encoding) - - # deserialize response data - - if response_type: - return_data = self.deserialize(response_data, response_type) - else: - return_data = None - - if _return_http_data_only: - return return_data - else: - return (return_data, response_data.status, response_data.headers) - - def sanitize_for_serialization(self, obj): - """Builds a JSON POST object. - - If obj is None, return None. - If obj is str, int, long, float, bool, return directly. - If obj is datetime.datetime, datetime.date - convert to string in iso8601 format. - If obj is list, sanitize each element in the list. - If obj is dict, return the dict. - If obj is OpenAPI model, return the properties dict. - - :param obj: The data to serialize. - :return: The serialized form of data. - """ - if obj is None: - return None - elif isinstance(obj, self.PRIMITIVE_TYPES): - return obj - elif isinstance(obj, list): - return [self.sanitize_for_serialization(sub_obj) for sub_obj in obj] - elif isinstance(obj, tuple): - return tuple(self.sanitize_for_serialization(sub_obj) for sub_obj in obj) - elif isinstance(obj, datetime.datetime | datetime.date): - return obj.isoformat() - - if isinstance(obj, dict): - obj_dict = obj - else: - # Convert model obj to dict except - # attributes `openapi_types`, `attribute_map` - # and attributes which value is not None. - # Convert attribute name to json key in - # model definition for request. - obj_dict = { - obj.attribute_map[attr]: getattr(obj, attr) - for attr, _ in obj.openapi_types.items() - if getattr(obj, attr) is not None - } - - return { - key: self.sanitize_for_serialization(val) for key, val in obj_dict.items() - } - - def deserialize(self, response, response_type): - """Deserializes response into an object. - - :param response: RESTResponse object to be deserialized. - :param response_type: class literal for - deserialized object, or string of class name. - - :return: deserialized object. - """ - - # fetch data from response object - try: - data = json.loads(response.data) - except ValueError: - data = response.data - - return self.__deserialize(data, response_type) - - def __deserialize(self, data, klass): - """Deserializes dict, list, str into an object. - - :param data: dict, list or str. - :param klass: class literal, or string of class name. - - :return: object. - """ - if data is None: - return None - - if type(klass) is str: - if klass.startswith("list["): - sub_kls = re.match(r"list\[(.*)\]", klass).group(1) - return [self.__deserialize(sub_data, sub_kls) for sub_data in data] - - if klass.startswith("dict["): - sub_kls = re.match(r"dict\[([^,]*), (.*)\]", klass).group(2) - return {k: self.__deserialize(v, sub_kls) for k, v in data.items()} - - # convert str to class - if klass in self.NATIVE_TYPES_MAPPING: - klass = self.NATIVE_TYPES_MAPPING[klass] - else: - klass = getattr(openfga_sdk.models, klass) - - if klass in self.PRIMITIVE_TYPES: - return self.__deserialize_primitive(data, klass) - - if klass is object: - return self.__deserialize_object(data) - - if klass is datetime.date: - return self.__deserialize_date(data) - - if klass is datetime.datetime: - return self.__deserialize_datetime(data) - - return self.__deserialize_model(data, klass) - - async def call_api( - self, - resource_path, - method, - path_params=None, - query_params=None, - header_params=None, - body=None, - post_params=None, - files=None, - response_types_map=None, - auth_settings=None, - async_req=None, - _return_http_data_only=None, - collection_formats=None, - _preload_content=True, - _request_timeout=None, - _host=None, - _request_auth=None, - _retry_params=None, - _oauth2_client=None, - _telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] - | None = None, - _streaming: bool = False, - ): - """Makes the HTTP request (synchronous) and returns deserialized data. - - To make an async_req request, set the async_req parameter. - - :param resource_path: Path to method endpoint. - :param method: Method to call. - :param path_params: Path parameters in the url. - :param query_params: Query parameters in the url. - :param header_params: Header parameters to be - placed in the request header. - :param body: Request body. - :param post_params dict: Request post form parameters, - for `application/x-www-form-urlencoded`, `multipart/form-data`. - :param auth_settings list: Auth Settings names for the request. - :param response: Response data type. - :param files dict: it will not be used - :param async_req bool: execute request asynchronously - :param _return_http_data_only: response data without head status code - and headers - :param collection_formats: dict of collection formats for path, query, - header, and post parameters. - :param _preload_content: if False, the urllib3.HTTPResponse object will - be returned without reading/decoding response - data. Default is True. - :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_params: If specified, override the default retry parameters - :type _request_token: dict, optional - :return: - If async_req parameter is True, - the request will be called asynchronously. - The method will return the request thread. - If parameter async_req is False or missing, - then the method will return the response directly. - """ - if not async_req: - return await self.__call_api( - resource_path, - method, - path_params, - query_params, - header_params, - body, - post_params, - response_types_map, - auth_settings, - _return_http_data_only, - collection_formats, - _preload_content, - _request_timeout, - _host, - _request_auth, - _retry_params, - _oauth2_client, - _telemetry_attributes, - _streaming, + retry_delay: int | None = self._should_retry( + retry=retry, + max_retries=max_retries, + min_wait=min_wait_in_ms, + e=exception, + response_status=response_status, ) - return self.pool.apply_async( - self.__call_api, - ( - resource_path, - method, - path_params, - query_params, - header_params, - body, - post_params, - response_types_map, - auth_settings, - _return_http_data_only, - collection_formats, - _preload_content, - _request_timeout, - _host, - _request_auth, - _retry_params, - _oauth2_client, - _telemetry_attributes, - _streaming, - ), - ) - - async def request( - self, - method, - url, - query_params=None, - headers=None, - post_params=None, - body=None, - _preload_content=True, - _request_timeout=None, - _streaming: bool = False, - ): - if method not in ["GET", "HEAD", "OPTIONS", "POST", "PATCH", "PUT", "DELETE"]: - raise ApiValueError( - "http method must be `GET`, `HEAD`, `OPTIONS`," - " `POST`, `PATCH`, `PUT` or `DELETE`." + # Update telemetry attributes with response data, if available + attributes = TelemetryAttributes.fromResponse( + response=response, + credentials=self.configuration.credentials, + attributes=attributes, ) - if _streaming: - return self.rest_client.stream( - method, - url, - query_params=query_params, - headers=headers, - post_params=post_params, - body=body, - _request_timeout=_request_timeout, + self.telemetry_client.metrics.request( + attributes=attributes, + configuration=self.configuration.telemetry, ) - return await self.rest_client.request( - method, - url, - query_params=query_params, - headers=headers, - post_params=post_params, - body=body, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - ) - - def parameters_to_tuples(self, params, collection_formats): - """Get parameters as list of tuples, formatting collections. - - :param params: Parameters as dict or list of two-tuples - :param dict collection_formats: Parameter collection formats - :return: Parameters as list of tuples, collections formatted - """ - new_params = [] - if collection_formats is None: - collection_formats = {} - for k, v in params.items() if isinstance(params, dict) else params: - if k in collection_formats: - collection_format = collection_formats[k] - if collection_format == "multi": - new_params.extend((k, value) for value in v) - else: - if collection_format == "ssv": - delimiter = " " - elif collection_format == "tsv": - delimiter = "\t" - elif collection_format == "pipes": - delimiter = "|" - else: # csv is the default - delimiter = "," - new_params.append((k, delimiter.join(str(value) for value in v))) - else: - new_params.append((k, v)) - return new_params - - def select_header_accept(self, accepts): - """Returns `Accept` based on an array of accepts provided. - - :param accepts: List of headers. - :return: Accept (e.g. application/json). - """ - if not accepts: - return - - accepts = [x.lower() for x in accepts] - - if "application/json" in accepts: - return "application/json" - else: - return ", ".join(accepts) - - def select_header_content_type(self, content_types, method=None, body=None): - """Returns `Content-Type` based on an array of content_types provided. - - :param content_types: List of content-types. - :param method: http method (e.g. POST, PATCH). - :param body: http body to send. - :return: Content-Type (e.g. application/json). - """ - if not content_types: - return None - - content_types = [x.lower() for x in content_types] - - if ( - method == "PATCH" - and "application/json-patch+json" in content_types - and isinstance(body, list) - ): - return "application/json-patch+json" - - if "application/json" in content_types or "*/*" in content_types: - return "application/json" - else: - return content_types[0] - - async def update_params_for_auth( - self, headers, queries, auth_settings, request_auth=None, oauth2_client=None - ): - """Updates header and query params based on authentication setting. - - :param headers: Header parameters dict to be updated. - :param queries: Query parameters tuple list to be updated. - :param auth_settings: Authentication setting identifiers list. - :param request_auth: if set, the provided settings will - override the token in the configuration. - :param oauth2_client: if set, will be used for credential exchange. - """ - credentials = self.configuration.credentials - if credentials is not None: - if credentials.method == "none": - pass - if credentials.method == "api_token": - headers["Authorization"] = ( - f"Bearer {credentials.configuration.api_token}" - ) - if credentials.method == "client_credentials": - if oauth2_client is None: - oauth2_client = oauth2.OAuth2Client(credentials, self.configuration) - oauth2_headers = await oauth2_client.get_authentication_header( - self.rest_client - ) - for key, value in oauth2_headers.items(): - headers[key] = value - - if not auth_settings: - return - - if request_auth: - self._apply_auth_params(headers, queries, request_auth) - return - - for auth in auth_settings: - auth_setting = self.configuration.auth_settings().get(auth) - if auth_setting: - self._apply_auth_params(headers, queries, auth_setting) - - def _apply_auth_params(self, headers, queries, auth_setting): - """Updates the request parameters based on a single auth_setting - - :param headers: Header parameters dict to be updated. - :param queries: Query parameters tuple list to be updated. - :param auth_setting: auth settings for the endpoint - """ - if auth_setting["in"] == "cookie": - headers["Cookie"] = auth_setting["value"] - elif auth_setting["in"] == "header": - headers[auth_setting["key"]] = auth_setting["value"] - elif auth_setting["in"] == "query": - queries.append((auth_setting["key"], auth_setting["value"])) - else: - raise ApiValueError("Authentication token must be in `query` or `header`") - - def __deserialize_primitive(self, data, klass): - """Deserializes string to primitive type. - - :param data: str. - :param klass: class literal. - - :return: int, long, float, str, bool. - """ - try: - return klass(data) - except UnicodeEncodeError: - return str(data) - except TypeError: - return data - - def __deserialize_object(self, value): - """Return an original value. - - :return: object. - """ - return value - - def __deserialize_date(self, string): - """Deserializes string to date. - - :param string: str. - :return: date. - """ - try: - return parse(string).date() - except ImportError: - return string - except ValueError: - raise rest.ApiException( - status=0, reason=f"Failed to parse `{string}` as date object" + self.telemetry_client.metrics.queryDuration( + attributes=attributes, + configuration=self.configuration.telemetry, ) - def __deserialize_datetime(self, string): - """Deserializes string to datetime. - - The string should be in iso8601 datetime format. - - :param string: str. - :return: datetime. - """ - try: - return parse(string) - except ImportError: - return string - except ValueError: - raise rest.ApiException( - status=0, - reason=(f"Failed to parse `{string}` as datetime object"), + self.telemetry_client.metrics.requestDuration( + attributes=attributes, + configuration=self.configuration.telemetry, ) - def __deserialize_model(self, data, klass): - """Deserializes list or dict to model. + if retry_delay is not None: + time.sleep(retry_delay) + continue - :param data: dict, list. - :param klass: class literal. - :return: model object. - """ - has_discriminator = False - if ( - hasattr(klass, "get_real_child_model") - and klass.discriminator_value_class_map - ): - has_discriminator = True - - if not klass.openapi_types and has_discriminator is False: - return data - - kwargs = {} - if ( - data is not None - and klass.openapi_types is not None - and isinstance(data, list | dict) - ): - for attr, attr_type in klass.openapi_types.items(): - if klass.attribute_map[attr] in data: - value = data[klass.attribute_map[attr]] - kwargs[attr] = self.__deserialize(value, attr_type) - - kwargs["local_vars_configuration"] = self.configuration - instance = klass(**kwargs) - - if has_discriminator: - klass_name = instance.get_real_child_model(data) - if klass_name: - instance = self.__deserialize(data, klass_name) - return instance - - def _get_store_id(self): - """ - Verify that the store id has been configured and not empty string. - It will return the store ID. - Otherwise, raise FgaValidationException - """ - configuration = self.configuration - if configuration.store_id is None or configuration.store_id == "": - raise FgaValidationException("store_id is required but not configured") - return configuration.store_id - - def set_store_id(self, value): - """ - Update the store ID in the configuration - """ - self.configuration.store_id = value - - def get_store_id(self): - """ - Return the store id (if any) store in the configuration - """ - return self.configuration.store_id + return ApiClientResponse( + retries=retry, + request=request, + response=response, + ) diff --git a/openfga_sdk/client/__init__.py b/openfga_sdk/client/__init__.py index b6d1de38..bf626b5c 100644 --- a/openfga_sdk/client/__init__.py +++ b/openfga_sdk/client/__init__.py @@ -11,12 +11,13 @@ """ from openfga_sdk.client.client import OpenFgaClient -from openfga_sdk.client.configuration import ClientConfiguration +from openfga_sdk.client.configuration import Configuration, ClientConfiguration from openfga_sdk.client.models.check_request import ClientCheckRequest __all__ = [ "OpenFgaClient", + "Configuration", "ClientConfiguration", "ClientCheckRequest", ] diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index eae1cd8d..0e7b5551 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -13,9 +13,8 @@ import asyncio import uuid -from openfga_sdk.api.open_fga_api import OpenFgaApi -from openfga_sdk.api_client import ApiClient -from openfga_sdk.client.configuration import ClientConfiguration +from click import option + from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ( ClientBatchCheckItem, @@ -41,27 +40,54 @@ from openfga_sdk.client.models.tuple import ClientTuple, convert_tuple_keys from openfga_sdk.client.models.write_request import ClientWriteRequest from openfga_sdk.client.models.write_response import ClientWriteResponse -from openfga_sdk.client.models.write_single_response import ( - construct_write_single_response, +from openfga_sdk.client.models.write_single_response import ClientWriteSingleResponse +from openfga_sdk.common.client import OpenFgaClientBase +from openfga_sdk.common.headers import HttpHeaderKeys +from openfga_sdk.common.options import ( + BatchCheckRequestOptions, + CheckRequestOptions, + CreateStoreRequestOptions, + DeleteStoreRequestOptions, + ExpandRequestOptions, + GetStoreRequestOptions, + ListObjectsRequestOptions, + ListRelationsRequestOptions, + ListStoresRequestOptions, + ListUsersRequestOptions, + ReadAssertionsRequestOptions, + ReadAuthorizationModelRequestOptions, + ReadAuthorizationModelsRequestOptions, + ReadChangesRequestOptions, + ReadLatestAuthorizationModelRequestOptions, + ReadRequestOptions, + WriteAssertionsRequestOptions, + WriteAuthorizationModelRequestOptions, + WriteRequestOptions, ) -from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts from openfga_sdk.exceptions import ( AuthenticationError, FgaValidationException, UnauthorizedException, ) -from openfga_sdk.models.assertion import Assertion from openfga_sdk.models.batch_check_request import BatchCheckRequest +from openfga_sdk.models.batch_check_response import BatchCheckResponse from openfga_sdk.models.check_request import CheckRequest +from openfga_sdk.models.check_response import CheckResponse from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys from openfga_sdk.models.create_store_request import CreateStoreRequest +from openfga_sdk.models.create_store_response import CreateStoreResponse from openfga_sdk.models.expand_request import ExpandRequest from openfga_sdk.models.expand_request_tuple_key import ExpandRequestTupleKey +from openfga_sdk.models.get_store_response import GetStoreResponse from openfga_sdk.models.list_objects_request import ListObjectsRequest +from openfga_sdk.models.list_stores_response import ListStoresResponse from openfga_sdk.models.list_users_request import ListUsersRequest 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_request import ReadRequest from openfga_sdk.models.read_request_tuple_key import ReadRequestTupleKey from openfga_sdk.models.streamed_list_objects_response import ( @@ -72,312 +98,236 @@ 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 -from openfga_sdk.validation import is_well_formed_ulid_string - - -CLIENT_METHOD_HEADER = "X-OpenFGA-Client-Method" -CLIENT_BULK_REQUEST_ID_HEADER = "X-OpenFGA-Client-Bulk-Request-Id" - - -def _chuck_array(array, max_size): - """ - Helper function to chuck array into arrays of max_size - """ - return [ - array[i * max_size : (i + 1) * max_size] - for i in range((len(array) + max_size - 1) // max_size) - ] - - -def set_heading_if_not_set( - options: dict[str, int | str | dict[str, int | str]] | None, - name: str, - value: str, -) -> dict[str, int | str | dict[str, int | str]]: - """ - Set heading to the value if it is not set - """ - _options: dict[str, int | str | dict[str, int | str]] = ( - options if options is not None else {} - ) - - if type(_options.get("headers")) is not dict: - _options["headers"] = {} - - if type(_options["headers"]) is dict: - if type(_options["headers"].get(name)) not in [int, str]: - _options["headers"][name] = value - - return _options - - -def options_to_kwargs( - options: dict[str, int | str | dict[str, int | str]] | None = None, -) -> dict[str, int | str | dict[str, int | str]]: - """ - Return kwargs with continuation_token and page_size - """ - kwargs = {} - if options is not None: - if options.get("page_size"): - kwargs["page_size"] = options["page_size"] - if options.get("continuation_token"): - kwargs["continuation_token"] = options["continuation_token"] - if options.get("headers"): - kwargs["_headers"] = options["headers"] - if options.get("retry_params"): - kwargs["_retry_params"] = options["retry_params"] - return kwargs - - -def options_to_transaction_info( - options: dict[str, int | str | dict[str, int | str]] | None = None, -): - """ - Return the transaction info - """ - if options is not None and options.get("transaction"): - return options["transaction"] - return WriteTransactionOpts() - - -def _check_allowed(response: ClientBatchCheckClientResponse): - """ - Helper function to return whether the response is check is allowed - """ - return response.allowed - - -class OpenFgaClient: - """ - OpenFgaClient is the entry point for invoking calls against the OpenFGA API. - """ - - def __init__(self, configuration: ClientConfiguration): - self._client_configuration = configuration - self._api_client = ApiClient(configuration) - self._api = OpenFgaApi(self._api_client) - - async def __aenter__(self): +from openfga_sdk.protocols import ApiClientResponseProtocol, FactoryProtocol + + +class OpenFgaClient(OpenFgaClientBase): + async def __aenter__(self) -> "OpenFgaClient": return self - async def __aexit__(self, exc_type, exc_value, traceback): + async def __aexit__(self, exc_type, exc_value, traceback) -> None: await self.close() - async def close(self): - await self._api.close() + async def close(self) -> None: + await self.api.close() - def _get_authorization_model_id( - self, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> str | None: - """ - Return the authorization model ID if specified in the options. - Otherwise, return the authorization model ID stored in the client's configuration - """ - authorization_model_id = self._client_configuration.authorization_model_id - if options is not None and "authorization_model_id" in options: - authorization_model_id = options["authorization_model_id"] - if authorization_model_id is None or authorization_model_id == "": - return None - if is_well_formed_ulid_string(authorization_model_id) is False: - raise FgaValidationException( - f"authorization_model_id ('{authorization_model_id}') is not in a valid ulid format" - ) - return authorization_model_id + @property + def _factory(self) -> FactoryProtocol: + return self._factory_async + + ################# + # Stores + ################# - def _get_consistency( + async def list_stores( self, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> str | None: - """ - Returns the consistency requested if specified in the options. - Otherwise, returns None. - """ - consistency: int | str | dict[str, int | str] | None = ( - options.get("consistency", None) if options is not None else None + options: ListStoresRequestOptions | None = None, + ) -> ListStoresResponse | ApiClientResponseProtocol: + options: ListStoresRequestOptions = ListStoresRequestOptions() | ( + options or ListStoresRequestOptions() ) - if type(consistency) is str: - return consistency + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="ListStores", + overwrite=False, + ) - return None + response = await self.api.list_stores(options) - def set_store_id(self, value): - """ - Update the store ID in the configuration - """ - self._api_client.set_store_id(value) - - def get_store_id(self): - """ - Return the store id (if any) store in the configuration - """ - return self._api_client.get_store_id() - - def set_authorization_model_id(self, value): - """ - Update the authorization model id in the configuration - """ - self._client_configuration.authorization_model_id = value - - def get_authorization_model_id(self): - """ - Return the authorization model id - """ - return self._client_configuration.authorization_model_id + if options.return_response: + return response - ################# - # Stores - ################# - - async def list_stores( - self, options: dict[str, int | str | dict[str, int | str]] | None = None - ): - """ - List the stores in the system - :param page_size(options) - Number of items returned per request - :param continuation_token(options) - No continuation_token by default - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - # convert options to kwargs - kwargs = options_to_kwargs(options) - api_response = await self._api.list_stores( - **kwargs, - ) - return api_response + return response.deserialized async def create_store( self, body: CreateStoreRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ): - """ - Create the stores in the system - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) - api_response = await self._api.create_store(body, **kwargs) - return api_response + options: CreateStoreRequestOptions | None = None, + ) -> CreateStoreResponse | ApiClientResponseProtocol: + options: CreateStoreRequestOptions = CreateStoreRequestOptions() | ( + options or CreateStoreRequestOptions() + ) + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="CreateStore", + overwrite=False, + ) + + response = await self.api.create_store(body, options) + + if options.return_response: + return response + + return response.deserialized async def get_store( - self, options: dict[str, int | str | dict[str, int | str]] | None = None - ): - """ - Get the store info in the system. Store id is from the configuration. - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) - api_response = await self._api.get_store( - **kwargs, - ) - return api_response + self, + store_id: str | None = None, + options: GetStoreRequestOptions | None = None, + ) -> GetStoreResponse | ApiClientResponseProtocol: + options: GetStoreRequestOptions = GetStoreRequestOptions() | ( + options or GetStoreRequestOptions() + ) + + options.store_id = store_id or options.store_id or self.configuration.store_id + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="GetStore", + overwrite=False, + ) + + response = await self.api.get_store(options) + + if options.return_response: + return response + + return response.deserialized async def delete_store( - self, options: dict[str, int | str | dict[str, int | str]] | None = None - ): - """ - Delete the store from the system. Store id is from the configuration. - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) - api_response = await self._api.delete_store( - **kwargs, - ) - return api_response + self, + store_id: str | None = None, + options: DeleteStoreRequestOptions | None = None, + ) -> None | ApiClientResponseProtocol: + options: DeleteStoreRequestOptions = DeleteStoreRequestOptions() | ( + options or DeleteStoreRequestOptions() + ) + + options.store_id = store_id or options.store_id or self.configuration.store_id + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="DeleteStore", + overwrite=False, + ) + + response = await self.api.delete_store(store_id, options) + + if options.return_response: + return response + + return None ####################### # Authorization Models ####################### async def read_authorization_models( - self, options: dict[str, int | str | dict[str, int | str]] | None = None - ): - """ - Return all the authorization models for a particular store. - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) - api_response = await self._api.read_authorization_models( - **kwargs, - ) - return api_response + self, + store_id: str | None = None, + options: ReadAuthorizationModelsRequestOptions | None = None, + ) -> None | ApiClientResponseProtocol: + options: ReadAuthorizationModelsRequestOptions = ( + ReadAuthorizationModelsRequestOptions() + | (options or ReadAuthorizationModelsRequestOptions()) + ) + + options.store_id = store_id or options.store_id or self.configuration.store_id + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="ReadAuthorizationModels", + overwrite=False, + ) + + response = await self.api.read_authorization_models(options) + + if options.return_response: + return response + + return None async def write_authorization_model( self, body: WriteAuthorizationModelRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ): - """ - Write authorization model. - :param body - WriteAuthorizationModelRequest - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) - api_response = await self._api.write_authorization_model( - body, - **kwargs, - ) - return api_response + options: WriteAuthorizationModelRequestOptions | None = None, + ) -> WriteAuthorizationModelResponse | ApiClientResponseProtocol: + options: WriteAuthorizationModelRequestOptions = ( + WriteAuthorizationModelRequestOptions() + | (options or WriteAuthorizationModelRequestOptions()) + ) + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="WriteAuthorizationModel", + overwrite=False, + ) + + response = await self.api.write_authorization_model(body, options) + + if options.return_response: + return response + + return response.deserialized async def read_authorization_model( - self, options: dict[str, int | str | dict[str, int | str]] | None = None - ): - """ - Read an authorization model. - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) - authorization_model_id = self._get_authorization_model_id(options) - api_response = await self._api.read_authorization_model( - authorization_model_id, - **kwargs, - ) - return api_response + self, + authorization_model_id: str | None = None, + options: ReadAuthorizationModelRequestOptions | None = None, + ) -> ReadAuthorizationModelResponse | ApiClientResponseProtocol: + options: ReadAuthorizationModelRequestOptions = ( + ReadAuthorizationModelRequestOptions() + | (options or ReadAuthorizationModelRequestOptions()) + ) + + options.authorization_model_id = ( + authorization_model_id + or options.authorization_model_id + or self.configuration.authorization_model_id + ) + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="ReadAuthorizationModel", + overwrite=False, + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + response = await self.api.read_authorization_model( + authorization_model_id, options + ) + + if options.return_response: + return response + + return response.deserialized async def read_latest_authorization_model( - self, options: dict[str, int | str | dict[str, int | str]] | None = None - ): - """ - Convenient method of reading the latest authorization model - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - options = set_heading_if_not_set( - options, CLIENT_METHOD_HEADER, "ReadLatestAuthorizationModel" - ) - options["page_size"] = 1 - api_response = await self.read_authorization_models(options) - model = ( - api_response.authorization_models[0] - if len(api_response.authorization_models) > 0 - else None + self, + options: ReadLatestAuthorizationModelRequestOptions | None = None, + ) -> ReadAuthorizationModelResponse | ApiClientResponseProtocol: + options: ReadAuthorizationModelsRequestOptions = ( + ReadAuthorizationModelsRequestOptions() + | (options or ReadLatestAuthorizationModelRequestOptions()) + ) + options.page_size = 1 + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="ReadLatestAuthorizationModel", + overwrite=True, ) - return ReadAuthorizationModelResponse(model) + + response = self.read_authorization_models(options) + + if len(response.deserialized.authorization_models) > 0: + response.deserialized = ReadAuthorizationModelResponse( + response.deserialized.authorization_models[0] + ) + + if options.return_response: + return response + + return response.deserialized ####################### # Relationship Tuples @@ -386,232 +336,225 @@ async def read_latest_authorization_model( async def read_changes( self, body: ClientReadChangesRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: ReadChangesRequestOptions | None = None, ): - """ - Read changes for specified type - :param body - the type we want to look for change - :param page_size(options) - Number of items returned per request - :param continuation_token(options) - No continuation_token by default - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) + options: ReadChangesRequestOptions = ReadChangesRequestOptions() | ( + options or ReadChangesRequestOptions() + ) - if body.type is not None: - kwargs["type"] = body.type + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="ReadChanges", + overwrite=False, + ) - if body.start_time is not None: - kwargs["start_time"] = body.start_time + response = await self.api.read_changes(body, options) - api_response = await self._api.read_changes( - **kwargs, - ) - return api_response + if options.return_response: + return response + + return response.deserialized async def read( self, - body: ReadRequestTupleKey, - options: dict[str, int | str | dict[str, int | str]] | None = None, + tuple_key: ReadRequestTupleKey | None = None, + options: ReadRequestOptions | None = None, + ): + options: ReadRequestOptions = ReadRequestOptions() | ( + options or ReadRequestOptions() + ) + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="Read", + overwrite=False, + ) + + request = ReadRequest( + tuple_key=tuple_key, + page_size=options.page_size, + continuation_token=options.continuation_token, + consistency=options.consistency, + ) + + response = await self.api.read(request, options) + + if options.return_response: + return response + + return response.deserialized + + async def _write_with_transaction( + self, + body: ClientWriteRequest, + options: WriteRequestOptions | None = None, ): - """ - Read changes for specified type - :param body - the tuples we want to read - :param page_size(options) - Number of items returned per request - :param continuation_token(options) - No continuation_token by default - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - :param consistency(options) - The type of consistency preferred for the request - """ - page_size = None - continuation_token = None - if options: - if options.get("page_size"): - page_size = options.get("page_size") - options.pop("page_size") - if options.get("continuation_token"): - continuation_token = options.get("continuation_token") - options.pop("continuation_token") - kwargs = options_to_kwargs(options) - - if body is None or ( - body.object is None and body.relation is None and body.user is None - ): - tuple_key = None - else: - tuple_key = body - - api_response = await self._api.read( - ReadRequest( - tuple_key=tuple_key, - page_size=page_size, - continuation_token=continuation_token, - consistency=self._get_consistency(options), + options: WriteRequestOptions = WriteRequestOptions() | ( + options or WriteRequestOptions() + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + await self.api.write( + WriteRequest( + writes=body.writes_tuple_keys, + deletes=body.deletes_tuple_keys, + authorization_model_id=authorization_model_id, ), - **kwargs, + options, + ) + + wrote = ( + [ClientWriteSingleResponse(i, True, None) for i in body.writes] + if body.writes + else None + ) + + deleted = ( + [ClientWriteSingleResponse(i, True, None) for i in body.deletes] + if body.deletes + else None ) - return api_response + + return ClientWriteResponse(writes=wrote, deletes=deleted) async def _write_single_batch( self, batch: list[ClientTuple], is_write: bool, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: WriteRequestOptions | None = None, ): + options: WriteRequestOptions = WriteRequestOptions() | ( + options or WriteRequestOptions() + ) + try: write_batch = None delete_batch = None + if is_write: write_batch = batch else: delete_batch = batch + await self._write_with_transaction( ClientWriteRequest(writes=write_batch, deletes=delete_batch), options ) - return [construct_write_single_response(i, True, None) for i in batch] + + return [ClientWriteSingleResponse(i, True, None) for i in batch] + except (AuthenticationError, UnauthorizedException) as err: raise err + except Exception as err: - return [construct_write_single_response(i, False, err) for i in batch] + return [ClientWriteSingleResponse(i, False, err) for i in batch] async def _write_batches( self, - tuple_keys: list[ClientTuple], - transaction: WriteTransactionOpts, + tuple_keys: list[ClientTuple] | None, is_write: bool, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ): - """ - Internal function for write/delete batches - """ - chunks = _chuck_array(tuple_keys, transaction.max_per_chunk) + options: WriteRequestOptions | None = None, + ) -> list[ClientWriteSingleResponse] | None: + options: WriteRequestOptions = WriteRequestOptions() | ( + options or WriteRequestOptions() + ) + + if tuple_keys is None: + return [] + + chunks = ( + tuple_keys[i : i + options.transaction.max_per_chunk] + for i in range(0, len(tuple_keys), options.transaction.max_per_chunk) + ) + + write_batches = ( + chunks[i : i + options.transaction.max_parallel_requests] + for i in range( + 0, + len(chunks), + options.transaction.max_parallel_requests, + ) + ) - 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) + 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( + async def write( self, body: ClientWriteRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: WriteRequestOptions | None = None, ): - """ - Write or deletes tuples - """ - kwargs = options_to_kwargs(options) - writes_tuple_keys = None - deletes_tuple_keys = None - if body.writes_tuple_keys: - writes_tuple_keys = body.writes_tuple_keys - if body.deletes_tuple_keys: - deletes_tuple_keys = body.deletes_tuple_keys - - await self._api.write( - WriteRequest( - writes=writes_tuple_keys, - deletes=deletes_tuple_keys, - authorization_model_id=self._get_authorization_model_id(options), - ), - **kwargs, + options: WriteRequestOptions = WriteRequestOptions() | ( + options or WriteRequestOptions() ) - # any error will result in exception being thrown and not reached below code - writes_response = None - if body.writes: - writes_response = [ - construct_write_single_response(i, True, None) for i in body.writes - ] - deletes_response = None - if body.deletes: - deletes_response = [ - construct_write_single_response(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, int | str | dict[str, int | str]] | None = None, - ): - """ - Write or deletes tuples - :param body - the write request - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "Write") - transaction = options_to_transaction_info(options) - if not transaction.disabled: + options.headers.use_bulk_request_id() + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="Write", + overwrite=False, + ) + + if not options.transaction.disabled: results = await self._write_with_transaction(body, options) return results - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) - ) + writes_response = await self._write_batches(body.writes, True, options) + deletes_response = await self._write_batches(body.deletes, False, 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 - ) - deletes_response = None - if body.deletes: - deletes_response = await 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, int | str | dict[str, int | str]] | None = None, + options: WriteRequestOptions | None = None, ): - """ - Convenient method for writing tuples - :param body - the list of tuples we want to write - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :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) - return result + options: WriteRequestOptions = WriteRequestOptions() | ( + options or WriteRequestOptions() + ) + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="WriteTuples", + overwrite=True, + ) + + return await self.write(ClientWriteRequest(writes=body), options) async def delete_tuples( self, body: list[ClientTuple], - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: WriteRequestOptions | None = None, ): - """ - Convenient method for deleteing tuples - :param body - the list of tuples we want to delete - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :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) - return result + options: WriteRequestOptions = WriteRequestOptions() | ( + options or WriteRequestOptions() + ) + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="DeleteTuples", + overwrite=True, + ) + + return await self.write(ClientWriteRequest(deletes=body), options) ####################### # Relationship Queries @@ -619,100 +562,100 @@ async def delete_tuples( async def check( self, body: ClientCheckRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ): - """ - Check whether a user is authorized to access an object - :param body - ClientCheckRequest defining check request - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - :param consistency(options) - The type of consistency preferred for the request - """ - kwargs = options_to_kwargs(options) - - req_body = CheckRequest( + options: CheckRequestOptions | None = None, + ) -> CheckResponse | ApiClientResponseProtocol: + options: CheckRequestOptions = CheckRequestOptions() | ( + options or CheckRequestOptions() + ) + + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="Check", + overwrite=False, + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + request = CheckRequest( tuple_key=TupleKey( user=body.user, relation=body.relation, object=body.object, ), context=body.context, - authorization_model_id=self._get_authorization_model_id(options), - consistency=self._get_consistency(options), + authorization_model_id=authorization_model_id, + consistency=options.consistency, ) + if body.contextual_tuples: - req_body.contextual_tuples = ContextualTupleKeys( + request.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = await self._api.check(body=req_body, **kwargs) - return api_response + + response = await self.api.check(request, options) + + if options.return_response: + return response + + return response.deserialized async def _single_client_batch_check( self, body: ClientCheckRequest, semaphore: asyncio.Semaphore, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ): - """ - 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 - """ + options: BatchCheckRequestOptions | None = None, + ) -> ClientBatchCheckClientResponse: + options: BatchCheckRequestOptions = BatchCheckRequestOptions() | ( + options or BatchCheckRequestOptions() + ) + await semaphore.acquire() + try: - api_response = await self.check(body, options) + response = await self.check(body, options) + return ClientBatchCheckClientResponse( - allowed=api_response.allowed, + allowed=response.allowed, request=body, - response=api_response, + response=response, error=None, ) + except (AuthenticationError, UnauthorizedException) as err: raise err + except Exception as err: return ClientBatchCheckClientResponse( allowed=False, request=body, response=None, error=err ) + finally: semaphore.release() async def client_batch_check( self, body: list[ClientCheckRequest], - options: dict[str, int | str | dict[str, int | str]] | None = None, - ): - """ - Run a set of checks - :param body - list of ClientCheckRequest defining check request - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param max_parallel_requests(options) - Max number of requests to issue in parallel. Defaults to 10 - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "BatchCheck") - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) - ) - - max_parallel_requests = 10 - if options is not None and "max_parallel_requests" in options: - if ( - isinstance(options["max_parallel_requests"], str) - and options["max_parallel_requests"].isdigit() - ): - max_parallel_requests = int(options["max_parallel_requests"]) - elif isinstance(options["max_parallel_requests"], int): - max_parallel_requests = options["max_parallel_requests"] - - sem = asyncio.Semaphore(max_parallel_requests) + options: BatchCheckRequestOptions | None = None, + ) -> list[ClientBatchCheckClientResponse]: + options: BatchCheckRequestOptions = BatchCheckRequestOptions() | ( + options or BatchCheckRequestOptions() + ) + + options.headers.use_bulk_request_id() + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="ClientBatchCheck", + overwrite=True, + ) + + sem = asyncio.Semaphore(options.max_parallel_requests) + batch_check_coros = [ self._single_client_batch_check(request, sem, options) for request in body ] + batch_check_response = await asyncio.gather(*batch_check_coros) return batch_check_response @@ -721,62 +664,42 @@ async def _single_batch_check( self, body: BatchCheckRequest, semaphore: asyncio.Semaphore, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ): - """ - Run a single BatchCheck request - :param body - list[ClientCheckRequest] defining check request - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - """ + options: BatchCheckRequestOptions | None = None, + ) -> BatchCheckResponse: + options: BatchCheckRequestOptions = BatchCheckRequestOptions() | ( + options or BatchCheckRequestOptions() + ) + await semaphore.acquire() + try: - kwargs = options_to_kwargs(options) - api_response = await self._api.batch_check(body, **kwargs) - return api_response + return await self.api.batch_check(body, options) + except Exception as err: raise err + finally: semaphore.release() async def batch_check( self, body: ClientBatchCheckRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: BatchCheckRequestOptions | None = None, ): - """ - Run a batchcheck request - :param body - BatchCheck request - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param max_parallel_requests(options) - Max number of requests to issue in parallel. Defaults to 10 - :param max_batch_size(options) - Max number of checks to include in a request. Defaults to 50 - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) - ) - - max_parallel_requests = 10 - if options is not None and "max_parallel_requests" in options: - if ( - isinstance(options["max_parallel_requests"], str) - and options["max_parallel_requests"].isdigit() - ): - max_parallel_requests = int(options["max_parallel_requests"]) - elif isinstance(options["max_parallel_requests"], int): - max_parallel_requests = options["max_parallel_requests"] - - max_batch_size = 50 - if options is not None and "max_batch_size" in options: - if ( - isinstance(options["max_batch_size"], str) - and options["max_batch_size"].isdigit() - ): - max_batch_size = int(options["max_batch_size"]) - elif isinstance(options["max_batch_size"], int): - max_batch_size = options["max_batch_size"] + options: BatchCheckRequestOptions = BatchCheckRequestOptions() | ( + options or BatchCheckRequestOptions() + ) + + options.headers.use_bulk_request_id() + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="BatchCheck", + overwrite=False, + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) id_to_check: dict[str, ClientBatchCheckItem] = {} @@ -798,13 +721,18 @@ def track_and_transform(checks): checks = [ track_and_transform( - body.checks[i * max_batch_size : (i + 1) * max_batch_size] + body.checks[ + i * options.max_batch_size : (i + 1) * options.max_batch_size + ] + ) + for i in range( + (len(body.checks) + options.max_batch_size - 1) + // options.max_batch_size ) - for i in range((len(body.checks) + max_batch_size - 1) // max_batch_size) ] result = [] - sem = asyncio.Semaphore(max_parallel_requests) + sem = asyncio.Semaphore(options.max_parallel_requests) def map_response(id, result): check = id_to_check[id] @@ -819,8 +747,8 @@ async def coro(checks): res = await self._single_batch_check( BatchCheckRequest( checks=checks, - authorization_model_id=self._get_authorization_model_id(options), - consistency=self._get_consistency(options), + authorization_model_id=authorization_model_id, + consistency=options.consistency, ), sem, options, @@ -838,127 +766,119 @@ async def coro(checks): async def expand( self, body: ClientExpandRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: ExpandRequestOptions | None = None, ): - """ - Run expand request - :param body - list of ClientExpandRequest defining expand request - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - :param consistency(options) - The type of consistency preferred for the request - """ - kwargs = options_to_kwargs(options) - - req_body = ExpandRequest( + options: ExpandRequestOptions = ExpandRequestOptions() | ( + options or ExpandRequestOptions() + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + request = ExpandRequest( tuple_key=ExpandRequestTupleKey( relation=body.relation, object=body.object, ), - authorization_model_id=self._get_authorization_model_id(options), - consistency=self._get_consistency(options), + authorization_model_id=authorization_model_id, + consistency=options.consistency, ) + if body.contextual_tuples: - req_body.contextual_tuples = ContextualTupleKeys( + request.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = await self._api.expand(body=req_body, **kwargs) - return api_response + + response = await self.api.expand(request, options) + + if options.return_response: + return response + + return response.deserialized async def list_objects( self, body: ClientListObjectsRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: ListObjectsRequestOptions | None = None, ): - """ - Run list object request - :param body - list object parameters - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - :param consistency(options) - The type of consistency preferred for the request - """ - kwargs = options_to_kwargs(options) - - req_body = ListObjectsRequest( - authorization_model_id=self._get_authorization_model_id(options), + options: ListObjectsRequestOptions = ListObjectsRequestOptions() | ( + options or ListObjectsRequestOptions() + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + request = ListObjectsRequest( + authorization_model_id=authorization_model_id, user=body.user, relation=body.relation, type=body.type, context=body.context, - consistency=self._get_consistency(options), + consistency=options.consistency, ) + if body.contextual_tuples: - req_body.contextual_tuples = ContextualTupleKeys( + request.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = await self._api.list_objects(body=req_body, **kwargs) - return api_response + + response = await self.api.list_objects(request, options) + + if options.return_response: + return response + + return response.deserialized async def streamed_list_objects( self, body: ClientListObjectsRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: ListObjectsRequestOptions | None = None, ): - """ - Retrieve all objects of the given type that the user has a relation with, using the streaming ListObjects API. - - :param body - list object parameters - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - :param consistency(options) - The type of consistency preferred for the request11 - """ - kwargs = options_to_kwargs(options) - kwargs["_streaming"] = True - - req_body = ListObjectsRequest( - authorization_model_id=self._get_authorization_model_id(options), + options: ListObjectsRequestOptions = ListObjectsRequestOptions() | ( + options or ListObjectsRequestOptions() + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + request = ListObjectsRequest( + authorization_model_id=authorization_model_id, user=body.user, relation=body.relation, type=body.type, context=body.context, - consistency=self._get_consistency(options), + consistency=options.consistency, ) if body.contextual_tuples: - req_body.contextual_tuples = ContextualTupleKeys( + request.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - async for response in await self._api.streamed_list_objects( - body=req_body, **kwargs - ): + async for response in await self.api.streamed_list_objects(request, option): if response and "result" in response and "object" in response["result"]: yield StreamedListObjectsResponse(response["result"]["object"]) async def list_relations( self, body: ClientListRelationsRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: ListRelationsRequestOptions | None = None, ): - """ - Return all the relations for which user has a relationship with the object - :param body - list relation request - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - :param consistency(options) - The type of consistency preferred for the request - """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "ListRelations") - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) - ) - - request_body = [ + options: ListRelationsRequestOptions = ListRelationsRequestOptions() | ( + options or ListRelationsRequestOptions() + ) + + options.headers.use_bulk_request_id() + options.headers.add_header( + name=HttpHeaderKeys.FGA_CLIENT_METHOD.value, + value="ListRelations", + overwrite=False, + ) + + request = [ construct_check_request( user=body.user, relation=i, @@ -968,97 +888,90 @@ async def list_relations( ) for i in body.relations ] - result = await self.client_batch_check(request_body, options) - # need to filter with the allowed response - result_iterator = filter(_check_allowed, result) + result = await self.client_batch_check(request, options) + + result_iterator = filter(lambda x: x.allowed, result) result_list = list(result_iterator) + return [i.request.relation for i in result_list] async def list_users( self, body: ClientListUsersRequest, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: ListUsersRequestOptions | None = None, ): - """ - Run list users request - :param body - list user parameters - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - :param consistency(options) - The type of consistency preferred for the request - """ - kwargs = options_to_kwargs(options) - - req_body = ListUsersRequest( - authorization_model_id=self._get_authorization_model_id(options), + options: ListUsersRequestOptions = ListUsersRequestOptions() | ( + options or ListUsersRequestOptions() + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + request = ListUsersRequest( + authorization_model_id=authorization_model_id, object=body.object, relation=body.relation, user_filters=body.user_filters, contextual_tuples=body.contextual_tuples, context=body.context, - consistency=self._get_consistency(options), + consistency=options.consistency, ) if body.contextual_tuples: - req_body.contextual_tuples = convert_tuple_keys(body.contextual_tuples) + request.contextual_tuples = convert_tuple_keys(body.contextual_tuples) + + response = await self.api.list_users(request, options) - api_response = await self._api.list_users(body=req_body, **kwargs) + if options.return_response: + return response - return api_response + return response.deserialized ####################### # Assertions ####################### async def read_assertions( - self, options: dict[str, int | str | dict[str, int | str]] | None = None + self, + options: ReadAssertionsRequestOptions | None = None, ): - """ - Return the assertions - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - - 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) - return api_response + options: ReadAssertionsRequestOptions = ReadAssertionsRequestOptions() | ( + options or ReadAssertionsRequestOptions() + ) + + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id + ) + + response = await self.api.read_assertions(authorization_model_id, options) + + if options.return_response: + return response + + return response.deserialized async def write_assertions( self, body: list[ClientAssertion], - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: WriteAssertionsRequestOptions | None = None, ): - """ - Upsert the assertions - :param body - Write assertion request - :param authorization_model_id(options) - Overrides the authorization model id in the configuration - :param header(options) - Custom headers to send alongside the request - :param retryParams(options) - Override the retry parameters for this request - :param retryParams.maxRetry(options) - Override the max number of retries on each API request - :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated - """ - kwargs = options_to_kwargs(options) - authorization_model_id = self._get_authorization_model_id(options) - - def map_to_assertion(client_assertion: ClientAssertion): - return Assertion( - TupleKey( - user=client_assertion.user, - relation=client_assertion.relation, - object=client_assertion.object, - ), - client_assertion.expectation, - ) + options: WriteAssertionsRequestOptions = WriteAssertionsRequestOptions() | ( + options or WriteAssertionsRequestOptions() + ) - api_request_body = WriteAssertionsRequest( - [map_to_assertion(client_assertion) for client_assertion in body] + authorization_model_id = ( + options.authorization_model_id or self.configuration.authorization_model_id ) - api_response = await self._api.write_assertions( - authorization_model_id, api_request_body, **kwargs + + request = WriteAssertionsRequest( + [assertion.to_assertion() for assertion in body] ) - return api_response + + response = await self.api.write_assertions( + authorization_model_id, request, options + ) + + if options.return_response: + return response + + return response.deserialized diff --git a/openfga_sdk/client/configuration.py b/openfga_sdk/client/configuration.py index 45868578..d099bd9d 100644 --- a/openfga_sdk/client/configuration.py +++ b/openfga_sdk/client/configuration.py @@ -10,56 +10,10 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ +import os + from openfga_sdk.configuration import Configuration -from openfga_sdk.exceptions import FgaValidationException -from openfga_sdk.validation import is_well_formed_ulid_string class ClientConfiguration(Configuration): - """ - OpenFGA client configuration - """ - - def __init__( - self, - api_scheme="https", - api_host=None, - store_id=None, - credentials=None, - retry_params=None, - authorization_model_id=None, - ssl_ca_cert=None, - api_url=None, # TODO: restructure when removing api_scheme/api_host - timeout_millisec: int | None = None, - ): - super().__init__( - api_scheme, - api_host, - store_id, - credentials, - retry_params, - ssl_ca_cert=ssl_ca_cert, - api_url=api_url, - timeout_millisec=timeout_millisec, - ) - self._authorization_model_id = authorization_model_id - - def is_valid(self): - super().is_valid() - - if ( - self.authorization_model_id is not None - and self.authorization_model_id != "" - and is_well_formed_ulid_string(self.authorization_model_id) is False - ): - raise FgaValidationException( - f"authorization_model_id ('{self.authorization_model_id}') is not in a valid ulid format" - ) - - @property - def authorization_model_id(self): - return self._authorization_model_id - - @authorization_model_id.setter - def authorization_model_id(self, value): - self._authorization_model_id = value + pass diff --git a/openfga_sdk/client/models/__init__.py b/openfga_sdk/client/models/__init__.py index a43a3202..ea0228f2 100644 --- a/openfga_sdk/client/models/__init__.py +++ b/openfga_sdk/client/models/__init__.py @@ -28,7 +28,6 @@ from openfga_sdk.client.models.tuple import ClientTuple from openfga_sdk.client.models.write_request import ClientWriteRequest from openfga_sdk.client.models.write_response import ClientWriteResponse -from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts __all__ = [ @@ -46,5 +45,4 @@ "ClientTuple", "ClientWriteRequest", "ClientWriteResponse", - "WriteTransactionOpts", ] diff --git a/openfga_sdk/client/models/assertion.py b/openfga_sdk/client/models/assertion.py index c8335e20..3b8a42a7 100644 --- a/openfga_sdk/client/models/assertion.py +++ b/openfga_sdk/client/models/assertion.py @@ -10,76 +10,25 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ +from dataclasses import dataclass -class ClientAssertion: - """ - ClientAssertion flattens the input necessary for an Assertion - """ - - def __init__( - self, - user: str, - relation: str, - object: str, - expectation: bool, - ) -> None: - self._user = user - self._relation = relation - self._object = object - self._expectation = expectation - - @property - def user(self) -> str: - """ - Return user - """ - return self._user - - @user.setter - def user(self, user: str) -> None: - """ - Set user - """ - self._user = user - - @property - def relation(self) -> str: - """ - Return relation - """ - return self._relation +from openfga_sdk.models.assertion import Assertion +from openfga_sdk.models.tuple_key import TupleKey - @relation.setter - def relation(self, relation: str) -> None: - """ - Set relation - """ - self._relation = relation - @property - def object(self) -> str: - """ - Return object - """ - return self._object - - @object.setter - def object(self, object: str) -> None: - """ - Set object - """ - self._object = object - - @property - def expectation(self) -> bool: - """ - Return expectation - """ - return self._expectation - - @expectation.setter - def expectation(self, expectation: bool) -> None: - """ - Set expectation - """ - self._expectation = expectation +@dataclass +class ClientAssertion: + user: str + relation: str + object: str + expectation: bool + + def to_assertion(self) -> Assertion: + return Assertion( + TupleKey( + user=self.user, + relation=self.relation, + object=self.object, + ), + self.expectation, + ) diff --git a/openfga_sdk/client/models/read_changes_request.py b/openfga_sdk/client/models/read_changes_request.py index a8257d1f..9385cb8b 100644 --- a/openfga_sdk/client/models/read_changes_request.py +++ b/openfga_sdk/client/models/read_changes_request.py @@ -18,7 +18,7 @@ class ClientReadChangesRequest: def __init__( self, - type: str, + type: str = "", start_time: str | None = None, ): self._type = type diff --git a/openfga_sdk/client/models/write_transaction_opts.py b/openfga_sdk/client/models/write_transaction_opts.py deleted file mode 100644 index 0e914a53..00000000 --- a/openfga_sdk/client/models/write_transaction_opts.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Python SDK for OpenFGA - -API version: 1.x -Website: https://openfga.dev -Documentation: https://openfga.dev/docs -Support: https://openfga.dev/community -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. -""" - - -class WriteTransactionOpts: - """ - OpenFGA client write transaction info - """ - - def __init__( - self, - disabled: bool = False, - max_per_chunk: int = 1, - max_parallel_requests: int = 10, - ) -> None: - self._disabled = disabled - self._max_per_chunk = max_per_chunk - self._max_parallel_requests = max_parallel_requests - - @property - def disabled(self) -> bool: - """ - Return disabled - """ - return self._disabled - - @disabled.setter - def disabled( - self, - value: bool, - ) -> None: - """ - Set disabled - """ - self._disabled = value - - @property - def max_per_chunk(self) -> int: - """ - Return max per chunk - """ - return self._max_per_chunk - - @max_per_chunk.setter - def max_per_chunk( - self, - value: int, - ) -> None: - """ - Set max_per_chunk - """ - self._max_per_chunk = value - - @property - def max_parallel_requests(self) -> int: - """ - Return max parallel requests - """ - return self._max_parallel_requests - - @max_parallel_requests.setter - def max_parallel_requests( - self, - value: int, - ) -> None: - """ - Set max_parallel_requests - """ - self._max_parallel_requests = value diff --git a/openfga_sdk/common/api_client.py b/openfga_sdk/common/api_client.py new file mode 100644 index 00000000..b0ad13c1 --- /dev/null +++ b/openfga_sdk/common/api_client.py @@ -0,0 +1,128 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 atexit +import datetime + +from dataclasses import dataclass, field +from multiprocessing.pool import ThreadPool +from openfga_sdk.common.cookies import HttpCookies +from openfga_sdk.common.factory import FactoryConsumer +from openfga_sdk.common.headers import HttpHeaders +from openfga_sdk.common.math import Math +from openfga_sdk.exceptions import ( + RateLimitExceededError, + ServiceException, +) +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ConfigurationProtocol, + FactoryProtocol, + ApiClientProtocol, + HttpHeadersProtocol, + HttpCookiesProtocol, + MergeableDataclassMixin, + RestClientRequestProtocol, + RestClientResponseProtocol, +) + + +@dataclass +class ApiClientResponse(MergeableDataclassMixin, ApiClientResponseProtocol): + retries: int = 0 + exception: Exception | None = None + request: RestClientRequestProtocol | None = None + response: RestClientResponseProtocol | None = None + + @property + def status(self) -> int | None: + if self.response: + return self.response.status + + +@dataclass +class ApiClientBase(FactoryConsumer, ApiClientProtocol): + configuration: ConfigurationProtocol + factory: FactoryProtocol | None = None + headers: HttpHeadersProtocol | None = field(default_factory=HttpHeaders) + cookies: HttpCookiesProtocol | None = field(default_factory=HttpCookies) + pool_threads: int = 1 + user_agent: str | None = None + pool: ThreadPool | None = None + + DEFAULT_USER_AGENT = "openfga-sdk python/0.9.1" + + PRIMITIVE_TYPES = (float, bool, bytes, str, int) + + NATIVE_TYPES_MAPPING = { + "int": int, + "long": int, + "float": float, + "str": str, + "bool": bool, + "date": datetime.date, + "datetime": datetime.datetime, + "object": object, + } + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def close(self): + if self.pool: + self.pool.close() + self.pool.join() + self.pool = None + + if hasattr(atexit, "unregister") and callable(atexit.unregister): + atexit.unregister(self.close) + + @property + def _pool(self) -> ThreadPool: + if self.pool is None: + atexit.register(self.close) + self.pool = ThreadPool(self.pool_threads) + + return self.pool + + def _should_retry( + self, + response_status: int, + retry: int, + max_retries: int, + min_wait: int, + e: Exception | None = None, + ) -> int | None: + if e is None or not e in [RateLimitExceededError, ServiceException]: + return None + + if response_status == 501: + return None + + if retry == max_retries: + return None + + return Math.jitter(retry, min_wait) + + def _handle_exception( + self, + e: Exception | None = None, + response_type: tuple[int, str] | None = None, + ) -> None: + if e is None: + return + + if response_type is not None: + raise e diff --git a/openfga_sdk/common/client.py b/openfga_sdk/common/client.py new file mode 100644 index 00000000..11ad7a1b --- /dev/null +++ b/openfga_sdk/common/client.py @@ -0,0 +1,160 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 dataclasses import dataclass +from openfga_sdk.client.models.client_batch_check_response import ( + ClientBatchCheckClientResponse, +) +from openfga_sdk.common.factory import FactoryConsumer +from openfga_sdk.exceptions import FgaValidationException +from openfga_sdk.protocols import ( + ApiClientProtocol, + OAuth2ClientProtocol, + OpenFgaApiProtocol, + OpenFgaClientProtocol, + ConfigurationProtocol, + FactoryProtocol, + RestClientProtocol, +) +from openfga_sdk.validation import is_well_formed_ulid_string + + +@dataclass +class OpenFgaClientBase(FactoryConsumer, OpenFgaClientProtocol): + configuration: ConfigurationProtocol + factory: FactoryProtocol | None = None + + def _get_authorization_model_id( + self, + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> str | None: + """ + Return the authorization model ID if specified in the options. + Otherwise, return the authorization model ID stored in the client's configuration + """ + authorization_model_id = self.configuration.authorization_model_id + + if ( + options is not None + and "authorization_model_id" in options + and type(options["authorization_model_id"]) is str + ): + authorization_model_id = options["authorization_model_id"] + + if authorization_model_id is None or authorization_model_id == "": + return None + + if is_well_formed_ulid_string(authorization_model_id) is False: + raise FgaValidationException( + f"authorization_model_id ('{authorization_model_id}') is not in a valid ulid format" + ) + + return authorization_model_id + + def set_store_id(self, value): + """ + Update the store ID in the configuration + """ + self.configuration.store_id = value + + def get_store_id(self): + """ + Return the store id (if any) store in the configuration + """ + return self.configuration.store_id + + def set_authorization_model_id(self, value): + """ + Update the authorization model id in the configuration + """ + self.configuration.authorization_model_id = value + + def get_authorization_model_id(self): + """ + Return the authorization model id + """ + return self.configuration.authorization_model_id + + @staticmethod + def _get_consistency( + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> str | None: + """ + Returns the consistency requested if specified in the options. + Otherwise, returns None. + """ + consistency: int | str | dict[str, int | str] | None = ( + options.get("consistency", None) if options is not None else None + ) + + if type(consistency) is str: + return consistency + + return None + + @staticmethod + def _chuck_array(array, max_size): + """ + Helper function to chuck array into arrays of max_size + """ + return [ + array[i * max_size : (i + 1) * max_size] + for i in range((len(array) + max_size - 1) // max_size) + ] + + @staticmethod + def _set_heading_if_not_set( + options: dict[str, int | str | dict[str, int | str]] | None, + name: str, + value: str, + ) -> dict[str, int | str | dict[str, int | str]]: + """ + Set heading to the value if it is not set + """ + _options: dict[str, int | str | dict[str, int | str]] = ( + options if options is not None else {} + ) + + if type(_options.get("headers")) is not dict: + _options["headers"] = {} + + if type(_options["headers"]) is dict: + if type(_options["headers"].get(name)) not in [int, str]: + _options["headers"][name] = value + + return _options + + @staticmethod + def _options_to_kwargs( + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> dict[str, int | str | dict[str, int | str]]: + """ + Return kwargs with continuation_token and page_size + """ + kwargs = {} + if options is not None: + if options.get("page_size"): + kwargs["page_size"] = options["page_size"] + if options.get("continuation_token"): + kwargs["continuation_token"] = options["continuation_token"] + if options.get("headers"): + kwargs["_headers"] = options["headers"] + if options.get("retry_params"): + kwargs["_retry_params"] = options["retry_params"] + return kwargs + + @staticmethod + def _check_allowed(response: ClientBatchCheckClientResponse): + """ + Helper function to return whether the response is check is allowed + """ + return response.allowed diff --git a/openfga_sdk/common/cookies.py b/openfga_sdk/common/cookies.py new file mode 100644 index 00000000..904bacc2 --- /dev/null +++ b/openfga_sdk/common/cookies.py @@ -0,0 +1,114 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 dataclasses import dataclass, field, fields + +from openfga_sdk.protocols import ( + HttpCookieProtocol, + HttpCookiesProtocol, + StoreRequestOptionsProtocol, +) + + +@dataclass +class HttpCookie(HttpCookieProtocol): + name: str + value: str + domain: str | None = None + path: str | None = "/" + secure: bool = False + http_only: bool = False + max_age: int | None = None + expires: str | None = None + + def __str__(self) -> str: + cookie = f"{self.name}={self.value}" + if self.domain: + cookie += f"; Domain={self.domain}" + if self.path: + cookie += f"; Path={self.path}" + if self.secure: + cookie += "; Secure" + if self.http_only: + cookie += "; HttpOnly" + if self.max_age is not None: + cookie += f"; Max-Age={self.max_age}" + if self.expires: + cookie += f"; Expires={self.expires}" + return cookie + + +@dataclass +class HttpCookies(HttpCookiesProtocol): + cookies: dict[str, HttpCookie] = field(default_factory=dict) + + def add_cookie(self, name: str, value: str, **kwargs) -> None: + self.cookies[name] = HttpCookie(name=name, value=value, **kwargs) + + def get_cookie(self, name: str) -> HttpCookieProtocol | None: + return self.cookies.get(name) + + def remove_cookie(self, name: str) -> None: + self.cookies.pop(name, None) + + def merge(self, other: "HttpCookiesProtocol") -> None: + for name, cookies_list in other.cookies.items(): + if name in self.cookies: + self.cookies[name].extend(cookies_list) + else: + self.cookies[name] = cookies_list.copy() + + def as_header(self) -> str: + return "; ".join(str(cookie) for cookie in self.cookies.values()) + + def __getitem__(self, name: str) -> HttpCookieProtocol | None: + return self.get_cookie(name) + + def __setitem__(self, name: str, value: HttpCookieProtocol) -> None: + self.cookies[name] = value + + def __delitem__(self, name: str) -> None: + self.remove_cookie(name) + + def __contains__(self, name: str) -> bool: + return name in self.cookies + + def __iter__(self): + return iter(self.cookies) + + def items(self): + return self.cookies.items() + + def __str__(self) -> str: + return "\n".join(str(cookie) for cookie in self.cookies.values()) + + @staticmethod + def from_options( + options: StoreRequestOptionsProtocol | None, + ) -> "HttpCookiesProtocol": + cookies = HttpCookies() + + if options is None: + return cookies + + for field in fields(options): + value = getattr(options, field.name) + + if value is None: + continue + + match field.name: + case "cookies": + if isinstance(value, HttpCookiesProtocol): + cookies.merge(value) + + return cookies diff --git a/openfga_sdk/common/factory.py b/openfga_sdk/common/factory.py new file mode 100644 index 00000000..ac38b497 --- /dev/null +++ b/openfga_sdk/common/factory.py @@ -0,0 +1,65 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 dataclasses import dataclass +from openfga_sdk.protocols import ( + FactoryProtocol, + FactoryConsumerProtocol, + OAuth2ClientProtocol, + OpenFgaApiProtocol, + ApiClientProtocol, + RestClientProtocol, +) +from openfga_sdk.telemetry.telemetry import Telemetry + + +class FactoryConsumer(FactoryConsumerProtocol): + @property + def _factory(self) -> FactoryProtocol: ... + + @property + def _factory_async(self) -> FactoryProtocol: + if self.factory is None: + from openfga_sdk.factory import Factory + + self.factory = Factory(self.configuration) + + return self.factory + + @property + def _factory_sync(self) -> FactoryProtocol: + if self.factory is None: + from openfga_sdk.sync.factory import Factory + + self.factory = Factory(self.configuration) + + return self.factory + + @property + def auth_client(self) -> OAuth2ClientProtocol: + return self._factory.auth_client + + @property + def api(self) -> OpenFgaApiProtocol: + return self._factory.api + + @property + def api_client(self) -> ApiClientProtocol: + return self._factory.api_client + + @property + def rest_client(self) -> RestClientProtocol: + return self._factory.rest_client + + @property + def telemetry_client(self) -> Telemetry: + return self._factory.telemetry_client diff --git a/openfga_sdk/common/headers.py b/openfga_sdk/common/headers.py new file mode 100644 index 00000000..d4eed15d --- /dev/null +++ b/openfga_sdk/common/headers.py @@ -0,0 +1,132 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 dataclasses import dataclass, field, fields +from enum import Enum +import uuid + +from openfga_sdk.protocols import ( + HttpHeaderProtocol, + HttpHeadersProtocol, + MergeableDataclassMixin, + StoreRequestOptionsProtocol, +) + + +class HttpHeaderKeys(Enum): + CONTENT_TYPE = "Content-Type" + FGA_CLIENT_METHOD = "X-OpenFGA-Client-Method" + FGA_CLIENT_BULK_REQUEST_ID_HEADER = "X-OpenFGA-Client-Bulk-Request-Id" + + +@dataclass +class HttpHeader(HttpHeaderProtocol): + name: str + value: str + + +@dataclass +class HttpHeaders(MergeableDataclassMixin, HttpHeadersProtocol): + headers: dict[str, HttpHeaderProtocol] = field(default_factory=dict) + + def add_header(self, name: str, value: str, overwrite: bool | None = None) -> None: + key = name.lower() + + if overwrite == False and key in self.headers: + return + + header = HttpHeader(name=name, value=value) + + if overwrite == True: + self.headers[key] = [header] + + self.headers.setdefault(key, []).append(header) + + def insert_header(self, header: HttpHeaderProtocol) -> None: + key = header.name.lower() + self.headers.setdefault(key, []).append(header) + + def use_bulk_request_id(self, value: str | None = None) -> None: + self.add_header( + HttpHeaderKeys.FGA_CLIENT_BULK_REQUEST_ID_HEADER.value, + value or str(uuid.uuid4()), + ) + + def get_header(self, name: str) -> list: + return [header for header in self.headers.get(name.lower(), [])] + + def remove_header(self, name: str) -> None: + self.headers.pop(name.lower(), None) + + def merge(self, other: "HttpHeadersProtocol") -> None: + for name, headers_list in other.headers.items(): + if name in self.headers: + self.headers[name].extend(headers_list) + else: + self.headers[name] = headers_list.copy() + + def as_dict(self) -> dict[str, str]: + return {name: value for name, value in self.headers.items()} + + def __getitem__(self, name: str) -> list: + return self.get_header(name) + + def __setitem__(self, name: str, value: str) -> None: + self.headers[name.lower()] = [HttpHeader(name=name, value=value)] + + def __delitem__(self, name: str) -> None: + self.remove_header(name) + + def __contains__(self, name: str) -> bool: + return name.lower() in self.headers + + def __iter__(self): + return iter(self.headers) + + def items(self): + for key, headers in self.headers.items(): + for header in headers: + yield (header.name, header.value) + + def __str__(self) -> str: + return "\n".join( + str(header) for headers in self.headers.values() for header in headers + ) + + @staticmethod + def for_json() -> "HttpHeadersProtocol": + headers = HttpHeaders() + headers.add_header("Content-Type", "application/json") + headers.add_header("Accept", "application/json") + return headers + + @staticmethod + def from_options( + options: StoreRequestOptionsProtocol | None, + ) -> "HttpHeadersProtocol": + headers = HttpHeaders() + + if options is None: + return headers + + for field in fields(options): + value = getattr(options, field.name) + + if value is None: + continue + + match field.name: + case "headers": + if isinstance(value, HttpHeadersProtocol): + headers.merge(value) + + return headers diff --git a/openfga_sdk/common/math.py b/openfga_sdk/common/math.py new file mode 100644 index 00000000..a118bb17 --- /dev/null +++ b/openfga_sdk/common/math.py @@ -0,0 +1,30 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 math +import random +import sys + + +class Math: + @staticmethod + def jitter( + loop_count: int, + min_wait_in_ms: int, + ) -> int: + minimum = int(math.ceil(2**loop_count * min_wait_in_ms)) + maximum = int(math.ceil(2 ** (loop_count + 1) * min_wait_in_ms)) + + if "pytest" in sys.modules or minimum == maximum: + return 0 + + return int(random.randrange(minimum, maximum) / 1000) diff --git a/openfga_sdk/common/open_fga_api.py b/openfga_sdk/common/open_fga_api.py new file mode 100644 index 00000000..4d1bb987 --- /dev/null +++ b/openfga_sdk/common/open_fga_api.py @@ -0,0 +1,340 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 datetime +import functools +import logging +import re + +from dataclasses import dataclass +from typing import Any + +import orjson + +from dateutil.parser import parse # type: ignore[import-untyped] + +import openfga_sdk.models + +from openfga_sdk.common.factory import FactoryConsumer +from openfga_sdk.exceptions import ApiException +from openfga_sdk.protocols import ( + ApiResponseProtocol, + ConfigurationProtocol, + FactoryProtocol, + FgaModelProtocol, + MergeableDataclassMixin, + OpenFgaApiProtocol, + RestClientRequestProtocol, + RestClientResponseProtocol, +) + + +logger = logging.getLogger(__name__) + + +@dataclass +class ApiResponse(MergeableDataclassMixin, ApiResponseProtocol): + retries: int = 0 + exception: Exception | None = None + request: RestClientRequestProtocol | None = None + response: RestClientResponseProtocol | None = None + deserialized: FgaModelProtocol | None = None + + @property + def status(self) -> int | None: + if self.response: + return self.response.status + + +@dataclass +class OpenFgaApiBase(FactoryConsumer, OpenFgaApiProtocol): + configuration: ConfigurationProtocol + factory: FactoryProtocol | None = None + + TYPE_PATTERN = re.compile(r"(?Plist|dict)\[(?P.+)\]") + + PRIMITIVE_TYPES = (float, bool, bytes, str, int) + + NATIVE_TYPES_MAPPING = { + "int": int, + "long": int, + "float": float, + "str": str, + "bool": bool, + "date": datetime.date, + "datetime": datetime.datetime, + "object": object, + } + + @staticmethod + @functools.lru_cache() + def build_response_types( + extra_response_type: tuple[int, str] | None = None, + ) -> tuple[tuple[int, str], ...]: + """ + Build the list of response types, including optional extra response types. + + Args: + extra_response_type (tuple[int, str] | None): An optional additional response type. + + Returns: + tuple[tuple[int, str], ...]: A tuple of response type mappings. + """ + + response_types = ( + (400, "ValidationErrorMessageResponse"), + (401, "UnauthenticatedResponse"), + (403, "ForbiddenResponse"), + (404, "PathUnknownErrorMessageResponse"), + (409, "AbortedMessageResponse"), + (422, "UnprocessableContentMessageResponse"), + (500, "InternalErrorMessageResponse"), + ) + + if extra_response_type: + return response_types + (extra_response_type,) + + return response_types + + def deserialize( + self, + response: RestClientResponseProtocol, + response_types: list[tuple[int, str]], + ): + """ + Deserialize a response to the appropriate model based on the status code. + + Args: + response (RestClientResponseProtocol): The HTTP response to deserialize. + response_types (list[tuple[int, str]]): Tuples of status code and response class. + + Returns: + Any: The deserialized object or raw data if no matching response type is found. + + Raises: + ValueError: If no matching response type is found and raw data is not allowed. + """ + + # response.data is a JSON string; convert to dict + data = None if not response.data else orjson.loads(response.data) + + for status, response_type in response_types: + if response.status == status: + return self._deserialize(data, response_type) + + return data + + def _deserialize( + self, + data: Any, + response_type: str | type, + ) -> Any: + if data is None: + return None + + logger.debug(f"_deserialize: data: {data}, response_type: {response_type}") + + if isinstance(response_type, type): + if response_type in self.PRIMITIVE_TYPES: + return OpenFgaApiBase._deserialize_primitive(data, response_type) + + if response_type is object: + return data + + if response_type is datetime.date: + return OpenFgaApiBase._deserialize_date(data) + + if response_type is datetime.datetime: + return OpenFgaApiBase._deserialize_datetime(data) + + return self._deserialize_model(data, response_type) + + if isinstance(response_type, str): + parsed_type = self._process_nested_types(response_type) + logger.debug(f"Parsed type: {parsed_type}") + + if isinstance(parsed_type, list): + sub_class = parsed_type[0] + return [self._deserialize(sub_data, sub_class) for sub_data in data] + + if isinstance(parsed_type, dict): + key_kls = parsed_type["key"] + value_kls = parsed_type["value"] + return { + self._deserialize(k, key_kls): self._deserialize(v, value_kls) + for k, v in data.items() + } + + if parsed_type in self.NATIVE_TYPES_MAPPING: + response_type = self.NATIVE_TYPES_MAPPING[parsed_type] + else: + try: + response_type = getattr(openfga_sdk.models, parsed_type) + except AttributeError: + raise ValueError(f"Unknown response type: {parsed_type}") + + return self._deserialize(data, response_type) + + def _deserialize_model( + self, + data: dict | list, + response_class: str | type, + ) -> Any: + if not isinstance(response_class, str | type): + raise TypeError( + f"Expected a string or class for response_class, got {type(response_class).__name__}: {response_class}" + ) + + if isinstance(response_class, str): + response_class = OpenFgaApiBase._get_openapi_model(response_class) + + if not response_class: + raise ValueError( + f"Model {response_class} not found in `openfga_sdk.models`" + ) + + if not getattr(response_class, "openapi_types", None): + allowable_values: list | None = getattr( + response_class, "allowable_values", None + ) + + if isinstance(allowable_values, list) and data in allowable_values: + logger.debug( + f"_deserialize: data: {data}, response_type: {response_class}, HIT HIT HIT" + ) + return data + + return response_class(data) + + attributes = {} + + for attr, attr_type in response_class.openapi_types.items(): + json_key = response_class.attribute_map.get(attr, attr) + + if json_key in data and data[json_key] is not None: + value = data[json_key] + attributes[attr] = self._deserialize(value, attr_type) + + return response_class(**attributes) + + @staticmethod + @functools.lru_cache() + def _get_openapi_model(model_name: str) -> type | None: + if not isinstance(model_name, str): + raise TypeError( + f"Expected a string for model_name, got {type(model_name).__name__}: {model_name}" + ) + + model_class = getattr(openfga_sdk.models, model_name, None) + + if not model_class: + logger.warning(f"Model {model_name} not found in `openfga_sdk.models`") + + return model_class + + @staticmethod + @functools.lru_cache() + def _process_nested_types(type_str: str) -> list | dict | str: + """ + Process nested type strings like `list[Store]` or `dict[str, Store]`. + """ + match = OpenFgaApiBase.TYPE_PATTERN.match(type_str) + + if not match: + return type_str + + container = match.group("container") + content = match.group("content") + + try: + if container == "list": + return [OpenFgaApiBase._process_nested_types(content.strip())] + + if container == "dict": + key_type, value_type = map(str.strip, content.split(",", 1)) + return { + "key": OpenFgaApiBase._process_nested_types(key_type), + "value": OpenFgaApiBase._process_nested_types(value_type), + } + + if container == "tuple": + return tuple( + OpenFgaApiBase._process_nested_types(c.strip()) + for c in content.split(",") + ) + + if container == "set": + return set( + OpenFgaApiBase._process_nested_types(c.strip()) + for c in content.split(",") + ) + + except Exception as e: + logger.debug(f"Failed to parse type string `{type_str}`: {e}") + return type_str + + return type_str + + @staticmethod + @functools.lru_cache() + def _deserialize_primitive(data: Any, response_class: type) -> Any: + """ + Deserialize primitive types (int, float, str, bool, etc.). + """ + try: + return ( + response_class(data) if not isinstance(data, response_class) else data + ) + except (ValueError, TypeError): + logger.debug( + f"Failed to cast `{data}` to `{response_class.__name__}`. Returning raw data." + ) + return data + + @staticmethod + @functools.lru_cache() + def _deserialize_date(string: str) -> datetime.date | str: + """ + Deserialize a string into a date object. + + Args: + string (str): The date string to parse. + + Returns: + datetime.date | str: The parsed date or the raw string if parsing fails. + """ + try: + return datetime.datetime.strptime(string, "%Y-%m-%d").date() + except (ValueError, TypeError) as e: + logger.warning(f"Failed to parse `{string}` as date: {e}") + return string + + @staticmethod + @functools.lru_cache() + def _deserialize_datetime(string: str) -> datetime.datetime | str: + """ + Deserialize a string into a datetime object. + + Args: + string (str): The datetime string to parse. + + Returns: + datetime.datetime | str: The parsed datetime or the raw string if parsing fails. + """ + try: + return datetime.datetime.strptime(string, "%Y-%m-%dT%H:%M:%S.%fZ") + except ValueError: + try: + return parse(string, fuzzy=False) + except (ValueError, TypeError) as e: + logger.warning(f"Failed to parse `{string}` as datetime: {e}") + return string diff --git a/openfga_sdk/common/options.py b/openfga_sdk/common/options.py new file mode 100644 index 00000000..8ad7e1e9 --- /dev/null +++ b/openfga_sdk/common/options.py @@ -0,0 +1,134 @@ +from dataclasses import asdict, dataclass, field + +from openfga_sdk.common.cookies import HttpCookies +from openfga_sdk.common.headers import HttpHeaders +from openfga_sdk.configuration import RetryParams +from openfga_sdk.protocols import ( + HttpCookiesProtocol, + HttpHeadersProtocol, + MergeableDataclassMixin, + RetryParamsProtocol, + StoreRequestOptionsProtocol, +) + + +@dataclass +class StoreRequestOptionsBase(MergeableDataclassMixin, StoreRequestOptionsProtocol): + store_id: str | None = None + authorization_model_id: str | None = None + headers: HttpHeadersProtocol = field(default_factory=lambda: HttpHeaders()) + cookies: HttpCookiesProtocol = field(default_factory=lambda: HttpCookies()) + retry_params: RetryParamsProtocol = field(default_factory=lambda: RetryParams()) + return_response: bool = False + timeout: int | None = None + + +@dataclass +class CreateStoreRequestOptions(StoreRequestOptionsBase): + pass + + +@dataclass +class ListStoresRequestOptions(StoreRequestOptionsBase): + page_size: int | None = None + continuation_token: str | None = None + + +@dataclass +class GetStoreRequestOptions(StoreRequestOptionsBase): + pass + + +@dataclass +class DeleteStoreRequestOptions(StoreRequestOptionsBase): + pass + + +@dataclass +class ReadAuthorizationModelsRequestOptions(StoreRequestOptionsBase): + page_size: int | None = None + continuation_token: str | None = None + + +@dataclass +class WriteAuthorizationModelRequestOptions(StoreRequestOptionsBase): + store_id: str | None = None + + +@dataclass +class ReadAuthorizationModelRequestOptions(StoreRequestOptionsBase): + pass + + +@dataclass +class ReadLatestAuthorizationModelRequestOptions(StoreRequestOptionsBase): + pass + + +@dataclass +class ReadChangesRequestOptions(StoreRequestOptionsBase): + page_size: int | None = None + continuation_token: str | None = None + + +@dataclass +class ReadRequestOptions(StoreRequestOptionsBase): + page_size: int | None = None + continuation_token: str | None = None + consistency: str | None = None + + +@dataclass +class WriteTransactionOptions: + disabled: bool = False + max_per_chunk: int = 1 + max_parallel_requests: int = 10 + + +@dataclass +class BatchCheckRequestOptions(StoreRequestOptionsBase): + max_batch_size: int = 50 + max_parallel_requests: int = 10 + consistency: str | None = None + + +@dataclass +class WriteRequestOptions(StoreRequestOptionsBase): + transaction: WriteTransactionOptions = field( + default_factory=WriteTransactionOptions + ) + + +@dataclass +class CheckRequestOptions(StoreRequestOptionsBase): + consistency: str | None = None + + +@dataclass +class ExpandRequestOptions(StoreRequestOptionsBase): + consistency: str | None = None + + +@dataclass +class ListObjectsRequestOptions(StoreRequestOptionsBase): + consistency: str | None = None + + +@dataclass +class ListRelationsRequestOptions(StoreRequestOptionsBase): + pass + + +@dataclass +class ListUsersRequestOptions(StoreRequestOptionsBase): + consistency: str | None = None + + +@dataclass +class ReadAssertionsRequestOptions(StoreRequestOptionsBase): + pass + + +@dataclass +class WriteAssertionsRequestOptions(StoreRequestOptionsBase): + pass diff --git a/openfga_sdk/common/rest.py b/openfga_sdk/common/rest.py new file mode 100644 index 00000000..35a9d418 --- /dev/null +++ b/openfga_sdk/common/rest.py @@ -0,0 +1,417 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 dataclasses import asdict, dataclass, field +from enum import Enum +import json +import logging +import socket +import ssl + +from typing import Any, Protocol +from urllib.parse import urlencode + +from attr import fields + +from openfga_sdk.common.headers import HttpHeaderKeys, HttpHeaders +from openfga_sdk.exceptions import ( + ApiException, + ApiValueError, + ForbiddenException, + NotFoundException, + RateLimitExceededError, + ServiceException, + UnauthorizedException, + ValidationException, +) +from openfga_sdk.protocols import ( + HttpHeadersProtocol, + MergeableDataclassMixin, + RestClientProtocol, + RestClientRequestBodyProtocol, + RestClientRequestFieldParameterProtocol, + RestClientRequestFieldParametersProtocol, + RestClientRequestProtocol, + RestClientRequestQueryParameterProtocol, + RestClientRequestQueryParametersProtocol, + RestClientResponseProtocol, + StoreRequestOptionsProtocol, +) + + +logger = logging.getLogger(__name__) + + +class RestClientRequestMethod(Enum): + GET = "GET" + HEAD = "HEAD" + DELETE = "DELETE" + POST = "POST" + PUT = "PUT" + PATCH = "PATCH" + OPTIONS = "OPTIONS" + + +class RestClientRequestContentType(Enum): + JSON = "application/json" + FORM_ENCODED = "application/x-www-form-urlencoded" + FORM_MULTIPART = "multipart/form-data" + + +@dataclass +class RestClientRequestQueryParameter(RestClientRequestQueryParameterProtocol): + name: str + value: str + + def __str__(self) -> str: + return f"{self.name}: {self.value}" + + +@dataclass +class RestClientRequestQueryParameters( + MergeableDataclassMixin, RestClientRequestQueryParametersProtocol +): + parameters: dict[str, RestClientRequestQueryParameterProtocol] = field( + default_factory=dict + ) + + def add_parameter(self, name: str, value: str, **kwargs) -> None: + self.parameters[name] = RestClientRequestQueryParameter( + name=name, value=value, **kwargs + ) + + def get_parameter( + self, name: str + ) -> RestClientRequestQueryParameterProtocol | None: + return self.parameters.get(name) + + def remove_parameter(self, name: str) -> None: + self.parameters.pop(name, None) + + def merge(self, other: "RestClientRequestQueryParametersProtocol") -> None: + for name, params_list in other.parameters.items(): + if name in self.parameters: + self.parameters[name].extend(params_list) + else: + self.parameters[name] = params_list.copy() + + def urlencode(self, prefix: str | None = None) -> str: + encoded = urlencode(dict(self.parameters)) + + if prefix is not None: + return f"{prefix}?{encoded}" + + return encoded + + def as_dict(self) -> dict[str, str]: + return {name: value.value for name, value in self.parameters.items()} + + def __getitem__(self, name: str) -> RestClientRequestQueryParameterProtocol | None: + return self.get_parameter(name) + + def __setitem__( + self, name: str, value: RestClientRequestQueryParameterProtocol + ) -> None: + self.parameters[name] = value + + def __delitem__(self, name: str) -> None: + self.remove_parameter(name) + + def __contains__(self, name: str) -> bool: + return name in self.parameters + + def __iter__(self): + return iter(self.parameters) + + def items(self): + return self.parameters.items() + + def __str__(self) -> str: + return "\n".join(str(parameter) for parameter in self.parameters.values()) + + @staticmethod + def from_options( + options: StoreRequestOptionsProtocol | None, + ) -> "RestClientRequestQueryParametersProtocol": + params = RestClientRequestQueryParameters() + + if options is None: + return params + + if hasattr(options, "page_size") and options.page_size is not None: + params.add_parameter("page_size", str(options.page_size)) + + if ( + hasattr(options, "continuation_token") + and options.continuation_token is not None + ): + params.add_parameter("continuation_token", options.continuation_token) + + return params + + +@dataclass +class RestClientRequestFieldParameter(RestClientRequestFieldParameterProtocol): + name: str + value: str + + def __str__(self) -> str: + return f"{self.name}: {self.value}" + + +@dataclass +class RestClientRequestFieldParameters( + MergeableDataclassMixin, RestClientRequestFieldParametersProtocol +): + parameters: dict[str, RestClientRequestFieldParameterProtocol] = field( + default_factory=dict + ) + + def add_parameter(self, name: str, value: str, **kwargs) -> None: + self.parameters[name] = RestClientRequestFieldParameter( + name=name, value=value, **kwargs + ) + + def get_parameter( + self, name: str + ) -> RestClientRequestFieldParameterProtocol | None: + return self.parameters.get(name) + + def remove_parameter(self, name: str) -> None: + self.parameters.pop(name, None) + + def merge(self, other: "RestClientRequestFieldParametersProtocol") -> None: + for name, params_list in other.parameters.items(): + if name in self.parameters: + self.parameters[name].extend(params_list) + else: + self.parameters[name] = params_list.copy() + + def as_dict(self) -> dict[str, str]: + return {name: value.value for name, value in self.parameters.items()} + + def __getitem__(self, name: str) -> RestClientRequestFieldParameterProtocol | None: + return self.get_parameter(name) + + def __setitem__( + self, name: str, value: RestClientRequestFieldParameterProtocol + ) -> None: + self.parameters[name] = value + + def __delitem__(self, name: str) -> None: + self.remove_parameter(name) + + def __contains__(self, name: str) -> bool: + return name in self.parameters + + def __iter__(self): + return iter(self.parameters) + + def items(self): + return self.parameters.items() + + def __str__(self) -> str: + return "\n".join(str(parameter) for parameter in self.parameters.values()) + + +@dataclass +class RestClientRequestBody(RestClientRequestBodyProtocol): + value: str = "" + + def __str__(self) -> str: + return f"{self.value}" + + +@dataclass +class RestClientRequest(RestClientRequestProtocol): + pass + + +class RestClientBase(RestClientProtocol): + _ssl_context: ssl.SSLContext | None = None + _pool_size_max: int | None = None + _pool_size: int | None = None + _debug: bool | None = None + + @property + def ssl_context(self) -> ssl.SSLContext: + """ + Returns a configured SSLContext. + """ + if self._ssl_context is None: + context = ssl.create_default_context() + + if type(context) is not ssl.SSLContext: + raise TypeError("Unable to create SSLContext") + + if self._configuration.ssl_ca_cert: + context.load_verify_locations(cafile=self._configuration.ssl_ca_cert) + + if self._configuration.cert_file: + context.load_cert_chain( + self._configuration.cert_file, keyfile=self._configuration.key_file + ) + + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + + # if self._configuration.verify_ssl: + # context.verify_mode = ssl.CERT_REQUIRED + + # if not self._configuration.assert_hostname: + # context.check_hostname = True + + self._ssl_context = context + + return self._ssl_context + + @property + def debug(self) -> bool: + return self._debug or self._configuration.debug + + @debug.setter + def debug(self, value: bool | None) -> None: + self._debug = value + + @staticmethod + def build_request( + method: RestClientRequestMethod, + url: str, + body: RestClientRequestBodyProtocol | None = None, + headers: HttpHeadersProtocol | None = None, + query: RestClientRequestQueryParametersProtocol | None = None, + fields: RestClientRequestFieldParametersProtocol | None = None, + ) -> RestClientRequestProtocol: + query = RestClientRequestQueryParameters() | query + fields = RestClientRequestFieldParameters() | fields + headers = HttpHeaders() | headers + data: str | None = None + multipart: bool | None = None + + if headers.get_header(HttpHeaderKeys.CONTENT_TYPE.value) == []: + headers.add_header( + HttpHeaderKeys.CONTENT_TYPE.value, + RestClientRequestContentType.JSON.value, + ) + + if query: + url = query.urlencode(url) + + if method in [ + RestClientRequestMethod.POST, + RestClientRequestMethod.PUT, + RestClientRequestMethod.PATCH, + RestClientRequestMethod.OPTIONS, + RestClientRequestMethod.DELETE, + ]: + content_type = headers.get_header(HttpHeaderKeys.CONTENT_TYPE.value) + content_type = content_type[0] if content_type != [] else None + + match content_type: + case RestClientRequestContentType.JSON.value: + if body is not None: + data = json.dumps(body) + else: + data = json.dumps({}) + + case RestClientRequestContentType.FORM_ENCODED.value: + multipart = False + + case RestClientRequestContentType.FORM_MULTIPART.value: + headers.remove_header(HttpHeaderKeys.CONTENT_TYPE.value) + multipart = True + + case _: + if isinstance(body, bytes): + data = body.decode("utf-8") + else: + raise ApiException( + status=0, + reason=( + "Cannot prepare a request message for provided arguments. " + "Please check that your arguments match declared content type." + ), + ) + + return RestClientRequest( + method=str(method.value), + url=url, + body=data, + fields=fields.as_dict(), + headers=headers.as_dict(), + multipart=multipart, + ) + + def _log_response(self, response: RestClientResponseProtocol) -> None: + if self.debug: + logger.debug("response body: %s", response.data.decode("utf-8")) + + def _handle_response_exception(self, response: RestClientResponseProtocol) -> None: + """ + Raises exceptions if response status indicates an error. + + :param response: The response to check. + :raises ValidationException: If status is 400. + :raises UnauthorizedException: If status is 401. + :raises ForbiddenException: If status is 403. + :raises NotFoundException: If status is 404. + :raises RateLimitExceededError: If status is 429. + :raises ServiceException: If status is 5xx. + :raises ApiException: For other non-2xx statuses. + """ + if 200 <= response.status <= 299: + return + + match response.status: + case 400: + raise ValidationException(http_resp=response) + case 401: + raise UnauthorizedException(http_resp=response) + case 403: + raise ForbiddenException(http_resp=response) + case 404: + raise NotFoundException(http_resp=response) + case 429: + raise RateLimitExceededError(http_resp=response) + case _ if 500 <= response.status <= 599: + raise ServiceException(http_resp=response) + case _: + raise ApiException(http_resp=response) + + def _accumulate_json_lines( + self, leftover: bytes, data: bytes, buffer: bytearray + ) -> tuple[bytes, list[Any]]: + """ + Processes a chunk of data and leftover bytes. Splits on newlines, decodes valid JSON, + and returns leftover bytes and a list of decoded JSON objects. + + :param leftover: Any leftover bytes from previous chunks. + :param data: The new chunk of data. + :param buffer: The main bytearray buffer for all data. + :return: Updated leftover bytes and a list of decoded JSON objects. + """ + + objects: list[Any] = [] + leftover += data + lines = leftover.split( + b"\n" + ) # Objects are received as one-per-line, so split at newlines + leftover = lines.pop() + buffer.extend(data) + for line in lines: + try: + decoded = json.loads(line.decode("utf-8")) + objects.append(decoded) + except json.JSONDecodeError as e: + logger.warning("Skipping invalid JSON segment: %s", e) + + return leftover, objects diff --git a/openfga_sdk/configuration.py b/openfga_sdk/configuration.py index 81c4e6e2..8d8477c7 100644 --- a/openfga_sdk/configuration.py +++ b/openfga_sdk/configuration.py @@ -10,15 +10,22 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ -import copy +from dataclasses import dataclass, field import http import logging import sys -import urllib -import urllib3 +from typing import Any +from urllib.parse import urlparse, urlunparse -from openfga_sdk.exceptions import ApiValueError, FgaValidationException +from openfga_sdk.exceptions import FgaValidationException +from openfga_sdk.protocols import ( + ConfigurationDataProtocol, + ConfigurationProtocol, + CredentialsProtocol, + RetryParamsProtocol, + TelemetryConfigurationProtocol, +) from openfga_sdk.telemetry.attributes import TelemetryAttribute from openfga_sdk.telemetry.configuration import ( TelemetryConfiguration, @@ -28,146 +35,160 @@ ) from openfga_sdk.telemetry.counters import TelemetryCounter from openfga_sdk.telemetry.histograms import TelemetryHistogram -from openfga_sdk.validation import is_well_formed_ulid_string +from openfga_sdk.validation import ( + ValidatedInteger, + is_well_formed_ulid_string, +) -class RetryParams: - """NOTE: This class is auto generated by OpenAPI Generator +@dataclass +class RetryParams(RetryParamsProtocol): + max_retries: ValidatedInteger = ValidatedInteger( + value=RetryParamsProtocol.DEFAULT_MAX_RETRIES, min=0, max=15 + ) + min_wait_in_ms: ValidatedInteger = ValidatedInteger( + value=RetryParamsProtocol.DEFAULT_MIN_WAIT, min=0, max=1000 + ) + + +@dataclass +class ConfigurationData(ConfigurationDataProtocol): + api_key_prefix: dict[str, bool | int | str] = field(default_factory=dict) + api_key: dict[str, bool | int | str] = field(default_factory=dict) + api_url: str | None = None + assert_hostname: str | None = None + cert_file: str | None = None + client_side_validation: bool = True + connection_pool_size: int = 4 + connection_pool_size_max: int = 100 + credentials: CredentialsProtocol | None = None + debug: bool = False + discard_unknown_keys: bool = False + key_file: str | None = None + logger_file_handler: logging.FileHandler | None = None + logger_file: str | None = None + logger_format = "%(asctime)s %(levelname)s %(message)s" + logger_formatter: logging.Formatter | None = None + logger: dict[str, logging.Logger] = field(default_factory=dict) + password: str | None = None + proxy_headers: dict[str, str] | None = field(default_factory=dict) + proxy: str | None = None + retry_params: RetryParamsProtocol = field(default_factory=lambda: RetryParams()) + socket_options: list[tuple[int, int, int | bytes]] | None = None + ssl_ca_cert: str | None = None + store_id: str | None = None + authorization_model_id: str | None = None + telemetry: TelemetryConfigurationProtocol = field( + default_factory=lambda: TelemetryConfiguration.withDefaults() + ) + timeout: int = 300000 + username: str | None = None + verify_ssl: bool = True + + +class Configuration(ConfigurationProtocol): + """ + OpenFGA configuration + """ - Ref: https://openapi-generator.tech - Do not edit the class manually. + _data: ConfigurationData - Retry configuration in case of HTTP too many request + def __init__( + self, + api_url: str, + store_id: str | None = None, + authorization_model_id: str | None = None, + credentials: CredentialsProtocol | None = None, + retry_params: RetryParamsProtocol | None = None, + api_key: dict[str, bool | int | str] = {}, + api_key_prefix: dict[str, bool | int | str] = {}, + username: str | None = None, + password: str | None = None, + discard_unknown_keys: bool = False, + ssl_ca_cert: str | None = None, + telemetry: ( + TelemetryConfigurationProtocol + | dict[ + TelemetryConfigurationType | str, + TelemetryMetricsConfiguration + | dict[ + TelemetryHistogram | TelemetryCounter | str, + TelemetryMetricConfiguration + | dict[TelemetryAttribute | str, bool] + | None, + ] + | None, + ] + | None + ) = None, + timeout: int = 300000, + ): + self._data = ConfigurationData() + + self.api_url = api_url + self.store_id = store_id + self.authorization_model_id = authorization_model_id + self.credentials = credentials + self.api_key = api_key + self.api_key_prefix = api_key_prefix + self.username = username + self.password = password + self.discard_unknown_keys = discard_unknown_keys + self.ssl_ca_cert = ssl_ca_cert + self.timeout = timeout - :param max_retry: Maximum number of retry - :param min_wait_in_ms: Minimum wait (in ms) between retry - """ + if retry_params is not None: + self.retry_params = retry_params - def __init__(self, max_retry=3, min_wait_in_ms=100): - self._max_retry = max_retry - self._min_wait_in_ms = min_wait_in_ms + if telemetry is not None: + self.telemetry = self._telemetry_from_config(telemetry) - @property - def max_retry(self): - """ - Return the maximum number of retry - """ - if self._max_retry > 15: - raise FgaValidationException( - "RetryParams.max_retry exceeds maximum allowed limit of 15" - ) + @classmethod + def get_default_copy(cls) -> ConfigurationData: + return ConfigurationData() - return self._max_retry + def get(self, key: str) -> Any: + return getattr(self._data, key) - @max_retry.setter - def max_retry(self, value): - """ - Update the maximum number of retry - """ - if not isinstance(value, int) or value < 0: - raise FgaValidationException( - "RetryParams.max_retry must be an integer greater than or equal to 0" - ) + def auth_settings(self): + """Gets Auth Settings dict for api client. - if value > 15: - raise FgaValidationException( - "RetryParams.max_retry exceeds maximum allowed limit of 15" - ) + :return: The Auth Settings information dict. + """ + auth = {} + return auth - self._max_retry = value + def to_debug_report(self) -> str: + """Gets the essential information for debugging. - @property - def min_wait_in_ms(self): - """ - Return the minimum wait (in ms) in between retry + :return: The report for debugging. """ - return self._min_wait_in_ms + return ( + "Python SDK Debug Report:\n" + f"OS: {sys.platform}\n" + f"Python Version: {sys.version}\n" + "Version of the API: 1.x\n" + "SDK Package Version: 0.9.1" + ) - @min_wait_in_ms.setter - def min_wait_in_ms(self, value): + def is_valid(self) -> bool: """ - Update the minimum wait (in ms) in between retry + Verify the configuration is valid. + Note that we are only doing basic validation to ensure input is sane. """ - if not isinstance(value, int) or value < 0: - raise FgaValidationException( - "RetryParams.min_wait_in_ms must be an integer greater than or equal to 0" - ) + return True - self._min_wait_in_ms = value - - -class Configuration: - """NOTE: This class is auto generated by OpenAPI Generator - - Ref: https://openapi-generator.tech - Do not edit the class manually. - - :param api_scheme: Whether connection is 'https' or 'http'. Default as 'https' - .. deprecated:: 0.4.1 - Use `api_url` instead. - :param api_host: Base url - .. deprecated:: 0.4.1 - Use `api_url` instead. - :param store_id: ID of store for API - :param credentials: Configuration for obtaining authentication credential - :param retry_params: Retry parameters upon HTTP too many request - :param api_key: Dict to store API key(s). - Each entry in the dict specifies an API key. - The dict key is the name of the security scheme in the OAS specification. - The dict value is the API key secret. - :param api_key_prefix: Dict to store API prefix (e.g. Bearer) - The dict key is the name of the security scheme in the OAS specification. - The dict value is an API key prefix when generating the auth data. - :param username: Username for HTTP basic authentication - :param password: Password for HTTP basic authentication - :param discard_unknown_keys: Boolean value indicating whether to discard - unknown properties. A server may send a response that includes additional - properties that are not known by the client in the following scenarios: - 1. The OpenAPI document is incomplete, i.e. it does not match the server - implementation. - 2. The client was generated using an older version of the OpenAPI document - and the server has been upgraded since then. - If a schema in the OpenAPI document defines the additionalProperties attribute, - then all undeclared properties received by the server are injected into the - additional properties map. In that case, there are undeclared properties, and - nothing to discard. - :param server_index: Index to servers configuration. - :param server_variables: Mapping with string values to replace variables in - templated server configuration. The validation of enums is performed for - variables with defined enum values before. - :param server_operation_index: Mapping from operation ID to an index to server - configuration. - :param server_operation_variables: Mapping from operation ID to a mapping with - string values to replace variables in templated server configuration. - The validation of enums is performed for variables with defined enum values before. - :param ssl_ca_cert: str - the path to a file of concatenated CA certificates - in PEM format - :param api_url: str - the URL of the FGA server - :param timeout_millisec: int | None - the default timeout in milliseconds for requests - """ + def _url_with_scheme(self, value: str) -> str: + if value.startswith("http://") or value.startswith("https://"): + return value - _default = None + return f"https://{value}" - def __init__( + def _telemetry_from_config( self, - api_scheme="https", - api_host=None, - store_id=None, - credentials=None, - retry_params=None, - api_key=None, - api_key_prefix=None, - username=None, - password=None, - discard_unknown_keys=False, - server_index=None, - server_variables=None, - server_operation_index=None, - server_operation_variables=None, - ssl_ca_cert=None, - api_url=None, # TODO: restructure when removing api_scheme/api_host - telemetry: ( - dict[ + value: ( + TelemetryConfigurationProtocol + | dict[ TelemetryConfigurationType | str, TelemetryMetricsConfiguration | dict[ @@ -180,542 +201,381 @@ def __init__( ] | None ) = None, - timeout_millisec: int | None = None, - ): - """Constructor""" - self._url = api_url - self._scheme = api_scheme - self._base_path = api_host - self._store_id = store_id - self._credentials = credentials - if retry_params is not None: - self._retry_params = retry_params - else: - # use the default parameters - self._retry_params = RetryParams() - - self._timeout_millisec = timeout_millisec or 5000 * 60 - """Default Base url - """ - self.server_index = 0 - self.server_operation_index = server_operation_index or {} - """Default server index - """ - self.server_variables = server_variables or {} - self.server_operation_variables = server_operation_variables or {} - """Default server variables - """ - self.temp_folder_path = None - """Temp file folder for downloading files + ) -> TelemetryConfigurationProtocol: """ - # Authentication Settings - self.api_key = {} - if api_key: - self.api_key = api_key - """dict to store API key(s) - """ - self.api_key_prefix = {} - if api_key_prefix: - self.api_key_prefix = api_key_prefix - """dict to store API prefix (e.g. Bearer) - """ - self.refresh_api_key_hook = None - """function hook to refresh API key if expired - """ - self.username = username - """Username for HTTP basic authentication - """ - self.password = password - """Password for HTTP basic authentication - """ - self.discard_unknown_keys = discard_unknown_keys - self.logger = {} - """Logging Settings - """ - self.logger["package_logger"] = logging.getLogger("openfga_sdk") - self.logger["urllib3_logger"] = logging.getLogger("urllib3") - self.logger_format = "%(asctime)s %(levelname)s %(message)s" - """Log format - """ - self.logger_stream_handler = None - """Log stream handler - """ - self.logger_file_handler = None - """Log file handler - """ - self.logger_file = None - """Debug file location - """ - self.debug = False - """Debug switch + Create a TelemetryConfiguration from a dictionary. """ + if isinstance(value, TelemetryConfigurationProtocol): + return value - self.verify_ssl = True - """SSL/TLS verification - Set this to false to skip verifying SSL certificate when calling API - from https server. - """ - self.ssl_ca_cert = ssl_ca_cert - """Set this to customize the certificate file to verify the peer. - """ - self.cert_file = None - """client certificate file - """ - self.key_file = None - """client key file - """ - self.assert_hostname = None - """Set this to True/False to enable/disable SSL hostname verification. - """ + if isinstance(value, dict): + return TelemetryConfiguration(value) - self.connection_pool_maxsize = 100 - """This value is passed to the aiohttp to limit simultaneous connections. - Default values is 100, None means no-limit. - """ + return TelemetryConfiguration.withDefaults() - self.proxy = None - """Proxy URL - """ - self.proxy_headers = None - """Proxy headers - """ - self.safe_chars_for_path_param = "" - """Safe chars for path_param - """ - self.retries = None - """Adding retries to override urllib3 default value 3 - """ - # Enable client side validation - self.client_side_validation = True + @property + def api_key_prefix(self) -> dict[str, bool | int | str]: + return self._data.api_key_prefix - self.socket_options = None - """Options to pass down to the underlying urllib3 socket - """ + @api_key_prefix.setter + def api_key_prefix(self, value: dict[str, bool | int | str]): + self._data.api_key_prefix = value + + @property + def api_key(self) -> dict[str, bool | int | str]: + return self._data.api_key + + @api_key.setter + def api_key(self, value: dict[str, bool | int | str]): + self._data.api_key = value + + @property + def api_url(self) -> str: + return self._data.api_url or "" - self._telemetry: TelemetryConfiguration | None = None - if telemetry is None: - self._telemetry = TelemetryConfiguration( - TelemetryConfiguration.getSdkDefaults() + @api_url.setter + def api_url(self, value: str) -> None: + _url = None + + try: + _url = urlparse( + self._url_with_scheme(value), scheme="https", allow_fragments=False ) - elif isinstance(telemetry, dict): - self._telemetry = TelemetryConfiguration(telemetry) - elif isinstance(telemetry, TelemetryConfiguration): - self._telemetry = telemetry - - """Telemetry configuration - """ - - def __deepcopy__(self, memo): - cls = self.__class__ - result = cls.__new__(cls) - memo[id(self)] = result - for k, v in self.__dict__.items(): - if k not in ("logger", "logger_file_handler"): - setattr(result, k, copy.deepcopy(v, memo)) - # shallow copy of loggers - result.logger = copy.copy(self.logger) - # use setters to configure loggers - result.logger_file = self.logger_file - result.debug = self.debug - return result + except Exception: + raise FgaValidationException(f"api_url `{value}` is invalid") - @classmethod - def set_default(cls, default): - """Set default instance of configuration. + if _url.scheme not in ["http", "https"]: + raise FgaValidationException( + f"api_url `{value}` must use either `http` or `https` scheme" + ) - It stores default configuration, which can be - returned by get_default_copy method. + if _url.hostname is None or _url.hostname == "": + raise FgaValidationException( + f"api_url `{value}` must include a valid hostname" + ) - :param default: object of Configuration - """ - cls._default = copy.deepcopy(default) + if _url.path != "": + raise FgaValidationException(f"api_url `{value}` must not include a path") - @classmethod - def get_default_copy(cls): - """Return new instance of configuration. + if _url.query != "": + raise FgaValidationException(f"api_url `{value}` must not include a query") - This method returns newly created, based on default constructor, - object of Configuration class or returns a copy of default - configuration passed by the set_default method. + if _url.fragment != "": + raise FgaValidationException( + f"api_url `{value}` must not include a fragment" + ) - :return: The configuration object. - """ - if cls._default is not None: - return copy.deepcopy(cls._default) - return Configuration() + self._data.api_url = str(urlunparse(_url)) @property - def logger_file(self): - """The logger file. - - If the logger_file is None, then add stream handler and remove file - handler. Otherwise, add file handler and remove stream handler. + def assert_hostname(self) -> str | None: + """ + When verifying SSL/TLS certificates, the hostname in the certificate is checked against the hostname of the server you are connecting to. If they do not match, the connection is considered insecure. This property allows you to specify the hostname that should be used for this check. + """ + return self._data.assert_hostname - :param value: The logger_file path. - :type: str + @assert_hostname.setter + def assert_hostname(self, value: str | None): """ - return self.__logger_file + When verifying SSL/TLS certificates, the hostname in the certificate is checked against the hostname of the server you are connecting to. If they do not match, the connection is considered insecure. This property allows you to specify the hostname that should be used for this check. + """ + self._data.assert_hostname = value - @logger_file.setter - def logger_file(self, value): - """The logger file. + @property + def authorization_model_id(self) -> str | None: + return self._data.authorization_model_id - If the logger_file is None, then add stream handler and remove file - handler. Otherwise, add file handler and remove stream handler. + @authorization_model_id.setter + def authorization_model_id(self, value: str | None) -> None: + if value is None or value == "": + self._data.authorization_model_id = None + return - :param value: The logger_file path. - :type: str - """ - self.__logger_file = value - if self.__logger_file: - # If set logging file, - # then add file handler and remove stream handler. - self.logger_file_handler = logging.FileHandler(self.__logger_file) - self.logger_file_handler.setFormatter(self.logger_formatter) - for _, logger in self.logger.items(): - logger.addHandler(self.logger_file_handler) + if is_well_formed_ulid_string(value): + self._data.authorization_model_id = value + return + + raise FgaValidationException( + f"authorization_model_id ('{value}') is not in a valid ulid format" + ) @property - def debug(self): - """Debug status + def cert_file(self) -> str | None: + """ + When verifying SSL/TLS certificates, a custom private key (key_file) and corresponding certificate (cert_file) can be specified. This is useful when the server uses a self-signed certificate or a certificate from a non-standard CA. + """ + return self._data.cert_file - :param value: The debug status, True or False. - :type: bool + @cert_file.setter + def cert_file(self, value: str | None): """ - return self.__debug + When verifying SSL/TLS certificates, a custom private key (key_file) and corresponding certificate (cert_file) can be specified. This is useful when the server uses a self-signed certificate or a certificate from a non-standard CA. + """ + self._data.cert_file = value - @debug.setter - def debug(self, value): - """Debug status + @property + def client_side_validation(self) -> bool: + return self._data.client_side_validation - :param value: The debug status, True or False. - :type: bool - """ - self.__debug = value - if self.__debug: - # if debug status is True, turn on debug logging - for _, logger in self.logger.items(): - logger.setLevel(logging.DEBUG) - # turn on httplib debug - http.client.HTTPConnection.set_debuglevel(http.client.HTTPConnection, 1) - else: - # if debug status is False, turn off debug logging, - # setting log level to default `logging.WARNING` - for _, logger in self.logger.items(): - logger.setLevel(logging.WARNING) - # turn off httplib debug - http.client.HTTPConnection.set_debuglevel(http.client.HTTPConnection, 0) + @client_side_validation.setter + def client_side_validation(self, value: bool): + self._data.client_side_validation = value @property - def logger_format(self): - """The logger format. + def connection_pool_size(self) -> int: + return self._data.connection_pool_size - The logger_formatter will be updated when sets logger_format. + @connection_pool_size.setter + def connection_pool_size(self, value: int): + if value < 1: + raise FgaValidationException( + f"connection_pool_size must be greater than or equal to 1, {value}" + ) - :param value: The format string. - :type: str - """ - return self.__logger_format + self._data.connection_pool_size = value - @logger_format.setter - def logger_format(self, value): - """The logger format. + @property + def connection_pool_size_max(self) -> int: + return self._data.connection_pool_size_max - The logger_formatter will be updated when sets logger_format. + @connection_pool_size_max.setter + def connection_pool_size_max(self, value: int): + if value < 1: + raise FgaValidationException( + f"connection_pool_size_max must be greater than or equal to 1, {value}" + ) - :param value: The format string. - :type: str - """ - self.__logger_format = value - self.logger_formatter = logging.Formatter(self.__logger_format) + self._data.connection_pool_size_max = value @property - def telemetry(self) -> TelemetryConfiguration | None: - """Return the telemetry configuration""" - return self._telemetry + def credentials(self) -> CredentialsProtocol | None: + return self._data.credentials - @telemetry.setter - def telemetry( - self, - value: ( - TelemetryConfiguration - | dict[ - TelemetryConfigurationType | str, - TelemetryMetricsConfiguration - | dict[ - TelemetryHistogram | TelemetryCounter | str, - TelemetryMetricConfiguration - | dict[TelemetryAttribute | str, bool] - | None, - ] - | None, - ] - | None - ), - ) -> None: - """Set the telemetry configuration""" + @credentials.setter + def credentials(self, value: CredentialsProtocol | None): if value is not None: - if isinstance(value, TelemetryConfiguration): - self._telemetry = value - return - - if isinstance(value, dict): - self._telemetry = TelemetryConfiguration(value) - return + value.validate() - self._telemetry = None + self._data.credentials = value - def get_api_key_with_prefix(self, identifier, alias=None): - """Gets API key (with prefix if set). - - :param identifier: The identifier of apiKey. - :param alias: The alternative identifier of apiKey. - :return: The token for api key authentication. - """ - if self.refresh_api_key_hook is not None: - self.refresh_api_key_hook(self) - key = self.api_key.get( - identifier, self.api_key.get(alias) if alias is not None else None - ) - if key: - prefix = self.api_key_prefix.get(identifier) - if prefix: - return f"{prefix} {key}" - else: - return key - - def get_basic_auth_token(self): - """Gets HTTP basic authentication header (string). - - :return: The token for basic HTTP authentication. - """ - username = "" - if self.username is not None: - username = self.username - password = "" - if self.password is not None: - password = self.password - return urllib3.util.make_headers(basic_auth=username + ":" + password).get( - "authorization" - ) + @property + def debug(self) -> bool: + return self._data.debug - def auth_settings(self): - """Gets Auth Settings dict for api client. + @debug.setter + def debug(self, value: bool) -> None: + self._data.debug = value - :return: The Auth Settings information dict. - """ - auth = {} - return auth + _loggerLevel = logging.DEBUG if self._data.debug else logging.WARNING + _httpDebugLevel = 1 if self._data.debug else 0 - def to_debug_report(self): - """Gets the essential information for debugging. + for _, logger in self.logger.items(): + logger.setLevel(_loggerLevel) - :return: The report for debugging. - """ - return ( - "Python SDK Debug Report:\n" - f"OS: {sys.platform}\n" - f"Python Version: {sys.version}\n" - "Version of the API: 1.x\n" - "SDK Package Version: 0.9.1" + http.client.HTTPConnection.set_debuglevel( + http.client.HTTPConnection, _httpDebugLevel ) - def get_host_settings(self): - """Gets an array of host settings + @property + def discard_unknown_keys(self) -> bool: + return self._data.discard_unknown_keys - :return: An array of host settings + @discard_unknown_keys.setter + def discard_unknown_keys(self, value: bool) -> None: + self._data.discard_unknown_keys = value + + @property + def key_file(self) -> str | None: """ - return [ - { - "url": "", - "description": "No description provided", - } - ] + When verifying SSL/TLS certificates, a custom private key (key_file) and corresponding certificate (cert_file) can be specified. This is useful when the server uses a self-signed certificate or a certificate from a non-standard CA. + """ + return self._data.key_file - def get_host_from_settings(self, index, variables=None, servers=None): - """Gets host URL based on the index and variables - :param index: array index of the host settings - :param variables: hash of variable and the corresponding value - :param servers: an array of host settings or None - :return: URL based on host settings + @key_file.setter + def key_file(self, value: str | None): """ - if index is None: - return self._base_path + When verifying SSL/TLS certificates, a custom private key (key_file) and corresponding certificate (cert_file) can be specified. This is useful when the server uses a self-signed certificate or a certificate from a non-standard CA. + """ + self._data.key_file = value - variables = {} if variables is None else variables - servers = self.get_host_settings() if servers is None else servers + @property + def logger_file(self) -> str | None: + return self._data.logger_file - try: - server = servers[index] - except IndexError: - raise ValueError( - f"Invalid index {index} when selecting the host settings. " - f"Must be less than {len(servers)}" - ) + @logger_file.setter + def logger_file(self, value: str | None) -> None: + if self._data.logger_file_handler is not None: + for _, logger in self.logger.items(): + logger.removeHandler(self._data.logger_file_handler) + + self._data.logger_file_handler = None - url = server["url"] + if type(value) is str and value == "": + value = None - # go through variables and replace placeholders - for variable_name, variable in server.get("variables", {}).items(): - used_value = variables.get(variable_name, variable["default_value"]) + self._data.logger_file = value - if "enum_values" in variable and used_value not in variable["enum_values"]: - raise ValueError( - "The variable `{}` in the host URL has invalid value " - "{}. Must be {}.".format( - variable_name, variables[variable_name], variable["enum_values"] - ) - ) + if self._data.logger_file is not None: + self._data.logger_file_handler = logging.FileHandler(self._data.logger_file) + self._data.logger_file_handler.setFormatter(self._data.logger_formatter) - url = url.replace("{" + variable_name + "}", used_value) + for _, logger in self.logger.items(): + logger.addHandler(self._data.logger_file_handler) + + @property + def logger_format(self) -> str: + return self._data.logger_format - return url + @logger_format.setter + def logger_format(self, value: str) -> None: + self._data.logger_format = value + self._data.logger_formatter = logging.Formatter(self._data.logger_format) - def is_valid(self): + @property + def logger(self) -> dict[str, logging.Logger]: """ - Verify the configuration is valid. - Note that we are only doing basic validation to ensure input is sane. + Return configured loggers """ - combined_url = self.api_url - if self.api_url is None: - if self.api_host is None or self.api_host == "": - raise FgaValidationException("api_host is required but not configured.") - if self.api_scheme is None or self.api_scheme == "": - raise FgaValidationException( - "api_scheme is required but not configured." - ) - combined_url = self.api_scheme + "://" + self.api_host - parsed_url = None - try: - parsed_url = urllib.parse.urlparse(combined_url) - except ValueError: - if self.api_url is None: - raise ApiValueError( - f"Either api_scheme `{self.api_scheme}` or api_host `{self.api_host}` is invalid" - ) - else: - raise ApiValueError(f"api_url `{self.api_url}` is invalid") - if self.api_url is None: - if parsed_url.scheme != "http" and parsed_url.scheme != "https": - raise ApiValueError( - f"api_scheme `{self.api_scheme}` must be either `http` or `https`" - ) - if parsed_url.netloc == "": - raise ApiValueError(f"api_host `{self.api_host}` is invalid") - if parsed_url.path != "": - raise ApiValueError( - f"api_host `{self.api_scheme}` is not expected to have path specified" - ) - if parsed_url.query != "": - raise ApiValueError( - f"api_host `{self.api_scheme}` is not expected to have query specified" - ) - - if ( - self.store_id is not None - and self.store_id != "" - and is_well_formed_ulid_string(self.store_id) is False - ): - raise FgaValidationException( - f"store_id ('{self.store_id}') is not in a valid ulid format" - ) - - if self._credentials is not None: - self._credentials.validate_credentials_config() + if self._data.logger is None: + self._data.logger = { + "package_logger": logging.getLogger("openfga_sdk"), + "urllib3_logger": logging.getLogger("urllib3"), + } - if self._timeout_millisec is not None: - if not isinstance(self._timeout_millisec, int): - raise FgaValidationException( - f"timeout_millisec unexpected type {self._timeout_millisec}" - ) + return self._data.logger - ten_minutes = 10000 * 60 - if self._timeout_millisec < 0 or self._timeout_millisec > ten_minutes: - raise FgaValidationException( - f"timeout_millisec not within reasonable range (0,60000), {self._timeout_millisec}" - ) + @logger.setter + def logger(self, value: dict[str, logging.Logger]) -> None: + """ + Update configured loggers + """ + self._data.logger = value @property - def api_scheme(self): - """Return connection is https or http.""" - return self._scheme - - @api_scheme.setter - def api_scheme(self, value): - """Update connection scheme (https or http).""" - if value is not None and value not in ["https", "http"]: - raise FgaValidationException( - f"api_scheme `{value}` must be either `http` or `https`" - ) + def password(self) -> str | None: + return self._data.password - self._scheme = value + @password.setter + def password(self, value: str | None) -> None: + self._data.password = value @property - def api_host(self): - """Return api_host.""" - return self._base_path + def proxy_headers(self) -> dict[str, str] | None: + """ + A dictionary containing headers that will be sent to the proxy. + """ + return self._data.proxy_headers - @api_host.setter - def api_host(self, value): - """Update configured host""" - self._base_path = value + @proxy_headers.setter + def proxy_headers(self, value: dict[str, str] | None) -> None: + """ + A dictionary containing headers that will be sent to the proxy. + """ + self._data.proxy_headers = value @property - def api_url(self): - """Return api_url""" - return self._url + def proxy(self) -> dict[str, str] | None: + """ + The URL of the proxy to be used. + """ + return self._data.proxy - @api_url.setter - def api_url(self, value): - """Update configured api_url""" - self._url = value + @proxy.setter + def proxy(self, value: dict[str, str] | None) -> None: + """ + The URL of the proxy to be used. + """ + self._data.proxy = value @property - def store_id(self): - """Return store id.""" - return self._store_id + def retry_params(self) -> RetryParams: + return self._data.retry_params - @store_id.setter - def store_id(self, value): - """Update store id.""" - self._store_id = value + @retry_params.setter + def retry_params(self, value: RetryParams) -> None: + self._data.retry_params = value @property - def credentials(self): + def socket_options(self) -> list[tuple[int, int, int | bytes]] | None: """ - Return configured credentials + (For urllib3 connections only.) Set specific options on the underlying socket. If not specified, then defaults are loaded from HTTPConnection.default_socket_options. """ - return self._credentials + return self._data.socket_options - @credentials.setter - def credentials(self, value): - """Update credentials""" - self._credentials = value + @socket_options.setter + def socket_options(self, value: list[tuple[int, int, int | bytes]] | None) -> None: + """ + (For urllib3 connections only.) Set specific options on the underlying socket. If not specified, then defaults are loaded from HTTPConnection.default_socket_options. + """ + self._data.socket_options = value @property - def retry_params(self): + def ssl_ca_cert(self) -> str | None: """ - Return retry parameters + Returns the path of the configured certificate authority bundle. """ - return self._retry_params + return self._data.ssl_ca_cert - @retry_params.setter - def retry_params(self, value): + @ssl_ca_cert.setter + def ssl_ca_cert(self, value: str | None) -> None: """ - Update retry parameters + Configure a custom certificate authority bundle using a file system path. """ - self._retry_params = value + self._data.ssl_ca_cert = value @property - def timeout_millisec(self): - """ - Return timeout milliseconds - """ - return self._timeout_millisec + def store_id(self) -> str | None: + return self._data.store_id - @timeout_millisec.setter - def timeout_millisec(self, value): - """ - Update timeout milliseconds - """ - self._timeout_millisec = value + @store_id.setter + def store_id(self, value: str | None) -> None: + if value is not None: + value = value.strip() + + if value is None or value == "": + self._data.store_id = None + return + + if not is_well_formed_ulid_string(value): + raise FgaValidationException( + f"store_id ('{value}') is not in a valid ulid format" + ) + + self._data.store_id = value + + @property + def telemetry(self) -> TelemetryConfigurationProtocol: + return self._data.telemetry + + @telemetry.setter + def telemetry(self, value: TelemetryConfigurationProtocol) -> None: + self._data.telemetry = value + + @property + def timeout(self) -> int: + return self._data.timeout + + @timeout.setter + def timeout(self, value: int) -> None: + if value < 0 or value > 600000: + raise FgaValidationException( + f"timeout not within reasonable range (0,60000), {value}" + ) + + self._data.timeout = value + + @property + def username(self) -> str | None: + return self._data.username + + @username.setter + def username(self, value: str | None) -> None: + self._data.username = value + + @property + def verify_ssl(self) -> bool: + return self._data.verify_ssl + + @verify_ssl.setter + def verify_ssl(self, value: bool) -> None: + self._data.verify_ssl = value diff --git a/openfga_sdk/credentials.py b/openfga_sdk/credentials.py index c2bcdf2b..d1cd7615 100644 --- a/openfga_sdk/credentials.py +++ b/openfga_sdk/credentials.py @@ -12,7 +12,10 @@ from urllib.parse import urlparse, urlunparse +from dataclasses import dataclass + from openfga_sdk.exceptions import ApiValueError +from openfga_sdk.protocols import CredentialConfigurationProtocol, CredentialsProtocol def none_or_empty(value): @@ -22,189 +25,38 @@ def none_or_empty(value): return value is None or value == "" -class CredentialConfiguration: - """ - Configuration for SDK credential - :param client_id: Client ID which will be matched with client_secret - :param client_secret: Client secret which will be matched with client_id - :param api_token: Bearer token to be sent for authentication - :param api_audience: API audience used for OAuth2 - :param api_issuer: API issuer used for OAuth2 - """ - - def __init__( - self, - client_id: str | None = None, - client_secret: str | None = None, - api_audience: str | None = None, - api_issuer: str | None = None, - api_token: str | None = None, - ): - self._client_id = client_id - self._client_secret = client_secret - self._api_audience = api_audience - self._api_issuer = api_issuer - self._api_token = api_token - - @property - def client_id(self): - """ - Return the client id configured - """ - return self._client_id - - @client_id.setter - def client_id(self, value): - """ - Update the client id - """ - self._client_id = value - - @property - def client_secret(self): - """ - Return the client secret configured - """ - return self._client_secret - - @client_secret.setter - def client_secret(self, value): - """ - Update the client secret - """ - self._client_secret = value - - @property - def api_audience(self): - """ - Return the api audience configured - """ - return self._api_audience - - @api_audience.setter - def api_audience(self, value): - """ - Update the api audience - """ - self._api_audience = value - - @property - def api_issuer(self): - """ - Return the api issuer configured - """ - return self._api_issuer - - @api_issuer.setter - def api_issuer(self, value): - """ - Update the api issuer - """ - self._api_issuer = value - - @property - def api_token(self): - """ - Return the api token configured - """ - return self._api_token - - @api_token.setter - def api_token(self, value): - """ - Update the api token - """ - self._api_token = value - - -class Credentials: - """ - Manage the credential for the API Client - :param method: Type of authentication. Possible value is 'none', 'api_token' and 'client_credentials'. Default as 'none'. - :param configuration: Credential configuration of type CredentialConfiguration. Default as None. - """ - - def __init__( - self, - method: str | None = "none", - configuration: CredentialConfiguration | None = None, - ): - self._method = method - self._configuration = configuration - - @property - def method(self): - """ - Return the method configured - """ - return self._method - - @method.setter - def method(self, value): - """ - Update the method - """ - self._method = value - - @property - def configuration(self): - """ - Return the configuration - """ - return self._configuration - - @configuration.setter - def configuration(self, value): - """ - Update the configuration - """ - self._configuration = value - - def _parse_issuer(self, issuer: str): - default_endpoint_path = "/oauth/token" +@dataclass +class CredentialConfiguration(CredentialConfigurationProtocol): + client_id: str | None = None + client_secret: str | None = None + api_audience: str | None = None + api_issuer: str | None = None + api_token: str | None = None - parsed_url = urlparse(issuer.strip()) - try: - parsed_url.port - except ValueError as e: - raise ApiValueError(e) +@dataclass +class Credentials(CredentialsProtocol): + method: str | None = None + configuration: CredentialConfigurationProtocol | None = None - if parsed_url.netloc is None and parsed_url.path is None: - raise ApiValueError("Invalid issuer") - - if parsed_url.scheme == "": - parsed_url = urlparse(f"https://{issuer}") - elif parsed_url.scheme not in ("http", "https"): - raise ApiValueError( - f"Invalid issuer scheme {parsed_url.scheme} must be HTTP or HTTPS" - ) - - if parsed_url.path in ("", "/"): - parsed_url = parsed_url._replace(path=default_endpoint_path) - - valid_url = urlunparse(parsed_url) - - return valid_url - - def validate_credentials_config(self): - """ - Check whether credentials configuration is valid - """ + def validate(self): if ( self.method != "none" + and self.method != None and self.method != "api_token" and self.method != "client_credentials" ): raise ApiValueError( f"method `{self.method}` must be either `none`, `api_token` or `client_credentials`" ) + if self.method == "api_token" and ( self.configuration is None or none_or_empty(self.configuration.api_token) ): raise ApiValueError( f"configuration `{self.configuration}` api_token must be defined and non empty when method is api_token" ) + if self.method == "client_credentials": if ( self.configuration is None @@ -217,5 +69,31 @@ def validate_credentials_config(self): "configuration `{}` requires client_id, client_secret, api_audience and api_issuer defined for client_credentials method." ) - # validate token issuer self._parse_issuer(self.configuration.api_issuer) + + def _parse_issuer(self, issuer: str): + default_endpoint_path = "/oauth/token" + + parsed_url = urlparse(issuer.strip()) + + try: + parsed_url.port + except ValueError as e: + raise ApiValueError(e) + + if parsed_url.netloc is None and parsed_url.path is None: + raise ApiValueError("Invalid issuer") + + if parsed_url.scheme == "": + parsed_url = urlparse(f"https://{issuer}") + elif parsed_url.scheme not in ("http", "https"): + raise ApiValueError( + f"Invalid issuer scheme {parsed_url.scheme} must be HTTP or HTTPS" + ) + + if parsed_url.path in ("", "/"): + parsed_url = parsed_url._replace(path=default_endpoint_path) + + valid_url = urlunparse(parsed_url) + + return valid_url diff --git a/openfga_sdk/exceptions.py b/openfga_sdk/exceptions.py index 3b6c9373..4054ec7d 100644 --- a/openfga_sdk/exceptions.py +++ b/openfga_sdk/exceptions.py @@ -119,7 +119,7 @@ def __init__(self, status=None, reason=None, http_resp=None): try: headers = http_resp.headers.items() except AttributeError: - headers = http_resp.getheaders().items() + headers = http_resp.headers().items() self.status = http_resp.status self.reason = http_resp.reason diff --git a/openfga_sdk/factory.py b/openfga_sdk/factory.py new file mode 100644 index 00000000..08b0e653 --- /dev/null +++ b/openfga_sdk/factory.py @@ -0,0 +1,87 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 dataclasses import dataclass +from openfga_sdk.protocols import ( + ApiClientProtocol, + FactoryProtocol, + OAuth2ClientProtocol, + OpenFgaApiProtocol, + OpenFgaClientProtocol, + RestClientProtocol, + ConfigurationProtocol, +) +from openfga_sdk.telemetry.telemetry import Telemetry + + +@dataclass +class Factory(FactoryProtocol): + configuration: ConfigurationProtocol + + _auth_client: OAuth2ClientProtocol | None = None + _client: OpenFgaClientProtocol | None = None + _api: OpenFgaApiProtocol | None = None + _api_client: ApiClientProtocol | None = None + _rest_client: RestClientProtocol | None = None + _telemetry_client: Telemetry | None = None + + @property + def client(self) -> OpenFgaClientProtocol: + if self._client is None: + from openfga_sdk.client import OpenFgaClient + + self._client = OpenFgaClient(self.configuration) + + return self._client + + @property + def auth_client(self) -> OAuth2ClientProtocol: + if self._auth_client is None: + from openfga_sdk.oauth2 import OAuth2Client + + self._auth_client = OAuth2Client(self.configuration) + + return self._auth_client + + @property + def api(self) -> OpenFgaApiProtocol: + if self._api is None: + from openfga_sdk.api import OpenFgaApi + + self._api = OpenFgaApi(self.configuration) + + return self._api + + @property + def api_client(self) -> ApiClientProtocol: + if self._api_client is None: + from openfga_sdk.api_client import ApiClient + + self._api_client = ApiClient(self.configuration) + + return self._api_client + + @property + def rest_client(self) -> RestClientProtocol: + if self._rest_client is None: + from openfga_sdk.rest import RestClient + + self._rest_client = RestClient(self.configuration) + + return self._rest_client + + @property + def telemetry_client(self) -> Telemetry: + if self._telemetry_client is None: + self._telemetry_client = Telemetry() + + return self._telemetry_client diff --git a/openfga_sdk/models/aborted_message_response.py b/openfga_sdk/models/aborted_message_response.py index 5bbba7b8..500b0c8f 100644 --- a/openfga_sdk/models/aborted_message_response.py +++ b/openfga_sdk/models/aborted_message_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class AbortedMessageResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class AbortedMessageResponse: def __init__(self, code=None, message=None, local_vars_configuration=None): """AbortedMessageResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/any.py b/openfga_sdk/models/any.py index 31a488b9..16f1795f 100644 --- a/openfga_sdk/models/any.py +++ b/openfga_sdk/models/any.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Any: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class Any: def __init__(self, type=None, local_vars_configuration=None): """Any - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type = None self.discriminator = None diff --git a/openfga_sdk/models/assertion.py b/openfga_sdk/models/assertion.py index 4702d2bd..6dbaf989 100644 --- a/openfga_sdk/models/assertion.py +++ b/openfga_sdk/models/assertion.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Assertion: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """Assertion - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_key = None self._expectation = None @@ -89,7 +86,10 @@ def tuple_key(self, tuple_key): :param tuple_key: The tuple_key of this Assertion. :type tuple_key: AssertionTupleKey """ - if self.local_vars_configuration.client_side_validation and tuple_key is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_key is None + ): raise ValueError("Invalid value for `tuple_key`, must not be `None`") self._tuple_key = tuple_key @@ -112,7 +112,10 @@ def expectation(self, expectation): :param expectation: The expectation of this Assertion. :type expectation: bool """ - if self.local_vars_configuration.client_side_validation and expectation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and expectation is None + ): raise ValueError("Invalid value for `expectation`, must not be `None`") self._expectation = expectation diff --git a/openfga_sdk/models/assertion_tuple_key.py b/openfga_sdk/models/assertion_tuple_key.py index c6c7d389..26bed127 100644 --- a/openfga_sdk/models/assertion_tuple_key.py +++ b/openfga_sdk/models/assertion_tuple_key.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class AssertionTupleKey: """NOTE: This class is auto generated by OpenAPI Generator. @@ -43,9 +41,8 @@ def __init__( self, object=None, relation=None, user=None, local_vars_configuration=None ): """AssertionTupleKey - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._object = None self._relation = None @@ -74,7 +71,10 @@ def object(self, object): :param object: The object of this AssertionTupleKey. :type object: str """ - if self.local_vars_configuration.client_side_validation and object is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and object is None + ): raise ValueError("Invalid value for `object`, must not be `None`") self._object = object @@ -97,7 +97,10 @@ def relation(self, relation): :param relation: The relation of this AssertionTupleKey. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation @@ -120,7 +123,10 @@ def user(self, user): :param user: The user of this AssertionTupleKey. :type user: str """ - if self.local_vars_configuration.client_side_validation and user is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and user is None + ): raise ValueError("Invalid value for `user`, must not be `None`") self._user = user diff --git a/openfga_sdk/models/auth_error_code.py b/openfga_sdk/models/auth_error_code.py index 3234b08b..fe57ca1a 100644 --- a/openfga_sdk/models/auth_error_code.py +++ b/openfga_sdk/models/auth_error_code.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class AuthErrorCode: """NOTE: This class is auto generated by OpenAPI Generator. @@ -62,9 +60,8 @@ class AuthErrorCode: def __init__(self, local_vars_configuration=None): """AuthErrorCode - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/authorization_model.py b/openfga_sdk/models/authorization_model.py index 4a5c840d..ba11bc07 100644 --- a/openfga_sdk/models/authorization_model.py +++ b/openfga_sdk/models/authorization_model.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class AuthorizationModel: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """AuthorizationModel - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._id = None self._schema_version = None @@ -88,7 +85,10 @@ def id(self, id): :param id: The id of this AuthorizationModel. :type id: str """ - if self.local_vars_configuration.client_side_validation and id is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and id is None + ): raise ValueError("Invalid value for `id`, must not be `None`") self._id = id @@ -112,7 +112,7 @@ def schema_version(self, schema_version): :type schema_version: str """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and schema_version is None ): raise ValueError("Invalid value for `schema_version`, must not be `None`") @@ -138,7 +138,7 @@ def type_definitions(self, type_definitions): :type type_definitions: list[TypeDefinition] """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and type_definitions is None ): raise ValueError("Invalid value for `type_definitions`, must not be `None`") diff --git a/openfga_sdk/models/batch_check_item.py b/openfga_sdk/models/batch_check_item.py index 1e984a5b..9c841a99 100644 --- a/openfga_sdk/models/batch_check_item.py +++ b/openfga_sdk/models/batch_check_item.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class BatchCheckItem: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """BatchCheckItem - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_key = None self._contextual_tuples = None @@ -89,7 +86,10 @@ def tuple_key(self, tuple_key): :param tuple_key: The tuple_key of this BatchCheckItem. :type tuple_key: CheckRequestTupleKey """ - if self.local_vars_configuration.client_side_validation and tuple_key is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_key is None + ): raise ValueError("Invalid value for `tuple_key`, must not be `None`") self._tuple_key = tuple_key @@ -157,7 +157,7 @@ def correlation_id(self, correlation_id): :type correlation_id: str """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and correlation_id is None ): raise ValueError("Invalid value for `correlation_id`, must not be `None`") diff --git a/openfga_sdk/models/batch_check_request.py b/openfga_sdk/models/batch_check_request.py index c8ab5f6c..bb5243c7 100644 --- a/openfga_sdk/models/batch_check_request.py +++ b/openfga_sdk/models/batch_check_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class BatchCheckRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """BatchCheckRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._checks = None self._authorization_model_id = None @@ -84,7 +81,10 @@ def checks(self, checks): :param checks: The checks of this BatchCheckRequest. :type checks: list[BatchCheckItem] """ - if self.local_vars_configuration.client_side_validation and checks is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and checks is None + ): raise ValueError("Invalid value for `checks`, must not be `None`") self._checks = checks diff --git a/openfga_sdk/models/batch_check_response.py b/openfga_sdk/models/batch_check_response.py index e1b0e080..17e4692c 100644 --- a/openfga_sdk/models/batch_check_response.py +++ b/openfga_sdk/models/batch_check_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class BatchCheckResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class BatchCheckResponse: def __init__(self, result=None, local_vars_configuration=None): """BatchCheckResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._result = None self.discriminator = None diff --git a/openfga_sdk/models/batch_check_single_result.py b/openfga_sdk/models/batch_check_single_result.py index dea7e35e..59f0e80d 100644 --- a/openfga_sdk/models/batch_check_single_result.py +++ b/openfga_sdk/models/batch_check_single_result.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class BatchCheckSingleResult: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class BatchCheckSingleResult: def __init__(self, allowed=None, error=None, local_vars_configuration=None): """BatchCheckSingleResult - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._allowed = None self._error = None diff --git a/openfga_sdk/models/check_error.py b/openfga_sdk/models/check_error.py index 06cc95a9..02ae8b6f 100644 --- a/openfga_sdk/models/check_error.py +++ b/openfga_sdk/models/check_error.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class CheckError: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """CheckError - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._input_error = None self._internal_error = None diff --git a/openfga_sdk/models/check_request.py b/openfga_sdk/models/check_request.py index f510945c..8db3cc12 100644 --- a/openfga_sdk/models/check_request.py +++ b/openfga_sdk/models/check_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class CheckRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -60,9 +58,8 @@ def __init__( local_vars_configuration=None, ): """CheckRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_key = None self._contextual_tuples = None @@ -102,7 +99,10 @@ def tuple_key(self, tuple_key): :param tuple_key: The tuple_key of this CheckRequest. :type tuple_key: CheckRequestTupleKey """ - if self.local_vars_configuration.client_side_validation and tuple_key is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_key is None + ): raise ValueError("Invalid value for `tuple_key`, must not be `None`") self._tuple_key = tuple_key diff --git a/openfga_sdk/models/check_request_tuple_key.py b/openfga_sdk/models/check_request_tuple_key.py index fd6c1971..4bafb1e0 100644 --- a/openfga_sdk/models/check_request_tuple_key.py +++ b/openfga_sdk/models/check_request_tuple_key.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class CheckRequestTupleKey: """NOTE: This class is auto generated by OpenAPI Generator. @@ -43,9 +41,8 @@ def __init__( self, user=None, relation=None, object=None, local_vars_configuration=None ): """CheckRequestTupleKey - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._user = None self._relation = None @@ -74,7 +71,10 @@ def user(self, user): :param user: The user of this CheckRequestTupleKey. :type user: str """ - if self.local_vars_configuration.client_side_validation and user is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and user is None + ): raise ValueError("Invalid value for `user`, must not be `None`") self._user = user @@ -97,7 +97,10 @@ def relation(self, relation): :param relation: The relation of this CheckRequestTupleKey. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation @@ -120,7 +123,10 @@ def object(self, object): :param object: The object of this CheckRequestTupleKey. :type object: str """ - if self.local_vars_configuration.client_side_validation and object is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and object is None + ): raise ValueError("Invalid value for `object`, must not be `None`") self._object = object diff --git a/openfga_sdk/models/check_response.py b/openfga_sdk/models/check_response.py index 20290ddd..2476e50f 100644 --- a/openfga_sdk/models/check_response.py +++ b/openfga_sdk/models/check_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class CheckResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class CheckResponse: def __init__(self, allowed=None, resolution=None, local_vars_configuration=None): """CheckResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._allowed = None self._resolution = None diff --git a/openfga_sdk/models/computed.py b/openfga_sdk/models/computed.py index 5739bab7..08d63692 100644 --- a/openfga_sdk/models/computed.py +++ b/openfga_sdk/models/computed.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Computed: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class Computed: def __init__(self, userset=None, local_vars_configuration=None): """Computed - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._userset = None self.discriminator = None @@ -64,7 +61,10 @@ def userset(self, userset): :param userset: The userset of this Computed. :type userset: str """ - if self.local_vars_configuration.client_side_validation and userset is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and userset is None + ): raise ValueError("Invalid value for `userset`, must not be `None`") self._userset = userset diff --git a/openfga_sdk/models/condition.py b/openfga_sdk/models/condition.py index be7c8e71..cffed338 100644 --- a/openfga_sdk/models/condition.py +++ b/openfga_sdk/models/condition.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Condition: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """Condition - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._name = None self._expression = None @@ -89,7 +86,10 @@ def name(self, name): :param name: The name of this Condition. :type name: str """ - if self.local_vars_configuration.client_side_validation and name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and name is None + ): raise ValueError("Invalid value for `name`, must not be `None`") self._name = name @@ -114,7 +114,10 @@ def expression(self, expression): :param expression: The expression of this Condition. :type expression: str """ - if self.local_vars_configuration.client_side_validation and expression is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and expression is None + ): raise ValueError("Invalid value for `expression`, must not be `None`") self._expression = expression diff --git a/openfga_sdk/models/condition_metadata.py b/openfga_sdk/models/condition_metadata.py index f0dcd004..b764229e 100644 --- a/openfga_sdk/models/condition_metadata.py +++ b/openfga_sdk/models/condition_metadata.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ConditionMetadata: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ConditionMetadata: def __init__(self, module=None, source_info=None, local_vars_configuration=None): """ConditionMetadata - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._module = None self._source_info = None diff --git a/openfga_sdk/models/condition_param_type_ref.py b/openfga_sdk/models/condition_param_type_ref.py index 38ad2d76..83cf0a30 100644 --- a/openfga_sdk/models/condition_param_type_ref.py +++ b/openfga_sdk/models/condition_param_type_ref.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ConditionParamTypeRef: """NOTE: This class is auto generated by OpenAPI Generator. @@ -45,9 +43,8 @@ def __init__( self, type_name=None, generic_types=None, local_vars_configuration=None ): """ConditionParamTypeRef - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type_name = None self._generic_types = None @@ -75,7 +72,10 @@ def type_name(self, type_name): :param type_name: The type_name of this ConditionParamTypeRef. :type type_name: TypeName """ - if self.local_vars_configuration.client_side_validation and type_name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type_name is None + ): raise ValueError("Invalid value for `type_name`, must not be `None`") self._type_name = type_name diff --git a/openfga_sdk/models/consistency_preference.py b/openfga_sdk/models/consistency_preference.py index 0919cbef..859b4c60 100644 --- a/openfga_sdk/models/consistency_preference.py +++ b/openfga_sdk/models/consistency_preference.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ConsistencyPreference: """NOTE: This class is auto generated by OpenAPI Generator. @@ -46,9 +44,8 @@ class ConsistencyPreference: def __init__(self, local_vars_configuration=None): """ConsistencyPreference - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/contextual_tuple_keys.py b/openfga_sdk/models/contextual_tuple_keys.py index c580b89c..0c4ed70d 100644 --- a/openfga_sdk/models/contextual_tuple_keys.py +++ b/openfga_sdk/models/contextual_tuple_keys.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ContextualTupleKeys: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ContextualTupleKeys: def __init__(self, tuple_keys=None, local_vars_configuration=None): """ContextualTupleKeys - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_keys = None self.discriminator = None @@ -64,7 +61,10 @@ def tuple_keys(self, tuple_keys): :param tuple_keys: The tuple_keys of this ContextualTupleKeys. :type tuple_keys: list[TupleKey] """ - if self.local_vars_configuration.client_side_validation and tuple_keys is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_keys is None + ): raise ValueError("Invalid value for `tuple_keys`, must not be `None`") self._tuple_keys = tuple_keys diff --git a/openfga_sdk/models/create_store_request.py b/openfga_sdk/models/create_store_request.py index 27fa65d8..f86779f4 100644 --- a/openfga_sdk/models/create_store_request.py +++ b/openfga_sdk/models/create_store_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class CreateStoreRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class CreateStoreRequest: def __init__(self, name=None, local_vars_configuration=None): """CreateStoreRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._name = None self.discriminator = None @@ -64,7 +61,10 @@ def name(self, name): :param name: The name of this CreateStoreRequest. :type name: str """ - if self.local_vars_configuration.client_side_validation and name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and name is None + ): raise ValueError("Invalid value for `name`, must not be `None`") self._name = name diff --git a/openfga_sdk/models/create_store_response.py b/openfga_sdk/models/create_store_response.py index 0f97a1d6..2f4c8698 100644 --- a/openfga_sdk/models/create_store_response.py +++ b/openfga_sdk/models/create_store_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class CreateStoreResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """CreateStoreResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._id = None self._name = None @@ -87,7 +84,10 @@ def id(self, id): :param id: The id of this CreateStoreResponse. :type id: str """ - if self.local_vars_configuration.client_side_validation and id is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and id is None + ): raise ValueError("Invalid value for `id`, must not be `None`") self._id = id @@ -110,7 +110,10 @@ def name(self, name): :param name: The name of this CreateStoreResponse. :type name: str """ - if self.local_vars_configuration.client_side_validation and name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and name is None + ): raise ValueError("Invalid value for `name`, must not be `None`") self._name = name @@ -133,7 +136,10 @@ def created_at(self, created_at): :param created_at: The created_at of this CreateStoreResponse. :type created_at: datetime """ - if self.local_vars_configuration.client_side_validation and created_at is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and created_at is None + ): raise ValueError("Invalid value for `created_at`, must not be `None`") self._created_at = created_at @@ -156,7 +162,10 @@ def updated_at(self, updated_at): :param updated_at: The updated_at of this CreateStoreResponse. :type updated_at: datetime """ - if self.local_vars_configuration.client_side_validation and updated_at is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and updated_at is None + ): raise ValueError("Invalid value for `updated_at`, must not be `None`") self._updated_at = updated_at diff --git a/openfga_sdk/models/difference.py b/openfga_sdk/models/difference.py index b6c42a9e..f3d14a70 100644 --- a/openfga_sdk/models/difference.py +++ b/openfga_sdk/models/difference.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Difference: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class Difference: def __init__(self, base=None, subtract=None, local_vars_configuration=None): """Difference - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._base = None self._subtract = None @@ -66,7 +63,10 @@ def base(self, base): :param base: The base of this Difference. :type base: Userset """ - if self.local_vars_configuration.client_side_validation and base is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and base is None + ): raise ValueError("Invalid value for `base`, must not be `None`") self._base = base @@ -89,7 +89,10 @@ def subtract(self, subtract): :param subtract: The subtract of this Difference. :type subtract: Userset """ - if self.local_vars_configuration.client_side_validation and subtract is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and subtract is None + ): raise ValueError("Invalid value for `subtract`, must not be `None`") self._subtract = subtract diff --git a/openfga_sdk/models/error_code.py b/openfga_sdk/models/error_code.py index 6ec2af04..a699c798 100644 --- a/openfga_sdk/models/error_code.py +++ b/openfga_sdk/models/error_code.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ErrorCode: """NOTE: This class is auto generated by OpenAPI Generator. @@ -156,9 +154,8 @@ class ErrorCode: def __init__(self, local_vars_configuration=None): """ErrorCode - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/expand_request.py b/openfga_sdk/models/expand_request.py index 03696fcf..26e9b36f 100644 --- a/openfga_sdk/models/expand_request.py +++ b/openfga_sdk/models/expand_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ExpandRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """ExpandRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_key = None self._authorization_model_id = None @@ -90,7 +87,10 @@ def tuple_key(self, tuple_key): :param tuple_key: The tuple_key of this ExpandRequest. :type tuple_key: ExpandRequestTupleKey """ - if self.local_vars_configuration.client_side_validation and tuple_key is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_key is None + ): raise ValueError("Invalid value for `tuple_key`, must not be `None`") self._tuple_key = tuple_key diff --git a/openfga_sdk/models/expand_request_tuple_key.py b/openfga_sdk/models/expand_request_tuple_key.py index c2e77d25..2501cdc9 100644 --- a/openfga_sdk/models/expand_request_tuple_key.py +++ b/openfga_sdk/models/expand_request_tuple_key.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ExpandRequestTupleKey: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ExpandRequestTupleKey: def __init__(self, relation=None, object=None, local_vars_configuration=None): """ExpandRequestTupleKey - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._relation = None self._object = None @@ -66,7 +63,10 @@ def relation(self, relation): :param relation: The relation of this ExpandRequestTupleKey. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation @@ -89,7 +89,10 @@ def object(self, object): :param object: The object of this ExpandRequestTupleKey. :type object: str """ - if self.local_vars_configuration.client_side_validation and object is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and object is None + ): raise ValueError("Invalid value for `object`, must not be `None`") self._object = object diff --git a/openfga_sdk/models/expand_response.py b/openfga_sdk/models/expand_response.py index 331f01ae..85f9d68b 100644 --- a/openfga_sdk/models/expand_response.py +++ b/openfga_sdk/models/expand_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ExpandResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ExpandResponse: def __init__(self, tree=None, local_vars_configuration=None): """ExpandResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tree = None self.discriminator = None diff --git a/openfga_sdk/models/fga_object.py b/openfga_sdk/models/fga_object.py index a0b069ec..84f8eb51 100644 --- a/openfga_sdk/models/fga_object.py +++ b/openfga_sdk/models/fga_object.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class FgaObject: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class FgaObject: def __init__(self, type=None, id=None, local_vars_configuration=None): """FgaObject - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type = None self._id = None @@ -66,7 +63,10 @@ def type(self, type): :param type: The type of this FgaObject. :type type: str """ - if self.local_vars_configuration.client_side_validation and type is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type is None + ): raise ValueError("Invalid value for `type`, must not be `None`") self._type = type @@ -89,7 +89,10 @@ def id(self, id): :param id: The id of this FgaObject. :type id: str """ - if self.local_vars_configuration.client_side_validation and id is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and id is None + ): raise ValueError("Invalid value for `id`, must not be `None`") self._id = id diff --git a/openfga_sdk/models/forbidden_response.py b/openfga_sdk/models/forbidden_response.py index 41ce7e9e..d81e70aa 100644 --- a/openfga_sdk/models/forbidden_response.py +++ b/openfga_sdk/models/forbidden_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ForbiddenResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ForbiddenResponse: def __init__(self, code=None, message=None, local_vars_configuration=None): """ForbiddenResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/get_store_response.py b/openfga_sdk/models/get_store_response.py index 1b0537da..230c87c7 100644 --- a/openfga_sdk/models/get_store_response.py +++ b/openfga_sdk/models/get_store_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class GetStoreResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -57,9 +55,8 @@ def __init__( local_vars_configuration=None, ): """GetStoreResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._id = None self._name = None @@ -93,7 +90,10 @@ def id(self, id): :param id: The id of this GetStoreResponse. :type id: str """ - if self.local_vars_configuration.client_side_validation and id is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and id is None + ): raise ValueError("Invalid value for `id`, must not be `None`") self._id = id @@ -116,7 +116,10 @@ def name(self, name): :param name: The name of this GetStoreResponse. :type name: str """ - if self.local_vars_configuration.client_side_validation and name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and name is None + ): raise ValueError("Invalid value for `name`, must not be `None`") self._name = name @@ -139,7 +142,10 @@ def created_at(self, created_at): :param created_at: The created_at of this GetStoreResponse. :type created_at: datetime """ - if self.local_vars_configuration.client_side_validation and created_at is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and created_at is None + ): raise ValueError("Invalid value for `created_at`, must not be `None`") self._created_at = created_at @@ -162,7 +168,10 @@ def updated_at(self, updated_at): :param updated_at: The updated_at of this GetStoreResponse. :type updated_at: datetime """ - if self.local_vars_configuration.client_side_validation and updated_at is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and updated_at is None + ): raise ValueError("Invalid value for `updated_at`, must not be `None`") self._updated_at = updated_at diff --git a/openfga_sdk/models/internal_error_code.py b/openfga_sdk/models/internal_error_code.py index df44b97b..7909b68c 100644 --- a/openfga_sdk/models/internal_error_code.py +++ b/openfga_sdk/models/internal_error_code.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class InternalErrorCode: """NOTE: This class is auto generated by OpenAPI Generator. @@ -64,9 +62,8 @@ class InternalErrorCode: def __init__(self, local_vars_configuration=None): """InternalErrorCode - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/internal_error_message_response.py b/openfga_sdk/models/internal_error_message_response.py index 06741dd3..e480cd21 100644 --- a/openfga_sdk/models/internal_error_message_response.py +++ b/openfga_sdk/models/internal_error_message_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class InternalErrorMessageResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class InternalErrorMessageResponse: def __init__(self, code=None, message=None, local_vars_configuration=None): """InternalErrorMessageResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/leaf.py b/openfga_sdk/models/leaf.py index 9090b66f..19f587c4 100644 --- a/openfga_sdk/models/leaf.py +++ b/openfga_sdk/models/leaf.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Leaf: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """Leaf - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._users = None self._computed = None diff --git a/openfga_sdk/models/list_objects_request.py b/openfga_sdk/models/list_objects_request.py index 36c42f0f..ac7d19b9 100644 --- a/openfga_sdk/models/list_objects_request.py +++ b/openfga_sdk/models/list_objects_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ListObjectsRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -63,9 +61,8 @@ def __init__( local_vars_configuration=None, ): """ListObjectsRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._authorization_model_id = None self._type = None @@ -127,7 +124,10 @@ def type(self, type): :param type: The type of this ListObjectsRequest. :type type: str """ - if self.local_vars_configuration.client_side_validation and type is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type is None + ): raise ValueError("Invalid value for `type`, must not be `None`") self._type = type @@ -150,7 +150,10 @@ def relation(self, relation): :param relation: The relation of this ListObjectsRequest. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation @@ -173,7 +176,10 @@ def user(self, user): :param user: The user of this ListObjectsRequest. :type user: str """ - if self.local_vars_configuration.client_side_validation and user is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and user is None + ): raise ValueError("Invalid value for `user`, must not be `None`") self._user = user diff --git a/openfga_sdk/models/list_objects_response.py b/openfga_sdk/models/list_objects_response.py index 7d879d7b..024aa0df 100644 --- a/openfga_sdk/models/list_objects_response.py +++ b/openfga_sdk/models/list_objects_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ListObjectsResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ListObjectsResponse: def __init__(self, objects=None, local_vars_configuration=None): """ListObjectsResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._objects = None self.discriminator = None @@ -64,7 +61,10 @@ def objects(self, objects): :param objects: The objects of this ListObjectsResponse. :type objects: list[str] """ - if self.local_vars_configuration.client_side_validation and objects is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and objects is None + ): raise ValueError("Invalid value for `objects`, must not be `None`") self._objects = objects diff --git a/openfga_sdk/models/list_stores_response.py b/openfga_sdk/models/list_stores_response.py index 4ea736f7..856b69d5 100644 --- a/openfga_sdk/models/list_stores_response.py +++ b/openfga_sdk/models/list_stores_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ListStoresResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -45,9 +43,8 @@ def __init__( self, stores=None, continuation_token=None, local_vars_configuration=None ): """ListStoresResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._stores = None self._continuation_token = None @@ -74,7 +71,10 @@ def stores(self, stores): :param stores: The stores of this ListStoresResponse. :type stores: list[Store] """ - if self.local_vars_configuration.client_side_validation and stores is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and stores is None + ): raise ValueError("Invalid value for `stores`, must not be `None`") self._stores = stores @@ -100,7 +100,7 @@ def continuation_token(self, continuation_token): :type continuation_token: str """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and continuation_token is None ): raise ValueError( diff --git a/openfga_sdk/models/list_users_request.py b/openfga_sdk/models/list_users_request.py index dce01119..8f79c1fb 100644 --- a/openfga_sdk/models/list_users_request.py +++ b/openfga_sdk/models/list_users_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ListUsersRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -63,9 +61,8 @@ def __init__( local_vars_configuration=None, ): """ListUsersRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._authorization_model_id = None self._object = None @@ -127,7 +124,10 @@ def object(self, object): :param object: The object of this ListUsersRequest. :type object: FgaObject """ - if self.local_vars_configuration.client_side_validation and object is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and object is None + ): raise ValueError("Invalid value for `object`, must not be `None`") self._object = object @@ -150,7 +150,10 @@ def relation(self, relation): :param relation: The relation of this ListUsersRequest. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation @@ -176,7 +179,7 @@ def user_filters(self, user_filters): :type user_filters: list[UserTypeFilter] """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and user_filters is None ): raise ValueError("Invalid value for `user_filters`, must not be `None`") diff --git a/openfga_sdk/models/list_users_response.py b/openfga_sdk/models/list_users_response.py index 29a3b634..3855bcb4 100644 --- a/openfga_sdk/models/list_users_response.py +++ b/openfga_sdk/models/list_users_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ListUsersResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ListUsersResponse: def __init__(self, users=None, local_vars_configuration=None): """ListUsersResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._users = None self.discriminator = None @@ -64,7 +61,10 @@ def users(self, users): :param users: The users of this ListUsersResponse. :type users: list[User] """ - if self.local_vars_configuration.client_side_validation and users is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and users is None + ): raise ValueError("Invalid value for `users`, must not be `None`") self._users = users diff --git a/openfga_sdk/models/metadata.py b/openfga_sdk/models/metadata.py index 5919d041..7cd6b971 100644 --- a/openfga_sdk/models/metadata.py +++ b/openfga_sdk/models/metadata.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Metadata: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """Metadata - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._relations = None self._module = None diff --git a/openfga_sdk/models/node.py b/openfga_sdk/models/node.py index b0625a04..ce03941d 100644 --- a/openfga_sdk/models/node.py +++ b/openfga_sdk/models/node.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Node: """NOTE: This class is auto generated by OpenAPI Generator. @@ -57,9 +55,8 @@ def __init__( local_vars_configuration=None, ): """Node - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._name = None self._leaf = None @@ -96,7 +93,10 @@ def name(self, name): :param name: The name of this Node. :type name: str """ - if self.local_vars_configuration.client_side_validation and name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and name is None + ): raise ValueError("Invalid value for `name`, must not be `None`") self._name = name diff --git a/openfga_sdk/models/nodes.py b/openfga_sdk/models/nodes.py index 84f1b401..2f353ed0 100644 --- a/openfga_sdk/models/nodes.py +++ b/openfga_sdk/models/nodes.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Nodes: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class Nodes: def __init__(self, nodes=None, local_vars_configuration=None): """Nodes - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._nodes = None self.discriminator = None @@ -64,7 +61,10 @@ def nodes(self, nodes): :param nodes: The nodes of this Nodes. :type nodes: list[Node] """ - if self.local_vars_configuration.client_side_validation and nodes is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and nodes is None + ): raise ValueError("Invalid value for `nodes`, must not be `None`") self._nodes = nodes diff --git a/openfga_sdk/models/not_found_error_code.py b/openfga_sdk/models/not_found_error_code.py index f4e3a887..2ab38fd7 100644 --- a/openfga_sdk/models/not_found_error_code.py +++ b/openfga_sdk/models/not_found_error_code.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class NotFoundErrorCode: """NOTE: This class is auto generated by OpenAPI Generator. @@ -52,9 +50,8 @@ class NotFoundErrorCode: def __init__(self, local_vars_configuration=None): """NotFoundErrorCode - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/null_value.py b/openfga_sdk/models/null_value.py index 30140475..1ded97ee 100644 --- a/openfga_sdk/models/null_value.py +++ b/openfga_sdk/models/null_value.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class NullValue: """NOTE: This class is auto generated by OpenAPI Generator. @@ -44,9 +42,8 @@ class NullValue: def __init__(self, local_vars_configuration=None): """NullValue - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/object_relation.py b/openfga_sdk/models/object_relation.py index 0044a621..431edd42 100644 --- a/openfga_sdk/models/object_relation.py +++ b/openfga_sdk/models/object_relation.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ObjectRelation: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ObjectRelation: def __init__(self, object=None, relation=None, local_vars_configuration=None): """ObjectRelation - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._object = None self._relation = None diff --git a/openfga_sdk/models/path_unknown_error_message_response.py b/openfga_sdk/models/path_unknown_error_message_response.py index 46030b5d..ea56f7eb 100644 --- a/openfga_sdk/models/path_unknown_error_message_response.py +++ b/openfga_sdk/models/path_unknown_error_message_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class PathUnknownErrorMessageResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class PathUnknownErrorMessageResponse: def __init__(self, code=None, message=None, local_vars_configuration=None): """PathUnknownErrorMessageResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/read_assertions_response.py b/openfga_sdk/models/read_assertions_response.py index 753bedf4..1efd5dc0 100644 --- a/openfga_sdk/models/read_assertions_response.py +++ b/openfga_sdk/models/read_assertions_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ReadAssertionsResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -48,9 +46,8 @@ def __init__( local_vars_configuration=None, ): """ReadAssertionsResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._authorization_model_id = None self._assertions = None @@ -79,7 +76,7 @@ def authorization_model_id(self, authorization_model_id): :type authorization_model_id: str """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and authorization_model_id is None ): raise ValueError( diff --git a/openfga_sdk/models/read_authorization_model_response.py b/openfga_sdk/models/read_authorization_model_response.py index e65b8d5b..01ce9c50 100644 --- a/openfga_sdk/models/read_authorization_model_response.py +++ b/openfga_sdk/models/read_authorization_model_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ReadAuthorizationModelResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ReadAuthorizationModelResponse: def __init__(self, authorization_model=None, local_vars_configuration=None): """ReadAuthorizationModelResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._authorization_model = None self.discriminator = None diff --git a/openfga_sdk/models/read_authorization_models_response.py b/openfga_sdk/models/read_authorization_models_response.py index 63d59410..b6196079 100644 --- a/openfga_sdk/models/read_authorization_models_response.py +++ b/openfga_sdk/models/read_authorization_models_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ReadAuthorizationModelsResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -48,9 +46,8 @@ def __init__( local_vars_configuration=None, ): """ReadAuthorizationModelsResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._authorization_models = None self._continuation_token = None @@ -79,7 +76,7 @@ def authorization_models(self, authorization_models): :type authorization_models: list[AuthorizationModel] """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and authorization_models is None ): raise ValueError( diff --git a/openfga_sdk/models/read_changes_response.py b/openfga_sdk/models/read_changes_response.py index ca3506c8..04c2777d 100644 --- a/openfga_sdk/models/read_changes_response.py +++ b/openfga_sdk/models/read_changes_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ReadChangesResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -45,9 +43,8 @@ def __init__( self, changes=None, continuation_token=None, local_vars_configuration=None ): """ReadChangesResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._changes = None self._continuation_token = None @@ -75,7 +72,10 @@ def changes(self, changes): :param changes: The changes of this ReadChangesResponse. :type changes: list[TupleChange] """ - if self.local_vars_configuration.client_side_validation and changes is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and changes is None + ): raise ValueError("Invalid value for `changes`, must not be `None`") self._changes = changes diff --git a/openfga_sdk/models/read_request.py b/openfga_sdk/models/read_request.py index 97ae802a..831d5dc8 100644 --- a/openfga_sdk/models/read_request.py +++ b/openfga_sdk/models/read_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ReadRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """ReadRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_key = None self._page_size = None diff --git a/openfga_sdk/models/read_request_tuple_key.py b/openfga_sdk/models/read_request_tuple_key.py index 2e365e81..47f3b204 100644 --- a/openfga_sdk/models/read_request_tuple_key.py +++ b/openfga_sdk/models/read_request_tuple_key.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ReadRequestTupleKey: """NOTE: This class is auto generated by OpenAPI Generator. @@ -43,9 +41,8 @@ def __init__( self, user=None, relation=None, object=None, local_vars_configuration=None ): """ReadRequestTupleKey - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._user = None self._relation = None diff --git a/openfga_sdk/models/read_response.py b/openfga_sdk/models/read_response.py index a545d258..cd3e01c6 100644 --- a/openfga_sdk/models/read_response.py +++ b/openfga_sdk/models/read_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ReadResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -45,9 +43,8 @@ def __init__( self, tuples=None, continuation_token=None, local_vars_configuration=None ): """ReadResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuples = None self._continuation_token = None @@ -74,7 +71,10 @@ def tuples(self, tuples): :param tuples: The tuples of this ReadResponse. :type tuples: list[Tuple] """ - if self.local_vars_configuration.client_side_validation and tuples is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuples is None + ): raise ValueError("Invalid value for `tuples`, must not be `None`") self._tuples = tuples @@ -100,7 +100,7 @@ def continuation_token(self, continuation_token): :type continuation_token: str """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and continuation_token is None ): raise ValueError( diff --git a/openfga_sdk/models/relation_metadata.py b/openfga_sdk/models/relation_metadata.py index 3f240ce8..e83bc54e 100644 --- a/openfga_sdk/models/relation_metadata.py +++ b/openfga_sdk/models/relation_metadata.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class RelationMetadata: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """RelationMetadata - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._directly_related_user_types = None self._module = None diff --git a/openfga_sdk/models/relation_reference.py b/openfga_sdk/models/relation_reference.py index 79c1fa2d..75e034c7 100644 --- a/openfga_sdk/models/relation_reference.py +++ b/openfga_sdk/models/relation_reference.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class RelationReference: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """RelationReference - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type = None self._relation = None @@ -90,7 +87,10 @@ def type(self, type): :param type: The type of this RelationReference. :type type: str """ - if self.local_vars_configuration.client_side_validation and type is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type is None + ): raise ValueError("Invalid value for `type`, must not be `None`") self._type = type diff --git a/openfga_sdk/models/relationship_condition.py b/openfga_sdk/models/relationship_condition.py index eda8dc68..f1f3884f 100644 --- a/openfga_sdk/models/relationship_condition.py +++ b/openfga_sdk/models/relationship_condition.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class RelationshipCondition: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class RelationshipCondition: def __init__(self, name=None, context=None, local_vars_configuration=None): """RelationshipCondition - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._name = None self._context = None @@ -69,7 +66,10 @@ def name(self, name): :param name: The name of this RelationshipCondition. :type name: str """ - if self.local_vars_configuration.client_side_validation and name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and name is None + ): raise ValueError("Invalid value for `name`, must not be `None`") self._name = name diff --git a/openfga_sdk/models/source_info.py b/openfga_sdk/models/source_info.py index b9c5dbce..795bc8c1 100644 --- a/openfga_sdk/models/source_info.py +++ b/openfga_sdk/models/source_info.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class SourceInfo: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class SourceInfo: def __init__(self, file=None, local_vars_configuration=None): """SourceInfo - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._file = None self.discriminator = None diff --git a/openfga_sdk/models/status.py b/openfga_sdk/models/status.py index 34e65890..e62ff1c1 100644 --- a/openfga_sdk/models/status.py +++ b/openfga_sdk/models/status.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Status: """NOTE: This class is auto generated by OpenAPI Generator. @@ -47,9 +45,8 @@ def __init__( self, code=None, message=None, details=None, local_vars_configuration=None ): """Status - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/store.py b/openfga_sdk/models/store.py index ef5af080..80078f6a 100644 --- a/openfga_sdk/models/store.py +++ b/openfga_sdk/models/store.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Store: """NOTE: This class is auto generated by OpenAPI Generator. @@ -57,9 +55,8 @@ def __init__( local_vars_configuration=None, ): """Store - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._id = None self._name = None @@ -93,7 +90,10 @@ def id(self, id): :param id: The id of this Store. :type id: str """ - if self.local_vars_configuration.client_side_validation and id is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and id is None + ): raise ValueError("Invalid value for `id`, must not be `None`") self._id = id @@ -116,7 +116,10 @@ def name(self, name): :param name: The name of this Store. :type name: str """ - if self.local_vars_configuration.client_side_validation and name is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and name is None + ): raise ValueError("Invalid value for `name`, must not be `None`") self._name = name @@ -139,7 +142,10 @@ def created_at(self, created_at): :param created_at: The created_at of this Store. :type created_at: datetime """ - if self.local_vars_configuration.client_side_validation and created_at is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and created_at is None + ): raise ValueError("Invalid value for `created_at`, must not be `None`") self._created_at = created_at @@ -162,7 +168,10 @@ def updated_at(self, updated_at): :param updated_at: The updated_at of this Store. :type updated_at: datetime """ - if self.local_vars_configuration.client_side_validation and updated_at is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and updated_at is None + ): raise ValueError("Invalid value for `updated_at`, must not be `None`") self._updated_at = updated_at diff --git a/openfga_sdk/models/stream_result_of_streamed_list_objects_response.py b/openfga_sdk/models/stream_result_of_streamed_list_objects_response.py index 4717972b..9b152594 100644 --- a/openfga_sdk/models/stream_result_of_streamed_list_objects_response.py +++ b/openfga_sdk/models/stream_result_of_streamed_list_objects_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class StreamResultOfStreamedListObjectsResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -40,9 +38,8 @@ class StreamResultOfStreamedListObjectsResponse: def __init__(self, result=None, error=None, local_vars_configuration=None): """StreamResultOfStreamedListObjectsResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._result = None self._error = None diff --git a/openfga_sdk/models/streamed_list_objects_response.py b/openfga_sdk/models/streamed_list_objects_response.py index d8322cf4..c425ac97 100644 --- a/openfga_sdk/models/streamed_list_objects_response.py +++ b/openfga_sdk/models/streamed_list_objects_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class StreamedListObjectsResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class StreamedListObjectsResponse: def __init__(self, object=None, local_vars_configuration=None): """StreamedListObjectsResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._object = None self.discriminator = None @@ -64,7 +61,10 @@ def object(self, object): :param object: The object of this StreamedListObjectsResponse. :type object: str """ - if self.local_vars_configuration.client_side_validation and object is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and object is None + ): raise ValueError("Invalid value for `object`, must not be `None`") self._object = object diff --git a/openfga_sdk/models/tuple.py b/openfga_sdk/models/tuple.py index 11cc2efa..2adaa809 100644 --- a/openfga_sdk/models/tuple.py +++ b/openfga_sdk/models/tuple.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Tuple: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class Tuple: def __init__(self, key=None, timestamp=None, local_vars_configuration=None): """Tuple - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._key = None self._timestamp = None @@ -66,7 +63,10 @@ def key(self, key): :param key: The key of this Tuple. :type key: TupleKey """ - if self.local_vars_configuration.client_side_validation and key is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and key is None + ): raise ValueError("Invalid value for `key`, must not be `None`") self._key = key @@ -89,7 +89,10 @@ def timestamp(self, timestamp): :param timestamp: The timestamp of this Tuple. :type timestamp: datetime """ - if self.local_vars_configuration.client_side_validation and timestamp is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and timestamp is None + ): raise ValueError("Invalid value for `timestamp`, must not be `None`") self._timestamp = timestamp diff --git a/openfga_sdk/models/tuple_change.py b/openfga_sdk/models/tuple_change.py index 8a1ee254..def2d35d 100644 --- a/openfga_sdk/models/tuple_change.py +++ b/openfga_sdk/models/tuple_change.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TupleChange: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """TupleChange - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_key = None self._operation = None @@ -82,7 +79,10 @@ def tuple_key(self, tuple_key): :param tuple_key: The tuple_key of this TupleChange. :type tuple_key: TupleKey """ - if self.local_vars_configuration.client_side_validation and tuple_key is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_key is None + ): raise ValueError("Invalid value for `tuple_key`, must not be `None`") self._tuple_key = tuple_key @@ -105,7 +105,10 @@ def operation(self, operation): :param operation: The operation of this TupleChange. :type operation: TupleOperation """ - if self.local_vars_configuration.client_side_validation and operation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and operation is None + ): raise ValueError("Invalid value for `operation`, must not be `None`") self._operation = operation @@ -128,7 +131,10 @@ def timestamp(self, timestamp): :param timestamp: The timestamp of this TupleChange. :type timestamp: datetime """ - if self.local_vars_configuration.client_side_validation and timestamp is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and timestamp is None + ): raise ValueError("Invalid value for `timestamp`, must not be `None`") self._timestamp = timestamp diff --git a/openfga_sdk/models/tuple_key.py b/openfga_sdk/models/tuple_key.py index 692edbdf..5746a09f 100644 --- a/openfga_sdk/models/tuple_key.py +++ b/openfga_sdk/models/tuple_key.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TupleKey: """NOTE: This class is auto generated by OpenAPI Generator. @@ -54,9 +52,8 @@ def __init__( local_vars_configuration=None, ): """TupleKey - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._user = None self._relation = None @@ -88,7 +85,10 @@ def user(self, user): :param user: The user of this TupleKey. :type user: str """ - if self.local_vars_configuration.client_side_validation and user is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and user is None + ): raise ValueError("Invalid value for `user`, must not be `None`") self._user = user @@ -111,7 +111,10 @@ def relation(self, relation): :param relation: The relation of this TupleKey. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation @@ -134,7 +137,10 @@ def object(self, object): :param object: The object of this TupleKey. :type object: str """ - if self.local_vars_configuration.client_side_validation and object is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and object is None + ): raise ValueError("Invalid value for `object`, must not be `None`") self._object = object diff --git a/openfga_sdk/models/tuple_key_without_condition.py b/openfga_sdk/models/tuple_key_without_condition.py index 2bda4b8c..e5979ca6 100644 --- a/openfga_sdk/models/tuple_key_without_condition.py +++ b/openfga_sdk/models/tuple_key_without_condition.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TupleKeyWithoutCondition: """NOTE: This class is auto generated by OpenAPI Generator. @@ -43,9 +41,8 @@ def __init__( self, user=None, relation=None, object=None, local_vars_configuration=None ): """TupleKeyWithoutCondition - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._user = None self._relation = None @@ -74,7 +71,10 @@ def user(self, user): :param user: The user of this TupleKeyWithoutCondition. :type user: str """ - if self.local_vars_configuration.client_side_validation and user is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and user is None + ): raise ValueError("Invalid value for `user`, must not be `None`") self._user = user @@ -97,7 +97,10 @@ def relation(self, relation): :param relation: The relation of this TupleKeyWithoutCondition. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation @@ -120,7 +123,10 @@ def object(self, object): :param object: The object of this TupleKeyWithoutCondition. :type object: str """ - if self.local_vars_configuration.client_side_validation and object is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and object is None + ): raise ValueError("Invalid value for `object`, must not be `None`") self._object = object diff --git a/openfga_sdk/models/tuple_operation.py b/openfga_sdk/models/tuple_operation.py index b76958cb..696903ee 100644 --- a/openfga_sdk/models/tuple_operation.py +++ b/openfga_sdk/models/tuple_operation.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TupleOperation: """NOTE: This class is auto generated by OpenAPI Generator. @@ -45,9 +43,8 @@ class TupleOperation: def __init__(self, local_vars_configuration=None): """TupleOperation - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/tuple_to_userset.py b/openfga_sdk/models/tuple_to_userset.py index 5c31f679..eae02bca 100644 --- a/openfga_sdk/models/tuple_to_userset.py +++ b/openfga_sdk/models/tuple_to_userset.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TupleToUserset: """NOTE: This class is auto generated by OpenAPI Generator. @@ -45,9 +43,8 @@ def __init__( self, tupleset=None, computed_userset=None, local_vars_configuration=None ): """TupleToUserset - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tupleset = None self._computed_userset = None @@ -74,7 +71,10 @@ def tupleset(self, tupleset): :param tupleset: The tupleset of this TupleToUserset. :type tupleset: ObjectRelation """ - if self.local_vars_configuration.client_side_validation and tupleset is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tupleset is None + ): raise ValueError("Invalid value for `tupleset`, must not be `None`") self._tupleset = tupleset @@ -98,7 +98,7 @@ def computed_userset(self, computed_userset): :type computed_userset: ObjectRelation """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and computed_userset is None ): raise ValueError("Invalid value for `computed_userset`, must not be `None`") diff --git a/openfga_sdk/models/type_definition.py b/openfga_sdk/models/type_definition.py index b63dd30c..9b637e42 100644 --- a/openfga_sdk/models/type_definition.py +++ b/openfga_sdk/models/type_definition.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TypeDefinition: """NOTE: This class is auto generated by OpenAPI Generator. @@ -47,9 +45,8 @@ def __init__( self, type=None, relations=None, metadata=None, local_vars_configuration=None ): """TypeDefinition - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type = None self._relations = None @@ -80,7 +77,10 @@ def type(self, type): :param type: The type of this TypeDefinition. :type type: str """ - if self.local_vars_configuration.client_side_validation and type is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type is None + ): raise ValueError("Invalid value for `type`, must not be `None`") self._type = type diff --git a/openfga_sdk/models/type_name.py b/openfga_sdk/models/type_name.py index 88d61a27..902c5f6f 100644 --- a/openfga_sdk/models/type_name.py +++ b/openfga_sdk/models/type_name.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TypeName: """NOTE: This class is auto generated by OpenAPI Generator. @@ -68,9 +66,8 @@ class TypeName: def __init__(self, local_vars_configuration=None): """TypeName - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/typed_wildcard.py b/openfga_sdk/models/typed_wildcard.py index 2ebf242b..a22e538f 100644 --- a/openfga_sdk/models/typed_wildcard.py +++ b/openfga_sdk/models/typed_wildcard.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class TypedWildcard: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class TypedWildcard: def __init__(self, type=None, local_vars_configuration=None): """TypedWildcard - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type = None self.discriminator = None @@ -64,7 +61,10 @@ def type(self, type): :param type: The type of this TypedWildcard. :type type: str """ - if self.local_vars_configuration.client_side_validation and type is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type is None + ): raise ValueError("Invalid value for `type`, must not be `None`") self._type = type diff --git a/openfga_sdk/models/unauthenticated_response.py b/openfga_sdk/models/unauthenticated_response.py index 9bd16389..4fe2895e 100644 --- a/openfga_sdk/models/unauthenticated_response.py +++ b/openfga_sdk/models/unauthenticated_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UnauthenticatedResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class UnauthenticatedResponse: def __init__(self, code=None, message=None, local_vars_configuration=None): """UnauthenticatedResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/unprocessable_content_error_code.py b/openfga_sdk/models/unprocessable_content_error_code.py index c5bde4e6..b69d79d9 100644 --- a/openfga_sdk/models/unprocessable_content_error_code.py +++ b/openfga_sdk/models/unprocessable_content_error_code.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UnprocessableContentErrorCode: """NOTE: This class is auto generated by OpenAPI Generator. @@ -45,9 +43,8 @@ class UnprocessableContentErrorCode: def __init__(self, local_vars_configuration=None): """UnprocessableContentErrorCode - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self.discriminator = None def to_dict(self, serialize=False): diff --git a/openfga_sdk/models/unprocessable_content_message_response.py b/openfga_sdk/models/unprocessable_content_message_response.py index cd8f5c35..cab829c8 100644 --- a/openfga_sdk/models/unprocessable_content_message_response.py +++ b/openfga_sdk/models/unprocessable_content_message_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UnprocessableContentMessageResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -40,9 +38,8 @@ class UnprocessableContentMessageResponse: def __init__(self, code=None, message=None, local_vars_configuration=None): """UnprocessableContentMessageResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/user.py b/openfga_sdk/models/user.py index ba5a677d..6c47cce3 100644 --- a/openfga_sdk/models/user.py +++ b/openfga_sdk/models/user.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class User: """NOTE: This class is auto generated by OpenAPI Generator. @@ -47,9 +45,8 @@ def __init__( self, object=None, userset=None, wildcard=None, local_vars_configuration=None ): """User - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._object = None self._userset = None diff --git a/openfga_sdk/models/user_type_filter.py b/openfga_sdk/models/user_type_filter.py index fc2476f8..155fba53 100644 --- a/openfga_sdk/models/user_type_filter.py +++ b/openfga_sdk/models/user_type_filter.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UserTypeFilter: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class UserTypeFilter: def __init__(self, type=None, relation=None, local_vars_configuration=None): """UserTypeFilter - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type = None self._relation = None @@ -67,7 +64,10 @@ def type(self, type): :param type: The type of this UserTypeFilter. :type type: str """ - if self.local_vars_configuration.client_side_validation and type is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type is None + ): raise ValueError("Invalid value for `type`, must not be `None`") self._type = type diff --git a/openfga_sdk/models/users.py b/openfga_sdk/models/users.py index 8ef88587..9e407090 100644 --- a/openfga_sdk/models/users.py +++ b/openfga_sdk/models/users.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Users: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class Users: def __init__(self, users=None, local_vars_configuration=None): """Users - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._users = None self.discriminator = None @@ -64,7 +61,10 @@ def users(self, users): :param users: The users of this Users. :type users: list[str] """ - if self.local_vars_configuration.client_side_validation and users is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and users is None + ): raise ValueError("Invalid value for `users`, must not be `None`") self._users = users diff --git a/openfga_sdk/models/userset.py b/openfga_sdk/models/userset.py index 54c16ab6..9b3c3f3a 100644 --- a/openfga_sdk/models/userset.py +++ b/openfga_sdk/models/userset.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Userset: """NOTE: This class is auto generated by OpenAPI Generator. @@ -60,9 +58,8 @@ def __init__( local_vars_configuration=None, ): """Userset - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._this = None self._computed_userset = None diff --git a/openfga_sdk/models/userset_tree.py b/openfga_sdk/models/userset_tree.py index 2b825ecf..b9e44bb9 100644 --- a/openfga_sdk/models/userset_tree.py +++ b/openfga_sdk/models/userset_tree.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UsersetTree: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class UsersetTree: def __init__(self, root=None, local_vars_configuration=None): """UsersetTree - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._root = None self.discriminator = None diff --git a/openfga_sdk/models/userset_tree_difference.py b/openfga_sdk/models/userset_tree_difference.py index e391e245..b8b4dbcc 100644 --- a/openfga_sdk/models/userset_tree_difference.py +++ b/openfga_sdk/models/userset_tree_difference.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UsersetTreeDifference: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class UsersetTreeDifference: def __init__(self, base=None, subtract=None, local_vars_configuration=None): """UsersetTreeDifference - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._base = None self._subtract = None @@ -66,7 +63,10 @@ def base(self, base): :param base: The base of this UsersetTreeDifference. :type base: Node """ - if self.local_vars_configuration.client_side_validation and base is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and base is None + ): raise ValueError("Invalid value for `base`, must not be `None`") self._base = base @@ -89,7 +89,10 @@ def subtract(self, subtract): :param subtract: The subtract of this UsersetTreeDifference. :type subtract: Node """ - if self.local_vars_configuration.client_side_validation and subtract is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and subtract is None + ): raise ValueError("Invalid value for `subtract`, must not be `None`") self._subtract = subtract diff --git a/openfga_sdk/models/userset_tree_tuple_to_userset.py b/openfga_sdk/models/userset_tree_tuple_to_userset.py index 16e3e44c..6d5321fe 100644 --- a/openfga_sdk/models/userset_tree_tuple_to_userset.py +++ b/openfga_sdk/models/userset_tree_tuple_to_userset.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UsersetTreeTupleToUserset: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class UsersetTreeTupleToUserset: def __init__(self, tupleset=None, computed=None, local_vars_configuration=None): """UsersetTreeTupleToUserset - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tupleset = None self._computed = None @@ -66,7 +63,10 @@ def tupleset(self, tupleset): :param tupleset: The tupleset of this UsersetTreeTupleToUserset. :type tupleset: str """ - if self.local_vars_configuration.client_side_validation and tupleset is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tupleset is None + ): raise ValueError("Invalid value for `tupleset`, must not be `None`") self._tupleset = tupleset @@ -89,7 +89,10 @@ def computed(self, computed): :param computed: The computed of this UsersetTreeTupleToUserset. :type computed: list[Computed] """ - if self.local_vars_configuration.client_side_validation and computed is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and computed is None + ): raise ValueError("Invalid value for `computed`, must not be `None`") self._computed = computed diff --git a/openfga_sdk/models/userset_user.py b/openfga_sdk/models/userset_user.py index 42a7e715..e9a26d31 100644 --- a/openfga_sdk/models/userset_user.py +++ b/openfga_sdk/models/userset_user.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class UsersetUser: """NOTE: This class is auto generated by OpenAPI Generator. @@ -39,9 +37,8 @@ def __init__( self, type=None, id=None, relation=None, local_vars_configuration=None ): """UsersetUser - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type = None self._id = None @@ -70,7 +67,10 @@ def type(self, type): :param type: The type of this UsersetUser. :type type: str """ - if self.local_vars_configuration.client_side_validation and type is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and type is None + ): raise ValueError("Invalid value for `type`, must not be `None`") self._type = type @@ -93,7 +93,10 @@ def id(self, id): :param id: The id of this UsersetUser. :type id: str """ - if self.local_vars_configuration.client_side_validation and id is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and id is None + ): raise ValueError("Invalid value for `id`, must not be `None`") self._id = id @@ -116,7 +119,10 @@ def relation(self, relation): :param relation: The relation of this UsersetUser. :type relation: str """ - if self.local_vars_configuration.client_side_validation and relation is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and relation is None + ): raise ValueError("Invalid value for `relation`, must not be `None`") self._relation = relation diff --git a/openfga_sdk/models/usersets.py b/openfga_sdk/models/usersets.py index 3029f6a5..2191a491 100644 --- a/openfga_sdk/models/usersets.py +++ b/openfga_sdk/models/usersets.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class Usersets: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class Usersets: def __init__(self, child=None, local_vars_configuration=None): """Usersets - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._child = None self.discriminator = None @@ -64,7 +61,10 @@ def child(self, child): :param child: The child of this Usersets. :type child: list[Userset] """ - if self.local_vars_configuration.client_side_validation and child is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and child is None + ): raise ValueError("Invalid value for `child`, must not be `None`") self._child = child diff --git a/openfga_sdk/models/validation_error_message_response.py b/openfga_sdk/models/validation_error_message_response.py index 25910ada..f10bad79 100644 --- a/openfga_sdk/models/validation_error_message_response.py +++ b/openfga_sdk/models/validation_error_message_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class ValidationErrorMessageResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class ValidationErrorMessageResponse: def __init__(self, code=None, message=None, local_vars_configuration=None): """ValidationErrorMessageResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._code = None self._message = None diff --git a/openfga_sdk/models/write_assertions_request.py b/openfga_sdk/models/write_assertions_request.py index 56baab72..fede2864 100644 --- a/openfga_sdk/models/write_assertions_request.py +++ b/openfga_sdk/models/write_assertions_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class WriteAssertionsRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class WriteAssertionsRequest: def __init__(self, assertions=None, local_vars_configuration=None): """WriteAssertionsRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._assertions = None self.discriminator = None @@ -64,7 +61,10 @@ def assertions(self, assertions): :param assertions: The assertions of this WriteAssertionsRequest. :type assertions: list[Assertion] """ - if self.local_vars_configuration.client_side_validation and assertions is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and assertions is None + ): raise ValueError("Invalid value for `assertions`, must not be `None`") self._assertions = assertions diff --git a/openfga_sdk/models/write_authorization_model_request.py b/openfga_sdk/models/write_authorization_model_request.py index 7ce5fe48..4f705c9b 100644 --- a/openfga_sdk/models/write_authorization_model_request.py +++ b/openfga_sdk/models/write_authorization_model_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class WriteAuthorizationModelRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """WriteAuthorizationModelRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._type_definitions = None self._schema_version = None @@ -84,7 +81,7 @@ def type_definitions(self, type_definitions): :type type_definitions: list[TypeDefinition] """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and type_definitions is None ): raise ValueError("Invalid value for `type_definitions`, must not be `None`") @@ -110,7 +107,7 @@ def schema_version(self, schema_version): :type schema_version: str """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and schema_version is None ): raise ValueError("Invalid value for `schema_version`, must not be `None`") diff --git a/openfga_sdk/models/write_authorization_model_response.py b/openfga_sdk/models/write_authorization_model_response.py index 891cd3a3..b7fdfff5 100644 --- a/openfga_sdk/models/write_authorization_model_response.py +++ b/openfga_sdk/models/write_authorization_model_response.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class WriteAuthorizationModelResponse: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class WriteAuthorizationModelResponse: def __init__(self, authorization_model_id=None, local_vars_configuration=None): """WriteAuthorizationModelResponse - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._authorization_model_id = None self.discriminator = None @@ -65,7 +62,7 @@ def authorization_model_id(self, authorization_model_id): :type authorization_model_id: str """ if ( - self.local_vars_configuration.client_side_validation + self.local_vars_configuration.get("client_side_validation") == True and authorization_model_id is None ): raise ValueError( diff --git a/openfga_sdk/models/write_request.py b/openfga_sdk/models/write_request.py index 5e26fcd7..b91f0aa2 100644 --- a/openfga_sdk/models/write_request.py +++ b/openfga_sdk/models/write_request.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class WriteRequest: """NOTE: This class is auto generated by OpenAPI Generator. @@ -51,9 +49,8 @@ def __init__( local_vars_configuration=None, ): """WriteRequest - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._writes = None self._deletes = None diff --git a/openfga_sdk/models/write_request_deletes.py b/openfga_sdk/models/write_request_deletes.py index f735c24f..0070812f 100644 --- a/openfga_sdk/models/write_request_deletes.py +++ b/openfga_sdk/models/write_request_deletes.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class WriteRequestDeletes: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class WriteRequestDeletes: def __init__(self, tuple_keys=None, local_vars_configuration=None): """WriteRequestDeletes - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_keys = None self.discriminator = None @@ -64,7 +61,10 @@ def tuple_keys(self, tuple_keys): :param tuple_keys: The tuple_keys of this WriteRequestDeletes. :type tuple_keys: list[TupleKeyWithoutCondition] """ - if self.local_vars_configuration.client_side_validation and tuple_keys is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_keys is None + ): raise ValueError("Invalid value for `tuple_keys`, must not be `None`") self._tuple_keys = tuple_keys diff --git a/openfga_sdk/models/write_request_writes.py b/openfga_sdk/models/write_request_writes.py index d6bbae17..14bbf317 100644 --- a/openfga_sdk/models/write_request_writes.py +++ b/openfga_sdk/models/write_request_writes.py @@ -14,8 +14,6 @@ from inspect import getfullargspec -from openfga_sdk.configuration import Configuration - class WriteRequestWrites: """NOTE: This class is auto generated by OpenAPI Generator. @@ -37,9 +35,8 @@ class WriteRequestWrites: def __init__(self, tuple_keys=None, local_vars_configuration=None): """WriteRequestWrites - a model defined in OpenAPI""" - if local_vars_configuration is None: - local_vars_configuration = Configuration.get_default_copy() - self.local_vars_configuration = local_vars_configuration + + self.local_vars_configuration: dict = local_vars_configuration or {} self._tuple_keys = None self.discriminator = None @@ -64,7 +61,10 @@ def tuple_keys(self, tuple_keys): :param tuple_keys: The tuple_keys of this WriteRequestWrites. :type tuple_keys: list[TupleKey] """ - if self.local_vars_configuration.client_side_validation and tuple_keys is None: + if ( + self.local_vars_configuration.get("client_side_validation") == True + and tuple_keys is None + ): raise ValueError("Invalid value for `tuple_keys`, must not be `None`") self._tuple_keys = tuple_keys diff --git a/openfga_sdk/oauth2.py b/openfga_sdk/oauth2.py index dffe7174..2f5e9dfd 100644 --- a/openfga_sdk/oauth2.py +++ b/openfga_sdk/oauth2.py @@ -12,70 +12,83 @@ import asyncio import json -import math -import random -import sys +from dataclasses import dataclass from datetime import datetime, timedelta import urllib3 -from openfga_sdk.configuration import Configuration -from openfga_sdk.credentials import Credentials +from openfga_sdk.common.headers import HttpHeader +from openfga_sdk.common.math import Math +from openfga_sdk.common.rest import RestClientProtocol from openfga_sdk.exceptions import AuthenticationError +from openfga_sdk.protocols import ( + ConfigurationProtocol, + HttpHeaderProtocol, + OAuth2ClientProtocol, +) from openfga_sdk.telemetry.attributes import TelemetryAttributes from openfga_sdk.telemetry.telemetry import Telemetry -def jitter(loop_count, min_wait_in_ms): - """ - Generate a random jitter value for exponential backoff - """ - minimum = math.ceil(2**loop_count * min_wait_in_ms) - maximum = math.ceil(2 ** (loop_count + 1) * min_wait_in_ms) - jitter = random.randrange(minimum, maximum) / 1000 +@dataclass +class OAuth2Client(OAuth2ClientProtocol): + configuration: ConfigurationProtocol | None = None + access_token: str | None = None + access_expiry_time: datetime | None = None - # If running in pytest, set jitter to 0 to speed up tests - if "pytest" in sys.modules: - jitter = 0 - - return jitter - - -class OAuth2Client: - def __init__(self, credentials: Credentials, configuration=None): - self._credentials = credentials - self._access_token = None - self._access_expiry_time = None - self._telemetry = Telemetry() + async def get_authentication_header( + self, + client: RestClientProtocol, + ) -> HttpHeaderProtocol: + """ + Get the authentication header for the client + """ - if configuration is None: - configuration = Configuration.get_default_copy() + if not self.token_valid(): + await self.obtain_token(client) - self.configuration = configuration + return HttpHeader(name="Authorization", value=f"Bearer {self.access_token}") - def _token_valid(self): + def token_valid(self) -> bool: """ Return whether token is valid """ - if self._access_token is None or self._access_expiry_time is None: + + if self.access_token is None or self.access_expiry_time is None: return False - if self._access_expiry_time < datetime.now(): + + if self.access_expiry_time < datetime.now(): return False + return True - async def _obtain_token(self, client): + async def obtain_token( + self, + client: RestClientProtocol, + ) -> str: """ - Perform OAuth2 and obtain token + Obtain a token from the OAuth2 server """ - configuration = self._credentials.configuration - token_url = f"https://{configuration.api_issuer}/oauth/token" + if ( + self.configuration is None + or self.configuration.credentials is None + or self.configuration.credentials.configuration is None + ): + raise AuthenticationError("Credentials are not configured") + + if self.configuration.credentials.method != "client_credentials": + raise AuthenticationError( + f"Credentials method `{self.configuration.credentials.method}` is not supported" + ) + + token_url = f"https://{self.configuration.credentials.configuration.api_issuer}/oauth/token" post_params = { - "client_id": configuration.client_id, - "client_secret": configuration.client_secret, - "audience": configuration.api_audience, + "client_id": self.configuration.credentials.configuration.client_id, + "client_secret": self.configuration.credentials.configuration.client_secret, + "audience": self.configuration.credentials.configuration.api_audience, "grant_type": "client_credentials", } @@ -87,68 +100,55 @@ async def _obtain_token(self, client): } ) - max_retry = ( - self.configuration.retry_params.max_retry - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.max_retry is not None - ) - else 0 - ) - - min_wait_in_ms = ( - self.configuration.retry_params.min_wait_in_ms - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.min_wait_in_ms is not None - ) - else 0 - ) - - for attempt in range(max_retry + 1): - raw_response = await client.request( + for attempt in range(self.configuration.retry_params.max_retries + 1): + response = await client.request( method="POST", url=token_url, headers=headers, - query_params=None, - body=None, - _preload_content=True, - _request_timeout=None, post_params=post_params, ) - if 500 <= raw_response.status <= 599 or raw_response.status == 429: - if attempt < max_retry and raw_response.status != 501: - await asyncio.sleep(jitter(attempt, min_wait_in_ms)) + if 500 <= response.status <= 599 or response.status == 429: + if ( + attempt < self.configuration.retry_params.max_retries + and response.status != 501 + ): + await asyncio.sleep( + Math.jitter( + attempt, self.configuration.retry_params.min_wait_in_ms + ) + ) + continue - if 200 <= raw_response.status <= 299: + if type(response.data) in [bytes, str] and 200 <= response.status <= 299: try: - api_response = json.loads(raw_response.data) + api_response = json.loads(response.data) except Exception: - raise AuthenticationError(http_resp=raw_response) - - if api_response.get("expires_in") and api_response.get("access_token"): - self._access_expiry_time = datetime.now() + timedelta( - seconds=int(api_response.get("expires_in")) - ) - self._access_token = api_response.get("access_token") - self._telemetry.metrics.credentialsRequest( + raise AuthenticationError(http_resp=response) + + expires_in = api_response.get("expires_in") + access_token = api_response.get("access_token") + + if ( + type(expires_in) is int + and type(access_token) is str + and self.configuration.credentials.configuration.client_id + is not None + ): + Telemetry().metrics.credentialsRequest( attributes={ - TelemetryAttributes.fga_client_request_client_id: configuration.client_id + TelemetryAttributes.fga_client_request_client_id: self.configuration.credentials.configuration.client_id }, configuration=self.configuration.telemetry, ) - break - raise AuthenticationError(http_resp=raw_response) + self.access_expiry_time = datetime.now() + timedelta( + seconds=int(expires_in) + ) - async def get_authentication_header(self, client): - """ - If configured, return the header for authentication - """ - # check to see token is valid - if not self._token_valid(): - # In this case, the token is not valid, we need to get the refresh the token - await self._obtain_token(client) - return {"Authorization": f"Bearer {self._access_token}"} + self.access_token = access_token + + return self.access_token + + raise AuthenticationError(http_resp=response) diff --git a/openfga_sdk/protocols.py b/openfga_sdk/protocols.py new file mode 100644 index 00000000..f2044930 --- /dev/null +++ b/openfga_sdk/protocols.py @@ -0,0 +1,511 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 enum import Enum +import logging +import ssl + +from multiprocessing.pool import ThreadPool +from typing import Any, Protocol, TypeVar, runtime_checkable +from dataclasses import dataclass, fields, is_dataclass + +T = TypeVar("T", bound="MergeableDataclassMixin") + + +@dataclass +class MergeableDataclassMixin: + """Mixin to add merging functionality to dataclasses.""" + + def merge_with(self: T, other: Any) -> T: + """Merge this dataclass with another dataclass, preserving nested structures.""" + if not is_dataclass(other): + raise TypeError(f"Cannot merge with non-dataclass object: {type(other)}") + return merge_dataclasses(self, other) + + def __or__(self: T, other: Any) -> T: + """Enable the `|` operator for merging.""" + return self.merge_with(other) + + +def merge_dataclasses(instance1, instance2): + if not is_dataclass(instance1) or not is_dataclass(instance2): + raise ValueError("merge_dataclasses can only merge dataclass instances") + + merged_data = {} + for field in fields(instance1): + field_name = field.name + value1 = getattr(instance1, field_name, None) + value2 = getattr(instance2, field_name, None) + + if is_dataclass(value1) and is_dataclass(value2): + merged_data[field_name] = merge_dataclasses(value1, value2) + else: + merged_data[field_name] = value2 if value2 is not None else value1 + + return instance1.__class__(**merged_data) + + +@runtime_checkable +class FgaModelProtocol(Protocol): + pass + + +@runtime_checkable +class CredentialConfigurationProtocol(Protocol): + pass + + +@runtime_checkable +class CredentialsProtocol(Protocol): + method: str | None = None + configuration: CredentialConfigurationProtocol | None = None + + def validate(self) -> None: ... + + +@runtime_checkable +class RetryParamsProtocol(Protocol): + DEFAULT_MAX_RETRIES = 3 + DEFAULT_MIN_WAIT = 100 + pass + + +@runtime_checkable +class TelemetryConfigurationProtocol(Protocol): + pass + + +@runtime_checkable +class ConfigurationDataProtocol(Protocol): + pass + + +@runtime_checkable +class ConfigurationProtocol(Protocol): + @classmethod + def get_default_copy(cls) -> ConfigurationDataProtocol: ... + + @property + def api_key_prefix(self) -> dict[str, bool | int | str]: ... + + @api_key_prefix.setter + def api_key_prefix(self, value: dict[str, bool | int | str]): ... + + @property + def api_key(self) -> dict[str, bool | int | str]: ... + + @api_key.setter + def api_key(self, value: dict[str, bool | int | str]): ... + + @property + def api_url(self) -> str: ... + + @api_url.setter + def api_url(self, value: str) -> None: ... + + @property + def authorization_model_id(self) -> str | None: ... + + @authorization_model_id.setter + def authorization_model_id(self, value: str | None) -> None: ... + + @property + def assert_hostname(self) -> str | None: ... + + @assert_hostname.setter + def assert_hostname(self, value: str | None): ... + + @property + def cert_file(self) -> str | None: ... + + @cert_file.setter + def cert_file(self, value: str | None): ... + + @property + def client_side_validation(self) -> bool: ... + + @client_side_validation.setter + def client_side_validation(self, value: bool): ... + + @property + def connection_pool_size(self) -> int: ... + + @connection_pool_size.setter + def connection_pool_size(self, value: int): ... + + @property + def connection_pool_size_max(self) -> int: ... + + @connection_pool_size_max.setter + def connection_pool_size_max(self, value: int): ... + + @property + def credentials(self) -> CredentialsProtocol | None: ... + + @credentials.setter + def credentials(self, value: CredentialsProtocol | None): ... + + @property + def debug(self) -> bool: ... + + @debug.setter + def debug(self, value: bool) -> None: ... + + @property + def discard_unknown_keys(self) -> bool: ... + + @discard_unknown_keys.setter + def discard_unknown_keys(self, value: bool) -> None: ... + + @property + def key_file(self) -> str | None: ... + + @key_file.setter + def key_file(self, value: str | None): ... + + @property + def logger_file(self) -> str | None: ... + + @logger_file.setter + def logger_file(self, value: str | None) -> None: ... + + @property + def logger_format(self) -> str: ... + + @logger_format.setter + def logger_format(self, value: str) -> None: ... + + @property + def logger(self) -> dict[str, logging.Logger]: ... + + @logger.setter + def logger(self, value: dict[str, logging.Logger]) -> None: ... + + @property + def password(self) -> str | None: ... + + @password.setter + def password(self, value: str | None) -> None: ... + + @property + def proxy_headers(self) -> dict[str, str] | None: ... + + @proxy_headers.setter + def proxy_headers(self, value: dict[str, str] | None) -> None: ... + + @property + def proxy(self) -> dict[str, str] | None: ... + + @proxy.setter + def proxy(self, value: dict[str, str] | None) -> None: ... + + @property + def retry_params(self) -> RetryParamsProtocol: ... + + @retry_params.setter + def retry_params(self, value: RetryParamsProtocol) -> None: ... + + @property + def socket_options(self) -> list[tuple[int, int, int | bytes]] | None: ... + + @socket_options.setter + def socket_options( + self, value: list[tuple[int, int, int | bytes]] | None + ) -> None: ... + + @property + def ssl_ca_cert(self) -> str | None: ... + + @ssl_ca_cert.setter + def ssl_ca_cert(self, value: str | None) -> None: ... + + @property + def store_id(self) -> str | None: ... + + @store_id.setter + def store_id(self, value: str | None) -> None: ... + + @property + def telemetry(self) -> TelemetryConfigurationProtocol: ... + + @telemetry.setter + def telemetry(self, value: TelemetryConfigurationProtocol) -> None: ... + + @property + def timeout(self) -> int: ... + + @timeout.setter + def timeout(self, value: int) -> None: ... + + @property + def username(self) -> str | None: ... + + @username.setter + def username(self, value: str | None) -> None: ... + + @property + def verify_ssl(self) -> bool: ... + + @verify_ssl.setter + def verify_ssl(self, value: bool) -> None: ... + + +@runtime_checkable +class HttpHeaderProtocol(Protocol): + pass + + +@runtime_checkable +class HttpHeadersProtocol(Protocol): + def add_header(self, name: str, value: str, overwrite: bool = True) -> None: ... + + def get_header(self, name: str) -> list: ... + + def remove_header(self, name: str) -> None: ... + + def items(self): ... + + +@runtime_checkable +class HttpCookieProtocol(Protocol): + pass + + +@runtime_checkable +class HttpCookiesProtocol(Protocol): + def add_cookie(self, name: str, value: str, **kwargs) -> None: ... + + def get_cookie(self, name: str) -> HttpCookieProtocol | None: ... + + def remove_cookie(self, name: str) -> None: ... + + def items(self): ... + + +@runtime_checkable +class RestClientResponseProtocol(Protocol): + response: ( + type["aiohttp.ClientResponse"] | type["urllib3.BaseHTTPResponse"] | None + ) = None + data: bytes | None = None + status: int | None = None + reason: str | None = None + + @property + def headers(self) -> dict[str, str]: ... + + def header(self, name: str, default: str | None = None) -> str | None: + return self.headers.get(name, default) + + +@runtime_checkable +@dataclass +class RestClientRequestProtocol(Protocol): + method: str + url: str + body: str | None = None + fields: dict[str, str] | None = None + headers: dict[str, str] | None = None + multipart: bool | None = None + + +@runtime_checkable +class RestClientRequestQueryParameterProtocol(Protocol): + pass + + +@runtime_checkable +class RestClientRequestQueryParametersProtocol(Protocol): + def add_parameter(self, name: str, value: str, **kwargs) -> None: ... + + def get_parameter( + self, name: str + ) -> RestClientRequestQueryParameterProtocol | None: ... + + def remove_parameter(self, name: str) -> None: ... + + def merge(self, other: "RestClientRequestQueryParametersProtocol") -> None: ... + + +@runtime_checkable +class RestClientRequestFieldParameterProtocol(Protocol): + pass + + +@runtime_checkable +class RestClientRequestFieldParametersProtocol(Protocol): + def add_parameter(self, name: str, value: str, **kwargs) -> None: ... + + def get_parameter( + self, name: str + ) -> RestClientRequestFieldParameterProtocol | None: ... + + def remove_parameter(self, name: str) -> None: ... + + def merge(self, other: "RestClientRequestFieldParametersProtocol") -> None: ... + + +@runtime_checkable +class RestClientRequestBodyProtocol(Protocol): + pass + + +@runtime_checkable +@dataclass +class RestClientProtocol(Protocol): + _ssl_context: ssl.SSLContext | None = None + _pool_size_max: int | None = None + _pool_size: int | None = None + _debug: bool | None = None + + @property + def ssl_context(self) -> ssl.SSLContext: ... + + @property + def debug(self) -> bool: + return self._debug or self._configuration.debug + + @debug.setter + def debug(self, value: bool | None) -> None: + self._debug = value + + @staticmethod + def build_request( + method: Enum, + url: str, + body: RestClientRequestBodyProtocol | None = None, + headers: HttpHeadersProtocol | None = None, + query: RestClientRequestQueryParametersProtocol | None = None, + fields: RestClientRequestFieldParametersProtocol | None = None, + ) -> RestClientRequestProtocol: ... + + +@runtime_checkable +class OAuth2ClientProtocol(Protocol): + def get_authentication_header( + self, + client: RestClientProtocol, + ) -> HttpHeaderProtocol: ... + + +@runtime_checkable +class TelemetryAttributeProtocol(Protocol): + # Duck type for TelemetryAttribute, as NamedTuples cannot inherit from Protocol + + @property + def name(self) -> str: ... + + @property + def format(self) -> str: ... + + +@runtime_checkable +class StoreRequestOptionsProtocol(Protocol): + pass + + +@runtime_checkable +class ApiResponseProtocol(Protocol): + retries: int = 0 + exception: Exception | None = None + request: RestClientRequestProtocol | None = None + response: RestClientResponseProtocol | None = None + deserialized: FgaModelProtocol | None = None + + @property + def status(self) -> int | None: ... + + +@runtime_checkable +class ApiClientResponseProtocol(Protocol): + retries: int = 0 + exception: Exception | None = None + request: RestClientRequestProtocol | None = None + response: RestClientResponseProtocol | None = None + + @property + def status(self) -> int | None: ... + + +@runtime_checkable +class ApiClientProtocol(Protocol): + @property + def pool(self) -> ThreadPool: ... + + def request( + self, + path: str, + method: Enum, + body: RestClientRequestBodyProtocol | None = None, + headers: HttpHeadersProtocol | None = None, + query: RestClientRequestQueryParametersProtocol | None = None, + fields: RestClientRequestFieldParametersProtocol | None = None, + cookies: HttpCookiesProtocol | None = None, + timeout: int | None = None, + response_types: list[tuple[int, str]] | None = None, + attributes: ( + dict[TelemetryAttributeProtocol, str | bool | int | float] | None + ) = None, + streaming: bool = False, + ) -> ApiClientResponseProtocol: ... + + +@runtime_checkable +class OpenFgaApiProtocol(Protocol): + pass + + +@runtime_checkable +class OpenFgaClientProtocol(Protocol): + pass + + +@runtime_checkable +class FactoryProtocol(Protocol): + @property + def auth_client(self) -> OAuth2ClientProtocol: ... + + @property + def api(self) -> OpenFgaApiProtocol: ... + + @property + def api_client(self) -> ApiClientProtocol: ... + + @property + def rest_client(self) -> RestClientProtocol: ... + + +@runtime_checkable +class FactoryConsumerProtocol(Protocol): + @property + def factory(self) -> FactoryProtocol: ... + + @factory.setter + def factory(self, value: FactoryProtocol) -> None: ... + + @property + def _factory_async(self) -> FactoryProtocol: ... + + @property + def _factory_sync(self) -> FactoryProtocol: ... + + @property + def auth_client(self) -> OAuth2ClientProtocol: ... + + @property + def api(self) -> OpenFgaApiProtocol: ... + + @property + def api_client(self) -> ApiClientProtocol: ... + + @property + def rest_client(self) -> RestClientProtocol: ... diff --git a/openfga_sdk/rest.py b/openfga_sdk/rest.py index b3e81089..ff38c8ff 100644 --- a/openfga_sdk/rest.py +++ b/openfga_sdk/rest.py @@ -10,166 +10,91 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ -import io import json import logging -import re +import socket import ssl -import urllib +from dataclasses import asdict, dataclass, fields, is_dataclass from typing import Any import aiohttp -from openfga_sdk.exceptions import ( - ApiException, - ApiValueError, - ForbiddenException, - NotFoundException, - RateLimitExceededError, - ServiceException, - UnauthorizedException, - ValidationException, +from openfga_sdk.common.rest import RestClientBase, RestClientResponseProtocol +from openfga_sdk.protocols import ( + ConfigurationProtocol, + MergeableDataclassMixin, + RestClientRequestProtocol, ) logger = logging.getLogger(__name__) -class RESTResponse(io.IOBase): - """ - Represents an HTTP response object in the asynchronous client. - """ +@dataclass +class RestClientResponse(MergeableDataclassMixin, RestClientResponseProtocol): + response: type["aiohttp.ClientResponse"] | None = None + data: bytes | None = None + status: int | None = None + reason: str | None = None + + @property + def headers(self) -> dict[str, str]: + return dict(self.response.headers) - _response: aiohttp.ClientResponse - _data: bytes - _status: int - _reason: str | None + +class RestClient(RestClientBase): + _connector: aiohttp.TCPConnector | None = None + _pool_manager: aiohttp.ClientSession | None = None def __init__( self, - response: aiohttp.ClientResponse, - data: bytes, - status: int | None = None, - reason: str | None = None, + configuration: ConfigurationProtocol, + pool_size: int | None = None, + pool_size_max: int | None = None, + timeout: int | None = None, + debug: bool | None = None, ) -> None: - """ - Initializes a RESTResponse with an aiohttp.ClientResponse and corresponding data. - - :param resp: The aiohttp.ClientResponse object. - :param data: The raw byte data read from the response. - """ - self._response = response - self._data = data - self._status = status or response.status - self._reason = reason or response.reason - - @property - def response(self) -> aiohttp.ClientResponse: - """ - Returns the underlying aiohttp.ClientResponse object. - """ - return self._response - - @response.setter - def response(self, value: aiohttp.ClientResponse) -> None: - """ - Sets the underlying aiohttp.ClientResponse object. - """ - self._response = value + self._configuration = configuration + self._pool_size = pool_size + self._pool_size_max = pool_size_max + self._timeout = timeout + self._debug = debug @property - def data(self) -> bytes: + def connector(self) -> aiohttp.TCPConnector: """ - Returns the raw byte data of the response. + Returns a configured aiohttp.TCPConnector. """ - return self._data - - @data.setter - def data(self, value: bytes) -> None: - """ - Sets the raw byte data of the response. - """ - self._data = value + if self._connector is None: + pool_size_max = ( + self._pool_size_max or self._configuration.connection_pool_size_max + ) - @property - def status(self) -> int: - """ - Returns the HTTP status code of the response. - """ - return self._status + self._connector = aiohttp.TCPConnector( + limit=pool_size_max, + ssl=self.ssl_context, + ) - @status.setter - def status(self, value: int) -> None: - """ - Sets the HTTP status code of the response. - """ - self._status = value + return self._connector @property - def reason(self) -> str | None: - """ - Returns the HTTP reason phrase of the response. - """ - return self._reason - - @reason.setter - def reason(self, value: str | None) -> None: + def pool_manager(self) -> aiohttp.ClientSession: """ - Sets the HTTP reason phrase of the response. - """ - self._reason = value - - def getheaders(self) -> dict[str, str]: - """ - Returns the response headers. - """ - return dict(self.response.headers) - - def getheader(self, name: str, default: str | None = None) -> str | None: - """ - Returns a specific header value by name. - - :param name: The name of the header. - :param default: The default value if header is not found. - :return: The header value, or default if not present. + Returns a configured aiohttp.ClientSession. """ - return self.response.headers.get(name, default) - - -class RESTClientObject: - """ - A client object that manages HTTP interactions. - """ - - def __init__( - self, configuration: Any, pools_size: int = 4, maxsize: int | None = None - ) -> None: - """ - Creates a new RESTClientObject. - - :param configuration: A configuration object with necessary parameters. - :param pools_size: The size of the connection pool (unused, present for compatibility). - :param maxsize: Maximum number of connections to allow. - """ - if maxsize is None: - maxsize = configuration.connection_pool_maxsize - - ssl_context = ssl.create_default_context(cafile=configuration.ssl_ca_cert) - if configuration.cert_file: - ssl_context.load_cert_chain( - configuration.cert_file, keyfile=configuration.key_file + if self._pool_manager is None: + self._pool_manager = aiohttp.ClientSession( + connector=self.connector, + trust_env=True, + timeout=aiohttp.ClientTimeout(total=self._timeout), ) - if not configuration.verify_ssl: - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE + return self._pool_manager - connector = aiohttp.TCPConnector(limit=maxsize, ssl=ssl_context) - self.proxy = configuration.proxy - self.proxy_headers = configuration.proxy_headers - self._timeout_millisec = configuration.timeout_millisec - self.pool_manager = aiohttp.ClientSession(connector=connector, trust_env=True) + @pool_manager.setter + def pool_manager(self, value: aiohttp.ClientSession) -> None: + self._pool_manager = value async def close(self) -> None: """ @@ -177,159 +102,10 @@ async def close(self) -> None: """ await self.pool_manager.close() - async def build_request( - self, - method: str, - url: str, - query_params: dict | None = None, - headers: dict | None = None, - body: Any | None = None, - post_params: list[tuple[str, Any]] | None = None, - _preload_content: bool = True, - _request_timeout: float | None = None, - ) -> dict: - """ - Builds a dictionary of request arguments suitable for aiohttp. - - :param method: The HTTP method. - :param url: The URL endpoint. - :param query_params: Optional query parameters. - :param headers: Optional request headers. - :param body: The request body, if any. - :param post_params: Form or multipart parameters, if any. - :param _preload_content: If True, content will be loaded immediately (not used here). - :param _request_timeout: Request timeout in seconds. - :return: A dictionary of request arguments. - """ - method = method.upper() - assert method in ["GET", "HEAD", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"] - - if post_params and body: - raise ApiValueError( - "body parameter cannot be used with post_params parameter." - ) - - post_params = post_params or [] - headers = headers or {} - timeout = _request_timeout or (self._timeout_millisec / 1000) - - if "Content-Type" not in headers: - headers["Content-Type"] = "application/json" - - args = { - "method": method, - "url": url, - "timeout": timeout, - "headers": headers, - } - - if self.proxy: - args["proxy"] = self.proxy - if self.proxy_headers: - args["proxy_headers"] = self.proxy_headers - - if query_params: - encoded_qs = urllib.parse.urlencode(query_params) - args["url"] = f"{url}?{encoded_qs}" - - if method in ["POST", "PUT", "PATCH", "OPTIONS", "DELETE"]: - if re.search("json", headers["Content-Type"], re.IGNORECASE): - if body is not None: - body = json.dumps(body) - args["data"] = body - elif headers["Content-Type"] == "application/x-www-form-urlencoded": - args["data"] = aiohttp.FormData(post_params) - elif headers["Content-Type"] == "multipart/form-data": - del headers["Content-Type"] - data = aiohttp.FormData() - for param in post_params: - k, v = param - if isinstance(v, tuple) and len(v) == 3: - data.add_field(k, value=v[1], filename=v[0], content_type=v[2]) - else: - data.add_field(k, v) - args["data"] = data - elif isinstance(body, bytes): - args["data"] = body - else: - msg = ( - "Cannot prepare a request message for provided arguments. " - "Please check that your arguments match declared content type." - ) - raise ApiException(status=0, reason=msg) - - return args - - async def handle_response_exception( - self, response: RESTResponse | aiohttp.ClientResponse - ) -> None: - """ - Raises exceptions if response status indicates an error. - - :param response: The response to check. - :raises ValidationException: If status is 400. - :raises UnauthorizedException: If status is 401. - :raises ForbiddenException: If status is 403. - :raises NotFoundException: If status is 404. - :raises RateLimitExceededError: If status is 429. - :raises ServiceException: If status is 5xx. - :raises ApiException: For other non-2xx statuses. - """ - if 200 <= response.status <= 299: - return - - match response.status: - case 400: - raise ValidationException(http_resp=response) - case 401: - raise UnauthorizedException(http_resp=response) - case 403: - raise ForbiddenException(http_resp=response) - case 404: - raise NotFoundException(http_resp=response) - case 429: - raise RateLimitExceededError(http_resp=response) - case _ if 500 <= response.status <= 599: - raise ServiceException(http_resp=response) - case _: - raise ApiException(http_resp=response) - - def _accumulate_json_lines( - self, leftover: bytes, data: bytes, buffer: bytearray - ) -> tuple[bytes, list[Any]]: - """ - Processes a chunk of data and leftover bytes. Splits on newlines, decodes valid JSON, - and returns leftover bytes and a list of decoded JSON objects. - - :param leftover: Any leftover bytes from previous chunks. - :param data: The new chunk of data. - :param buffer: The main bytearray buffer for all data. - :return: Updated leftover bytes and a list of decoded JSON objects. - """ - objects: list[Any] = [] - leftover += data - lines = leftover.split( - b"\n" - ) # Objects are received as one-per-line, so split at newlines - leftover = lines.pop() - buffer.extend(data) - for line in lines: - try: - decoded = json.loads(line.decode("utf-8")) - objects.append(decoded) - except json.JSONDecodeError as e: - logger.warning("Skipping invalid JSON segment: %s", e) - return leftover, objects - async def stream( self, - method: str, - url: str, - query_params: dict | None = None, - headers: dict | None = None, - body: Any | None = None, - post_params: list[tuple[str, Any]] | None = None, - _request_timeout: float | None = None, + request: RestClientRequestProtocol, + timeout: float | None = None, ): """ Streams JSON objects from a specified endpoint, handling partial chunks @@ -341,22 +117,10 @@ async def stream( :param headers: Optional headers to include in the request. :param body: Optional body for the request. :param post_params: Optional form/multipart parameters. - :param _request_timeout: An optional request timeout in seconds. + :param timeout: An optional request timeout in seconds. :yields: Parsed JSON objects as Python data structures. """ - # Build our request payload - args = await self.build_request( - method, - url, - query_params=query_params, - headers=headers, - body=body, - post_params=post_params, - _preload_content=False, - _request_timeout=_request_timeout, - ) - # Initialize buffers for data chunks buffer = bytearray() leftover = b"" @@ -364,7 +128,18 @@ async def stream( try: # Send request, collect response handler - async with self.pool_manager.request(**args) as resp: + async with self.pool_manager.request( + method=request.method, + url=request.url, + data=request.body, + headers=request.headers, + params=request.fields, + timeout=aiohttp.ClientTimeout( + (timeout / 1000) if timeout is not None else self._timeout + ), + proxy=self._configuration.proxy, + proxy_headers=self._configuration.proxy_headers, + ) as resp: response = resp try: # Iterate over streamed/chunked response data @@ -399,12 +174,17 @@ async def stream( except json.JSONDecodeError: logger.debug("Incomplete leftover data at end of stream.") - # Decode the complete/buffered data for logging purposes - if isinstance(response, aiohttp.ClientResponse): - logger.debug("response body: %s", buffer.decode("utf-8")) + wrapped_response = RestClientResponse( + data=buffer, + status=response.status, + reason=response.reason, + response=response, + ) + + self._log_response(wrapped_response) # Handle any HTTP errors that may have occurred - await self.handle_response_exception(response) + await self._handle_response_exception(wrapped_response) # Release the response object (required!) response.release() @@ -414,57 +194,47 @@ async def stream( async def request( self, - method: str, - url: str, - query_params: dict | None = None, - headers: dict | None = None, - body: Any | None = None, - post_params: list[tuple[str, Any]] | None = None, - _preload_content: bool = True, - _request_timeout: float | None = None, - ) -> RESTResponse | aiohttp.ClientResponse: + request: RestClientRequestProtocol, + timeout: int | None = None, + ) -> RestClientResponseProtocol: """ - Executes a request and returns the response object. + Configure and send a request. :param method: The HTTP method. :param url: The endpoint URL. :param query_params: Query parameters to be appended to the URL. :param headers: Optional request headers. :param body: A request body for JSON or other content types. - :param post_params: Form/multipart parameters for the request. - :param _preload_content: If True, the response body is read immediately. - :param _request_timeout: An optional request timeout in seconds. - :return: A RESTResponse if _preload_content is True, otherwise an aiohttp.ClientResponse. - """ - - # Build our request payload - args = await self.build_request( - method, - url, - query_params=query_params, - headers=headers, - body=body, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, + :param post_params: form/multipart parameters for the request. + :param timeout: An optional request timeout in seconds. + """ + + # Send request and collect response + response: aiohttp.ClientResponse = await self.pool_manager.request( + method=request.method, + url=request.url, + data=request.body, + headers=request.headers, + params=request.fields, + timeout=aiohttp.ClientTimeout( + (timeout / 1000) if timeout is not None else self._timeout + ), + proxy=self._configuration.proxy, + proxy_headers=self._configuration.proxy_headers, ) - # Send request, collect response handler - wrapped_response: RESTResponse | None = None - raw_response: aiohttp.ClientResponse = await self.pool_manager.request(**args) - - # If we want to preload the response, read it - if _preload_content: - # Collect response data - data = await raw_response.read() + # Collect response data + data = await response.read() - # Transform response JSON data into RESTResponse object - wrapped_response = RESTResponse(raw_response, data) - - # Log the response body - logger.debug("response body: %s", data.decode("utf-8")) + # Transform response JSON data into RESTResponse object + wrapped_response = RestClientResponse( + data=data.decode("utf-8"), + status=response.status, + reason=response.reason, + response=response, + ) - # Handle any errors that may have occurred - await self.handle_response_exception(raw_response) + self._log_response(wrapped_response) + self._handle_response_exception(wrapped_response) - return wrapped_response or raw_response + return wrapped_response diff --git a/openfga_sdk/sync/__init__.py b/openfga_sdk/sync/__init__.py index ad7fb682..c0a574eb 100644 --- a/openfga_sdk/sync/__init__.py +++ b/openfga_sdk/sync/__init__.py @@ -11,10 +11,16 @@ """ from openfga_sdk.sync.api_client import ApiClient -from openfga_sdk.sync.client.client import OpenFgaClient - +from openfga_sdk.sync.client import OpenFgaClient +from openfga_sdk.sync.open_fga_api import OpenFgaApi +from openfga_sdk.sync.oauth2 import OAuth2Client +from openfga_sdk.sync.rest import RestClient, RestClientResponse __all__ = [ "ApiClient", + "OAuth2Client", + "OpenFgaApi", "OpenFgaClient", + "RestClient", + "RestClientResponse", ] diff --git a/openfga_sdk/sync/api_client.py b/openfga_sdk/sync/api_client.py index 695fd786..739df406 100644 --- a/openfga_sdk/sync/api_client.py +++ b/openfga_sdk/sync/api_client.py @@ -10,19 +10,24 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ -import atexit +from dataclasses import dataclass import datetime import json -import math -import random import re import time -import urllib - -from multiprocessing.pool import ThreadPool from dateutil.parser import parse # type: ignore[import-untyped] +from openfga_sdk.common.api_client import ApiClientBase, ApiClientResponse +from openfga_sdk.common.cookies import HttpCookies +from openfga_sdk.common.headers import HttpHeaders +from openfga_sdk.common.math import Math +from openfga_sdk.common.rest import ( + RestClientRequestBody, + RestClientRequestFieldParameters, + RestClientRequestMethod, + RestClientRequestQueryParameters, +) import openfga_sdk.models from openfga_sdk.configuration import Configuration @@ -30,367 +35,197 @@ ApiException, ApiValueError, FgaValidationException, - RateLimitExceededError, - ServiceException, ) -from openfga_sdk.sync import oauth2, rest -from openfga_sdk.telemetry import Telemetry +from openfga_sdk.protocols import ( + FactoryProtocol, + HttpCookiesProtocol, + HttpHeadersProtocol, + RestClientRequestBodyProtocol, + RestClientRequestFieldParametersProtocol, + RestClientRequestQueryParametersProtocol, + RetryParamsProtocol, + TelemetryAttributeProtocol, +) +from openfga_sdk.sync import oauth2 +from openfga_sdk.sync.rest import RestClient from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes -DEFAULT_USER_AGENT = "openfga-sdk python/0.9.1" - - -def random_time(loop_count, min_wait_in_ms) -> float: - """ - Helper function to return the time (in s) to wait before retry - """ - minimum = math.ceil(2**loop_count * min_wait_in_ms) - maximum = math.ceil(2 ** (loop_count + 1) * min_wait_in_ms) - - return random.randrange(minimum, maximum) / 1000 - - -class ApiClient: - """Generic API client for OpenAPI client library builds. - - OpenAPI generic API client. This client handles the client- - server communication, and is invariant across implementations. Specifics of - the methods and models for each application are generated from the OpenAPI - templates. - - NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - Do not edit the class manually. - - :param configuration: .Configuration object for this client - :param header_name: a header to pass when making calls to the API. - :param header_value: a header value to pass when making calls to - the API. - :param cookie: a cookie to include in the header when making calls - to the API - :param pool_threads: The number of threads to use for async requests - to the API. More threads means more concurrent API requests. - """ +class ApiClient(ApiClientBase): + @property + def _factory(self) -> FactoryProtocol: + if self.factory is None: + return self._factory_sync - PRIMITIVE_TYPES = (float, bool, bytes, str, int) - NATIVE_TYPES_MAPPING = { - "int": int, - "long": int, - "float": float, - "str": str, - "bool": bool, - "date": datetime.date, - "datetime": datetime.datetime, - "object": object, - } - _pool = None + return self.factory - def __init__( + def request( self, - configuration=None, - header_name=None, - header_value=None, - cookie=None, - pool_threads=1, - ): - if configuration is None: - configuration = Configuration.get_default_copy() - - self.configuration = configuration - self.pool_threads = pool_threads - - self.rest_client = rest.RESTClientObject(configuration) - - self.default_headers = {} - if header_name is not None: - self.default_headers[header_name] = header_value - - self.cookie = cookie - - self.user_agent = DEFAULT_USER_AGENT - - self.client_side_validation = configuration.client_side_validation - self._telemetry = Telemetry() + path: str, + method: RestClientRequestMethod, + body: RestClientRequestBodyProtocol | None = None, + headers: HttpHeadersProtocol | None = None, + query: RestClientRequestQueryParametersProtocol | None = None, + fields: RestClientRequestFieldParametersProtocol | None = None, + cookies: HttpCookiesProtocol | None = None, + timeout: int | None = None, + response_types: list[tuple[int, str]] | None = None, + attributes: ( + dict[TelemetryAttributeProtocol, str | bool | int | float] | None + ) = None, + streaming: bool = False, + ) -> ApiClientResponse: + start = float(time.time()) - def __enter__(self): - return self + # Assemble request URL + url = self.configuration.api_url + path - def __exit__(self, exc_type, exc_value, traceback): - self.close() + # Prepare query parameters + query: RestClientRequestQueryParameters = ( + query or RestClientRequestQueryParameters() + ) - def close(self): - if self._pool: - self._pool.close() - self._pool.join() - self._pool = None + # Prepare POST parameters + fields: RestClientRequestFieldParameters = ( + fields or RestClientRequestFieldParameters() + ) - if hasattr(atexit, "unregister") and callable(atexit.unregister): - atexit.unregister(self.close) + # Prepare request body + body: RestClientRequestBody = body or RestClientRequestBody() - @property - def pool(self) -> ThreadPool: - """Create thread pool on first request - avoids instantiating unused threadpool for blocking clients. - """ - if self._pool is None: - atexit.register(self.close) - self._pool = ThreadPool(self.pool_threads) - return self._pool + # Prepare response type map + response_types: tuple[int, str] = response_types or () - @property - def user_agent(self): - """User agent for this API client""" - return self.default_headers["User-Agent"] + # Prepare telemetry attributes + attributes: dict[TelemetryAttribute, str | bool | int | float] = ( + attributes or {} + ) - @user_agent.setter - def user_agent(self, value): - self.default_headers["User-Agent"] = value + # Prepare cookies + cookies: HttpCookies = cookies or HttpCookies() + cookies.merge(self.cookies) - def set_default_header(self, header_name, header_value): - self.default_headers[header_name] = header_value + # Prepare headers + headers: HttpHeaders = headers or HttpHeaders() + headers.merge(self.headers) - def __call_api( - self, - resource_path, - method, - path_params=None, - query_params=None, - header_params=None, - body=None, - post_params=None, - response_types_map=None, - auth_settings=None, - _return_http_data_only=None, - collection_formats=None, - _preload_content=True, - _request_timeout=None, - _host=None, - _request_auth=None, - _retry_params=None, - _oauth2_client=None, - _telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] - | None = None, - _streaming: bool = False, - ): - self.configuration.is_valid() - config = self.configuration - start = float(time.time()) + # Add cookie headers to the request, if any + if cookies: + headers.add_header("Cookie", cookies.as_header()) - # header parameters - header_params = header_params or {} - header_params.update(self.default_headers) - if self.cookie: - header_params["Cookie"] = self.cookie - if header_params: - header_params = self.sanitize_for_serialization(header_params) - header_params = dict( - self.parameters_to_tuples(header_params, collection_formats) + # Perform credentials exchange if necessary + if ( + self.configuration.credentials is not None + and self.configuration.credentials.method == "client_credentials" + ): + headers.insert_header( + self.auth_client.get_authentication_header(self.rest_client) ) - # path parameters - if path_params: - path_params = self.sanitize_for_serialization(path_params) - path_params = self.parameters_to_tuples(path_params, collection_formats) - for k, v in path_params: - # specified safe chars, encode everything - _k = urllib.parse.quote(str(k), safe=config.safe_chars_for_path_param) - _v = urllib.parse.quote(str(v), safe=config.safe_chars_for_path_param) - resource_path = resource_path.replace("{" + str(k) + "}", _v) - - # query parameters - if query_params: - query_params = self.sanitize_for_serialization(query_params) - query_params = self.parameters_to_tuples(query_params, collection_formats) - - # post parameters - if post_params: - post_params = post_params if post_params else [] - post_params = self.sanitize_for_serialization(post_params) - post_params = self.parameters_to_tuples(post_params, collection_formats) - - # auth setting - self.update_params_for_auth( - header_params, - query_params, - auth_settings, - request_auth=_request_auth, - oauth2_client=_oauth2_client, + # Collect automatic retry conditions + max_retries = ( + self.configuration.retry_params.max_retries + or RetryParamsProtocol.DEFAULT_MAX_RETRIES ) - # body - if body: - body = self.sanitize_for_serialization(body) - - # request url - if _host is None: - if self.configuration.api_url is not None: - url = self.configuration.api_url + resource_path - else: - url = ( - self.configuration.api_scheme - + "://" - + self.configuration.api_host - + resource_path - ) - else: - # use server/host defined in path or operation instead - url = self.configuration.api_scheme + "://" + _host + resource_path - - max_retry = ( - self.configuration.retry_params.max_retry - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.max_retry is not None - ) - else 0 - ) min_wait_in_ms = ( self.configuration.retry_params.min_wait_in_ms - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.min_wait_in_ms is not None - ) - else 0 + or RetryParamsProtocol.DEFAULT_MIN_WAIT ) - if _retry_params is not None: - if _retry_params.max_retry is not None: - max_retry = _retry_params.max_retry - if _retry_params.min_wait_in_ms is not None: - max_retry = _retry_params.min_wait_in_ms - _telemetry_attributes = TelemetryAttributes.fromRequest( + # Build the request object + request = self.rest_client.build_request( + method=method, + url=url, + body=body, + headers=headers, + query=query, + fields=fields, + ) + + # Seed the telemetry attributes with the request data + attributes = TelemetryAttributes.fromRequest( user_agent=self.user_agent, - fga_method=resource_path, + fga_method=path, http_method=method, url=url, resend_count=0, start=start, credentials=self.configuration.credentials, - attributes=_telemetry_attributes, + attributes=attributes, ) - for retry in range(max_retry + 1): - _telemetry_attributes[TelemetryAttributes.http_request_resend_count] = retry + for retry in range(max_retries + 1): + attributes[TelemetryAttributes.http_request_resend_count] = retry + exception: Exception | None = None try: - # perform request and return response - response_data = self.request( - method, - url, - query_params=query_params, - headers=header_params, - post_params=post_params, - body=body, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - _streaming=_streaming, - ) - except (RateLimitExceededError, ServiceException) as e: - if retry < max_retry and e.status != 501: - _telemetry_attributes = TelemetryAttributes.fromResponse( - response=e.body.decode("utf-8"), - credentials=self.configuration.credentials, - attributes=_telemetry_attributes, - ) - - self._telemetry.metrics.request( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) - - time.sleep(random_time(retry, min_wait_in_ms)) - - continue - e.body = e.body.decode("utf-8") - response_type = response_types_map.get(e.status, None) - if response_type is not None: - e.parsed_exception = self.__deserialize( - json.loads(e.body), response_type - ) - e.body = None - raise e - except ApiException as e: - e.body = e.body.decode("utf-8") - response_type = response_types_map.get(e.status, None) - if response_type is not None: - e.parsed_exception = self.__deserialize( - json.loads(e.body), response_type - ) - e.body = None - - _telemetry_attributes = TelemetryAttributes.fromResponse( - response=e, - credentials=self.configuration.credentials, - attributes=_telemetry_attributes, - ) - - self._telemetry.metrics.request( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) - - self._telemetry.metrics.queryDuration( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, + response = ( + self.rest_client.request(request) + if not streaming + else self.rest_client.stream(request) ) + except Exception as e: + exception = e - self._telemetry.metrics.requestDuration( - attributes=_telemetry_attributes, - configuration=self.configuration.telemetry, - ) - raise e - - self.last_response = response_data + response_status: int = ( + exception.status + if exception is not None + else response.status if response is not None else 500 + ) - return_data = response_data + retry_delay: int | None = self._should_retry( + retry=retry, + max_retries=max_retries, + min_wait=min_wait_in_ms, + e=exception, + response_status=response_status, + ) - _telemetry_attributes = TelemetryAttributes.fromResponse( - response=response_data, + # Update telemetry attributes with response data, if available + attributes = TelemetryAttributes.fromResponse( + response=response, credentials=self.configuration.credentials, - attributes=_telemetry_attributes, + attributes=attributes, ) - self._telemetry.metrics.request( - attributes=_telemetry_attributes, + self.telemetry_client.metrics.request( + attributes=attributes, configuration=self.configuration.telemetry, ) - self._telemetry.metrics.queryDuration( - attributes=_telemetry_attributes, + self.telemetry_client.metrics.queryDuration( + attributes=attributes, configuration=self.configuration.telemetry, ) - self._telemetry.metrics.requestDuration( - attributes=_telemetry_attributes, + self.telemetry_client.metrics.requestDuration( + attributes=attributes, configuration=self.configuration.telemetry, ) - if not _preload_content or _streaming: - return return_data - - response_type = response_types_map.get(response_data.status, None) + if retry_delay is not None: + time.sleep(retry_delay) + continue - if response_type not in ["file", "bytes"]: - match = None - content_type = response_data.getheader("content-type") - if content_type is not None: - match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) - encoding = match.group(1) if match else "utf-8" - if response_data.data is not None: - response_data.data = response_data.data.decode(encoding) + response_type: str | None = next( + (value for code, value in response_types if code == response_status), + None, + ) - # deserialize response data + response_wrapper = ApiClientResponse( + status=response_status, + retries=retry, + request=request, + response=response, + ) if response_type: - return_data = self.deserialize(response_data, response_type) - else: - return_data = None + response_wrapper.deserialized = self.deserialize( + response, response_type + ) - if _return_http_data_only: - return return_data - else: - return (return_data, response_data.status, response_data.headers) + return response_wrapper def sanitize_for_serialization(self, obj): """Builds a JSON POST object. @@ -493,159 +328,6 @@ def __deserialize(self, data, klass): return self.__deserialize_model(data, klass) - def call_api( - self, - resource_path, - method, - path_params=None, - query_params=None, - header_params=None, - body=None, - post_params=None, - files=None, - response_types_map=None, - auth_settings=None, - async_req=None, - _return_http_data_only=None, - collection_formats=None, - _preload_content=True, - _request_timeout=None, - _host=None, - _request_auth=None, - _retry_params=None, - _oauth2_client=None, - _telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] - | None = None, - _streaming: bool = False, - ): - """Makes the HTTP request (synchronous) and returns deserialized data. - - To make an async_req request, set the async_req parameter. - - :param resource_path: Path to method endpoint. - :param method: Method to call. - :param path_params: Path parameters in the url. - :param query_params: Query parameters in the url. - :param header_params: Header parameters to be - placed in the request header. - :param body: Request body. - :param post_params dict: Request post form parameters, - for `application/x-www-form-urlencoded`, `multipart/form-data`. - :param auth_settings list: Auth Settings names for the request. - :param response: Response data type. - :param files dict: it will not be used - :param async_req bool: execute request asynchronously - :param _return_http_data_only: response data without head status code - and headers - :param collection_formats: dict of collection formats for path, query, - header, and post parameters. - :param _preload_content: if False, the urllib3.HTTPResponse object will - be returned without reading/decoding response - data. Default is True. - :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_params: If specified, override the default retry parameters - :type _request_token: dict, optional - :return: - If async_req parameter is True, - the request will be called asynchronously. - The method will return the request thread. - If parameter async_req is False or missing, - then the method will return the response directly. - """ - if not async_req: - return self.__call_api( - resource_path, - method, - path_params, - query_params, - header_params, - body, - post_params, - response_types_map, - auth_settings, - _return_http_data_only, - collection_formats, - _preload_content, - _request_timeout, - _host, - _request_auth, - _retry_params, - _oauth2_client, - _telemetry_attributes, - _streaming, - ) - - return self.pool.apply_async( - self.__call_api, - ( - resource_path, - method, - path_params, - query_params, - header_params, - body, - post_params, - response_types_map, - auth_settings, - _return_http_data_only, - collection_formats, - _preload_content, - _request_timeout, - _host, - _request_auth, - _retry_params, - _oauth2_client, - _telemetry_attributes, - _streaming, - ), - ) - - def request( - self, - method, - url, - query_params=None, - headers=None, - post_params=None, - body=None, - _preload_content=True, - _request_timeout=None, - _streaming: bool = False, - ): - if method not in ["GET", "HEAD", "OPTIONS", "POST", "PATCH", "PUT", "DELETE"]: - raise ApiValueError( - "http method must be `GET`, `HEAD`, `OPTIONS`," - " `POST`, `PATCH`, `PUT` or `DELETE`." - ) - - if _streaming: - return self.rest_client.stream( - method, - url, - query_params=query_params, - headers=headers, - post_params=post_params, - body=body, - _request_timeout=_request_timeout, - ) - - return self.rest_client.request( - method, - url, - query_params=query_params, - headers=headers, - post_params=post_params, - body=body, - _preload_content=_preload_content, - _request_timeout=_request_timeout, - ) - def parameters_to_tuples(self, params, collection_formats): """Get parameters as list of tuples, formatting collections. @@ -806,7 +488,7 @@ def __deserialize_date(self, string): except ImportError: return string except ValueError: - raise rest.ApiException( + raise ApiException( status=0, reason=f"Failed to parse `{string}` as date object" ) @@ -823,7 +505,7 @@ def __deserialize_datetime(self, string): except ImportError: return string except ValueError: - raise rest.ApiException( + raise ApiException( status=0, reason=(f"Failed to parse `{string}` as datetime object"), ) diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client.py similarity index 81% rename from openfga_sdk/sync/client/client.py rename to openfga_sdk/sync/client.py index 27c55a06..e11c252a 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client.py @@ -14,7 +14,6 @@ from concurrent.futures import ThreadPoolExecutor -from openfga_sdk.client.configuration import ClientConfiguration from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ( ClientBatchCheckItem, @@ -43,7 +42,8 @@ from openfga_sdk.client.models.write_single_response import ( construct_write_single_response, ) -from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts +from openfga_sdk.common.client import OpenFgaClientBase +from openfga_sdk.common.options import WriteTransactionOptions from openfga_sdk.exceptions import ( AuthenticationError, FgaValidationException, @@ -72,162 +72,22 @@ WriteAuthorizationModelRequest, ) from openfga_sdk.models.write_request import WriteRequest -from openfga_sdk.sync.api_client import ApiClient -from openfga_sdk.sync.open_fga_api import OpenFgaApi -from openfga_sdk.validation import is_well_formed_ulid_string - - -CLIENT_METHOD_HEADER = "X-OpenFGA-Client-Method" -CLIENT_BULK_REQUEST_ID_HEADER = "X-OpenFGA-Client-Bulk-Request-Id" - - -def _chuck_array(array, max_size): - """ - Helper function to chuck array into arrays of max_size - """ - return [ - array[i * max_size : (i + 1) * max_size] - for i in range((len(array) + max_size - 1) // max_size) - ] - - -def set_heading_if_not_set( - options: dict[str, int | str | dict[str, int | str]] | None, - name: str, - value: str, -) -> dict[str, int | str | dict[str, int | str]]: - """ - Set heading to the value if it is not set - """ - _options: dict[str, int | str | dict[str, int | str]] = ( - options if options is not None else {} - ) - - if type(_options.get("headers")) is not dict: - _options["headers"] = {} - - if type(_options["headers"]) is dict: - if type(_options["headers"].get(name)) not in [int, str]: - _options["headers"][name] = value - - return _options - - -def options_to_kwargs( - options: dict[str, int | str | dict[str, int | str]] | None = None, -) -> dict[str, int | str | dict[str, int | str]]: - """ - Return kwargs with continuation_token and page_size - """ - kwargs = {} - if options is not None: - if options.get("page_size"): - kwargs["page_size"] = options["page_size"] - if options.get("continuation_token"): - kwargs["continuation_token"] = options["continuation_token"] - if options.get("headers"): - kwargs["_headers"] = options["headers"] - if options.get("retry_params"): - kwargs["_retry_params"] = options["retry_params"] - return kwargs - - -def options_to_transaction_info( - options: dict[str, int | str | dict[str, int | str]] | None = None, -): - """ - Return the transaction info - """ - if options is not None and options.get("transaction"): - return options["transaction"] - return WriteTransactionOpts() - - -def _check_allowed(response: ClientBatchCheckClientResponse): - """ - Helper function to return whether the response is check is allowed - """ - return response.allowed - - -class OpenFgaClient: - """ - OpenFgaClient is the entry point for invoking calls against the OpenFGA API. - """ - - def __init__(self, configuration: ClientConfiguration) -> None: - self._client_configuration = configuration - self._api_client = ApiClient(configuration) - self._api = OpenFgaApi(self._api_client) - - def __enter__(self): +from openfga_sdk.protocols import FactoryProtocol + + +class OpenFgaClient(OpenFgaClientBase): + def __enter__(self) -> "OpenFgaClient": return self def __exit__(self, exc_type, exc_value, traceback) -> None: self.close() def close(self) -> None: - self._api.close() - - def _get_authorization_model_id( - self, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> str | None: - """ - Return the authorization model ID if specified in the options. - Otherwise, return the authorization model ID stored in the client's configuration - """ - authorization_model_id = self._client_configuration.authorization_model_id - if options is not None and "authorization_model_id" in options: - authorization_model_id = options["authorization_model_id"] - if authorization_model_id is None or authorization_model_id == "": - return None - if is_well_formed_ulid_string(authorization_model_id) is False: - raise FgaValidationException( - f"authorization_model_id ('{authorization_model_id}') is not in a valid ulid format" - ) - return authorization_model_id - - def _get_consistency( - self, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> str | None: - """ - Returns the consistency requested if specified in the options. - Otherwise, returns None. - """ - consistency: int | str | dict[str, int | str] | None = ( - options.get("consistency", None) if options is not None else None - ) - - if type(consistency) is str: - return consistency - - return None + self.api.close() - def set_store_id(self, value): - """ - Update the store ID in the configuration - """ - self._api_client.set_store_id(value) - - def get_store_id(self): - """ - Return the store id (if any) store in the configuration - """ - return self._api_client.get_store_id() - - def set_authorization_model_id(self, value): - """ - Update the authorization model id in the configuration - """ - self._client_configuration.authorization_model_id = value - - def get_authorization_model_id(self): - """ - Return the authorization model id - """ - return self._client_configuration.authorization_model_id + @property + def _factory(self) -> FactoryProtocol: + return self._factory_sync ################# # Stores @@ -246,8 +106,8 @@ def list_stores( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ # convert options to kwargs - kwargs = options_to_kwargs(options) - api_response = self._api.list_stores( + kwargs = self._options_to_kwargs(options) + api_response = self.api.list_stores( **kwargs, ) return api_response @@ -264,8 +124,8 @@ def create_store( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) - api_response = self._api.create_store(body, **kwargs) + kwargs = self._options_to_kwargs(options) + api_response = self.api.create_store(body, **kwargs) return api_response def get_store( @@ -278,8 +138,8 @@ def get_store( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) - api_response = self._api.get_store( + kwargs = self._options_to_kwargs(options) + api_response = self.api.get_store( **kwargs, ) return api_response @@ -294,8 +154,8 @@ def delete_store( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) - api_response = self._api.delete_store( + kwargs = self._options_to_kwargs(options) + api_response = self.api.delete_store( **kwargs, ) return api_response @@ -314,8 +174,8 @@ def read_authorization_models( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) - api_response = self._api.read_authorization_models( + kwargs = self._options_to_kwargs(options) + api_response = self.api.read_authorization_models( **kwargs, ) return api_response @@ -333,8 +193,8 @@ def write_authorization_model( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) - api_response = self._api.write_authorization_model( + kwargs = self._options_to_kwargs(options) + api_response = self.api.write_authorization_model( body, **kwargs, ) @@ -350,9 +210,9 @@ def read_authorization_model( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) authorization_model_id = self._get_authorization_model_id(options) - api_response = self._api.read_authorization_model( + api_response = self.api.read_authorization_model( authorization_model_id, **kwargs, ) @@ -368,8 +228,8 @@ def read_latest_authorization_model( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - options = set_heading_if_not_set( - options, CLIENT_METHOD_HEADER, "ReadLatestAuthorizationModel" + options = self._set_heading_if_not_set( + options, self.CLIENT_METHOD_HEADER, "ReadLatestAuthorizationModel" ) options["page_size"] = 1 api_response = self.read_authorization_models(options) @@ -399,7 +259,7 @@ def read_changes( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) if body.type is not None: kwargs["type"] = body.type @@ -407,7 +267,7 @@ def read_changes( if body.start_time is not None: kwargs["start_time"] = body.start_time - api_response = self._api.read_changes( + api_response = self.api.read_changes( **kwargs, ) return api_response @@ -437,7 +297,7 @@ def read( if options.get("continuation_token"): continuation_token = options.get("continuation_token") options.pop("continuation_token") - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) if body is None or ( body.object is None and body.relation is None and body.user is None @@ -446,7 +306,7 @@ def read( else: tuple_key = body - api_response = self._api.read( + api_response = self.api.read( ReadRequest( tuple_key=tuple_key, page_size=page_size, @@ -482,16 +342,16 @@ def _write_single_batch( def _write_batches( self, tuple_keys: list[ClientTuple], - transaction: WriteTransactionOpts, + transaction: WriteTransactionOptions, is_write: bool, options: dict[str, int | str | dict[str, int | str]] | None = None, ): """ Internal function for write/delete batches """ - chunks = _chuck_array(tuple_keys, transaction.max_per_chunk) + chunks = self._chuck_array(tuple_keys, transaction.max_per_chunk) - write_batches = _chuck_array(chunks, transaction.max_parallel_requests) + write_batches = self._chuck_array(chunks, transaction.max_parallel_requests) batch_write_responses = [] for write_batch in write_batches: response = [ @@ -514,7 +374,7 @@ def _write_with_transaction( """ Write or deletes tuples """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) writes_tuple_keys = None deletes_tuple_keys = None if body.writes_tuple_keys: @@ -522,7 +382,7 @@ def _write_with_transaction( if body.deletes_tuple_keys: deletes_tuple_keys = body.deletes_tuple_keys - self._api.write( + self.api.write( WriteRequest( writes=writes_tuple_keys, deletes=deletes_tuple_keys, @@ -556,14 +416,16 @@ def write( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "Writes") - transaction = options_to_transaction_info(options) + options = self._set_heading_if_not_set( + options, self.CLIENT_METHOD_HEADER, "Writes" + ) + transaction = self._options_to_transaction_info(options) if not transaction.disabled: results = self._write_with_transaction(body, options) return results - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) + options = self._set_heading_if_not_set( + options, self.CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) ) # otherwise, it is not a transaction and it is a batch write requests @@ -592,7 +454,9 @@ def write_tuples( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "WriteTuples") + options = self._set_heading_if_not_set( + options, self.CLIENT_METHOD_HEADER, "WriteTuples" + ) result = self.write(ClientWriteRequest(body, None), options) return result @@ -602,14 +466,16 @@ def delete_tuples( options: dict[str, int | str | dict[str, int | str]] | None = None, ): """ - Convenient method for deleteing tuples + Convenient method for deleting tuples :param body - the list of tuples we want to delete :param header(options) - Custom headers to send alongside the request :param retryParams(options) - Override the retry parameters for this request :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "DeleteTuples") + options = self._set_heading_if_not_set( + options, self.CLIENT_METHOD_HEADER, "DeleteTuples" + ) result = self.write(ClientWriteRequest(None, body), options) return result @@ -631,7 +497,7 @@ def check( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated :param consistency(options) - The type of consistency preferred for the request """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) req_body = CheckRequest( tuple_key=TupleKey( @@ -647,7 +513,7 @@ def check( req_body.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = self._api.check(body=req_body, **kwargs) + api_response = self.api.check(body=req_body, **kwargs) return api_response def _single_client_batch_check( @@ -691,9 +557,11 @@ def client_batch_check( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated :param consistency(options) - The type of consistency preferred for the request """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "BatchCheck") - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) + options = self._set_heading_if_not_set( + options, self.CLIENT_METHOD_HEADER, "BatchCheck" + ) + options = self._set_heading_if_not_set( + options, self.CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) ) max_parallel_requests = 10 @@ -728,8 +596,8 @@ def _single_batch_check( :param authorization_model_id(options) - Overrides the authorization model id in the configuration """ try: - kwargs = options_to_kwargs(options) - api_response = self._api.batch_check(body, **kwargs) + kwargs = self._options_to_kwargs(options) + api_response = self.api.batch_check(body, **kwargs) return api_response # Does this cover all error cases? If one fails with a 4xx/5xx then all should? except Exception as err: @@ -751,8 +619,8 @@ def batch_check( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) + options = self._set_heading_if_not_set( + options, self.CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) ) max_parallel_requests = 10 @@ -849,7 +717,7 @@ def expand( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated :param consistency(options) - The type of consistency preferred for the request """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) req_body = ExpandRequest( tuple_key=ExpandRequestTupleKey( @@ -863,7 +731,7 @@ def expand( req_body.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = self._api.expand(body=req_body, **kwargs) + api_response = self.api.expand(body=req_body, **kwargs) return api_response def list_objects( @@ -881,7 +749,7 @@ def list_objects( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated :param consistency(options) - The type of consistency preferred for the request """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) req_body = ListObjectsRequest( authorization_model_id=self._get_authorization_model_id(options), @@ -895,7 +763,7 @@ def list_objects( req_body.contextual_tuples = ContextualTupleKeys( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - api_response = self._api.list_objects(body=req_body, **kwargs) + api_response = self.api.list_objects(body=req_body, **kwargs) return api_response def streamed_list_objects( @@ -914,7 +782,7 @@ def streamed_list_objects( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated :param consistency(options) - The type of consistency preferred for the request """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) kwargs["_streaming"] = True req_body = ListObjectsRequest( @@ -931,7 +799,7 @@ def streamed_list_objects( tuple_keys=convert_tuple_keys(body.contextual_tuples) ) - for response in self._api.streamed_list_objects(body=req_body, **kwargs): + for response in self.api.streamed_list_objects(body=req_body, **kwargs): if response and "result" in response and "object" in response["result"]: yield StreamedListObjectsResponse(response["result"]["object"]) @@ -952,9 +820,11 @@ def list_relations( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated :param consistency(options) - The type of consistency preferred for the request """ - options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "ListRelations") - options = set_heading_if_not_set( - options, CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) + options = self._set_heading_if_not_set( + options, self.CLIENT_METHOD_HEADER, "ListRelations" + ) + options = self._set_heading_if_not_set( + options, self.CLIENT_BULK_REQUEST_ID_HEADER, str(uuid.uuid4()) ) request_body = [ @@ -969,7 +839,7 @@ def list_relations( ] result = self.client_batch_check(request_body, options) # need to filter with the allowed response - result_iterator = filter(_check_allowed, result) + result_iterator = filter(self._check_allowed, result) result_list = list(result_iterator) return [i.request.relation for i in result_list] @@ -988,7 +858,7 @@ def list_users( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated :param consistency(options) - The type of consistency preferred for the request """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) req_body = ListUsersRequest( authorization_model_id=self._get_authorization_model_id(options), @@ -1003,7 +873,7 @@ def list_users( if body.contextual_tuples: req_body.contextual_tuples = convert_tuple_keys(body.contextual_tuples) - api_response = self._api.list_users(body=req_body, **kwargs) + api_response = self.api.list_users(body=req_body, **kwargs) return api_response @@ -1022,9 +892,9 @@ def read_assertions( :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) authorization_model_id = self._get_authorization_model_id(options) - api_response = self._api.read_assertions(authorization_model_id, **kwargs) + api_response = self.api.read_assertions(authorization_model_id, **kwargs) return api_response def write_assertions( @@ -1041,7 +911,7 @@ def write_assertions( :param retryParams.maxRetry(options) - Override the max number of retries on each API request :param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated """ - kwargs = options_to_kwargs(options) + kwargs = self._options_to_kwargs(options) authorization_model_id = self._get_authorization_model_id(options) def map_to_assertion(client_assertion: ClientAssertion) -> Assertion: @@ -1057,7 +927,7 @@ def map_to_assertion(client_assertion: ClientAssertion) -> Assertion: api_request_body = WriteAssertionsRequest( [map_to_assertion(client_assertion) for client_assertion in body] ) - api_response = self._api.write_assertions( + api_response = self.api.write_assertions( authorization_model_id, api_request_body, **kwargs ) return api_response diff --git a/openfga_sdk/sync/client/__init__.py b/openfga_sdk/sync/client/__init__.py deleted file mode 100644 index 97b78a43..00000000 --- a/openfga_sdk/sync/client/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Python SDK for OpenFGA - -API version: 1.x -Website: https://openfga.dev -Documentation: https://openfga.dev/docs -Support: https://openfga.dev/community -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. -""" diff --git a/openfga_sdk/sync/factory.py b/openfga_sdk/sync/factory.py new file mode 100644 index 00000000..a2a21df9 --- /dev/null +++ b/openfga_sdk/sync/factory.py @@ -0,0 +1,87 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 dataclasses import dataclass +from openfga_sdk.protocols import ( + ApiClientProtocol, + FactoryProtocol, + OAuth2ClientProtocol, + OpenFgaApiProtocol, + OpenFgaClientProtocol, + RestClientProtocol, + ConfigurationProtocol, +) +from openfga_sdk.telemetry.telemetry import Telemetry + + +@dataclass +class Factory(FactoryProtocol): + configuration: ConfigurationProtocol + + _auth_client: OAuth2ClientProtocol | None = None + _client: OpenFgaClientProtocol | None = None + _api: OpenFgaApiProtocol | None = None + _api_client: ApiClientProtocol | None = None + _rest_client: RestClientProtocol | None = None + _telemetry_client: Telemetry | None = None + + @property + def client(self) -> OpenFgaClientProtocol: + if self._client is None: + from openfga_sdk.sync import OpenFgaClient + + self._client = OpenFgaClient(self.configuration) + + return self._client + + @property + def auth_client(self) -> OAuth2ClientProtocol: + if self._auth_client is None: + from openfga_sdk.sync import OAuth2Client + + self._auth_client = OAuth2Client(self.configuration) + + return self._auth_client + + @property + def api(self) -> OpenFgaApiProtocol: + if self._api is None: + from openfga_sdk.sync import OpenFgaApi + + self._api = OpenFgaApi(self.configuration) + + return self._api + + @property + def api_client(self) -> ApiClientProtocol: + if self._api_client is None: + from openfga_sdk.sync import ApiClient + + self._api_client = ApiClient(self.configuration) + + return self._api_client + + @property + def rest_client(self) -> RestClientProtocol: + if self._rest_client is None: + from openfga_sdk.sync import RestClient + + self._rest_client = RestClient(self.configuration) + + return self._rest_client + + @property + def telemetry_client(self) -> Telemetry: + if self._telemetry_client is None: + self._telemetry_client = Telemetry() + + return self._telemetry_client diff --git a/openfga_sdk/sync/oauth2.py b/openfga_sdk/sync/oauth2.py index b61e9bdf..5372fbb3 100644 --- a/openfga_sdk/sync/oauth2.py +++ b/openfga_sdk/sync/oauth2.py @@ -11,71 +11,84 @@ """ import json -import math -import random -import sys import time +from dataclasses import dataclass from datetime import datetime, timedelta import urllib3 -from openfga_sdk.configuration import Configuration -from openfga_sdk.credentials import Credentials +from openfga_sdk.common.headers import HttpHeader +from openfga_sdk.common.math import Math from openfga_sdk.exceptions import AuthenticationError +from openfga_sdk.protocols import ( + ConfigurationProtocol, + HttpHeaderProtocol, + OAuth2ClientProtocol, + RestClientProtocol, +) from openfga_sdk.telemetry.attributes import TelemetryAttributes from openfga_sdk.telemetry.telemetry import Telemetry -def jitter(loop_count, min_wait_in_ms): - """ - Generate a random jitter value for exponential backoff - """ - minimum = math.ceil(2**loop_count * min_wait_in_ms) - maximum = math.ceil(2 ** (loop_count + 1) * min_wait_in_ms) - jitter = random.randrange(minimum, maximum) / 1000 +@dataclass +class OAuth2Client(OAuth2ClientProtocol): + configuration: ConfigurationProtocol | None = None + access_token: str | None = None + access_expiry_time: datetime | None = None - # If running in pytest, set jitter to 0 to speed up tests - if "pytest" in sys.modules: - jitter = 0 - - return jitter - - -class OAuth2Client: - def __init__(self, credentials: Credentials, configuration=None): - self._credentials = credentials - self._access_token = None - self._access_expiry_time = None - self._telemetry = Telemetry() + def get_authentication_header( + self, + client: RestClientProtocol, + ) -> HttpHeaderProtocol: + """ + Get the authentication header for the client + """ - if configuration is None: - configuration = Configuration.get_default_copy() + if not self.token_valid(): + self.obtain_token(client) - self.configuration = configuration + return HttpHeader(name="Authorization", value=f"Bearer {self.access_token}") - def _token_valid(self): + def token_valid(self) -> bool: """ Return whether token is valid """ - if self._access_token is None or self._access_expiry_time is None: + + if self.access_token is None or self.access_expiry_time is None: return False - if self._access_expiry_time < datetime.now(): + + if self.access_expiry_time < datetime.now(): return False + return True - def _obtain_token(self, client): + def obtain_token( + self, + client: RestClientProtocol, + ) -> str: """ - Perform OAuth2 and obtain token + Obtain a token from the OAuth2 server """ - configuration = self._credentials.configuration - token_url = f"https://{configuration.api_issuer}/oauth/token" + if ( + self.configuration is None + or self.configuration.credentials is None + or self.configuration.credentials.configuration is None + ): + raise AuthenticationError("Credentials are not configured") + + if self.configuration.credentials.method != "client_credentials": + raise AuthenticationError( + f"Credentials method `{self.configuration.credentials.method}` is not supported" + ) + + token_url = f"https://{self.configuration.credentials.configuration.api_issuer}/oauth/token" post_params = { - "client_id": configuration.client_id, - "client_secret": configuration.client_secret, - "audience": configuration.api_audience, + "client_id": self.configuration.credentials.configuration.client_id, + "client_secret": self.configuration.credentials.configuration.client_secret, + "audience": self.configuration.credentials.configuration.api_audience, "grant_type": "client_credentials", } @@ -87,68 +100,55 @@ def _obtain_token(self, client): } ) - max_retry = ( - self.configuration.retry_params.max_retry - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.max_retry is not None - ) - else 0 - ) - - min_wait_in_ms = ( - self.configuration.retry_params.min_wait_in_ms - if ( - self.configuration.retry_params is not None - and self.configuration.retry_params.min_wait_in_ms is not None - ) - else 0 - ) - - for attempt in range(max_retry + 1): - raw_response = client.request( + for attempt in range(self.configuration.retry_params.max_retries + 1): + response = client.request( method="POST", url=token_url, headers=headers, - query_params=None, - body=None, - _preload_content=True, - _request_timeout=None, post_params=post_params, ) - if 500 <= raw_response.status <= 599 or raw_response.status == 429: - if attempt < max_retry and raw_response.status != 501: - time.sleep(jitter(attempt, min_wait_in_ms)) + if 500 <= response.status <= 599 or response.status == 429: + if ( + attempt < self.configuration.retry_params.max_retries + and response.status != 501 + ): + time.sleep( + Math.jitter( + attempt, self.configuration.retry_params.min_wait_in_ms + ) + ) + continue - if 200 <= raw_response.status <= 299: + if 200 <= response.status <= 299: try: - api_response = json.loads(raw_response.data) + api_response = json.loads(response.data) except Exception: - raise AuthenticationError(http_resp=raw_response) - - if api_response.get("expires_in") and api_response.get("access_token"): - self._access_expiry_time = datetime.now() + timedelta( - seconds=int(api_response.get("expires_in")) - ) - self._access_token = api_response.get("access_token") - self._telemetry.metrics.credentialsRequest( + raise AuthenticationError(http_resp=response) + + expires_in = api_response.get("expires_in") + access_token = api_response.get("access_token") + + if ( + type(expires_in) is int + and type(access_token) is str + and self.configuration.credentials.configuration.client_id + is not None + ): + Telemetry().metrics.credentialsRequest( attributes={ - TelemetryAttributes.fga_client_request_client_id: configuration.client_id + TelemetryAttributes.fga_client_request_client_id: self.configuration.credentials.configuration.client_id }, configuration=self.configuration.telemetry, ) - break - raise AuthenticationError(http_resp=raw_response) + self.access_expiry_time = datetime.now() + timedelta( + seconds=int(expires_in) + ) - def get_authentication_header(self, client): - """ - If configured, return the header for authentication - """ - # check to see token is valid - if not self._token_valid(): - # In this case, the token is not valid, we need to get the refresh the token - self._obtain_token(client) - return {"Authorization": f"Bearer {self._access_token}"} + self.access_token = access_token + + return self.access_token + + raise AuthenticationError(http_resp=response) diff --git a/openfga_sdk/sync/open_fga_api.py b/openfga_sdk/sync/open_fga_api.py index 401c1dd9..6f7d10ff 100644 --- a/openfga_sdk/sync/open_fga_api.py +++ b/openfga_sdk/sync/open_fga_api.py @@ -10,42 +10,29 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ +from openfga_sdk.common.open_fga_api import OpenFgaApiBase from openfga_sdk.exceptions import ApiValueError, FgaValidationException -from openfga_sdk.sync.api_client import ApiClient -from openfga_sdk.sync.oauth2 import OAuth2Client -from openfga_sdk.telemetry import Telemetry +from openfga_sdk.protocols import FactoryProtocol from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes -class OpenFgaApi: - """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: ApiClient = api_client - - self._oauth2_client = None - if api_client.configuration is not None: - credentials = api_client.configuration.credentials - if credentials is not None and credentials.method == "client_credentials": - self._oauth2_client = OAuth2Client(credentials) - - self._telemetry = Telemetry() - - def __enter__(self): +class OpenFgaApi(OpenFgaApiBase): + def __enter__(self) -> "OpenFgaApi": return self - def __exit__(self): + def __exit__(self, exc_type, exc_value, traceback) -> None: self.close() - def close(self): + def close(self) -> None: self.api_client.close() + @property + def _factory(self) -> FactoryProtocol: + if self.factory is None: + return self._factory_sync + + return self.factory + def batch_check(self, body, **kwargs): """Send a list of `check` operations in a single request @@ -57,14 +44,8 @@ def batch_check(self, body, **kwargs): :type body: BatchCheckRequest :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. @@ -87,14 +68,8 @@ def batch_check_with_http_info(self, body, **kwargs): :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. @@ -114,8 +89,6 @@ def batch_check_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -133,7 +106,7 @@ def batch_check_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -204,7 +177,7 @@ def batch_check_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/batch-check".replace("{store_id}", store_id), "POST", path_params, @@ -217,12 +190,10 @@ def batch_check_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -238,14 +209,8 @@ def check(self, body, **kwargs): :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. @@ -268,14 +233,8 @@ def check_with_http_info(self, body, **kwargs): :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. @@ -295,8 +254,6 @@ def check_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -314,7 +271,7 @@ def check_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -385,7 +342,7 @@ def check_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/check".replace("{store_id}", store_id), "POST", path_params, @@ -398,12 +355,10 @@ def check_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -419,14 +374,8 @@ def create_store(self, body, **kwargs): :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. @@ -449,14 +398,8 @@ def create_store_with_http_info(self, body, **kwargs): :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. @@ -476,8 +419,6 @@ def create_store_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -552,7 +493,7 @@ def create_store_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores", "POST", path_params, @@ -565,12 +506,10 @@ def create_store_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -584,14 +523,8 @@ def delete_store(self, **kwargs): :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. @@ -612,14 +545,8 @@ def delete_store_with_http_info(self, **kwargs): :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. @@ -639,8 +566,6 @@ def delete_store_with_http_info(self, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -700,7 +625,7 @@ def delete_store_with_http_info(self, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}".replace("{store_id}", store_id), "DELETE", path_params, @@ -713,12 +638,10 @@ def delete_store_with_http_info(self, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -734,14 +657,8 @@ def expand(self, body, **kwargs): :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. @@ -764,14 +681,8 @@ def expand_with_http_info(self, body, **kwargs): :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. @@ -791,8 +702,6 @@ def expand_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -810,7 +719,7 @@ def expand_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -881,7 +790,7 @@ def expand_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/expand".replace("{store_id}", store_id), "POST", path_params, @@ -894,12 +803,10 @@ def expand_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -913,14 +820,8 @@ def get_store(self, **kwargs): :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. @@ -941,14 +842,8 @@ def get_store_with_http_info(self, **kwargs): :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. @@ -968,8 +863,6 @@ def get_store_with_http_info(self, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -1038,7 +931,7 @@ def get_store_with_http_info(self, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}".replace("{store_id}", store_id), "GET", path_params, @@ -1051,12 +944,10 @@ def get_store_with_http_info(self, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -1072,14 +963,8 @@ def list_objects(self, body, **kwargs): :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. @@ -1102,14 +987,8 @@ def list_objects_with_http_info(self, body, **kwargs): :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. @@ -1129,8 +1008,6 @@ def list_objects_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -1148,7 +1025,7 @@ def list_objects_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -1219,7 +1096,7 @@ def list_objects_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/list-objects".replace("{store_id}", store_id), "POST", path_params, @@ -1232,12 +1109,10 @@ def list_objects_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -1255,14 +1130,8 @@ def list_stores(self, **kwargs): :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. @@ -1287,14 +1156,8 @@ def list_stores_with_http_info(self, **kwargs): :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. @@ -1314,8 +1177,6 @@ def list_stores_with_http_info(self, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -1384,7 +1245,7 @@ def list_stores_with_http_info(self, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores", "GET", path_params, @@ -1397,12 +1258,10 @@ def list_stores_with_http_info(self, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -1418,14 +1277,8 @@ def list_users(self, body, **kwargs): :type body: ListUsersRequest :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. @@ -1448,14 +1301,8 @@ def list_users_with_http_info(self, body, **kwargs): :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. @@ -1475,8 +1322,6 @@ def list_users_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -1494,7 +1339,7 @@ def list_users_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -1565,7 +1410,7 @@ def list_users_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/list-users".replace("{store_id}", store_id), "POST", path_params, @@ -1578,12 +1423,10 @@ def list_users_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -1599,14 +1442,8 @@ def read(self, body, **kwargs): :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. @@ -1629,14 +1466,8 @@ def read_with_http_info(self, body, **kwargs): :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. @@ -1656,8 +1487,6 @@ def read_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -1675,7 +1504,7 @@ def read_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -1746,7 +1575,7 @@ def read_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/read".replace("{store_id}", store_id), "POST", path_params, @@ -1759,12 +1588,10 @@ def read_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -1780,14 +1607,8 @@ def read_assertions(self, authorization_model_id, **kwargs): :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. @@ -1810,14 +1631,8 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): :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. @@ -1837,8 +1652,6 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -1856,7 +1669,7 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'authorization_model_id' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("authorization_model_id") is None ): raise ApiValueError( @@ -1920,7 +1733,7 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/assertions/{authorization_model_id}".replace( "{store_id}", store_id ), @@ -1935,12 +1748,10 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -1956,14 +1767,8 @@ def read_authorization_model(self, id, **kwargs): :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. @@ -1986,14 +1791,8 @@ def read_authorization_model_with_http_info(self, id, **kwargs): :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. @@ -2013,8 +1812,6 @@ def read_authorization_model_with_http_info(self, id, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2032,7 +1829,7 @@ def read_authorization_model_with_http_info(self, id, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'id' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("id") is None ): raise ApiValueError( @@ -2094,7 +1891,7 @@ def read_authorization_model_with_http_info(self, id, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/authorization-models/{id}".replace( "{store_id}", store_id ), @@ -2109,12 +1906,10 @@ def read_authorization_model_with_http_info(self, id, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -2132,14 +1927,8 @@ def read_authorization_models(self, **kwargs): :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. @@ -2164,14 +1953,8 @@ def read_authorization_models_with_http_info(self, **kwargs): :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. @@ -2191,8 +1974,6 @@ def read_authorization_models_with_http_info(self, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2267,7 +2048,7 @@ def read_authorization_models_with_http_info(self, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/authorization-models".replace("{store_id}", store_id), "GET", path_params, @@ -2280,12 +2061,10 @@ def read_authorization_models_with_http_info(self, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -2307,14 +2086,8 @@ def read_changes(self, **kwargs): :type start_time: datetime, 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. @@ -2343,14 +2116,8 @@ def read_changes_with_http_info(self, **kwargs): :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. @@ -2370,8 +2137,6 @@ def read_changes_with_http_info(self, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2450,7 +2215,7 @@ def read_changes_with_http_info(self, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/changes".replace("{store_id}", store_id), "GET", path_params, @@ -2463,12 +2228,10 @@ def read_changes_with_http_info(self, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -2484,14 +2247,8 @@ def streamed_list_objects(self, body, **kwargs): :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. @@ -2514,14 +2271,8 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): :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. @@ -2541,8 +2292,6 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2560,7 +2309,7 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -2631,7 +2380,7 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/streamed-list-objects".replace("{store_id}", store_id), "POST", path_params, @@ -2644,12 +2393,10 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -2665,14 +2412,8 @@ def write(self, body, **kwargs): :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. @@ -2695,14 +2436,8 @@ def write_with_http_info(self, body, **kwargs): :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. @@ -2722,8 +2457,6 @@ def write_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2741,7 +2474,7 @@ def write_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -2812,7 +2545,7 @@ def write_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/write".replace("{store_id}", store_id), "POST", path_params, @@ -2825,12 +2558,10 @@ def write_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -2848,14 +2579,8 @@ def write_assertions(self, authorization_model_id, body, **kwargs): :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. @@ -2882,14 +2607,8 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs :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. @@ -2909,8 +2628,6 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -2928,7 +2645,7 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs del local_var_params["kwargs"] # verify the required parameter 'authorization_model_id' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("authorization_model_id") is None ): raise ApiValueError( @@ -2936,7 +2653,7 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs ) # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -3003,7 +2720,7 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/assertions/{authorization_model_id}".replace( "{store_id}", store_id ), @@ -3018,12 +2735,10 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) @@ -3039,14 +2754,8 @@ def write_authorization_model(self, body, **kwargs): :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. @@ -3069,14 +2778,8 @@ def write_authorization_model_with_http_info(self, body, **kwargs): :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. @@ -3096,8 +2799,6 @@ def write_authorization_model_with_http_info(self, body, **kwargs): [ "async_req", "_return_http_data_only", - "_preload_content", - "_request_timeout", "_request_auth", "_content_type", "_headers", @@ -3115,7 +2816,7 @@ def write_authorization_model_with_http_info(self, body, **kwargs): del local_var_params["kwargs"] # verify the required parameter 'body' is set if ( - self.api_client.client_side_validation + self.configuration.client_side_validation and local_var_params.get("body") is None ): raise ApiValueError( @@ -3186,7 +2887,7 @@ def write_authorization_model_with_http_info(self, body, **kwargs): attributes=telemetry_attributes, ) - return self.api_client.call_api( + return self.api_client.request( "/stores/{store_id}/authorization-models".replace("{store_id}", store_id), "POST", path_params, @@ -3199,12 +2900,10 @@ def write_authorization_model_with_http_info(self, body, **kwargs): auth_settings=auth_settings, async_req=local_var_params.get("async_req"), _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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"), - _oauth2_client=self._oauth2_client, + _oauth2_client=self.auth_client, _telemetry_attributes=telemetry_attributes, _streaming=local_var_params.get("_streaming", False), ) diff --git a/openfga_sdk/sync/rest.py b/openfga_sdk/sync/rest.py index fb083430..0e52d054 100644 --- a/openfga_sdk/sync/rest.py +++ b/openfga_sdk/sync/rest.py @@ -10,380 +10,97 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ -import io +from dataclasses import dataclass import json import logging -import re import ssl -import urllib from typing import Any import urllib3 -from openfga_sdk.exceptions import ( - ApiException, - ApiValueError, - ForbiddenException, - NotFoundException, - RateLimitExceededError, - ServiceException, - UnauthorizedException, - ValidationException, +from openfga_sdk.common.rest import RestClientBase +from openfga_sdk.protocols import ( + ConfigurationProtocol, + RestClientRequestProtocol, + RestClientResponseProtocol, ) logger = logging.getLogger(__name__) -class RESTResponse(io.IOBase): - """ - Represents an HTTP response object in the synchronous client. - """ - - _response: urllib3.BaseHTTPResponse - _data: bytes - _status: int - _reason: str | None - - def __init__( - self, - response: urllib3.BaseHTTPResponse, - data: bytes, - status: int | None = None, - reason: str | None = None, - ) -> None: - """ - Initializes a RESTResponse with a urllib3.BaseHTTPResponse and corresponding data. - - :param resp: The urllib3.BaseHTTPResponse object. - :param data: The raw byte data read from the response. - """ - self._response = response - self._data = data - self._status = status or response.status - self._reason = reason or response.reason - - @property - def response(self) -> urllib3.BaseHTTPResponse: - """ - Returns the underlying urllib3.BaseHTTPResponse object. - """ - return self._response - - @response.setter - def response(self, value: urllib3.BaseHTTPResponse) -> None: - """ - Sets the underlying urllib3.BaseHTTPResponse object. - """ - self._response = value +@dataclass +class RestClientResponse(RestClientResponseProtocol): + response: urllib3.BaseHTTPResponse | None = None + data: bytes | None = None + status: int | None = None + reason: str | None = None @property - def data(self) -> bytes: - """ - Returns the raw byte data of the response. - """ - return self._data - - @data.setter - def data(self, value: bytes) -> None: - """ - Sets the raw byte data of the response. - """ - self._data = value - - @property - def status(self) -> int: - """ - Returns the HTTP status code of the response. - """ - return self._status - - @status.setter - def status(self, value: int) -> None: - """ - Sets the HTTP status code of the response. - """ - self._status = value - - @property - def reason(self) -> str | None: - """ - Returns the HTTP reason phrase of the response. - """ - return self._reason - - @reason.setter - def reason(self, value: str | None) -> None: - """ - Sets the HTTP reason phrase of the response. - """ - self._reason = value - - def getheaders(self) -> dict[str, str]: - """ - Returns a dictionary of the response headers. - """ + def headers(self) -> dict[str, str]: return dict(self.response.headers) - def getheader(self, name: str, default: str | None = None) -> str | None: - """ - Returns a specific header value by name. - - :param name: The name of the header. - :param default: The default value if header is not found. - :return: The header value, or default if not present. - """ - return self.response.headers.get(name, default) - -class RESTClientObject: - """ - A synchronous client object that manages HTTP interactions using urllib3. - """ +class RestClient(RestClientBase): + _pool_manager: urllib3.ProxyManager | urllib3.PoolManager | None = None def __init__( self, - configuration: Any, - pools_size: int = 4, - maxsize: int | None = None, + configuration: ConfigurationProtocol, + pool_size: int | None = None, + pool_size_max: int | None = None, + timeout: int | None = None, + debug: bool | None = None, ) -> None: - """ - Creates a new RESTClientObject using urllib3. + self._configuration = configuration + self._pool_size = pool_size + self._pool_size_max = pool_size_max + self._timeout = timeout + self._debug = debug - :param configuration: A configuration object with necessary parameters. - :param pools_size: The number of connection pools to use. - :param maxsize: The maximum number of connections per pool. - """ - if hasattr(configuration, "verify_ssl") and configuration.verify_ssl: - cert_reqs = ssl.CERT_REQUIRED - else: - cert_reqs = ssl.CERT_NONE - - addition_pool_args = {} - - if ( - hasattr(configuration, "assert_hostname") - and configuration.assert_hostname is not None - ): - addition_pool_args["assert_hostname"] = configuration.assert_hostname - - if hasattr(configuration, "retries") and configuration.retries is not None: - addition_pool_args["retries"] = configuration.retries - - if ( - hasattr(configuration, "socket_options") - and configuration.socket_options is not None - ): - addition_pool_args["socket_options"] = configuration.socket_options - - if maxsize is None: - if ( - hasattr(configuration, "connection_pool_maxsize") - and configuration.connection_pool_maxsize is not None - ): - maxsize = configuration.connection_pool_maxsize - else: - maxsize = 4 - - self._timeout_millisec = configuration.timeout_millisec - - if hasattr(configuration, "proxy") and configuration.proxy is not None: - self.pool_manager: urllib3.ProxyManager | urllib3.PoolManager = ( - urllib3.ProxyManager( - num_pools=pools_size, - maxsize=maxsize, - cert_reqs=cert_reqs, - ca_certs=configuration.ssl_ca_cert, - cert_file=configuration.cert_file, - key_file=configuration.key_file, - proxy_url=configuration.proxy, - proxy_headers=configuration.proxy_headers, - **addition_pool_args, + @property + def pool_manager(self) -> urllib3.ProxyManager | urllib3.PoolManager: + """ + Returns the underlying urllib3.PoolManager or urllib3.ProxyManager object. + """ + if self._pool_manager is None: + pool_configuration = { + "num_pools": self._pool_size + or self._configuration.connection_pool_size, + "maxsize": self._pool_size_max, + "timeout": self._timeout, + "cert_reqs": ( + ssl.CERT_REQUIRED + if self._configuration.verify_ssl + else ssl.CERT_NONE + ), + "ca_certs": self._configuration.ssl_ca_cert, + "cert_file": self._configuration.cert_file, + "key_file": self._configuration.key_file, + "ssl_context": self.ssl_context, + } + + if self._configuration.proxy: + self._pool_manager = urllib3.ProxyManager( + **pool_configuration, ) - ) - - return - - self.pool_manager = urllib3.PoolManager( - num_pools=pools_size, - maxsize=maxsize, - cert_reqs=cert_reqs, - ca_certs=configuration.ssl_ca_cert, - cert_file=configuration.cert_file, - key_file=configuration.key_file, - **addition_pool_args, - ) - - def close(self) -> None: - """ - Closes all pooled connections. - """ - self.pool_manager.clear() - - def build_request( - self, - method: str, - url: str, - query_params: dict | None = None, - headers: dict | None = None, - body: Any | None = None, - post_params: dict | None = None, - _preload_content: bool = True, - _request_timeout: float | tuple | None = None, - ) -> dict: - """ - Builds a dictionary of request arguments suitable for urllib3. - - :param method: The HTTP method (GET, POST, etc.). - :param url: The URL endpoint. - :param query_params: Optional query parameters. - :param headers: Optional request headers. - :param body: The request body, if any. - :param post_params: Form or multipart parameters, if any. - :param _preload_content: If True, response data is read immediately (by urllib3). - :param _request_timeout: Timeout setting, in seconds or a (connect, read) tuple. - :return: A dictionary of request arguments for urllib3. - """ - method = method.upper() - assert method in ["GET", "HEAD", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"] - - if post_params and body: - raise ApiValueError( - "body parameter cannot be used with post_params parameter." - ) - - headers = headers or {} - post_params = post_params or {} - timeout_val = _request_timeout or self._timeout_millisec - - if isinstance(timeout_val, float | int): - if timeout_val > 100: - timeout_val /= 1000 - timeout = urllib3.Timeout(total=timeout_val) - elif isinstance(timeout_val, tuple) and len(timeout_val) == 2: - connect_t, read_t = timeout_val - if connect_t > 100: - connect_t /= 1000 - if read_t > 100: - read_t /= 1000 - timeout = urllib3.Timeout(connect=connect_t, read=read_t) - else: - timeout = urllib3.Timeout(total=None) # fallback - - if "Content-Type" not in headers: - headers["Content-Type"] = "application/json" - - args = { - "method": method, - "url": url, - "timeout": timeout, - "headers": headers, - "preload_content": _preload_content, - } - - if query_params: - encoded_qs = urllib.parse.urlencode(query_params) - args["url"] = f"{url}?{encoded_qs}" - - # Handle body/post_params for methods that send payloads - if method in ["POST", "PUT", "PATCH", "OPTIONS", "DELETE"]: - if re.search("json", headers["Content-Type"], re.IGNORECASE): - if body is not None: - body = json.dumps(body) - args["body"] = body - - elif headers["Content-Type"] == "application/x-www-form-urlencoded": - args["fields"] = post_params - args["encode_multipart"] = False - - elif headers["Content-Type"] == "multipart/form-data": - del headers["Content-Type"] - args["fields"] = post_params - args["encode_multipart"] = True - - elif isinstance(body, str | bytes): - args["body"] = body else: - msg = ( - "Cannot prepare a request message for provided arguments. " - "Please check that your arguments match declared content type." - ) - raise ApiException(status=0, reason=msg) - else: - # For GET, HEAD, etc., we can pass query_params as fields if needed - # but we've already appended them to the URL above - pass + self._pool_manager = urllib3.PoolManager(**pool_configuration) - return args + return self._pool_manager - def handle_response_exception( - self, response: RESTResponse | urllib3.BaseHTTPResponse - ) -> None: - """ - Raises exceptions if response status indicates an error. + @pool_manager.setter + def pool_manager(self, value: urllib3.ProxyManager | urllib3.PoolManager) -> None: + self._pool_manager = value - :param response: The response to check (could be RESTResponse or raw urllib3.HTTPResponse). - """ - if 200 <= response.status <= 299: - return - - match response.status: - case 400: - raise ValidationException(http_resp=response) - case 401: - raise UnauthorizedException(http_resp=response) - case 403: - raise ForbiddenException(http_resp=response) - case 404: - raise NotFoundException(http_resp=response) - case 429: - raise RateLimitExceededError(http_resp=response) - case _ if 500 <= response.status <= 599: - raise ServiceException(http_resp=response) - case _: - raise ApiException(http_resp=response) - - def _accumulate_json_lines( - self, leftover: bytes, data: bytes, buffer: bytearray - ) -> tuple[bytes, list[Any]]: - """ - Processes a chunk of data plus any leftover bytes from a previous iteration. - Splits on newlines, decodes valid JSON lines, and returns updated leftover bytes - plus a list of decoded JSON objects. - - :param leftover: Any leftover bytes from previous chunks. - :param data: The new chunk of data. - :param buffer: The main bytearray buffer for all data in this request. - :return: A tuple of (updated leftover bytes, list of decoded objects). - """ - objects: list[Any] = [] - leftover += data - lines = leftover.split( - b"\n" - ) # Objects are received as one-per-line, so split at newlines - leftover = lines.pop() - buffer.extend(data) - - for line in lines: - line_str = line.decode("utf-8") - try: - decoded = json.loads(line_str) - objects.append(decoded) - except json.JSONDecodeError as e: - logger.warning("Skipping invalid JSON segment: %s", e) - - return leftover, objects + def close(self) -> None: + self.pool_manager.clear() def stream( self, - method: str, - url: str, - query_params: dict | None = None, - headers: dict | None = None, - body: Any | None = None, - post_params: dict | None = None, - _request_timeout: float | tuple | None = None, + request: RestClientRequestProtocol, + timeout: float | None = None, ): """ Streams JSON objects from a specified endpoint, reassembling partial chunks @@ -399,24 +116,20 @@ def stream( :yields: Parsed JSON objects as Python data structures. """ - # Build our request payload - args = self.build_request( - method, - url, - query_params=query_params, - headers=headers, - body=body, - post_params=post_params, - _preload_content=False, - _request_timeout=_request_timeout, - ) - # Initialize buffers for data chunks buffer = bytearray() leftover = b"" # Send request, collect response handler - response = self.pool_manager.request(**args) + response = self.pool_manager.request( + method=request.method, + url=request.url, + data=request.body, + headers=request.headers, + params=request.fields, + proxy=self._configuration.proxy, + proxy_headers=self._configuration.proxy_headers, + ) try: # Iterate over streamed/chunked response data @@ -447,7 +160,7 @@ def stream( logger.debug("Incomplete leftover data at end of stream.") # Handle any HTTP errors that may have occurred - self.handle_response_exception(response) + self._handle_response_exception(response) # Release the response object (required!) response.release_conn() @@ -457,59 +170,41 @@ def stream( def request( self, - method: str, - url: str, - query_params: dict | None = None, - headers: dict | None = None, - body: Any | None = None, - post_params: dict | None = None, - _preload_content: bool = True, - _request_timeout: float | tuple | None = None, - ) -> RESTResponse | urllib3.BaseHTTPResponse: + request: RestClientRequestProtocol, + timeout: int | None = None, + ) -> RestClientResponseProtocol: """ - Executes a request and returns the response object. + Configure and send a request. :param method: The HTTP method. :param url: The endpoint URL. :param query_params: Query parameters to be appended to the URL. :param headers: Optional request headers. :param body: A request body for JSON or other content types. - :param post_params: Form/multipart parameters for the request. - :param _preload_content: If True, the response body is read immediately - and wrapped in a RESTResponse. Otherwise, - an un-consumed urllib3.HTTPResponse is returned. - :param _request_timeout: Timeout in seconds or a (connect, read) tuple. - :return: A RESTResponse if _preload_content=True, otherwise a raw HTTPResponse. - """ - - # Build our request payload - args = self.build_request( - method, - url, - query_params=query_params, - headers=headers, - body=body, - post_params=post_params, - _preload_content=_preload_content, - _request_timeout=_request_timeout, + :param post_params: form/multipart parameters for the request. + :param timeout: An optional request timeout in seconds. + """ + + # Send request and collect response + response: urllib3.BaseHTTPResponse = self.pool_manager.request( + method=request.method, + url=request.url, + data=request.body, + headers=request.headers, + params=request.fields, + proxy=self._configuration.proxy, + proxy_headers=self._configuration.proxy_headers, ) - # Send request, collect response handler - wrapped_response: RESTResponse | None = None - raw_response: urllib3.BaseHTTPResponse = self.pool_manager.request(**args) - - # If we want to preload the response, read it - if _preload_content: - # Collect response data and transform response (JSON) into RESTResponse object - wrapped_response = RESTResponse(raw_response, raw_response.data) - - # Log the response body - logger.debug("response body: %s", wrapped_response.data.decode("utf-8")) - - # Handle any errors that may have occurred - self.handle_response_exception(raw_response) + # Transform response JSON data into RESTResponse object + wrapped_response = RestClientResponse( + data=response.data.decode("utf-8"), + status=response.status, + reason=response.reason, + response=response, + ) - # Release the connection back to the pool - self.close() + self._log_response(wrapped_response) + self._handle_response_exception(wrapped_response) - return wrapped_response or raw_response + return wrapped_response diff --git a/openfga_sdk/telemetry/attributes.py b/openfga_sdk/telemetry/attributes.py index a560c885..c671a243 100644 --- a/openfga_sdk/telemetry/attributes.py +++ b/openfga_sdk/telemetry/attributes.py @@ -10,17 +10,13 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ +from dataclasses import dataclass import time import urllib from typing import Any, NamedTuple - -from aiohttp import ClientResponse -from urllib3 import HTTPResponse - -from openfga_sdk.credentials import Credentials -from openfga_sdk.exceptions import ApiException -from openfga_sdk.rest import RESTResponse +from openfga_sdk.protocols import CredentialsProtocol, RestClientRequestBodyProtocol +from openfga_sdk.rest import RestClientResponseProtocol class TelemetryAttribute(NamedTuple): @@ -180,7 +176,7 @@ def prepare( @staticmethod def fromBody( - body: Any, + body: RestClientRequestBodyProtocol | None = None, attributes: dict[TelemetryAttribute, str | bool | int | float] | None = None, ): from openfga_sdk.models.batch_check_request import BatchCheckRequest @@ -206,7 +202,7 @@ def fromRequest( url: str | None = None, resend_count: int | None = None, start: float | None = None, - credentials: Credentials | None = None, + credentials: CredentialsProtocol | None = None, attributes: dict[TelemetryAttribute, str | bool | int | float] | None = None, ) -> dict[TelemetryAttribute, str | bool | int | float]: _attributes: dict[TelemetryAttribute, str | bool | int | float] = {} @@ -282,63 +278,39 @@ def fromRequest( @staticmethod def fromResponse( - response: ( - HTTPResponse | RESTResponse | ClientResponse | ApiException | None - ) = None, - credentials: Credentials | None = None, + response: RestClientResponseProtocol | None = None, + credentials: CredentialsProtocol | None = None, attributes: dict[TelemetryAttribute, str | bool | int | float] | None = None, ) -> dict[TelemetryAttribute, str | bool | int | float]: - response_model_id = None - response_query_duration = None _attributes: dict[TelemetryAttribute, str | bool | int | float] = {} if attributes is not None: _attributes = attributes - if isinstance(response, ApiException): - if response.status is not None: - _attributes[TelemetryAttributes.http_response_status_code] = int( - response.status + if response is not None: + response_model_id = response.header("openfga-authorization-model-id") + response_query_duration = response.header("fga-query-duration-ms") + + if response_model_id is not None: + _attributes[TelemetryAttributes.fga_client_response_model_id] = ( + response_model_id ) - if response.body is not None: - response_model_id = response.body.get( - "openfga-authorization-model-id" - ) or response.body.get("openfga_authorization_model_id") - response_query_duration = response.body.get("fga-query-duration-ms") + if response_query_duration is not None: + _attributes[TelemetryAttributes.http_server_request_duration] = ( + response_query_duration + ) - if response is not None: - if hasattr(response, "status"): + if response.status is not None: _attributes[TelemetryAttributes.http_response_status_code] = int( response.status ) - if hasattr(response, "getheader") and callable(response.getheader): - response_model_id = response.getheader("openfga-authorization-model-id") - response_query_duration = response.getheader("fga-query-duration-ms") - - if hasattr(response, "headers"): - response_model_id = response.headers.get( - "openfga-authorization-model-id" - ) - response_query_duration = response.headers.get("fga-query-duration-ms") - - if response_model_id is not None: - _attributes[TelemetryAttributes.fga_client_response_model_id] = ( - response_model_id - ) - - if response_query_duration is not None: - _attributes[TelemetryAttributes.http_server_request_duration] = ( - response_query_duration + if credentials is not None and credentials.method == "client_credentials": + _attributes[TelemetryAttributes.fga_client_request_client_id] = ( + credentials.configuration.client_id ) - if isinstance(credentials, Credentials): - if credentials.method == "client_credentials": - _attributes[TelemetryAttributes.fga_client_request_client_id] = ( - credentials.configuration.client_id - ) - return _attributes @staticmethod diff --git a/openfga_sdk/telemetry/configuration.py b/openfga_sdk/telemetry/configuration.py index 4e1bd79f..f190ad21 100644 --- a/openfga_sdk/telemetry/configuration.py +++ b/openfga_sdk/telemetry/configuration.py @@ -12,6 +12,7 @@ from typing import NamedTuple, Protocol, runtime_checkable +from openfga_sdk.protocols import TelemetryConfigurationProtocol from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes from openfga_sdk.telemetry.counters import TelemetryCounter, TelemetryCounters from openfga_sdk.telemetry.histograms import TelemetryHistogram, TelemetryHistograms @@ -658,9 +659,7 @@ def configure( clear: bool = False, ) -> None: ... - def getMetrics( - self, filter_enabled: bool = True - ) -> dict[ + def getMetrics(self, filter_enabled: bool = True) -> dict[ TelemetryHistogram | TelemetryCounter, TelemetryMetricConfiguration | dict[TelemetryAttribute | str, bool] | None, ]: ... @@ -905,9 +904,7 @@ def configure( self._valid = None - def getMetrics( - self, filter_enabled: bool = True - ) -> dict[ + def getMetrics(self, filter_enabled: bool = True) -> dict[ TelemetryHistogram | TelemetryCounter, TelemetryMetricConfiguration | dict[TelemetryAttribute | str, bool] | None, ]: @@ -1033,7 +1030,7 @@ def get( return None -class TelemetryConfiguration: +class TelemetryConfiguration(TelemetryConfigurationProtocol): _state: dict[TelemetryConfigurationType, TelemetryMetricsConfiguration | None] = {} _valid: bool | None = None @@ -1258,6 +1255,15 @@ def getSdkDefaults() -> dict[ TelemetryConfigurations.metrics: TelemetryMetricsConfiguration.getSdkDefaults(), } + @staticmethod + def withDefaults() -> "TelemetryConfiguration": + """ + Get the default SDK configuration for telemetry. + + :return: The default SDK configuration for telemetry. + """ + return TelemetryConfiguration(config=TelemetryConfiguration.getSdkDefaults()) + def isMetricEnabled( config: TelemetryConfiguration | TelemetryMetricsConfiguration | None, diff --git a/openfga_sdk/telemetry/counters.py b/openfga_sdk/telemetry/counters.py index 03091aa7..7c71ed6f 100644 --- a/openfga_sdk/telemetry/counters.py +++ b/openfga_sdk/telemetry/counters.py @@ -26,7 +26,7 @@ class TelemetryCounters: ) fga_client_request: TelemetryCounter = TelemetryCounter( - name="fga-client.request", + name="fga-client.http_request.duration", description="Total number of requests made to the FGA server.", ) diff --git a/openfga_sdk/validation.py b/openfga_sdk/validation.py index 5a047137..8c672e36 100644 --- a/openfga_sdk/validation.py +++ b/openfga_sdk/validation.py @@ -12,6 +12,8 @@ import re +from openfga_sdk.exceptions import FgaValidationException + def is_well_formed_ulid_string(ulid): regex = re.compile("^[0-7][0-9A-HJKMNP-TV-Z]{25}$") @@ -21,3 +23,50 @@ def is_well_formed_ulid_string(ulid): if match is None: return False return True + + +class ValidatedInteger: + _name: str | None = None + _value: int | None = None + _min: int | None = None + _max: int | None = None + _exception: Exception | None = None + + def __init__( + self, + *, + value: int, + min: int | None = None, + max: int | None = None, + exception: Exception = FgaValidationException, + ): + self._value = value + self._min = min + self._max = max + self._exception = exception + + def __set_name__(self, owner, name): + self._name = "_" + name + + def __get__(self, obj, type) -> int: + if obj is None: + return self._value + + return getattr(obj, self._name, self._value) + + def __set__(self, obj, value): + setattr(obj, self._name, int(value)) + + if self._min is not None and self._max is not None: + if not (self._min <= value <= self._max): + raise self._exception( + f"{value} is not in range [{self._min}, {self._max}]" + ) + + if self._min is not None: + if value < self._min: + raise self._exception(f"{value} is less than {self._min}") + + if self._max is not None: + if value > self._max: + raise self._exception(f"{value} is greater than {self._max}") diff --git a/requirements.txt b/requirements.txt index 4eef03dd..872cbbcc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ setuptools >= 69.1.1 build >= 1.2.1, < 2 urllib3 >= 1.25.11, < 3 opentelemetry-api >= 1.25.0, < 2 +orjson >= 3.10, <4 diff --git a/test-requirements.txt b/test-requirements.txt index b39447e1..5f421943 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,8 +1,10 @@ -r requirements.txt griffe >= 0.41.2, < 2 -mock >= 5.1.0, < 6 +pytest >= 8, < 9 +pytest-aiohttp >= 1, < 2 pytest-asyncio >= 0.25, < 1 +pytest-mock >= 3, < 4 pytest-cov >= 5, < 7 ruff >= 0.9, < 1 mypy >= 1.14.1, < 2 diff --git a/test/api/open_fga_api_test.py b/test/api/open_fga_api_test.py index dac5f7a0..4c2d48e5 100644 --- a/test/api/open_fga_api_test.py +++ b/test/api/open_fga_api_test.py @@ -20,6 +20,7 @@ import openfga_sdk +from openfga_sdk.configuration import Configuration from openfga_sdk import rest from openfga_sdk.api import open_fga_api from openfga_sdk.credentials import CredentialConfiguration, Credentials @@ -109,21 +110,22 @@ def http_mock_response(body, status): def mock_response(body, status): obj = http_mock_response(body, status) - return rest.RESTResponse(obj, obj.data) + return rest.RestClientResponse(response=obj, data=obj.data, status=obj.status) class TestOpenFgaApi(IsolatedAsyncioTestCase): """OpenFgaApi unit test stubs""" def setUp(self): - self.configuration = openfga_sdk.Configuration( + self.configuration = Configuration( api_url="http://api.fga.example", ) def tearDown(self): + self.configuration = None pass - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_check(self, mock_request): """Test case for check @@ -137,7 +139,7 @@ async def test_check(self, mock_request): configuration = self.configuration configuration.store_id = store_id async 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", @@ -146,7 +148,7 @@ async def test_check(self, mock_request): ), authorization_model_id="01GXSA8YR785C4FYS3C0RTG7B1", ) - api_response = await api_instance.check( + api_response = await api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) @@ -166,12 +168,11 @@ async def test_check(self, mock_request): }, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_create_store(self, mock_request): """Test case for create_store @@ -187,11 +188,11 @@ async def test_create_store(self, mock_request): configuration = self.configuration async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CreateStoreRequest( name="test-store", ) - api_response = await api_instance.create_store( + api_response = await api_client.api.create_store( body=body, ) self.assertIsInstance(api_response, CreateStoreResponse) @@ -203,12 +204,11 @@ async def test_create_store(self, mock_request): query_params=[], post_params=[], body={"name": "test-store"}, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_delete_store(self, mock_request): """Test case for delete_store @@ -219,8 +219,8 @@ async def test_delete_store(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) - await api_instance.delete_store() + + await api_client.api.delete_store() mock_request.assert_called_once_with( "DELETE", "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4", @@ -228,12 +228,11 @@ async def test_delete_store(self, mock_request): body=None, query_params=[], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_expand(self, mock_request): """Test case for expand @@ -246,7 +245,7 @@ async def test_expand(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = ExpandRequest( tuple_key=ExpandRequestTupleKey( object="document:budget", @@ -254,7 +253,7 @@ async def test_expand(self, mock_request): ), authorization_model_id="01GXSA8YR785C4FYS3C0RTG7B1", ) - api_response = await api_instance.expand( + api_response = await api_client.api.expand( body=body, ) self.assertIsInstance(api_response, ExpandResponse) @@ -274,12 +273,11 @@ async def test_expand(self, mock_request): "tuple_key": {"object": "document:budget", "relation": "reader"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_get_store(self, mock_request): """Test case for get_store @@ -296,9 +294,9 @@ async def test_get_store(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + # Get a store - api_response = await api_instance.get_store() + api_response = await api_client.api.get_store() self.assertIsInstance(api_response, GetStoreResponse) self.assertEqual(api_response.id, "01H0H015178Y2V4CX10C2KGHF4") self.assertEqual(api_response.name, "test_store") @@ -309,12 +307,11 @@ async def test_get_store(self, mock_request): body=None, query_params=[], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_list_objects(self, mock_request): """Test case for list_objects @@ -331,7 +328,7 @@ async def test_list_objects(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = ListObjectsRequest( authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", type="document", @@ -339,7 +336,7 @@ async def test_list_objects(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ) # Get all stores - api_response = await api_instance.list_objects(body) + api_response = await api_client.api.list_objects(body) self.assertIsInstance(api_response, ListObjectsResponse) self.assertEqual(api_response.objects, ["document:abcd1234"]) mock_request.assert_called_once_with( @@ -354,12 +351,11 @@ async def test_list_objects(self, mock_request): "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_list_stores(self, mock_request): """Test case for list_stores @@ -389,9 +385,9 @@ async def test_list_stores(self, mock_request): mock_request.return_value = mock_response(response_body, 200) configuration = self.configuration async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + # Get all stores - api_response = await api_instance.list_stores( + api_response = await api_client.api.list_stores( page_size=1, continuation_token="continuation_token_example", ) @@ -427,12 +423,11 @@ async def test_list_stores(self, mock_request): ("continuation_token", "continuation_token_example"), ], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_list_users(self, mock_request): """ Test case for list_users @@ -467,7 +462,6 @@ async def test_list_users(self, mock_request): configuration.store_id = store_id async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) request = ListUsersRequest( authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", @@ -491,7 +485,7 @@ async def test_list_users(self, mock_request): ], ) - response = await api_instance.list_users(request) + response = await api_client.api.list_users(request) self.assertIsInstance(response, ListUsersResponse) @@ -544,13 +538,12 @@ async def test_list_users(self, mock_request): }, ], }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) await api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read(self, mock_request): """Test case for read @@ -575,7 +568,7 @@ async def test_read(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = ReadRequest( tuple_key=ReadRequestTupleKey( object="document:2021-budget", @@ -585,7 +578,7 @@ async def test_read(self, mock_request): page_size=50, continuation_token="eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", ) - api_response = await api_instance.read( + api_response = await api_client.api.read( body=body, ) self.assertIsInstance(api_response, ReadResponse) @@ -614,11 +607,10 @@ async def test_read(self, mock_request): "page_size": 50, "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read_assertions(self, mock_request): """Test case for read_assertions @@ -643,8 +635,8 @@ async def test_read_assertions(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with openfga_sdk.ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) - api_response = await api_instance.read_assertions( + + api_response = await api_client.api.read_assertions( "01G5JAVJ41T49E9TT3SKVS7X1J", ) self.assertIsInstance(api_response, ReadAssertionsResponse) @@ -667,11 +659,10 @@ async def test_read_assertions(self, mock_request): body=None, query_params=[], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read_authorization_model(self, mock_request): """Test case for read_authorization_model @@ -716,10 +707,9 @@ async def test_read_authorization_model(self, mock_request): # Enter a context with an instance of the API client async 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 = await api_instance.read_authorization_model( + api_response = await api_client.api.read_authorization_model( "01G5JAVJ41T49E9TT3SKVS7X1J", ) self.assertIsInstance(api_response, ReadAuthorizationModelResponse) @@ -759,11 +749,10 @@ async def test_read_authorization_model(self, mock_request): body=None, query_params=[], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read_changes(self, mock_request): """Test case for read_changes @@ -791,10 +780,9 @@ async def test_read_changes(self, mock_request): # Enter a context with an instance of the API client async 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 = await api_instance.read_changes( + api_response = await api_client.api.read_changes( page_size=1, continuation_token="abcdefg", start_time="2022-01-01T00:00:00+00:00", @@ -827,11 +815,10 @@ async def test_read_changes(self, mock_request): ("start_time", "2022-01-01T00:00:00+00:00"), ], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write(self, mock_request): """Test case for write @@ -844,7 +831,6 @@ async def test_write(self, mock_request): # Enter a context with an instance of the API client async 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 @@ -860,7 +846,7 @@ async def test_write(self, mock_request): ), authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", ) - await api_instance.write( + await api_client.api.write( body, ) mock_request.assert_called_once_with( @@ -881,11 +867,10 @@ async def test_write(self, mock_request): }, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write_delete(self, mock_request): """Test case for write @@ -898,7 +883,6 @@ async def test_write_delete(self, mock_request): # Enter a context with an instance of the API client async 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 @@ -914,7 +898,7 @@ async def test_write_delete(self, mock_request): ), authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", ) - await api_instance.write( + await api_client.api.write( body, ) mock_request.assert_called_once_with( @@ -935,11 +919,10 @@ async def test_write_delete(self, mock_request): }, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write_assertions(self, mock_request): """Test case for write_assertions @@ -952,7 +935,6 @@ async def test_write_assertions(self, mock_request): # Enter a context with an instance of the API client async 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( @@ -968,7 +950,7 @@ async def test_write_assertions(self, mock_request): ], ) # Upsert assertions for an authorization model ID - await api_instance.write_assertions( + await api_client.api.write_assertions( authorization_model_id="xyz0123", body=body, ) @@ -990,11 +972,10 @@ async def test_write_assertions(self, mock_request): } ] }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write_authorization_model(self, mock_request): """Test case for write_authorization_model @@ -1006,7 +987,6 @@ async def test_write_authorization_model(self, mock_request): configuration.store_id = store_id async 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( @@ -1036,7 +1016,7 @@ async def test_write_authorization_model(self, mock_request): ], ) # Create a new authorization model - api_response = await api_instance.write_authorization_model(body) + api_response = await api_client.api.write_authorization_model(body) self.assertIsInstance(api_response, WriteAuthorizationModelResponse) expected_response = WriteAuthorizationModelResponse( authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J" @@ -1072,82 +1052,9 @@ async def test_write_authorization_model(self, mock_request): } ], }, - _preload_content=ANY, - _request_timeout=None, + 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) - def test_url(self): """ Ensure that api_url is set and validated @@ -1156,70 +1063,37 @@ def test_url(self): self.assertEqual(configuration.api_url, "http://localhost:8080") configuration.is_valid() - def test_url_with_scheme_and_host(self): - """ - Ensure that api_url takes precedence over api_host and scheme - """ - configuration = openfga_sdk.Configuration( - api_url="http://localhost:8080", api_host="localhost:8080", api_scheme="foo" - ) - self.assertEqual(configuration.api_url, "http://localhost:8080") - configuration.is_valid() # Should not throw and complain about scheme being invalid - - def test_timeout_millisec(self): + def test_timeout(self): """ Ensure that timeout_seconds is set and validated """ configuration = openfga_sdk.Configuration( api_url="http://localhost:8080", - timeout_millisec=10000, + timeout=10000, ) - self.assertEqual(configuration.timeout_millisec, 10000) + self.assertEqual(configuration.timeout, 10000) 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 - async 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): - await 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", + api_url="api.fga.example", ) # Notice the store_id is not set # Enter a context with an instance of the API client async 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): - await api_instance.read_authorization_models( + await api_client.api.read_authorization_models( page_size=1, continuation_token="abcdefg" ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_400_error(self, mock_request): """ Test to ensure 400 errors are handled properly @@ -1237,7 +1111,7 @@ async def test_400_error(self, mock_request): configuration = self.configuration configuration.store_id = store_id async 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", @@ -1246,7 +1120,7 @@ async def test_400_error(self, mock_request): ), ) with self.assertRaises(ValidationException) as api_exception: - await api_instance.check( + await api_client.api.check( body=body, ) self.assertIsInstance( @@ -1264,7 +1138,7 @@ async def test_400_error(self, mock_request): api_exception.exception.header.get(FGA_REQUEST_ID), request_id ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_404_error(self, mock_request): """ Test to ensure 404 errors are handled properly @@ -1282,7 +1156,7 @@ async def test_404_error(self, mock_request): configuration = self.configuration configuration.store_id = store_id async 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", @@ -1291,7 +1165,7 @@ async def test_404_error(self, mock_request): ), ) with self.assertRaises(NotFoundException) as api_exception: - await api_instance.check( + await api_client.api.check( body=body, ) self.assertIsInstance( @@ -1306,7 +1180,7 @@ async def test_404_error(self, mock_request): api_exception.exception.parsed_exception.message, "Endpoint not enabled" ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_429_error_no_retry(self, mock_request): """ Test to ensure 429 errors are handled properly. @@ -1327,7 +1201,7 @@ async def test_429_error_no_retry(self, mock_request): configuration.store_id = store_id configuration.retry_params = retry async 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", @@ -1336,14 +1210,14 @@ async def test_429_error_no_retry(self, mock_request): ), ) with self.assertRaises(RateLimitExceededError) as api_exception: - await api_instance.check( + await api_client.api.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") + @patch.object(rest.RestClient, "request") async def test_429_error_first_error(self, mock_request): """ Test to ensure 429 errors are handled properly. @@ -1368,7 +1242,7 @@ async def test_429_error_first_error(self, mock_request): configuration.store_id = store_id configuration.retry_params = retry async 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", @@ -1376,7 +1250,7 @@ async def test_429_error_first_error(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ), ) - api_response = await api_instance.check( + api_response = await api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) @@ -1384,7 +1258,7 @@ async def test_429_error_first_error(self, mock_request): mock_request.assert_called() self.assertEqual(mock_request.call_count, 2) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_500_error(self, mock_request): """ Test to ensure 500 errors are handled properly @@ -1401,10 +1275,10 @@ async def test_500_error(self, mock_request): configuration = self.configuration configuration.store_id = store_id - configuration.retry_params.max_retry = 0 + configuration.retry_params.max_retries = 0 async 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", @@ -1413,7 +1287,7 @@ async def test_500_error(self, mock_request): ), ) with self.assertRaises(ServiceException) as api_exception: - await api_instance.check( + await api_client.api.check( body=body, ) self.assertIsInstance( @@ -1430,7 +1304,7 @@ async def test_500_error(self, mock_request): mock_request.assert_called() self.assertEqual(mock_request.call_count, 1) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_500_error_retry(self, mock_request): """ Test to ensure 5xxx retries are handled properly @@ -1455,7 +1329,7 @@ async def test_500_error_retry(self, mock_request): configuration.retry_params = retry async 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", @@ -1464,7 +1338,7 @@ async def test_500_error_retry(self, mock_request): ), ) - api_response = await api_instance.check( + api_response = await api_client.api.check( body=body, ) @@ -1472,7 +1346,7 @@ async def test_500_error_retry(self, mock_request): mock_request.assert_called() self.assertEqual(mock_request.call_count, 5) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_501_error_retry(self, mock_request): """ Test to ensure 501 responses are not auto-retried @@ -1496,7 +1370,7 @@ async def test_501_error_retry(self, mock_request): configuration.retry_params = retry async 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", @@ -1505,13 +1379,13 @@ async def test_501_error_retry(self, mock_request): ), ) with self.assertRaises(ServiceException): - await api_instance.check( + await api_client.api.check( body=body, ) mock_request.assert_called() self.assertEqual(mock_request.call_count, 1) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_check_api_token(self, mock_request): """Test case for API token @@ -1529,7 +1403,7 @@ async def test_check_api_token(self, mock_request): configuration=CredentialConfiguration(api_token="TOKEN1"), ) async 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", @@ -1537,7 +1411,7 @@ async def test_check_api_token(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ), ) - api_response = await api_instance.check( + api_response = await api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) @@ -1564,11 +1438,10 @@ async def test_check_api_token(self, mock_request): "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", } }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_check_custom_header(self, mock_request): """Test case for custom header @@ -1582,8 +1455,8 @@ async def test_check_custom_header(self, mock_request): configuration = self.configuration configuration.store_id = store_id async 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) + api_client.headers.add_header("Custom Header", "custom value") + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1591,7 +1464,7 @@ async def test_check_custom_header(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ), ) - api_response = await api_instance.check( + api_response = await api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) @@ -1618,8 +1491,7 @@ async def test_check_custom_header(self, mock_request): "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", } }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) diff --git a/test/client/client_test.py b/test/client/client_test.py index 959accbf..f4a715b3 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -10,3182 +10,3064 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ -import uuid - from datetime import datetime -from unittest import IsolatedAsyncioTestCase -from unittest.mock import ANY, patch +from unittest.mock import AsyncMock +from aiohttp import web +import json +import uuid +import pytest +import pytest_asyncio import urllib3 -from openfga_sdk import rest -from openfga_sdk.client import ClientConfiguration -from openfga_sdk.client.client import OpenFgaClient -from openfga_sdk.client.models.assertion import ClientAssertion -from openfga_sdk.client.models.batch_check_item import ClientBatchCheckItem -from openfga_sdk.client.models.batch_check_request import ClientBatchCheckRequest -from openfga_sdk.client.models.check_request import ClientCheckRequest -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.list_users_request import ClientListUsersRequest -from openfga_sdk.client.models.read_changes_request import ClientReadChangesRequest -from openfga_sdk.client.models.tuple import ClientTuple -from openfga_sdk.client.models.write_request import ClientWriteRequest -from openfga_sdk.client.models.write_single_response import ClientWriteSingleResponse -from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts -from openfga_sdk.configuration import RetryParams -from openfga_sdk.exceptions import ( - FgaValidationException, - UnauthorizedException, - ValidationException, -) -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.consistency_preference import ConsistencyPreference -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.fga_object import FgaObject -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.api.open_fga_api import OpenFgaApi from openfga_sdk.models.list_stores_response import ListStoresResponse -from openfga_sdk.models.list_users_response import ListUsersResponse -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_request_tuple_key import ReadRequestTupleKey -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_key_without_condition import TupleKeyWithoutCondition -from openfga_sdk.models.tuple_operation import TupleOperation -from openfga_sdk.models.type_definition import TypeDefinition -from openfga_sdk.models.user_type_filter import UserTypeFilter -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_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_url="http://api.fga.example", - ) - - def tearDown(self): - pass - - @patch.object(rest.RESTClientObject, "request") - async def test_list_stores(self, mock_request): - """Test case for list_stores - - Get all stores - """ - 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await 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, - body=None, - query_params=[ - ("page_size", 1), - ("continuation_token", "continuation_token_example"), - ], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_create_store(self, mock_request): - """Test case for create_store - - Create a store - """ - 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await 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, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_store(self, mock_request): - """Test case for get_store - - Get all stores - """ - 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await 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, - body=None, - query_params=[], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_delete_store(self, mock_request): - """Test case for delete_store - - Get all stores - """ - mock_request.return_value = mock_response("", 201) - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - await api_client.delete_store(options={}) - mock_request.assert_called_once_with( - "DELETE", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X", - headers=ANY, - body=None, - query_params=[], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_read_authorization_models(self, mock_request): - """Test case for read_authorization_models - - Return all authorization models configured for the store - """ - 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 - async with OpenFgaClient(configuration) as api_client: - # Return a particular version of an authorization model - api_response = await 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, - body=None, - query_params=[], - post_params=[], - _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 - """ - response_body = '{"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}' - mock_request.return_value = mock_response(response_body, 201) - configuration = self.configuration - configuration.store_id = store_id - async 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 = await 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") - async def test_read_authorization_model(self, mock_request): - """Test case for read_authorization_model - - Return a particular version of an authorization model - """ - 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 - async with OpenFgaClient(configuration) as api_client: - # Return a particular version of an authorization model - api_response = await 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, - body=None, - query_params=[], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - - @patch.object(rest.RESTClientObject, "request") - async 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 - """ - 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 - async with OpenFgaClient(configuration) as api_client: - # Return a particular version of an authorization model - api_response = await 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, - body=None, - query_params=[("page_size", 1)], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - - @patch.object(rest.RESTClientObject, "request") - async def test_read_latest_authorization_model_with_no_models(self, mock_request): - """Test case for read_latest_authorization_model when no models are in the store - - Return the latest authorization models configured for the store - """ - response_body = """ -{ - "authorization_models": [] -} - """ - 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 - async with OpenFgaClient(configuration) as api_client: - # Return a particular version of an authorization model - api_response = await api_client.read_latest_authorization_model(options={}) - self.assertIsInstance(api_response, ReadAuthorizationModelResponse) - self.assertIsNone(api_response.authorization_model) - mock_request.assert_called_once_with( - "GET", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models", - headers=ANY, - body=None, - query_params=[("page_size", 1)], - post_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 - """ - 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 - async with OpenFgaClient(configuration) as api_client: - # Return a particular version of an authorization model - api_response = await api_client.read_changes( - ClientReadChangesRequest("document", "2022-01-01T00:00:00+00:00"), - 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, - body=None, - query_params=[ - ("type", "document"), - ("page_size", 1), - ("continuation_token", "abcdefg"), - ("start_time", "2022-01-01T00:00:00+00:00"), - ], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - - @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 - """ - response_body = """ - { - "tuples": [ - { - "key": { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget" - }, - "timestamp": "2021-10-06T15:32:11.128Z" - } - ], - "continuation_token": "" -} - """ - 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 - async with OpenFgaClient(configuration) as api_client: - body = ReadRequestTupleKey( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - ) - api_response = await api_client.read( - body=body, - options={ - "page_size": 50, - "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - "retry_params": RetryParams(max_retry=3, min_wait_in_ms=1000), - }, - ) - 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)], continuation_token="" - ) - 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==", - "consistency": "MINIMIZE_LATENCY", - }, - _preload_content=ANY, - _request_timeout=None, - ) - - @patch.object(rest.RESTClientObject, "request") - async 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 - """ - response_body = """ - { - "tuples": [ - { - "key": { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget" - }, - "timestamp": "2021-10-06T15:32:11.128Z" - } - ], - "continuation_token": "" -} - """ - 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 - async with OpenFgaClient(configuration) as api_client: - body = ReadRequestTupleKey( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - ) - api_response = await 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)], continuation_token="" - ) - 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") - async def test_read_empty_body(self, mock_request): - """Test case for read with empty body - - Get tuples from the store that matches a query, without following userset rewrite rules - """ - response_body = """ - { - "tuples": [ - { - "key": { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget" - }, - "timestamp": "2021-10-06T15:32:11.128Z" - } - ], - "continuation_token": "" -} - """ - 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 - async with OpenFgaClient(configuration) as api_client: - body = ReadRequestTupleKey() - api_response = await 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)], continuation_token="" - ) - self.assertEqual(api_response, expected_data) - mock_request.assert_called_once_with( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read", - headers=ANY, - body={}, - query_params=[], - post_params=[], - _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 with transaction enabled - """ - response_body = "{}" - mock_request.return_value = mock_response(response_body, 200) - configuration = self.configuration - configuration.store_id = store_id - async 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", - ), - ], - ) - await 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") - async 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 - async with OpenFgaClient(configuration) as api_client: - body = ClientWriteRequest( - deletes=[ - ClientTuple( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - ) - ], - ) - await 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") - async 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), - ] - configuration = self.configuration - configuration.store_id = store_id - async 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 = await 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( - "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") - async 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), - ] - configuration = self.configuration - configuration.store_id = store_id - async 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 = await 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( - "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") - async 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), - ] - configuration = self.configuration - configuration.store_id = store_id - async 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 = await 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, 2) - 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") - async 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), - ValidationException(http_resp=http_mock_response(response_body, 400)), - mock_response("{}", 200), - ] - configuration = self.configuration - configuration.store_id = store_id - async 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 = await 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, 3) - 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") - async 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), - ] - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - body = ClientWriteRequest( - deletes=[ - ClientTuple( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - ) - ], - writes=[], - ) - transaction = WriteTransactionOpts( - disabled=True, max_per_chunk=1, max_parallel_requests=10 - ) - await api_client.write( - body, - options={ - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - "transaction": transaction, - }, - ) - 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") - async 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 - async with OpenFgaClient(configuration) as api_client: - await 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") - async 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 - async with OpenFgaClient(configuration) as api_client: - await 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") - async 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 - async with OpenFgaClient(configuration) as api_client: - with self.assertRaises(UnauthorizedException): - 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 - ) - await api_client.write( - body, - options={ - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - "transaction": transaction, - }, - ) - - mock_request.assert_called() - self.assertEqual(mock_request.call_count, 1) - - mock_request.assert_called_once_with( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write", - headers=ANY, - query_params=[], - post_params=[], - body={ - "writes": { - "tuple_keys": [ - { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget", - } - ] - }, - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - }, - _preload_content=ANY, - _request_timeout=ANY, - ) - await api_client.close() - - @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 - """ - - # 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await api_client.check( - body=body, - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async 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 - """ - - # 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" - async with OpenFgaClient(configuration) as api_client: - api_response = await 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, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_client_batch_check_single_request(self, mock_request): - """Test case for check with single request - - Check whether a user is authorized to access an object - """ - - # First, mock the response - response_body = '{"allowed": true, "resolution": "1234"}' - mock_request.side_effect = [ - 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await api_client.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( - "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, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_client_batch_check_multiple_request(self, mock_request): - """Test case for check with multiple request - - Check whether a user is authorized to access an object - """ - - # First, mock the response - mock_request.side_effect = [ - 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await api_client.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( - "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, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_client_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 - """ - response_body = """ -{ - "code": "validation_error", - "message": "Generic validation error" -} - """ - - # First, mock the response - mock_request.side_effect = [ - 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await api_client.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( - "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, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async 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 - """ - - # First, mock the response - response_body = """ - { - "result": { - "1": { - "allowed": true - } - } - } - """ - mock_request.side_effect = [ - mock_response(response_body, 200), - ] - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ] - ) - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - api_response = await api_client.batch_check( - body=body, - options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, - ) - self.assertEqual(len(api_response.result), 1) - self.assertEqual(api_response.result[0].error, None) - self.assertTrue(api_response.result[0].allowed) - self.assertEqual(api_response.result[0].correlation_id, "1") - self.assertEqual(api_response.result[0].request, body.checks[0]) - # Make sure the API was called with the right data - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - }, - "correlation_id": "1", - } - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(uuid, "uuid4") - @patch.object(rest.RESTClientObject, "request") - async def test_batch_check_multiple_request(self, mock_request, mock_uuid): - """Test case for check with multiple request - - Check whether a user is authorized to access an object - """ - first_response_body = """ - { - "result": { - "1": { - "allowed": true - }, - "2": { - "allowed": false - } - } - } -""" - - second_response_body = """ -{ - "result": { - "fake-uuid": { - "error": { - "input_error": "validation_error", - "message": "type 'doc' not found" - } - } - } -}""" - - # First, mock the response - mock_request.side_effect = [ - mock_response(first_response_body, 200), - mock_response(second_response_body, 200), - ] - - def mock_v4(val: str): - return val - - mock_uuid.side_effect = [mock_v4("batch-id-header"), mock_v4("fake-uuid")] - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", - correlation_id="2", - ), - ClientBatchCheckItem( - object="doc:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", - ), - ] - ) - - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - api_response = await api_client.batch_check( - body=body, - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "max_parallel_requests": 1, - "max_batch_size": 2, - }, - ) - self.assertEqual(len(api_response.result), 3) - self.assertEqual(api_response.result[0].error, None) - self.assertTrue(api_response.result[0].allowed) - self.assertEqual(api_response.result[1].error, None) - self.assertFalse(api_response.result[1].allowed) - self.assertEqual( - api_response.result[2].error.message, "type 'doc' not found" - ) - self.assertFalse(api_response.result[2].allowed) - # value generated from the uuid mock - self.assertEqual(api_response.result[2].correlation_id, "fake-uuid") - # Make sure the API was called with the right data - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - }, - "correlation_id": "1", - }, - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", - }, - "correlation_id": "2", - }, - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "doc:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", - }, - "correlation_id": "fake-uuid", - } - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - async def test_batch_check_errors_dupe_cor_id(self): - """Test case for duplicate correlation_id being provided to batch_check""" - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ] - ) - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - with self.assertRaises(FgaValidationException) as error: - await api_client.batch_check( - body=body, - options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, - ) - self.assertEqual( - "Duplicate correlation_id (1) provided", str(error.exception) - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_batch_check_errors_unauthorized(self, mock_request): - """Test case for BatchCheck with a 401""" - first_response_body = """ - { - "result": { - "1": { - "allowed": true - }, - "2": { - "allowed": false - } - } - } -""" - - # First, mock the response - mock_request.side_effect = [ - mock_response(first_response_body, 200), - UnauthorizedException(http_resp=http_mock_response("{}", 401)), - ] - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", - correlation_id="2", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", - correlation_id="3", - ), - ] - ) - - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - with self.assertRaises(UnauthorizedException): - await api_client.batch_check( - body=body, - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "max_parallel_requests": 1, - "max_batch_size": 2, - }, - ) - - # Make sure the API was called with the right data - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - }, - "correlation_id": "1", - }, - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", - }, - "correlation_id": "2", - }, - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", - }, - "correlation_id": "3", - } - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - await 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 - """ - 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 - async with OpenFgaClient(configuration) as api_client: - body = ClientExpandRequest( - object="document:budget", - relation="reader", - ) - api_response = await api_client.expand( - body=body, - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - self.assertIsInstance(api_response, ExpandResponse) - cur_users = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]) - leaf = Leaf(users=cur_users) - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_list_objects(self, mock_request): - """Test case for list_objects - - List objects - """ - response_body = """ -{ - "objects": [ - "document:abcd1234" - ] -} - """ - mock_request.return_value = mock_response(response_body, 200) - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - body = ClientListObjectsRequest( - type="document", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - ) - # Get all stores - api_response = await api_client.list_objects( - body, - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_list_objects_contextual_tuples(self, mock_request): - """Test case for list_objects - - List objects - """ - response_body = """ -{ - "objects": [ - "document:abcd1234" - ] -} - """ - mock_request.return_value = mock_response(response_body, 200) - configuration = self.configuration - configuration.store_id = store_id - async 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 = await 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, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_list_relations(self, mock_request): - """Test case for list relations - - Check whether a user is authorized to access an object - """ - - def mock_check_requests(*args, **kwargs): - body = kwargs.get("body") - tuple_key = body.get("tuple_key") - if tuple_key["relation"] == "owner": - return mock_response('{"allowed": false, "resolution": "1234"}', 200) - return mock_response('{"allowed": true, "resolution": "1234"}', 200) - - # First, mock the response - mock_request.side_effect = mock_check_requests - - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - api_response = await 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", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - self.assertEqual(api_response, ["reader", "viewer"]) - - # Make sure the API was called with the right data - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _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", - "consistency": "MINIMIZE_LATENCY", - }, - _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", - "consistency": "MINIMIZE_LATENCY", - }, - _preload_content=ANY, - _request_timeout=None, - ) - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async 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 - async with OpenFgaClient(configuration) as api_client: - with self.assertRaises(UnauthorizedException): - await 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"}, - ) - - mock_request.assert_called() - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_list_users(self, mock_request): - """ - Test case for list_users - """ - - response_body = """{ - "users": [ - { - "object": { - "id": "81684243-9356-4421-8fbf-a4f8d36aa31b", - "type": "user" - } - }, - { - "userset": { - "id": "fga", - "relation": "member", - "type": "team" - } - }, - { - "wildcard": { - "type": "user" - } - } - ] -}""" - - mock_request.return_value = mock_response(response_body, 200) - - configuration = self.configuration - configuration.store_id = store_id - - async with OpenFgaClient(configuration) as api_client: - body = ClientListUsersRequest( - object=FgaObject(type="document", id="2021-budget"), - relation="can_read", - user_filters=[ - UserTypeFilter(type="user"), - ], - context={}, - contextual_tuples=[ - ClientTuple( - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - relation="editor", - object="folder:product", - ), - ClientTuple( - user="folder:product", - relation="parent", - object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", - ), - ], - ) - - response = await api_client.list_users( - body, - options={ - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - - self.assertIsInstance(response, ListUsersResponse) - - self.assertEqual(response.users.__len__(), 3) - - self.assertIsNotNone(response.users[0].object) - self.assertEqual( - response.users[0].object.id, "81684243-9356-4421-8fbf-a4f8d36aa31b" - ) - self.assertEqual(response.users[0].object.type, "user") - self.assertIsNone(response.users[0].userset) - self.assertIsNone(response.users[0].wildcard) - - self.assertIsNone(response.users[1].object) - self.assertIsNotNone(response.users[1].userset) - self.assertEqual(response.users[1].userset.id, "fga") - self.assertEqual(response.users[1].userset.relation, "member") - self.assertEqual(response.users[1].userset.type, "team") - self.assertIsNone(response.users[1].wildcard) - - self.assertIsNone(response.users[2].object) - self.assertIsNone(response.users[2].userset) - self.assertIsNotNone(response.users[2].wildcard) - self.assertEqual(response.users[2].wildcard.type, "user") - - mock_request.assert_called_once_with( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-users", - headers=ANY, - query_params=[], - post_params=[], - body={ - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - "object": {"id": "2021-budget", "type": "document"}, - "relation": "can_read", - "user_filters": [ - {"type": "user"}, - ], - "contextual_tuples": [ - { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "editor", - "object": "folder:product", - }, - { - "user": "folder:product", - "relation": "parent", - "object": "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", - }, - ], - "context": {}, - "consistency": "MINIMIZE_LATENCY", - }, - _preload_content=ANY, - _request_timeout=None, - ) - - await api_client.close() - - @patch.object(rest.RESTClientObject, "request") - async 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 - async with OpenFgaClient(configuration) as api_client: - api_response = await api_client.read_assertions( - options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} - ) - self.assertEqual( - api_response, - ReadAssertionsResponse( - authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", - assertions=[ - Assertion( - tuple_key=TupleKeyWithoutCondition( - 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, - body=None, - query_params=[], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - - @patch.object(rest.RESTClientObject, "request") - async def test_write_assertions(self, mock_request): - """Test case for write assertions - - Get all stores - """ - mock_request.return_value = mock_response("", 204) - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - await 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") - async def test_set_store_id(self, mock_request): - """Test case for write assertions - - Get all stores - """ - mock_request.return_value = mock_response("", 204) - configuration = self.configuration - configuration.store_id = store_id - async with OpenFgaClient(configuration) as api_client: - api_client.set_store_id("01YCP46JKYM8FJCQ37NMBYHE5Y") - - await 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") - async def test_config_auth_model(self, mock_request): - """Test case for write assertions - - Get all stores - """ - mock_request.return_value = mock_response("", 204) - configuration = self.configuration - configuration.store_id = store_id - configuration.authorization_model_id = "01G5JAVJ41T49E9TT3SKVS7X1J" - async with OpenFgaClient(configuration) as api_client: - await 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") - async def test_update_auth_model(self, mock_request): - """Test case for write assertions - - Get all stores - """ - mock_request.return_value = mock_response("", 204) - configuration = self.configuration - configuration.store_id = store_id - configuration.authorization_model_id = "01G5JAVJ41T49E9TT3SKVS7X1J" - async with OpenFgaClient(configuration) as api_client: - api_client.set_authorization_model_id("01G5JAVJ41T49E9TT3SKVS7X2J") - - await 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) +from openfga_sdk.rest import RestClient, RestClientResponse +from openfga_sdk.configuration import Configuration +from openfga_sdk.protocols import OpenFgaApiProtocol, OpenFgaClientProtocol +from openfga_sdk.client import OpenFgaClient + + +# def mock_response(body: dict | str, status) -> RestClientResponse: +# body = json.dumps(body) if isinstance(body, dict) else body +# headers = urllib3.response.HTTPHeaderDict({"content-type": "application/json"}) +# obj = urllib3.HTTPResponse(body, headers, status, preload_content=False) +# return RestClientResponse(response=obj, data=body, status=status, reason="OK") + + +# @pytest.fixture +# def configuration(): +# yield Configuration( +# api_url="https://api.fga.example", +# ) + + +# @pytest_asyncio.fixture +# async def client(configuration): +# client = OpenFgaClient( +# configuration=configuration, +# ) +# yield client +# await client.close() + + +# @pytest_asyncio.fixture +# async def store_id() -> str: +# return str(uuid.uuid4()) + + +# @pytest_asyncio.fixture +# async def request_id() -> str: +# return str(uuid.uuid4()) + + +# @pytest.mark.asyncio +# class TestOpenFgaClient: +# async def test_list_stores(self, client: OpenFgaClientProtocol, mocker): +# mock_api = mocker.patch.object( +# client.api, +# "list_stores", +# AsyncMock(return_value=ListStoresResponse()), +# ) + +# response = await client.list_stores() + +# mock_api.assert_called_once() + +# print(response) + + +# @patch.object(RestClient, "request") +# async def test_list_stores(self, mock_request): +# """Test case for list_stores + +# Get all stores +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await 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, +# body=None, +# query_params=[ +# ("page_size", 1), +# ("continuation_token", "continuation_token_example"), +# ], +# post_params=[], +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_create_store(self, mock_request): +# """Test case for create_store + +# Create a store +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await 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"}, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_get_store(self, mock_request): +# """Test case for get_store + +# Get all stores +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_delete_store(self, mock_request): +# """Test case for delete_store + +# Get all stores +# """ +# mock_request.return_value = mock_response("", 201) +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# await api_client.delete_store(options={}) +# mock_request.assert_called_once_with( +# "DELETE", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X", +# headers=ANY, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_read_authorization_models(self, mock_request): +# """Test case for read_authorization_models + +# Return all authorization models configured for the store +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# # Return a particular version of an authorization model +# api_response = await 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_write_authorization_model(self, mock_request): +# """Test case for write_authorization_model + +# Create a new authorization model +# """ +# response_body = '{"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}' +# mock_request.return_value = mock_response(response_body, 201) +# configuration = self.configuration +# configuration.store_id = store_id +# async 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 = await 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", +# } +# }, +# ] +# } +# }, +# }, +# } +# ], +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_read_authorization_model(self, mock_request): +# """Test case for read_authorization_model + +# Return a particular version of an authorization model +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# # Return a particular version of an authorization model +# api_response = await 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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 +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# # Return a particular version of an authorization model +# api_response = await 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, +# body=None, +# query_params=[("page_size", 1)], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_read_latest_authorization_model_with_no_models(self, mock_request): +# """Test case for read_latest_authorization_model when no models are in the store + +# Return the latest authorization models configured for the store +# """ +# response_body = """ +# { +# "authorization_models": [] +# } +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# # Return a particular version of an authorization model +# api_response = await api_client.read_latest_authorization_model(options={}) +# self.assertIsInstance(api_response, ReadAuthorizationModelResponse) +# self.assertIsNone(api_response.authorization_model) +# mock_request.assert_called_once_with( +# "GET", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models", +# headers=ANY, +# body=None, +# query_params=[("page_size", 1)], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_read_changes(self, mock_request): +# """Test case for read_changes + +# Return a list of all the tuple changes +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# # Return a particular version of an authorization model +# api_response = await api_client.read_changes( +# ClientReadChangesRequest("document", "2022-01-01T00:00:00+00:00"), +# 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, +# body=None, +# query_params=[ +# ("type", "document"), +# ("page_size", 1), +# ("continuation_token", "abcdefg"), +# ("start_time", "2022-01-01T00:00:00+00:00"), +# ], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "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 +# """ +# response_body = """ +# { +# "tuples": [ +# { +# "key": { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget" +# }, +# "timestamp": "2021-10-06T15:32:11.128Z" +# } +# ], +# "continuation_token": "" +# } +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# body = ReadRequestTupleKey( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# ) +# api_response = await api_client.read( +# body=body, +# options={ +# "page_size": 50, +# "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# "retry_params": RetryParams(max_retries=3, min_wait_in_ms=1000), +# }, +# ) +# 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)], continuation_token="" +# ) +# 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==", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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 +# """ +# response_body = """ +# { +# "tuples": [ +# { +# "key": { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget" +# }, +# "timestamp": "2021-10-06T15:32:11.128Z" +# } +# ], +# "continuation_token": "" +# } +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# body = ReadRequestTupleKey( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# ) +# api_response = await 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)], continuation_token="" +# ) +# 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", +# } +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_read_empty_body(self, mock_request): +# """Test case for read with empty body + +# Get tuples from the store that matches a query, without following userset rewrite rules +# """ +# response_body = """ +# { +# "tuples": [ +# { +# "key": { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget" +# }, +# "timestamp": "2021-10-06T15:32:11.128Z" +# } +# ], +# "continuation_token": "" +# } +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# body = ReadRequestTupleKey() +# api_response = await 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)], continuation_token="" +# ) +# self.assertEqual(api_response, expected_data) +# mock_request.assert_called_once_with( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read", +# headers=ANY, +# body={}, +# query_params=[], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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 +# async 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", +# ), +# ], +# ) +# await 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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 +# async with OpenFgaClient(configuration) as api_client: +# body = ClientWriteRequest( +# deletes=[ +# ClientTuple( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# ) +# ], +# ) +# await 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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), +# ] +# configuration = self.configuration +# configuration.store_id = store_id +# async 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 = await 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( +# "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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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), +# ] +# configuration = self.configuration +# configuration.store_id = store_id +# async 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 = await 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( +# "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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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), +# ] +# configuration = self.configuration +# configuration.store_id = store_id +# async 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 = await 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, 2) +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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), +# ValidationException(http_resp=http_mock_response(response_body, 400)), +# mock_response("{}", 200), +# ] +# configuration = self.configuration +# configuration.store_id = store_id +# async 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 = await 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, 3) +# 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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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), +# ] +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# body = ClientWriteRequest( +# deletes=[ +# ClientTuple( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# ) +# ], +# writes=[], +# ) +# transaction = WriteTransactionOpts( +# disabled=True, max_per_chunk=1, max_parallel_requests=10 +# ) +# await api_client.write( +# body, +# options={ +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# "transaction": transaction, +# }, +# ) +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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 +# async with OpenFgaClient(configuration) as api_client: +# await 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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 +# async with OpenFgaClient(configuration) as api_client: +# await 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async 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 +# async with OpenFgaClient(configuration) as api_client: +# with self.assertRaises(UnauthorizedException): +# 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 +# ) +# await api_client.write( +# body, +# options={ +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# "transaction": transaction, +# }, +# ) + +# mock_request.assert_called() +# self.assertEqual(mock_request.call_count, 1) + +# mock_request.assert_called_once_with( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "writes": { +# "tuple_keys": [ +# { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget", +# } +# ] +# }, +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_check(self, mock_request): +# """Test case for check + +# Check whether a user is authorized to access an object +# """ + +# # 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await api_client.check( +# body=body, +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async 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 +# """ + +# # 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" +# async with OpenFgaClient(configuration) as api_client: +# api_response = await 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", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_client_batch_check_single_request(self, mock_request): +# """Test case for check with single request + +# Check whether a user is authorized to access an object +# """ + +# # First, mock the response +# response_body = '{"allowed": true, "resolution": "1234"}' +# mock_request.side_effect = [ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await api_client.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( +# "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", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_client_batch_check_multiple_request(self, mock_request): +# """Test case for check with multiple request + +# Check whether a user is authorized to access an object +# """ + +# # First, mock the response +# mock_request.side_effect = [ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await api_client.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( +# "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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_client_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 +# """ +# response_body = """ +# { +# "code": "validation_error", +# "message": "Generic validation error" +# } +# """ + +# # First, mock the response +# mock_request.side_effect = [ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await api_client.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( +# "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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async 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 +# """ + +# # First, mock the response +# response_body = """ +# { +# "result": { +# "1": { +# "allowed": true +# } +# } +# } +# """ +# mock_request.side_effect = [ +# mock_response(response_body, 200), +# ] + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ] +# ) +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# api_response = await api_client.batch_check( +# body=body, +# options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, +# ) +# self.assertEqual(len(api_response.result), 1) +# self.assertEqual(api_response.result[0].error, None) +# self.assertTrue(api_response.result[0].allowed) +# self.assertEqual(api_response.result[0].correlation_id, "1") +# self.assertEqual(api_response.result[0].request, body.checks[0]) +# # Make sure the API was called with the right data +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# }, +# "correlation_id": "1", +# } +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(uuid, "uuid4") +# @patch.object(RestClient, "request") +# async def test_batch_check_multiple_request(self, mock_request, mock_uuid): +# """Test case for check with multiple request + +# Check whether a user is authorized to access an object +# """ +# first_response_body = """ +# { +# "result": { +# "1": { +# "allowed": true +# }, +# "2": { +# "allowed": false +# } +# } +# } +# """ + +# second_response_body = """ +# { +# "result": { +# "fake-uuid": { +# "error": { +# "input_error": "validation_error", +# "message": "type 'doc' not found" +# } +# } +# } +# }""" + +# # First, mock the response +# mock_request.side_effect = [ +# mock_response(first_response_body, 200), +# mock_response(second_response_body, 200), +# ] + +# def mock_v4(val: str): +# return val + +# mock_uuid.side_effect = [mock_v4("batch-id-header"), mock_v4("fake-uuid")] + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# correlation_id="2", +# ), +# ClientBatchCheckItem( +# object="doc:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", +# ), +# ] +# ) + +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# api_response = await api_client.batch_check( +# body=body, +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "max_parallel_requests": 1, +# "max_batch_size": 2, +# }, +# ) +# self.assertEqual(len(api_response.result), 3) +# self.assertEqual(api_response.result[0].error, None) +# self.assertTrue(api_response.result[0].allowed) +# self.assertEqual(api_response.result[1].error, None) +# self.assertFalse(api_response.result[1].allowed) +# self.assertEqual( +# api_response.result[2].error.message, "type 'doc' not found" +# ) +# self.assertFalse(api_response.result[2].allowed) +# # value generated from the uuid mock +# self.assertEqual(api_response.result[2].correlation_id, "fake-uuid") +# # Make sure the API was called with the right data +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# }, +# "correlation_id": "1", +# }, +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# }, +# "correlation_id": "2", +# }, +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "doc:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", +# }, +# "correlation_id": "fake-uuid", +# } +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# await api_client.close() + +# async def test_batch_check_errors_dupe_cor_id(self): +# """Test case for duplicate correlation_id being provided to batch_check""" + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ] +# ) +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# with self.assertRaises(FgaValidationException) as error: +# await api_client.batch_check( +# body=body, +# options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, +# ) +# self.assertEqual( +# "Duplicate correlation_id (1) provided", str(error.exception) +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_batch_check_errors_unauthorized(self, mock_request): +# """Test case for BatchCheck with a 401""" +# first_response_body = """ +# { +# "result": { +# "1": { +# "allowed": true +# }, +# "2": { +# "allowed": false +# } +# } +# } +# """ + +# # First, mock the response +# mock_request.side_effect = [ +# mock_response(first_response_body, 200), +# UnauthorizedException(http_resp=http_mock_response("{}", 401)), +# ] + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# correlation_id="2", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", +# correlation_id="3", +# ), +# ] +# ) + +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# with self.assertRaises(UnauthorizedException): +# await api_client.batch_check( +# body=body, +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "max_parallel_requests": 1, +# "max_batch_size": 2, +# }, +# ) + +# # Make sure the API was called with the right data +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# }, +# "correlation_id": "1", +# }, +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# }, +# "correlation_id": "2", +# }, +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", +# }, +# "correlation_id": "3", +# } +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "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 +# """ +# 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 +# async with OpenFgaClient(configuration) as api_client: +# body = ClientExpandRequest( +# object="document:budget", +# relation="reader", +# ) +# api_response = await api_client.expand( +# body=body, +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# self.assertIsInstance(api_response, ExpandResponse) +# cur_users = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]) +# leaf = Leaf(users=cur_users) +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_list_objects(self, mock_request): +# """Test case for list_objects + +# List objects +# """ +# response_body = """ +# { +# "objects": [ +# "document:abcd1234" +# ] +# } +# """ +# mock_request.return_value = mock_response(response_body, 200) +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# body = ClientListObjectsRequest( +# type="document", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# ) +# # Get all stores +# api_response = await api_client.list_objects( +# body, +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_list_objects_contextual_tuples(self, mock_request): +# """Test case for list_objects + +# List objects +# """ +# response_body = """ +# { +# "objects": [ +# "document:abcd1234" +# ] +# } +# """ +# mock_request.return_value = mock_response(response_body, 200) +# configuration = self.configuration +# configuration.store_id = store_id +# async 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 = await 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", +# } +# ] +# }, +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_list_relations(self, mock_request): +# """Test case for list relations + +# Check whether a user is authorized to access an object +# """ + +# def mock_check_requests(*args, **kwargs): +# body = kwargs.get("body") +# tuple_key = body.get("tuple_key") +# if tuple_key["relation"] == "owner": +# return mock_response('{"allowed": false, "resolution": "1234"}', 200) +# return mock_response('{"allowed": true, "resolution": "1234"}', 200) + +# # First, mock the response +# mock_request.side_effect = mock_check_requests + +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# api_response = await 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", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# self.assertEqual(api_response, ["reader", "viewer"]) + +# # Make sure the API was called with the right data +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# await api_client.close() + +# @patch.object(RestClient, "request") +# async 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 +# async with OpenFgaClient(configuration) as api_client: +# with self.assertRaises(UnauthorizedException): +# await 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"}, +# ) + +# mock_request.assert_called() +# await api_client.close() + +# @patch.object(RestClient, "request") +# async def test_list_users(self, mock_request): +# """ +# Test case for list_users +# """ + +# response_body = """{ +# "users": [ +# { +# "object": { +# "id": "81684243-9356-4421-8fbf-a4f8d36aa31b", +# "type": "user" +# } +# }, +# { +# "userset": { +# "id": "fga", +# "relation": "member", +# "type": "team" +# } +# }, +# { +# "wildcard": { +# "type": "user" +# } +# } +# ] +# }""" + +# mock_request.return_value = mock_response(response_body, 200) + +# configuration = self.configuration +# configuration.store_id = store_id + +# async with OpenFgaClient(configuration) as api_client: +# body = ClientListUsersRequest( +# object=FgaObject(type="document", id="2021-budget"), +# relation="can_read", +# user_filters=[ +# UserTypeFilter(type="user"), +# ], +# context={}, +# contextual_tuples=[ +# ClientTuple( +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# relation="editor", +# object="folder:product", +# ), +# ClientTuple( +# user="folder:product", +# relation="parent", +# object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", +# ), +# ], +# ) + +# response = await api_client.list_users( +# body, +# options={ +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) + +# self.assertIsInstance(response, ListUsersResponse) + +# self.assertEqual(response.users.__len__(), 3) + +# self.assertIsNotNone(response.users[0].object) +# self.assertEqual( +# response.users[0].object.id, "81684243-9356-4421-8fbf-a4f8d36aa31b" +# ) +# self.assertEqual(response.users[0].object.type, "user") +# self.assertIsNone(response.users[0].userset) +# self.assertIsNone(response.users[0].wildcard) + +# self.assertIsNone(response.users[1].object) +# self.assertIsNotNone(response.users[1].userset) +# self.assertEqual(response.users[1].userset.id, "fga") +# self.assertEqual(response.users[1].userset.relation, "member") +# self.assertEqual(response.users[1].userset.type, "team") +# self.assertIsNone(response.users[1].wildcard) + +# self.assertIsNone(response.users[2].object) +# self.assertIsNone(response.users[2].userset) +# self.assertIsNotNone(response.users[2].wildcard) +# self.assertEqual(response.users[2].wildcard.type, "user") + +# mock_request.assert_called_once_with( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-users", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# "object": {"id": "2021-budget", "type": "document"}, +# "relation": "can_read", +# "user_filters": [ +# {"type": "user"}, +# ], +# "contextual_tuples": [ +# { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "editor", +# "object": "folder:product", +# }, +# { +# "user": "folder:product", +# "relation": "parent", +# "object": "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", +# }, +# ], +# "context": {}, +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) + +# await api_client.close() + +# @patch.object(RestClient, "request") +# async 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 +# async with OpenFgaClient(configuration) as api_client: +# api_response = await api_client.read_assertions( +# options={"authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"} +# ) +# self.assertEqual( +# api_response, +# ReadAssertionsResponse( +# authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", +# assertions=[ +# Assertion( +# tuple_key=TupleKeyWithoutCondition( +# 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_write_assertions(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# mock_request.return_value = mock_response("", 204) +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# await 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=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_set_store_id(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# mock_request.return_value = mock_response("", 204) +# configuration = self.configuration +# configuration.store_id = store_id +# async with OpenFgaClient(configuration) as api_client: +# api_client.set_store_id("01YCP46JKYM8FJCQ37NMBYHE5Y") + +# await 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=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_config_auth_model(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# mock_request.return_value = mock_response("", 204) +# configuration = self.configuration +# configuration.store_id = store_id +# configuration.authorization_model_id = "01G5JAVJ41T49E9TT3SKVS7X1J" +# async with OpenFgaClient(configuration) as api_client: +# await 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=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# async def test_update_auth_model(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# mock_request.return_value = mock_response("", 204) +# configuration = self.configuration +# configuration.store_id = store_id +# configuration.authorization_model_id = "01G5JAVJ41T49E9TT3SKVS7X1J" +# async with OpenFgaClient(configuration) as api_client: +# api_client.set_authorization_model_id("01G5JAVJ41T49E9TT3SKVS7X2J") + +# await 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=[], +# timeout=None, +# ) diff --git a/test/configuration_test.py b/test/configuration_test.py index 50455065..30c1a06e 100644 --- a/test/configuration_test.py +++ b/test/configuration_test.py @@ -17,291 +17,111 @@ import pytest from openfga_sdk.configuration import Configuration, RetryParams -from openfga_sdk.exceptions import ApiValueError, FgaValidationException +from openfga_sdk.exceptions import FgaValidationException @pytest.fixture def configuration(): - return Configuration() + yield Configuration(api_url="https://api.fga.example") -@pytest.fixture -def servers(): - return [ - {"url": "https://example1.fga.example", "description": "Example 1"}, - {"url": "https://example2.fga.example", "description": "Example 2"}, - ] - - -@pytest.mark.parametrize( - "scheme,expected", - [ - ("https", True), - ("http", True), - ("ftp", False), - ], -) -def test_configuration_api_scheme(configuration, scheme, expected): - assert configuration.api_scheme == "https" - - if expected is True: - configuration.api_scheme = scheme - assert configuration.api_scheme == scheme - else: - with pytest.raises(FgaValidationException): - configuration.api_scheme = scheme - - -def test_configuration_api_host(configuration): - assert configuration.api_host is None - configuration.api_host = "fga.example" - assert configuration.api_host == "fga.example" - - with pytest.raises(ApiValueError): - configuration.api_host = "ftp://fga.example" - configuration.is_valid() - - -def test_configuration_api_url(configuration): - assert configuration.api_url is None - configuration.api_url = "https://fga.example/api" - assert configuration.api_url == "https://fga.example/api" - - -def test_configuration_store_id(configuration): - assert configuration.store_id is None - configuration.store_id = "store123" - assert configuration.store_id == "store123" - - -def test_configuration_credentials(configuration): - assert configuration.credentials is None - credentials_mock = Mock() - configuration.credentials = credentials_mock - assert configuration.credentials == credentials_mock +class TestConfigurationProperties: + def test_configuration_api_url(self, configuration: Configuration): + assert configuration.api_url == "https://api.fga.example" + with pytest.raises(FgaValidationException): + configuration.api_url = "https://fga.example/api" -def test_configuration_retry_params_default_values(configuration): - assert configuration.retry_params.max_retry == 3 - assert configuration.retry_params.min_wait_in_ms == 100 + configuration.api_url = "https://fga.example" + assert configuration.api_url == "https://fga.example" + def test_configuration_store_id(self, configuration: Configuration): + assert configuration.store_id is None -def test_configuration_retry_params_custom_values(configuration): - retry_params = RetryParams(max_retry=10, min_wait_in_ms=50) - configuration.retry_params = retry_params - assert configuration.retry_params.max_retry == 10 - assert configuration.retry_params.min_wait_in_ms == 50 + configuration.store_id = "01YCP46JKYM8FJCQ37NMBYHE5X" + assert configuration.store_id == "01YCP46JKYM8FJCQ37NMBYHE5X" + def test_configuration_credentials(self, configuration: Configuration): + assert configuration.credentials is None -def test_configuration_retry_params_invalid_max_retry(configuration): - with pytest.raises(FgaValidationException): - configuration.retry_params.max_retry = -1 + credentials_mock = Mock() + configuration.credentials = credentials_mock + assert configuration.credentials == credentials_mock -def test_configuration_retry_params_max_retry_greater_than_max(configuration): - with pytest.raises(FgaValidationException): - configuration.retry_params.max_retry = 20 +class TestConfigurationRetryParams: + def test_configuration_retry_params_default_values( + self, configuration: Configuration + ): + assert configuration.retry_params.max_retries == 3 # 5 + assert configuration.retry_params.min_wait_in_ms == 100 # 10 + def test_configuration_retry_params_custom_values( + self, configuration: Configuration + ): + retry_params = RetryParams(max_retries=10, min_wait_in_ms=50) + configuration.retry_params = retry_params + assert configuration.retry_params.max_retries == 10 + assert configuration.retry_params.min_wait_in_ms == 50 -def test_configuration_retry_params_invalid_min_wait_in_ms(configuration): - with pytest.raises(FgaValidationException): - configuration.retry_params.min_wait_in_ms = -1 + def test_configuration_retry_params_invalid_max_retry( + self, configuration: Configuration + ): + with pytest.raises(FgaValidationException): + configuration.retry_params.max_retries = -1 + def test_configuration_retry_params_max_retry_greater_than_max( + self, configuration: Configuration + ): + with pytest.raises(FgaValidationException): + configuration.retry_params.max_retries = 20 -class TestConfigurationSetDefaultAndGetDefaultCopy: - def test_configuration_set_default(self, configuration): - default_config = Configuration() - default_config.api_scheme = "https" - default_config.api_host = "fga.example" - default_config.store_id = "store123" - default_config.credentials = Mock( - _client_id="client123", - _client_secret="secret123", - _api_audience="audience123", - _api_issuer="issuer123", - _api_token="token123", - ) - default_config.retry_params = RetryParams(max_retry=10, min_wait_in_ms=50) - default_config.api_key = {"api_key1": "key1", "api_key2": "key2"} - default_config.api_key_prefix = {"api_key1": "Bearer", "api_key2": "Bearer"} - default_config.username = "user" - default_config.password = "pass" - default_config.discard_unknown_keys = True - default_config.server_index = 1 - default_config.server_variables = {"variable1": "value1", "variable2": "value2"} - default_config.server_operation_index = {"operation1": 1, "operation2": 2} - default_config.server_operation_variables = { - "operation1": {"var1": "val1"}, - "operation2": {"var2": "val2"}, - } - default_config.ssl_ca_cert = "/path/to/ca_cert.pem" - default_config.api_url = "https://fga.example/api" - default_config.timeout_millisec = 10000 - Configuration.set_default(default_config) - - assert Configuration._default.api_scheme == "https" - assert Configuration._default.api_host == "fga.example" - assert Configuration._default.store_id == "store123" - assert ( - isinstance(Configuration._default.credentials, Mock) - or Configuration._default.credentials is None - ) - if Configuration._default.credentials: - assert Configuration._default.credentials._client_id == "client123" - assert Configuration._default.credentials._client_secret == "secret123" - assert Configuration._default.credentials._api_audience == "audience123" - assert Configuration._default.credentials._api_issuer == "issuer123" - assert Configuration._default.credentials._api_token == "token123" - assert Configuration._default.retry_params.max_retry == 10 - assert Configuration._default.retry_params.min_wait_in_ms == 50 - assert Configuration._default.api_key == { - "api_key1": "key1", - "api_key2": "key2", - } - assert Configuration._default.api_key_prefix == { - "api_key1": "Bearer", - "api_key2": "Bearer", - } - assert Configuration._default.username == "user" - assert Configuration._default.password == "pass" - assert Configuration._default.discard_unknown_keys is True - assert Configuration._default.server_index == 1 - assert Configuration._default.server_variables == { - "variable1": "value1", - "variable2": "value2", - } - assert Configuration._default.server_operation_index == { - "operation1": 1, - "operation2": 2, - } - assert Configuration._default.server_operation_variables == { - "operation1": {"var1": "val1"}, - "operation2": {"var2": "val2"}, - } - assert Configuration._default.ssl_ca_cert == "/path/to/ca_cert.pem" - assert Configuration._default.api_url == "https://fga.example/api" - assert Configuration._default.timeout_millisec == 10000 - - def test_configuration_get_default_copy(self, configuration): - default_config = Configuration() - default_config.api_scheme = "https" - default_config.api_host = "fga.example" - default_config.store_id = "store123" - default_config.credentials = Mock( - _client_id="client123", - _client_secret="secret123", - _api_audience="audience123", - _api_issuer="issuer123", - _api_token="token123", - ) - default_config.retry_params = RetryParams(max_retry=10, min_wait_in_ms=50) - default_config.api_key = {"api_key1": "key1", "api_key2": "key2"} - default_config.api_key_prefix = {"api_key1": "Bearer", "api_key2": "Bearer"} - default_config.username = "user" - default_config.password = "pass" - default_config.discard_unknown_keys = True - default_config.server_index = 1 - default_config.server_variables = {"variable1": "value1", "variable2": "value2"} - default_config.server_operation_index = {"operation1": 1, "operation2": 2} - default_config.server_operation_variables = { - "operation1": {"var1": "val1"}, - "operation2": {"var2": "val2"}, - } - default_config.ssl_ca_cert = "/path/to/ca_cert.pem" - default_config.api_url = "https://fga.example/api" - default_config.timeout_millisec = 10000 - Configuration.set_default(default_config) - - copied_config = Configuration.get_default_copy() - - assert copied_config.api_scheme == "https" - assert copied_config.api_host == "fga.example" - assert copied_config.store_id == "store123" - assert ( - isinstance(copied_config.credentials, Mock) - or copied_config.credentials is None - ) - if copied_config.credentials: - assert copied_config.credentials._client_id == "client123" - assert copied_config.credentials._client_secret == "secret123" - assert copied_config.credentials._api_audience == "audience123" - assert copied_config.credentials._api_issuer == "issuer123" - assert copied_config.credentials._api_token == "token123" - assert Configuration._default.timeout_millisec == 10000 + def test_configuration_retry_params_invalid_min_wait_in_ms( + self, configuration: Configuration + ): + with pytest.raises(FgaValidationException): + configuration.retry_params.min_wait_in_ms = -1 class TestConfigurationValidityChecks: - def test_configuration_is_valid_missing_api_url(self, configuration): - with pytest.raises(FgaValidationException): - configuration.is_valid() - - def test_configuration_is_valid_invalid_store_id(self, configuration): + def test_configuration_is_valid_invalid_store_id( + self, configuration: Configuration + ): configuration.api_url = "https://fga.example" - configuration.store_id = "invalid_ulid" + with pytest.raises(FgaValidationException): - configuration.is_valid() + configuration.store_id = "invalid_ulid" - def test_configuration_is_valid(self, configuration): + def test_configuration_is_valid(self, configuration: Configuration): configuration.api_url = "https://fga.example" configuration.store_id = "01F9ZCDXDZBXK83WVMY1VZT23V" - assert configuration.is_valid() is None + assert configuration.is_valid() class TestConfigurationLogging: - def test_configuration_logger_format(self, configuration): + def test_configuration_logger_format(self, configuration: Configuration): assert configuration.logger_format == "%(asctime)s %(levelname)s %(message)s" configuration.logger_format = "%(levelname)s: %(message)s" assert configuration.logger_format == "%(levelname)s: %(message)s" - def test_configuration_debug(self, configuration): + def test_configuration_debug(self, configuration: Configuration): assert not configuration.debug configuration.debug = True assert configuration.debug configuration.debug = False assert not configuration.debug - def test_configuration_logger_file(self, configuration): + def test_configuration_logger_file(self, configuration: Configuration): assert configuration.logger_file is None configuration.logger_file = "debug.log" assert configuration.logger_file == "debug.log" -class TestConfigurationHostSettings: - def test_configuration_get_host_settings(self, configuration): - assert configuration.get_host_settings() == [ - {"url": "", "description": "No description provided"} - ] - - def test_configuration_get_host_from_settings(self, configuration, servers): - assert ( - configuration.get_host_from_settings(0, servers=servers) - == "https://example1.fga.example" - ) - assert ( - configuration.get_host_from_settings(1, servers=servers) - == "https://example2.fga.example" - ) - - def test_configuration_get_host_from_settings_invalid_value(self, configuration): - with pytest.raises(ValueError): - configuration.get_host_from_settings(999, variables={"var": "value"}) - - class TestConfigurationMiscellaneous: - def test_configuration_get_api_key_with_prefix(self, configuration): - configuration.api_key = {"api_key": "123"} - configuration.api_key_prefix = {"api_key": "Bearer"} - assert configuration.get_api_key_with_prefix("api_key") == "Bearer 123" - - def test_configuration_get_basic_auth_token(self, configuration): - configuration.username = "user" - configuration.password = "pass" - assert configuration.get_basic_auth_token() == "Basic dXNlcjpwYXNz" - - def test_configuration_auth_settings(self, configuration): + def test_configuration_auth_settings(self, configuration: Configuration): assert configuration.auth_settings() == {} - def test_configuration_to_debug_report(self, configuration): + def test_configuration_to_debug_report(self, configuration: Configuration): report = configuration.to_debug_report() assert "Python SDK Debug Report" in report assert "OS" in report @@ -309,12 +129,11 @@ def test_configuration_to_debug_report(self, configuration): assert "Version of the API" in report assert "SDK Package Version" in report - def test_configuration_deepcopy(self, configuration): + def test_configuration_deepcopy(self, configuration: Configuration): # Create a Configuration object with some values config = Configuration( - api_scheme="https", - api_host="fga.example", - store_id="store123", + api_url="https://fga.example", + store_id="01YCP46JKYM8FJCQ37NMBYHE5X", credentials=Mock( _client_id="client123", _client_secret="secret123", @@ -322,49 +141,26 @@ def test_configuration_deepcopy(self, configuration): _api_issuer="issuer123", _api_token="token123", ), - retry_params=RetryParams(max_retry=10, min_wait_in_ms=50), + retry_params=RetryParams(max_retries=10, min_wait_in_ms=50), api_key={"api_key1": "key1", "api_key2": "key2"}, api_key_prefix={"api_key1": "Bearer", "api_key2": "Bearer"}, username="user", password="pass", discard_unknown_keys=True, - server_index=1, - server_variables={"variable1": "value1", "variable2": "value2"}, - server_operation_index={"operation1": 1, "operation2": 2}, - server_operation_variables={ - "operation1": {"var1": "val1"}, - "operation2": {"var2": "val2"}, - }, ssl_ca_cert="/path/to/ca_cert.pem", - api_url="https://fga.example/api", - timeout_millisec=10000, + timeout=10000, ) # Perform deep copy copied_config = copy.deepcopy(config) # Verify all attributes of copied object are equal to original object - assert copied_config.api_scheme == config.api_scheme - assert copied_config.api_host == config.api_host + assert copied_config.api_url == config.api_url assert copied_config.store_id == config.store_id - assert ( - isinstance(Configuration._default.credentials, Mock) - or Configuration._default.credentials is None - ) - assert Configuration._default.retry_params.max_retry == 10 - assert Configuration._default.retry_params.min_wait_in_ms == 50 assert copied_config.api_key == config.api_key assert copied_config.api_key_prefix == config.api_key_prefix assert copied_config.username == config.username assert copied_config.password == config.password assert copied_config.discard_unknown_keys == config.discard_unknown_keys - assert copied_config.server_index == config.server_index - assert copied_config.server_variables == config.server_variables - assert copied_config.server_operation_index == config.server_operation_index - assert ( - copied_config.server_operation_variables - == config.server_operation_variables - ) assert copied_config.ssl_ca_cert == config.ssl_ca_cert - assert copied_config.api_url == config.api_url - assert copied_config.timeout_millisec == config.timeout_millisec + assert copied_config.timeout == config.timeout diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 00000000..f590c356 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,248 @@ +import random +import uuid + +from datetime import datetime, timedelta +from typing import Literal + +import pytest + + +def random_datetime_obj( + min_year: int = 1900, + max_year: int = datetime.now().year, + min_date: datetime | None = None, + max_date: datetime | None = None, +) -> datetime: + if min_date and max_date and min_date > max_date: + raise ValueError("`min_date` must be less than or equal to `max_date`.") + + if not min_date: + min_date = datetime(min_year, 1, 1, 0, 0, 0) + if not max_date: + max_date = datetime(max_year, 12, 31, 23, 59, 59) + + total_seconds = int((max_date - min_date).total_seconds()) + + random_seconds = random.randint(0, total_seconds) + + return min_date + timedelta(seconds=random_seconds) + + +def random_datetime( + min_year: int = 1900, + max_year: int = datetime.now().year, + min_date: datetime | None = None, + max_date: datetime | None = None, +) -> str: + result = random_datetime_obj(min_year, max_year, min_date, max_date) + return f"{result.strftime('%Y-%m-%dT%H:%M:%S')}.{result.microsecond // 1000:03d}Z" + + +def parse_iso_datetime(date_str): + if not date_str: + return None + try: + return datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%fZ") + except ValueError: + return datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%SZ") + + +@pytest.fixture +def mock_api_response_list_stores() -> list[ + dict[ + Literal["id", "name", "created_at", "updated_at", "deleted_at"], + str | datetime | None, + ] +]: + """ + Mock API response for list_stores endpoint. + + Returns: + list[dict[Literal["id", "name", "created_at", "updated_at", "deleted_at"], str | datetime | None]]: A list of mocked store responses. + """ + + def generate_store() -> dict[ + Literal["id", "name", "created_at", "updated_at", "deleted_at"], + str | datetime | None, + ]: + """Generate a single mock store.""" + return { + "id": str(uuid.uuid4()), + "name": f"Store {random.randint(1, 1000)}", + "created_at": random_datetime(), + "updated_at": random_datetime(), + "deleted_at": (random_datetime() if random.random() > 0.7 else None), + } + + num_stores = random.randint(1, 10) + return [generate_store() for _ in range(num_stores)] + + +@pytest.fixture +def mock_api_response_create_store() -> dict[ + Literal["id", "name", "created_at", "updated_at"], str | datetime +]: + """ + Mock API response for create_store endpoint. + + Returns: + dict[Literal["id", "name", "created_at", "updated_at"], str | datetime]: A mocked store response. + """ + + return { + "id": str(uuid.uuid4()), + "name": f"Store {random.randint(1, 1000)}", + "created_at": random_datetime(), + "updated_at": random_datetime(), + } + + +@pytest.fixture +def mock_api_response_get_store() -> dict[ + Literal["id", "name", "created_at", "updated_at"], str | datetime +]: + """ + Mock API response for get_store endpoint. + + Returns: + dict[Literal["id", "name", "created_at", "updated_at"], str | datetime]: A mocked store response. + """ + + return { + "id": str(uuid.uuid4()), + "name": f"Store {random.randint(1, 1000)}", + "created_at": random_datetime(), + "updated_at": random_datetime(), + "deleted_at": (random_datetime() if random.random() > 0.7 else None), + } + + +@pytest.fixture +def mock_api_response_read_authorization_model() -> dict[ + Literal["conditions", "id", "schema_version", "type_definitions"], str | None +]: + """ + Mock API response for read_authorization_model endpoint. + + Returns: + dict[Literal["conditions", "id", "schema_version", "type_definitions"], str | None]: A mocked model response. + """ + + return { + "conditions": None, + "id": str(uuid.uuid4()), + "schema_version": "1.0", + "type_definitions": None, + } + + +@pytest.fixture +def mock_api_response_read_authorization_models() -> list[ + dict[Literal["conditions", "id", "schema_version", "type_definitions"], str | None] +]: + """ + Mock API response for read_authorization_models endpoint. + + Returns: + list[dict[Literal["conditions", "id", "schema_version", "type_definitions"], str | None]]: A list of mocked model responses. + """ + + def generate_model() -> dict[ + Literal["conditions", "id", "schema_version", "type_definitions"], str | None + ]: + return { + "conditions": None, + "id": str(uuid.uuid4()), + "schema_version": "1.0", + "type_definitions": None, + } + + num_models = random.randint(1, 10) + return [generate_model() for _ in range(num_models)] + + +@pytest.fixture +def mock_api_response_write_authorization_model() -> dict[ + Literal["authorization_model_id"], str +]: + """ + Mock API response for write_authorization_model endpoint. + + Returns: + dict[Literal["authorization_model_id"], str]: A mocked response with authorization model ID. + """ + + return { + "authorization_model_id": str(uuid.uuid4()), + } + + +@pytest.fixture +def mock_api_response_read_changes() -> list[ + dict[ + Literal["timestamp", "tuple_key", "operation"], + str + | datetime + | dict[Literal["object", "relation", "user", "conditions"], str | None], + ] +]: + """ + Mock API response for read_changes endpoint. + + Returns: + list[dict[Literal["timestamp", "tuple_key", "operation"], str | datetime | dict[Literal["object", "relation", "user", "conditions"], str | None]]]: A list of mocked changes. + """ + + def generate_change() -> dict[ + Literal["timestamp", "tuple_key", "operation"], + str + | datetime + | dict[Literal["object", "relation", "user", "conditions"], str | None], + ]: + return { + "tuple_key": { + "object": f"object_{random.randint(1, 1000)}", + "relation": f"relation_{random.randint(1, 1000)}", + "user": f"user_{random.randint(1, 1000)}", + "condition": None, + }, + "operation": random.choice( + ["TUPLE_OPERATION_WRITE", "TUPLE_OPERATION_DELETE"] + ), + "timestamp": random_datetime(), + } + + num_changes = random.randint(1, 10) + return [generate_change() for _ in range(num_changes)] + + +@pytest.fixture +def mock_api_response_read() -> list[ + dict[ + Literal["timestamp", "tuple_key"], + str | datetime | dict[Literal["object", "relation", "user"], str], + ] +]: + """ + Mock API response for read endpoint. + + Returns: + list[dict[Literal["timestamp", "tuple_key"], str | datetime | dict[Literal["object", "relation", "user"], str]]]: A list of mocked results. + """ + + def generate_change() -> dict[ + Literal["timestamp", "tuple_key"], + str | datetime | dict[Literal["object", "relation", "user"], str], + ]: + return { + "key": { + "object": f"object_{random.randint(1, 1000)}", + "relation": f"relation_{random.randint(1, 1000)}", + "user": f"user_{random.randint(1, 1000)}", + "condition": None, + }, + "timestamp": random_datetime(), + } + + num_changes = random.randint(1, 10) + return [generate_change() for _ in range(num_changes)] diff --git a/test/credentials_test.py b/test/credentials_test.py index 238608f3..c878a3c3 100644 --- a/test/credentials_test.py +++ b/test/credentials_test.py @@ -33,14 +33,14 @@ def test_bad_method(self): """ credential = Credentials("bad") with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_method_none(self): """ Test credential with method none is valid """ credential = Credentials("none") - credential.validate_credentials_config() + credential.validate() self.assertEqual(credential.method, "none") def test_method_default(self): @@ -48,8 +48,8 @@ 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") + credential.validate() + self.assertEqual(credential.method, None) def test_configuration_api_token(self): """ @@ -59,7 +59,7 @@ def test_configuration_api_token(self): method="api_token", configuration=CredentialConfiguration(api_token="ABCDEFG"), ) - credential.validate_credentials_config() + credential.validate() self.assertEqual(credential.method, "api_token") self.assertEqual(credential.configuration.api_token, "ABCDEFG") @@ -69,7 +69,7 @@ def test_configuration_api_token_missing_configuration(self): """ credential = Credentials(method="api_token") with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_configuration_api_token_missing_token(self): """ @@ -79,7 +79,7 @@ def test_configuration_api_token_missing_token(self): method="api_token", configuration=CredentialConfiguration() ) with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_configuration_api_token_empty_token(self): """ @@ -89,7 +89,7 @@ def test_configuration_api_token_empty_token(self): method="api_token", configuration=CredentialConfiguration(api_token="") ) with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_configuration_client_credentials(self): """ @@ -104,7 +104,7 @@ def test_configuration_client_credentials(self): api_audience="myaudience", ), ) - credential.validate_credentials_config() + credential.validate() self.assertEqual(credential.method, "client_credentials") def test_configuration_client_credentials_missing_config(self): @@ -113,7 +113,7 @@ def test_configuration_client_credentials_missing_config(self): """ credential = Credentials(method="client_credentials") with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_configuration_client_credentials_missing_client_id(self): """ @@ -128,7 +128,7 @@ def test_configuration_client_credentials_missing_client_id(self): ), ) with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_configuration_client_credentials_missing_client_secret(self): """ @@ -143,7 +143,7 @@ def test_configuration_client_credentials_missing_client_secret(self): ), ) with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_configuration_client_credentials_missing_api_issuer(self): """ @@ -158,7 +158,7 @@ def test_configuration_client_credentials_missing_api_issuer(self): ), ) with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() def test_configuration_client_credentials_missing_api_audience(self): """ @@ -173,7 +173,7 @@ def test_configuration_client_credentials_missing_api_audience(self): ), ) with self.assertRaises(openfga_sdk.ApiValueError): - credential.validate_credentials_config() + credential.validate() class TestCredentialsIssuer(IsolatedAsyncioTestCase): @@ -210,9 +210,7 @@ def test_valid_issuer_http(self): def test_invalid_issuer_no_scheme(self): # Test an issuer URL without a scheme - self.configuration.api_issuer = ( - "https://issuer.fga.example:8080/some_endpoint " - ) + self.configuration.api_issuer = "https://issuer.fga.example:8080/some_endpoint " result = self.credentials._parse_issuer(self.configuration.api_issuer) self.assertEqual(result, "https://issuer.fga.example:8080/some_endpoint") diff --git a/test/endpoints/async/conftest.py b/test/endpoints/async/conftest.py new file mode 100644 index 00000000..28919136 --- /dev/null +++ b/test/endpoints/async/conftest.py @@ -0,0 +1,208 @@ +import uuid + +from unittest.mock import MagicMock + +import pytest +import pytest_asyncio + +from openfga_sdk.api.open_fga_api import OpenFgaApi +from openfga_sdk.client import OpenFgaClient +from openfga_sdk.factory import Factory +from openfga_sdk.models.authorization_model import AuthorizationModel +from openfga_sdk.models.create_store_response import CreateStoreResponse +from openfga_sdk.models.get_store_response import GetStoreResponse +from openfga_sdk.models.list_stores_response import ListStoresResponse +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.write_authorization_model_response import ( + WriteAuthorizationModelResponse, +) +from openfga_sdk.protocols import ConfigurationProtocol, FactoryProtocol +from test.conftest import parse_iso_datetime + + +@pytest.fixture +def mock_configuration_api_url() -> str: + return "https://api.fga.example" + + +@pytest.fixture +def mock_configuration(mock_configuration_api_url: str) -> ConfigurationProtocol: + configuration = MagicMock() + configuration.api_url = mock_configuration_api_url + + return configuration + + +@pytest_asyncio.fixture +async def factory( + mock_configuration: ConfigurationProtocol, +): + yield Factory( + configuration=mock_configuration, + ) + + +@pytest_asyncio.fixture +async def client( + mock_configuration: ConfigurationProtocol, + factory: FactoryProtocol, +): + client = OpenFgaClient( + configuration=mock_configuration, + factory=factory, + ) + yield client + await client.close() + + +@pytest_asyncio.fixture +async def api( + mock_configuration: ConfigurationProtocol, + factory: FactoryProtocol, +): + api = OpenFgaApi( + configuration=mock_configuration, + factory=factory, + ) + yield api + await api.close() + + +@pytest.fixture +def mock_api_response_list_stores_deserialized( + mock_api_response_list_stores, +) -> ListStoresResponse: + return ListStoresResponse( + stores=list( + Store( + **{ + **store, + "created_at": parse_iso_datetime(store.get("created_at")), + "updated_at": parse_iso_datetime(store.get("updated_at")), + "deleted_at": parse_iso_datetime(store.get("deleted_at")), + } + ) + for store in mock_api_response_list_stores + ), + continuation_token=None, + ) + + +@pytest.fixture +def mock_api_response_create_store_deserialized( + mock_api_response_create_store, +) -> CreateStoreResponse: + return CreateStoreResponse( + **{ + **mock_api_response_create_store, + "created_at": parse_iso_datetime( + mock_api_response_create_store["created_at"] + ), + "updated_at": parse_iso_datetime( + mock_api_response_create_store["updated_at"] + ), + } + ) + + +@pytest.fixture +def mock_api_response_get_store_deserialized( + mock_api_response_get_store, +) -> GetStoreResponse: + return GetStoreResponse( + **{ + **mock_api_response_get_store, + "created_at": parse_iso_datetime(mock_api_response_get_store["created_at"]), + "updated_at": parse_iso_datetime(mock_api_response_get_store["updated_at"]), + "deleted_at": parse_iso_datetime(mock_api_response_get_store["deleted_at"]), + } + ) + + +@pytest.fixture +def mock_api_response_read_authorization_model_deserialized( + mock_api_response_read_authorization_model, +) -> ReadAuthorizationModelResponse: + return ReadAuthorizationModelResponse( + authorization_model=mock_api_response_read_authorization_model + ) + + +@pytest.fixture +def mock_api_response_read_authorization_models_deserialized( + mock_api_response_read_authorization_models, +) -> ReadAuthorizationModelsResponse: + return ReadAuthorizationModelsResponse( + authorization_models=list( + AuthorizationModel( + **{ + **model, + } + ) + for model in mock_api_response_read_authorization_models + ), + ) + + +@pytest.fixture +def mock_api_continuation_token() -> str: + return str(uuid.uuid4()) + + +@pytest.fixture +def mock_api_response_write_authorization_model_deserialized( + mock_api_response_write_authorization_model, +) -> WriteAuthorizationModelResponse: + return WriteAuthorizationModelResponse( + authorization_model_id=mock_api_response_write_authorization_model[ + "authorization_model_id" + ] + ) + + +@pytest.fixture +def mock_api_response_read_changes_deserialized( + mock_api_response_read_changes, + mock_api_continuation_token, +) -> ReadChangesResponse: + return ReadChangesResponse( + changes=list( + TupleChange( + **{ + **change, + "timestamp": parse_iso_datetime(change["timestamp"]), + } + ) + for change in mock_api_response_read_changes + ), + continuation_token=mock_api_continuation_token, + ) + + +@pytest.fixture +def mock_api_response_read_deserialized( + mock_api_response_read, + mock_api_continuation_token, +) -> ReadChangesResponse: + return ReadResponse( + tuples=list( + Tuple( + **{ + **tuple, + "timestamp": parse_iso_datetime(tuple["timestamp"]), + } + ) + for tuple in mock_api_response_read + ), + continuation_token=mock_api_continuation_token, + ) diff --git a/test/endpoints/async/test_create_store_api.py b/test/endpoints/async/test_create_store_api.py new file mode 100644 index 00000000..32cc9a5b --- /dev/null +++ b/test/endpoints/async/test_create_store_api.py @@ -0,0 +1,115 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 json + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.models.create_store_request import CreateStoreRequest +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_create_store, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = mock_api_response_create_store + + return RestClientResponse( + response=mock_response, + data=json.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_create_store_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_create_store_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiCreateStoreEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.create_store. + """ + + async def test_create_store_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the create_store method issues a request to ApiClient. + """ + create_store_body = CreateStoreRequest() + + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.create_store(create_store_body) + + api_request.assert_called_once() + + async def test_create_store_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + mock_api_response_create_store, + api_client_request_conditions, + ): + """ + Test that the create_store method returns an ApiClientResponse instance containing the expected response. + """ + create_store_body = CreateStoreRequest( + name=mock_api_response_create_store["name"], + ) + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.create_store(create_store_body) + + assert response == expected_response diff --git a/test/endpoints/async/test_create_store_client.py b/test/endpoints/async/test_create_store_client.py new file mode 100644 index 00000000..4463743d --- /dev/null +++ b/test/endpoints/async/test_create_store_client.py @@ -0,0 +1,107 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ( + CreateStoreRequestOptions, +) +from openfga_sdk.models.create_store_request import CreateStoreRequest +from openfga_sdk.models.create_store_response import CreateStoreResponse +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response( + mock_api_response_create_store_deserialized: CreateStoreResponse, +) -> ApiResponseProtocol: + return ApiResponse( + deserialized=mock_api_response_create_store_deserialized, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "create_store", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientCreateStoreEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.create_store + """ + + async def test_create_store_issues_request( + self, + client: OpenFgaClientProtocol, + mock_api_response_create_store, + api_request_conditions, + ): + """ + Test that the create_store method issues a request to OpenFgaApi. + """ + create_store_body = CreateStoreRequest() + + with patch.object(client.api, **api_request_conditions) as api_request: + await client.create_store(create_store_body) + + api_request.assert_called_once() + + async def test_create_store_returns_expected_response( + self, + client: OpenFgaClientProtocol, + mock_api_response_create_store_deserialized: CreateStoreResponse, + mock_api_response_create_store, + api_request_conditions, + ): + """ + Test that the create_store method returns a CreateStoreResponse instance containing the expected response. + """ + create_store_body = CreateStoreRequest( + name=mock_api_response_create_store["name"], + ) + + with patch.object(client.api, **api_request_conditions): + response = await client.create_store(create_store_body) + + assert response == mock_api_response_create_store_deserialized + + async def test_create_store_returns_full_response( + self, + client: OpenFgaClientProtocol, + mock_api_response_create_store, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the create_store method returns an ApiResponse instance containing the expected response. + """ + options = CreateStoreRequestOptions( + return_response=True, + ) + + create_store_body = CreateStoreRequest( + name=mock_api_response_create_store["name"], + ) + + with patch.object(client.api, **api_request_conditions): + response = await client.create_store(create_store_body, options) + + assert response == api_request_response diff --git a/test/endpoints/async/test_delete_store_api.py b/test/endpoints/async/test_delete_store_api.py new file mode 100644 index 00000000..a08ae40d --- /dev/null +++ b/test/endpoints/async/test_delete_store_api.py @@ -0,0 +1,107 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 uuid + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import DeleteStoreRequestOptions +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response() -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 204 + + return RestClientResponse( + response=mock_response, + data="", + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = None + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiDeleteStoreEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.delete_store. + """ + + async def test_delete_store_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the delete_store method issues a request to ApiClient. + """ + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.delete_store() + + api_request.assert_called_once() + + async def test_delete_store_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the create_store method returns an ApiClientResponse instance containing the expected response. + """ + delete_store_body = DeleteStoreRequestOptions( + store_id=str(uuid.uuid4()), + ) + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.delete_store(delete_store_body) + + assert response == expected_response diff --git a/test/endpoints/async/test_delete_store_client.py b/test/endpoints/async/test_delete_store_client.py new file mode 100644 index 00000000..64b406ed --- /dev/null +++ b/test/endpoints/async/test_delete_store_client.py @@ -0,0 +1,89 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 uuid + +from unittest.mock import AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import DeleteStoreRequestOptions +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response() -> ApiResponseProtocol: + return ApiResponse( + deserialized=None, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "delete_store", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientDeleteStoreEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.delete_store + """ + + async def test_delete_store_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the delete_store method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.delete_store(str(uuid.uuid4())) + + api_request.assert_called_once() + + async def test_delete_store_returns_expected_response( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the delete_store method returns the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.delete_store(str(uuid.uuid4())) + + assert response is None + + async def test_delete_store_returns_full_response( + self, + client: OpenFgaClientProtocol, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the delete_store method returns an ApiResponse instance containing the expected response. + """ + options = DeleteStoreRequestOptions( + return_response=True, + ) + + with patch.object(client.api, **api_request_conditions): + response = await client.delete_store(str(uuid.uuid4()), options) + + assert response == api_request_response diff --git a/test/endpoints/async/test_get_store_api.py b/test/endpoints/async/test_get_store_api.py new file mode 100644 index 00000000..73f89cc4 --- /dev/null +++ b/test/endpoints/async/test_get_store_api.py @@ -0,0 +1,118 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 json + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import GetStoreRequestOptions +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_get_store, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = mock_api_response_get_store + + return RestClientResponse( + response=mock_response, + data=json.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_get_store_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_get_store_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiGetStoreEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.get_store. + """ + + async def test_get_store_issues_request( + self, + api: OpenFgaApiProtocol, + mock_api_response_get_store, + api_client_request_conditions, + ): + """ + Test that the get_store method issues a request to ApiClient. + """ + options = GetStoreRequestOptions( + store_id=mock_api_response_get_store["id"], + ) + + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.get_store(options) + + api_request.assert_called_once() + + async def test_get_store_returns_expected_response( + self, + api: OpenFgaApiProtocol, + mock_api_response_get_store, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the get_store method returns an ApiClientResponse instance containing the expected response. + """ + options = GetStoreRequestOptions( + store_id=mock_api_response_get_store["id"], + ) + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.get_store(options) + + assert response == expected_response diff --git a/test/endpoints/async/test_get_store_client.py b/test/endpoints/async/test_get_store_client.py new file mode 100644 index 00000000..8fda7c5b --- /dev/null +++ b/test/endpoints/async/test_get_store_client.py @@ -0,0 +1,97 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import GetStoreRequestOptions +from openfga_sdk.models.get_store_response import GetStoreResponse +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response( + mock_api_response_get_store_deserialized: GetStoreResponse, +) -> ApiResponseProtocol: + return ApiResponse( + deserialized=mock_api_response_get_store_deserialized, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "get_store", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientGetStoreEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.get_store + """ + + async def test_create_get_issues_request( + self, + client: OpenFgaClientProtocol, + mock_api_response_get_store, + api_request_conditions, + ): + """ + Test that the get_store method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.get_store(mock_api_response_get_store["id"]) + + api_request.assert_called_once() + + async def test_get_store_returns_expected_response( + self, + client: OpenFgaClientProtocol, + mock_api_response_get_store_deserialized: GetStoreResponse, + mock_api_response_get_store, + api_request_conditions, + ): + """ + Test that the get_store method returns a GetStoreResponse instance containing the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.get_store(mock_api_response_get_store["id"]) + + assert response == mock_api_response_get_store_deserialized + + async def test_get_store_returns_full_response( + self, + client: OpenFgaClientProtocol, + mock_api_response_get_store, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the get_store method returns an ApiResponse instance containing the expected response. + """ + options = GetStoreRequestOptions( + store_id=mock_api_response_get_store["id"], + return_response=True, + ) + + with patch.object(client.api, **api_request_conditions): + response = await client.get_store( + mock_api_response_get_store["id"], options + ) + + assert response == api_request_response diff --git a/test/endpoints/async/test_list_stores_api.py b/test/endpoints/async/test_list_stores_api.py new file mode 100644 index 00000000..74452db0 --- /dev/null +++ b/test/endpoints/async/test_list_stores_api.py @@ -0,0 +1,110 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 json + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_list_stores, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = { + "stores": mock_api_response_list_stores, + "continuation_token": None, + } + + return RestClientResponse( + response=mock_response, + data=json.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_list_stores_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_list_stores_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiListStoresEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.list_stores. + """ + + async def test_list_stores_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the list_stores method issues a request to ApiClient. + """ + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.list_stores() + + api_request.assert_called_once() + + async def test_list_stores_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the list_stores method returns an ApiClientResponse instance containing the expected response. + """ + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.list_stores() + + assert response == expected_response diff --git a/test/endpoints/async/test_list_stores_client.py b/test/endpoints/async/test_list_stores_client.py new file mode 100644 index 00000000..9ab67a9d --- /dev/null +++ b/test/endpoints/async/test_list_stores_client.py @@ -0,0 +1,91 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ListStoresRequestOptions +from openfga_sdk.models.list_stores_response import ListStoresResponse +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response( + mock_api_response_list_stores_deserialized: ListStoresResponse, +) -> ApiResponseProtocol: + return ApiResponse( + deserialized=mock_api_response_list_stores_deserialized, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "list_stores", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientListStoresEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.list_stores + """ + + async def test_list_stores_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the list_stores method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.list_stores() + + api_request.assert_called_once() + + async def test_list_stores_returns_expected_response( + self, + client: OpenFgaClientProtocol, + mock_api_response_list_stores_deserialized: ListStoresResponse, + api_request_conditions, + ): + """ + Test that the list_stores method returns a ListStoresResponse instance containing the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.list_stores() + + assert response == mock_api_response_list_stores_deserialized + + async def test_list_stores_returns_full_response( + self, + client: OpenFgaClientProtocol, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the list_stores method returns an ApiResponse instance containing the expected response. + """ + options = ListStoresRequestOptions( + return_response=True, + ) + + with patch.object(client.api, **api_request_conditions): + response = await client.list_stores(options) + + assert response == api_request_response diff --git a/test/endpoints/async/test_read_api.py b/test/endpoints/async/test_read_api.py new file mode 100644 index 00000000..5d0d531e --- /dev/null +++ b/test/endpoints/async/test_read_api.py @@ -0,0 +1,121 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 uuid + +from unittest.mock import AsyncMock, MagicMock, patch + +import orjson +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ReadRequestOptions +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_read, + mock_api_continuation_token, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = { + "tuples": mock_api_response_read, + "continuation_token": mock_api_continuation_token, + } + + return RestClientResponse( + response=mock_response, + data=orjson.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_read_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_read_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiReadEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.read. + """ + + async def test_read_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the read method issues a request to ApiClient. + """ + options = ReadRequestOptions( + store_id=str(uuid.uuid4()), + ) + + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.read(options) + + api_request.assert_called_once() + + async def test_read_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the read method returns an ApiClientResponse instance containing the expected response. + """ + options = ReadRequestOptions( + store_id=str(uuid.uuid4()), + ) + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.read(options) + + assert response == expected_response diff --git a/test/endpoints/async/test_read_authorization_model_api.py b/test/endpoints/async/test_read_authorization_model_api.py new file mode 100644 index 00000000..7cefe181 --- /dev/null +++ b/test/endpoints/async/test_read_authorization_model_api.py @@ -0,0 +1,109 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 uuid + +from unittest.mock import AsyncMock, MagicMock, patch + +import orjson +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_read_authorization_model, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = {"authorization_model": mock_api_response_read_authorization_model} + + return RestClientResponse( + response=mock_response, + data=orjson.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_read_authorization_model_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_read_authorization_model_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiReadAuthorizationModelEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.read_authorization_model. + """ + + async def test_read_authorization_model_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the read_authorization_model method issues a request to ApiClient. + """ + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.read_authorization_model(str(uuid.uuid4())) + + api_request.assert_called_once() + + async def test_read_authorization_model_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the read_authorization_model method returns an ApiClientResponse instance containing the expected response. + """ + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.read_authorization_model(str(uuid.uuid4())) + + assert response == expected_response diff --git a/test/endpoints/async/test_read_authorization_model_client.py b/test/endpoints/async/test_read_authorization_model_client.py new file mode 100644 index 00000000..565e5315 --- /dev/null +++ b/test/endpoints/async/test_read_authorization_model_client.py @@ -0,0 +1,93 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 uuid + +from unittest.mock import AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ( + ReadAuthorizationModelRequestOptions, +) +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response() -> ApiResponseProtocol: + return ApiResponse( + deserialized=None, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "read_authorization_model", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientReadAuthorizationModelEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.read_authorization_model + """ + + async def test_read_authorization_model_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read_authorization_model method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.read_authorization_model(str(uuid.uuid4())) + + api_request.assert_called_once() + + async def test_read_authorization_model_returns_expected_response( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read_authorization_model method returns the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.read_authorization_model(str(uuid.uuid4())) + + assert response is None + + async def test_read_authorization_model_returns_full_response( + self, + client: OpenFgaClientProtocol, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the read_authorization_model method returns an ApiResponse instance containing the expected response. + """ + + with patch.object(client.api, **api_request_conditions): + response = await client.read_authorization_model( + str(uuid.uuid4()), + ReadAuthorizationModelRequestOptions( + return_response=True, + ), + ) + + assert response == api_request_response diff --git a/test/endpoints/async/test_read_authorization_models_api.py b/test/endpoints/async/test_read_authorization_models_api.py new file mode 100644 index 00000000..87a499ea --- /dev/null +++ b/test/endpoints/async/test_read_authorization_models_api.py @@ -0,0 +1,110 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, MagicMock, patch + +import orjson +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_read_authorization_models, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = { + "authorization_models": mock_api_response_read_authorization_models, + "continuation_token": None, + } + + return RestClientResponse( + response=mock_response, + data=orjson.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_read_authorization_models_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_read_authorization_models_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiReadAuthorizationModelsEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.read_authorization_models. + """ + + async def test_read_authorization_models_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the read_authorization_models method issues a request to ApiClient. + """ + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.read_authorization_models() + + api_request.assert_called_once() + + async def test_read_authorization_models_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the read_authorization_models method returns an ApiClientResponse instance containing the expected response. + """ + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.read_authorization_models() + + assert response == expected_response diff --git a/test/endpoints/async/test_read_authorization_models_client.py b/test/endpoints/async/test_read_authorization_models_client.py new file mode 100644 index 00000000..8b0ce9f9 --- /dev/null +++ b/test/endpoints/async/test_read_authorization_models_client.py @@ -0,0 +1,90 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ( + ReadAuthorizationModelsRequestOptions, +) +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response() -> ApiResponseProtocol: + return ApiResponse( + deserialized=None, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "read_authorization_models", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientReadAuthorizationModelsEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.read_authorization_models + """ + + async def test_read_authorization_models_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read_authorization_models method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.read_authorization_models() + + api_request.assert_called_once() + + async def test_read_authorization_models_returns_expected_response( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read_authorization_models method returns the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.read_authorization_models() + + assert response is None + + async def test_read_authorization_models_returns_full_response( + self, + client: OpenFgaClientProtocol, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the read_authorization_models method returns an ApiResponse instance containing the expected response. + """ + + with patch.object(client.api, **api_request_conditions): + response = await client.read_authorization_models( + options=ReadAuthorizationModelsRequestOptions( + return_response=True, + ) + ) + + assert response == api_request_response diff --git a/test/endpoints/async/test_read_changes_api.py b/test/endpoints/async/test_read_changes_api.py new file mode 100644 index 00000000..e6c492bf --- /dev/null +++ b/test/endpoints/async/test_read_changes_api.py @@ -0,0 +1,121 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 uuid + +from unittest.mock import AsyncMock, MagicMock, patch + +import orjson +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ReadChangesRequestOptions +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_read_changes, + mock_api_continuation_token, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = { + "changes": mock_api_response_read_changes, + "continuation_token": mock_api_continuation_token, + } + + return RestClientResponse( + response=mock_response, + data=orjson.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_read_changes_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_read_changes_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiReadChangesEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.read_changes. + """ + + async def test_read_changes_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the read_changes method issues a request to ApiClient. + """ + options = ReadChangesRequestOptions( + store_id=str(uuid.uuid4()), + ) + + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.read_changes(options) + + api_request.assert_called_once() + + async def test_read_changes_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the read_changes method returns an ApiClientResponse instance containing the expected response. + """ + options = ReadChangesRequestOptions( + store_id=str(uuid.uuid4()), + ) + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.read_changes(options) + + assert response == expected_response diff --git a/test/endpoints/async/test_read_changes_client.py b/test/endpoints/async/test_read_changes_client.py new file mode 100644 index 00000000..25ee0347 --- /dev/null +++ b/test/endpoints/async/test_read_changes_client.py @@ -0,0 +1,90 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.client.models.read_changes_request import ClientReadChangesRequest +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ReadChangesRequestOptions +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response() -> ApiResponseProtocol: + return ApiResponse( + deserialized=None, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "read_changes", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientReadAuthorizationModelsEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.read_changes + """ + + async def test_read_changes_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read_changes method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.read_changes(ClientReadChangesRequest()) + + api_request.assert_called_once() + + async def test_read_changes_returns_expected_response( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read_changes method returns the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.read_changes(ClientReadChangesRequest()) + + assert response is None + + async def test_read_changes_returns_full_response( + self, + client: OpenFgaClientProtocol, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the read_changes method returns an ApiResponse instance containing the expected response. + """ + + with patch.object(client.api, **api_request_conditions): + response = await client.read_changes( + ClientReadChangesRequest(), + options=ReadChangesRequestOptions( + return_response=True, + ), + ) + + assert response == api_request_response diff --git a/test/endpoints/async/test_read_client.py b/test/endpoints/async/test_read_client.py new file mode 100644 index 00000000..ed6a7da3 --- /dev/null +++ b/test/endpoints/async/test_read_client.py @@ -0,0 +1,90 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ReadRequestOptions +from openfga_sdk.models import ReadRequestTupleKey +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response() -> ApiResponseProtocol: + return ApiResponse( + deserialized=None, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "read", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientReadAuthorizationModelsEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.read + """ + + async def test_read_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.read(ReadRequestTupleKey()) + + api_request.assert_called_once() + + async def test_read_returns_expected_response( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the read method returns the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.read(ReadRequestTupleKey()) + + assert response is None + + async def test_read_returns_full_response( + self, + client: OpenFgaClientProtocol, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the read method returns an ApiResponse instance containing the expected response. + """ + + with patch.object(client.api, **api_request_conditions): + response = await client.read( + ReadRequestTupleKey(), + options=ReadRequestOptions( + return_response=True, + ), + ) + + assert response == api_request_response diff --git a/test/endpoints/async/test_write_api.py b/test/endpoints/async/test_write_api.py new file mode 100644 index 00000000..38b15717 --- /dev/null +++ b/test/endpoints/async/test_write_api.py @@ -0,0 +1,113 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 uuid + +from unittest.mock import AsyncMock, MagicMock, patch + +import orjson +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import WriteRequestOptions +from openfga_sdk.models.write_request import WriteRequest +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response() -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + return RestClientResponse( + response=mock_response, + data="", + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = None + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiWriteEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.write. + """ + + async def test_write_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the write method issues a request to ApiClient. + """ + options = WriteRequestOptions( + store_id=str(uuid.uuid4()), + ) + + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.write(WriteRequest(), options) + + api_request.assert_called_once() + + async def test_write_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the write method returns an ApiClientResponse instance containing the expected response. + """ + options = WriteRequestOptions( + store_id=str(uuid.uuid4()), + ) + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.write(WriteRequest(), options) + + assert response == expected_response diff --git a/test/endpoints/async/test_write_authorization_model_api.py b/test/endpoints/async/test_write_authorization_model_api.py new file mode 100644 index 00000000..3f3c7c9d --- /dev/null +++ b/test/endpoints/async/test_write_authorization_model_api.py @@ -0,0 +1,112 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, MagicMock, patch + +import orjson +import pytest + +from openfga_sdk.common.api_client import ApiClientResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.models.write_authorization_model_request import ( + WriteAuthorizationModelRequest, +) +from openfga_sdk.protocols import ( + ApiClientResponseProtocol, + ApiResponseProtocol, + OpenFgaApiProtocol, + RestClientResponseProtocol, +) +from openfga_sdk.rest import RestClientResponse + + +@pytest.fixture +def rest_client_response( + mock_api_response_write_authorization_model, +) -> RestClientResponseProtocol: + mock_response = MagicMock() + mock_response.status = 200 + + api_response = mock_api_response_write_authorization_model + + return RestClientResponse( + response=mock_response, + data=orjson.dumps(api_response), + status=200, + reason="OK", + ) + + +@pytest.fixture +def api_client_response( + rest_client_response, +) -> ApiClientResponseProtocol: + return ApiClientResponse(response=rest_client_response) + + +@pytest.fixture +def expected_response( + api_client_response, + mock_api_response_write_authorization_model_deserialized, +) -> ApiResponseProtocol: + response: ApiResponseProtocol = ApiResponse() | api_client_response + response.deserialized = mock_api_response_write_authorization_model_deserialized + return response + + +@pytest.fixture +def api_client_request_conditions( + api_client_response: ApiClientResponseProtocol, +): + return { + "attribute": "request", + "new": AsyncMock(return_value=api_client_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaApiWriteAuthorizationModelEndpoint: + """ + @covers openfga_sdk.api.open_fga_api.OpenFgaApi.write_authorization_model. + """ + + async def test_write_authorization_model_issues_request( + self, + api: OpenFgaApiProtocol, + api_client_request_conditions, + ): + """ + Test that the write_authorization_model method issues a request to ApiClient. + """ + with patch.object( + api.api_client, **api_client_request_conditions + ) as api_request: + await api.write_authorization_model(WriteAuthorizationModelRequest()) + + api_request.assert_called_once() + + async def test_write_authorization_model_returns_expected_response( + self, + api: OpenFgaApiProtocol, + expected_response: ApiResponseProtocol, + api_client_request_conditions, + ): + """ + Test that the write_authorization_model method returns an ApiClientResponse instance containing the expected response. + """ + + with patch.object(api.api_client, **api_client_request_conditions): + response = await api.write_authorization_model( + WriteAuthorizationModelRequest() + ) + + assert response == expected_response diff --git a/test/endpoints/async/test_write_authorization_model_client.py b/test/endpoints/async/test_write_authorization_model_client.py new file mode 100644 index 00000000..9c363fd2 --- /dev/null +++ b/test/endpoints/async/test_write_authorization_model_client.py @@ -0,0 +1,96 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import ( + WriteAuthorizationModelRequestOptions, +) +from openfga_sdk.models.write_authorization_model_request import ( + WriteAuthorizationModelRequest, +) +from openfga_sdk.protocols import ApiResponseProtocol, OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response() -> ApiResponseProtocol: + return ApiResponse( + deserialized=None, + ) + + +@pytest.fixture +def api_request_conditions( + api_request_response: ApiResponse, +): + return { + "attribute": "write_authorization_model", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientWriteAuthorizationModelEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.write_authorization_model + """ + + async def test_write_authorization_model_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the write_authorization_model method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.write_authorization_model(WriteAuthorizationModelRequest()) + + api_request.assert_called_once() + + async def test_write_authorization_model_returns_expected_response( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the write_authorization_model method returns the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.write_authorization_model( + WriteAuthorizationModelRequest() + ) + + assert response == None + + async def test_write_authorization_model_returns_full_response( + self, + client: OpenFgaClientProtocol, + api_request_response: ApiResponse, + api_request_conditions, + ): + """ + Test that the write_authorization_model method returns an ApiResponse instance containing the expected response. + """ + + with patch.object(client.api, **api_request_conditions): + response = await client.write_authorization_model( + WriteAuthorizationModelRequest(), + WriteAuthorizationModelRequestOptions( + return_response=True, + ), + ) + + assert response == api_request_response diff --git a/test/endpoints/async/test_write_client.py b/test/endpoints/async/test_write_client.py new file mode 100644 index 00000000..d66f46c1 --- /dev/null +++ b/test/endpoints/async/test_write_client.py @@ -0,0 +1,69 @@ +""" +Python SDK for OpenFGA + +API version: 1.x +Website: https://openfga.dev +Documentation: https://openfga.dev/docs +Support: https://openfga.dev/community +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 AsyncMock, patch + +import pytest + +from openfga_sdk.client.models.write_request import ClientWriteRequest +from openfga_sdk.client.models.write_response import ClientWriteResponse +from openfga_sdk.common.open_fga_api import ApiResponse +from openfga_sdk.common.options import DeleteStoreRequestOptions +from openfga_sdk.protocols import OpenFgaClientProtocol + + +@pytest.fixture +def api_request_response() -> ClientWriteResponse: + return ClientWriteResponse() + + +@pytest.fixture +def api_request_conditions( + api_request_response: ClientWriteResponse, +) -> dict[str, AsyncMock]: + return { + "attribute": "write", + "new": AsyncMock(return_value=api_request_response), + } + + +@pytest.mark.asyncio +class TestOpenFgaClientWriteStoreEndpoint: + """ + @covers openfga_sdk.client.OpenFgaClient.write + """ + + async def test_write_issues_request( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the write method issues a request to OpenFgaApi. + """ + with patch.object(client.api, **api_request_conditions) as api_request: + await client.write(ClientWriteRequest()) + + api_request.assert_called_once() + + async def test_write_returns_expected_response( + self, + client: OpenFgaClientProtocol, + api_request_conditions, + ): + """ + Test that the write method returns the expected response. + """ + with patch.object(client.api, **api_request_conditions): + response = await client.write(ClientWriteRequest()) + + assert isinstance(response, ClientWriteResponse) diff --git a/test/oauth2_test.py b/test/oauth2_test.py index 90251f08..07636ed9 100644 --- a/test/oauth2_test.py +++ b/test/oauth2_test.py @@ -11,75 +11,106 @@ """ from datetime import datetime, timedelta -from unittest import IsolatedAsyncioTestCase -from unittest.mock import patch +from unittest.mock import AsyncMock +import json +import pytest +import pytest_asyncio import urllib3 -from openfga_sdk import rest from openfga_sdk.configuration import Configuration from openfga_sdk.credentials import CredentialConfiguration, Credentials from openfga_sdk.exceptions import AuthenticationError from openfga_sdk.oauth2 import OAuth2Client +from openfga_sdk.rest import RestClientResponse, RestClient +from openfga_sdk.protocols import OAuth2ClientProtocol, RestClientProtocol -# Helper function to construct mock response -def mock_response(body, status): +def mock_response(body: dict | str, status) -> RestClientResponse: + body = json.dumps(body) if isinstance(body, dict) else body headers = urllib3.response.HTTPHeaderDict({"content-type": "application/json"}) obj = urllib3.HTTPResponse(body, headers, status, preload_content=False) - return rest.RESTResponse(obj, obj.data) - - -class TestOAuth2Client(IsolatedAsyncioTestCase): - """TestOAuth2Client unit test""" - - def setUp(self): - pass - - def tearDown(self): - pass - - async def test_get_authentication_valid_client_credentials(self): - """ - Test getting authentication header when method is client credentials - """ - client = OAuth2Client(None) - client._access_token = "XYZ123" - client._access_expiry_time = datetime.now() + timedelta(seconds=60) - auth_header = await client.get_authentication_header(None) - self.assertEqual(auth_header, {"Authorization": "Bearer XYZ123"}) - - @patch.object(rest.RESTClientObject, "request") - async 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) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", + return RestClientResponse(response=obj, data=body, status=status, reason="OK") + + +@pytest.fixture +def credentials(): + yield Credentials( + method="client_credentials", + configuration=CredentialConfiguration( + client_id="myclientid", + client_secret="mysecret", + api_issuer="issuer.fga.example", + api_audience="myaudience", + ), + ) + + +@pytest.fixture +def configuration(credentials): + yield Configuration( + api_url="https://api.fga.example", + credentials=credentials, + ) + + +@pytest_asyncio.fixture +async def oauth2_client(configuration): + yield OAuth2Client( + configuration=configuration, + ) + + +@pytest_asyncio.fixture +async def rest_client(configuration): + rest_client = RestClient( + configuration=configuration, + ) + yield rest_client + await rest_client.close() + + +@pytest.mark.asyncio +class TestAsyncOAuth2Client: + async def test_get_authentication_valid_client_credentials( + self, + rest_client: RestClientProtocol, + oauth2_client: OAuth2ClientProtocol, + ): + oauth2_client.access_token = "XYZ123" + oauth2_client.access_expiry_time = datetime.now() + timedelta(seconds=60) + + auth_header = await oauth2_client.get_authentication_header(rest_client) + + assert auth_header.name == "Authorization" + assert auth_header.value == "Bearer XYZ123" + + async def test_get_authentication_obtain_client_credentials( + self, + rest_client, + oauth2_client, + mocker, + ): + mock_request = mocker.patch.object( + rest_client, + "request", + AsyncMock( + return_value=mock_response( + {"expires_in": 120, "access_token": "AABBCCDD"}, 200 + ) ), ) - rest_client = rest.RESTClientObject(Configuration()) + current_time = datetime.now() - client = OAuth2Client(credentials) - auth_header = await client.get_authentication_header(rest_client) - self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"}) - self.assertEqual(client._access_token, "AABBCCDD") - self.assertGreaterEqual( - client._access_expiry_time, current_time + timedelta(seconds=120) - ) + + auth_header = await oauth2_client.get_authentication_header(rest_client) + + assert auth_header.name == "Authorization" + assert auth_header.value == "Bearer AABBCCDD" + + assert oauth2_client.access_token == "AABBCCDD" + assert oauth2_client.access_expiry_time >= current_time + timedelta(seconds=120) + expected_header = urllib3.response.HTTPHeaderDict( { "Accept": "application/json", @@ -87,14 +118,11 @@ async def test_get_authentication_obtain_client_credentials(self, mock_request): "User-Agent": "openfga-sdk (python) 0.9.1", } ) + mock_request.assert_called_once_with( method="POST", url="https://issuer.fga.example/oauth/token", headers=expected_header, - query_params=None, - body=None, - _preload_content=True, - _request_timeout=None, post_params={ "client_id": "myclientid", "client_secret": "mysecret", @@ -102,175 +130,109 @@ async def test_get_authentication_obtain_client_credentials(self, mock_request): "grant_type": "client_credentials", }, ) - await rest_client.close() - @patch.object(rest.RESTClientObject, "request") async def test_get_authentication_obtain_client_credentials_failed( - self, mock_request + self, + rest_client, + oauth2_client, + mocker, ): - """ - 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) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), + mocker.patch.object( + rest_client, + "request", + return_value=mock_response({"reason": "Unauthorized"}, 403), + new_callable=AsyncMock, ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - with self.assertRaises(AuthenticationError): - await client.get_authentication_header(rest_client) - await rest_client.close() - @patch.object(rest.RESTClientObject, "request") + with pytest.raises(AuthenticationError): + await oauth2_client.get_authentication_header(rest_client) + async def test_get_authentication_obtain_with_expired_client_credentials_failed( - self, mock_request + self, + rest_client, + oauth2_client, + mocker, ): - """ - Expired token should trigger a new token request - """ - - response_body = """ -{ - "reason": "Unauthorized" -} - """ - mock_request.return_value = mock_response(response_body, 403) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), + mocker.patch.object( + rest_client, + "request", + return_value=mock_response({"reason": "Unauthorized"}, 403), + new_callable=AsyncMock, ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - - client._access_token = "XYZ123" - client._access_expiry_time = datetime.now() - timedelta(seconds=240) - - with self.assertRaises(AuthenticationError): - await client.get_authentication_header(rest_client) - await rest_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_authentication_unexpected_response_fails(self, mock_request): - """ - Receiving an unexpected response from the server should raise an exception - """ - - response_body = """ -This is not a JSON response - """ - mock_request.return_value = mock_response(response_body, 200) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), + + oauth2_client.access_token = "XYZ123" + oauth2_client.access_expiry_time = datetime.now() - timedelta(seconds=240) + + with pytest.raises(AuthenticationError): + await oauth2_client.get_authentication_header(rest_client) + + async def test_get_authentication_unexpected_response_fails( + self, + mocker, + rest_client, + oauth2_client, + ): + mocker.patch.object( + rest_client, + "request", + return_value=mock_response("This is not a JSON response", 200), + new_callable=AsyncMock, ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - - with self.assertRaises(AuthenticationError): - await client.get_authentication_header(rest_client) - await rest_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_authentication_erroneous_response_fails(self, mock_request): - """ - Receiving an erroneous response from the server that's missing properties should raise an exception - """ - - response_body = """ -{ - "access_token": "AABBCCDD" -} - """ - mock_request.return_value = mock_response(response_body, 200) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), + + with pytest.raises(AuthenticationError): + await oauth2_client.get_authentication_header(rest_client) + + async def test_get_authentication_erroneous_response_fails( + self, + rest_client, + oauth2_client, + mocker, + ): + mocker.patch.object( + rest_client, + "request", + return_value=mock_response({"access_token": "AABBCCDD"}, 200), + new_callable=AsyncMock, ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - - with self.assertRaises(AuthenticationError): - await client.get_authentication_header(rest_client) - await rest_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_authentication_retries_5xx_responses(self, mock_request): - """ - Receiving a 5xx response from the server should be retried - """ - - error_response_body = """ -{ - "code": "rate_limit_exceeded", - "message": "Rate Limit exceeded" -} - """ - - response_body = """ -{ - "expires_in": 120, - "access_token": "AABBCCDD" -} - """ - - mock_request.side_effect = [ - mock_response(error_response_body, 429), - mock_response(error_response_body, 429), - mock_response(error_response_body, 429), - mock_response(response_body, 200), + + with pytest.raises(AuthenticationError): + await oauth2_client.get_authentication_header(rest_client) + + async def test_get_authentication_retries_5xx_responses( + self, + rest_client, + oauth2_client, + mocker, + ): + error_response = { + "code": "rate_limit_exceeded", + "message": "Rate Limit exceeded", + } + + success_response = {"expires_in": 120, "access_token": "AABBCCDD"} + + side_effect = [ + mock_response(error_response, 429), + mock_response(error_response, 429), + mock_response(error_response, 429), + mock_response(success_response, 200), ] - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), + mock_request = mocker.patch.object( + rest_client, + "request", + side_effect=side_effect, + new_callable=AsyncMock, ) - configuration = Configuration() - configuration.retry_params.max_retry = 5 - configuration.retry_params.retry_interval = 0 - - rest_client = rest.RESTClientObject(configuration) - client = OAuth2Client(credentials, configuration) + oauth2_client.configuration.retry_params.max_retries = 5 + oauth2_client.configuration.retry_params.min_wait_in_ms = 0 - auth_header = await client.get_authentication_header(rest_client) + auth_header = await oauth2_client.get_authentication_header(rest_client) mock_request.assert_called() - self.assertEqual(mock_request.call_count, 4) # 3 retries, 1 success - self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"}) - await rest_client.close() + assert mock_request.call_count == 4 + + assert auth_header.name == "Authorization" + assert auth_header.value == "Bearer AABBCCDD" diff --git a/test/rest_test.py b/test/rest_test.py index 03d9d86c..b4abc12d 100644 --- a/test/rest_test.py +++ b/test/rest_test.py @@ -25,17 +25,19 @@ UnauthorizedException, ValidationException, ) -from openfga_sdk.rest import RESTClientObject, RESTResponse +from openfga_sdk.rest import RestClient, RestClientResponse @pytest.mark.asyncio -async def test_restresponse_init(): +async def test_RestClientResponse_init(): mock_resp = MagicMock() mock_resp.status = 200 mock_resp.reason = "OK" resp_data = b'{"test":"data"}' - rest_resp = RESTResponse(mock_resp, resp_data) + rest_resp = RestClientResponse( + response=mock_resp, data=resp_data, status=200, reason="OK" + ) assert rest_resp.status == 200 assert rest_resp.reason == "OK" @@ -43,21 +45,25 @@ async def test_restresponse_init(): assert rest_resp.response == mock_resp -def test_restresponse_getheaders(): +def test_RestClientResponse_getheaders(): mock_resp = MagicMock() mock_resp.headers = {"Content-Type": "application/json", "X-Testing": "true"} - rest_resp = RESTResponse(mock_resp, b"") - headers = rest_resp.getheaders() + rest_resp = RestClientResponse( + response=mock_resp, data=b"", status=200, reason="OK" + ) + headers = rest_resp.headers assert headers["Content-Type"] == "application/json" assert headers["X-Testing"] == "true" -def test_restresponse_getheader(): +def test_RestClientResponse_getheader(): mock_resp = MagicMock() mock_resp.headers = {"Content-Type": "application/json"} - rest_resp = RESTResponse(mock_resp, b"") - val = rest_resp.getheader("Content-Type") - missing = rest_resp.getheader("X-Not-Here", default="fallback") + rest_resp = RestClientResponse( + response=mock_resp, data=b"", status=200, reason="OK" + ) + val = rest_resp.header("Content-Type") + missing = rest_resp.header("X-Not-Here", default="fallback") assert val == "application/json" assert missing == "fallback" @@ -69,22 +75,22 @@ async def test_build_request_json_body(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) - req_args = await client.build_request( + client = RestClient(configuration=mock_config) + req_args = client.build_request( method="POST", url="http://example.com/test", body={"foo": "bar"}, headers={"Content-Type": "application/json"}, ) - assert req_args["method"] == "POST" - assert req_args["url"] == "http://example.com/test" - assert req_args["headers"]["Content-Type"] == "application/json" - assert json.loads(req_args["data"]) == {"foo": "bar"} + assert req_args.method == "POST" + assert req_args.url == "http://example.com/test" + assert req_args.headers["Content-Type"] == "application/json" + assert json.loads(req_args.body) == {"foo": "bar"} @pytest.mark.asyncio @@ -94,43 +100,21 @@ async def test_build_request_form_data(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) - req_args = await client.build_request( + client = RestClient(configuration=mock_config) + req_args = client.build_request( method="POST", url="http://example.com/upload", post_params=[("file", ("filename.txt", b"contents", "text/plain"))], headers={"Content-Type": "multipart/form-data"}, ) - assert req_args["method"] == "POST" - assert req_args["url"] == "http://example.com/upload" - assert "Content-Type" not in req_args["headers"] - assert "data" in req_args - - -@pytest.mark.asyncio -async def test_build_request_timeout(): - mock_config = MagicMock() - mock_config.ssl_ca_cert = None - mock_config.cert_file = None - mock_config.key_file = None - mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.proxy = None - mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 - - client = RESTClientObject(configuration=mock_config) - req_args = await client.build_request( - method="GET", - url="http://example.com", - _request_timeout=10.0, - ) - assert req_args["timeout"] == 10.0 + assert req_args.method == "POST" + assert req_args.url == "http://example.com/upload" + assert "Content-Type" not in req_args.headers @pytest.mark.asyncio @@ -140,15 +124,15 @@ async def test_handle_response_exception_success(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_response = MagicMock() mock_response.status = 200 - await client.handle_response_exception(mock_response) + client._handle_response_exception(mock_response) @pytest.mark.parametrize( @@ -170,17 +154,17 @@ async def test_handle_response_exception_error(status, exc): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_response = MagicMock() mock_response.status = status with pytest.raises(exc): - await client.handle_response_exception(mock_response) + client._handle_response_exception(mock_response) @pytest.mark.asyncio @@ -190,12 +174,12 @@ async def test_close(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_session = MagicMock() mock_session.close = AsyncMock() @@ -206,76 +190,6 @@ async def test_close(): mock_session.close.assert_awaited_once() -@pytest.mark.asyncio -async def test_request_preload_content(): - # Mock config - mock_config = MagicMock() - mock_config.ssl_ca_cert = None - mock_config.cert_file = None - mock_config.key_file = None - mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.proxy = None - mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 - - client = RESTClientObject(configuration=mock_config) - - mock_session = MagicMock() - client.pool_manager = mock_session - - mock_raw_response = MagicMock() - mock_raw_response.status = 200 - mock_raw_response.reason = "OK" - mock_raw_response.read = AsyncMock(return_value=b'{"some":"data"}') - mock_session.request = AsyncMock(return_value=mock_raw_response) - - resp = await client.request( - method="GET", url="http://example.com", _preload_content=True - ) - - mock_session.request.assert_awaited_once() - mock_raw_response.read.assert_awaited_once() - - assert resp.status == 200 - assert resp.reason == "OK" - assert resp.data == b'{"some":"data"}' - - -@pytest.mark.asyncio -async def test_request_no_preload_content(): - mock_config = MagicMock() - mock_config.ssl_ca_cert = None - mock_config.cert_file = None - mock_config.key_file = None - mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.proxy = None - mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 - - client = RESTClientObject(configuration=mock_config) - - mock_session = MagicMock() - client.pool_manager = mock_session - - mock_raw_response = MagicMock() - mock_raw_response.status = 200 - mock_raw_response.reason = "OK" - mock_raw_response.read = AsyncMock(return_value=b"unused") - mock_session.request = AsyncMock(return_value=mock_raw_response) - - resp = await client.request( - method="GET", url="http://example.com", _preload_content=False - ) - - mock_session.request.assert_awaited_once() - - assert resp == mock_raw_response - assert resp.status == 200 - assert resp.reason == "OK" - - @pytest.mark.asyncio async def test_stream_happy_path(): mock_config = MagicMock() @@ -283,12 +197,12 @@ async def test_stream_happy_path(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_session = MagicMock() client.pool_manager = mock_session @@ -308,7 +222,7 @@ async def iter_chunks(self): mock_session.request.return_value = mock_context_manager - client.handle_response_exception = AsyncMock() + client._handle_response_exception = AsyncMock() client.close = AsyncMock() results = [] @@ -317,7 +231,7 @@ async def iter_chunks(self): assert results == [{"foo": "bar"}, {"hello": "world"}] - client.handle_response_exception.assert_awaited_once() + client._handle_response_exception.assert_awaited_once() mock_response.release.assert_called_once() client.close.assert_awaited_once() @@ -329,12 +243,12 @@ async def test_stream_exception_in_chunks(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_session = MagicMock() client.pool_manager = mock_session @@ -354,7 +268,7 @@ async def iter_chunks(self): mock_session.request.return_value = mock_context_manager - client.handle_response_exception = AsyncMock() + client._handle_response_exception = AsyncMock() client.close = AsyncMock() results = [] @@ -362,7 +276,7 @@ async def iter_chunks(self): results.append(item) assert results == [] - client.handle_response_exception.assert_awaited_once() + client._handle_response_exception.assert_awaited_once() mock_response.release.assert_called_once() client.close.assert_awaited_once() @@ -374,12 +288,12 @@ async def test_stream_partial_chunks(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 + mock_config.connection_pool_size_max = 4 mock_config.proxy = None mock_config.proxy_headers = None - mock_config.timeout_millisec = 5000 + mock_config.timeout = 5000 - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_session = MagicMock() client.pool_manager = mock_session @@ -400,7 +314,7 @@ async def iter_chunks(self): mock_session.request.return_value = mock_context_manager - client.handle_response_exception = AsyncMock() + client._handle_response_exception = AsyncMock() client.close = AsyncMock() results = [] @@ -409,6 +323,6 @@ async def iter_chunks(self): assert results == [{"foo": "bar"}, {"hello": "world"}] - client.handle_response_exception.assert_awaited_once() + client._handle_response_exception.assert_awaited_once() mock_response.release.assert_called_once() client.close.assert_awaited_once() diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index 124fbdbc..0fe626d3 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -18,7 +18,7 @@ import urllib3 -from openfga_sdk.client import ClientConfiguration +from openfga_sdk.configuration import Configuration from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ClientBatchCheckItem from openfga_sdk.client.models.batch_check_request import ClientBatchCheckRequest @@ -31,7 +31,6 @@ from openfga_sdk.client.models.tuple import ClientTuple from openfga_sdk.client.models.write_request import ClientWriteRequest 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 ( FgaValidationException, UnauthorizedException, @@ -83,3111 +82,3047 @@ from openfga_sdk.models.write_authorization_model_response import ( WriteAuthorizationModelResponse, ) -from openfga_sdk.sync import rest -from openfga_sdk.sync.client.client import OpenFgaClient +from openfga_sdk.protocols import ConfigurationProtocol +from openfga_sdk.sync import OpenFgaClient, RestClient, RestClientResponse 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_url="http://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 - """ - 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, - body=None, - query_params=[ - ("page_size", 1), - ("continuation_token", "continuation_token_example"), - ], - post_params=[], - _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 - """ - 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 - """ - 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, - body=None, - query_params=[], - post_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 - """ - 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, - body=None, - query_params=[], - post_params=[], - _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 - """ - 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, - body=None, - query_params=[], - post_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 - """ - 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 - """ - 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, - body=None, - query_params=[], - post_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 - """ - 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, - body=None, - query_params=[("page_size", 1)], - post_params=[], - _preload_content=ANY, - _request_timeout=None, - ) - - @patch.object(rest.RESTClientObject, "request") - def test_read_latest_authorization_model_with_no_models(self, mock_request): - """Test case for read_latest_authorization_model when no models are in the store - - Return the latest authorization models configured for the store - """ - response_body = """ -{ - "authorization_models": [] -} - """ - 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) - self.assertIsNone(api_response.authorization_model) - mock_request.assert_called_once_with( - "GET", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models", - headers=ANY, - body=None, - query_params=[("page_size", 1)], - post_params=[], - _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 - """ - 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", "2022-01-01T00:00:00+00:00"), - 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, - body=None, - query_params=[ - ("type", "document"), - ("page_size", 1), - ("continuation_token", "abcdefg"), - ("start_time", "2022-01-01T00:00:00+00:00"), - ], - post_params=[], - _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 - """ - response_body = """ - { - "tuples": [ - { - "key": { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget" - }, - "timestamp": "2021-10-06T15:32:11.128Z" - } - ], - "continuation_token": "" -} - """ - 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 = ReadRequestTupleKey( - 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==", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - 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)], continuation_token="" - ) - 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==", - "consistency": "MINIMIZE_LATENCY", - }, - _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 - """ - response_body = """ - { - "tuples": [ - { - "key": { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget" - }, - "timestamp": "2021-10-06T15:32:11.128Z" - } - ], - "continuation_token": "" -} - """ - 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 = ReadRequestTupleKey( - 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)], continuation_token="" - ) - 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_read_empty_body(self, mock_request): - """Test case for read with empty body - - Get tuples from the store that matches a query, without following userset rewrite rules - """ - response_body = """ - { - "tuples": [ - { - "key": { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget" - }, - "timestamp": "2021-10-06T15:32:11.128Z" - } - ], - "continuation_token": "" -} - """ - 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 = ReadRequestTupleKey() - 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)], continuation_token="" - ) - 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={}, - _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), - ] - 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, 3) - 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), - ] - 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, 3) - 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, 2) - 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), - 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, 3) - 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), - ] - 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", - ) - ], - writes=[], - ) - 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( - "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): - 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, - }, - ) - - mock_request.assert_called() - self.assertEqual(mock_request.call_count, 1) - - mock_request.assert_called_once_with( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write", - headers=ANY, - query_params=[], - post_params=[], - body={ - "writes": { - "tuple_keys": [ - { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "reader", - "object": "document:2021-budget", - } - ] - }, - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - }, - _preload_content=ANY, - _request_timeout=ANY, - ) - 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 - """ - - # 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", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _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 - """ - - # 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_client_batch_check_single_request(self, mock_request): - """Test case for check with single request - - Check whether a user is authorized to access an object - """ - - # First, mock the response - response_body = '{"allowed": true, "resolution": "1234"}' - mock_request.side_effect = [ - 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.client_batch_check( - body=[body], - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - 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( - "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", - "consistency": "MINIMIZE_LATENCY", - }, - _preload_content=ANY, - _request_timeout=None, - ) - api_client.close() - - @patch.object(rest.RESTClientObject, "request") - def test_client_batch_check_multiple_request(self, mock_request): - """Test case for check with multiple request - - Check whether a user is authorized to access an object - """ - - # First, mock the response - mock_request.side_effect = [ - 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.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( - "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_client_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 - """ - response_body = """ -{ - "code": "validation_error", - "message": "Generic validation error" -} - """ - - # First, mock the response - mock_request.side_effect = [ - 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.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( - "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_single_request(self, mock_request): - """Test case for check with single request - - Check whether a user is authorized to access an object - """ - - # First, mock the response - response_body = """ - { - "result": { - "1": { - "allowed": true - } - } - } - """ - mock_request.side_effect = [ - mock_response(response_body, 200), - ] - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ] - ) - 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.assertEqual(len(api_response.result), 1) - self.assertEqual(api_response.result[0].error, None) - self.assertTrue(api_response.result[0].allowed) - self.assertEqual(api_response.result[0].correlation_id, "1") - self.assertEqual(api_response.result[0].request, body.checks[0]) - # Make sure the API was called with the right data - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - }, - "correlation_id": "1", - } - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - api_client.close() - - @patch.object(uuid, "uuid4") - @patch.object(rest.RESTClientObject, "request") - def test_batch_check_multiple_request(self, mock_request, mock_uuid): - """Test case for check with multiple request - - Check whether a user is authorized to access an object - """ - first_response_body = """ - { - "result": { - "1": { - "allowed": true - }, - "2": { - "allowed": false - } - } - } -""" - - second_response_body = """ -{ - "result": { - "fake-uuid": { - "error": { - "input_error": "validation_error", - "message": "type 'doc' not found" - } - } - } -}""" - # First, mock the response - mock_request.side_effect = [ - mock_response(first_response_body, 200), - mock_response(second_response_body, 200), - ] - - def mock_v4(val: str): - return val - - mock_uuid.side_effect = [mock_v4("batch-id-header"), mock_v4("fake-uuid")] - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", - correlation_id="2", - ), - ClientBatchCheckItem( - object="doc: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=body, - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "max_parallel_requests": 1, - "max_batch_size": 2, - }, - ) - self.assertEqual(len(api_response.result), 3) - self.assertEqual(api_response.result[0].error, None) - self.assertTrue(api_response.result[0].allowed) - self.assertEqual(api_response.result[1].error, None) - self.assertFalse(api_response.result[1].allowed) - self.assertEqual( - api_response.result[2].error.message, "type 'doc' not found" - ) - self.assertFalse(api_response.result[2].allowed) - # value generated from the uuid mock - self.assertEqual(api_response.result[2].correlation_id, "fake-uuid") - # Make sure the API was called with the right data - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - }, - "correlation_id": "1", - }, - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", - }, - "correlation_id": "2", - }, - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "doc:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", - }, - "correlation_id": "fake-uuid", - } - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - api_client.close() - - def test_batch_check_errors_dupe_cor_id(self): - """Test case for duplicate correlation_id being provided to batch_check""" - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ] - ) - configuration = self.configuration - configuration.store_id = store_id - with OpenFgaClient(configuration) as api_client: - with self.assertRaises(FgaValidationException) as error: - api_client.batch_check( - body=body, - options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, - ) - self.assertEqual( - "Duplicate correlation_id (1) provided", str(error.exception) - ) - api_client.close() - - @patch.object(rest.RESTClientObject, "request") - def test_batch_check_errors_unauthorized(self, mock_request): - """Test case for BatchCheck with a 401""" - first_response_body = """ - { - "result": { - "1": { - "allowed": true - }, - "2": { - "allowed": false - } - } - } -""" - - # First, mock the response - mock_request.side_effect = [ - mock_response(first_response_body, 200), - UnauthorizedException(http_resp=http_mock_response("{}", 401)), - ] - - body = ClientBatchCheckRequest( - checks=[ - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - correlation_id="1", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", - correlation_id="2", - ), - ClientBatchCheckItem( - object="document:2021-budget", - relation="reader", - user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", - correlation_id="3", - ), - ] - ) - - configuration = self.configuration - configuration.store_id = store_id - with OpenFgaClient(configuration) as api_client: - with self.assertRaises(UnauthorizedException): - api_client.batch_check( - body=body, - options={ - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - "max_parallel_requests": 1, - "max_batch_size": 2, - }, - ) - - # Make sure the API was called with the right data - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - }, - "correlation_id": "1", - }, - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", - }, - "correlation_id": "2", - }, - ], - "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", - }, - _preload_content=ANY, - _request_timeout=None, - ) - mock_request.assert_any_call( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", - headers=ANY, - query_params=[], - post_params=[], - body={ - "checks": [ - { - "tuple_key": { - "object": "document:2021-budget", - "relation": "reader", - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", - }, - "correlation_id": "3", - } - ], - "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 - """ - 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", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - self.assertIsInstance(api_response, ExpandResponse) - cur_users = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]) - leaf = Leaf(users=cur_users) - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _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 - """ - 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", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _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 - """ - 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 - """ - - def mock_check_requests(*args, **kwargs): - body = kwargs.get("body") - tuple_key = body.get("tuple_key") - if tuple_key["relation"] == "owner": - return mock_response('{"allowed": false, "resolution": "1234"}', 200) - return mock_response('{"allowed": true, "resolution": "1234"}', 200) - - # First, mock the response - mock_request.side_effect = mock_check_requests - - 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", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - self.assertEqual(api_response, ["reader", "viewer"]) - - # Make sure the API was called with the right data - 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", - "consistency": "MINIMIZE_LATENCY", - }, - _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", - "consistency": "MINIMIZE_LATENCY", - }, - _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", - "consistency": "MINIMIZE_LATENCY", - }, - _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() - api_client.close() - - @patch.object(rest.RESTClientObject, "request") - def test_list_users(self, mock_request): - """ - Test case for list_users - """ - - response_body = """{ - "users": [ - { - "object": { - "id": "81684243-9356-4421-8fbf-a4f8d36aa31b", - "type": "user" - } - }, - { - "userset": { - "id": "fga", - "relation": "member", - "type": "team" - } - }, - { - "wildcard": { - "type": "user" - } - } - ] -}""" - - mock_request.return_value = mock_response(response_body, 200) - - configuration = self.configuration - configuration.store_id = store_id - - with OpenFgaClient(configuration) as api_client: - body = ClientListUsersRequest() - body.object = FgaObject(type="document", id="2021-budget") - body.relation = "can_read" - body.user_filters = [ - UserTypeFilter(type="user"), - ] - body.context = {} - body.contextual_tuples = [ - ClientTuple( - user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", - relation="editor", - object="folder:product", - ), - ClientTuple( - user="folder:product", - relation="parent", - object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", - ), - ] - - response = api_client.list_users( - body, - options={ - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - "consistency": ConsistencyPreference.MINIMIZE_LATENCY, - }, - ) - - self.assertIsInstance(response, ListUsersResponse) - - self.assertEqual(response.users.__len__(), 3) - - self.assertIsNotNone(response.users[0].object) - self.assertEqual( - response.users[0].object.id, "81684243-9356-4421-8fbf-a4f8d36aa31b" - ) - self.assertEqual(response.users[0].object.type, "user") - self.assertIsNone(response.users[0].userset) - self.assertIsNone(response.users[0].wildcard) - - self.assertIsNone(response.users[1].object) - self.assertIsNotNone(response.users[1].userset) - self.assertEqual(response.users[1].userset.id, "fga") - self.assertEqual(response.users[1].userset.relation, "member") - self.assertEqual(response.users[1].userset.type, "team") - self.assertIsNone(response.users[1].wildcard) - - self.assertIsNone(response.users[2].object) - self.assertIsNone(response.users[2].userset) - self.assertIsNotNone(response.users[2].wildcard) - self.assertEqual(response.users[2].wildcard.type, "user") - - mock_request.assert_called_once_with( - "POST", - "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-users", - headers=ANY, - query_params=[], - post_params=[], - body={ - "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", - "object": {"id": "2021-budget", "type": "document"}, - "relation": "can_read", - "user_filters": [ - {"type": "user"}, - ], - "contextual_tuples": [ - { - "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - "relation": "editor", - "object": "folder:product", - }, - { - "user": "folder:product", - "relation": "parent", - "object": "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", - }, - ], - "context": {}, - "consistency": "MINIMIZE_LATENCY", - }, - _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=TupleKeyWithoutCondition( - 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, - body=None, - query_params=[], - post_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 - """ - 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 - """ - 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 - """ - 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 - """ - 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) +# # 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 RestClientResponse( +# response=obj, data=obj.data, status=obj.status, reason=obj.reason +# ) + + +# class TestOpenFgaClient(IsolatedAsyncioTestCase): +# """Test for OpenFGA Client""" + +# def setUp(self): +# self.configuration: ConfigurationProtocol = Configuration( +# api_url="http://api.fga.example", +# ) + +# def tearDown(self): +# pass + +# @patch.object(RestClient, "request") +# def test_list_stores(self, mock_request): +# """Test case for list_stores + +# Get all stores +# """ +# 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, +# body=None, +# query_params=[ +# ("page_size", 1), +# ("continuation_token", "continuation_token_example"), +# ], +# post_params=[], +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_create_store(self, mock_request): +# """Test case for create_store + +# Create a store +# """ +# 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"}, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_get_store(self, mock_request): +# """Test case for get_store + +# Get all stores +# """ +# 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_delete_store(self, mock_request): +# """Test case for delete_store + +# Get all stores +# """ +# 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_read_authorization_models(self, mock_request): +# """Test case for read_authorization_models + +# Return all authorization models configured for the store +# """ +# 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_write_authorization_model(self, mock_request): +# """Test case for write_authorization_model + +# Create a new authorization model +# """ +# 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", +# } +# }, +# ] +# } +# }, +# }, +# } +# ], +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_read_authorization_model(self, mock_request): +# """Test case for read_authorization_model + +# Return a particular version of an authorization model +# """ +# 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "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 +# """ +# 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, +# body=None, +# query_params=[("page_size", 1)], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_read_latest_authorization_model_with_no_models(self, mock_request): +# """Test case for read_latest_authorization_model when no models are in the store + +# Return the latest authorization models configured for the store +# """ +# response_body = """ +# { +# "authorization_models": [] +# } +# """ +# 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) +# self.assertIsNone(api_response.authorization_model) +# mock_request.assert_called_once_with( +# "GET", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models", +# headers=ANY, +# body=None, +# query_params=[("page_size", 1)], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_read_changes(self, mock_request): +# """Test case for read_changes + +# Return a list of all the tuple changes +# """ +# 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", "2022-01-01T00:00:00+00:00"), +# 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, +# body=None, +# query_params=[ +# ("type", "document"), +# ("page_size", 1), +# ("continuation_token", "abcdefg"), +# ("start_time", "2022-01-01T00:00:00+00:00"), +# ], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "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 +# """ +# response_body = """ +# { +# "tuples": [ +# { +# "key": { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget" +# }, +# "timestamp": "2021-10-06T15:32:11.128Z" +# } +# ], +# "continuation_token": "" +# } +# """ +# 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 = ReadRequestTupleKey( +# 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==", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# 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)], continuation_token="" +# ) +# 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==", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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 +# """ +# response_body = """ +# { +# "tuples": [ +# { +# "key": { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget" +# }, +# "timestamp": "2021-10-06T15:32:11.128Z" +# } +# ], +# "continuation_token": "" +# } +# """ +# 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 = ReadRequestTupleKey( +# 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)], continuation_token="" +# ) +# 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", +# } +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_read_empty_body(self, mock_request): +# """Test case for read with empty body + +# Get tuples from the store that matches a query, without following userset rewrite rules +# """ +# response_body = """ +# { +# "tuples": [ +# { +# "key": { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget" +# }, +# "timestamp": "2021-10-06T15:32:11.128Z" +# } +# ], +# "continuation_token": "" +# } +# """ +# 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 = ReadRequestTupleKey() +# 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)], continuation_token="" +# ) +# 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={}, +# timeout=None, +# ) + +# @patch.object(RestClient, "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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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), +# ] +# 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, 3) +# 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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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), +# ] +# 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, 3) +# 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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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, 2) +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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), +# 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, 3) +# 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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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), +# ] +# 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", +# ) +# ], +# writes=[], +# ) +# 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( +# "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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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", +# }, +# timeout=None, +# ) + +# @patch.object(RestClient, "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): +# 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, +# }, +# ) + +# mock_request.assert_called() +# self.assertEqual(mock_request.call_count, 1) + +# mock_request.assert_called_once_with( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "writes": { +# "tuple_keys": [ +# { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "reader", +# "object": "document:2021-budget", +# } +# ] +# }, +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_check(self, mock_request): +# """Test case for check + +# Check whether a user is authorized to access an object +# """ + +# # 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", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "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 +# """ + +# # 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", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_client_batch_check_single_request(self, mock_request): +# """Test case for check with single request + +# Check whether a user is authorized to access an object +# """ + +# # First, mock the response +# response_body = '{"allowed": true, "resolution": "1234"}' +# mock_request.side_effect = [ +# 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.client_batch_check( +# body=[body], +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# 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( +# "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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_client_batch_check_multiple_request(self, mock_request): +# """Test case for check with multiple request + +# Check whether a user is authorized to access an object +# """ + +# # First, mock the response +# mock_request.side_effect = [ +# 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.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( +# "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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_client_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 +# """ +# response_body = """ +# { +# "code": "validation_error", +# "message": "Generic validation error" +# } +# """ + +# # First, mock the response +# mock_request.side_effect = [ +# 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.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( +# "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", +# }, +# 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", +# }, +# 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", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "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 +# """ + +# # First, mock the response +# response_body = """ +# { +# "result": { +# "1": { +# "allowed": true +# } +# } +# } +# """ +# mock_request.side_effect = [ +# mock_response(response_body, 200), +# ] + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ] +# ) +# 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.assertEqual(len(api_response.result), 1) +# self.assertEqual(api_response.result[0].error, None) +# self.assertTrue(api_response.result[0].allowed) +# self.assertEqual(api_response.result[0].correlation_id, "1") +# self.assertEqual(api_response.result[0].request, body.checks[0]) +# # Make sure the API was called with the right data +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# }, +# "correlation_id": "1", +# } +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(uuid, "uuid4") +# @patch.object(RestClient, "request") +# def test_batch_check_multiple_request(self, mock_request, mock_uuid): +# """Test case for check with multiple request + +# Check whether a user is authorized to access an object +# """ +# first_response_body = """ +# { +# "result": { +# "1": { +# "allowed": true +# }, +# "2": { +# "allowed": false +# } +# } +# } +# """ + +# second_response_body = """ +# { +# "result": { +# "fake-uuid": { +# "error": { +# "input_error": "validation_error", +# "message": "type 'doc' not found" +# } +# } +# } +# }""" +# # First, mock the response +# mock_request.side_effect = [ +# mock_response(first_response_body, 200), +# mock_response(second_response_body, 200), +# ] + +# def mock_v4(val: str): +# return val + +# mock_uuid.side_effect = [mock_v4("batch-id-header"), mock_v4("fake-uuid")] + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# correlation_id="2", +# ), +# ClientBatchCheckItem( +# object="doc: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=body, +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "max_parallel_requests": 1, +# "max_batch_size": 2, +# }, +# ) +# self.assertEqual(len(api_response.result), 3) +# self.assertEqual(api_response.result[0].error, None) +# self.assertTrue(api_response.result[0].allowed) +# self.assertEqual(api_response.result[1].error, None) +# self.assertFalse(api_response.result[1].allowed) +# self.assertEqual( +# api_response.result[2].error.message, "type 'doc' not found" +# ) +# self.assertFalse(api_response.result[2].allowed) +# # value generated from the uuid mock +# self.assertEqual(api_response.result[2].correlation_id, "fake-uuid") +# # Make sure the API was called with the right data +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# }, +# "correlation_id": "1", +# }, +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# }, +# "correlation_id": "2", +# }, +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "doc:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", +# }, +# "correlation_id": "fake-uuid", +# } +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# api_client.close() + +# def test_batch_check_errors_dupe_cor_id(self): +# """Test case for duplicate correlation_id being provided to batch_check""" + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ] +# ) +# configuration = self.configuration +# configuration.store_id = store_id +# with OpenFgaClient(configuration) as api_client: +# with self.assertRaises(FgaValidationException) as error: +# api_client.batch_check( +# body=body, +# options={"authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1"}, +# ) +# self.assertEqual( +# "Duplicate correlation_id (1) provided", str(error.exception) +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_batch_check_errors_unauthorized(self, mock_request): +# """Test case for BatchCheck with a 401""" +# first_response_body = """ +# { +# "result": { +# "1": { +# "allowed": true +# }, +# "2": { +# "allowed": false +# } +# } +# } +# """ + +# # First, mock the response +# mock_request.side_effect = [ +# mock_response(first_response_body, 200), +# UnauthorizedException(http_resp=http_mock_response("{}", 401)), +# ] + +# body = ClientBatchCheckRequest( +# checks=[ +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# correlation_id="1", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# correlation_id="2", +# ), +# ClientBatchCheckItem( +# object="document:2021-budget", +# relation="reader", +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31d", +# correlation_id="3", +# ), +# ] +# ) + +# configuration = self.configuration +# configuration.store_id = store_id +# with OpenFgaClient(configuration) as api_client: +# with self.assertRaises(UnauthorizedException): +# api_client.batch_check( +# body=body, +# options={ +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# "max_parallel_requests": 1, +# "max_batch_size": 2, +# }, +# ) + +# # Make sure the API was called with the right data +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# }, +# "correlation_id": "1", +# }, +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31c", +# }, +# "correlation_id": "2", +# }, +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# mock_request.assert_any_call( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/batch-check", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "checks": [ +# { +# "tuple_key": { +# "object": "document:2021-budget", +# "relation": "reader", +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31d", +# }, +# "correlation_id": "3", +# } +# ], +# "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "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 +# """ +# 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", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# self.assertIsInstance(api_response, ExpandResponse) +# cur_users = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]) +# leaf = Leaf(users=cur_users) +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_list_objects(self, mock_request): +# """Test case for list_objects + +# List objects +# """ +# 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", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_list_objects_contextual_tuples(self, mock_request): +# """Test case for list_objects + +# List objects +# """ +# 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", +# } +# ] +# }, +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_list_relations(self, mock_request): +# """Test case for list relations + +# Check whether a user is authorized to access an object +# """ + +# def mock_check_requests(*args, **kwargs): +# body = kwargs.get("body") +# tuple_key = body.get("tuple_key") +# if tuple_key["relation"] == "owner": +# return mock_response('{"allowed": false, "resolution": "1234"}', 200) +# return mock_response('{"allowed": true, "resolution": "1234"}', 200) + +# # First, mock the response +# mock_request.side_effect = mock_check_requests + +# 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", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) +# self.assertEqual(api_response, ["reader", "viewer"]) + +# # Make sure the API was called with the right data +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# 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", +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) +# api_client.close() + +# @patch.object(RestClient, "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() +# api_client.close() + +# @patch.object(RestClient, "request") +# def test_list_users(self, mock_request): +# """ +# Test case for list_users +# """ + +# response_body = """{ +# "users": [ +# { +# "object": { +# "id": "81684243-9356-4421-8fbf-a4f8d36aa31b", +# "type": "user" +# } +# }, +# { +# "userset": { +# "id": "fga", +# "relation": "member", +# "type": "team" +# } +# }, +# { +# "wildcard": { +# "type": "user" +# } +# } +# ] +# }""" + +# mock_request.return_value = mock_response(response_body, 200) + +# configuration = self.configuration +# configuration.store_id = store_id + +# with OpenFgaClient(configuration) as api_client: +# body = ClientListUsersRequest() +# body.object = FgaObject(type="document", id="2021-budget") +# body.relation = "can_read" +# body.user_filters = [ +# UserTypeFilter(type="user"), +# ] +# body.context = {} +# body.contextual_tuples = [ +# ClientTuple( +# user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# relation="editor", +# object="folder:product", +# ), +# ClientTuple( +# user="folder:product", +# relation="parent", +# object="document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", +# ), +# ] + +# response = api_client.list_users( +# body, +# options={ +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# "consistency": ConsistencyPreference.MINIMIZE_LATENCY, +# }, +# ) + +# self.assertIsInstance(response, ListUsersResponse) + +# self.assertEqual(response.users.__len__(), 3) + +# self.assertIsNotNone(response.users[0].object) +# self.assertEqual( +# response.users[0].object.id, "81684243-9356-4421-8fbf-a4f8d36aa31b" +# ) +# self.assertEqual(response.users[0].object.type, "user") +# self.assertIsNone(response.users[0].userset) +# self.assertIsNone(response.users[0].wildcard) + +# self.assertIsNone(response.users[1].object) +# self.assertIsNotNone(response.users[1].userset) +# self.assertEqual(response.users[1].userset.id, "fga") +# self.assertEqual(response.users[1].userset.relation, "member") +# self.assertEqual(response.users[1].userset.type, "team") +# self.assertIsNone(response.users[1].wildcard) + +# self.assertIsNone(response.users[2].object) +# self.assertIsNone(response.users[2].userset) +# self.assertIsNotNone(response.users[2].wildcard) +# self.assertEqual(response.users[2].wildcard.type, "user") + +# mock_request.assert_called_once_with( +# "POST", +# "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-users", +# headers=ANY, +# query_params=[], +# post_params=[], +# body={ +# "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", +# "object": {"id": "2021-budget", "type": "document"}, +# "relation": "can_read", +# "user_filters": [ +# {"type": "user"}, +# ], +# "contextual_tuples": [ +# { +# "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", +# "relation": "editor", +# "object": "folder:product", +# }, +# { +# "user": "folder:product", +# "relation": "parent", +# "object": "document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a", +# }, +# ], +# "context": {}, +# "consistency": "MINIMIZE_LATENCY", +# }, +# timeout=None, +# ) + +# api_client.close() + +# @patch.object(RestClient, "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=TupleKeyWithoutCondition( +# 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, +# body=None, +# query_params=[], +# post_params=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_write_assertions(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# 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=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_set_store_id(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# 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=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_config_auth_model(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# 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=[], +# timeout=None, +# ) + +# @patch.object(RestClient, "request") +# def test_update_auth_model(self, mock_request): +# """Test case for write assertions + +# Get all stores +# """ +# 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=[], +# timeout=None, +# ) + +# def test_configuration_authorization_model_id_invalid(self): +# """ +# Test whether ApiValueError is raised if host has query +# """ +# with self.assertRaises(FgaValidationException): +# Configuration( +# api_url="localhost", +# store_id="01H15K9J85050XTEDPVM8DJM78", +# authorization_model_id="abcd", +# ) diff --git a/test/sync/oauth2_test.py b/test/sync/oauth2_test.py index 82f7d367..76da955c 100644 --- a/test/sync/oauth2_test.py +++ b/test/sync/oauth2_test.py @@ -11,264 +11,215 @@ """ from datetime import datetime, timedelta -from unittest import IsolatedAsyncioTestCase -from unittest.mock import patch +import json +import pytest import urllib3 from openfga_sdk.configuration import Configuration from openfga_sdk.credentials import CredentialConfiguration, Credentials from openfga_sdk.exceptions import AuthenticationError -from openfga_sdk.sync import rest from openfga_sdk.sync.oauth2 import OAuth2Client +from openfga_sdk.sync.rest import RestClientResponse, RestClient -# Helper function to construct mock response -def mock_response(body, status): +def mock_response(body: dict | str, status) -> RestClientResponse: + body = json.dumps(body) if isinstance(body, dict) else body headers = urllib3.response.HTTPHeaderDict({"content-type": "application/json"}) - obj = urllib3.HTTPResponse(body, headers, status, preload_content=False) - return rest.RESTResponse(obj, obj.data) - - -class TestOAuth2Client(IsolatedAsyncioTestCase): - """TestOAuth2Client unit test""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_get_authentication_valid_client_credentials(self): - """ - Test getting authentication header when method is client credentials - """ - client = OAuth2Client(None) - client._access_token = "XYZ123" - client._access_expiry_time = datetime.now() + timedelta(seconds=60) - auth_header = client.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) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), - ) - rest_client = rest.RESTClientObject(Configuration()) - current_time = datetime.now() - client = OAuth2Client(credentials) - auth_header = client.get_authentication_header(rest_client) - self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"}) - self.assertEqual(client._access_token, "AABBCCDD") - self.assertGreaterEqual( - client._access_expiry_time, current_time + timedelta(seconds=120) - ) - expected_header = urllib3.response.HTTPHeaderDict( - { - "Accept": "application/json", - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": "openfga-sdk (python) 0.9.1", - } - ) - mock_request.assert_called_once_with( - method="POST", - url="https://issuer.fga.example/oauth/token", - headers=expected_header, - query_params=None, - body=None, - _preload_content=True, - _request_timeout=None, - post_params={ - "client_id": "myclientid", - "client_secret": "mysecret", - "audience": "myaudience", - "grant_type": "client_credentials", - }, - ) - rest_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) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), - ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - with self.assertRaises(AuthenticationError): - client.get_authentication_header(rest_client) - rest_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_authentication_obtain_with_expired_client_credentials_failed( - self, mock_request - ): - """ - Expired token should trigger a new token request - """ - - response_body = """ -{ - "reason": "Unauthorized" -} - """ - mock_request.return_value = mock_response(response_body, 403) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), - ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - - client._access_token = "XYZ123" - client._access_expiry_time = datetime.now() - timedelta(seconds=240) - - with self.assertRaises(AuthenticationError): - client.get_authentication_header(rest_client) - rest_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_authentication_unexpected_response_fails(self, mock_request): - """ - Receiving an unexpected response from the server should raise an exception - """ - - response_body = """ -This is not a JSON response - """ - mock_request.return_value = mock_response(response_body, 200) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), - ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - - with self.assertRaises(AuthenticationError): - client.get_authentication_header(rest_client) - rest_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_authentication_erroneous_response_fails(self, mock_request): - """ - Receiving an erroneous response from the server that's missing properties should raise an exception - """ - - response_body = """ -{ - "access_token": "AABBCCDD" -} - """ - mock_request.return_value = mock_response(response_body, 200) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), - ) - rest_client = rest.RESTClientObject(Configuration()) - client = OAuth2Client(credentials) - - with self.assertRaises(AuthenticationError): - client.get_authentication_header(rest_client) - rest_client.close() - - @patch.object(rest.RESTClientObject, "request") - async def test_get_authentication_retries_5xx_responses(self, mock_request): - """ - Receiving a 5xx response from the server should be retried - """ - - error_response_body = """ -{ - "code": "rate_limit_exceeded", - "message": "Rate Limit exceeded" -} - """ - - response_body = """ -{ - "expires_in": 120, - "access_token": "AABBCCDD" -} - """ - - mock_request.side_effect = [ - mock_response(error_response_body, 429), - mock_response(error_response_body, 429), - mock_response(error_response_body, 429), - mock_response(response_body, 200), - ] - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration( - client_id="myclientid", - client_secret="mysecret", - api_issuer="issuer.fga.example", - api_audience="myaudience", - ), - ) - - configuration = Configuration() - configuration.retry_params.max_retry = 5 - configuration.retry_params.retry_interval = 0 - - rest_client = rest.RESTClientObject(configuration) - client = OAuth2Client(credentials, configuration) - - auth_header = client.get_authentication_header(rest_client) - - mock_request.assert_called() - self.assertEqual(mock_request.call_count, 4) # 3 retries, 1 success - self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"}) - - rest_client.close() + obj = urllib3.HTTPResponse(body, headers, status) + return RestClientResponse( + response=obj, + data=body, + status=status, + ) + + +@pytest.fixture +def credentials(): + yield Credentials( + method="client_credentials", + configuration=CredentialConfiguration( + client_id="myclientid", + client_secret="mysecret", + api_issuer="issuer.fga.example", + api_audience="myaudience", + ), + ) + + +@pytest.fixture +def configuration(credentials): + yield Configuration( + api_url="https://api.fga.example", + credentials=credentials, + ) + + +@pytest.fixture +def oauth2_client(configuration): + yield OAuth2Client( + configuration=configuration, + ) + + +@pytest.fixture +def rest_client(configuration): + rest_client = RestClient( + configuration=configuration, + ) + yield rest_client + rest_client.close() + + +def test_get_authentication_valid_client_credentials( + rest_client, + oauth2_client, +): + oauth2_client.access_token = "XYZ123" + oauth2_client.access_expiry_time = datetime.now() + timedelta(seconds=60) + + auth_header = oauth2_client.get_authentication_header(rest_client) + + assert auth_header.name == "Authorization" + assert auth_header.value == "Bearer XYZ123" + + +def test_get_authentication_obtain_client_credentials( + rest_client, + oauth2_client, + mocker, +): + mock_request = mocker.patch.object( + rest_client, + "request", + return_value=mock_response( + {"expires_in": 120, "access_token": "AABBCCDD"}, 200 + ), + ) + + current_time = datetime.now() + + auth_header = oauth2_client.get_authentication_header(rest_client) + + assert auth_header.name == "Authorization" + assert auth_header.value == "Bearer AABBCCDD" + + assert oauth2_client.access_token == "AABBCCDD" + assert oauth2_client.access_expiry_time >= current_time + timedelta(seconds=120) + + expected_header = urllib3.response.HTTPHeaderDict( + { + "Accept": "application/json", + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": "openfga-sdk (python) 0.9.1", + } + ) + + mock_request.assert_called_once_with( + method="POST", + url="https://issuer.fga.example/oauth/token", + headers=expected_header, + post_params={ + "client_id": "myclientid", + "client_secret": "mysecret", + "audience": "myaudience", + "grant_type": "client_credentials", + }, + ) + + +def test_get_authentication_obtain_client_credentials_failed( + rest_client, + oauth2_client, + mocker, +): + mocker.patch.object( + rest_client, + "request", + return_value=mock_response({"reason": "Unauthorized"}, 403), + ) + + with pytest.raises(AuthenticationError): + oauth2_client.get_authentication_header(rest_client) + + +def test_get_authentication_obtain_with_expired_client_credentials_failed( + rest_client, + oauth2_client, + mocker, +): + mocker.patch.object( + rest_client, + "request", + return_value=mock_response({"reason": "Unauthorized"}, 403), + ) + + oauth2_client.access_token = "XYZ123" + oauth2_client.access_expiry_time = datetime.now() - timedelta(seconds=240) + + with pytest.raises(AuthenticationError): + oauth2_client.get_authentication_header(rest_client) + + +def test_get_authentication_unexpected_response_fails( + mocker, + rest_client, + oauth2_client, +): + mocker.patch.object( + rest_client, + "request", + return_value=mock_response("This is not a JSON response", 200), + ) + + with pytest.raises(AuthenticationError): + oauth2_client.get_authentication_header(rest_client) + + +def test_get_authentication_erroneous_response_fails( + mocker, + rest_client, + oauth2_client, +): + mocker.patch.object( + rest_client, + "request", + return_value=mock_response({"access_token": "AABBCCDD"}, 200), + ) + + with pytest.raises(AuthenticationError): + oauth2_client.get_authentication_header(rest_client) + + +def test_get_authentication_retries_5xx_responses( + mocker, + rest_client, + oauth2_client, +): + error_response = { + "code": "rate_limit_exceeded", + "message": "Rate Limit exceeded", + } + + success_response = {"expires_in": 120, "access_token": "AABBCCDD"} + + side_effect = [ + mock_response(error_response, 429), + mock_response(error_response, 429), + mock_response(error_response, 429), + mock_response(success_response, 200), + ] + + mock_request = mocker.patch.object(rest_client, "request", side_effect=side_effect) + + oauth2_client.configuration.retry_params.max_retries = 5 + oauth2_client.configuration.retry_params.min_wait_in_ms = 0 + + auth_header = oauth2_client.get_authentication_header(rest_client) + + mock_request.assert_called() + + assert mock_request.call_count == 4 + + assert auth_header.name == "Authorization" + assert auth_header.value == "Bearer AABBCCDD" diff --git a/test/sync/open_fga_api_test.py b/test/sync/open_fga_api_test.py index fc1fc871..d0f7a748 100644 --- a/test/sync/open_fga_api_test.py +++ b/test/sync/open_fga_api_test.py @@ -100,9 +100,8 @@ # 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} - ) + headers = {"content-type": "application/json", "Fga-Request-Id": request_id} + return urllib3.HTTPResponse( body.encode("utf-8"), headers, status, preload_content=False ) @@ -110,7 +109,7 @@ def http_mock_response(body, status): def mock_response(body, status): obj = http_mock_response(body, status) - return rest.RESTResponse(obj, obj.data) + return rest.RestClientResponse(response=obj, data=obj.data, status=obj.status) class TestOpenFgaApiSync(IsolatedAsyncioTestCase): @@ -124,7 +123,7 @@ def setUp(self): def tearDown(self): pass - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_check(self, mock_request): """Test case for check @@ -138,7 +137,7 @@ async def test_check(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -147,7 +146,7 @@ async def test_check(self, mock_request): ), authorization_model_id="01GXSA8YR785C4FYS3C0RTG7B1", ) - api_response = api_instance.check( + api_response = api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) @@ -167,12 +166,11 @@ async def test_check(self, mock_request): }, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_create_store(self, mock_request): """Test case for create_store @@ -188,11 +186,11 @@ async def test_create_store(self, mock_request): configuration = self.configuration with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CreateStoreRequest( name="test-store", ) - api_response = api_instance.create_store( + api_response = api_client.api.create_store( body=body, ) self.assertIsInstance(api_response, CreateStoreResponse) @@ -204,12 +202,11 @@ async def test_create_store(self, mock_request): query_params=[], post_params=[], body={"name": "test-store"}, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_delete_store(self, mock_request): """Test case for delete_store @@ -220,8 +217,8 @@ async def test_delete_store(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) - api_instance.delete_store() + + api_client.api.delete_store() mock_request.assert_called_once_with( "DELETE", "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4", @@ -229,12 +226,11 @@ async def test_delete_store(self, mock_request): query_params=[], post_params=[], body=None, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_expand(self, mock_request): """Test case for expand @@ -247,7 +243,7 @@ async def test_expand(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = ExpandRequest( tuple_key=ExpandRequestTupleKey( object="document:budget", @@ -255,7 +251,7 @@ async def test_expand(self, mock_request): ), authorization_model_id="01GXSA8YR785C4FYS3C0RTG7B1", ) - api_response = api_instance.expand( + api_response = api_client.api.expand( body=body, ) self.assertIsInstance(api_response, ExpandResponse) @@ -275,12 +271,11 @@ async def test_expand(self, mock_request): "tuple_key": {"object": "document:budget", "relation": "reader"}, "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_get_store(self, mock_request): """Test case for get_store @@ -297,9 +292,9 @@ async def test_get_store(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + # Get a store - api_response = api_instance.get_store() + api_response = api_client.api.get_store() self.assertIsInstance(api_response, GetStoreResponse) self.assertEqual(api_response.id, "01H0H015178Y2V4CX10C2KGHF4") self.assertEqual(api_response.name, "test_store") @@ -310,12 +305,11 @@ async def test_get_store(self, mock_request): body=None, query_params=[], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_list_objects(self, mock_request): """Test case for list_objects @@ -332,7 +326,7 @@ async def test_list_objects(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = ListObjectsRequest( authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", type="document", @@ -340,7 +334,7 @@ async def test_list_objects(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ) # Get all stores - api_response = api_instance.list_objects(body) + api_response = api_client.api.list_objects(body) self.assertIsInstance(api_response, ListObjectsResponse) self.assertEqual(api_response.objects, ["document:abcd1234"]) mock_request.assert_called_once_with( @@ -355,12 +349,11 @@ async def test_list_objects(self, mock_request): "relation": "reader", "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_list_stores(self, mock_request): """Test case for list_stores @@ -390,9 +383,9 @@ async def test_list_stores(self, mock_request): mock_request.return_value = mock_response(response_body, 200) configuration = self.configuration with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + # Get all stores - api_response = api_instance.list_stores( + api_response = api_client.api.list_stores( page_size=1, continuation_token="continuation_token_example", ) @@ -428,12 +421,11 @@ async def test_list_stores(self, mock_request): ("continuation_token", "continuation_token_example"), ], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_list_users(self, mock_request): """ Test case for list_users @@ -468,7 +460,6 @@ async def test_list_users(self, mock_request): configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) request = ListUsersRequest( object="test:123", @@ -495,7 +486,7 @@ async def test_list_users(self, mock_request): }, ] - response = api_instance.list_users(request) + response = api_client.api.list_users(request) self.assertIsInstance(response, ListUsersResponse) @@ -548,13 +539,12 @@ async def test_list_users(self, mock_request): }, ], }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) api_client.close() - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read(self, mock_request): """Test case for read @@ -579,7 +569,7 @@ async def test_read(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = ReadRequest( tuple_key=ReadRequestTupleKey( object="document:2021-budget", @@ -589,7 +579,7 @@ async def test_read(self, mock_request): page_size=50, continuation_token="eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", ) - api_response = api_instance.read( + api_response = api_client.api.read( body=body, ) self.assertIsInstance(api_response, ReadResponse) @@ -618,11 +608,10 @@ async def test_read(self, mock_request): "page_size": 50, "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read_assertions(self, mock_request): """Test case for read_assertions @@ -647,8 +636,8 @@ async def test_read_assertions(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) - api_response = api_instance.read_assertions( + + api_response = api_client.api.read_assertions( "01G5JAVJ41T49E9TT3SKVS7X1J", ) self.assertIsInstance(api_response, ReadAssertionsResponse) @@ -671,11 +660,10 @@ async def test_read_assertions(self, mock_request): body=None, query_params=[], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read_authorization_model(self, mock_request): """Test case for read_authorization_model @@ -720,10 +708,9 @@ async def test_read_authorization_model(self, mock_request): # Enter a context with an instance of the API client with 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( + api_response = api_client.api.read_authorization_model( "01G5JAVJ41T49E9TT3SKVS7X1J", ) self.assertIsInstance(api_response, ReadAuthorizationModelResponse) @@ -763,11 +750,10 @@ async def test_read_authorization_model(self, mock_request): body=None, query_params=[], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_read_changes(self, mock_request): """Test case for read_changes @@ -795,10 +781,9 @@ async def test_read_changes(self, mock_request): # Enter a context with an instance of the API client with 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( + api_response = api_client.api.read_changes( page_size=1, continuation_token="abcdefg", start_time="2022-01-01T00:00:00+00:00", @@ -831,11 +816,10 @@ async def test_read_changes(self, mock_request): ("start_time", "2022-01-01T00:00:00+00:00"), ], post_params=[], - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write(self, mock_request): """Test case for write @@ -848,7 +832,6 @@ async def test_write(self, mock_request): # Enter a context with an instance of the API client with 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 @@ -864,7 +847,7 @@ async def test_write(self, mock_request): ), authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", ) - api_instance.write( + api_client.api.write( body, ) mock_request.assert_called_once_with( @@ -885,11 +868,10 @@ async def test_write(self, mock_request): }, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write_delete(self, mock_request): """Test case for write @@ -902,7 +884,6 @@ async def test_write_delete(self, mock_request): # Enter a context with an instance of the API client with 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 @@ -918,7 +899,7 @@ async def test_write_delete(self, mock_request): ), authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J", ) - api_instance.write( + api_client.api.write( body, ) mock_request.assert_called_once_with( @@ -939,11 +920,10 @@ async def test_write_delete(self, mock_request): }, "authorization_model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write_assertions(self, mock_request): """Test case for write_assertions @@ -956,7 +936,6 @@ async def test_write_assertions(self, mock_request): # Enter a context with an instance of the API client with 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( @@ -972,7 +951,7 @@ async def test_write_assertions(self, mock_request): ], ) # Upsert assertions for an authorization model ID - api_instance.write_assertions( + api_client.api.write_assertions( authorization_model_id="xyz0123", body=body, ) @@ -994,11 +973,10 @@ async def test_write_assertions(self, mock_request): } ] }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_write_authorization_model(self, mock_request): """Test case for write_authorization_model @@ -1010,7 +988,6 @@ async def test_write_authorization_model(self, mock_request): configuration.store_id = store_id with 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( @@ -1040,7 +1017,7 @@ async def test_write_authorization_model(self, mock_request): ], ) # Create a new authorization model - api_response = api_instance.write_authorization_model(body) + api_response = api_client.api.write_authorization_model(body) self.assertIsInstance(api_response, WriteAuthorizationModelResponse) expected_response = WriteAuthorizationModelResponse( authorization_model_id="01G5JAVJ41T49E9TT3SKVS7X1J" @@ -1076,150 +1053,111 @@ async def test_write_authorization_model(self, mock_request): } ], }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) def test_default_scheme(self): """ Ensure default scheme is https """ - configuration = Configuration(api_host="localhost") - self.assertEqual(configuration.api_scheme, "https") + configuration = Configuration(api_url="localhost") + self.assertEqual(configuration.api_url, "https://localhost") + + def test_http_scheme(self): + """ + Ensure the scheme can be overridden + """ + configuration = Configuration(api_url="http://localhost") + self.assertEqual(configuration.api_url, "http://localhost") def test_host_port(self): """ Ensure host has port will not raise error """ - configuration = Configuration(api_host="localhost:3000") - self.assertEqual(configuration.api_host, "localhost:3000") + configuration = Configuration(api_url="localhost:3000") + self.assertEqual(configuration.api_url, "https://localhost:3000") def test_configuration_missing_host(self): """ Test whether FgaValidationException is raised if configuration does not have host specified """ - configuration = 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 = Configuration(api_host="localhost") - configuration.api_scheme = None - self.assertRaises(FgaValidationException, configuration.is_valid) + with self.assertRaises(FgaValidationException): + configuration = Configuration(api_url="http://") def test_configuration_bad_scheme(self): """ Test whether ApiValueError is raised if scheme is bad """ - configuration = Configuration(api_host="localhost", api_scheme="foo") - self.assertRaises(ApiValueError, configuration.is_valid) + with self.assertRaises(FgaValidationException): + configuration = Configuration(api_url="foo://localhost") + print(configuration.api_url) def test_configuration_bad_host(self): """ Test whether ApiValueError is raised if host is bad """ - configuration = Configuration(api_host="/", api_scheme="foo") - self.assertRaises(ApiValueError, configuration.is_valid) + with self.assertRaises(FgaValidationException): + configuration = Configuration(api_url="https://?") def test_configuration_has_path(self): """ Test whether ApiValueError is raised if host has path """ - configuration = Configuration(api_host="localhost/mypath", api_scheme="http") - self.assertRaises(ApiValueError, configuration.is_valid) + with self.assertRaises(FgaValidationException): + configuration = Configuration(api_url="localhost/mypath") def test_configuration_has_query(self): """ Test whether ApiValueError is raised if host has query """ - configuration = Configuration( - api_host="localhost?mypath=foo", api_scheme="http" - ) - self.assertRaises(ApiValueError, configuration.is_valid) + with self.assertRaises(FgaValidationException): + configuration = Configuration(api_url="localhost?mypath=foo") def test_configuration_store_id_invalid(self): """ Test whether ApiValueError is raised if host has query """ - configuration = Configuration( - api_host="localhost", api_scheme="http", store_id="abcd" - ) - self.assertRaises(FgaValidationException, configuration.is_valid) + with self.assertRaises(FgaValidationException): + configuration = Configuration(api_url="localhost", store_id="abcd") def test_url(self): """ Ensure that api_url is set and validated """ configuration = Configuration(api_url="http://localhost:8080") - self.assertEqual(configuration.api_url, "http://localhost:8080") configuration.is_valid() - def test_url_with_scheme_and_host(self): - """ - Ensure that api_url takes precedence over api_host and scheme - """ - configuration = Configuration( - api_url="http://localhost:8080", api_host="localhost:8080", api_scheme="foo" - ) - self.assertEqual(configuration.api_url, "http://localhost:8080") - configuration.is_valid() # Should not throw and complain about scheme being invalid - - def test_timeout_millisec(self): + def test_timeout(self): """ - Ensure that timeout_millisec is set and validated + Ensure that timeout is set and validated """ configuration = Configuration( api_url="http://localhost:8080", - timeout_millisec=10000, + timeout=10000, ) - self.assertEqual(configuration.timeout_millisec, 10000) + self.assertEqual(configuration.timeout, 10000) 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 = Configuration( - api_scheme="bad", - api_host="api.fga.example", - ) - configuration.store_id = "xyz123" - # Enter a context with an instance of the API client - with 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 = Configuration( - api_scheme="http", - api_host="api.fga.example", + api_url="api.fga.example", ) # Notice the store_id is not set # Enter a context with an instance of the API client with 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( + api_client.api.read_authorization_models( page_size=1, continuation_token="abcdefg" ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_400_error(self, mock_request): """ Test to ensure 400 errors are handled properly @@ -1237,7 +1175,7 @@ async def test_400_error(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1246,7 +1184,7 @@ async def test_400_error(self, mock_request): ), ) with self.assertRaises(ValidationException) as api_exception: - api_instance.check( + api_client.api.check( body=body, ) self.assertIsInstance( @@ -1264,7 +1202,7 @@ async def test_400_error(self, mock_request): api_exception.exception.header.get(FGA_REQUEST_ID), request_id ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_404_error(self, mock_request): """ Test to ensure 404 errors are handled properly @@ -1282,7 +1220,7 @@ async def test_404_error(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1291,7 +1229,7 @@ async def test_404_error(self, mock_request): ), ) with self.assertRaises(NotFoundException) as api_exception: - api_instance.check( + api_client.api.check( body=body, ) self.assertIsInstance( @@ -1306,7 +1244,7 @@ async def test_404_error(self, mock_request): api_exception.exception.parsed_exception.message, "Endpoint not enabled" ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_429_error_no_retry(self, mock_request): """ Test to ensure 429 errors are handled properly. @@ -1327,7 +1265,7 @@ async def test_429_error_no_retry(self, mock_request): configuration.store_id = store_id configuration.retry_params = retry with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1336,14 +1274,14 @@ async def test_429_error_no_retry(self, mock_request): ), ) with self.assertRaises(RateLimitExceededError) as api_exception: - api_instance.check( + api_client.api.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") + @patch.object(rest.RestClient, "request") async def test_429_error_first_error(self, mock_request): """ Test to ensure 429 errors are handled properly. @@ -1368,7 +1306,7 @@ async def test_429_error_first_error(self, mock_request): configuration.store_id = store_id configuration.retry_params = retry with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1376,7 +1314,7 @@ async def test_429_error_first_error(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ), ) - api_response = api_instance.check( + api_response = api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) @@ -1384,7 +1322,7 @@ async def test_429_error_first_error(self, mock_request): mock_request.assert_called() self.assertEqual(mock_request.call_count, 2) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_500_error(self, mock_request): """ Test to ensure 500 errors are handled properly @@ -1401,10 +1339,10 @@ async def test_500_error(self, mock_request): configuration = self.configuration configuration.store_id = store_id - configuration.retry_params.max_retry = 0 + configuration.retry_params.max_retries = 0 with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1413,7 +1351,7 @@ async def test_500_error(self, mock_request): ), ) with self.assertRaises(ServiceException) as api_exception: - api_instance.check( + api_client.api.check( body=body, ) self.assertIsInstance( @@ -1430,7 +1368,7 @@ async def test_500_error(self, mock_request): mock_request.assert_called() self.assertEqual(mock_request.call_count, 1) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_500_error_retry(self, mock_request): """ Test to ensure 5xx retries are handled properly @@ -1455,7 +1393,7 @@ async def test_500_error_retry(self, mock_request): configuration.retry_params = retry with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1464,7 +1402,7 @@ async def test_500_error_retry(self, mock_request): ), ) - api_response = api_instance.check( + api_response = api_client.api.check( body=body, ) @@ -1472,7 +1410,7 @@ async def test_500_error_retry(self, mock_request): mock_request.assert_called() self.assertEqual(mock_request.call_count, 5) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_501_error_retry(self, mock_request): """ Test to ensure 501 responses are not auto-retried @@ -1496,7 +1434,7 @@ async def test_501_error_retry(self, mock_request): configuration.retry_params = retry with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1505,13 +1443,13 @@ async def test_501_error_retry(self, mock_request): ), ) with self.assertRaises(ServiceException): - api_instance.check( + api_client.api.check( body=body, ) mock_request.assert_called() self.assertEqual(mock_request.call_count, 1) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_check_api_token(self, mock_request): """Test case for API token @@ -1529,7 +1467,7 @@ async def test_check_api_token(self, mock_request): configuration=CredentialConfiguration(api_token="TOKEN1"), ) with ApiClient(configuration) as api_client: - api_instance = open_fga_api.OpenFgaApi(api_client) + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1537,20 +1475,19 @@ async def test_check_api_token(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ), ) - api_response = api_instance.check( + api_response = api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) self.assertTrue(api_response.allowed) # Make sure the API was called with the right data - expected_headers = urllib3.response.HTTPHeaderDict( - { - "Accept": "application/json", - "Content-Type": "application/json", - "User-Agent": "openfga-sdk python/0.9.1", - "Authorization": "Bearer TOKEN1", - } - ) + expected_headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.1", + "Authorization": "Bearer TOKEN1", + } + mock_request.assert_called_once_with( "POST", "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", @@ -1564,11 +1501,10 @@ async def test_check_api_token(self, mock_request): "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", } }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) - @patch.object(rest.RESTClientObject, "request") + @patch.object(rest.RestClient, "request") async def test_check_custom_header(self, mock_request): """Test case for custom header @@ -1582,8 +1518,8 @@ async def test_check_custom_header(self, mock_request): configuration = self.configuration configuration.store_id = store_id with ApiClient(configuration) as api_client: - api_client.set_default_header("Custom Header", "custom value") - api_instance = open_fga_api.OpenFgaApi(api_client) + api_client.headers.add_header("Custom Header", "custom value") + body = CheckRequest( tuple_key=TupleKey( object="document:2021-budget", @@ -1591,20 +1527,19 @@ async def test_check_custom_header(self, mock_request): user="user:81684243-9356-4421-8fbf-a4f8d36aa31b", ), ) - api_response = api_instance.check( + api_response = api_client.api.check( body=body, ) self.assertIsInstance(api_response, CheckResponse) self.assertTrue(api_response.allowed) # Make sure the API was called with the right data - expected_headers = urllib3.response.HTTPHeaderDict( - { - "Accept": "application/json", - "Content-Type": "application/json", - "User-Agent": "openfga-sdk python/0.9.1", - "Custom Header": "custom value", - } - ) + expected_headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "openfga-sdk python/0.9.1", + "Custom Header": "custom value", + } + mock_request.assert_called_once_with( "POST", "http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check", @@ -1618,8 +1553,7 @@ async def test_check_custom_header(self, mock_request): "user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b", } }, - _preload_content=ANY, - _request_timeout=None, + timeout=None, ) diff --git a/test/sync/rest_test.py b/test/sync/rest_test.py index bb59d40c..18d0f6e0 100644 --- a/test/sync/rest_test.py +++ b/test/sync/rest_test.py @@ -25,16 +25,18 @@ UnauthorizedException, ValidationException, ) -from openfga_sdk.sync.rest import RESTClientObject, RESTResponse +from openfga_sdk.sync.rest import RestClient, RestClientResponse -def test_restresponse_init(): +def test_RestClientResponse_init(): mock_resp = MagicMock() mock_resp.status = 200 mock_resp.reason = "OK" resp_data = b'{"test":"data"}' - rest_resp = RESTResponse(mock_resp, resp_data) + rest_resp = RestClientResponse( + response=mock_resp, data=resp_data, status=200, reason="OK" + ) assert rest_resp.status == 200 assert rest_resp.reason == "OK" @@ -42,24 +44,28 @@ def test_restresponse_init(): assert rest_resp.response == mock_resp -def test_restresponse_getheaders(): +def test_RestClientResponse_headers(): mock_resp = MagicMock() mock_resp.headers = {"Content-Type": "application/json", "X-Testing": "true"} - rest_resp = RESTResponse(mock_resp, b"") - headers = rest_resp.getheaders() + rest_resp = RestClientResponse( + response=mock_resp, data=b"", status=200, reason="OK" + ) + headers = rest_resp.headers assert headers["Content-Type"] == "application/json" assert headers["X-Testing"] == "true" -def test_restresponse_getheader(): +def test_RestClientResponse_header(): mock_resp = MagicMock() mock_resp.headers = {"Content-Type": "application/json"} - rest_resp = RESTResponse(mock_resp, b"") - val = rest_resp.getheader("Content-Type") - missing = rest_resp.getheader("X-Not-Here", default="fallback") + rest_resp = RestClientResponse( + response=mock_resp, data=b"", status=200, reason="OK" + ) + val = rest_resp.header("Content-Type") + missing = rest_resp.header("X-Not-Here", default="fallback") assert val == "application/json" assert missing == "fallback" @@ -75,8 +81,8 @@ def test_build_request_json_body(): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -85,12 +91,12 @@ def test_build_request_json_body(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) req_args = client.build_request( method="POST", url="http://example.com/test", @@ -98,10 +104,10 @@ def test_build_request_json_body(): headers={"Content-Type": "application/json"}, ) - assert req_args["method"] == "POST" - assert req_args["url"] == "http://example.com/test" - assert req_args["headers"]["Content-Type"] == "application/json" - assert json.loads(req_args["body"]) == {"foo": "bar"} + assert req_args.method == "POST" + assert req_args.url == "http://example.com/test" + assert req_args.headers["Content-Type"] == "application/json" + assert json.loads(req_args.body) == {"foo": "bar"} def test_build_request_multipart(): @@ -114,8 +120,8 @@ def test_build_request_multipart(): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -124,12 +130,12 @@ def test_build_request_multipart(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) req_args = client.build_request( method="POST", url="http://example.com/upload", @@ -137,48 +143,11 @@ def test_build_request_multipart(): headers={"Content-Type": "multipart/form-data"}, ) - assert req_args["method"] == "POST" - assert req_args["url"] == "http://example.com/upload" - assert "Content-Type" not in req_args["headers"] - assert req_args["encode_multipart"] is True - assert req_args["fields"] == {"file": ("filename.txt", b"contents", "text/plain")} - - -def test_build_request_timeout(): - mock_config = MagicMock( - spec=[ - "verify_ssl", - "ssl_ca_cert", - "cert_file", - "key_file", - "assert_hostname", - "retries", - "socket_options", - "connection_pool_maxsize", - "timeout_millisec", - "proxy", - "proxy_headers", - ] - ) - mock_config.ssl_ca_cert = None - mock_config.cert_file = None - mock_config.key_file = None - mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 - mock_config.proxy = None - mock_config.proxy_headers = None - - client = RESTClientObject(configuration=mock_config) - req_args = client.build_request( - method="GET", - url="http://example.com", - _request_timeout=10.0, - ) - - # We'll just confirm that the "timeout" object was set to 10.0 - # A deeper check might be verifying urllib3.Timeout, but this suffices. - assert req_args["timeout"].total == 10.0 + assert req_args.method == "POST" + assert req_args.url == "http://example.com/upload" + assert "Content-Type" not in req_args.headers + assert req_args.multipart is True + assert req_args.fields == {"file": ("filename.txt", b"contents", "text/plain")} def test_handle_response_exception_success(): @@ -191,8 +160,8 @@ def test_handle_response_exception_success(): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -201,16 +170,16 @@ def test_handle_response_exception_success(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_response = MagicMock() mock_response.status = 200 - client.handle_response_exception(mock_response) # no exception + client._handle_response_exception(mock_response) # no exception @pytest.mark.parametrize( @@ -235,8 +204,8 @@ def test_handle_response_exception_error(status, exc): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -245,17 +214,17 @@ def test_handle_response_exception_error(status, exc): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_response = MagicMock() mock_response.status = status with pytest.raises(exc): - client.handle_response_exception(mock_response) + client._handle_response_exception(mock_response) def test_close(): @@ -268,8 +237,8 @@ def test_close(): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -278,12 +247,12 @@ def test_close(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_pool_manager = MagicMock() client.pool_manager = mock_pool_manager @@ -291,98 +260,6 @@ def test_close(): mock_pool_manager.clear.assert_called_once() -def test_request_preload_content(): - mock_config = MagicMock( - spec=[ - "verify_ssl", - "ssl_ca_cert", - "cert_file", - "key_file", - "assert_hostname", - "retries", - "socket_options", - "connection_pool_maxsize", - "timeout_millisec", - "proxy", - "proxy_headers", - ] - ) - mock_config.ssl_ca_cert = None - mock_config.cert_file = None - mock_config.key_file = None - mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 - mock_config.proxy = None - mock_config.proxy_headers = None - - client = RESTClientObject(configuration=mock_config) - mock_pool_manager = MagicMock() - client.pool_manager = mock_pool_manager - - mock_raw_response = MagicMock() - mock_raw_response.status = 200 - mock_raw_response.reason = "OK" - mock_raw_response.data = b'{"some":"data"}' - - mock_pool_manager.request.return_value = mock_raw_response - - resp = client.request(method="GET", url="http://example.com", _preload_content=True) - - mock_pool_manager.request.assert_called_once() - assert isinstance(resp, RESTResponse) - assert resp.status == 200 - assert resp.data == b'{"some":"data"}' - mock_pool_manager.clear.assert_called_once() - - -def test_request_no_preload_content(): - mock_config = MagicMock( - spec=[ - "verify_ssl", - "ssl_ca_cert", - "cert_file", - "key_file", - "assert_hostname", - "retries", - "socket_options", - "connection_pool_maxsize", - "timeout_millisec", - "proxy", - "proxy_headers", - ] - ) - mock_config.ssl_ca_cert = None - mock_config.cert_file = None - mock_config.key_file = None - mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 - mock_config.proxy = None - mock_config.proxy_headers = None - - client = RESTClientObject(configuration=mock_config) - mock_pool_manager = MagicMock() - client.pool_manager = mock_pool_manager - - mock_raw_response = MagicMock() - mock_raw_response.status = 200 - mock_raw_response.reason = "OK" - mock_raw_response.data = b"unused" - - mock_pool_manager.request.return_value = mock_raw_response - - resp = client.request( - method="GET", url="http://example.com", _preload_content=False - ) - - mock_pool_manager.request.assert_called_once() - # We expect the raw HTTPResponse - assert resp == mock_raw_response - assert resp.status == 200 - mock_pool_manager.clear.assert_called_once() - - def test_stream_happy_path(): mock_config = MagicMock( spec=[ @@ -393,8 +270,8 @@ def test_stream_happy_path(): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -403,12 +280,12 @@ def test_stream_happy_path(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_pool_manager = MagicMock() client.pool_manager = mock_pool_manager @@ -444,8 +321,8 @@ def test_stream_partial_chunks(): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -454,12 +331,12 @@ def test_stream_partial_chunks(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_pool_manager = MagicMock() client.pool_manager = mock_pool_manager @@ -496,8 +373,8 @@ def test_stream_exception_in_chunks(): "assert_hostname", "retries", "socket_options", - "connection_pool_maxsize", - "timeout_millisec", + "connection_pool_size_max", + "tmeout", "proxy", "proxy_headers", ] @@ -506,12 +383,12 @@ def test_stream_exception_in_chunks(): mock_config.cert_file = None mock_config.key_file = None mock_config.verify_ssl = True - mock_config.connection_pool_maxsize = 4 - mock_config.timeout_millisec = 5000 + mock_config.connection_pool_size_max = 4 + mock_config.tmeout = 5000 mock_config.proxy = None mock_config.proxy_headers = None - client = RESTClientObject(configuration=mock_config) + client = RestClient(configuration=mock_config) mock_pool_manager = MagicMock() client.pool_manager = mock_pool_manager diff --git a/test/telemetry/attributes_test.py b/test/telemetry/attributes_test.py index aeb30040..22ac660e 100644 --- a/test/telemetry/attributes_test.py +++ b/test/telemetry/attributes_test.py @@ -16,12 +16,10 @@ import pytest -from urllib3 import HTTPResponse - from openfga_sdk.credentials import CredentialConfiguration, Credentials from openfga_sdk.models.batch_check_request import BatchCheckRequest from openfga_sdk.models.check_request import CheckRequest -from openfga_sdk.rest import RESTResponse +from openfga_sdk.rest import RestClientResponse from openfga_sdk.telemetry.attributes import ( TelemetryAttributes, ) @@ -122,37 +120,14 @@ def test_from_request_without_optional_params(telemetry_attributes): assert TelemetryAttributes.fga_client_request_client_id not in attributes -def test_from_response_with_http_response(telemetry_attributes): - response = MagicMock(spec=HTTPResponse) - response.status = 200 - response.getheader.side_effect = lambda header: { - "openfga-authorization-model-id": "model_123", - "fga-query-duration-ms": "50", - }.get(header) - - credentials = Credentials( - method="client_credentials", - configuration=CredentialConfiguration(client_id="client_123"), - ) - attributes = telemetry_attributes.fromResponse( - response=response, credentials=credentials - ) - - assert attributes[TelemetryAttributes.http_response_status_code] == 200 - assert attributes[TelemetryAttributes.fga_client_response_model_id] == "model_123" - assert attributes[TelemetryAttributes.http_server_request_duration] == "50" - assert attributes[TelemetryAttributes.fga_client_request_client_id] == "client_123" - - def test_from_response_with_rest_response(telemetry_attributes): - response = MagicMock(spec=RESTResponse) + response = MagicMock(spec=RestClientResponse) response.status = 404 response.headers = { "openfga-authorization-model-id": "model_404", "fga-query-duration-ms": "100", } - - response.getheader = lambda key: response.headers.get(key) + response.header = lambda key: response.headers.get(key) credentials = Credentials( method="client_credentials",