diff --git a/README.md b/README.md index fd19182b..f2c08101 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,17 @@ You need your store id to call the OpenFGA API (unless it is to call the [Create If your server is configured with [authentication enabled](https://openfga.dev/docs/getting-started/setup-openfga#configuring-authentication), you also need to have your credentials ready. +To call a different store for a single request, pass `store_id` in the `options`. It overrides the store ID in the configuration for that request only, so one client can work with several stores. If the client has an `authorization_model_id` configured, pass the other store's model ID in the options as well: + +```python +store = await fga_client.get_store(options={"store_id": "01JD9Z1F6Y0XWAGKM9KYQ8GXMC"}) + +response = await fga_client.check( + body=ClientCheckRequest(user="user:anne", relation="viewer", object="document:roadmap"), + options={"store_id": "01JD9Z1F6Y0XWAGKM9KYQ8GXMC"}, +) +``` + ### Calling the API #### Stores diff --git a/openfga_sdk/api/open_fga_api.py b/openfga_sdk/api/open_fga_api.py index 124c0e08..9abc6a01 100644 --- a/openfga_sdk/api/open_fga_api.py +++ b/openfga_sdk/api/open_fga_api.py @@ -35,6 +35,7 @@ class OpenFgaApi: "_headers", "_retry_params", "_streaming", + "_store_id", ] _COMMON_ERROR_RESPONSE_TYPES = { @@ -71,6 +72,16 @@ async def __aexit__(self, exc_type, exc_value, traceback): async def close(self): await self.api_client.close() + def _resolve_store_id(self, options: dict | None = None) -> str: + """ + Return the store ID passed for this request in `_store_id`, falling back + to the store ID in the api_client's configuration. + """ + store_id = (options or {}).get("_store_id") + if store_id: + return store_id + return self.api_client._get_store_id() + async def _execute( self, method: str, @@ -103,7 +114,8 @@ async def _execute( telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { TelemetryAttributes.fga_client_request_method: operation_name, - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), + TelemetryAttributes.fga_client_request_store_id: options.get("_store_id") + or self.api_client.get_store_id(), TelemetryAttributes.fga_client_request_model_id: options.get( "authorization_model_id", "" ), @@ -467,11 +479,7 @@ async def batch_check_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `batch_check`" ) - 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 = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/batch-check", @@ -564,11 +572,7 @@ async def check_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `check`" ) - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `check`" - ) - store_id = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/check", @@ -739,11 +743,7 @@ async def delete_store_with_http_info(self, **kwargs): ) local_var_params[key] = val del local_var_params["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 = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="DELETE", path=f"/stores/{store_id}", @@ -835,11 +835,7 @@ async def expand_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `expand`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/expand", @@ -918,11 +914,7 @@ async def get_store_with_http_info(self, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="GET", path=f"/stores/{store_id}", @@ -1014,11 +1006,7 @@ async def list_objects_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `list_objects`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/list-objects", @@ -1214,11 +1202,7 @@ async def list_users_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `list_users`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/list-users", @@ -1311,11 +1295,7 @@ async def read_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `read`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/read", @@ -1409,11 +1389,7 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) raise ApiValueError( "Missing the required parameter `authorization_model_id` when calling `read_assertions`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="GET", path=f"/stores/{store_id}/assertions/{authorization_model_id}", @@ -1504,11 +1480,7 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): raise ApiValueError( "Missing the required parameter `id` when calling `read_authorization_model`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="GET", path=f"/stores/{store_id}/authorization-models/{id}", @@ -1597,11 +1569,7 @@ async def read_authorization_models_with_http_info(self, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - 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() + store_id = self._resolve_store_id(local_var_params) query_params = [] if local_var_params.get("page_size") is not None: query_params.append(("page_size", local_var_params["page_size"])) @@ -1708,11 +1676,7 @@ async def read_changes_with_http_info(self, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - 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() + store_id = self._resolve_store_id(local_var_params) query_params = [] if local_var_params.get("type") is not None: query_params.append(("type", local_var_params["type"])) @@ -1816,11 +1780,7 @@ async def streamed_list_objects_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `streamed_list_objects`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/streamed-list-objects", @@ -1913,11 +1873,7 @@ async def write_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `write`" ) - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `write`" - ) - store_id = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/write", @@ -2026,11 +1982,7 @@ async def write_assertions_with_http_info( raise ApiValueError( "Missing the required parameter `body` when calling `write_assertions`" ) - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `write_assertions`" - ) - store_id = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="PUT", path=f"/stores/{store_id}/assertions/{authorization_model_id}", @@ -2123,11 +2075,7 @@ async def write_authorization_model_with_http_info(self, body, **kwargs): raise ApiValueError( "Missing the required parameter `body` when calling `write_authorization_model`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return await self._execute( method="POST", path=f"/stores/{store_id}/authorization-models", diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index b48a3ec8..17d2b437 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -110,7 +110,7 @@ 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 + Return the API kwargs for the client options """ kwargs = {} if options is not None: @@ -124,6 +124,8 @@ def options_to_kwargs( kwargs["_headers"] = options["headers"] if options.get("retry_params"): kwargs["_retry_params"] = options["retry_params"] + if options.get("store_id"): + kwargs["_store_id"] = options["store_id"] return kwargs diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index c736daec..5797f73e 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -110,7 +110,7 @@ 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 + Return the API kwargs for the client options """ kwargs = {} if options is not None: @@ -124,6 +124,8 @@ def options_to_kwargs( kwargs["_headers"] = options["headers"] if options.get("retry_params"): kwargs["_retry_params"] = options["retry_params"] + if options.get("store_id"): + kwargs["_store_id"] = options["store_id"] return kwargs diff --git a/openfga_sdk/sync/open_fga_api.py b/openfga_sdk/sync/open_fga_api.py index 8518138e..9b29d296 100644 --- a/openfga_sdk/sync/open_fga_api.py +++ b/openfga_sdk/sync/open_fga_api.py @@ -37,6 +37,7 @@ class OpenFgaApi: "_headers", "_retry_params", "_streaming", + "_store_id", ] _COMMON_ERROR_RESPONSE_TYPES = { @@ -71,6 +72,16 @@ def __exit__(self): def close(self): self.api_client.close() + def _resolve_store_id(self, options: dict | None = None) -> str: + """ + Return the store ID passed for this request in `_store_id`, falling back + to the store ID in the api_client's configuration. + """ + store_id = (options or {}).get("_store_id") + if store_id: + return store_id + return self.api_client._get_store_id() + def _execute( self, method: str, @@ -103,7 +114,8 @@ def _execute( telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { TelemetryAttributes.fga_client_request_method: operation_name, - TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), + TelemetryAttributes.fga_client_request_store_id: options.get("_store_id") + or self.api_client.get_store_id(), TelemetryAttributes.fga_client_request_model_id: options.get( "authorization_model_id", "" ), @@ -465,11 +477,7 @@ def batch_check_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `batch_check`" ) - 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 = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -562,11 +570,7 @@ def check_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `check`" ) - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `check`" - ) - store_id = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -730,11 +734,7 @@ def delete_store_with_http_info(self, **kwargs): local_var_params[key] = val del local_var_params["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 = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="DELETE", @@ -826,11 +826,7 @@ def expand_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `expand`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -911,11 +907,7 @@ def get_store_with_http_info(self, **kwargs): local_var_params[key] = val del local_var_params["kwargs"] - 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() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="GET", @@ -1007,11 +999,7 @@ def list_objects_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `list_objects`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -1205,11 +1193,7 @@ def list_users_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `list_users`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -1302,11 +1286,7 @@ def read_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `read`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -1399,11 +1379,7 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): "Missing the required parameter `authorization_model_id` when calling `read_assertions`" ) - 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() + store_id = self._resolve_store_id(local_var_params) auth_model_id = urllib.parse.quote( str(local_var_params["authorization_model_id"]), safe="" ) @@ -1498,11 +1474,7 @@ def read_authorization_model_with_http_info(self, id, **kwargs): "Missing the required parameter `id` when calling `read_authorization_model`" ) - 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() + store_id = self._resolve_store_id(local_var_params) model_id = urllib.parse.quote(str(local_var_params["id"]), safe="") return self._execute( @@ -1591,11 +1563,7 @@ def read_authorization_models_with_http_info(self, **kwargs): local_var_params[key] = val del local_var_params["kwargs"] - 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() + store_id = self._resolve_store_id(local_var_params) query_params = [] if local_var_params.get("page_size") is not None: @@ -1700,11 +1668,7 @@ def read_changes_with_http_info(self, **kwargs): local_var_params[key] = val del local_var_params["kwargs"] - 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() + store_id = self._resolve_store_id(local_var_params) query_params = [] if local_var_params.get("type") is not None: @@ -1809,11 +1773,7 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `streamed_list_objects`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -1906,11 +1866,7 @@ def write_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `write`" ) - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `write`" - ) - store_id = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", @@ -2017,11 +1973,7 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs "Missing the required parameter `body` when calling `write_assertions`" ) - if self.api_client._get_store_id() is None: - raise ApiValueError( - "Store ID expected in api_client's configuration when calling `write_assertions`" - ) - store_id = self.api_client._get_store_id() + store_id = self._resolve_store_id(local_var_params) auth_model_id = urllib.parse.quote( str(local_var_params["authorization_model_id"]), safe="" ) @@ -2117,11 +2069,7 @@ def write_authorization_model_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `write_authorization_model`" ) - 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() + store_id = self._resolve_store_id(local_var_params) return self._execute( method="POST", diff --git a/test/client/client_test.py b/test/client/client_test.py index 9850b5d1..4cdcccee 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -312,6 +312,84 @@ async def test_get_store(self, mock_request): ) await api_client.close() + @patch.object(rest.RESTClientObject, "request") + async def test_get_store_with_store_id_option(self, mock_request): + """get_store uses the store_id from the options over the configured one""" + other_store_id = "01JD9Z1F6Y0XWAGKM9KYQ8GXMC" + response_body = f""" +{{ + "id": "{other_store_id}", + "name": "store2", + "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={"store_id": other_store_id} + ) + self.assertEqual(api_response.id, other_store_id) + mock_request.assert_called_once_with( + "GET", + f"http://api.fga.example/stores/{other_store_id}", + headers=ANY, + body=None, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + # the configured store ID is left alone + self.assertEqual(api_client.get_store_id(), store_id) + + @patch.object(rest.RESTClientObject, "request") + async def test_check_with_store_id_option(self, mock_request): + """check uses the store_id from the options over the configured one""" + other_store_id = "01JD9Z1F6Y0XWAGKM9KYQ8GXMC" + mock_request.return_value = mock_response('{"allowed": true}', 200) + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + api_response = await api_client.check( + body=ClientCheckRequest( + user="user:anne", + relation="reader", + object="document:budget", + ), + options={ + "store_id": other_store_id, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + }, + ) + self.assertTrue(api_response.allowed) + self.assertEqual( + mock_request.call_args.args[1], + f"http://api.fga.example/stores/{other_store_id}/check", + ) + + @patch.object(rest.RESTClientObject, "request") + async def test_read_authorization_models_with_store_id_option_and_no_configured_store( + self, mock_request + ): + """a store_id in the options is enough when none is configured""" + other_store_id = "01JD9Z1F6Y0XWAGKM9KYQ8GXMC" + mock_request.return_value = mock_response( + '{"authorization_models": [], "continuation_token": ""}', 200 + ) + configuration = self.configuration + configuration.store_id = None + async with OpenFgaClient(configuration) as api_client: + await api_client.read_authorization_models( + options={"store_id": other_store_id} + ) + self.assertEqual( + mock_request.call_args.args[1], + f"http://api.fga.example/stores/{other_store_id}/authorization-models", + ) + @patch.object(rest.RESTClientObject, "request") async def test_delete_store(self, mock_request): """Test case for delete_store diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index ec2686af..efa6247d 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -312,6 +312,80 @@ def test_get_store(self, mock_request): ) api_client.close() + @patch.object(rest.RESTClientObject, "request") + def test_get_store_with_store_id_option(self, mock_request): + """get_store uses the store_id from the options over the configured one""" + other_store_id = "01JD9Z1F6Y0XWAGKM9KYQ8GXMC" + response_body = f""" +{{ + "id": "{other_store_id}", + "name": "store2", + "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={"store_id": other_store_id}) + self.assertEqual(api_response.id, other_store_id) + mock_request.assert_called_once_with( + "GET", + f"http://api.fga.example/stores/{other_store_id}", + headers=ANY, + body=None, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + # the configured store ID is left alone + self.assertEqual(api_client.get_store_id(), store_id) + + @patch.object(rest.RESTClientObject, "request") + def test_check_with_store_id_option(self, mock_request): + """check uses the store_id from the options over the configured one""" + other_store_id = "01JD9Z1F6Y0XWAGKM9KYQ8GXMC" + mock_request.return_value = mock_response('{"allowed": true}', 200) + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + api_response = api_client.check( + body=ClientCheckRequest( + user="user:anne", + relation="reader", + object="document:budget", + ), + options={ + "store_id": other_store_id, + "authorization_model_id": "01GXSA8YR785C4FYS3C0RTG7B1", + }, + ) + self.assertTrue(api_response.allowed) + self.assertEqual( + mock_request.call_args.args[1], + f"http://api.fga.example/stores/{other_store_id}/check", + ) + + @patch.object(rest.RESTClientObject, "request") + def test_read_authorization_models_with_store_id_option_and_no_configured_store( + self, mock_request + ): + """a store_id in the options is enough when none is configured""" + other_store_id = "01JD9Z1F6Y0XWAGKM9KYQ8GXMC" + mock_request.return_value = mock_response( + '{"authorization_models": [], "continuation_token": ""}', 200 + ) + configuration = self.configuration + configuration.store_id = None + with OpenFgaClient(configuration) as api_client: + api_client.read_authorization_models(options={"store_id": other_store_id}) + self.assertEqual( + mock_request.call_args.args[1], + f"http://api.fga.example/stores/{other_store_id}/authorization-models", + ) + @patch.object(rest.RESTClientObject, "request") def test_delete_store(self, mock_request): """Test case for delete_store