From f4dec148bdf79dae40fabab2c453e77195bae047 Mon Sep 17 00:00:00 2001 From: kcbiradar Date: Thu, 15 Jan 2026 15:23:26 +0530 Subject: [PATCH 01/17] feat: add raw_request method --- README.md | 132 ++++++++++ openfga_sdk/api_client.py | 2 +- openfga_sdk/client/client.py | 167 ++++++++++++ openfga_sdk/client/models/__init__.py | 2 + openfga_sdk/client/models/raw_response.py | 90 +++++++ openfga_sdk/sync/api_client.py | 2 +- openfga_sdk/sync/client/client.py | 169 ++++++++++++ openfga_sdk/telemetry/attributes.py | 2 +- test/client/client_test.py | 308 ++++++++++++++++++++++ test/sync/client/client_test.py | 306 +++++++++++++++++++++ 10 files changed, 1177 insertions(+), 3 deletions(-) create mode 100644 openfga_sdk/client/models/raw_response.py diff --git a/README.md b/README.md index c424a14..03d9531 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ This is an autogenerated python SDK for OpenFGA. It provides a wrapper around th - [Read Assertions](#read-assertions) - [Write Assertions](#write-assertions) - [Retries](#retries) + - [Calling Other Endpoints](#calling-other-endpoints) - [API Endpoints](#api-endpoints) - [Models](#models) - [OpenTelemetry](#opentelemetry) @@ -1260,6 +1261,137 @@ body = [ClientAssertion( response = await fga_client.write_assertions(body, options) ``` +### Calling Other Endpoints + +In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `raw_request` method available on the `OpenFgaClient`. The `raw_request` method allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the operation name, HTTP method, path, parameters, body, and headers, while still honoring the client configuration (authentication, telemetry, retries, and error handling). + +This is useful when: +- You want to call a new endpoint that is not yet supported by the SDK +- You are using an earlier version of the SDK that doesn't yet support a particular endpoint +- You have a custom endpoint deployed that extends the OpenFGA API + +In all cases, you initialize the SDK the same way as usual, and then call `raw_request` on the `fga_client` instance. + +```python +from openfga_sdk import ClientConfiguration, OpenFgaClient + +configuration = ClientConfiguration( + api_url=FGA_API_URL, + store_id=FGA_STORE_ID, +) + +async with OpenFgaClient(configuration) as fga_client: + request_body = { + "user": "user:bob", + "action": "custom_action", + "resource": "resource:123", + } + + response = await fga_client.raw_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": FGA_STORE_ID}, + query_params={"page_size": "20"}, + body=request_body, + headers={"X-Experimental-Feature": "enabled"}, + ) +``` + +#### Example: Calling a new "Custom Endpoint" endpoint and handling raw response + +```python +# Get raw response without automatic decoding +raw_response = await fga_client.raw_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": FGA_STORE_ID}, + body={"user": "user:bob", "action": "custom_action"}, +) + +# Access the response data +if raw_response.status == 200: + # Manually decode the response + result = raw_response.json() + if result: + print(f"Response: {result}") + + # You can access fields like headers, status code, etc. from raw_response: + print(f"Status Code: {raw_response.status}") + print(f"Headers: {raw_response.headers}") + print(f"Body as text: {raw_response.text()}") +``` + +#### Example: Calling a new "Custom Endpoint" endpoint and decoding response into a dictionary + +```python +# Get raw response decoded into a dictionary +response = await fga_client.raw_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": FGA_STORE_ID}, + body={"user": "user:bob", "action": "custom_action"}, +) + +# The response body is automatically parsed as JSON if possible +result = response.json() # Returns dict or None if not JSON + +if result: + print(f"Response: {result}") + # Access fields from the decoded response + if "allowed" in result: + print(f"Allowed: {result['allowed']}") + +print(f"Status Code: {response.status}") +print(f"Headers: {response.headers}") +``` + +#### Example: Calling an existing endpoint with GET + +```python +# Get a list of stores with query parameters +response = await fga_client.raw_request( + operation_name="ListStores", # Required: descriptive name for the operation + method="GET", + path="/stores", + query_params={ + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, +) + +stores = response.json() +print("Stores:", stores) +``` + +#### Example: Using Path Parameters + +Path parameters are specified in the path using `{param_name}` syntax and are replaced with URL-encoded values from the `path_params` dictionary. If `{store_id}` is present in the path and not provided in `path_params`, it will be automatically replaced with the configured store_id: + +```python +# Using explicit path parameters +response = await fga_client.raw_request( + operation_name="ReadAuthorizationModel", # Required: descriptive name for the operation + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={ + "store_id": "your-store-id", + "model_id": "your-model-id", + }, +) + +# Using automatic store_id substitution +response = await fga_client.raw_request( + operation_name="ReadAuthorizationModel", # Required: descriptive name for the operation + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={ + "model_id": "your-model-id", + }, +) +``` ### Retries diff --git a/openfga_sdk/api_client.py b/openfga_sdk/api_client.py index d4cc632..68cd5d2 100644 --- a/openfga_sdk/api_client.py +++ b/openfga_sdk/api_client.py @@ -420,7 +420,7 @@ async def __call_api( if _return_http_data_only: return return_data else: - return (return_data, response_data.status, response_data.headers) + return (return_data, response_data.status, response_data.getheaders()) def _parse_retry_after_header(self, headers) -> int: retry_after_header = headers.get("retry-after") diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index aba8943..9984ba2 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1,5 +1,8 @@ import asyncio import uuid +import urllib.parse +from typing import Any +import json from openfga_sdk.api.open_fga_api import OpenFgaApi from openfga_sdk.api_client import ApiClient @@ -33,6 +36,7 @@ construct_write_single_response, ) from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts +from openfga_sdk.client.models.raw_response import RawResponse from openfga_sdk.constants import ( CLIENT_BULK_REQUEST_ID_HEADER, CLIENT_MAX_BATCH_SIZE, @@ -40,6 +44,7 @@ CLIENT_METHOD_HEADER, ) from openfga_sdk.exceptions import ( + ApiException, AuthenticationError, FgaValidationException, UnauthorizedException, @@ -68,6 +73,7 @@ ) from openfga_sdk.models.write_request import WriteRequest from openfga_sdk.validation import is_well_formed_ulid_string +from openfga_sdk.rest import RESTResponse def _chuck_array(array, max_size): @@ -1096,3 +1102,164 @@ def map_to_assertion(client_assertion: ClientAssertion): authorization_model_id, api_request_body, **kwargs ) return api_response + + ####################### + # Raw Request + ####################### + async def raw_request( + self, + method: str, + path: str, + query_params: dict[str, str | int | list[str | int]] | None = None, + path_params: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + operation_name: str | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Make a raw HTTP request to any OpenFGA API endpoint. + + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) + :param path: API endpoint path (e.g., "/stores/{store_id}/check" or "/stores") + :param query_params: Optional query parameters as a dictionary + :param path_params: Optional path parameters to replace placeholders in path + (e.g., {"store_id": "abc", "model_id": "xyz"}) + :param headers: Optional request headers (will be merged with default headers) + :param body: Optional request body (dict/list will be JSON serialized, str/bytes sent as-is) + :param operation_name: Required operation name for telemetry/logging (e.g., "Check", "Write", "CustomEndpoint") + :param options: Optional request options: + - headers: Additional headers (merged with headers parameter) + - retry_params: Override retry parameters for this request + - authorization_model_id: Not used in raw_request, but kept for consistency + :return: RawResponse object with status, headers, and body + :raises FgaValidationException: If path contains {store_id} but store_id is not configured + :raises ApiException: For HTTP errors (with SDK error handling applied) + """ + + request_headers = dict(headers) if headers else {} + if options and options.get("headers"): + request_headers.update(options["headers"]) + + if not operation_name: + raise FgaValidationException("operation_name is required for raw_request") + + resource_path = path + path_params_dict = dict(path_params) if path_params else {} + + if "{store_id}" in resource_path and "store_id" not in path_params_dict: + store_id = self.get_store_id() + if store_id is None or store_id == "": + raise FgaValidationException( + "Path contains {store_id} but store_id is not configured. " + "Set store_id in ClientConfiguration, use set_store_id(), or provide it in path_params." + ) + path_params_dict["store_id"] = store_id + + for param_name, param_value in path_params_dict.items(): + placeholder = f"{{{param_name}}}" + if placeholder in resource_path: + encoded_value = urllib.parse.quote(str(param_value), safe="") + resource_path = resource_path.replace(placeholder, encoded_value) + if "{" in resource_path or "}" in resource_path: + raise FgaValidationException( + f"Not all path parameters were provided for path: {path}" + ) + + query_params_list = [] + if query_params: + for key, value in query_params.items(): + if value is None: + continue + if isinstance(value, list): + for item in value: + if item is not None: + query_params_list.append((key, str(item))) + continue + query_params_list.append((key, str(value))) + + body_params = None + if body is not None: + if isinstance(body, (dict, list)): + body_params = json.dumps(body) + elif isinstance(body, str): + body_params = body + elif isinstance(body, bytes): + body_params = body + else: + body_params = json.dumps(body) + + retry_params = None + if options and options.get("retry_params"): + retry_params = options["retry_params"] + + if "Content-Type" not in request_headers: + request_headers["Content-Type"] = "application/json" + if "Accept" not in request_headers: + request_headers["Accept"] = "application/json" + + auth_headers = dict(request_headers) if request_headers else {} + await self._api_client.update_params_for_auth( + auth_headers, + query_params_list, + auth_settings=[], + oauth2_client=self._api._oauth2_client, + ) + + telemetry_attributes = None + if operation_name: + from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes + telemetry_attributes = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = self.get_store_id() + + try: + _, http_status, http_headers = await self._api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=auth_headers if auth_headers else None, + body=body_params, + response_types_map={}, + auth_settings=[], + _return_http_data_only=False, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._api._oauth2_client, + _telemetry_attributes=telemetry_attributes, + ) + except ApiException as e: + raise + rest_response: RESTResponse | None = getattr( + self._api_client, "last_response", None + ) + + if rest_response is None: + raise RuntimeError("Failed to get response from API client") + + response_body: bytes | str | dict[str, Any] | None = None + if rest_response.data is not None: + if isinstance(rest_response.data, str): + try: + response_body = json.loads(rest_response.data) + except (json.JSONDecodeError, ValueError): + response_body = rest_response.data + elif isinstance(rest_response.data, bytes): + try: + decoded = rest_response.data.decode("utf-8") + try: + response_body = json.loads(decoded) + except (json.JSONDecodeError, ValueError): + response_body = decoded + except UnicodeDecodeError: + response_body = rest_response.data + else: + response_body = rest_response.data + + return RawResponse( + status=http_status, + headers=http_headers if http_headers else {}, + body=response_body, + ) diff --git a/openfga_sdk/client/models/__init__.py b/openfga_sdk/client/models/__init__.py index 528d527..52644a5 100644 --- a/openfga_sdk/client/models/__init__.py +++ b/openfga_sdk/client/models/__init__.py @@ -23,6 +23,7 @@ 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 +from openfga_sdk.client.models.raw_response import RawResponse __all__ = [ @@ -45,4 +46,5 @@ "ClientWriteRequestOnMissingDeletes", "ConflictOptions", "ClientWriteOptions", + "RawResponse", ] diff --git a/openfga_sdk/client/models/raw_response.py b/openfga_sdk/client/models/raw_response.py new file mode 100644 index 0000000..26503db --- /dev/null +++ b/openfga_sdk/client/models/raw_response.py @@ -0,0 +1,90 @@ +""" +Raw response wrapper for raw_request method. + +This module provides a simple response wrapper for raw HTTP requests +made through the SDK's raw_request method. +""" + +from dataclasses import dataclass +from typing import Any + + +@dataclass +class RawResponse: + """ + Response wrapper for raw HTTP requests. + + This class provides a simple interface to access the response + from a raw_request call, including status code, headers, and body. + + The body is automatically parsed as JSON if possible, otherwise + it's returned as a string or bytes. + """ + + status: int + """HTTP status code""" + + headers: dict[str, str] + """Response headers as a dictionary""" + + body: bytes | str | dict[str, Any] | None = None + """Response body (already parsed as dict if JSON, otherwise str or bytes)""" + + def json(self) -> dict[str, Any] | None: + """ + Return the response body as a JSON dictionary. + + If the body is already a dict (parsed JSON), returns it directly. + If the body is a string or bytes, attempts to parse it as JSON. + Returns None if body is None or cannot be parsed. + + :return: Parsed JSON dictionary or None + """ + if self.body is None: + return None + + if isinstance(self.body, dict): + return self.body + + if isinstance(self.body, str): + import json + + try: + return json.loads(self.body) + except (json.JSONDecodeError, ValueError): + return None + + if isinstance(self.body, bytes): + import json + + try: + return json.loads(self.body.decode("utf-8")) + except (json.JSONDecodeError, ValueError, UnicodeDecodeError): + return None + + return None + + def text(self) -> str | None: + """ + Return the response body as a string. + + :return: Response body as string or None + """ + if self.body is None: + return None + + if isinstance(self.body, str): + return self.body + + if isinstance(self.body, bytes): + try: + return self.body.decode("utf-8") + except UnicodeDecodeError: + return self.body.decode("utf-8", errors="replace") + + if isinstance(self.body, dict): + import json + + return json.dumps(self.body) + + return str(self.body) diff --git a/openfga_sdk/sync/api_client.py b/openfga_sdk/sync/api_client.py index 7990e25..dfcdff9 100644 --- a/openfga_sdk/sync/api_client.py +++ b/openfga_sdk/sync/api_client.py @@ -420,7 +420,7 @@ def __call_api( if _return_http_data_only: return return_data else: - return (return_data, response_data.status, response_data.headers) + return (return_data, response_data.status, response_data.getheaders()) def _parse_retry_after_header(self, headers) -> int: retry_after_header = headers.get("retry-after") diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 1efa33f..078df6f 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1,4 +1,7 @@ import uuid +import json +import urllib.parse +from typing import Any from concurrent.futures import ThreadPoolExecutor @@ -32,6 +35,7 @@ construct_write_single_response, ) from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts +from openfga_sdk.client.models.raw_response import RawResponse from openfga_sdk.constants import ( CLIENT_BULK_REQUEST_ID_HEADER, CLIENT_MAX_BATCH_SIZE, @@ -39,6 +43,7 @@ CLIENT_METHOD_HEADER, ) from openfga_sdk.exceptions import ( + ApiException, AuthenticationError, FgaValidationException, UnauthorizedException, @@ -69,6 +74,7 @@ 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 +from openfga_sdk.sync.rest import RESTResponse def _chuck_array(array, max_size): @@ -1095,3 +1101,166 @@ def map_to_assertion(client_assertion: ClientAssertion) -> Assertion: authorization_model_id, api_request_body, **kwargs ) return api_response + + ####################### + # Raw Request + ####################### + def raw_request( + self, + method: str, + path: str, + query_params: dict[str, str | int | list[str | int]] | None = None, + path_params: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + operation_name: str | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Make a raw HTTP request to any OpenFGA API endpoint. + + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) + :param path: API endpoint path (e.g., "/stores/{store_id}/check" or "/stores") + :param query_params: Optional query parameters as a dictionary + :param path_params: Optional path parameters to replace placeholders in path + (e.g., {"store_id": "abc", "model_id": "xyz"}) + :param headers: Optional request headers (will be merged with default headers) + :param body: Optional request body (dict/list will be JSON serialized, str/bytes sent as-is) + :param operation_name: Required operation name for telemetry/logging (e.g., "Check", "Write", "CustomEndpoint") + :param options: Optional request options: + - headers: Additional headers (merged with headers parameter) + - retry_params: Override retry parameters for this request + - authorization_model_id: Not used in raw_request, but kept for consistency + :return: RawResponse object with status, headers, and body + :raises FgaValidationException: If path contains {store_id} but store_id is not configured + :raises ApiException: For HTTP errors (with SDK error handling applied) + """ + + request_headers = dict(headers) if headers else {} + if options and options.get("headers"): + request_headers.update(options["headers"]) + + if not operation_name: + raise FgaValidationException("operation_name is required for raw_request") + + resource_path = path + path_params_dict = dict(path_params) if path_params else {} + + if "{store_id}" in resource_path and "store_id" not in path_params_dict: + store_id = self.get_store_id() + if store_id is None or store_id == "": + raise FgaValidationException( + "Path contains {store_id} but store_id is not configured. " + "Set store_id in ClientConfiguration, use set_store_id(), or provide it in path_params." + ) + path_params_dict["store_id"] = store_id + + for param_name, param_value in path_params_dict.items(): + placeholder = f"{{{param_name}}}" + if placeholder in resource_path: + encoded_value = urllib.parse.quote(str(param_value), safe="") + resource_path = resource_path.replace(placeholder, encoded_value) + + if "{" in resource_path or "}" in resource_path: + raise FgaValidationException( + f"Not all path parameters were provided for path: {path}" + ) + + query_params_list = [] + if query_params: + for key, value in query_params.items(): + if value is None: + continue + if isinstance(value, list): + for item in value: + if item is not None: + query_params_list.append((key, str(item))) + continue + query_params_list.append((key, str(value))) + + body_params = None + if body is not None: + if isinstance(body, (dict, list)): + body_params = json.dumps(body) + elif isinstance(body, str): + body_params = body + elif isinstance(body, bytes): + body_params = body + else: + body_params = json.dumps(body) + + retry_params = None + if options and options.get("retry_params"): + retry_params = options["retry_params"] + + if "Content-Type" not in request_headers: + request_headers["Content-Type"] = "application/json" + if "Accept" not in request_headers: + request_headers["Accept"] = "application/json" + + auth_headers = dict(request_headers) if request_headers else {} + self._api_client.update_params_for_auth( + auth_headers, + query_params_list, + auth_settings=[], + oauth2_client=self._api._oauth2_client, + ) + + telemetry_attributes = None + if operation_name: + from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes + telemetry_attributes = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = self.get_store_id() + + try: + _, http_status, http_headers = self._api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=auth_headers if auth_headers else None, + body=body_params, + response_types_map={}, + auth_settings=[], + _return_http_data_only=False, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._api._oauth2_client, + _telemetry_attributes=telemetry_attributes, + ) + except ApiException as e: + raise + + rest_response: RESTResponse | None = getattr( + self._api_client, "last_response", None + ) + + if rest_response is None: + raise RuntimeError("Failed to get response from API client") + + response_body: bytes | str | dict[str, Any] | None = None + if rest_response.data is not None: + if isinstance(rest_response.data, str): + try: + response_body = json.loads(rest_response.data) + except (json.JSONDecodeError, ValueError): + response_body = rest_response.data + elif isinstance(rest_response.data, bytes): + try: + decoded = rest_response.data.decode("utf-8") + try: + response_body = json.loads(decoded) + except (json.JSONDecodeError, ValueError): + response_body = decoded + except UnicodeDecodeError: + response_body = rest_response.data + else: + response_body = rest_response.data + + return RawResponse( + status=http_status, + headers=http_headers if http_headers else {}, + body=response_body, + ) diff --git a/openfga_sdk/telemetry/attributes.py b/openfga_sdk/telemetry/attributes.py index ddda43f..e7b10dd 100644 --- a/openfga_sdk/telemetry/attributes.py +++ b/openfga_sdk/telemetry/attributes.py @@ -295,7 +295,7 @@ def fromResponse( response.status ) - if response.body is not None: + if response.body is not None and isinstance(response.body, dict): response_model_id = response.body.get( "openfga-authorization-model-id" ) or response.body.get("openfga_authorization_model_id") diff --git a/test/client/client_test.py b/test/client/client_test.py index 947134d..c2972be 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -25,6 +25,7 @@ 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.client.models.raw_response import RawResponse from openfga_sdk.configuration import RetryParams from openfga_sdk.exceptions import ( FgaValidationException, @@ -4165,3 +4166,310 @@ async def test_write_with_conflict_options_both(self, mock_request): _preload_content=ANY, _request_timeout=None, ) + + @patch.object(rest.RESTClientObject, "request") + async def test_raw_request_post_with_body(self, mock_request): + """Test case for raw_request + + Make a raw POST request with JSON body + """ + response_body = '{"result": "success", "data": {"id": "123"}}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + response = await api_client.raw_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"user": "user:bob", "action": "custom_action"}, + query_params={"page_size": "20"}, + headers={"X-Experimental-Feature": "enabled"}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + self.assertIsNotNone(response.headers) + self.assertIsNotNone(response.body) + self.assertIsInstance(response.body, dict) + self.assertEqual(response.body["result"], "success") + + # Verify response helper methods + json_data = response.json() + self.assertIsNotNone(json_data) + self.assertEqual(json_data["result"], "success") + + text_data = response.text() + self.assertIsNotNone(text_data) + self.assertIn("success", text_data) + + # Verify the API was called with correct parameters + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom-endpoint", + headers=ANY, + body={"user": "user:bob", "action": "custom_action"}, + query_params=[("page_size", "20")], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + await api_client.close() + + @patch.object(rest.RESTClientObject, "request") + async def test_raw_request_get_with_query_params(self, mock_request): + """Test case for raw_request + + Make a raw GET request with query parameters + """ + response_body = '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + async with OpenFgaClient(configuration) as api_client: + response = await api_client.raw_request( + operation_name="ListStores", + method="GET", + path="/stores", + query_params={ + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + self.assertIsNotNone(response.body) + self.assertIsInstance(response.body, dict) + self.assertIn("stores", response.body) + + # Verify the API was called with correct parameters + mock_request.assert_called_once_with( + "GET", + "http://api.fga.example/stores", + headers=ANY, + body=None, + query_params=[("page_size", "10"), ("continuation_token", "eyJwayI6...")], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + await api_client.close() + + @patch.object(rest.RESTClientObject, "request") + async def test_raw_request_with_path_params(self, mock_request): + """Test case for raw_request + + Make a raw request with path parameters + """ + response_body = '{"authorization_model": {"id": "01G5JAVJ41T49E9TT3SKVS7X1J"}}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + response = await api_client.raw_request( + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + self.assertIsNotNone(response.body) + + # Verify the API was called with correct path (store_id auto-substituted) + 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, + ) + await api_client.close() + + @patch.object(rest.RESTClientObject, "request") + async def test_raw_request_auto_store_id_substitution(self, mock_request): + """Test case for raw_request + + Test automatic store_id substitution when not provided in path_params + """ + response_body = '{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + response = await api_client.raw_request( + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + + # Verify store_id was automatically substituted + 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() + + async def test_raw_request_missing_operation_name(self): + """Test case for raw_request + + Test that operation_name is required + """ + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + with self.assertRaises(FgaValidationException) as error: + await api_client.raw_request( + method="GET", + path="/stores", + ) + self.assertIn("operation_name is required", str(error.exception)) + await api_client.close() + + async def test_raw_request_missing_store_id(self): + """Test case for raw_request + + Test that store_id is required when path contains {store_id} + """ + configuration = self.configuration + # Don't set store_id + async with OpenFgaClient(configuration) as api_client: + with self.assertRaises(FgaValidationException) as error: + await api_client.raw_request( + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", + ) + self.assertIn("store_id is not configured", str(error.exception)) + await api_client.close() + + async def test_raw_request_missing_path_params(self): + """Test case for raw_request + + Test that all path parameters must be provided + """ + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + with self.assertRaises(FgaValidationException) as error: + await api_client.raw_request( + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + # Missing model_id in path_params + ) + self.assertIn("Not all path parameters were provided", str(error.exception)) + await api_client.close() + + @patch.object(rest.RESTClientObject, "request") + async def test_raw_request_with_list_query_params(self, mock_request): + """Test case for raw_request + + Test query parameters with list values + """ + response_body = '{"results": []}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + async with OpenFgaClient(configuration) as api_client: + response = await api_client.raw_request( + operation_name="ListStores", + method="GET", + path="/stores", + query_params={"ids": ["id1", "id2", "id3"]}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + + # Verify list query params are expanded + mock_request.assert_called_once_with( + "GET", + "http://api.fga.example/stores", + headers=ANY, + body=None, + query_params=[("ids", "id1"), ("ids", "id2"), ("ids", "id3")], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + await api_client.close() + + @patch.object(rest.RESTClientObject, "request") + async def test_raw_request_default_headers(self, mock_request): + """Test case for raw_request + + Test that default headers (Content-Type, Accept) are set + """ + response_body = '{"result": "success"}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + response = await api_client.raw_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"test": "data"}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + + # Verify default headers are set + call_args = mock_request.call_args + headers = call_args[1]["headers"] + self.assertEqual(headers.get("Content-Type"), "application/json") + self.assertEqual(headers.get("Accept"), "application/json") + await api_client.close() + + @patch.object(rest.RESTClientObject, "request") + async def test_raw_request_url_encoded_path_params(self, mock_request): + """Test case for raw_request + + Test that path parameters are URL encoded + """ + response_body = '{"result": "success"}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + response = await api_client.raw_request( + operation_name="CustomEndpoint", + method="GET", + path="/stores/{store_id}/custom/{param}", + path_params={"param": "value with spaces & special chars"}, + ) + + self.assertIsInstance(response, RawResponse) + + mock_request.assert_called_once_with( + "GET", + "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom/value%20with%20spaces%20%26%20special%20chars", + headers=ANY, + body=None, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + await api_client.close() diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index dbffbd0..44394b2 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -23,6 +23,7 @@ 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.client.models.raw_response import RawResponse from openfga_sdk.exceptions import ( FgaValidationException, UnauthorizedException, @@ -4146,3 +4147,308 @@ def test_sync_write_with_conflict_options_both(self, mock_request): _preload_content=ANY, _request_timeout=None, ) + + @patch.object(rest.RESTClientObject, "request") + def test_raw_request_post_with_body(self, mock_request): + """Test case for raw_request + + Make a raw POST request with JSON body + """ + response_body = '{"result": "success", "data": {"id": "123"}}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + response = api_client.raw_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"user": "user:bob", "action": "custom_action"}, + query_params={"page_size": "20"}, + headers={"X-Experimental-Feature": "enabled"}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + self.assertIsNotNone(response.headers) + self.assertIsNotNone(response.body) + self.assertIsInstance(response.body, dict) + self.assertEqual(response.body["result"], "success") + + # Verify response helper methods + json_data = response.json() + self.assertIsNotNone(json_data) + self.assertEqual(json_data["result"], "success") + + text_data = response.text() + self.assertIsNotNone(text_data) + self.assertIn("success", text_data) + + # Verify the API was called with correct parameters + mock_request.assert_called_once_with( + "POST", + "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom-endpoint", + headers=ANY, + body={"user": "user:bob", "action": "custom_action"}, + query_params=[("page_size", "20")], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_raw_request_get_with_query_params(self, mock_request): + """Test case for raw_request + + Make a raw GET request with query parameters + """ + response_body = '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + with OpenFgaClient(configuration) as api_client: + response = api_client.raw_request( + operation_name="ListStores", + method="GET", + path="/stores", + query_params={ + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + self.assertIsNotNone(response.body) + self.assertIsInstance(response.body, dict) + self.assertIn("stores", response.body) + + # Verify the API was called with correct parameters + mock_request.assert_called_once_with( + "GET", + "http://api.fga.example/stores", + headers=ANY, + body=None, + query_params=[("page_size", "10"), ("continuation_token", "eyJwayI6...")], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_raw_request_with_path_params(self, mock_request): + """Test case for raw_request + + Make a raw request with path parameters + """ + response_body = '{"authorization_model": {"id": "01G5JAVJ41T49E9TT3SKVS7X1J"}}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + response = api_client.raw_request( + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + self.assertIsNotNone(response.body) + + # Verify the API was called with correct path (store_id auto-substituted) + 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, + ) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_raw_request_auto_store_id_substitution(self, mock_request): + """Test case for raw_request + + Test automatic store_id substitution when not provided in path_params + """ + response_body = '{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + response = api_client.raw_request( + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + + # Verify store_id was automatically substituted + 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() + + def test_raw_request_missing_operation_name(self): + """Test case for raw_request + + Test that operation_name is required + """ + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + with self.assertRaises(FgaValidationException) as error: + api_client.raw_request( + method="GET", + path="/stores", + ) + self.assertIn("operation_name is required", str(error.exception)) + api_client.close() + + def test_raw_request_missing_store_id(self): + """Test case for raw_request + + Test that store_id is required when path contains {store_id} + """ + configuration = self.configuration + # Don't set store_id + with OpenFgaClient(configuration) as api_client: + with self.assertRaises(FgaValidationException) as error: + api_client.raw_request( + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", + ) + self.assertIn("store_id is not configured", str(error.exception)) + api_client.close() + + def test_raw_request_missing_path_params(self): + """Test case for raw_request + + Test that all path parameters must be provided + """ + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + with self.assertRaises(FgaValidationException) as error: + api_client.raw_request( + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + # Missing model_id in path_params + ) + self.assertIn("Not all path parameters were provided", str(error.exception)) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_raw_request_with_list_query_params(self, mock_request): + """Test case for raw_request + + Test query parameters with list values + """ + response_body = '{"results": []}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + with OpenFgaClient(configuration) as api_client: + response = api_client.raw_request( + operation_name="ListStores", + method="GET", + path="/stores", + query_params={"ids": ["id1", "id2", "id3"]}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + + mock_request.assert_called_once_with( + "GET", + "http://api.fga.example/stores", + headers=ANY, + body=None, + query_params=[("ids", "id1"), ("ids", "id2"), ("ids", "id3")], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_raw_request_default_headers(self, mock_request): + """Test case for raw_request + + Test that default headers (Content-Type, Accept) are set + """ + response_body = '{"result": "success"}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + response = api_client.raw_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"test": "data"}, + ) + + self.assertIsInstance(response, RawResponse) + self.assertEqual(response.status, 200) + + call_args = mock_request.call_args + headers = call_args[1]["headers"] + self.assertEqual(headers.get("Content-Type"), "application/json") + self.assertEqual(headers.get("Accept"), "application/json") + api_client.close() + + @patch.object(rest.RESTClientObject, "request") + def test_raw_request_url_encoded_path_params(self, mock_request): + """Test case for raw_request + + Test that path parameters are URL encoded + """ + response_body = '{"result": "success"}' + mock_request.return_value = mock_response(response_body, 200) + + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + response = api_client.raw_request( + operation_name="CustomEndpoint", + method="GET", + path="/stores/{store_id}/custom/{param}", + path_params={"param": "value with spaces & special chars"}, + ) + + self.assertIsInstance(response, RawResponse) + + mock_request.assert_called_once_with( + "GET", + "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom/value%20with%20spaces%20%26%20special%20chars", + headers=ANY, + body=None, + query_params=[], + post_params=[], + _preload_content=ANY, + _request_timeout=None, + ) + api_client.close() From 3c2e66ab7d3f84a39e178c8cfb5d9e1249c26f3f Mon Sep 17 00:00:00 2001 From: kcbiradar Date: Thu, 15 Jan 2026 22:22:12 +0530 Subject: [PATCH 02/17] update: handling double-JSON-encodes --- openfga_sdk/client/client.py | 17 +++++++---------- openfga_sdk/sync/client/client.py | 17 +++++++---------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 9984ba2..110881f 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1178,23 +1178,20 @@ async def raw_request( continue query_params_list.append((key, str(value))) - body_params = None - if body is not None: - if isinstance(body, (dict, list)): - body_params = json.dumps(body) + body_params = body + if "Content-Type" not in request_headers: + if isinstance(body, (dict, list)) or body is None: + request_headers["Content-Type"] = "application/json" elif isinstance(body, str): - body_params = body + request_headers["Content-Type"] = "text/plain" elif isinstance(body, bytes): - body_params = body + request_headers["Content-Type"] = "application/octet-stream" else: - body_params = json.dumps(body) + request_headers["Content-Type"] = "application/json" retry_params = None if options and options.get("retry_params"): retry_params = options["retry_params"] - - if "Content-Type" not in request_headers: - request_headers["Content-Type"] = "application/json" if "Accept" not in request_headers: request_headers["Accept"] = "application/json" diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 078df6f..e7d7738 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1178,23 +1178,20 @@ def raw_request( continue query_params_list.append((key, str(value))) - body_params = None - if body is not None: - if isinstance(body, (dict, list)): - body_params = json.dumps(body) + body_params = body + if "Content-Type" not in request_headers: + if isinstance(body, (dict, list)) or body is None: + request_headers["Content-Type"] = "application/json" elif isinstance(body, str): - body_params = body + request_headers["Content-Type"] = "text/plain" elif isinstance(body, bytes): - body_params = body + request_headers["Content-Type"] = "application/octet-stream" else: - body_params = json.dumps(body) + request_headers["Content-Type"] = "application/json" retry_params = None if options and options.get("retry_params"): retry_params = options["retry_params"] - - if "Content-Type" not in request_headers: - request_headers["Content-Type"] = "application/json" if "Accept" not in request_headers: request_headers["Accept"] = "application/json" From 3f85dfad753f87ff3ffd212f6fdfba7044aa6f0a Mon Sep 17 00:00:00 2001 From: kcbiradar Date: Sat, 17 Jan 2026 15:40:32 +0530 Subject: [PATCH 03/17] addRawRequestMethod: fix testcases & remove trailing spaces --- openfga_sdk/client/client.py | 62 ++++++++++--------- openfga_sdk/client/models/__init__.py | 2 +- openfga_sdk/client/models/raw_response.py | 4 +- openfga_sdk/sync/client/client.py | 63 +++++++++++--------- test/client/client_test.py | 72 +++++++++++++++-------- test/sync/client/client_test.py | 66 ++++++++++++--------- 6 files changed, 156 insertions(+), 113 deletions(-) diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 110881f..9eba189 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1,8 +1,9 @@ import asyncio -import uuid +import json import urllib.parse +import uuid + from typing import Any -import json from openfga_sdk.api.open_fga_api import OpenFgaApi from openfga_sdk.api_client import ApiClient @@ -28,6 +29,7 @@ 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.raw_response import RawResponse from openfga_sdk.client.models.read_changes_request import ClientReadChangesRequest from openfga_sdk.client.models.tuple import ClientTuple, convert_tuple_keys from openfga_sdk.client.models.write_request import ClientWriteRequest @@ -36,7 +38,6 @@ construct_write_single_response, ) from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts -from openfga_sdk.client.models.raw_response import RawResponse from openfga_sdk.constants import ( CLIENT_BULK_REQUEST_ID_HEADER, CLIENT_MAX_BATCH_SIZE, @@ -44,7 +45,6 @@ CLIENT_METHOD_HEADER, ) from openfga_sdk.exceptions import ( - ApiException, AuthenticationError, FgaValidationException, UnauthorizedException, @@ -72,8 +72,11 @@ WriteAuthorizationModelRequest, ) from openfga_sdk.models.write_request import WriteRequest -from openfga_sdk.validation import is_well_formed_ulid_string from openfga_sdk.rest import RESTResponse +from openfga_sdk.telemetry.attributes import ( + TelemetryAttributes, +) +from openfga_sdk.validation import is_well_formed_ulid_string def _chuck_array(array, max_size): @@ -1146,7 +1149,7 @@ async def raw_request( resource_path = path path_params_dict = dict(path_params) if path_params else {} - + if "{store_id}" in resource_path and "store_id" not in path_params_dict: store_id = self.get_store_id() if store_id is None or store_id == "": @@ -1155,7 +1158,7 @@ async def raw_request( "Set store_id in ClientConfiguration, use set_store_id(), or provide it in path_params." ) path_params_dict["store_id"] = store_id - + for param_name, param_value in path_params_dict.items(): placeholder = f"{{{param_name}}}" if placeholder in resource_path: @@ -1205,36 +1208,39 @@ async def raw_request( telemetry_attributes = None if operation_name: - from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes telemetry_attributes = { TelemetryAttributes.fga_client_request_method: operation_name.lower(), } if self.get_store_id(): - telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = self.get_store_id() - - try: - _, http_status, http_headers = await self._api_client.call_api( - resource_path=resource_path, - method=method.upper(), - query_params=query_params_list if query_params_list else None, - header_params=auth_headers if auth_headers else None, - body=body_params, - response_types_map={}, - auth_settings=[], - _return_http_data_only=False, - _preload_content=True, - _retry_params=retry_params, - _oauth2_client=self._api._oauth2_client, - _telemetry_attributes=telemetry_attributes, - ) - except ApiException as e: - raise + telemetry_attributes[ + TelemetryAttributes.fga_client_request_store_id + ] = self.get_store_id() + + _, http_status, http_headers = await self._api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=auth_headers if auth_headers else None, + body=body_params, + response_types_map={}, + auth_settings=[], + _return_http_data_only=False, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._api._oauth2_client, + _telemetry_attributes=telemetry_attributes, + ) rest_response: RESTResponse | None = getattr( self._api_client, "last_response", None ) if rest_response is None: - raise RuntimeError("Failed to get response from API client") + raise RuntimeError( + f"Failed to get response from API client for {method.upper()} " + f"request to '{resource_path}'" + f"{f' (operation: {operation_name})' if operation_name else ''}. " + "This may indicate an internal SDK error, network problem, or client configuration issue." + ) response_body: bytes | str | dict[str, Any] | None = None if rest_response.data is not None: diff --git a/openfga_sdk/client/models/__init__.py b/openfga_sdk/client/models/__init__.py index 52644a5..2d3cde1 100644 --- a/openfga_sdk/client/models/__init__.py +++ b/openfga_sdk/client/models/__init__.py @@ -12,6 +12,7 @@ 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.raw_response import RawResponse from openfga_sdk.client.models.read_changes_request import ClientReadChangesRequest from openfga_sdk.client.models.tuple import ClientTuple from openfga_sdk.client.models.write_conflict_opts import ( @@ -23,7 +24,6 @@ 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 -from openfga_sdk.client.models.raw_response import RawResponse __all__ = [ diff --git a/openfga_sdk/client/models/raw_response.py b/openfga_sdk/client/models/raw_response.py index 26503db..e369088 100644 --- a/openfga_sdk/client/models/raw_response.py +++ b/openfga_sdk/client/models/raw_response.py @@ -23,10 +23,10 @@ class RawResponse: status: int """HTTP status code""" - + headers: dict[str, str] """Response headers as a dictionary""" - + body: bytes | str | dict[str, Any] | None = None """Response body (already parsed as dict if JSON, otherwise str or bytes)""" diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index e7d7738..18b0a56 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1,9 +1,9 @@ -import uuid import json import urllib.parse -from typing import Any +import uuid from concurrent.futures import ThreadPoolExecutor +from typing import Any from openfga_sdk.client.configuration import ClientConfiguration from openfga_sdk.client.models.assertion import ClientAssertion @@ -27,6 +27,7 @@ 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.raw_response import RawResponse from openfga_sdk.client.models.read_changes_request import ClientReadChangesRequest from openfga_sdk.client.models.tuple import ClientTuple, convert_tuple_keys from openfga_sdk.client.models.write_request import ClientWriteRequest @@ -35,7 +36,6 @@ construct_write_single_response, ) from openfga_sdk.client.models.write_transaction_opts import WriteTransactionOpts -from openfga_sdk.client.models.raw_response import RawResponse from openfga_sdk.constants import ( CLIENT_BULK_REQUEST_ID_HEADER, CLIENT_MAX_BATCH_SIZE, @@ -43,7 +43,6 @@ CLIENT_METHOD_HEADER, ) from openfga_sdk.exceptions import ( - ApiException, AuthenticationError, FgaValidationException, UnauthorizedException, @@ -73,8 +72,11 @@ 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 from openfga_sdk.sync.rest import RESTResponse +from openfga_sdk.telemetry.attributes import ( + TelemetryAttributes, +) +from openfga_sdk.validation import is_well_formed_ulid_string def _chuck_array(array, max_size): @@ -1145,7 +1147,7 @@ def raw_request( resource_path = path path_params_dict = dict(path_params) if path_params else {} - + if "{store_id}" in resource_path and "store_id" not in path_params_dict: store_id = self.get_store_id() if store_id is None or store_id == "": @@ -1154,13 +1156,13 @@ def raw_request( "Set store_id in ClientConfiguration, use set_store_id(), or provide it in path_params." ) path_params_dict["store_id"] = store_id - + for param_name, param_value in path_params_dict.items(): placeholder = f"{{{param_name}}}" if placeholder in resource_path: encoded_value = urllib.parse.quote(str(param_value), safe="") resource_path = resource_path.replace(placeholder, encoded_value) - + if "{" in resource_path or "}" in resource_path: raise FgaValidationException( f"Not all path parameters were provided for path: {path}" @@ -1205,37 +1207,40 @@ def raw_request( telemetry_attributes = None if operation_name: - from openfga_sdk.telemetry.attributes import TelemetryAttribute, TelemetryAttributes telemetry_attributes = { TelemetryAttributes.fga_client_request_method: operation_name.lower(), } if self.get_store_id(): - telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = self.get_store_id() - - try: - _, http_status, http_headers = self._api_client.call_api( - resource_path=resource_path, - method=method.upper(), - query_params=query_params_list if query_params_list else None, - header_params=auth_headers if auth_headers else None, - body=body_params, - response_types_map={}, - auth_settings=[], - _return_http_data_only=False, - _preload_content=True, - _retry_params=retry_params, - _oauth2_client=self._api._oauth2_client, - _telemetry_attributes=telemetry_attributes, - ) - except ApiException as e: - raise + telemetry_attributes[ + TelemetryAttributes.fga_client_request_store_id + ] = self.get_store_id() + + _, http_status, http_headers = self._api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=auth_headers if auth_headers else None, + body=body_params, + response_types_map={}, + auth_settings=[], + _return_http_data_only=False, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._api._oauth2_client, + _telemetry_attributes=telemetry_attributes, + ) rest_response: RESTResponse | None = getattr( self._api_client, "last_response", None ) if rest_response is None: - raise RuntimeError("Failed to get response from API client") + raise RuntimeError( + f"Failed to get response from API client for {method.upper()} " + f"request to '{resource_path}'" + f"{f' (operation: {operation_name})' if operation_name else ''}. " + "This may indicate an internal SDK error, network problem, or client configuration issue." + ) response_body: bytes | str | dict[str, Any] | None = None if rest_response.data is not None: diff --git a/test/client/client_test.py b/test/client/client_test.py index c2972be..52f8b97 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -20,12 +20,12 @@ 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.raw_response import RawResponse 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.client.models.raw_response import RawResponse from openfga_sdk.configuration import RetryParams from openfga_sdk.exceptions import ( FgaValidationException, @@ -3907,12 +3907,17 @@ def client_configuration(): ) -class TestClientConfigurationHeaders: +class TestClientConfigurationHeaders(IsolatedAsyncioTestCase): """Tests for ClientConfiguration headers parameter""" - def test_client_configuration_headers_default_none(self, client_configuration): + def setUp(self): + self.configuration = ClientConfiguration( + api_url="http://api.fga.example", + ) + + def test_client_configuration_headers_default_none(self): """Test that headers default to an empty dict in ClientConfiguration""" - assert client_configuration.headers == {} + assert self.configuration.headers == {} def test_client_configuration_headers_initialization_with_dict(self): """Test initializing ClientConfiguration with headers""" @@ -3937,11 +3942,11 @@ def test_client_configuration_headers_initialization_with_none(self): ) assert config.headers == {} - def test_client_configuration_headers_setter(self, client_configuration): + def test_client_configuration_headers_setter(self): """Test setting headers via property setter""" headers = {"X-Test": "test-value"} - client_configuration.headers = headers - assert client_configuration.headers == headers + self.configuration.headers = headers + assert self.configuration.headers == headers def test_client_configuration_headers_with_authorization_model_id(self): """Test ClientConfiguration with headers and authorization_model_id""" @@ -4168,6 +4173,7 @@ async def test_write_with_conflict_options_both(self, mock_request): ) @patch.object(rest.RESTClientObject, "request") + @pytest.mark.asyncio async def test_raw_request_post_with_body(self, mock_request): """Test case for raw_request @@ -4208,22 +4214,25 @@ async def test_raw_request_post_with_body(self, mock_request): mock_request.assert_called_once_with( "POST", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom-endpoint", + query_params=[("page_size", "20")], headers=ANY, + post_params=None, body={"user": "user:bob", "action": "custom_action"}, - query_params=[("page_size", "20")], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) await api_client.close() @patch.object(rest.RESTClientObject, "request") + @pytest.mark.asyncio async def test_raw_request_get_with_query_params(self, mock_request): """Test case for raw_request Make a raw GET request with query parameters """ - response_body = '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' + response_body = ( + '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' + ) mock_request.return_value = mock_response(response_body, 200) configuration = self.configuration @@ -4248,16 +4257,20 @@ async def test_raw_request_get_with_query_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores", + query_params=[ + ("page_size", "10"), + ("continuation_token", "eyJwayI6..."), + ], headers=ANY, + post_params=None, body=None, - query_params=[("page_size", "10"), ("continuation_token", "eyJwayI6...")], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) await api_client.close() @patch.object(rest.RESTClientObject, "request") + @pytest.mark.asyncio async def test_raw_request_with_path_params(self, mock_request): """Test case for raw_request @@ -4284,16 +4297,17 @@ async def test_raw_request_with_path_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J", + query_params=None, headers=ANY, + post_params=None, body=None, - query_params=[], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) await api_client.close() @patch.object(rest.RESTClientObject, "request") + @pytest.mark.asyncio async def test_raw_request_auto_store_id_substitution(self, mock_request): """Test case for raw_request @@ -4318,15 +4332,16 @@ async def test_raw_request_auto_store_id_substitution(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X", + query_params=None, headers=ANY, + post_params=None, body=None, - query_params=[], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) await api_client.close() + @pytest.mark.asyncio async def test_raw_request_missing_operation_name(self): """Test case for raw_request @@ -4343,6 +4358,7 @@ async def test_raw_request_missing_operation_name(self): self.assertIn("operation_name is required", str(error.exception)) await api_client.close() + @pytest.mark.asyncio async def test_raw_request_missing_store_id(self): """Test case for raw_request @@ -4360,6 +4376,7 @@ async def test_raw_request_missing_store_id(self): self.assertIn("store_id is not configured", str(error.exception)) await api_client.close() + @pytest.mark.asyncio async def test_raw_request_missing_path_params(self): """Test case for raw_request @@ -4379,6 +4396,7 @@ async def test_raw_request_missing_path_params(self): await api_client.close() @patch.object(rest.RESTClientObject, "request") + @pytest.mark.asyncio async def test_raw_request_with_list_query_params(self, mock_request): """Test case for raw_request @@ -4403,16 +4421,17 @@ async def test_raw_request_with_list_query_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores", + query_params=[("ids", "id1"), ("ids", "id2"), ("ids", "id3")], headers=ANY, + post_params=None, body=None, - query_params=[("ids", "id1"), ("ids", "id2"), ("ids", "id3")], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) await api_client.close() @patch.object(rest.RESTClientObject, "request") + @pytest.mark.asyncio async def test_raw_request_default_headers(self, mock_request): """Test case for raw_request @@ -4442,6 +4461,7 @@ async def test_raw_request_default_headers(self, mock_request): await api_client.close() @patch.object(rest.RESTClientObject, "request") + @pytest.mark.asyncio async def test_raw_request_url_encoded_path_params(self, mock_request): """Test case for raw_request @@ -4465,11 +4485,11 @@ async def test_raw_request_url_encoded_path_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom/value%20with%20spaces%20%26%20special%20chars", + query_params=None, headers=ANY, + post_params=None, body=None, - query_params=[], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) await api_client.close() diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index 44394b2..d3fe576 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -3,7 +3,7 @@ import uuid from datetime import datetime -from unittest import IsolatedAsyncioTestCase +from unittest import IsolatedAsyncioTestCase, TestCase from unittest.mock import ANY, patch import pytest @@ -18,12 +18,12 @@ 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.raw_response import RawResponse 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.client.models.raw_response import RawResponse from openfga_sdk.exceptions import ( FgaValidationException, UnauthorizedException, @@ -3886,12 +3886,19 @@ def client_configuration(): ) -class TestSyncClientConfigurationHeaders: +class TestSyncClientConfigurationHeaders(TestCase): """Tests for ClientConfiguration headers parameter in sync client""" - def test_sync_client_configuration_headers_default_none(self, client_configuration): + def setUp(self): + from openfga_sdk.client.configuration import ClientConfiguration + + self.configuration = ClientConfiguration( + api_url="http://api.fga.example", + ) + + def test_sync_client_configuration_headers_default_none(self): """Test that headers default to an empty dict in ClientConfiguration""" - assert client_configuration.headers == {} + assert self.configuration.headers == {} def test_sync_client_configuration_headers_initialization_with_dict(self): """Test initializing ClientConfiguration with headers""" @@ -3920,11 +3927,11 @@ def test_sync_client_configuration_headers_initialization_with_none(self): ) assert config.headers == {} - def test_sync_client_configuration_headers_setter(self, client_configuration): + def test_sync_client_configuration_headers_setter(self): """Test setting headers via property setter""" headers = {"X-Test": "test-value"} - client_configuration.headers = headers - assert client_configuration.headers == headers + self.configuration.headers = headers + assert self.configuration.headers == headers def test_sync_client_configuration_headers_with_authorization_model_id(self): """Test ClientConfiguration with headers and authorization_model_id""" @@ -4189,11 +4196,11 @@ def test_raw_request_post_with_body(self, mock_request): mock_request.assert_called_once_with( "POST", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom-endpoint", + query_params=[("page_size", "20")], headers=ANY, + post_params=None, body={"user": "user:bob", "action": "custom_action"}, - query_params=[("page_size", "20")], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) api_client.close() @@ -4204,7 +4211,9 @@ def test_raw_request_get_with_query_params(self, mock_request): Make a raw GET request with query parameters """ - response_body = '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' + response_body = ( + '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' + ) mock_request.return_value = mock_response(response_body, 200) configuration = self.configuration @@ -4229,11 +4238,14 @@ def test_raw_request_get_with_query_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores", + query_params=[ + ("page_size", "10"), + ("continuation_token", "eyJwayI6..."), + ], headers=ANY, + post_params=None, body=None, - query_params=[("page_size", "10"), ("continuation_token", "eyJwayI6...")], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) api_client.close() @@ -4265,11 +4277,11 @@ def test_raw_request_with_path_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J", + query_params=None, headers=ANY, + post_params=None, body=None, - query_params=[], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) api_client.close() @@ -4299,11 +4311,11 @@ def test_raw_request_auto_store_id_substitution(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X", + query_params=None, headers=ANY, + post_params=None, body=None, - query_params=[], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) api_client.close() @@ -4383,11 +4395,11 @@ def test_raw_request_with_list_query_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores", + query_params=[("ids", "id1"), ("ids", "id2"), ("ids", "id3")], headers=ANY, + post_params=None, body=None, - query_params=[("ids", "id1"), ("ids", "id2"), ("ids", "id3")], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) api_client.close() @@ -4444,11 +4456,11 @@ def test_raw_request_url_encoded_path_params(self, mock_request): mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/custom/value%20with%20spaces%20%26%20special%20chars", + query_params=None, headers=ANY, + post_params=None, body=None, - query_params=[], - post_params=[], - _preload_content=ANY, + _preload_content=True, _request_timeout=None, ) api_client.close() From b6b72aa80781912844c3eab68403224f08607f15 Mon Sep 17 00:00:00 2001 From: kcbiradar Date: Sun, 18 Jan 2026 14:02:58 +0530 Subject: [PATCH 04/17] addRawRequestMethod: update-changes-requested --- openfga_sdk/api_client.py | 2 +- openfga_sdk/client/client.py | 8 +++---- openfga_sdk/client/models/raw_response.py | 28 +++-------------------- openfga_sdk/sync/api_client.py | 2 +- openfga_sdk/sync/client/client.py | 8 +++---- 5 files changed, 13 insertions(+), 35 deletions(-) diff --git a/openfga_sdk/api_client.py b/openfga_sdk/api_client.py index 68cd5d2..d4cc632 100644 --- a/openfga_sdk/api_client.py +++ b/openfga_sdk/api_client.py @@ -420,7 +420,7 @@ async def __call_api( if _return_http_data_only: return return_data else: - return (return_data, response_data.status, response_data.getheaders()) + return (return_data, response_data.status, response_data.headers) def _parse_retry_after_header(self, headers) -> int: retry_after_header = headers.get("retry-after") diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 9eba189..09da6f0 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1216,7 +1216,7 @@ async def raw_request( TelemetryAttributes.fga_client_request_store_id ] = self.get_store_id() - _, http_status, http_headers = await self._api_client.call_api( + await self._api_client.call_api( resource_path=resource_path, method=method.upper(), query_params=query_params_list if query_params_list else None, @@ -1224,7 +1224,7 @@ async def raw_request( body=body_params, response_types_map={}, auth_settings=[], - _return_http_data_only=False, + _return_http_data_only=True, _preload_content=True, _retry_params=retry_params, _oauth2_client=self._api._oauth2_client, @@ -1262,7 +1262,7 @@ async def raw_request( response_body = rest_response.data return RawResponse( - status=http_status, - headers=http_headers if http_headers else {}, + status=rest_response.status, + headers=dict(rest_response.getheaders()), body=response_body, ) diff --git a/openfga_sdk/client/models/raw_response.py b/openfga_sdk/client/models/raw_response.py index e369088..043e2b4 100644 --- a/openfga_sdk/client/models/raw_response.py +++ b/openfga_sdk/client/models/raw_response.py @@ -7,6 +7,7 @@ from dataclasses import dataclass from typing import Any +import json @dataclass @@ -34,34 +35,13 @@ def json(self) -> dict[str, Any] | None: """ Return the response body as a JSON dictionary. - If the body is already a dict (parsed JSON), returns it directly. - If the body is a string or bytes, attempts to parse it as JSON. - Returns None if body is None or cannot be parsed. + The body is already parsed during the request, so this typically + just returns the body if it's a dict, or None otherwise. :return: Parsed JSON dictionary or None """ - if self.body is None: - return None - if isinstance(self.body, dict): return self.body - - if isinstance(self.body, str): - import json - - try: - return json.loads(self.body) - except (json.JSONDecodeError, ValueError): - return None - - if isinstance(self.body, bytes): - import json - - try: - return json.loads(self.body.decode("utf-8")) - except (json.JSONDecodeError, ValueError, UnicodeDecodeError): - return None - return None def text(self) -> str | None: @@ -83,8 +63,6 @@ def text(self) -> str | None: return self.body.decode("utf-8", errors="replace") if isinstance(self.body, dict): - import json - return json.dumps(self.body) return str(self.body) diff --git a/openfga_sdk/sync/api_client.py b/openfga_sdk/sync/api_client.py index dfcdff9..7990e25 100644 --- a/openfga_sdk/sync/api_client.py +++ b/openfga_sdk/sync/api_client.py @@ -420,7 +420,7 @@ def __call_api( if _return_http_data_only: return return_data else: - return (return_data, response_data.status, response_data.getheaders()) + return (return_data, response_data.status, response_data.headers) def _parse_retry_after_header(self, headers) -> int: retry_after_header = headers.get("retry-after") diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 18b0a56..ba6a26a 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1215,7 +1215,7 @@ def raw_request( TelemetryAttributes.fga_client_request_store_id ] = self.get_store_id() - _, http_status, http_headers = self._api_client.call_api( + self._api_client.call_api( resource_path=resource_path, method=method.upper(), query_params=query_params_list if query_params_list else None, @@ -1223,7 +1223,7 @@ def raw_request( body=body_params, response_types_map={}, auth_settings=[], - _return_http_data_only=False, + _return_http_data_only=True, _preload_content=True, _retry_params=retry_params, _oauth2_client=self._api._oauth2_client, @@ -1262,7 +1262,7 @@ def raw_request( response_body = rest_response.data return RawResponse( - status=http_status, - headers=http_headers if http_headers else {}, + status=rest_response.status, + headers=dict(rest_response.getheaders()), body=response_body, ) From 6188d9e8ca3954f44973b5a1b379b30c7e5cde89 Mon Sep 17 00:00:00 2001 From: kcbiradar Date: Sun, 18 Jan 2026 14:29:00 +0530 Subject: [PATCH 05/17] updates --- openfga_sdk/client/models/raw_response.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openfga_sdk/client/models/raw_response.py b/openfga_sdk/client/models/raw_response.py index 043e2b4..889182b 100644 --- a/openfga_sdk/client/models/raw_response.py +++ b/openfga_sdk/client/models/raw_response.py @@ -5,9 +5,10 @@ made through the SDK's raw_request method. """ +import json + from dataclasses import dataclass from typing import Any -import json @dataclass From a34de525b07805d3d0ad35003f14ebb3492f4b6e Mon Sep 17 00:00:00 2001 From: kcbiradar Date: Mon, 19 Jan 2026 15:08:23 +0530 Subject: [PATCH 06/17] updated-requested-changes --- openfga_sdk/client/client.py | 9 ++++++--- openfga_sdk/sync/client/client.py | 9 ++++++--- openfga_sdk/telemetry/attributes.py | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 09da6f0..8a7183d 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1142,7 +1142,8 @@ async def raw_request( request_headers = dict(headers) if headers else {} if options and options.get("headers"): - request_headers.update(options["headers"]) + if isinstance(options["headers"], dict): + request_headers.update(options["headers"]) if not operation_name: raise FgaValidationException("operation_name is required for raw_request") @@ -1235,10 +1236,12 @@ async def raw_request( ) if rest_response is None: + operation_suffix = ( + f" (operation: {operation_name})" if operation_name else "" + ) raise RuntimeError( f"Failed to get response from API client for {method.upper()} " - f"request to '{resource_path}'" - f"{f' (operation: {operation_name})' if operation_name else ''}. " + f"request to '{resource_path}'{operation_suffix}. " "This may indicate an internal SDK error, network problem, or client configuration issue." ) diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index ba6a26a..daac606 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1140,7 +1140,8 @@ def raw_request( request_headers = dict(headers) if headers else {} if options and options.get("headers"): - request_headers.update(options["headers"]) + if isinstance(options["headers"], dict): + request_headers.update(options["headers"]) if not operation_name: raise FgaValidationException("operation_name is required for raw_request") @@ -1235,10 +1236,12 @@ def raw_request( ) if rest_response is None: + operation_suffix = ( + f" (operation: {operation_name})" if operation_name else "" + ) raise RuntimeError( f"Failed to get response from API client for {method.upper()} " - f"request to '{resource_path}'" - f"{f' (operation: {operation_name})' if operation_name else ''}. " + f"request to '{resource_path}'{operation_suffix}. " "This may indicate an internal SDK error, network problem, or client configuration issue." ) diff --git a/openfga_sdk/telemetry/attributes.py b/openfga_sdk/telemetry/attributes.py index e7b10dd..ddda43f 100644 --- a/openfga_sdk/telemetry/attributes.py +++ b/openfga_sdk/telemetry/attributes.py @@ -295,7 +295,7 @@ def fromResponse( response.status ) - if response.body is not None and isinstance(response.body, dict): + if response.body is not None: response_model_id = response.body.get( "openfga-authorization-model-id" ) or response.body.get("openfga_authorization_model_id") From 5cc73eabd92c1cdf628513d40cc0a103c5a815ef Mon Sep 17 00:00:00 2001 From: kcbiradar Date: Tue, 20 Jan 2026 12:29:37 +0530 Subject: [PATCH 07/17] update-suggested-changes --- openfga_sdk/client/client.py | 9 +-------- openfga_sdk/sync/client/client.py | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 8a7183d..883a702 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1184,14 +1184,7 @@ async def raw_request( body_params = body if "Content-Type" not in request_headers: - if isinstance(body, (dict, list)) or body is None: - request_headers["Content-Type"] = "application/json" - elif isinstance(body, str): - request_headers["Content-Type"] = "text/plain" - elif isinstance(body, bytes): - request_headers["Content-Type"] = "application/octet-stream" - else: - request_headers["Content-Type"] = "application/json" + request_headers["Content-Type"] = "application/json" retry_params = None if options and options.get("retry_params"): diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index daac606..b5e2d13 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1183,14 +1183,7 @@ def raw_request( body_params = body if "Content-Type" not in request_headers: - if isinstance(body, (dict, list)) or body is None: - request_headers["Content-Type"] = "application/json" - elif isinstance(body, str): - request_headers["Content-Type"] = "text/plain" - elif isinstance(body, bytes): - request_headers["Content-Type"] = "application/octet-stream" - else: - request_headers["Content-Type"] = "application/json" + request_headers["Content-Type"] = "application/json" retry_params = None if options and options.get("retry_params"): From 256d6e1f278e29fb927d1532326457aa4c69734d Mon Sep 17 00:00:00 2001 From: SoulPancake Date: Fri, 6 Mar 2026 15:28:33 +0530 Subject: [PATCH 08/17] feat: refactor method to execute api method --- README.md | 123 ++++++++++++------------------ openfga_sdk/client/client.py | 90 ++++++++++++++++------ openfga_sdk/sync/client/client.py | 86 +++++++++++++++------ test/client/client_test.py | 122 ++++++++++++++++------------- test/sync/client/client_test.py | 122 ++++++++++++++++------------- 5 files changed, 320 insertions(+), 223 deletions(-) diff --git a/README.md b/README.md index cafb733..056ddda 100644 --- a/README.md +++ b/README.md @@ -1263,14 +1263,16 @@ response = await fga_client.write_assertions(body, options) ### Calling Other Endpoints -In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `raw_request` method available on the `OpenFgaClient`. The `raw_request` method allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the operation name, HTTP method, path, parameters, body, and headers, while still honoring the client configuration (authentication, telemetry, retries, and error handling). +In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `execute_api_request` method available on the `OpenFgaClient`. The `execute_api_request` method allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the HTTP method, path, body, query parameters, and path parameters, while still honoring the client configuration (authentication, telemetry, retries, and error handling). + +For streaming endpoints, use `execute_streamed_api_request` instead. This is useful when: - You want to call a new endpoint that is not yet supported by the SDK - You are using an earlier version of the SDK that doesn't yet support a particular endpoint - You have a custom endpoint deployed that extends the OpenFGA API -In all cases, you initialize the SDK the same way as usual, and then call `raw_request` on the `fga_client` instance. +In all cases, you initialize the SDK the same way as usual, and then call `execute_api_request` on the `fga_client` instance. ```python from openfga_sdk import ClientConfiguration, OpenFgaClient @@ -1287,82 +1289,57 @@ async with OpenFgaClient(configuration) as fga_client: "resource": "resource:123", } - response = await fga_client.raw_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - path_params={"store_id": FGA_STORE_ID}, - query_params={"page_size": "20"}, - body=request_body, - headers={"X-Experimental-Feature": "enabled"}, - ) + response = await fga_client.execute_api_request({ + "operation_name": "CustomEndpoint", + "method": "POST", + "path": "/stores/{store_id}/custom-endpoint", + "path_params": {"store_id": FGA_STORE_ID}, + "query_params": {"page_size": "20"}, + "body": request_body, + "headers": {"X-Experimental-Feature": "enabled"}, + }) ``` -#### Example: Calling a new "Custom Endpoint" endpoint and handling raw response +#### Example: Calling a custom endpoint with POST ```python -# Get raw response without automatic decoding -raw_response = await fga_client.raw_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - path_params={"store_id": FGA_STORE_ID}, - body={"user": "user:bob", "action": "custom_action"}, -) +# Call a custom endpoint using path parameters +response = await fga_client.execute_api_request({ + "operation_name": "CustomEndpoint", # For telemetry/logging + "method": "POST", + "path": "/stores/{store_id}/custom-endpoint", + "path_params": {"store_id": FGA_STORE_ID}, + "body": { + "user": "user:bob", + "action": "custom_action", + "resource": "resource:123", + }, + "query_params": { + "page_size": 20, + }, +}) # Access the response data -if raw_response.status == 200: - # Manually decode the response - result = raw_response.json() - if result: - print(f"Response: {result}") - - # You can access fields like headers, status code, etc. from raw_response: - print(f"Status Code: {raw_response.status}") - print(f"Headers: {raw_response.headers}") - print(f"Body as text: {raw_response.text()}") -``` - -#### Example: Calling a new "Custom Endpoint" endpoint and decoding response into a dictionary - -```python -# Get raw response decoded into a dictionary -response = await fga_client.raw_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - path_params={"store_id": FGA_STORE_ID}, - body={"user": "user:bob", "action": "custom_action"}, -) - -# The response body is automatically parsed as JSON if possible -result = response.json() # Returns dict or None if not JSON - -if result: +if response.status == 200: + result = response.json() print(f"Response: {result}") - # Access fields from the decoded response - if "allowed" in result: - print(f"Allowed: {result['allowed']}") - -print(f"Status Code: {response.status}") -print(f"Headers: {response.headers}") ``` #### Example: Calling an existing endpoint with GET ```python # Get a list of stores with query parameters -response = await fga_client.raw_request( - operation_name="ListStores", # Required: descriptive name for the operation - method="GET", - path="/stores", - query_params={ +stores_response = await fga_client.execute_api_request({ + "operation_name": "ListStores", + "method": "GET", + "path": "/stores", + "query_params": { "page_size": 10, "continuation_token": "eyJwayI6...", }, -) +}) -stores = response.json() +stores = stores_response.json() print("Stores:", stores) ``` @@ -1372,25 +1349,25 @@ Path parameters are specified in the path using `{param_name}` syntax and are re ```python # Using explicit path parameters -response = await fga_client.raw_request( - operation_name="ReadAuthorizationModel", # Required: descriptive name for the operation - method="GET", - path="/stores/{store_id}/authorization-models/{model_id}", - path_params={ +response = await fga_client.execute_api_request({ + "operation_name": "GetAuthorizationModel", + "method": "GET", + "path": "/stores/{store_id}/authorization-models/{model_id}", + "path_params": { "store_id": "your-store-id", "model_id": "your-model-id", }, -) +}) # Using automatic store_id substitution -response = await fga_client.raw_request( - operation_name="ReadAuthorizationModel", # Required: descriptive name for the operation - method="GET", - path="/stores/{store_id}/authorization-models/{model_id}", - path_params={ +response = await fga_client.execute_api_request({ + "operation_name": "GetAuthorizationModel", + "method": "GET", + "path": "/stores/{store_id}/authorization-models/{model_id}", + "path_params": { "model_id": "your-model-id", }, -) +}) ``` ### Retries diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 883a702..8cde6f8 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1107,47 +1107,89 @@ def map_to_assertion(client_assertion: ClientAssertion): return api_response ####################### - # Raw Request + # Execute API Request ####################### - async def raw_request( + async def execute_api_request( self, - method: str, - path: str, - query_params: dict[str, str | int | list[str | int]] | None = None, - path_params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - body: dict[str, Any] | list[Any] | str | bytes | None = None, - operation_name: str | None = None, + request: dict[str, Any], options: dict[str, int | str | dict[str, int | str]] | None = None, ) -> RawResponse: """ - Make a raw HTTP request to any OpenFGA API endpoint. + Execute an arbitrary HTTP request to any OpenFGA API endpoint. - :param method: HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) - :param path: API endpoint path (e.g., "/stores/{store_id}/check" or "/stores") - :param query_params: Optional query parameters as a dictionary - :param path_params: Optional path parameters to replace placeholders in path - (e.g., {"store_id": "abc", "model_id": "xyz"}) - :param headers: Optional request headers (will be merged with default headers) - :param body: Optional request body (dict/list will be JSON serialized, str/bytes sent as-is) - :param operation_name: Required operation name for telemetry/logging (e.g., "Check", "Write", "CustomEndpoint") + Useful when you need to call a new or experimental API that doesn't yet have a built-in method in the SDK. + You still get the benefits of the SDK, like authentication, configuration, and consistent error handling. + + :param request: Request parameters dict with the following keys: + - operation_name (str): Required. Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint") + - method (str): Required. HTTP method (GET, POST, PUT, DELETE, PATCH) + - path (str): Required. API path (e.g., "/stores/{store_id}/my-endpoint") + - path_params (dict[str, str], optional): Path parameters to replace template variables in the path + - body (dict/list/str/bytes, optional): Request body for POST/PUT/PATCH requests + - query_params (dict[str, ...], optional): Query parameters + - headers (dict[str, str], optional): Custom request headers :param options: Optional request options: - - headers: Additional headers (merged with headers parameter) + - headers: Additional headers (merged with request['headers']; options['headers'] takes precedence) - retry_params: Override retry parameters for this request - - authorization_model_id: Not used in raw_request, but kept for consistency :return: RawResponse object with status, headers, and body - :raises FgaValidationException: If path contains {store_id} but store_id is not configured + :raises FgaValidationException: If required parameters are missing or invalid + :raises ApiException: For HTTP errors (with SDK error handling applied) + """ + return await self._execute_api_request_internal( + request, options, streaming=False + ) + + async def execute_streamed_api_request( + self, + request: dict[str, Any], + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. + + Similar to execute_api_request but for streaming endpoints. + + :param request: Request parameters dict (see execute_api_request for details) + :param options: Optional request options (see execute_api_request for details) + :return: RawResponse object with status, headers, and body + :raises FgaValidationException: If required parameters are missing or invalid :raises ApiException: For HTTP errors (with SDK error handling applied) """ + return await self._execute_api_request_internal( + request, options, streaming=True + ) + + async def _execute_api_request_internal( + self, + request: dict[str, Any], + options: dict[str, int | str | dict[str, int | str]] | None = None, + streaming: bool = False, + ) -> RawResponse: + """Internal implementation for execute_api_request and execute_streamed_api_request.""" + # Extract request parameters + operation_name = request.get("operation_name") + method = request.get("method") + path = request.get("path") + query_params = request.get("query_params") + path_params = request.get("path_params") + headers = request.get("headers") + body = request.get("body") + + # Validate required parameters + if not operation_name: + raise FgaValidationException( + "operation_name is required for execute_api_request" + ) + if not method: + raise FgaValidationException("method is required for execute_api_request") + if not path: + raise FgaValidationException("path is required for execute_api_request") request_headers = dict(headers) if headers else {} if options and options.get("headers"): if isinstance(options["headers"], dict): request_headers.update(options["headers"]) - if not operation_name: - raise FgaValidationException("operation_name is required for raw_request") - resource_path = path path_params_dict = dict(path_params) if path_params else {} diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index b5e2d13..4a28f46 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1105,47 +1105,85 @@ def map_to_assertion(client_assertion: ClientAssertion) -> Assertion: return api_response ####################### - # Raw Request + # Execute API Request ####################### - def raw_request( + def execute_api_request( self, - method: str, - path: str, - query_params: dict[str, str | int | list[str | int]] | None = None, - path_params: dict[str, str] | None = None, - headers: dict[str, str] | None = None, - body: dict[str, Any] | list[Any] | str | bytes | None = None, - operation_name: str | None = None, + request: dict[str, Any], options: dict[str, int | str | dict[str, int | str]] | None = None, ) -> RawResponse: """ - Make a raw HTTP request to any OpenFGA API endpoint. + Execute an arbitrary HTTP request to any OpenFGA API endpoint. - :param method: HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) - :param path: API endpoint path (e.g., "/stores/{store_id}/check" or "/stores") - :param query_params: Optional query parameters as a dictionary - :param path_params: Optional path parameters to replace placeholders in path - (e.g., {"store_id": "abc", "model_id": "xyz"}) - :param headers: Optional request headers (will be merged with default headers) - :param body: Optional request body (dict/list will be JSON serialized, str/bytes sent as-is) - :param operation_name: Required operation name for telemetry/logging (e.g., "Check", "Write", "CustomEndpoint") + Useful when you need to call a new or experimental API that doesn't yet have a built-in method in the SDK. + You still get the benefits of the SDK, like authentication, configuration, and consistent error handling. + + :param request: Request parameters dict with the following keys: + - operation_name (str): Required. Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint") + - method (str): Required. HTTP method (GET, POST, PUT, DELETE, PATCH) + - path (str): Required. API path (e.g., "/stores/{store_id}/my-endpoint") + - path_params (dict[str, str], optional): Path parameters to replace template variables in the path + - body (dict/list/str/bytes, optional): Request body for POST/PUT/PATCH requests + - query_params (dict[str, ...], optional): Query parameters + - headers (dict[str, str], optional): Custom request headers :param options: Optional request options: - - headers: Additional headers (merged with headers parameter) + - headers: Additional headers (merged with request['headers']; options['headers'] takes precedence) - retry_params: Override retry parameters for this request - - authorization_model_id: Not used in raw_request, but kept for consistency :return: RawResponse object with status, headers, and body - :raises FgaValidationException: If path contains {store_id} but store_id is not configured + :raises FgaValidationException: If required parameters are missing or invalid + :raises ApiException: For HTTP errors (with SDK error handling applied) + """ + return self._execute_api_request_internal(request, options, streaming=False) + + def execute_streamed_api_request( + self, + request: dict[str, Any], + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. + + Similar to execute_api_request but for streaming endpoints. + + :param request: Request parameters dict (see execute_api_request for details) + :param options: Optional request options (see execute_api_request for details) + :return: RawResponse object with status, headers, and body + :raises FgaValidationException: If required parameters are missing or invalid :raises ApiException: For HTTP errors (with SDK error handling applied) """ + return self._execute_api_request_internal(request, options, streaming=True) + + def _execute_api_request_internal( + self, + request: dict[str, Any], + options: dict[str, int | str | dict[str, int | str]] | None = None, + streaming: bool = False, + ) -> RawResponse: + """Internal implementation for execute_api_request and execute_streamed_api_request.""" + # Extract request parameters + operation_name = request.get("operation_name") + method = request.get("method") + path = request.get("path") + query_params = request.get("query_params") + path_params = request.get("path_params") + headers = request.get("headers") + body = request.get("body") + + # Validate required parameters + if not operation_name: + raise FgaValidationException( + "operation_name is required for execute_api_request" + ) + if not method: + raise FgaValidationException("method is required for execute_api_request") + if not path: + raise FgaValidationException("path is required for execute_api_request") request_headers = dict(headers) if headers else {} if options and options.get("headers"): if isinstance(options["headers"], dict): request_headers.update(options["headers"]) - if not operation_name: - raise FgaValidationException("operation_name is required for raw_request") - resource_path = path path_params_dict = dict(path_params) if path_params else {} diff --git a/test/client/client_test.py b/test/client/client_test.py index 52f8b97..8f991b8 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -4185,13 +4185,15 @@ async def test_raw_request_post_with_body(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: - response = await api_client.raw_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - body={"user": "user:bob", "action": "custom_action"}, - query_params={"page_size": "20"}, - headers={"X-Experimental-Feature": "enabled"}, + response = await api_client.execute_api_request( + { + "operation_name": "CustomEndpoint", + "method": "POST", + "path": "/stores/{store_id}/custom-endpoint", + "body": {"user": "user:bob", "action": "custom_action"}, + "query_params": {"page_size": "20"}, + "headers": {"X-Experimental-Feature": "enabled"}, + } ) self.assertIsInstance(response, RawResponse) @@ -4237,14 +4239,16 @@ async def test_raw_request_get_with_query_params(self, mock_request): configuration = self.configuration async with OpenFgaClient(configuration) as api_client: - response = await api_client.raw_request( - operation_name="ListStores", - method="GET", - path="/stores", - query_params={ - "page_size": 10, - "continuation_token": "eyJwayI6...", - }, + response = await api_client.execute_api_request( + { + "operation_name": "ListStores", + "method": "GET", + "path": "/stores", + "query_params": { + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, + } ) self.assertIsInstance(response, RawResponse) @@ -4282,11 +4286,13 @@ async def test_raw_request_with_path_params(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: - response = await api_client.raw_request( - operation_name="ReadAuthorizationModel", - method="GET", - path="/stores/{store_id}/authorization-models/{model_id}", - path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + response = await api_client.execute_api_request( + { + "operation_name": "ReadAuthorizationModel", + "method": "GET", + "path": "/stores/{store_id}/authorization-models/{model_id}", + "path_params": {"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + } ) self.assertIsInstance(response, RawResponse) @@ -4319,10 +4325,12 @@ async def test_raw_request_auto_store_id_substitution(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: - response = await api_client.raw_request( - operation_name="GetStore", - method="GET", - path="/stores/{store_id}", + response = await api_client.execute_api_request( + { + "operation_name": "GetStore", + "method": "GET", + "path": "/stores/{store_id}", + } ) self.assertIsInstance(response, RawResponse) @@ -4351,9 +4359,11 @@ async def test_raw_request_missing_operation_name(self): configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: - await api_client.raw_request( - method="GET", - path="/stores", + await api_client.execute_api_request( + { + "method": "GET", + "path": "/stores", + } ) self.assertIn("operation_name is required", str(error.exception)) await api_client.close() @@ -4368,10 +4378,12 @@ async def test_raw_request_missing_store_id(self): # Don't set store_id async with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: - await api_client.raw_request( - operation_name="GetStore", - method="GET", - path="/stores/{store_id}", + await api_client.execute_api_request( + { + "operation_name": "GetStore", + "method": "GET", + "path": "/stores/{store_id}", + } ) self.assertIn("store_id is not configured", str(error.exception)) await api_client.close() @@ -4386,11 +4398,13 @@ async def test_raw_request_missing_path_params(self): configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: - await api_client.raw_request( - operation_name="ReadAuthorizationModel", - method="GET", - path="/stores/{store_id}/authorization-models/{model_id}", - # Missing model_id in path_params + await api_client.execute_api_request( + { + "operation_name": "ReadAuthorizationModel", + "method": "GET", + "path": "/stores/{store_id}/authorization-models/{model_id}", + # Missing model_id in path_params + } ) self.assertIn("Not all path parameters were provided", str(error.exception)) await api_client.close() @@ -4407,11 +4421,13 @@ async def test_raw_request_with_list_query_params(self, mock_request): configuration = self.configuration async with OpenFgaClient(configuration) as api_client: - response = await api_client.raw_request( - operation_name="ListStores", - method="GET", - path="/stores", - query_params={"ids": ["id1", "id2", "id3"]}, + response = await api_client.execute_api_request( + { + "operation_name": "ListStores", + "method": "GET", + "path": "/stores", + "query_params": {"ids": ["id1", "id2", "id3"]}, + } ) self.assertIsInstance(response, RawResponse) @@ -4443,11 +4459,13 @@ async def test_raw_request_default_headers(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: - response = await api_client.raw_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - body={"test": "data"}, + response = await api_client.execute_api_request( + { + "operation_name": "CustomEndpoint", + "method": "POST", + "path": "/stores/{store_id}/custom-endpoint", + "body": {"test": "data"}, + } ) self.assertIsInstance(response, RawResponse) @@ -4473,11 +4491,13 @@ async def test_raw_request_url_encoded_path_params(self, mock_request): configuration = self.configuration configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: - response = await api_client.raw_request( - operation_name="CustomEndpoint", - method="GET", - path="/stores/{store_id}/custom/{param}", - path_params={"param": "value with spaces & special chars"}, + response = await api_client.execute_api_request( + { + "operation_name": "CustomEndpoint", + "method": "GET", + "path": "/stores/{store_id}/custom/{param}", + "path_params": {"param": "value with spaces & special chars"}, + } ) self.assertIsInstance(response, RawResponse) diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index d3fe576..6af7226 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -4167,13 +4167,15 @@ def test_raw_request_post_with_body(self, mock_request): configuration = self.configuration configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: - response = api_client.raw_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - body={"user": "user:bob", "action": "custom_action"}, - query_params={"page_size": "20"}, - headers={"X-Experimental-Feature": "enabled"}, + response = api_client.execute_api_request( + { + "operation_name": "CustomEndpoint", + "method": "POST", + "path": "/stores/{store_id}/custom-endpoint", + "body": {"user": "user:bob", "action": "custom_action"}, + "query_params": {"page_size": "20"}, + "headers": {"X-Experimental-Feature": "enabled"}, + } ) self.assertIsInstance(response, RawResponse) @@ -4218,14 +4220,16 @@ def test_raw_request_get_with_query_params(self, mock_request): configuration = self.configuration with OpenFgaClient(configuration) as api_client: - response = api_client.raw_request( - operation_name="ListStores", - method="GET", - path="/stores", - query_params={ - "page_size": 10, - "continuation_token": "eyJwayI6...", - }, + response = api_client.execute_api_request( + { + "operation_name": "ListStores", + "method": "GET", + "path": "/stores", + "query_params": { + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, + } ) self.assertIsInstance(response, RawResponse) @@ -4262,11 +4266,13 @@ def test_raw_request_with_path_params(self, mock_request): configuration = self.configuration configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: - response = api_client.raw_request( - operation_name="ReadAuthorizationModel", - method="GET", - path="/stores/{store_id}/authorization-models/{model_id}", - path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + response = api_client.execute_api_request( + { + "operation_name": "ReadAuthorizationModel", + "method": "GET", + "path": "/stores/{store_id}/authorization-models/{model_id}", + "path_params": {"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + } ) self.assertIsInstance(response, RawResponse) @@ -4298,10 +4304,12 @@ def test_raw_request_auto_store_id_substitution(self, mock_request): configuration = self.configuration configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: - response = api_client.raw_request( - operation_name="GetStore", - method="GET", - path="/stores/{store_id}", + response = api_client.execute_api_request( + { + "operation_name": "GetStore", + "method": "GET", + "path": "/stores/{store_id}", + } ) self.assertIsInstance(response, RawResponse) @@ -4329,9 +4337,11 @@ def test_raw_request_missing_operation_name(self): configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: - api_client.raw_request( - method="GET", - path="/stores", + api_client.execute_api_request( + { + "method": "GET", + "path": "/stores", + } ) self.assertIn("operation_name is required", str(error.exception)) api_client.close() @@ -4345,10 +4355,12 @@ def test_raw_request_missing_store_id(self): # Don't set store_id with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: - api_client.raw_request( - operation_name="GetStore", - method="GET", - path="/stores/{store_id}", + api_client.execute_api_request( + { + "operation_name": "GetStore", + "method": "GET", + "path": "/stores/{store_id}", + } ) self.assertIn("store_id is not configured", str(error.exception)) api_client.close() @@ -4362,11 +4374,13 @@ def test_raw_request_missing_path_params(self): configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: - api_client.raw_request( - operation_name="ReadAuthorizationModel", - method="GET", - path="/stores/{store_id}/authorization-models/{model_id}", - # Missing model_id in path_params + api_client.execute_api_request( + { + "operation_name": "ReadAuthorizationModel", + "method": "GET", + "path": "/stores/{store_id}/authorization-models/{model_id}", + # Missing model_id in path_params + } ) self.assertIn("Not all path parameters were provided", str(error.exception)) api_client.close() @@ -4382,11 +4396,13 @@ def test_raw_request_with_list_query_params(self, mock_request): configuration = self.configuration with OpenFgaClient(configuration) as api_client: - response = api_client.raw_request( - operation_name="ListStores", - method="GET", - path="/stores", - query_params={"ids": ["id1", "id2", "id3"]}, + response = api_client.execute_api_request( + { + "operation_name": "ListStores", + "method": "GET", + "path": "/stores", + "query_params": {"ids": ["id1", "id2", "id3"]}, + } ) self.assertIsInstance(response, RawResponse) @@ -4416,11 +4432,13 @@ def test_raw_request_default_headers(self, mock_request): configuration = self.configuration configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: - response = api_client.raw_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - body={"test": "data"}, + response = api_client.execute_api_request( + { + "operation_name": "CustomEndpoint", + "method": "POST", + "path": "/stores/{store_id}/custom-endpoint", + "body": {"test": "data"}, + } ) self.assertIsInstance(response, RawResponse) @@ -4444,11 +4462,13 @@ def test_raw_request_url_encoded_path_params(self, mock_request): configuration = self.configuration configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: - response = api_client.raw_request( - operation_name="CustomEndpoint", - method="GET", - path="/stores/{store_id}/custom/{param}", - path_params={"param": "value with spaces & special chars"}, + response = api_client.execute_api_request( + { + "operation_name": "CustomEndpoint", + "method": "GET", + "path": "/stores/{store_id}/custom/{param}", + "path_params": {"param": "value with spaces & special chars"}, + } ) self.assertIsInstance(response, RawResponse) From 1e71e79b57ec27f12d7c9ce3eb31462a7a74124a Mon Sep 17 00:00:00 2001 From: SoulPancake Date: Fri, 6 Mar 2026 15:51:24 +0530 Subject: [PATCH 09/17] fix: tetss and refactor --- README.md | 70 ++--- openfga_sdk/client/client.py | 242 ++++++++--------- .../client/execute_api_request_builder.py | 190 +++++++++++++ openfga_sdk/sync/client/client.py | 249 ++++++++---------- .../client/execute_api_request_builder.py | 14 + test/client/client_test.py | 129 ++++----- test/sync/client/client_test.py | 129 ++++----- 7 files changed, 575 insertions(+), 448 deletions(-) create mode 100644 openfga_sdk/client/execute_api_request_builder.py create mode 100644 openfga_sdk/sync/client/execute_api_request_builder.py diff --git a/README.md b/README.md index 056ddda..985a393 100644 --- a/README.md +++ b/README.md @@ -1289,35 +1289,35 @@ async with OpenFgaClient(configuration) as fga_client: "resource": "resource:123", } - response = await fga_client.execute_api_request({ - "operation_name": "CustomEndpoint", - "method": "POST", - "path": "/stores/{store_id}/custom-endpoint", - "path_params": {"store_id": FGA_STORE_ID}, - "query_params": {"page_size": "20"}, - "body": request_body, - "headers": {"X-Experimental-Feature": "enabled"}, - }) + response = await fga_client.execute_api_request( + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": FGA_STORE_ID}, + query_params={"page_size": "20"}, + body=request_body, + headers={"X-Experimental-Feature": "enabled"}, + ) ``` #### Example: Calling a custom endpoint with POST ```python # Call a custom endpoint using path parameters -response = await fga_client.execute_api_request({ - "operation_name": "CustomEndpoint", # For telemetry/logging - "method": "POST", - "path": "/stores/{store_id}/custom-endpoint", - "path_params": {"store_id": FGA_STORE_ID}, - "body": { +response = await fga_client.execute_api_request( + operation_name="CustomEndpoint", # For telemetry/logging + method="POST", + path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": FGA_STORE_ID}, + body={ "user": "user:bob", "action": "custom_action", "resource": "resource:123", }, - "query_params": { + query_params={ "page_size": 20, }, -}) +) # Access the response data if response.status == 200: @@ -1329,15 +1329,15 @@ if response.status == 200: ```python # Get a list of stores with query parameters -stores_response = await fga_client.execute_api_request({ - "operation_name": "ListStores", - "method": "GET", - "path": "/stores", - "query_params": { +stores_response = await fga_client.execute_api_request( + operation_name="ListStores", + method="GET", + path="/stores", + query_params={ "page_size": 10, "continuation_token": "eyJwayI6...", }, -}) +) stores = stores_response.json() print("Stores:", stores) @@ -1349,25 +1349,25 @@ Path parameters are specified in the path using `{param_name}` syntax and are re ```python # Using explicit path parameters -response = await fga_client.execute_api_request({ - "operation_name": "GetAuthorizationModel", - "method": "GET", - "path": "/stores/{store_id}/authorization-models/{model_id}", - "path_params": { +response = await fga_client.execute_api_request( + operation_name="GetAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={ "store_id": "your-store-id", "model_id": "your-model-id", }, -}) +) # Using automatic store_id substitution -response = await fga_client.execute_api_request({ - "operation_name": "GetAuthorizationModel", - "method": "GET", - "path": "/stores/{store_id}/authorization-models/{model_id}", - "path_params": { +response = await fga_client.execute_api_request( + operation_name="GetAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={ "model_id": "your-model-id", }, -}) +) ``` ### Retries diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 8cde6f8..01e412e 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1,6 +1,4 @@ import asyncio -import json -import urllib.parse import uuid from typing import Any @@ -8,6 +6,10 @@ from openfga_sdk.api.open_fga_api import OpenFgaApi from openfga_sdk.api_client import ApiClient from openfga_sdk.client.configuration import ClientConfiguration +from openfga_sdk.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ResponseParser, +) from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ( ClientBatchCheckItem, @@ -1111,130 +1113,121 @@ def map_to_assertion(client_assertion: ClientAssertion): ####################### async def execute_api_request( self, - request: dict[str, Any], + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, options: dict[str, int | str | dict[str, int | str]] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. - Useful when you need to call a new or experimental API that doesn't yet have a built-in method in the SDK. - You still get the benefits of the SDK, like authentication, configuration, and consistent error handling. - - :param request: Request parameters dict with the following keys: - - operation_name (str): Required. Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint") - - method (str): Required. HTTP method (GET, POST, PUT, DELETE, PATCH) - - path (str): Required. API path (e.g., "/stores/{store_id}/my-endpoint") - - path_params (dict[str, str], optional): Path parameters to replace template variables in the path - - body (dict/list/str/bytes, optional): Request body for POST/PUT/PATCH requests - - query_params (dict[str, ...], optional): Query parameters - - headers (dict[str, str], optional): Custom request headers - :param options: Optional request options: - - headers: Additional headers (merged with request['headers']; options['headers'] takes precedence) + Useful when you need to call a new or experimental API that doesn't + yet have a built-in method in the SDK. You still get the benefits of + the SDK: authentication, configuration, retries, and error handling. + + :param operation_name: Required. Operation name for telemetry/logging + (e.g., "CustomCheck", "CustomEndpoint") + :param method: Required. HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: Required. API path with optional template parameters + (e.g., "/stores/{store_id}/my-endpoint") + :param path_params: Path parameters to replace template variables. + If {store_id} is in the path and not provided here, it will be + automatically substituted from the client configuration. + :param body: Request body for POST/PUT/PATCH requests + :param query_params: Query string parameters + :param headers: Custom request headers. SDK always enforces + Content-Type and Accept as application/json. + :param options: Additional request options: + - headers: Extra headers (merged; options headers take precedence) - retry_params: Override retry parameters for this request - :return: RawResponse object with status, headers, and body - :raises FgaValidationException: If required parameters are missing or invalid - :raises ApiException: For HTTP errors (with SDK error handling applied) + :return: RawResponse with status, headers, and body + :raises FgaValidationException: If required parameters are missing + :raises ApiException: For HTTP errors """ return await self._execute_api_request_internal( - request, options, streaming=False + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=False, ) async def execute_streamed_api_request( self, - request: dict[str, Any], + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, options: dict[str, int | str | dict[str, int | str]] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. - Similar to execute_api_request but for streaming endpoints. - - :param request: Request parameters dict (see execute_api_request for details) - :param options: Optional request options (see execute_api_request for details) - :return: RawResponse object with status, headers, and body - :raises FgaValidationException: If required parameters are missing or invalid - :raises ApiException: For HTTP errors (with SDK error handling applied) + Same interface as execute_api_request but for streaming endpoints. + See execute_api_request for full parameter documentation. """ return await self._execute_api_request_internal( - request, options, streaming=True + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=True, ) async def _execute_api_request_internal( self, - request: dict[str, Any], + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, options: dict[str, int | str | dict[str, int | str]] | None = None, streaming: bool = False, ) -> RawResponse: """Internal implementation for execute_api_request and execute_streamed_api_request.""" - # Extract request parameters - operation_name = request.get("operation_name") - method = request.get("method") - path = request.get("path") - query_params = request.get("query_params") - path_params = request.get("path_params") - headers = request.get("headers") - body = request.get("body") - - # Validate required parameters - if not operation_name: - raise FgaValidationException( - "operation_name is required for execute_api_request" - ) - if not method: - raise FgaValidationException("method is required for execute_api_request") - if not path: - raise FgaValidationException("path is required for execute_api_request") - - request_headers = dict(headers) if headers else {} - if options and options.get("headers"): - if isinstance(options["headers"], dict): - request_headers.update(options["headers"]) - - resource_path = path - path_params_dict = dict(path_params) if path_params else {} - - if "{store_id}" in resource_path and "store_id" not in path_params_dict: - store_id = self.get_store_id() - if store_id is None or store_id == "": - raise FgaValidationException( - "Path contains {store_id} but store_id is not configured. " - "Set store_id in ClientConfiguration, use set_store_id(), or provide it in path_params." - ) - path_params_dict["store_id"] = store_id - - for param_name, param_value in path_params_dict.items(): - placeholder = f"{{{param_name}}}" - if placeholder in resource_path: - encoded_value = urllib.parse.quote(str(param_value), safe="") - resource_path = resource_path.replace(placeholder, encoded_value) - if "{" in resource_path or "}" in resource_path: - raise FgaValidationException( - f"Not all path parameters were provided for path: {path}" - ) + # 1. Validate and build request + builder = ExecuteApiRequestBuilder( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + ) + builder.validate() - query_params_list = [] - if query_params: - for key, value in query_params.items(): - if value is None: - continue - if isinstance(value, list): - for item in value: - if item is not None: - query_params_list.append((key, str(item))) - continue - query_params_list.append((key, str(value))) - - body_params = body - if "Content-Type" not in request_headers: - request_headers["Content-Type"] = "application/json" + # 2. Build path, query params, and headers + resource_path = builder.build_path(self.get_store_id()) + query_params_list = builder.build_query_params_list() - retry_params = None - if options and options.get("retry_params"): - retry_params = options["retry_params"] - if "Accept" not in request_headers: - request_headers["Accept"] = "application/json" + options_headers = None + if options and isinstance(options.get("headers"), dict): + options_headers = options["headers"] + final_headers = builder.build_headers(options_headers) - auth_headers = dict(request_headers) if request_headers else {} + # 3. Apply authentication + auth_headers = dict(final_headers) await self._api_client.update_params_for_auth( auth_headers, query_params_list, @@ -1242,22 +1235,27 @@ async def _execute_api_request_internal( oauth2_client=self._api._oauth2_client, ) - telemetry_attributes = None - if operation_name: - telemetry_attributes = { - TelemetryAttributes.fga_client_request_method: operation_name.lower(), - } - if self.get_store_id(): - telemetry_attributes[ - TelemetryAttributes.fga_client_request_store_id - ] = self.get_store_id() + # 4. Build telemetry attributes + telemetry_attributes = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( + self.get_store_id() + ) + + # 5. Extract retry params + retry_params = None + if options and options.get("retry_params"): + retry_params = options["retry_params"] + # 6. Make API request await self._api_client.call_api( resource_path=resource_path, method=method.upper(), query_params=query_params_list if query_params_list else None, header_params=auth_headers if auth_headers else None, - body=body_params, + body=body, response_types_map={}, auth_settings=[], _return_http_data_only=True, @@ -1265,42 +1263,24 @@ async def _execute_api_request_internal( _retry_params=retry_params, _oauth2_client=self._api._oauth2_client, _telemetry_attributes=telemetry_attributes, + _streaming=streaming, ) + + # 7. Parse response rest_response: RESTResponse | None = getattr( self._api_client, "last_response", None ) if rest_response is None: - operation_suffix = ( - f" (operation: {operation_name})" if operation_name else "" - ) raise RuntimeError( f"Failed to get response from API client for {method.upper()} " - f"request to '{resource_path}'{operation_suffix}. " - "This may indicate an internal SDK error, network problem, or client configuration issue." + f"request to '{resource_path}' (operation: {operation_name}). " + "This may indicate an internal SDK error, network problem, " + "or client configuration issue." ) - response_body: bytes | str | dict[str, Any] | None = None - if rest_response.data is not None: - if isinstance(rest_response.data, str): - try: - response_body = json.loads(rest_response.data) - except (json.JSONDecodeError, ValueError): - response_body = rest_response.data - elif isinstance(rest_response.data, bytes): - try: - decoded = rest_response.data.decode("utf-8") - try: - response_body = json.loads(decoded) - except (json.JSONDecodeError, ValueError): - response_body = decoded - except UnicodeDecodeError: - response_body = rest_response.data - else: - response_body = rest_response.data - return RawResponse( status=rest_response.status, headers=dict(rest_response.getheaders()), - body=response_body, + body=ResponseParser.parse_body(rest_response.data), ) diff --git a/openfga_sdk/client/execute_api_request_builder.py b/openfga_sdk/client/execute_api_request_builder.py new file mode 100644 index 0000000..2ccfb2b --- /dev/null +++ b/openfga_sdk/client/execute_api_request_builder.py @@ -0,0 +1,190 @@ +""" +Builders and utilities for execute_api_request functionality. + +This module provides reusable utilities for request building, header management, +and response parsing to reduce code duplication across sync and async clients. +""" + +import json +import re +import urllib.parse + +from typing import Any + +from openfga_sdk.exceptions import FgaValidationException + + +class ExecuteApiRequestBuilder: + """ + Builder for API request execution. + + Encapsulates request validation, parameter building, and path construction + to eliminate duplication and improve maintainability. + """ + + def __init__( + self, + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, + ): + """Initialize builder with request parameters.""" + self.operation_name = operation_name + self.method = method + self.path = path + self.path_params = path_params + self.body = body + self.query_params = query_params + self.headers = headers + + def validate(self) -> None: + """ + Validate all required parameters are present. + + :raises FgaValidationException: If any required parameter is missing + """ + if not self.operation_name: + raise FgaValidationException( + "operation_name is required for execute_api_request" + ) + if not self.method: + raise FgaValidationException("method is required for execute_api_request") + if not self.path: + raise FgaValidationException("path is required for execute_api_request") + + def build_path(self, configured_store_id: str | None = None) -> str: + """ + Build and validate final resource path with parameter substitution. + + Automatically substitutes {store_id} with configured store_id if not + explicitly provided in path_params. + + :param configured_store_id: Store ID from client configuration + :return: Final resource path with all parameters substituted + :raises FgaValidationException: If path construction fails + """ + path = self.path + params = dict(self.path_params) if self.path_params else {} + + # Auto-substitute store_id if needed + if "{store_id}" in path and "store_id" not in params: + if not configured_store_id: + raise FgaValidationException( + "Path contains {store_id} but store_id is not configured. " + "Set store_id in ClientConfiguration, use set_store_id(), " + "or provide it in path_params." + ) + params["store_id"] = configured_store_id + + # Replace all params + result = path + for key, value in params.items(): + placeholder = f"{{{key}}}" + if placeholder in result: + encoded = urllib.parse.quote(str(value), safe="") + result = result.replace(placeholder, encoded) + + # Validate no unresolved params remain + if "{" in result and "}" in result: + match = re.search(r"\{([^}]+)\}", result) + if match: + raise FgaValidationException( + f"Not all path parameters were provided for path: {path}" + ) + return result + + def build_query_params_list(self) -> list[tuple[str, str]]: + """ + Convert query_params dict to list of tuples for the API client. + + Handles: + - List values (expanded to multiple tuples with same key) + - None values (filtered out) + - Type conversion to string + + :return: List of (key, value) tuples for query parameters + """ + if not self.query_params: + return [] + + result = [] + for key, value in self.query_params.items(): + if value is None: + continue + if isinstance(value, list): + result.extend((key, str(item)) for item in value if item is not None) + else: + result.append((key, str(value))) + return result + + def build_headers( + self, + options_headers: dict[str, str] | None = None, + ) -> dict[str, str]: + """ + Build final headers with proper precedence and SDK enforcement. + + Header precedence (highest to lowest): + 1. SDK-enforced headers (Content-Type, Accept — always application/json) + 2. Options headers (from method options parameter) + 3. Request headers (from headers parameter) + + :param options_headers: Headers from method options + :return: Final merged headers with SDK enforcement + """ + result = dict(self.headers) if self.headers else {} + if options_headers: + result.update(options_headers) + # SDK always enforces these for consistent behavior + result["Content-Type"] = "application/json" + result["Accept"] = "application/json" + return result + + +class ResponseParser: + """Parse raw REST responses to appropriate Python types.""" + + @staticmethod + def parse_body( + data: bytes | str | dict[str, Any] | None, + ) -> bytes | str | dict[str, Any] | None: + """ + Parse response body, attempting JSON deserialization. + + Handles: + - None values (returned as-is) + - Dict objects (returned as-is) + - String values (attempts JSON parsing, falls back to string) + - Bytes values (attempts UTF-8 decode + JSON, falls back gracefully) + + :param data: Raw response data from REST client + :return: Parsed response (dict if JSON, else original type) + """ + if data is None: + return None + + if isinstance(data, dict): + return data + + if isinstance(data, str): + try: + return json.loads(data) + except (json.JSONDecodeError, ValueError): + return data + + if isinstance(data, bytes): + try: + decoded = data.decode("utf-8") + try: + return json.loads(decoded) + except (json.JSONDecodeError, ValueError): + return decoded + except UnicodeDecodeError: + return data + + return data diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 4a28f46..fef72f6 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1,11 +1,13 @@ -import json -import urllib.parse import uuid from concurrent.futures import ThreadPoolExecutor from typing import Any from openfga_sdk.client.configuration import ClientConfiguration +from openfga_sdk.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ResponseParser, +) from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ( ClientBatchCheckItem, @@ -1109,127 +1111,121 @@ def map_to_assertion(client_assertion: ClientAssertion) -> Assertion: ####################### def execute_api_request( self, - request: dict[str, Any], + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, options: dict[str, int | str | dict[str, int | str]] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. - Useful when you need to call a new or experimental API that doesn't yet have a built-in method in the SDK. - You still get the benefits of the SDK, like authentication, configuration, and consistent error handling. - - :param request: Request parameters dict with the following keys: - - operation_name (str): Required. Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint") - - method (str): Required. HTTP method (GET, POST, PUT, DELETE, PATCH) - - path (str): Required. API path (e.g., "/stores/{store_id}/my-endpoint") - - path_params (dict[str, str], optional): Path parameters to replace template variables in the path - - body (dict/list/str/bytes, optional): Request body for POST/PUT/PATCH requests - - query_params (dict[str, ...], optional): Query parameters - - headers (dict[str, str], optional): Custom request headers - :param options: Optional request options: - - headers: Additional headers (merged with request['headers']; options['headers'] takes precedence) + Useful when you need to call a new or experimental API that doesn't + yet have a built-in method in the SDK. You still get the benefits of + the SDK: authentication, configuration, retries, and error handling. + + :param operation_name: Required. Operation name for telemetry/logging + (e.g., "CustomCheck", "CustomEndpoint") + :param method: Required. HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: Required. API path with optional template parameters + (e.g., "/stores/{store_id}/my-endpoint") + :param path_params: Path parameters to replace template variables. + If {store_id} is in the path and not provided here, it will be + automatically substituted from the client configuration. + :param body: Request body for POST/PUT/PATCH requests + :param query_params: Query string parameters + :param headers: Custom request headers. SDK always enforces + Content-Type and Accept as application/json. + :param options: Additional request options: + - headers: Extra headers (merged; options headers take precedence) - retry_params: Override retry parameters for this request - :return: RawResponse object with status, headers, and body - :raises FgaValidationException: If required parameters are missing or invalid - :raises ApiException: For HTTP errors (with SDK error handling applied) - """ - return self._execute_api_request_internal(request, options, streaming=False) + :return: RawResponse with status, headers, and body + :raises FgaValidationException: If required parameters are missing + :raises ApiException: For HTTP errors + """ + return self._execute_api_request_internal( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=False, + ) def execute_streamed_api_request( self, - request: dict[str, Any], + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, options: dict[str, int | str | dict[str, int | str]] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. - Similar to execute_api_request but for streaming endpoints. - - :param request: Request parameters dict (see execute_api_request for details) - :param options: Optional request options (see execute_api_request for details) - :return: RawResponse object with status, headers, and body - :raises FgaValidationException: If required parameters are missing or invalid - :raises ApiException: For HTTP errors (with SDK error handling applied) - """ - return self._execute_api_request_internal(request, options, streaming=True) + Same interface as execute_api_request but for streaming endpoints. + See execute_api_request for full parameter documentation. + """ + return self._execute_api_request_internal( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=True, + ) def _execute_api_request_internal( self, - request: dict[str, Any], + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, options: dict[str, int | str | dict[str, int | str]] | None = None, streaming: bool = False, ) -> RawResponse: """Internal implementation for execute_api_request and execute_streamed_api_request.""" - # Extract request parameters - operation_name = request.get("operation_name") - method = request.get("method") - path = request.get("path") - query_params = request.get("query_params") - path_params = request.get("path_params") - headers = request.get("headers") - body = request.get("body") - - # Validate required parameters - if not operation_name: - raise FgaValidationException( - "operation_name is required for execute_api_request" - ) - if not method: - raise FgaValidationException("method is required for execute_api_request") - if not path: - raise FgaValidationException("path is required for execute_api_request") - - request_headers = dict(headers) if headers else {} - if options and options.get("headers"): - if isinstance(options["headers"], dict): - request_headers.update(options["headers"]) - - resource_path = path - path_params_dict = dict(path_params) if path_params else {} - - if "{store_id}" in resource_path and "store_id" not in path_params_dict: - store_id = self.get_store_id() - if store_id is None or store_id == "": - raise FgaValidationException( - "Path contains {store_id} but store_id is not configured. " - "Set store_id in ClientConfiguration, use set_store_id(), or provide it in path_params." - ) - path_params_dict["store_id"] = store_id - - for param_name, param_value in path_params_dict.items(): - placeholder = f"{{{param_name}}}" - if placeholder in resource_path: - encoded_value = urllib.parse.quote(str(param_value), safe="") - resource_path = resource_path.replace(placeholder, encoded_value) - - if "{" in resource_path or "}" in resource_path: - raise FgaValidationException( - f"Not all path parameters were provided for path: {path}" - ) + # 1. Validate and build request + builder = ExecuteApiRequestBuilder( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + ) + builder.validate() - query_params_list = [] - if query_params: - for key, value in query_params.items(): - if value is None: - continue - if isinstance(value, list): - for item in value: - if item is not None: - query_params_list.append((key, str(item))) - continue - query_params_list.append((key, str(value))) - - body_params = body - if "Content-Type" not in request_headers: - request_headers["Content-Type"] = "application/json" + # 2. Build path, query params, and headers + resource_path = builder.build_path(self.get_store_id()) + query_params_list = builder.build_query_params_list() - retry_params = None - if options and options.get("retry_params"): - retry_params = options["retry_params"] - if "Accept" not in request_headers: - request_headers["Accept"] = "application/json" + options_headers = None + if options and isinstance(options.get("headers"), dict): + options_headers = options["headers"] + final_headers = builder.build_headers(options_headers) - auth_headers = dict(request_headers) if request_headers else {} + # 3. Apply authentication + auth_headers = dict(final_headers) self._api_client.update_params_for_auth( auth_headers, query_params_list, @@ -1237,22 +1233,27 @@ def _execute_api_request_internal( oauth2_client=self._api._oauth2_client, ) - telemetry_attributes = None - if operation_name: - telemetry_attributes = { - TelemetryAttributes.fga_client_request_method: operation_name.lower(), - } - if self.get_store_id(): - telemetry_attributes[ - TelemetryAttributes.fga_client_request_store_id - ] = self.get_store_id() + # 4. Build telemetry attributes + telemetry_attributes = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( + self.get_store_id() + ) + + # 5. Extract retry params + retry_params = None + if options and options.get("retry_params"): + retry_params = options["retry_params"] + # 6. Make API request self._api_client.call_api( resource_path=resource_path, method=method.upper(), query_params=query_params_list if query_params_list else None, header_params=auth_headers if auth_headers else None, - body=body_params, + body=body, response_types_map={}, auth_settings=[], _return_http_data_only=True, @@ -1262,41 +1263,21 @@ def _execute_api_request_internal( _telemetry_attributes=telemetry_attributes, ) + # 7. Parse response rest_response: RESTResponse | None = getattr( self._api_client, "last_response", None ) if rest_response is None: - operation_suffix = ( - f" (operation: {operation_name})" if operation_name else "" - ) raise RuntimeError( f"Failed to get response from API client for {method.upper()} " - f"request to '{resource_path}'{operation_suffix}. " - "This may indicate an internal SDK error, network problem, or client configuration issue." + f"request to '{resource_path}' (operation: {operation_name}). " + "This may indicate an internal SDK error, network problem, " + "or client configuration issue." ) - response_body: bytes | str | dict[str, Any] | None = None - if rest_response.data is not None: - if isinstance(rest_response.data, str): - try: - response_body = json.loads(rest_response.data) - except (json.JSONDecodeError, ValueError): - response_body = rest_response.data - elif isinstance(rest_response.data, bytes): - try: - decoded = rest_response.data.decode("utf-8") - try: - response_body = json.loads(decoded) - except (json.JSONDecodeError, ValueError): - response_body = decoded - except UnicodeDecodeError: - response_body = rest_response.data - else: - response_body = rest_response.data - return RawResponse( status=rest_response.status, headers=dict(rest_response.getheaders()), - body=response_body, + body=ResponseParser.parse_body(rest_response.data), ) diff --git a/openfga_sdk/sync/client/execute_api_request_builder.py b/openfga_sdk/sync/client/execute_api_request_builder.py new file mode 100644 index 0000000..a63b8ce --- /dev/null +++ b/openfga_sdk/sync/client/execute_api_request_builder.py @@ -0,0 +1,14 @@ +""" +Re-export execute_api_request utilities for sync client. +""" + +from openfga_sdk.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ResponseParser, +) + + +__all__ = [ + "ExecuteApiRequestBuilder", + "ResponseParser", +] diff --git a/test/client/client_test.py b/test/client/client_test.py index 8f991b8..18a56a1 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -4175,9 +4175,9 @@ async def test_write_with_conflict_options_both(self, mock_request): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio async def test_raw_request_post_with_body(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request - Make a raw POST request with JSON body + Make a POST request with JSON body """ response_body = '{"result": "success", "data": {"id": "123"}}' mock_request.return_value = mock_response(response_body, 200) @@ -4186,14 +4186,12 @@ async def test_raw_request_post_with_body(self, mock_request): configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: response = await api_client.execute_api_request( - { - "operation_name": "CustomEndpoint", - "method": "POST", - "path": "/stores/{store_id}/custom-endpoint", - "body": {"user": "user:bob", "action": "custom_action"}, - "query_params": {"page_size": "20"}, - "headers": {"X-Experimental-Feature": "enabled"}, - } + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"user": "user:bob", "action": "custom_action"}, + query_params={"page_size": "20"}, + headers={"X-Experimental-Feature": "enabled"}, ) self.assertIsInstance(response, RawResponse) @@ -4228,9 +4226,9 @@ async def test_raw_request_post_with_body(self, mock_request): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio async def test_raw_request_get_with_query_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request - Make a raw GET request with query parameters + Make a GET request with query parameters """ response_body = ( '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' @@ -4240,15 +4238,13 @@ async def test_raw_request_get_with_query_params(self, mock_request): configuration = self.configuration async with OpenFgaClient(configuration) as api_client: response = await api_client.execute_api_request( - { - "operation_name": "ListStores", - "method": "GET", - "path": "/stores", - "query_params": { - "page_size": 10, - "continuation_token": "eyJwayI6...", - }, - } + operation_name="ListStores", + method="GET", + path="/stores", + query_params={ + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, ) self.assertIsInstance(response, RawResponse) @@ -4276,9 +4272,9 @@ async def test_raw_request_get_with_query_params(self, mock_request): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio async def test_raw_request_with_path_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request - Make a raw request with path parameters + Make a request with path parameters """ response_body = '{"authorization_model": {"id": "01G5JAVJ41T49E9TT3SKVS7X1J"}}' mock_request.return_value = mock_response(response_body, 200) @@ -4287,12 +4283,10 @@ async def test_raw_request_with_path_params(self, mock_request): configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: response = await api_client.execute_api_request( - { - "operation_name": "ReadAuthorizationModel", - "method": "GET", - "path": "/stores/{store_id}/authorization-models/{model_id}", - "path_params": {"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, - } + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, ) self.assertIsInstance(response, RawResponse) @@ -4315,7 +4309,7 @@ async def test_raw_request_with_path_params(self, mock_request): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio async def test_raw_request_auto_store_id_substitution(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test automatic store_id substitution when not provided in path_params """ @@ -4326,11 +4320,9 @@ async def test_raw_request_auto_store_id_substitution(self, mock_request): configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: response = await api_client.execute_api_request( - { - "operation_name": "GetStore", - "method": "GET", - "path": "/stores/{store_id}", - } + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", ) self.assertIsInstance(response, RawResponse) @@ -4351,7 +4343,7 @@ async def test_raw_request_auto_store_id_substitution(self, mock_request): @pytest.mark.asyncio async def test_raw_request_missing_operation_name(self): - """Test case for raw_request + """Test case for execute_api_request Test that operation_name is required """ @@ -4360,17 +4352,16 @@ async def test_raw_request_missing_operation_name(self): async with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: await api_client.execute_api_request( - { - "method": "GET", - "path": "/stores", - } + operation_name="", + method="GET", + path="/stores", ) self.assertIn("operation_name is required", str(error.exception)) await api_client.close() @pytest.mark.asyncio async def test_raw_request_missing_store_id(self): - """Test case for raw_request + """Test case for execute_api_request Test that store_id is required when path contains {store_id} """ @@ -4379,18 +4370,16 @@ async def test_raw_request_missing_store_id(self): async with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: await api_client.execute_api_request( - { - "operation_name": "GetStore", - "method": "GET", - "path": "/stores/{store_id}", - } + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", ) self.assertIn("store_id is not configured", str(error.exception)) await api_client.close() @pytest.mark.asyncio async def test_raw_request_missing_path_params(self): - """Test case for raw_request + """Test case for execute_api_request Test that all path parameters must be provided """ @@ -4399,12 +4388,10 @@ async def test_raw_request_missing_path_params(self): async with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: await api_client.execute_api_request( - { - "operation_name": "ReadAuthorizationModel", - "method": "GET", - "path": "/stores/{store_id}/authorization-models/{model_id}", - # Missing model_id in path_params - } + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + # Missing model_id in path_params ) self.assertIn("Not all path parameters were provided", str(error.exception)) await api_client.close() @@ -4412,7 +4399,7 @@ async def test_raw_request_missing_path_params(self): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio async def test_raw_request_with_list_query_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test query parameters with list values """ @@ -4422,12 +4409,10 @@ async def test_raw_request_with_list_query_params(self, mock_request): configuration = self.configuration async with OpenFgaClient(configuration) as api_client: response = await api_client.execute_api_request( - { - "operation_name": "ListStores", - "method": "GET", - "path": "/stores", - "query_params": {"ids": ["id1", "id2", "id3"]}, - } + operation_name="ListStores", + method="GET", + path="/stores", + query_params={"ids": ["id1", "id2", "id3"]}, ) self.assertIsInstance(response, RawResponse) @@ -4449,7 +4434,7 @@ async def test_raw_request_with_list_query_params(self, mock_request): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio async def test_raw_request_default_headers(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test that default headers (Content-Type, Accept) are set """ @@ -4460,12 +4445,10 @@ async def test_raw_request_default_headers(self, mock_request): configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: response = await api_client.execute_api_request( - { - "operation_name": "CustomEndpoint", - "method": "POST", - "path": "/stores/{store_id}/custom-endpoint", - "body": {"test": "data"}, - } + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"test": "data"}, ) self.assertIsInstance(response, RawResponse) @@ -4481,7 +4464,7 @@ async def test_raw_request_default_headers(self, mock_request): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio async def test_raw_request_url_encoded_path_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test that path parameters are URL encoded """ @@ -4492,12 +4475,10 @@ async def test_raw_request_url_encoded_path_params(self, mock_request): configuration.store_id = store_id async with OpenFgaClient(configuration) as api_client: response = await api_client.execute_api_request( - { - "operation_name": "CustomEndpoint", - "method": "GET", - "path": "/stores/{store_id}/custom/{param}", - "path_params": {"param": "value with spaces & special chars"}, - } + operation_name="CustomEndpoint", + method="GET", + path="/stores/{store_id}/custom/{param}", + path_params={"param": "value with spaces & special chars"}, ) self.assertIsInstance(response, RawResponse) diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index 6af7226..bc6bb9c 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -4157,9 +4157,9 @@ def test_sync_write_with_conflict_options_both(self, mock_request): @patch.object(rest.RESTClientObject, "request") def test_raw_request_post_with_body(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request - Make a raw POST request with JSON body + Make a POST request with JSON body """ response_body = '{"result": "success", "data": {"id": "123"}}' mock_request.return_value = mock_response(response_body, 200) @@ -4168,14 +4168,12 @@ def test_raw_request_post_with_body(self, mock_request): configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: response = api_client.execute_api_request( - { - "operation_name": "CustomEndpoint", - "method": "POST", - "path": "/stores/{store_id}/custom-endpoint", - "body": {"user": "user:bob", "action": "custom_action"}, - "query_params": {"page_size": "20"}, - "headers": {"X-Experimental-Feature": "enabled"}, - } + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"user": "user:bob", "action": "custom_action"}, + query_params={"page_size": "20"}, + headers={"X-Experimental-Feature": "enabled"}, ) self.assertIsInstance(response, RawResponse) @@ -4209,9 +4207,9 @@ def test_raw_request_post_with_body(self, mock_request): @patch.object(rest.RESTClientObject, "request") def test_raw_request_get_with_query_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request - Make a raw GET request with query parameters + Make a GET request with query parameters """ response_body = ( '{"stores": [{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}]}' @@ -4221,15 +4219,13 @@ def test_raw_request_get_with_query_params(self, mock_request): configuration = self.configuration with OpenFgaClient(configuration) as api_client: response = api_client.execute_api_request( - { - "operation_name": "ListStores", - "method": "GET", - "path": "/stores", - "query_params": { - "page_size": 10, - "continuation_token": "eyJwayI6...", - }, - } + operation_name="ListStores", + method="GET", + path="/stores", + query_params={ + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, ) self.assertIsInstance(response, RawResponse) @@ -4256,9 +4252,9 @@ def test_raw_request_get_with_query_params(self, mock_request): @patch.object(rest.RESTClientObject, "request") def test_raw_request_with_path_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request - Make a raw request with path parameters + Make a request with path parameters """ response_body = '{"authorization_model": {"id": "01G5JAVJ41T49E9TT3SKVS7X1J"}}' mock_request.return_value = mock_response(response_body, 200) @@ -4267,12 +4263,10 @@ def test_raw_request_with_path_params(self, mock_request): configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: response = api_client.execute_api_request( - { - "operation_name": "ReadAuthorizationModel", - "method": "GET", - "path": "/stores/{store_id}/authorization-models/{model_id}", - "path_params": {"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, - } + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, ) self.assertIsInstance(response, RawResponse) @@ -4294,7 +4288,7 @@ def test_raw_request_with_path_params(self, mock_request): @patch.object(rest.RESTClientObject, "request") def test_raw_request_auto_store_id_substitution(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test automatic store_id substitution when not provided in path_params """ @@ -4305,11 +4299,9 @@ def test_raw_request_auto_store_id_substitution(self, mock_request): configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: response = api_client.execute_api_request( - { - "operation_name": "GetStore", - "method": "GET", - "path": "/stores/{store_id}", - } + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", ) self.assertIsInstance(response, RawResponse) @@ -4329,7 +4321,7 @@ def test_raw_request_auto_store_id_substitution(self, mock_request): api_client.close() def test_raw_request_missing_operation_name(self): - """Test case for raw_request + """Test case for execute_api_request Test that operation_name is required """ @@ -4338,16 +4330,15 @@ def test_raw_request_missing_operation_name(self): with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: api_client.execute_api_request( - { - "method": "GET", - "path": "/stores", - } + operation_name="", + method="GET", + path="/stores", ) self.assertIn("operation_name is required", str(error.exception)) api_client.close() def test_raw_request_missing_store_id(self): - """Test case for raw_request + """Test case for execute_api_request Test that store_id is required when path contains {store_id} """ @@ -4356,17 +4347,15 @@ def test_raw_request_missing_store_id(self): with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: api_client.execute_api_request( - { - "operation_name": "GetStore", - "method": "GET", - "path": "/stores/{store_id}", - } + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", ) self.assertIn("store_id is not configured", str(error.exception)) api_client.close() def test_raw_request_missing_path_params(self): - """Test case for raw_request + """Test case for execute_api_request Test that all path parameters must be provided """ @@ -4375,19 +4364,17 @@ def test_raw_request_missing_path_params(self): with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: api_client.execute_api_request( - { - "operation_name": "ReadAuthorizationModel", - "method": "GET", - "path": "/stores/{store_id}/authorization-models/{model_id}", - # Missing model_id in path_params - } + operation_name="ReadAuthorizationModel", + method="GET", + path="/stores/{store_id}/authorization-models/{model_id}", + # Missing model_id in path_params ) self.assertIn("Not all path parameters were provided", str(error.exception)) api_client.close() @patch.object(rest.RESTClientObject, "request") def test_raw_request_with_list_query_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test query parameters with list values """ @@ -4397,12 +4384,10 @@ def test_raw_request_with_list_query_params(self, mock_request): configuration = self.configuration with OpenFgaClient(configuration) as api_client: response = api_client.execute_api_request( - { - "operation_name": "ListStores", - "method": "GET", - "path": "/stores", - "query_params": {"ids": ["id1", "id2", "id3"]}, - } + operation_name="ListStores", + method="GET", + path="/stores", + query_params={"ids": ["id1", "id2", "id3"]}, ) self.assertIsInstance(response, RawResponse) @@ -4422,7 +4407,7 @@ def test_raw_request_with_list_query_params(self, mock_request): @patch.object(rest.RESTClientObject, "request") def test_raw_request_default_headers(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test that default headers (Content-Type, Accept) are set """ @@ -4433,12 +4418,10 @@ def test_raw_request_default_headers(self, mock_request): configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: response = api_client.execute_api_request( - { - "operation_name": "CustomEndpoint", - "method": "POST", - "path": "/stores/{store_id}/custom-endpoint", - "body": {"test": "data"}, - } + operation_name="CustomEndpoint", + method="POST", + path="/stores/{store_id}/custom-endpoint", + body={"test": "data"}, ) self.assertIsInstance(response, RawResponse) @@ -4452,7 +4435,7 @@ def test_raw_request_default_headers(self, mock_request): @patch.object(rest.RESTClientObject, "request") def test_raw_request_url_encoded_path_params(self, mock_request): - """Test case for raw_request + """Test case for execute_api_request Test that path parameters are URL encoded """ @@ -4463,12 +4446,10 @@ def test_raw_request_url_encoded_path_params(self, mock_request): configuration.store_id = store_id with OpenFgaClient(configuration) as api_client: response = api_client.execute_api_request( - { - "operation_name": "CustomEndpoint", - "method": "GET", - "path": "/stores/{store_id}/custom/{param}", - "path_params": {"param": "value with spaces & special chars"}, - } + operation_name="CustomEndpoint", + method="GET", + path="/stores/{store_id}/custom/{param}", + path_params={"param": "value with spaces & special chars"}, ) self.assertIsInstance(response, RawResponse) From 1ba7b3d97dd6092485b8ce170b55fbe4504fe0b5 Mon Sep 17 00:00:00 2001 From: SoulPancake Date: Fri, 6 Mar 2026 16:04:35 +0530 Subject: [PATCH 10/17] feat: example for existing endpoints --- .../execute_api_request_example.py | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 example/execute-api-request/execute_api_request_example.py diff --git a/example/execute-api-request/execute_api_request_example.py b/example/execute-api-request/execute_api_request_example.py new file mode 100644 index 0000000..b82a6bd --- /dev/null +++ b/example/execute-api-request/execute_api_request_example.py @@ -0,0 +1,271 @@ +# ruff: noqa: E402 + +""" +execute_api_request example — calls real OpenFGA endpoints and compares +the results with the regular SDK methods to verify correctness. + +Requires a running OpenFGA server (default: http://localhost:8080). + export FGA_API_URL=http://localhost:8080 # optional, this is the default + python3 execute_api_request_example.py +""" + +import asyncio +import os +import sys + +sdk_path = os.path.realpath(os.path.join(os.path.abspath(__file__), "..", "..", "..")) +sys.path.insert(0, sdk_path) + +from openfga_sdk import ( + ClientConfiguration, + CreateStoreRequest, + Metadata, + ObjectRelation, + OpenFgaClient, + RelationMetadata, + RelationReference, + TypeDefinition, + Userset, + Usersets, + WriteAuthorizationModelRequest, +) +from openfga_sdk.client.models import ( + ClientCheckRequest, + ClientTuple, + ClientWriteRequest, +) +from openfga_sdk.credentials import Credentials + + +async def main(): + api_url = os.getenv("FGA_API_URL", "http://localhost:8080") + + configuration = ClientConfiguration( + api_url=api_url, + credentials=Credentials(), + ) + + async with OpenFgaClient(configuration) as fga_client: + + # ─── Setup: create a store, model, and tuple ───────────── + print("=== Setup ===") + + # Create a test store via the SDK + store = await fga_client.create_store( + CreateStoreRequest(name="execute_api_request_test") + ) + fga_client.set_store_id(store.id) + print(f"Created store: {store.id}") + + # Write an authorization model + model_resp = await fga_client.write_authorization_model( + WriteAuthorizationModelRequest( + schema_version="1.1", + type_definitions=[ + TypeDefinition(type="user"), + TypeDefinition( + type="document", + relations=dict( + writer=Userset(this=dict()), + viewer=Userset( + union=Usersets( + child=[ + Userset(this=dict()), + Userset( + computed_userset=ObjectRelation( + object="", relation="writer" + ) + ), + ] + ) + ), + ), + metadata=Metadata( + relations=dict( + writer=RelationMetadata( + directly_related_user_types=[ + RelationReference(type="user"), + ] + ), + viewer=RelationMetadata( + directly_related_user_types=[ + RelationReference(type="user"), + ] + ), + ) + ), + ), + ], + ) + ) + auth_model_id = model_resp.authorization_model_id + fga_client.set_authorization_model_id(auth_model_id) + print(f"Created model: {auth_model_id}") + + # Write a tuple + await fga_client.write( + ClientWriteRequest( + writes=[ + ClientTuple( + user="user:anne", + relation="writer", + object="document:roadmap", + ), + ] + ) + ) + print("Wrote tuple: user:anne → writer → document:roadmap") + + # ─── Tests ──────────────────────────────────────────────── + print("\n=== execute_api_request tests ===\n") + + # ── 1. GET /stores ──────────────────────────────────────── + print("1. ListStores (GET /stores)") + raw = await fga_client.execute_api_request( + operation_name="ListStores", + method="GET", + path="/stores", + query_params={"page_size": 100}, + ) + sdk = await fga_client.list_stores() + body = raw.json() + assert raw.status == 200, f"Expected 200, got {raw.status}" + assert "stores" in body + assert len(body["stores"]) == len(sdk.stores), ( + f"Count mismatch: {len(body['stores'])} vs {len(sdk.stores)}" + ) + print(f" ✅ {len(body['stores'])} stores (status {raw.status})") + + # ── 2. GET /stores/{store_id} (auto-substitution) ──────── + print("2. GetStore (GET /stores/{store_id})") + raw = await fga_client.execute_api_request( + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", + ) + sdk = await fga_client.get_store() + body = raw.json() + assert raw.status == 200 + assert body["id"] == sdk.id + assert body["name"] == sdk.name + print(f" ✅ id={body['id']}, name={body['name']}") + + # ── 3. GET /stores/{store_id}/authorization-models ──────── + print("3. ReadAuthorizationModels (GET /stores/{store_id}/authorization-models)") + raw = await fga_client.execute_api_request( + operation_name="ReadAuthorizationModels", + method="GET", + path="/stores/{store_id}/authorization-models", + ) + sdk = await fga_client.read_authorization_models() + body = raw.json() + assert raw.status == 200 + assert len(body["authorization_models"]) == len(sdk.authorization_models) + print(f" ✅ {len(body['authorization_models'])} models") + + # ── 4. POST /stores/{store_id}/check ────────────────────── + print("4. Check (POST /stores/{store_id}/check)") + raw = await fga_client.execute_api_request( + operation_name="Check", + method="POST", + path="/stores/{store_id}/check", + body={ + "tuple_key": { + "user": "user:anne", + "relation": "viewer", + "object": "document:roadmap", + }, + "authorization_model_id": auth_model_id, + }, + ) + sdk = await fga_client.check( + ClientCheckRequest( + user="user:anne", + relation="viewer", + object="document:roadmap", + ) + ) + body = raw.json() + assert raw.status == 200 + assert body["allowed"] == sdk.allowed + print(f" ✅ allowed={body['allowed']}") + + # ── 5. POST /stores/{store_id}/read ─────────────────────── + print("5. Read (POST /stores/{store_id}/read)") + raw = await fga_client.execute_api_request( + operation_name="Read", + method="POST", + path="/stores/{store_id}/read", + body={ + "tuple_key": { + "user": "user:anne", + "object": "document:", + }, + }, + ) + body = raw.json() + assert raw.status == 200 + assert "tuples" in body + assert len(body["tuples"]) >= 1 + print(f" ✅ {len(body['tuples'])} tuples returned") + + # ── 6. POST /stores — create store via raw request ──────── + print("6. CreateStore (POST /stores)") + raw = await fga_client.execute_api_request( + operation_name="CreateStore", + method="POST", + path="/stores", + body={"name": "raw_request_test_store"}, + ) + body = raw.json() + assert raw.status == 201, f"Expected 201, got {raw.status}" + assert "id" in body + new_store_id = body["id"] + print(f" ✅ created store: {new_store_id}") + + # ── 7. DELETE /stores/{store_id} — clean up ─────────────── + print("7. DeleteStore (DELETE /stores/{store_id})") + raw = await fga_client.execute_api_request( + operation_name="DeleteStore", + method="DELETE", + path="/stores/{store_id}", + path_params={"store_id": new_store_id}, + ) + assert raw.status == 204, f"Expected 204, got {raw.status}" + print(f" ✅ deleted store: {new_store_id} (status 204 No Content)") + + # ── 8. Custom headers ───────────────────────────────────── + print("8. Custom headers (GET /stores/{store_id})") + raw = await fga_client.execute_api_request( + operation_name="GetStoreWithHeaders", + method="GET", + path="/stores/{store_id}", + headers={"X-Custom-Header": "test-value"}, + ) + assert raw.status == 200 + print(f" ✅ custom headers accepted (status {raw.status})") + + # ── 9. Explicit path_params override for store_id ───────── + print("9. Explicit store_id in path_params") + raw = await fga_client.execute_api_request( + operation_name="GetStore", + method="GET", + path="/stores/{store_id}", + path_params={"store_id": store.id}, + ) + body = raw.json() + assert raw.status == 200 + assert body["id"] == store.id + print(f" ✅ explicit store_id matched: {body['id']}") + + # ─── Cleanup ───────────────────────────────────────────── + print("\n=== Cleanup ===") + await fga_client.delete_store() + print(f"Deleted test store: {store.id}") + + print("\n All execute_api_request integration tests passed!\n") + + +asyncio.run(main()) + + From 51b92aee2794494da57eb331387390e79dd6c818 Mon Sep 17 00:00:00 2001 From: SoulPancake Date: Fri, 6 Mar 2026 16:05:11 +0530 Subject: [PATCH 11/17] fix: lint --- .../execute-api-request/execute_api_request_example.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/execute-api-request/execute_api_request_example.py b/example/execute-api-request/execute_api_request_example.py index b82a6bd..aa874fe 100644 --- a/example/execute-api-request/execute_api_request_example.py +++ b/example/execute-api-request/execute_api_request_example.py @@ -13,6 +13,7 @@ import os import sys + sdk_path = os.path.realpath(os.path.join(os.path.abspath(__file__), "..", "..", "..")) sys.path.insert(0, sdk_path) @@ -46,7 +47,6 @@ async def main(): ) async with OpenFgaClient(configuration) as fga_client: - # ─── Setup: create a store, model, and tuple ───────────── print("=== Setup ===") @@ -151,7 +151,9 @@ async def main(): print(f" ✅ id={body['id']}, name={body['name']}") # ── 3. GET /stores/{store_id}/authorization-models ──────── - print("3. ReadAuthorizationModels (GET /stores/{store_id}/authorization-models)") + print( + "3. ReadAuthorizationModels (GET /stores/{store_id}/authorization-models)" + ) raw = await fga_client.execute_api_request( operation_name="ReadAuthorizationModels", method="GET", @@ -267,5 +269,3 @@ async def main(): asyncio.run(main()) - - From 4569a6aba06b77f77d3078386fd4dc0196037255 Mon Sep 17 00:00:00 2001 From: SoulPancake Date: Fri, 6 Mar 2026 16:17:16 +0530 Subject: [PATCH 12/17] fix: cleanup --- README.md | 32 +-------- openfga_sdk/client/client.py | 47 ++++---------- .../client/execute_api_request_builder.py | 65 +++---------------- openfga_sdk/client/models/raw_response.py | 29 +++------ openfga_sdk/sync/client/client.py | 47 ++++---------- .../client/execute_api_request_builder.py | 4 +- 6 files changed, 50 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 985a393..515135d 100644 --- a/README.md +++ b/README.md @@ -1263,7 +1263,7 @@ response = await fga_client.write_assertions(body, options) ### Calling Other Endpoints -In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `execute_api_request` method available on the `OpenFgaClient`. The `execute_api_request` method allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the HTTP method, path, body, query parameters, and path parameters, while still honoring the client configuration (authentication, telemetry, retries, and error handling). +In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `execute_api_request` method available on the `OpenFgaClient`. It allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the HTTP method, path, body, query parameters, and path parameters, while still honoring the client configuration (authentication, telemetry, retries, and error handling). For streaming endpoints, use `execute_streamed_api_request` instead. @@ -1272,35 +1272,7 @@ This is useful when: - You are using an earlier version of the SDK that doesn't yet support a particular endpoint - You have a custom endpoint deployed that extends the OpenFGA API -In all cases, you initialize the SDK the same way as usual, and then call `execute_api_request` on the `fga_client` instance. - -```python -from openfga_sdk import ClientConfiguration, OpenFgaClient - -configuration = ClientConfiguration( - api_url=FGA_API_URL, - store_id=FGA_STORE_ID, -) - -async with OpenFgaClient(configuration) as fga_client: - request_body = { - "user": "user:bob", - "action": "custom_action", - "resource": "resource:123", - } - - response = await fga_client.execute_api_request( - operation_name="CustomEndpoint", - method="POST", - path="/stores/{store_id}/custom-endpoint", - path_params={"store_id": FGA_STORE_ID}, - query_params={"page_size": "20"}, - body=request_body, - headers={"X-Experimental-Feature": "enabled"}, - ) -``` - -#### Example: Calling a custom endpoint with POST +#### Example: Calling a Custom Endpoint with POST ```python # Call a custom endpoint using path parameters diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 01e412e..6b59195 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1126,28 +1126,19 @@ async def execute_api_request( """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. - Useful when you need to call a new or experimental API that doesn't - yet have a built-in method in the SDK. You still get the benefits of - the SDK: authentication, configuration, retries, and error handling. - - :param operation_name: Required. Operation name for telemetry/logging - (e.g., "CustomCheck", "CustomEndpoint") - :param method: Required. HTTP method (GET, POST, PUT, DELETE, PATCH) - :param path: Required. API path with optional template parameters - (e.g., "/stores/{store_id}/my-endpoint") - :param path_params: Path parameters to replace template variables. - If {store_id} is in the path and not provided here, it will be - automatically substituted from the client configuration. - :param body: Request body for POST/PUT/PATCH requests + Useful for calling endpoints not yet wrapped by the SDK while + still getting authentication, retries, and error handling. + + :param operation_name: Operation name for telemetry (e.g., "CustomCheck") + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: API path, e.g. "/stores/{store_id}/my-endpoint". + {store_id} is auto-substituted from config if not in path_params. + :param path_params: Path parameter substitutions (URL-encoded automatically) + :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters - :param headers: Custom request headers. SDK always enforces - Content-Type and Accept as application/json. - :param options: Additional request options: - - headers: Extra headers (merged; options headers take precedence) - - retry_params: Override retry parameters for this request + :param headers: Custom headers (SDK enforces Content-Type and Accept) + :param options: Extra options (headers, retry_params) :return: RawResponse with status, headers, and body - :raises FgaValidationException: If required parameters are missing - :raises ApiException: For HTTP errors """ return await self._execute_api_request_internal( operation_name=operation_name, @@ -1204,8 +1195,7 @@ async def _execute_api_request_internal( options: dict[str, int | str | dict[str, int | str]] | None = None, streaming: bool = False, ) -> RawResponse: - """Internal implementation for execute_api_request and execute_streamed_api_request.""" - # 1. Validate and build request + """Shared implementation for execute_api_request and execute_streamed_api_request.""" builder = ExecuteApiRequestBuilder( operation_name=operation_name, method=method, @@ -1217,7 +1207,6 @@ async def _execute_api_request_internal( ) builder.validate() - # 2. Build path, query params, and headers resource_path = builder.build_path(self.get_store_id()) query_params_list = builder.build_query_params_list() @@ -1226,7 +1215,6 @@ async def _execute_api_request_internal( options_headers = options["headers"] final_headers = builder.build_headers(options_headers) - # 3. Apply authentication auth_headers = dict(final_headers) await self._api_client.update_params_for_auth( auth_headers, @@ -1235,7 +1223,6 @@ async def _execute_api_request_internal( oauth2_client=self._api._oauth2_client, ) - # 4. Build telemetry attributes telemetry_attributes = { TelemetryAttributes.fga_client_request_method: operation_name.lower(), } @@ -1244,12 +1231,10 @@ async def _execute_api_request_internal( self.get_store_id() ) - # 5. Extract retry params retry_params = None if options and options.get("retry_params"): retry_params = options["retry_params"] - # 6. Make API request await self._api_client.call_api( resource_path=resource_path, method=method.upper(), @@ -1266,17 +1251,13 @@ async def _execute_api_request_internal( _streaming=streaming, ) - # 7. Parse response rest_response: RESTResponse | None = getattr( self._api_client, "last_response", None ) - if rest_response is None: raise RuntimeError( - f"Failed to get response from API client for {method.upper()} " - f"request to '{resource_path}' (operation: {operation_name}). " - "This may indicate an internal SDK error, network problem, " - "or client configuration issue." + f"No response for {method.upper()} {resource_path} " + f"(operation: {operation_name})" ) return RawResponse( diff --git a/openfga_sdk/client/execute_api_request_builder.py b/openfga_sdk/client/execute_api_request_builder.py index 2ccfb2b..564ec75 100644 --- a/openfga_sdk/client/execute_api_request_builder.py +++ b/openfga_sdk/client/execute_api_request_builder.py @@ -1,9 +1,4 @@ -""" -Builders and utilities for execute_api_request functionality. - -This module provides reusable utilities for request building, header management, -and response parsing to reduce code duplication across sync and async clients. -""" +"""Utilities for execute_api_request: request building, header management, response parsing.""" import json import re @@ -15,12 +10,7 @@ class ExecuteApiRequestBuilder: - """ - Builder for API request execution. - - Encapsulates request validation, parameter building, and path construction - to eliminate duplication and improve maintainability. - """ + """Builds and validates parameters for execute_api_request calls.""" def __init__( self, @@ -43,11 +33,7 @@ def __init__( self.headers = headers def validate(self) -> None: - """ - Validate all required parameters are present. - - :raises FgaValidationException: If any required parameter is missing - """ + """Validate that all required parameters are present.""" if not self.operation_name: raise FgaValidationException( "operation_name is required for execute_api_request" @@ -59,14 +45,9 @@ def validate(self) -> None: def build_path(self, configured_store_id: str | None = None) -> str: """ - Build and validate final resource path with parameter substitution. - - Automatically substitutes {store_id} with configured store_id if not - explicitly provided in path_params. + Build resource path with parameter substitution. - :param configured_store_id: Store ID from client configuration - :return: Final resource path with all parameters substituted - :raises FgaValidationException: If path construction fails + Auto-substitutes {store_id} from client config if not in path_params. """ path = self.path params = dict(self.path_params) if self.path_params else {} @@ -99,16 +80,7 @@ def build_path(self, configured_store_id: str | None = None) -> str: return result def build_query_params_list(self) -> list[tuple[str, str]]: - """ - Convert query_params dict to list of tuples for the API client. - - Handles: - - List values (expanded to multiple tuples with same key) - - None values (filtered out) - - Type conversion to string - - :return: List of (key, value) tuples for query parameters - """ + """Convert query_params dict to list of (key, value) tuples. Expands lists, filters None.""" if not self.query_params: return [] @@ -127,15 +99,9 @@ def build_headers( options_headers: dict[str, str] | None = None, ) -> dict[str, str]: """ - Build final headers with proper precedence and SDK enforcement. + Merge request headers, options headers, and SDK-enforced defaults. - Header precedence (highest to lowest): - 1. SDK-enforced headers (Content-Type, Accept — always application/json) - 2. Options headers (from method options parameter) - 3. Request headers (from headers parameter) - - :param options_headers: Headers from method options - :return: Final merged headers with SDK enforcement + SDK always enforces Content-Type and Accept as application/json. """ result = dict(self.headers) if self.headers else {} if options_headers: @@ -147,24 +113,13 @@ def build_headers( class ResponseParser: - """Parse raw REST responses to appropriate Python types.""" + """Parse raw REST responses into Python types.""" @staticmethod def parse_body( data: bytes | str | dict[str, Any] | None, ) -> bytes | str | dict[str, Any] | None: - """ - Parse response body, attempting JSON deserialization. - - Handles: - - None values (returned as-is) - - Dict objects (returned as-is) - - String values (attempts JSON parsing, falls back to string) - - Bytes values (attempts UTF-8 decode + JSON, falls back gracefully) - - :param data: Raw response data from REST client - :return: Parsed response (dict if JSON, else original type) - """ + """Parse response data, attempting JSON deserialization.""" if data is None: return None diff --git a/openfga_sdk/client/models/raw_response.py b/openfga_sdk/client/models/raw_response.py index 889182b..e05d47b 100644 --- a/openfga_sdk/client/models/raw_response.py +++ b/openfga_sdk/client/models/raw_response.py @@ -1,9 +1,4 @@ -""" -Raw response wrapper for raw_request method. - -This module provides a simple response wrapper for raw HTTP requests -made through the SDK's raw_request method. -""" +"""Response wrapper for execute_api_request.""" import json @@ -14,32 +9,26 @@ @dataclass class RawResponse: """ - Response wrapper for raw HTTP requests. - - This class provides a simple interface to access the response - from a raw_request call, including status code, headers, and body. + Response from execute_api_request / execute_streamed_api_request. - The body is automatically parsed as JSON if possible, otherwise - it's returned as a string or bytes. + The body is automatically parsed as JSON if possible, + otherwise returned as a string or bytes. """ status: int """HTTP status code""" headers: dict[str, str] - """Response headers as a dictionary""" + """Response headers""" body: bytes | str | dict[str, Any] | None = None - """Response body (already parsed as dict if JSON, otherwise str or bytes)""" + """Response body (dict if JSON, otherwise str or bytes)""" def json(self) -> dict[str, Any] | None: """ - Return the response body as a JSON dictionary. - - The body is already parsed during the request, so this typically - just returns the body if it's a dict, or None otherwise. + Return the response body as a parsed JSON dictionary. - :return: Parsed JSON dictionary or None + :return: Parsed dict or None """ if isinstance(self.body, dict): return self.body @@ -49,7 +38,7 @@ def text(self) -> str | None: """ Return the response body as a string. - :return: Response body as string or None + :return: Body as string, or None """ if self.body is None: return None diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index fef72f6..3f3ee41 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1124,28 +1124,19 @@ def execute_api_request( """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. - Useful when you need to call a new or experimental API that doesn't - yet have a built-in method in the SDK. You still get the benefits of - the SDK: authentication, configuration, retries, and error handling. - - :param operation_name: Required. Operation name for telemetry/logging - (e.g., "CustomCheck", "CustomEndpoint") - :param method: Required. HTTP method (GET, POST, PUT, DELETE, PATCH) - :param path: Required. API path with optional template parameters - (e.g., "/stores/{store_id}/my-endpoint") - :param path_params: Path parameters to replace template variables. - If {store_id} is in the path and not provided here, it will be - automatically substituted from the client configuration. - :param body: Request body for POST/PUT/PATCH requests + Useful for calling endpoints not yet wrapped by the SDK while + still getting authentication, retries, and error handling. + + :param operation_name: Operation name for telemetry (e.g., "CustomCheck") + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: API path, e.g. "/stores/{store_id}/my-endpoint". + {store_id} is auto-substituted from config if not in path_params. + :param path_params: Path parameter substitutions (URL-encoded automatically) + :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters - :param headers: Custom request headers. SDK always enforces - Content-Type and Accept as application/json. - :param options: Additional request options: - - headers: Extra headers (merged; options headers take precedence) - - retry_params: Override retry parameters for this request + :param headers: Custom headers (SDK enforces Content-Type and Accept) + :param options: Extra options (headers, retry_params) :return: RawResponse with status, headers, and body - :raises FgaValidationException: If required parameters are missing - :raises ApiException: For HTTP errors """ return self._execute_api_request_internal( operation_name=operation_name, @@ -1202,8 +1193,7 @@ def _execute_api_request_internal( options: dict[str, int | str | dict[str, int | str]] | None = None, streaming: bool = False, ) -> RawResponse: - """Internal implementation for execute_api_request and execute_streamed_api_request.""" - # 1. Validate and build request + """Shared implementation for execute_api_request and execute_streamed_api_request.""" builder = ExecuteApiRequestBuilder( operation_name=operation_name, method=method, @@ -1215,7 +1205,6 @@ def _execute_api_request_internal( ) builder.validate() - # 2. Build path, query params, and headers resource_path = builder.build_path(self.get_store_id()) query_params_list = builder.build_query_params_list() @@ -1224,7 +1213,6 @@ def _execute_api_request_internal( options_headers = options["headers"] final_headers = builder.build_headers(options_headers) - # 3. Apply authentication auth_headers = dict(final_headers) self._api_client.update_params_for_auth( auth_headers, @@ -1233,7 +1221,6 @@ def _execute_api_request_internal( oauth2_client=self._api._oauth2_client, ) - # 4. Build telemetry attributes telemetry_attributes = { TelemetryAttributes.fga_client_request_method: operation_name.lower(), } @@ -1242,12 +1229,10 @@ def _execute_api_request_internal( self.get_store_id() ) - # 5. Extract retry params retry_params = None if options and options.get("retry_params"): retry_params = options["retry_params"] - # 6. Make API request self._api_client.call_api( resource_path=resource_path, method=method.upper(), @@ -1263,17 +1248,13 @@ def _execute_api_request_internal( _telemetry_attributes=telemetry_attributes, ) - # 7. Parse response rest_response: RESTResponse | None = getattr( self._api_client, "last_response", None ) - if rest_response is None: raise RuntimeError( - f"Failed to get response from API client for {method.upper()} " - f"request to '{resource_path}' (operation: {operation_name}). " - "This may indicate an internal SDK error, network problem, " - "or client configuration issue." + f"No response for {method.upper()} {resource_path} " + f"(operation: {operation_name})" ) return RawResponse( diff --git a/openfga_sdk/sync/client/execute_api_request_builder.py b/openfga_sdk/sync/client/execute_api_request_builder.py index a63b8ce..1da312b 100644 --- a/openfga_sdk/sync/client/execute_api_request_builder.py +++ b/openfga_sdk/sync/client/execute_api_request_builder.py @@ -1,6 +1,4 @@ -""" -Re-export execute_api_request utilities for sync client. -""" +"""Re-export for sync client.""" from openfga_sdk.client.execute_api_request_builder import ( ExecuteApiRequestBuilder, From b79b23509d4d89388c041caa271df50f88079894 Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Sat, 14 Mar 2026 00:45:10 +0530 Subject: [PATCH 13/17] feat: execute api request api layer refactor --- openfga_sdk/api/open_fga_api.py | 1995 ++++++++--------------------- openfga_sdk/client/client.py | 98 +- openfga_sdk/sync/client/client.py | 97 +- openfga_sdk/sync/open_fga_api.py | 1829 +++++++------------------- 4 files changed, 1043 insertions(+), 2976 deletions(-) diff --git a/openfga_sdk/api/open_fga_api.py b/openfga_sdk/api/open_fga_api.py index 84637f4..4845a83 100644 --- a/openfga_sdk/api/open_fga_api.py +++ b/openfga_sdk/api/open_fga_api.py @@ -10,6 +10,12 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ +from __future__ import annotations + +import urllib.parse + +from typing import Any + from openfga_sdk.api_client import ApiClient from openfga_sdk.exceptions import ApiValueError, FgaValidationException from openfga_sdk.oauth2 import OAuth2Client @@ -48,6 +54,219 @@ async def __aexit__(self, exc_type, exc_value, traceback): async def close(self): await self.api_client.close() + async def _execute( + self, + method: str, + path: str, + method_name: str, + response_types_map: dict, + body=None, + query_params=None, + local_var_params: dict | None = None, + ) -> Any: + """Shared executor for all API endpoint methods.""" + if local_var_params is None: + local_var_params = {} + + header_params = dict(local_var_params.get("_headers") or {}) + header_params["Accept"] = self.api_client.select_header_accept( + ["application/json"] + ) + if body is not None: + content_type = local_var_params.get( + "_content_type", + self.api_client.select_header_content_type( + ["application/json"], method, body + ), + ) + if content_type: + header_params["Content-Type"] = content_type + + telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: method_name, + 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, attributes=telemetry_attributes + ) + + return await self.api_client.call_api( + path, + method, + {}, + query_params or [], + header_params, + body=body, + post_params=[], + files={}, + response_types_map=response_types_map, + 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={}, + _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 execute_api_request( + self, + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Execute an arbitrary HTTP request to any OpenFGA API endpoint. + + Useful for calling endpoints not yet wrapped by the SDK while + still getting authentication, retries, and error handling. + + :param operation_name: Operation name for telemetry (e.g., "CustomCheck") + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: API path, e.g. "/stores/{store_id}/my-endpoint". + {store_id} is auto-substituted from config if not in path_params. + :param path_params: Path parameter substitutions (URL-encoded automatically) + :param body: Request body for POST/PUT/PATCH + :param query_params: Query string parameters + :param headers: Custom headers (SDK enforces Content-Type and Accept) + :param options: Extra options (headers, retry_params) + :return: RawResponse with status, headers, and body + """ + return await self._execute_api_request_internal( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=False, + ) + + async def execute_streamed_api_request( + self, + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. + + Same interface as execute_api_request but for streaming endpoints. + See execute_api_request for full parameter documentation. + """ + return await self._execute_api_request_internal( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=True, + ) + + async def _execute_api_request_internal( + self, + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + streaming: bool = False, + ) -> RawResponse: + """Shared implementation for execute_api_request and execute_streamed_api_request.""" + from openfga_sdk.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ResponseParser, + ) + from openfga_sdk.client.models.raw_response import RawResponse + + builder = ExecuteApiRequestBuilder( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + ) + builder.validate() + + resource_path = builder.build_path(self.api_client.get_store_id()) + query_params_list = builder.build_query_params_list() + + options_headers = None + if options and isinstance(options.get("headers"), dict): + options_headers = options["headers"] + final_headers = builder.build_headers(options_headers) + + retry_params = None + if options and options.get("retry_params"): + retry_params = options["retry_params"] + + telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.api_client.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( + self.api_client.get_store_id() + ) + + await self.api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=final_headers, + body=body, + response_types_map={}, + auth_settings=[], + _return_http_data_only=True, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._oauth2_client, + _telemetry_attributes=telemetry_attributes, + _streaming=streaming, + ) + + rest_response = getattr(self.api_client, "last_response", None) + if rest_response is None: + raise RuntimeError( + f"No response for {method.upper()} {resource_path} " + f"(operation: {operation_name})" + ) + + return RawResponse( + status=rest_response.status, + headers=dict(rest_response.getheaders()), + body=ResponseParser.parse_body(rest_response.data), + ) + async def batch_check(self, body, **kwargs): """Send a list of `check` operations in a single request @@ -112,19 +331,11 @@ async def batch_check_with_http_info(self, body, **kwargs): 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", - ] - ) + 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: @@ -133,7 +344,7 @@ async def batch_check_with_http_info(self, body, **kwargs): ) 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 @@ -142,91 +353,28 @@ async def batch_check_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `batch_check`" ) - 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 `batch_check`" ) 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: "BatchCheckResponse", - 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: "batch_check", - 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}/batch-check".replace("{store_id}", store_id), - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 await self._execute( + method="POST", + path=f"/stores/{store_id}/batch-check", + method_name="batch_check", + response_types_map={ + 200: "BatchCheckResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def check(self, body, **kwargs): @@ -293,19 +441,11 @@ async def check_with_http_info(self, body, **kwargs): 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", - ] - ) + 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: @@ -314,7 +454,7 @@ async def check_with_http_info(self, body, **kwargs): ) 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 @@ -323,91 +463,28 @@ async def check_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `check`" ) - 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 `check`" ) 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: "CheckResponse", - 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: "check", - 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}/check".replace("{store_id}", store_id), - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 await self._execute( + method="POST", + path=f"/stores/{store_id}/check", + method_name="check", + response_types_map={ + 200: "CheckResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def create_store(self, body, **kwargs): @@ -474,19 +551,11 @@ async def create_store_with_http_info(self, body, **kwargs): 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", - ] - ) + 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: @@ -496,85 +565,22 @@ async def create_store_with_http_info(self, body, **kwargs): local_var_params[key] = val del local_var_params["kwargs"] - collection_formats = {} - - path_params = {} - - store_id = None - - 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 = { - 201: "CreateStoreResponse", - 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: "create_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", - "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 await self._execute( + method="POST", + path="/stores", + method_name="create_store", + response_types_map={ + 201: "CreateStoreResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def delete_store(self, **kwargs): @@ -637,19 +643,11 @@ async def delete_store_with_http_info(self, **kwargs): 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", - ] - ) + 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: @@ -659,70 +657,18 @@ async def delete_store_with_http_info(self, **kwargs): 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 `delete_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 = {} - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "delete_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), - "DELETE", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 await self._execute( + method="DELETE", + path=f"/stores/{store_id}", + method_name="delete_store", + response_types_map={}, + local_var_params=local_var_params, ) async def expand(self, body, **kwargs): @@ -789,19 +735,11 @@ async def expand_with_http_info(self, body, **kwargs): 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", - ] - ) + 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: @@ -810,7 +748,7 @@ async def expand_with_http_info(self, body, **kwargs): ) 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 @@ -819,91 +757,28 @@ async def expand_with_http_info(self, body, **kwargs): "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), + return await self._execute( + method="POST", + path=f"/stores/{store_id}/expand", + method_name="expand", + response_types_map={ + 200: "ExpandResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def get_store(self, **kwargs): @@ -963,23 +838,15 @@ async def get_store_with_http_info(self, **kwargs): :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", - ] - ) - + 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( @@ -988,79 +855,27 @@ async def get_store_with_http_info(self, **kwargs): 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), + return await self._execute( + method="GET", + path=f"/stores/{store_id}", + method_name="get_store", + response_types_map={ + 200: "GetStoreResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + local_var_params=local_var_params, ) async def list_objects(self, body, **kwargs): @@ -1148,7 +963,7 @@ async def list_objects_with_http_info(self, body, **kwargs): ) 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 @@ -1157,91 +972,28 @@ async def list_objects_with_http_info(self, body, **kwargs): "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), + return await self._execute( + method="POST", + path=f"/stores/{store_id}/list-objects", + method_name="list_objects", + response_types_map={ + 200: "ListObjectsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def list_stores(self, **kwargs): @@ -1338,12 +1090,6 @@ async def list_stores_with_http_info(self, **kwargs): 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"])) @@ -1354,65 +1100,22 @@ async def list_stores_with_http_info(self, **kwargs): if local_var_params.get("name") is not None: query_params.append(("name", local_var_params["name"])) - 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), + return await self._execute( + method="GET", + path="/stores", + method_name="list_stores", + response_types_map={ + 200: "ListStoresResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + query_params=query_params, + local_var_params=local_var_params, ) async def list_users(self, body, **kwargs): @@ -1500,7 +1203,7 @@ async def list_users_with_http_info(self, body, **kwargs): ) 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 @@ -1509,91 +1212,28 @@ async def list_users_with_http_info(self, body, **kwargs): "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), + return await self._execute( + method="POST", + path=f"/stores/{store_id}/list-users", + method_name="list_users", + response_types_map={ + 200: "ListUsersResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def read(self, body, **kwargs): @@ -1681,7 +1321,7 @@ async def read_with_http_info(self, body, **kwargs): ) 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 @@ -1690,91 +1330,28 @@ async def read_with_http_info(self, body, **kwargs): "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), + return await self._execute( + method="POST", + path=f"/stores/{store_id}/read", + method_name="read", + response_types_map={ + 200: "ReadResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def read_assertions(self, authorization_model_id, **kwargs): @@ -1843,19 +1420,11 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) 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", - ] - ) + 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: @@ -1864,7 +1433,7 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) ) 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 @@ -1873,86 +1442,30 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) "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), + auth_model_id = urllib.parse.quote( + str(local_var_params["authorization_model_id"]), safe="" + ) + + return await self._execute( + method="GET", + path=f"/stores/{store_id}/assertions/{auth_model_id}", + method_name="read_assertions", + response_types_map={ + 200: "ReadAssertionsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + local_var_params=local_var_params, ) async def read_authorization_model(self, id, **kwargs): @@ -2019,19 +1532,11 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): 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", - ] - ) + 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: @@ -2040,7 +1545,7 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): ) 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 @@ -2049,84 +1554,28 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): "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), + model_id = urllib.parse.quote(str(local_var_params["id"]), safe="") + + return await self._execute( + method="GET", + path=f"/stores/{store_id}/authorization-models/{model_id}", + method_name="read_authorization_model", + response_types_map={ + 200: "ReadAuthorizationModelResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + local_var_params=local_var_params, ) async def read_authorization_models(self, **kwargs): @@ -2219,12 +1668,6 @@ async def read_authorization_models_with_http_info(self, **kwargs): 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`" @@ -2239,65 +1682,22 @@ async def read_authorization_models_with_http_info(self, **kwargs): ("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), + return await self._execute( + method="GET", + path=f"/stores/{store_id}/authorization-models", + method_name="read_authorization_models", + response_types_map={ + 200: "ReadAuthorizationModelsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + query_params=query_params, + local_var_params=local_var_params, ) async def read_changes(self, **kwargs): @@ -2398,12 +1798,6 @@ async def read_changes_with_http_info(self, **kwargs): 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`" @@ -2422,65 +1816,22 @@ async def read_changes_with_http_info(self, **kwargs): 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), + return await self._execute( + method="GET", + path=f"/stores/{store_id}/changes", + method_name="read_changes", + response_types_map={ + 200: "ReadChangesResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + query_params=query_params, + local_var_params=local_var_params, ) async def streamed_list_objects(self, body, **kwargs): @@ -2577,91 +1928,28 @@ async def streamed_list_objects_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `streamed_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 `streamed_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: "StreamResultOfStreamedListObjectsResponse", - 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: "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", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_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), + return await self._execute( + method="POST", + path=f"/stores/{store_id}/streamed-list-objects", + method_name="streamed_list_objects", + response_types_map={ + 200: "StreamResultOfStreamedListObjectsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def write(self, body, **kwargs): @@ -2758,91 +2046,28 @@ async def write_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `write`" ) - 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`" ) 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: "object", - 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", - 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}/write".replace("{store_id}", store_id), - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 await self._execute( + method="POST", + path=f"/stores/{store_id}/write", + method_name="write", + response_types_map={ + 200: "object", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def write_assertions(self, authorization_model_id, body, **kwargs): @@ -2955,89 +2180,22 @@ async def write_assertions_with_http_info( "Missing the required parameter `body` when calling `write_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 `write_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 - 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"], "PUT", body_params - ), - ) - if content_types_list: - header_params["Content-Type"] = content_types_list - - # Authentication setting - auth_settings = [] - - response_types_map = {} - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "write_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, + auth_model_id = urllib.parse.quote( + str(local_var_params["authorization_model_id"]), safe="" ) - return await self.api_client.call_api( - "/stores/{store_id}/assertions/{authorization_model_id}".replace( - "{store_id}", store_id - ), - "PUT", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 await self._execute( + method="PUT", + path=f"/stores/{store_id}/assertions/{auth_model_id}", + method_name="write_assertions", + response_types_map={}, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) async def write_authorization_model(self, body, **kwargs): @@ -3134,89 +2292,26 @@ async def write_authorization_model_with_http_info(self, body, **kwargs): "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() - 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 = { - 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", "" - ), - } - - 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), - "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 await self._execute( + method="POST", + path=f"/stores/{store_id}/authorization-models", + method_name="write_authorization_model", + response_types_map={ + 201: "WriteAuthorizationModelResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 6b59195..e353bb3 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -6,10 +6,6 @@ from openfga_sdk.api.open_fga_api import OpenFgaApi from openfga_sdk.api_client import ApiClient from openfga_sdk.client.configuration import ClientConfiguration -from openfga_sdk.client.execute_api_request_builder import ( - ExecuteApiRequestBuilder, - ResponseParser, -) from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ( ClientBatchCheckItem, @@ -74,10 +70,6 @@ WriteAuthorizationModelRequest, ) from openfga_sdk.models.write_request import WriteRequest -from openfga_sdk.rest import RESTResponse -from openfga_sdk.telemetry.attributes import ( - TelemetryAttributes, -) from openfga_sdk.validation import is_well_formed_ulid_string @@ -1140,7 +1132,7 @@ async def execute_api_request( :param options: Extra options (headers, retry_params) :return: RawResponse with status, headers, and body """ - return await self._execute_api_request_internal( + return await self._api.execute_api_request( operation_name=operation_name, method=method, path=path, @@ -1149,7 +1141,6 @@ async def execute_api_request( query_params=query_params, headers=headers, options=options, - streaming=False, ) async def execute_streamed_api_request( @@ -1170,7 +1161,7 @@ async def execute_streamed_api_request( Same interface as execute_api_request but for streaming endpoints. See execute_api_request for full parameter documentation. """ - return await self._execute_api_request_internal( + return await self._api.execute_streamed_api_request( operation_name=operation_name, method=method, path=path, @@ -1179,89 +1170,4 @@ async def execute_streamed_api_request( query_params=query_params, headers=headers, options=options, - streaming=True, - ) - - async def _execute_api_request_internal( - self, - *, - operation_name: str, - method: str, - path: str, - path_params: dict[str, str] | None = None, - body: dict[str, Any] | list[Any] | str | bytes | None = None, - query_params: dict[str, str | int | list[str | int]] | None = None, - headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - streaming: bool = False, - ) -> RawResponse: - """Shared implementation for execute_api_request and execute_streamed_api_request.""" - builder = ExecuteApiRequestBuilder( - operation_name=operation_name, - method=method, - path=path, - path_params=path_params, - body=body, - query_params=query_params, - headers=headers, - ) - builder.validate() - - resource_path = builder.build_path(self.get_store_id()) - query_params_list = builder.build_query_params_list() - - options_headers = None - if options and isinstance(options.get("headers"), dict): - options_headers = options["headers"] - final_headers = builder.build_headers(options_headers) - - auth_headers = dict(final_headers) - await self._api_client.update_params_for_auth( - auth_headers, - query_params_list, - auth_settings=[], - oauth2_client=self._api._oauth2_client, - ) - - telemetry_attributes = { - TelemetryAttributes.fga_client_request_method: operation_name.lower(), - } - if self.get_store_id(): - telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( - self.get_store_id() - ) - - retry_params = None - if options and options.get("retry_params"): - retry_params = options["retry_params"] - - await self._api_client.call_api( - resource_path=resource_path, - method=method.upper(), - query_params=query_params_list if query_params_list else None, - header_params=auth_headers if auth_headers else None, - body=body, - response_types_map={}, - auth_settings=[], - _return_http_data_only=True, - _preload_content=True, - _retry_params=retry_params, - _oauth2_client=self._api._oauth2_client, - _telemetry_attributes=telemetry_attributes, - _streaming=streaming, - ) - - rest_response: RESTResponse | None = getattr( - self._api_client, "last_response", None - ) - if rest_response is None: - raise RuntimeError( - f"No response for {method.upper()} {resource_path} " - f"(operation: {operation_name})" - ) - - return RawResponse( - status=rest_response.status, - headers=dict(rest_response.getheaders()), - body=ResponseParser.parse_body(rest_response.data), ) diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 3f3ee41..5c1fd54 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -4,10 +4,6 @@ from typing import Any from openfga_sdk.client.configuration import ClientConfiguration -from openfga_sdk.client.execute_api_request_builder import ( - ExecuteApiRequestBuilder, - ResponseParser, -) from openfga_sdk.client.models.assertion import ClientAssertion from openfga_sdk.client.models.batch_check_item import ( ClientBatchCheckItem, @@ -74,10 +70,6 @@ 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.sync.rest import RESTResponse -from openfga_sdk.telemetry.attributes import ( - TelemetryAttributes, -) from openfga_sdk.validation import is_well_formed_ulid_string @@ -1138,7 +1130,7 @@ def execute_api_request( :param options: Extra options (headers, retry_params) :return: RawResponse with status, headers, and body """ - return self._execute_api_request_internal( + return self._api.execute_api_request( operation_name=operation_name, method=method, path=path, @@ -1147,7 +1139,6 @@ def execute_api_request( query_params=query_params, headers=headers, options=options, - streaming=False, ) def execute_streamed_api_request( @@ -1168,7 +1159,7 @@ def execute_streamed_api_request( Same interface as execute_api_request but for streaming endpoints. See execute_api_request for full parameter documentation. """ - return self._execute_api_request_internal( + return self._api.execute_streamed_api_request( operation_name=operation_name, method=method, path=path, @@ -1177,88 +1168,4 @@ def execute_streamed_api_request( query_params=query_params, headers=headers, options=options, - streaming=True, - ) - - def _execute_api_request_internal( - self, - *, - operation_name: str, - method: str, - path: str, - path_params: dict[str, str] | None = None, - body: dict[str, Any] | list[Any] | str | bytes | None = None, - query_params: dict[str, str | int | list[str | int]] | None = None, - headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - streaming: bool = False, - ) -> RawResponse: - """Shared implementation for execute_api_request and execute_streamed_api_request.""" - builder = ExecuteApiRequestBuilder( - operation_name=operation_name, - method=method, - path=path, - path_params=path_params, - body=body, - query_params=query_params, - headers=headers, - ) - builder.validate() - - resource_path = builder.build_path(self.get_store_id()) - query_params_list = builder.build_query_params_list() - - options_headers = None - if options and isinstance(options.get("headers"), dict): - options_headers = options["headers"] - final_headers = builder.build_headers(options_headers) - - auth_headers = dict(final_headers) - self._api_client.update_params_for_auth( - auth_headers, - query_params_list, - auth_settings=[], - oauth2_client=self._api._oauth2_client, - ) - - telemetry_attributes = { - TelemetryAttributes.fga_client_request_method: operation_name.lower(), - } - if self.get_store_id(): - telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( - self.get_store_id() - ) - - retry_params = None - if options and options.get("retry_params"): - retry_params = options["retry_params"] - - self._api_client.call_api( - resource_path=resource_path, - method=method.upper(), - query_params=query_params_list if query_params_list else None, - header_params=auth_headers if auth_headers else None, - body=body, - response_types_map={}, - auth_settings=[], - _return_http_data_only=True, - _preload_content=True, - _retry_params=retry_params, - _oauth2_client=self._api._oauth2_client, - _telemetry_attributes=telemetry_attributes, - ) - - rest_response: RESTResponse | None = getattr( - self._api_client, "last_response", None - ) - if rest_response is None: - raise RuntimeError( - f"No response for {method.upper()} {resource_path} " - f"(operation: {operation_name})" - ) - - return RawResponse( - status=rest_response.status, - headers=dict(rest_response.getheaders()), - body=ResponseParser.parse_body(rest_response.data), ) diff --git a/openfga_sdk/sync/open_fga_api.py b/openfga_sdk/sync/open_fga_api.py index 73b4f80..6ee64c9 100644 --- a/openfga_sdk/sync/open_fga_api.py +++ b/openfga_sdk/sync/open_fga_api.py @@ -10,6 +10,12 @@ NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. """ +from __future__ import annotations + +import urllib.parse + +from typing import Any + from openfga_sdk.exceptions import ApiValueError, FgaValidationException from openfga_sdk.sync.api_client import ApiClient from openfga_sdk.sync.oauth2 import OAuth2Client @@ -46,6 +52,219 @@ def __exit__(self): def close(self): self.api_client.close() + def _execute( + self, + method: str, + path: str, + method_name: str, + response_types_map: dict, + body=None, + query_params=None, + local_var_params: dict | None = None, + ) -> Any: + """Shared executor for all API endpoint methods.""" + if local_var_params is None: + local_var_params = {} + + header_params = dict(local_var_params.get("_headers") or {}) + header_params["Accept"] = self.api_client.select_header_accept( + ["application/json"] + ) + if body is not None: + content_type = local_var_params.get( + "_content_type", + self.api_client.select_header_content_type( + ["application/json"], method, body + ), + ) + if content_type: + header_params["Content-Type"] = content_type + + telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: method_name, + 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, attributes=telemetry_attributes + ) + + return self.api_client.call_api( + path, + method, + {}, + query_params or [], + header_params, + body=body, + post_params=[], + files={}, + response_types_map=response_types_map, + 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={}, + _request_auth=local_var_params.get("_request_auth"), + _oauth2_client=self._oauth2_client, + _telemetry_attributes=telemetry_attributes, + _streaming=local_var_params.get("_streaming", False), + ) + + def execute_api_request( + self, + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Execute an arbitrary HTTP request to any OpenFGA API endpoint. + + Useful for calling endpoints not yet wrapped by the SDK while + still getting authentication, retries, and error handling. + + :param operation_name: Operation name for telemetry (e.g., "CustomCheck") + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: API path, e.g. "/stores/{store_id}/my-endpoint". + {store_id} is auto-substituted from config if not in path_params. + :param path_params: Path parameter substitutions (URL-encoded automatically) + :param body: Request body for POST/PUT/PATCH + :param query_params: Query string parameters + :param headers: Custom headers (SDK enforces Content-Type and Accept) + :param options: Extra options (headers, retry_params) + :return: RawResponse with status, headers, and body + """ + return self._execute_api_request_internal( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=False, + ) + + def execute_streamed_api_request( + self, + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + ) -> RawResponse: + """ + Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. + + Same interface as execute_api_request but for streaming endpoints. + See execute_api_request for full parameter documentation. + """ + return self._execute_api_request_internal( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + options=options, + streaming=True, + ) + + def _execute_api_request_internal( + self, + *, + operation_name: str, + method: str, + path: str, + path_params: dict[str, str] | None = None, + body: dict[str, Any] | list[Any] | str | bytes | None = None, + query_params: dict[str, str | int | list[str | int]] | None = None, + headers: dict[str, str] | None = None, + options: dict[str, int | str | dict[str, int | str]] | None = None, + streaming: bool = False, + ) -> RawResponse: + """Shared implementation for execute_api_request and execute_streamed_api_request.""" + from openfga_sdk.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ResponseParser, + ) + from openfga_sdk.client.models.raw_response import RawResponse + + builder = ExecuteApiRequestBuilder( + operation_name=operation_name, + method=method, + path=path, + path_params=path_params, + body=body, + query_params=query_params, + headers=headers, + ) + builder.validate() + + resource_path = builder.build_path(self.api_client.get_store_id()) + query_params_list = builder.build_query_params_list() + + options_headers = None + if options and isinstance(options.get("headers"), dict): + options_headers = options["headers"] + final_headers = builder.build_headers(options_headers) + + retry_params = None + if options and options.get("retry_params"): + retry_params = options["retry_params"] + + telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.api_client.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( + self.api_client.get_store_id() + ) + + self.api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=final_headers, + body=body, + response_types_map={}, + auth_settings=[], + _return_http_data_only=True, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._oauth2_client, + _telemetry_attributes=telemetry_attributes, + _streaming=streaming, + ) + + rest_response = getattr(self.api_client, "last_response", None) + if rest_response is None: + raise RuntimeError( + f"No response for {method.upper()} {resource_path} " + f"(operation: {operation_name})" + ) + + return RawResponse( + status=rest_response.status, + headers=dict(rest_response.getheaders()), + body=ResponseParser.parse_body(rest_response.data), + ) + def batch_check(self, body, **kwargs): """Send a list of `check` operations in a single request @@ -140,91 +359,28 @@ def batch_check_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `batch_check`" ) - 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 `batch_check`" ) 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: "BatchCheckResponse", - 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: "batch_check", - 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 self.api_client.call_api( - "/stores/{store_id}/batch-check".replace("{store_id}", store_id), - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 self._execute( + method="POST", + path=f"/stores/{store_id}/batch-check", + method_name="batch_check", + response_types_map={ + 200: "BatchCheckResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def check(self, body, **kwargs): @@ -321,91 +477,28 @@ def check_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `check`" ) - 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 `check`" ) 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: "CheckResponse", - 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: "check", - 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 self.api_client.call_api( - "/stores/{store_id}/check".replace("{store_id}", store_id), - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 self._execute( + method="POST", + path=f"/stores/{store_id}/check", + method_name="check", + response_types_map={ + 200: "CheckResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def create_store(self, body, **kwargs): @@ -494,85 +587,22 @@ def create_store_with_http_info(self, body, **kwargs): local_var_params[key] = val del local_var_params["kwargs"] - collection_formats = {} - - path_params = {} - - store_id = None - - 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 = { - 201: "CreateStoreResponse", - 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: "create_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 self.api_client.call_api( - "/stores", - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 self._execute( + method="POST", + path="/stores", + method_name="create_store", + response_types_map={ + 201: "CreateStoreResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def delete_store(self, **kwargs): @@ -657,70 +687,18 @@ def delete_store_with_http_info(self, **kwargs): 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 `delete_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 = {} - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "delete_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 self.api_client.call_api( - "/stores/{store_id}".replace("{store_id}", store_id), - "DELETE", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 self._execute( + method="DELETE", + path=f"/stores/{store_id}", + method_name="delete_store", + response_types_map={}, + local_var_params=local_var_params, ) def expand(self, body, **kwargs): @@ -817,91 +795,28 @@ def expand_with_http_info(self, body, **kwargs): "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 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), + return self._execute( + method="POST", + path=f"/stores/{store_id}/expand", + method_name="expand", + response_types_map={ + 200: "ExpandResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def get_store(self, **kwargs): @@ -986,79 +901,27 @@ def get_store_with_http_info(self, **kwargs): 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 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), + return self._execute( + method="GET", + path=f"/stores/{store_id}", + method_name="get_store", + response_types_map={ + 200: "GetStoreResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + local_var_params=local_var_params, ) def list_objects(self, body, **kwargs): @@ -1155,91 +1018,28 @@ def list_objects_with_http_info(self, body, **kwargs): "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 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), + return self._execute( + method="POST", + path=f"/stores/{store_id}/list-objects", + method_name="list_objects", + response_types_map={ + 200: "ListObjectsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def list_stores(self, **kwargs): @@ -1336,12 +1136,6 @@ def list_stores_with_http_info(self, **kwargs): 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"])) @@ -1352,65 +1146,22 @@ def list_stores_with_http_info(self, **kwargs): if local_var_params.get("name") is not None: query_params.append(("name", local_var_params["name"])) - 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 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), + return self._execute( + method="GET", + path="/stores", + method_name="list_stores", + response_types_map={ + 200: "ListStoresResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + query_params=query_params, + local_var_params=local_var_params, ) def list_users(self, body, **kwargs): @@ -1507,91 +1258,28 @@ def list_users_with_http_info(self, body, **kwargs): "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 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), + return self._execute( + method="POST", + path=f"/stores/{store_id}/list-users", + method_name="list_users", + response_types_map={ + 200: "ListUsersResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def read(self, body, **kwargs): @@ -1688,91 +1376,28 @@ def read_with_http_info(self, body, **kwargs): "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 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), + return self._execute( + method="POST", + path=f"/stores/{store_id}/read", + method_name="read", + response_types_map={ + 200: "ReadResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def read_assertions(self, authorization_model_id, **kwargs): @@ -1869,86 +1494,30 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): "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 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), + store_id = self.api_client._get_store_id() + auth_model_id = urllib.parse.quote( + str(local_var_params["authorization_model_id"]), safe="" + ) + + return self._execute( + method="GET", + path=f"/stores/{store_id}/assertions/{auth_model_id}", + method_name="read_assertions", + response_types_map={ + 200: "ReadAssertionsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + local_var_params=local_var_params, ) def read_authorization_model(self, id, **kwargs): @@ -2045,84 +1614,28 @@ def read_authorization_model_with_http_info(self, id, **kwargs): "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 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), + model_id = urllib.parse.quote(str(local_var_params["id"]), safe="") + + return self._execute( + method="GET", + path=f"/stores/{store_id}/authorization-models/{model_id}", + method_name="read_authorization_model", + response_types_map={ + 200: "ReadAuthorizationModelResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + local_var_params=local_var_params, ) def read_authorization_models(self, **kwargs): @@ -2215,12 +1728,6 @@ def read_authorization_models_with_http_info(self, **kwargs): 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`" @@ -2235,65 +1742,22 @@ def read_authorization_models_with_http_info(self, **kwargs): ("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 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), + return self._execute( + method="GET", + path=f"/stores/{store_id}/authorization-models", + method_name="read_authorization_models", + response_types_map={ + 200: "ReadAuthorizationModelsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + query_params=query_params, + local_var_params=local_var_params, ) def read_changes(self, **kwargs): @@ -2394,12 +1858,6 @@ def read_changes_with_http_info(self, **kwargs): 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`" @@ -2418,65 +1876,22 @@ def read_changes_with_http_info(self, **kwargs): 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 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), + return self._execute( + method="GET", + path=f"/stores/{store_id}/changes", + method_name="read_changes", + response_types_map={ + 200: "ReadChangesResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + query_params=query_params, + local_var_params=local_var_params, ) def streamed_list_objects(self, body, **kwargs): @@ -2573,91 +1988,28 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `streamed_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 `streamed_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: "StreamResultOfStreamedListObjectsResponse", - 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: "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", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return 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), + return self._execute( + method="POST", + path=f"/stores/{store_id}/streamed-list-objects", + method_name="streamed_list_objects", + response_types_map={ + 200: "StreamResultOfStreamedListObjectsResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def write(self, body, **kwargs): @@ -2754,91 +2106,28 @@ def write_with_http_info(self, body, **kwargs): "Missing the required parameter `body` when calling `write`" ) - 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`" ) 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: "object", - 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", - 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 self.api_client.call_api( - "/stores/{store_id}/write".replace("{store_id}", store_id), - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 self._execute( + method="POST", + path=f"/stores/{store_id}/write", + method_name="write", + response_types_map={ + 200: "object", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def write_assertions(self, authorization_model_id, body, **kwargs): @@ -2949,89 +2238,22 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs "Missing the required parameter `body` when calling `write_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 `write_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 - 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"], "PUT", body_params - ), - ) - if content_types_list: - header_params["Content-Type"] = content_types_list - - # Authentication setting - auth_settings = [] - - response_types_map = {} - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "write_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, + auth_model_id = urllib.parse.quote( + str(local_var_params["authorization_model_id"]), safe="" ) - return self.api_client.call_api( - "/stores/{store_id}/assertions/{authorization_model_id}".replace( - "{store_id}", store_id - ), - "PUT", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 self._execute( + method="PUT", + path=f"/stores/{store_id}/assertions/{auth_model_id}", + method_name="write_assertions", + response_types_map={}, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) def write_authorization_model(self, body, **kwargs): @@ -3128,89 +2350,26 @@ def write_authorization_model_with_http_info(self, body, **kwargs): "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() - 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 = { - 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", "" - ), - } - - telemetry_attributes = TelemetryAttributes.fromBody( - body=body_params, - attributes=telemetry_attributes, - ) - - return self.api_client.call_api( - "/stores/{store_id}/authorization-models".replace("{store_id}", store_id), - "POST", - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_types_map=response_types_map, - auth_settings=auth_settings, - async_req=local_var_params.get("async_req"), - _return_http_data_only=local_var_params.get("_return_http_data_only"), - _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 self._execute( + method="POST", + path=f"/stores/{store_id}/authorization-models", + method_name="write_authorization_model", + response_types_map={ + 201: "WriteAuthorizationModelResponse", + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + }, + body=local_var_params.get("body"), + local_var_params=local_var_params, ) From 9946cf8636f7ba5d4d725e47105b7ffa80ada563 Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Sat, 14 Mar 2026 01:04:08 +0530 Subject: [PATCH 14/17] fix: changelog and ruff lint --- CHANGELOG.md | 2 ++ openfga_sdk/api/open_fga_api.py | 6 +++++- openfga_sdk/sync/open_fga_api.py | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c64672..4704a4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased](https://github.com/openfga/python-sdk/compare/v0.9.9...HEAD) +- feat: add `execute_api_request` and `execute_streamed_api_request` methods to `OpenFgaClient` and `OpenFgaApi` for making arbitrary HTTP requests to any OpenFGA API endpoint with full auth, retry, and telemetry support (#252) - thanks @kcbiradar + ### [0.9.9](https://github.com/openfga/python-sdk/compare/v0.9.8...v0.9.9) (2025-12-09) - feat: improve error messaging (#245) diff --git a/openfga_sdk/api/open_fga_api.py b/openfga_sdk/api/open_fga_api.py index 4845a83..4f8bc17 100644 --- a/openfga_sdk/api/open_fga_api.py +++ b/openfga_sdk/api/open_fga_api.py @@ -14,9 +14,13 @@ import urllib.parse -from typing import Any +from typing import TYPE_CHECKING, Any from openfga_sdk.api_client import ApiClient + + +if TYPE_CHECKING: + from openfga_sdk.client.models.raw_response import RawResponse from openfga_sdk.exceptions import ApiValueError, FgaValidationException from openfga_sdk.oauth2 import OAuth2Client from openfga_sdk.telemetry import Telemetry diff --git a/openfga_sdk/sync/open_fga_api.py b/openfga_sdk/sync/open_fga_api.py index 6ee64c9..bdcecbe 100644 --- a/openfga_sdk/sync/open_fga_api.py +++ b/openfga_sdk/sync/open_fga_api.py @@ -14,9 +14,13 @@ import urllib.parse -from typing import Any +from typing import TYPE_CHECKING, Any from openfga_sdk.exceptions import ApiValueError, FgaValidationException + + +if TYPE_CHECKING: + from openfga_sdk.client.models.raw_response import RawResponse from openfga_sdk.sync.api_client import ApiClient from openfga_sdk.sync.oauth2 import OAuth2Client from openfga_sdk.telemetry import Telemetry From 826f1d9dfab6c11f3a33b34610d37c24be704cfe Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Mon, 16 Mar 2026 20:32:49 +0530 Subject: [PATCH 15/17] feat: remove unused import --- openfga_sdk/api/open_fga_api.py | 209 ++++++++++++++++---------------- 1 file changed, 107 insertions(+), 102 deletions(-) diff --git a/openfga_sdk/api/open_fga_api.py b/openfga_sdk/api/open_fga_api.py index 4f8bc17..0fc3b3d 100644 --- a/openfga_sdk/api/open_fga_api.py +++ b/openfga_sdk/api/open_fga_api.py @@ -12,8 +12,6 @@ from __future__ import annotations -import urllib.parse - from typing import TYPE_CHECKING, Any from openfga_sdk.api_client import ApiClient @@ -335,11 +333,19 @@ async def batch_check_with_http_info(self, body, **kwargs): 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", - ]) + 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: @@ -348,7 +354,6 @@ async def batch_check_with_http_info(self, body, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("body") is None @@ -356,13 +361,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/batch-check", @@ -445,11 +448,19 @@ async def check_with_http_info(self, body, **kwargs): 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", - ]) + 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: @@ -458,7 +469,6 @@ async def check_with_http_info(self, body, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("body") is None @@ -466,13 +476,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/check", @@ -555,11 +563,19 @@ async def create_store_with_http_info(self, body, **kwargs): 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", - ]) + 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: @@ -568,7 +584,6 @@ async def create_store_with_http_info(self, body, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - return await self._execute( method="POST", path="/stores", @@ -647,11 +662,19 @@ async def delete_store_with_http_info(self, **kwargs): 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", - ]) + 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: @@ -660,13 +683,11 @@ 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() - return await self._execute( method="DELETE", path=f"/stores/{store_id}", @@ -739,11 +760,19 @@ async def expand_with_http_info(self, body, **kwargs): 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", - ]) + 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: @@ -752,7 +781,6 @@ async def expand_with_http_info(self, body, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("body") is None @@ -760,13 +788,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/expand", @@ -845,11 +871,19 @@ async def get_store_with_http_info(self, **kwargs): 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", - ]) + 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: @@ -858,13 +892,11 @@ 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() - return await self._execute( method="GET", path=f"/stores/{store_id}", @@ -967,7 +999,6 @@ async def list_objects_with_http_info(self, body, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("body") is None @@ -975,13 +1006,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/list-objects", @@ -1093,7 +1122,6 @@ async def list_stores_with_http_info(self, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - query_params = [] if local_var_params.get("page_size") is not None: query_params.append(("page_size", local_var_params["page_size"])) @@ -1103,7 +1131,6 @@ async def list_stores_with_http_info(self, **kwargs): ) if local_var_params.get("name") is not None: query_params.append(("name", local_var_params["name"])) - return await self._execute( method="GET", path="/stores", @@ -1207,7 +1234,6 @@ async def list_users_with_http_info(self, body, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("body") is None @@ -1215,13 +1241,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/list-users", @@ -1325,7 +1349,6 @@ async def read_with_http_info(self, body, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("body") is None @@ -1333,13 +1356,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/read", @@ -1424,11 +1445,19 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) 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", - ]) + 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: @@ -1437,7 +1466,6 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("authorization_model_id") is None @@ -1445,19 +1473,14 @@ 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() - auth_model_id = urllib.parse.quote( - str(local_var_params["authorization_model_id"]), safe="" - ) - return await self._execute( method="GET", - path=f"/stores/{store_id}/assertions/{auth_model_id}", + path=f"/stores/{store_id}/assertions/{authorization_model_id}", method_name="read_assertions", response_types_map={ 200: "ReadAssertionsResponse", @@ -1536,11 +1559,19 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): 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", - ]) + 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: @@ -1549,7 +1580,6 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): ) local_var_params[key] = val del local_var_params["kwargs"] - if ( self.api_client.client_side_validation and local_var_params.get("id") is None @@ -1557,17 +1587,14 @@ 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() - model_id = urllib.parse.quote(str(local_var_params["id"]), safe="") - return await self._execute( method="GET", - path=f"/stores/{store_id}/authorization-models/{model_id}", + path=f"/stores/{store_id}/authorization-models/{id}", method_name="read_authorization_model", response_types_map={ 200: "ReadAuthorizationModelResponse", @@ -1671,13 +1698,11 @@ 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() - query_params = [] if local_var_params.get("page_size") is not None: query_params.append(("page_size", local_var_params["page_size"])) @@ -1685,7 +1710,6 @@ async def read_authorization_models_with_http_info(self, **kwargs): query_params.append( ("continuation_token", local_var_params["continuation_token"]) ) - return await self._execute( method="GET", path=f"/stores/{store_id}/authorization-models", @@ -1801,13 +1825,11 @@ 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() - query_params = [] if local_var_params.get("type") is not None: query_params.append(("type", local_var_params["type"])) @@ -1819,7 +1841,6 @@ async def read_changes_with_http_info(self, **kwargs): ) if local_var_params.get("start_time") is not None: query_params.append(("start_time", local_var_params["start_time"])) - return await self._execute( method="GET", path=f"/stores/{store_id}/changes", @@ -1923,7 +1944,6 @@ async def streamed_list_objects_with_http_info(self, body, **kwargs): ) 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 @@ -1931,13 +1951,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/streamed-list-objects", @@ -2041,7 +2059,6 @@ async def write_with_http_info(self, body, **kwargs): ) 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 @@ -2049,13 +2066,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/write", @@ -2167,7 +2182,6 @@ async def write_assertions_with_http_info( ) 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 @@ -2175,7 +2189,6 @@ async def write_assertions_with_http_info( raise ApiValueError( "Missing the required parameter `authorization_model_id` when calling `write_assertions`" ) - # verify the required parameter 'body' is set if ( self.api_client.client_side_validation and local_var_params.get("body") is None @@ -2183,19 +2196,14 @@ 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() - auth_model_id = urllib.parse.quote( - str(local_var_params["authorization_model_id"]), safe="" - ) - return await self._execute( method="PUT", - path=f"/stores/{store_id}/assertions/{auth_model_id}", + path=f"/stores/{store_id}/assertions/{authorization_model_id}", method_name="write_assertions", response_types_map={}, body=local_var_params.get("body"), @@ -2287,7 +2295,6 @@ async def write_authorization_model_with_http_info(self, body, **kwargs): ) 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 @@ -2295,13 +2302,11 @@ 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() - return await self._execute( method="POST", path=f"/stores/{store_id}/authorization-models", From 0535b1cb18cfd410da3ba2d98694bd7264062d68 Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Wed, 18 Mar 2026 19:24:39 +0530 Subject: [PATCH 16/17] feat: address comments --- CHANGELOG.md | 4 + README.md | 13 +- .../execute_api_request_example.py | 33 +- openfga_sdk/api/open_fga_api.py | 602 ++++-------------- openfga_sdk/client/client.py | 4 +- .../client/execute_api_request_builder.py | 22 +- openfga_sdk/sync/client/client.py | 4 +- openfga_sdk/sync/open_fga_api.py | 602 ++++-------------- test/client/client_test.py | 29 +- test/sync/client/client_test.py | 29 +- 10 files changed, 320 insertions(+), 1022 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4704a4a..bdd4683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - feat: add `execute_api_request` and `execute_streamed_api_request` methods to `OpenFgaClient` and `OpenFgaApi` for making arbitrary HTTP requests to any OpenFGA API endpoint with full auth, retry, and telemetry support (#252) - thanks @kcbiradar +### Breaking Changes + +- The `_return_http_data_only`, `_request_auth`, and `_preload_content` kwargs have been removed from all `OpenFgaApi` and `SyncOpenFgaApi` endpoint methods. These were internal implementation details not intended for external use. `_return_http_data_only` is now hardcoded to `True` internally, meaning all endpoint methods always return the deserialized response object directly. Users relying on `_with_http_info` methods returning a `(data, status, headers)` tuple should use `execute_api_request` instead. + ### [0.9.9](https://github.com/openfga/python-sdk/compare/v0.9.8...v0.9.9) (2025-12-09) - feat: improve error messaging (#245) diff --git a/README.md b/README.md index 515135d..84b6fb0 100644 --- a/README.md +++ b/README.md @@ -1317,10 +1317,9 @@ print("Stores:", stores) #### Example: Using Path Parameters -Path parameters are specified in the path using `{param_name}` syntax and are replaced with URL-encoded values from the `path_params` dictionary. If `{store_id}` is present in the path and not provided in `path_params`, it will be automatically replaced with the configured store_id: +Path parameters are specified in the path using `{param_name}` syntax and must all be provided explicitly via `path_params` (URL-encoded automatically): ```python -# Using explicit path parameters response = await fga_client.execute_api_request( operation_name="GetAuthorizationModel", method="GET", @@ -1330,16 +1329,6 @@ response = await fga_client.execute_api_request( "model_id": "your-model-id", }, ) - -# Using automatic store_id substitution -response = await fga_client.execute_api_request( - operation_name="GetAuthorizationModel", - method="GET", - path="/stores/{store_id}/authorization-models/{model_id}", - path_params={ - "model_id": "your-model-id", - }, -) ``` ### Retries diff --git a/example/execute-api-request/execute_api_request_example.py b/example/execute-api-request/execute_api_request_example.py index aa874fe..bd650af 100644 --- a/example/execute-api-request/execute_api_request_example.py +++ b/example/execute-api-request/execute_api_request_example.py @@ -47,7 +47,6 @@ async def main(): ) async with OpenFgaClient(configuration) as fga_client: - # ─── Setup: create a store, model, and tuple ───────────── print("=== Setup ===") # Create a test store via the SDK @@ -116,10 +115,8 @@ async def main(): ) print("Wrote tuple: user:anne → writer → document:roadmap") - # ─── Tests ──────────────────────────────────────────────── - print("\n=== execute_api_request tests ===\n") + print("\n=== execute_api_request ===\n") - # ── 1. GET /stores ──────────────────────────────────────── print("1. ListStores (GET /stores)") raw = await fga_client.execute_api_request( operation_name="ListStores", @@ -136,12 +133,12 @@ async def main(): ) print(f" ✅ {len(body['stores'])} stores (status {raw.status})") - # ── 2. GET /stores/{store_id} (auto-substitution) ──────── print("2. GetStore (GET /stores/{store_id})") raw = await fga_client.execute_api_request( operation_name="GetStore", method="GET", path="/stores/{store_id}", + path_params={"store_id": store.id}, ) sdk = await fga_client.get_store() body = raw.json() @@ -150,7 +147,6 @@ async def main(): assert body["name"] == sdk.name print(f" ✅ id={body['id']}, name={body['name']}") - # ── 3. GET /stores/{store_id}/authorization-models ──────── print( "3. ReadAuthorizationModels (GET /stores/{store_id}/authorization-models)" ) @@ -158,6 +154,7 @@ async def main(): operation_name="ReadAuthorizationModels", method="GET", path="/stores/{store_id}/authorization-models", + path_params={"store_id": store.id}, ) sdk = await fga_client.read_authorization_models() body = raw.json() @@ -165,12 +162,12 @@ async def main(): assert len(body["authorization_models"]) == len(sdk.authorization_models) print(f" ✅ {len(body['authorization_models'])} models") - # ── 4. POST /stores/{store_id}/check ────────────────────── print("4. Check (POST /stores/{store_id}/check)") raw = await fga_client.execute_api_request( operation_name="Check", method="POST", path="/stores/{store_id}/check", + path_params={"store_id": store.id}, body={ "tuple_key": { "user": "user:anne", @@ -192,12 +189,12 @@ async def main(): assert body["allowed"] == sdk.allowed print(f" ✅ allowed={body['allowed']}") - # ── 5. POST /stores/{store_id}/read ─────────────────────── print("5. Read (POST /stores/{store_id}/read)") raw = await fga_client.execute_api_request( operation_name="Read", method="POST", path="/stores/{store_id}/read", + path_params={"store_id": store.id}, body={ "tuple_key": { "user": "user:anne", @@ -211,7 +208,6 @@ async def main(): assert len(body["tuples"]) >= 1 print(f" ✅ {len(body['tuples'])} tuples returned") - # ── 6. POST /stores — create store via raw request ──────── print("6. CreateStore (POST /stores)") raw = await fga_client.execute_api_request( operation_name="CreateStore", @@ -225,7 +221,6 @@ async def main(): new_store_id = body["id"] print(f" ✅ created store: {new_store_id}") - # ── 7. DELETE /stores/{store_id} — clean up ─────────────── print("7. DeleteStore (DELETE /stores/{store_id})") raw = await fga_client.execute_api_request( operation_name="DeleteStore", @@ -236,36 +231,22 @@ async def main(): assert raw.status == 204, f"Expected 204, got {raw.status}" print(f" ✅ deleted store: {new_store_id} (status 204 No Content)") - # ── 8. Custom headers ───────────────────────────────────── print("8. Custom headers (GET /stores/{store_id})") raw = await fga_client.execute_api_request( operation_name="GetStoreWithHeaders", method="GET", path="/stores/{store_id}", + path_params={"store_id": store.id}, headers={"X-Custom-Header": "test-value"}, ) assert raw.status == 200 print(f" ✅ custom headers accepted (status {raw.status})") - # ── 9. Explicit path_params override for store_id ───────── - print("9. Explicit store_id in path_params") - raw = await fga_client.execute_api_request( - operation_name="GetStore", - method="GET", - path="/stores/{store_id}", - path_params={"store_id": store.id}, - ) - body = raw.json() - assert raw.status == 200 - assert body["id"] == store.id - print(f" ✅ explicit store_id matched: {body['id']}") - - # ─── Cleanup ───────────────────────────────────────────── print("\n=== Cleanup ===") await fga_client.delete_store() print(f"Deleted test store: {store.id}") - print("\n All execute_api_request integration tests passed!\n") + print("\nAll execute_api_request examples completed successfully.\n") asyncio.run(main()) diff --git a/openfga_sdk/api/open_fga_api.py b/openfga_sdk/api/open_fga_api.py index 0fc3b3d..0993840 100644 --- a/openfga_sdk/api/open_fga_api.py +++ b/openfga_sdk/api/open_fga_api.py @@ -32,6 +32,24 @@ class OpenFgaApi: Do not edit the class manually. """ + _COMMON_PARAMS = [ + "async_req", + "_request_timeout", + "_headers", + "_retry_params", + "_streaming", + ] + + _COMMON_ERROR_RESPONSE_TYPES = { + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + } + def __init__(self, api_client=None): if api_client is None: api_client = ApiClient() @@ -60,34 +78,32 @@ async def _execute( self, method: str, path: str, - method_name: str, + operation_name: str, response_types_map: dict, body=None, query_params=None, - local_var_params: dict | None = None, + headers: dict | None = None, + options: dict | None = None, ) -> Any: """Shared executor for all API endpoint methods.""" - if local_var_params is None: - local_var_params = {} + if options is None: + options = {} - header_params = dict(local_var_params.get("_headers") or {}) + header_params = dict(headers or {}) header_params["Accept"] = self.api_client.select_header_accept( ["application/json"] ) if body is not None: - content_type = local_var_params.get( - "_content_type", - self.api_client.select_header_content_type( - ["application/json"], method, body - ), + content_type = self.api_client.select_header_content_type( + ["application/json"], method, body ) if content_type: header_params["Content-Type"] = content_type telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: method_name, + TelemetryAttributes.fga_client_request_method: operation_name, TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( + TelemetryAttributes.fga_client_request_model_id: options.get( "authorization_model_id", "" ), } @@ -95,6 +111,11 @@ async def _execute( body=body, attributes=telemetry_attributes ) + merged_response_types_map = { + **self._COMMON_ERROR_RESPONSE_TYPES, + **response_types_map, + } + return await self.api_client.call_api( path, method, @@ -104,18 +125,17 @@ async def _execute( body=body, post_params=[], files={}, - response_types_map=response_types_map, + response_types_map=merged_response_types_map, 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"), + async_req=options.get("async_req"), + _return_http_data_only=True, + _preload_content=True, + _request_timeout=options.get("_request_timeout"), + _retry_params=options.get("_retry_params"), 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), + _streaming=options.get("_streaming", False), ) async def execute_api_request( @@ -139,8 +159,8 @@ async def execute_api_request( :param operation_name: Operation name for telemetry (e.g., "CustomCheck") :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) :param path: API path, e.g. "/stores/{store_id}/my-endpoint". - {store_id} is auto-substituted from config if not in path_params. - :param path_params: Path parameter substitutions (URL-encoded automatically) + :param path_params: Path parameter substitutions (URL-encoded automatically). + All path parameters, including store_id, must be provided explicitly. :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) @@ -220,7 +240,7 @@ async def _execute_api_request_internal( ) builder.validate() - resource_path = builder.build_path(self.api_client.get_store_id()) + resource_path = builder.build_path() query_params_list = builder.build_query_params_list() options_headers = None @@ -293,7 +313,6 @@ async def batch_check(self, body, **kwargs): returns the request thread. :rtype: BatchCheckResponse """ - kwargs["_return_http_data_only"] = True return await self.batch_check_with_http_info(body, **kwargs) async def batch_check_with_http_info(self, body, **kwargs): @@ -333,19 +352,7 @@ async def batch_check_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -369,19 +376,11 @@ async def batch_check_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/batch-check", - method_name="batch_check", - response_types_map={ - 200: "BatchCheckResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="batch_check", + response_types_map={200: "BatchCheckResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def check(self, body, **kwargs): @@ -408,7 +407,6 @@ async def check(self, body, **kwargs): returns the request thread. :rtype: CheckResponse """ - kwargs["_return_http_data_only"] = True return await self.check_with_http_info(body, **kwargs) async def check_with_http_info(self, body, **kwargs): @@ -448,19 +446,7 @@ async def check_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -484,19 +470,11 @@ async def check_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/check", - method_name="check", - response_types_map={ - 200: "CheckResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="check", + response_types_map={200: "CheckResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def create_store(self, body, **kwargs): @@ -523,7 +501,6 @@ async def create_store(self, body, **kwargs): returns the request thread. :rtype: CreateStoreResponse """ - kwargs["_return_http_data_only"] = True return await self.create_store_with_http_info(body, **kwargs) async def create_store_with_http_info(self, body, **kwargs): @@ -563,19 +540,7 @@ async def create_store_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -587,19 +552,11 @@ async def create_store_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path="/stores", - method_name="create_store", - response_types_map={ - 201: "CreateStoreResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="create_store", + response_types_map={201: "CreateStoreResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def delete_store(self, **kwargs): @@ -624,7 +581,6 @@ async def delete_store(self, **kwargs): returns the request thread. :rtype: None """ - kwargs["_return_http_data_only"] = True return await self.delete_store_with_http_info(**kwargs) async def delete_store_with_http_info(self, **kwargs): @@ -662,19 +618,7 @@ async def delete_store_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -691,9 +635,10 @@ async def delete_store_with_http_info(self, **kwargs): return await self._execute( method="DELETE", path=f"/stores/{store_id}", - method_name="delete_store", + operation_name="delete_store", response_types_map={}, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def expand(self, body, **kwargs): @@ -720,7 +665,6 @@ async def expand(self, body, **kwargs): returns the request thread. :rtype: ExpandResponse """ - kwargs["_return_http_data_only"] = True return await self.expand_with_http_info(body, **kwargs) async def expand_with_http_info(self, body, **kwargs): @@ -760,19 +704,7 @@ async def expand_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -796,19 +728,11 @@ async def expand_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/expand", - method_name="expand", - response_types_map={ - 200: "ExpandResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="expand", + response_types_map={200: "ExpandResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def get_store(self, **kwargs): @@ -833,7 +757,6 @@ async def get_store(self, **kwargs): 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): @@ -871,19 +794,7 @@ async def get_store_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -900,18 +811,10 @@ async def get_store_with_http_info(self, **kwargs): return await self._execute( method="GET", path=f"/stores/{store_id}", - method_name="get_store", - response_types_map={ - 200: "GetStoreResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, - local_var_params=local_var_params, + operation_name="get_store", + response_types_map={200: "GetStoreResponse"}, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def list_objects(self, body, **kwargs): @@ -938,7 +841,6 @@ async def list_objects(self, body, **kwargs): 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): @@ -978,19 +880,7 @@ async def list_objects_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1014,19 +904,11 @@ async def list_objects_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/list-objects", - method_name="list_objects", - response_types_map={ - 200: "ListObjectsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="list_objects", + response_types_map={200: "ListObjectsResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def list_stores(self, **kwargs): @@ -1057,7 +939,6 @@ async def list_stores(self, **kwargs): 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): @@ -1101,19 +982,7 @@ async def list_stores_with_http_info(self, **kwargs): local_var_params = locals() all_params = ["page_size", "continuation_token", "name"] - all_params.extend( - [ - "async_req", - "_return_http_data_only", - "_preload_content", - "_request_timeout", - "_request_auth", - "_content_type", - "_headers", - "_retry_params", - "_streaming", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1134,19 +1003,11 @@ async def list_stores_with_http_info(self, **kwargs): return await self._execute( method="GET", path="/stores", - method_name="list_stores", - response_types_map={ - 200: "ListStoresResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="list_stores", + response_types_map={200: "ListStoresResponse"}, query_params=query_params, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def list_users(self, body, **kwargs): @@ -1173,7 +1034,6 @@ async def list_users(self, body, **kwargs): 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): @@ -1213,19 +1073,7 @@ async def list_users_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1249,19 +1097,11 @@ async def list_users_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/list-users", - method_name="list_users", - response_types_map={ - 200: "ListUsersResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="list_users", + response_types_map={200: "ListUsersResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def read(self, body, **kwargs): @@ -1288,7 +1128,6 @@ async def read(self, body, **kwargs): 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): @@ -1328,19 +1167,7 @@ async def read_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1364,19 +1191,11 @@ async def read_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/read", - method_name="read", - response_types_map={ - 200: "ReadResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="read", + response_types_map={200: "ReadResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def read_assertions(self, authorization_model_id, **kwargs): @@ -1403,7 +1222,6 @@ async def read_assertions(self, authorization_model_id, **kwargs): returns the request thread. :rtype: ReadAssertionsResponse """ - kwargs["_return_http_data_only"] = True return await self.read_assertions_with_http_info( authorization_model_id, **kwargs ) @@ -1445,19 +1263,7 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1481,18 +1287,10 @@ async def read_assertions_with_http_info(self, authorization_model_id, **kwargs) return await self._execute( method="GET", path=f"/stores/{store_id}/assertions/{authorization_model_id}", - method_name="read_assertions", - response_types_map={ - 200: "ReadAssertionsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, - local_var_params=local_var_params, + operation_name="read_assertions", + response_types_map={200: "ReadAssertionsResponse"}, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def read_authorization_model(self, id, **kwargs): @@ -1519,7 +1317,6 @@ async def read_authorization_model(self, id, **kwargs): 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): @@ -1559,19 +1356,7 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1595,18 +1380,10 @@ async def read_authorization_model_with_http_info(self, id, **kwargs): return await self._execute( method="GET", path=f"/stores/{store_id}/authorization-models/{id}", - method_name="read_authorization_model", - response_types_map={ - 200: "ReadAuthorizationModelResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, - local_var_params=local_var_params, + operation_name="read_authorization_model", + response_types_map={200: "ReadAuthorizationModelResponse"}, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def read_authorization_models(self, **kwargs): @@ -1635,7 +1412,6 @@ async def read_authorization_models(self, **kwargs): 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): @@ -1677,19 +1453,7 @@ async def read_authorization_models_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1713,19 +1477,11 @@ async def read_authorization_models_with_http_info(self, **kwargs): return await self._execute( method="GET", path=f"/stores/{store_id}/authorization-models", - method_name="read_authorization_models", - response_types_map={ - 200: "ReadAuthorizationModelsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="read_authorization_models", + response_types_map={200: "ReadAuthorizationModelsResponse"}, query_params=query_params, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def read_changes(self, **kwargs): @@ -1758,7 +1514,6 @@ async def read_changes(self, **kwargs): 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): @@ -1804,19 +1559,7 @@ async def read_changes_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1844,19 +1587,11 @@ async def read_changes_with_http_info(self, **kwargs): return await self._execute( method="GET", path=f"/stores/{store_id}/changes", - method_name="read_changes", - response_types_map={ - 200: "ReadChangesResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="read_changes", + response_types_map={200: "ReadChangesResponse"}, query_params=query_params, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def streamed_list_objects(self, body, **kwargs): @@ -1883,7 +1618,6 @@ async def streamed_list_objects(self, body, **kwargs): 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): @@ -1923,19 +1657,7 @@ async def streamed_list_objects_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1959,19 +1681,11 @@ async def streamed_list_objects_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/streamed-list-objects", - method_name="streamed_list_objects", - response_types_map={ - 200: "StreamResultOfStreamedListObjectsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="streamed_list_objects", + response_types_map={200: "StreamResultOfStreamedListObjectsResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def write(self, body, **kwargs): @@ -1998,7 +1712,6 @@ async def write(self, body, **kwargs): returns the request thread. :rtype: object """ - kwargs["_return_http_data_only"] = True return await self.write_with_http_info(body, **kwargs) async def write_with_http_info(self, body, **kwargs): @@ -2038,19 +1751,7 @@ async def write_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -2074,19 +1775,11 @@ async def write_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/write", - method_name="write", - response_types_map={ - 200: "object", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="write", + response_types_map={200: "object"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def write_assertions(self, authorization_model_id, body, **kwargs): @@ -2115,7 +1808,6 @@ async def write_assertions(self, authorization_model_id, body, **kwargs): returns the request thread. :rtype: None """ - kwargs["_return_http_data_only"] = True return await self.write_assertions_with_http_info( authorization_model_id, body, **kwargs ) @@ -2161,19 +1853,7 @@ async def write_assertions_with_http_info( local_var_params = locals() all_params = ["authorization_model_id", "body"] - all_params.extend( - [ - "async_req", - "_return_http_data_only", - "_preload_content", - "_request_timeout", - "_request_auth", - "_content_type", - "_headers", - "_retry_params", - "_streaming", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -2204,10 +1884,11 @@ async def write_assertions_with_http_info( return await self._execute( method="PUT", path=f"/stores/{store_id}/assertions/{authorization_model_id}", - method_name="write_assertions", + operation_name="write_assertions", response_types_map={}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) async def write_authorization_model(self, body, **kwargs): @@ -2234,7 +1915,6 @@ async def write_authorization_model(self, body, **kwargs): 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): @@ -2274,19 +1954,7 @@ async def write_authorization_model_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -2310,17 +1978,9 @@ async def write_authorization_model_with_http_info(self, body, **kwargs): return await self._execute( method="POST", path=f"/stores/{store_id}/authorization-models", - method_name="write_authorization_model", - response_types_map={ - 201: "WriteAuthorizationModelResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="write_authorization_model", + response_types_map={201: "WriteAuthorizationModelResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index e353bb3..01c69e8 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1124,8 +1124,8 @@ async def execute_api_request( :param operation_name: Operation name for telemetry (e.g., "CustomCheck") :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) :param path: API path, e.g. "/stores/{store_id}/my-endpoint". - {store_id} is auto-substituted from config if not in path_params. - :param path_params: Path parameter substitutions (URL-encoded automatically) + :param path_params: Path parameter substitutions (URL-encoded automatically). + All path parameters, including store_id, must be provided explicitly. :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) diff --git a/openfga_sdk/client/execute_api_request_builder.py b/openfga_sdk/client/execute_api_request_builder.py index 564ec75..f8ef3d1 100644 --- a/openfga_sdk/client/execute_api_request_builder.py +++ b/openfga_sdk/client/execute_api_request_builder.py @@ -43,26 +43,11 @@ def validate(self) -> None: if not self.path: raise FgaValidationException("path is required for execute_api_request") - def build_path(self, configured_store_id: str | None = None) -> str: - """ - Build resource path with parameter substitution. - - Auto-substitutes {store_id} from client config if not in path_params. - """ + def build_path(self) -> str: + """Build resource path with parameter substitution from path_params.""" path = self.path params = dict(self.path_params) if self.path_params else {} - # Auto-substitute store_id if needed - if "{store_id}" in path and "store_id" not in params: - if not configured_store_id: - raise FgaValidationException( - "Path contains {store_id} but store_id is not configured. " - "Set store_id in ClientConfiguration, use set_store_id(), " - "or provide it in path_params." - ) - params["store_id"] = configured_store_id - - # Replace all params result = path for key, value in params.items(): placeholder = f"{{{key}}}" @@ -75,7 +60,8 @@ def build_path(self, configured_store_id: str | None = None) -> str: match = re.search(r"\{([^}]+)\}", result) if match: raise FgaValidationException( - f"Not all path parameters were provided for path: {path}" + f"Path parameter '{match.group(1)}' not provided for path: {path}. " + "All path parameters must be supplied via path_params." ) return result diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 5c1fd54..47d6c68 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1122,8 +1122,8 @@ def execute_api_request( :param operation_name: Operation name for telemetry (e.g., "CustomCheck") :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) :param path: API path, e.g. "/stores/{store_id}/my-endpoint". - {store_id} is auto-substituted from config if not in path_params. - :param path_params: Path parameter substitutions (URL-encoded automatically) + :param path_params: Path parameter substitutions (URL-encoded automatically). + All path parameters, including store_id, must be provided explicitly. :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) diff --git a/openfga_sdk/sync/open_fga_api.py b/openfga_sdk/sync/open_fga_api.py index bdcecbe..b356578 100644 --- a/openfga_sdk/sync/open_fga_api.py +++ b/openfga_sdk/sync/open_fga_api.py @@ -34,6 +34,24 @@ class OpenFgaApi: Do not edit the class manually. """ + _COMMON_PARAMS = [ + "async_req", + "_request_timeout", + "_headers", + "_retry_params", + "_streaming", + ] + + _COMMON_ERROR_RESPONSE_TYPES = { + 400: "ValidationErrorMessageResponse", + 401: "UnauthenticatedResponse", + 403: "ForbiddenResponse", + 404: "PathUnknownErrorMessageResponse", + 409: "AbortedMessageResponse", + 422: "UnprocessableContentMessageResponse", + 500: "InternalErrorMessageResponse", + } + def __init__(self, api_client=None): if api_client is None: api_client = ApiClient() @@ -60,34 +78,32 @@ def _execute( self, method: str, path: str, - method_name: str, + operation_name: str, response_types_map: dict, body=None, query_params=None, - local_var_params: dict | None = None, + headers: dict | None = None, + options: dict | None = None, ) -> Any: """Shared executor for all API endpoint methods.""" - if local_var_params is None: - local_var_params = {} + if options is None: + options = {} - header_params = dict(local_var_params.get("_headers") or {}) + header_params = dict(headers or {}) header_params["Accept"] = self.api_client.select_header_accept( ["application/json"] ) if body is not None: - content_type = local_var_params.get( - "_content_type", - self.api_client.select_header_content_type( - ["application/json"], method, body - ), + content_type = self.api_client.select_header_content_type( + ["application/json"], method, body ) if content_type: header_params["Content-Type"] = content_type telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: method_name, + TelemetryAttributes.fga_client_request_method: operation_name, TelemetryAttributes.fga_client_request_store_id: self.api_client.get_store_id(), - TelemetryAttributes.fga_client_request_model_id: local_var_params.get( + TelemetryAttributes.fga_client_request_model_id: options.get( "authorization_model_id", "" ), } @@ -95,6 +111,11 @@ def _execute( body=body, attributes=telemetry_attributes ) + merged_response_types_map = { + **self._COMMON_ERROR_RESPONSE_TYPES, + **response_types_map, + } + return self.api_client.call_api( path, method, @@ -104,18 +125,17 @@ def _execute( body=body, post_params=[], files={}, - response_types_map=response_types_map, + response_types_map=merged_response_types_map, 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"), + async_req=options.get("async_req"), + _return_http_data_only=True, + _preload_content=True, + _request_timeout=options.get("_request_timeout"), + _retry_params=options.get("_retry_params"), 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), + _streaming=options.get("_streaming", False), ) def execute_api_request( @@ -139,8 +159,8 @@ def execute_api_request( :param operation_name: Operation name for telemetry (e.g., "CustomCheck") :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) :param path: API path, e.g. "/stores/{store_id}/my-endpoint". - {store_id} is auto-substituted from config if not in path_params. - :param path_params: Path parameter substitutions (URL-encoded automatically) + :param path_params: Path parameter substitutions (URL-encoded automatically). + All path parameters, including store_id, must be provided explicitly. :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) @@ -220,7 +240,7 @@ def _execute_api_request_internal( ) builder.validate() - resource_path = builder.build_path(self.api_client.get_store_id()) + resource_path = builder.build_path() query_params_list = builder.build_query_params_list() options_headers = None @@ -293,7 +313,6 @@ def batch_check(self, body, **kwargs): returns the request thread. :rtype: BatchCheckResponse """ - kwargs["_return_http_data_only"] = True return self.batch_check_with_http_info(body, **kwargs) def batch_check_with_http_info(self, body, **kwargs): @@ -333,19 +352,7 @@ def batch_check_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -372,19 +379,11 @@ def batch_check_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/batch-check", - method_name="batch_check", - response_types_map={ - 200: "BatchCheckResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="batch_check", + response_types_map={200: "BatchCheckResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def check(self, body, **kwargs): @@ -411,7 +410,6 @@ def check(self, body, **kwargs): returns the request thread. :rtype: CheckResponse """ - kwargs["_return_http_data_only"] = True return self.check_with_http_info(body, **kwargs) def check_with_http_info(self, body, **kwargs): @@ -451,19 +449,7 @@ def check_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -490,19 +476,11 @@ def check_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/check", - method_name="check", - response_types_map={ - 200: "CheckResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="check", + response_types_map={200: "CheckResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def create_store(self, body, **kwargs): @@ -529,7 +507,6 @@ def create_store(self, body, **kwargs): returns the request thread. :rtype: CreateStoreResponse """ - kwargs["_return_http_data_only"] = True return self.create_store_with_http_info(body, **kwargs) def create_store_with_http_info(self, body, **kwargs): @@ -569,19 +546,7 @@ def create_store_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -594,19 +559,11 @@ def create_store_with_http_info(self, body, **kwargs): return self._execute( method="POST", path="/stores", - method_name="create_store", - response_types_map={ - 201: "CreateStoreResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="create_store", + response_types_map={201: "CreateStoreResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def delete_store(self, **kwargs): @@ -631,7 +588,6 @@ def delete_store(self, **kwargs): returns the request thread. :rtype: None """ - kwargs["_return_http_data_only"] = True return self.delete_store_with_http_info(**kwargs) def delete_store_with_http_info(self, **kwargs): @@ -669,19 +625,7 @@ def delete_store_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -700,9 +644,10 @@ def delete_store_with_http_info(self, **kwargs): return self._execute( method="DELETE", path=f"/stores/{store_id}", - method_name="delete_store", + operation_name="delete_store", response_types_map={}, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def expand(self, body, **kwargs): @@ -729,7 +674,6 @@ def expand(self, body, **kwargs): returns the request thread. :rtype: ExpandResponse """ - kwargs["_return_http_data_only"] = True return self.expand_with_http_info(body, **kwargs) def expand_with_http_info(self, body, **kwargs): @@ -769,19 +713,7 @@ def expand_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -808,19 +740,11 @@ def expand_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/expand", - method_name="expand", - response_types_map={ - 200: "ExpandResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="expand", + response_types_map={200: "ExpandResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def get_store(self, **kwargs): @@ -845,7 +769,6 @@ def get_store(self, **kwargs): returns the request thread. :rtype: GetStoreResponse """ - kwargs["_return_http_data_only"] = True return self.get_store_with_http_info(**kwargs) def get_store_with_http_info(self, **kwargs): @@ -883,19 +806,7 @@ def get_store_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -914,18 +825,10 @@ def get_store_with_http_info(self, **kwargs): return self._execute( method="GET", path=f"/stores/{store_id}", - method_name="get_store", - response_types_map={ - 200: "GetStoreResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, - local_var_params=local_var_params, + operation_name="get_store", + response_types_map={200: "GetStoreResponse"}, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def list_objects(self, body, **kwargs): @@ -952,7 +855,6 @@ def list_objects(self, body, **kwargs): returns the request thread. :rtype: ListObjectsResponse """ - kwargs["_return_http_data_only"] = True return self.list_objects_with_http_info(body, **kwargs) def list_objects_with_http_info(self, body, **kwargs): @@ -992,19 +894,7 @@ def list_objects_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1031,19 +921,11 @@ def list_objects_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/list-objects", - method_name="list_objects", - response_types_map={ - 200: "ListObjectsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="list_objects", + response_types_map={200: "ListObjectsResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def list_stores(self, **kwargs): @@ -1074,7 +956,6 @@ def list_stores(self, **kwargs): returns the request thread. :rtype: ListStoresResponse """ - kwargs["_return_http_data_only"] = True return self.list_stores_with_http_info(**kwargs) def list_stores_with_http_info(self, **kwargs): @@ -1118,19 +999,7 @@ def list_stores_with_http_info(self, **kwargs): local_var_params = locals() all_params = ["page_size", "continuation_token", "name"] - all_params.extend( - [ - "async_req", - "_return_http_data_only", - "_preload_content", - "_request_timeout", - "_request_auth", - "_content_type", - "_headers", - "_retry_params", - "_streaming", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1153,19 +1022,11 @@ def list_stores_with_http_info(self, **kwargs): return self._execute( method="GET", path="/stores", - method_name="list_stores", - response_types_map={ - 200: "ListStoresResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="list_stores", + response_types_map={200: "ListStoresResponse"}, query_params=query_params, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def list_users(self, body, **kwargs): @@ -1192,7 +1053,6 @@ def list_users(self, body, **kwargs): returns the request thread. :rtype: ListUsersResponse """ - kwargs["_return_http_data_only"] = True return self.list_users_with_http_info(body, **kwargs) def list_users_with_http_info(self, body, **kwargs): @@ -1232,19 +1092,7 @@ def list_users_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1271,19 +1119,11 @@ def list_users_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/list-users", - method_name="list_users", - response_types_map={ - 200: "ListUsersResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="list_users", + response_types_map={200: "ListUsersResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def read(self, body, **kwargs): @@ -1310,7 +1150,6 @@ def read(self, body, **kwargs): returns the request thread. :rtype: ReadResponse """ - kwargs["_return_http_data_only"] = True return self.read_with_http_info(body, **kwargs) def read_with_http_info(self, body, **kwargs): @@ -1350,19 +1189,7 @@ def read_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1389,19 +1216,11 @@ def read_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/read", - method_name="read", - response_types_map={ - 200: "ReadResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="read", + response_types_map={200: "ReadResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def read_assertions(self, authorization_model_id, **kwargs): @@ -1428,7 +1247,6 @@ def read_assertions(self, authorization_model_id, **kwargs): returns the request thread. :rtype: ReadAssertionsResponse """ - kwargs["_return_http_data_only"] = True return self.read_assertions_with_http_info(authorization_model_id, **kwargs) def read_assertions_with_http_info(self, authorization_model_id, **kwargs): @@ -1468,19 +1286,7 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1510,18 +1316,10 @@ def read_assertions_with_http_info(self, authorization_model_id, **kwargs): return self._execute( method="GET", path=f"/stores/{store_id}/assertions/{auth_model_id}", - method_name="read_assertions", - response_types_map={ - 200: "ReadAssertionsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, - local_var_params=local_var_params, + operation_name="read_assertions", + response_types_map={200: "ReadAssertionsResponse"}, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def read_authorization_model(self, id, **kwargs): @@ -1548,7 +1346,6 @@ def read_authorization_model(self, id, **kwargs): returns the request thread. :rtype: ReadAuthorizationModelResponse """ - kwargs["_return_http_data_only"] = True return self.read_authorization_model_with_http_info(id, **kwargs) def read_authorization_model_with_http_info(self, id, **kwargs): @@ -1588,19 +1385,7 @@ def read_authorization_model_with_http_info(self, id, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1628,18 +1413,10 @@ def read_authorization_model_with_http_info(self, id, **kwargs): return self._execute( method="GET", path=f"/stores/{store_id}/authorization-models/{model_id}", - method_name="read_authorization_model", - response_types_map={ - 200: "ReadAuthorizationModelResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, - local_var_params=local_var_params, + operation_name="read_authorization_model", + response_types_map={200: "ReadAuthorizationModelResponse"}, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def read_authorization_models(self, **kwargs): @@ -1668,7 +1445,6 @@ def read_authorization_models(self, **kwargs): returns the request thread. :rtype: ReadAuthorizationModelsResponse """ - kwargs["_return_http_data_only"] = True return self.read_authorization_models_with_http_info(**kwargs) def read_authorization_models_with_http_info(self, **kwargs): @@ -1710,19 +1486,7 @@ def read_authorization_models_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1749,19 +1513,11 @@ def read_authorization_models_with_http_info(self, **kwargs): return self._execute( method="GET", path=f"/stores/{store_id}/authorization-models", - method_name="read_authorization_models", - response_types_map={ - 200: "ReadAuthorizationModelsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="read_authorization_models", + response_types_map={200: "ReadAuthorizationModelsResponse"}, query_params=query_params, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def read_changes(self, **kwargs): @@ -1794,7 +1550,6 @@ def read_changes(self, **kwargs): returns the request thread. :rtype: ReadChangesResponse """ - kwargs["_return_http_data_only"] = True return self.read_changes_with_http_info(**kwargs) def read_changes_with_http_info(self, **kwargs): @@ -1840,19 +1595,7 @@ def read_changes_with_http_info(self, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -1883,19 +1626,11 @@ def read_changes_with_http_info(self, **kwargs): return self._execute( method="GET", path=f"/stores/{store_id}/changes", - method_name="read_changes", - response_types_map={ - 200: "ReadChangesResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="read_changes", + response_types_map={200: "ReadChangesResponse"}, query_params=query_params, - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def streamed_list_objects(self, body, **kwargs): @@ -1922,7 +1657,6 @@ def streamed_list_objects(self, body, **kwargs): returns the request thread. :rtype: StreamResultOfStreamedListObjectsResponse """ - kwargs["_return_http_data_only"] = True return self.streamed_list_objects_with_http_info(body, **kwargs) def streamed_list_objects_with_http_info(self, body, **kwargs): @@ -1962,19 +1696,7 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -2001,19 +1723,11 @@ def streamed_list_objects_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/streamed-list-objects", - method_name="streamed_list_objects", - response_types_map={ - 200: "StreamResultOfStreamedListObjectsResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="streamed_list_objects", + response_types_map={200: "StreamResultOfStreamedListObjectsResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def write(self, body, **kwargs): @@ -2040,7 +1754,6 @@ def write(self, body, **kwargs): returns the request thread. :rtype: object """ - kwargs["_return_http_data_only"] = True return self.write_with_http_info(body, **kwargs) def write_with_http_info(self, body, **kwargs): @@ -2080,19 +1793,7 @@ def write_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -2119,19 +1820,11 @@ def write_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/write", - method_name="write", - response_types_map={ - 200: "object", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="write", + response_types_map={200: "object"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def write_assertions(self, authorization_model_id, body, **kwargs): @@ -2160,7 +1853,6 @@ def write_assertions(self, authorization_model_id, body, **kwargs): returns the request thread. :rtype: None """ - kwargs["_return_http_data_only"] = True return self.write_assertions_with_http_info( authorization_model_id, body, **kwargs ) @@ -2204,19 +1896,7 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs local_var_params = locals() all_params = ["authorization_model_id", "body"] - all_params.extend( - [ - "async_req", - "_return_http_data_only", - "_preload_content", - "_request_timeout", - "_request_auth", - "_content_type", - "_headers", - "_retry_params", - "_streaming", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -2254,10 +1934,11 @@ def write_assertions_with_http_info(self, authorization_model_id, body, **kwargs return self._execute( method="PUT", path=f"/stores/{store_id}/assertions/{auth_model_id}", - method_name="write_assertions", + operation_name="write_assertions", response_types_map={}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) def write_authorization_model(self, body, **kwargs): @@ -2284,7 +1965,6 @@ def write_authorization_model(self, body, **kwargs): returns the request thread. :rtype: WriteAuthorizationModelResponse """ - kwargs["_return_http_data_only"] = True return self.write_authorization_model_with_http_info(body, **kwargs) def write_authorization_model_with_http_info(self, body, **kwargs): @@ -2324,19 +2004,7 @@ def write_authorization_model_with_http_info(self, body, **kwargs): 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", - ] - ) + all_params.extend(self._COMMON_PARAMS) for key, val in local_var_params["kwargs"].items(): if key not in all_params: @@ -2363,17 +2031,9 @@ def write_authorization_model_with_http_info(self, body, **kwargs): return self._execute( method="POST", path=f"/stores/{store_id}/authorization-models", - method_name="write_authorization_model", - response_types_map={ - 201: "WriteAuthorizationModelResponse", - 400: "ValidationErrorMessageResponse", - 401: "UnauthenticatedResponse", - 403: "ForbiddenResponse", - 404: "PathUnknownErrorMessageResponse", - 409: "AbortedMessageResponse", - 422: "UnprocessableContentMessageResponse", - 500: "InternalErrorMessageResponse", - }, + operation_name="write_authorization_model", + response_types_map={201: "WriteAuthorizationModelResponse"}, body=local_var_params.get("body"), - local_var_params=local_var_params, + headers=local_var_params.get("_headers"), + options=local_var_params, ) diff --git a/test/client/client_test.py b/test/client/client_test.py index 18a56a1..75ed301 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -4189,6 +4189,7 @@ async def test_raw_request_post_with_body(self, mock_request): operation_name="CustomEndpoint", method="POST", path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": store_id}, body={"user": "user:bob", "action": "custom_action"}, query_params={"page_size": "20"}, headers={"X-Experimental-Feature": "enabled"}, @@ -4286,14 +4287,17 @@ async def test_raw_request_with_path_params(self, mock_request): operation_name="ReadAuthorizationModel", method="GET", path="/stores/{store_id}/authorization-models/{model_id}", - path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + path_params={ + "store_id": store_id, + "model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + }, ) self.assertIsInstance(response, RawResponse) self.assertEqual(response.status, 200) self.assertIsNotNone(response.body) - # Verify the API was called with correct path (store_id auto-substituted) + # Verify the API was called with correct path mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J", @@ -4308,10 +4312,10 @@ async def test_raw_request_with_path_params(self, mock_request): @patch.object(rest.RESTClientObject, "request") @pytest.mark.asyncio - async def test_raw_request_auto_store_id_substitution(self, mock_request): + async def test_raw_request_explicit_store_id_in_path_params(self, mock_request): """Test case for execute_api_request - Test automatic store_id substitution when not provided in path_params + Test that store_id must be provided explicitly in path_params """ response_body = '{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}' mock_request.return_value = mock_response(response_body, 200) @@ -4323,12 +4327,13 @@ async def test_raw_request_auto_store_id_substitution(self, mock_request): operation_name="GetStore", method="GET", path="/stores/{store_id}", + path_params={"store_id": store_id}, ) self.assertIsInstance(response, RawResponse) self.assertEqual(response.status, 200) - # Verify store_id was automatically substituted + # Verify store_id was substituted from explicit path_params mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X", @@ -4363,10 +4368,9 @@ async def test_raw_request_missing_operation_name(self): async def test_raw_request_missing_store_id(self): """Test case for execute_api_request - Test that store_id is required when path contains {store_id} + Test that store_id must be provided in path_params when path contains {store_id} """ configuration = self.configuration - # Don't set store_id async with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: await api_client.execute_api_request( @@ -4374,7 +4378,7 @@ async def test_raw_request_missing_store_id(self): method="GET", path="/stores/{store_id}", ) - self.assertIn("store_id is not configured", str(error.exception)) + self.assertIn("store_id", str(error.exception)) await api_client.close() @pytest.mark.asyncio @@ -4391,9 +4395,10 @@ async def test_raw_request_missing_path_params(self): operation_name="ReadAuthorizationModel", method="GET", path="/stores/{store_id}/authorization-models/{model_id}", + path_params={"store_id": store_id}, # Missing model_id in path_params ) - self.assertIn("Not all path parameters were provided", str(error.exception)) + self.assertIn("model_id", str(error.exception)) await api_client.close() @patch.object(rest.RESTClientObject, "request") @@ -4448,6 +4453,7 @@ async def test_raw_request_default_headers(self, mock_request): operation_name="CustomEndpoint", method="POST", path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": store_id}, body={"test": "data"}, ) @@ -4478,7 +4484,10 @@ async def test_raw_request_url_encoded_path_params(self, mock_request): operation_name="CustomEndpoint", method="GET", path="/stores/{store_id}/custom/{param}", - path_params={"param": "value with spaces & special chars"}, + path_params={ + "store_id": store_id, + "param": "value with spaces & special chars", + }, ) self.assertIsInstance(response, RawResponse) diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index bc6bb9c..79ea467 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -4171,6 +4171,7 @@ def test_raw_request_post_with_body(self, mock_request): operation_name="CustomEndpoint", method="POST", path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": store_id}, body={"user": "user:bob", "action": "custom_action"}, query_params={"page_size": "20"}, headers={"X-Experimental-Feature": "enabled"}, @@ -4266,14 +4267,17 @@ def test_raw_request_with_path_params(self, mock_request): operation_name="ReadAuthorizationModel", method="GET", path="/stores/{store_id}/authorization-models/{model_id}", - path_params={"model_id": "01G5JAVJ41T49E9TT3SKVS7X1J"}, + path_params={ + "store_id": store_id, + "model_id": "01G5JAVJ41T49E9TT3SKVS7X1J", + }, ) self.assertIsInstance(response, RawResponse) self.assertEqual(response.status, 200) self.assertIsNotNone(response.body) - # Verify the API was called with correct path (store_id auto-substituted) + # Verify the API was called with correct path mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J", @@ -4287,10 +4291,10 @@ def test_raw_request_with_path_params(self, mock_request): api_client.close() @patch.object(rest.RESTClientObject, "request") - def test_raw_request_auto_store_id_substitution(self, mock_request): + def test_raw_request_explicit_store_id_in_path_params(self, mock_request): """Test case for execute_api_request - Test automatic store_id substitution when not provided in path_params + Test that store_id must be provided explicitly in path_params """ response_body = '{"id": "01YCP46JKYM8FJCQ37NMBYHE5X", "name": "store1"}' mock_request.return_value = mock_response(response_body, 200) @@ -4302,12 +4306,13 @@ def test_raw_request_auto_store_id_substitution(self, mock_request): operation_name="GetStore", method="GET", path="/stores/{store_id}", + path_params={"store_id": store_id}, ) self.assertIsInstance(response, RawResponse) self.assertEqual(response.status, 200) - # Verify store_id was automatically substituted + # Verify store_id was substituted from explicit path_params mock_request.assert_called_once_with( "GET", "http://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X", @@ -4340,10 +4345,9 @@ def test_raw_request_missing_operation_name(self): def test_raw_request_missing_store_id(self): """Test case for execute_api_request - Test that store_id is required when path contains {store_id} + Test that store_id must be provided in path_params when path contains {store_id} """ configuration = self.configuration - # Don't set store_id with OpenFgaClient(configuration) as api_client: with self.assertRaises(FgaValidationException) as error: api_client.execute_api_request( @@ -4351,7 +4355,7 @@ def test_raw_request_missing_store_id(self): method="GET", path="/stores/{store_id}", ) - self.assertIn("store_id is not configured", str(error.exception)) + self.assertIn("store_id", str(error.exception)) api_client.close() def test_raw_request_missing_path_params(self): @@ -4367,9 +4371,10 @@ def test_raw_request_missing_path_params(self): operation_name="ReadAuthorizationModel", method="GET", path="/stores/{store_id}/authorization-models/{model_id}", + path_params={"store_id": store_id}, # Missing model_id in path_params ) - self.assertIn("Not all path parameters were provided", str(error.exception)) + self.assertIn("model_id", str(error.exception)) api_client.close() @patch.object(rest.RESTClientObject, "request") @@ -4421,6 +4426,7 @@ def test_raw_request_default_headers(self, mock_request): operation_name="CustomEndpoint", method="POST", path="/stores/{store_id}/custom-endpoint", + path_params={"store_id": store_id}, body={"test": "data"}, ) @@ -4449,7 +4455,10 @@ def test_raw_request_url_encoded_path_params(self, mock_request): operation_name="CustomEndpoint", method="GET", path="/stores/{store_id}/custom/{param}", - path_params={"param": "value with spaces & special chars"}, + path_params={ + "store_id": store_id, + "param": "value with spaces & special chars", + }, ) self.assertIsInstance(response, RawResponse) From 785af00b8da20c3ca3895f8bb9ade4f7de6e8d18 Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Wed, 18 Mar 2026 21:57:02 +0530 Subject: [PATCH 17/17] fix: streaming stuff --- CHANGELOG.md | 2 +- README.md | 23 +++- .../execute_api_request_example.py | 21 ++++ openfga_sdk/__init__.py | 2 + openfga_sdk/api/open_fga_api.py | 85 ++++++++++---- openfga_sdk/client/client.py | 16 ++- openfga_sdk/sync/client/client.py | 13 ++- openfga_sdk/sync/open_fga_api.py | 84 ++++++++++---- test/client/client_test.py | 100 +++++++++++++++++ test/sync/client/client_test.py | 105 ++++++++++++++++++ 10 files changed, 394 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd4683..602722d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -- The `_return_http_data_only`, `_request_auth`, and `_preload_content` kwargs have been removed from all `OpenFgaApi` and `SyncOpenFgaApi` endpoint methods. These were internal implementation details not intended for external use. `_return_http_data_only` is now hardcoded to `True` internally, meaning all endpoint methods always return the deserialized response object directly. Users relying on `_with_http_info` methods returning a `(data, status, headers)` tuple should use `execute_api_request` instead. +- The `_return_http_data_only`, `_preload_content`, `_request_auth`, `async_req`, and `_request_timeout` kwargs have been removed from all `OpenFgaApi` and `SyncOpenFgaApi` endpoint methods. These were internal implementation details not intended for external use. `_return_http_data_only` is now hardcoded to `True`; all endpoint methods return the deserialized response object directly. Users relying on `_with_http_info` methods returning a `(data, status, headers)` tuple should use `execute_api_request` instead. ### [0.9.9](https://github.com/openfga/python-sdk/compare/v0.9.8...v0.9.9) (2025-12-09) - feat: improve error messaging (#245) diff --git a/README.md b/README.md index 84b6fb0..62c6158 100644 --- a/README.md +++ b/README.md @@ -1265,7 +1265,7 @@ response = await fga_client.write_assertions(body, options) In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `execute_api_request` method available on the `OpenFgaClient`. It allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the HTTP method, path, body, query parameters, and path parameters, while still honoring the client configuration (authentication, telemetry, retries, and error handling). -For streaming endpoints, use `execute_streamed_api_request` instead. +For streaming endpoints (e.g. `streamed-list-objects`), use `execute_streamed_api_request` instead. It returns an `AsyncIterator` (or `Iterator` in the sync client) that yields one parsed JSON object per chunk. This is useful when: - You want to call a new endpoint that is not yet supported by the SDK @@ -1315,6 +1315,27 @@ stores = stores_response.json() print("Stores:", stores) ``` +#### Example: Calling a Streaming Endpoint + +```python +# Stream objects visible to a user +async for chunk in fga_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": FGA_STORE_ID}, + body={ + "type": "document", + "relation": "viewer", + "user": "user:anne", + "authorization_model_id": FGA_MODEL_ID, + }, +): + # Each chunk has the shape {"result": {"object": "..."}} or {"error": {...}} + if "result" in chunk: + print(chunk["result"]["object"]) # e.g. "document:roadmap" +``` + #### Example: Using Path Parameters Path parameters are specified in the path using `{param_name}` syntax and must all be provided explicitly via `path_params` (URL-encoded automatically): diff --git a/example/execute-api-request/execute_api_request_example.py b/example/execute-api-request/execute_api_request_example.py index bd650af..632c50d 100644 --- a/example/execute-api-request/execute_api_request_example.py +++ b/example/execute-api-request/execute_api_request_example.py @@ -242,6 +242,27 @@ async def main(): assert raw.status == 200 print(f" ✅ custom headers accepted (status {raw.status})") + print("9. StreamedListObjects (POST /stores/{store_id}/streamed-list-objects)") + chunks = [] + async for chunk in fga_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": store.id}, + body={ + "type": "document", + "relation": "viewer", + "user": "user:anne", + "authorization_model_id": auth_model_id, + }, + ): + chunks.append(chunk) + assert len(chunks) >= 1, f"Expected at least 1 chunk, got {len(chunks)}" + # Each chunk has the shape {"result": {"object": "..."}} or {"error": {...}} + objects = [c["result"]["object"] for c in chunks if "result" in c] + assert "document:roadmap" in objects, f"Expected document:roadmap in {objects}" + print(f" ✅ {len(chunks)} chunks, objects={objects}") + print("\n=== Cleanup ===") await fga_client.delete_store() print(f"Deleted test store: {store.id}") diff --git a/openfga_sdk/__init__.py b/openfga_sdk/__init__.py index b4e6080..2c99928 100644 --- a/openfga_sdk/__init__.py +++ b/openfga_sdk/__init__.py @@ -2,6 +2,7 @@ from openfga_sdk.api_client import ApiClient from openfga_sdk.client.client import OpenFgaClient from openfga_sdk.client.configuration import ClientConfiguration +from openfga_sdk.client.models.raw_response import RawResponse from openfga_sdk.configuration import Configuration from openfga_sdk.constants import SDK_VERSION from openfga_sdk.exceptions import ( @@ -136,6 +137,7 @@ __all__ = [ "OpenFgaClient", "ClientConfiguration", + "RawResponse", "OpenFgaApi", "ApiClient", "Configuration", diff --git a/openfga_sdk/api/open_fga_api.py b/openfga_sdk/api/open_fga_api.py index 0993840..29924e8 100644 --- a/openfga_sdk/api/open_fga_api.py +++ b/openfga_sdk/api/open_fga_api.py @@ -12,6 +12,7 @@ from __future__ import annotations +from collections.abc import AsyncIterator from typing import TYPE_CHECKING, Any from openfga_sdk.api_client import ApiClient @@ -148,7 +149,7 @@ async def execute_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: dict[str, Any] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. @@ -164,7 +165,7 @@ async def execute_api_request( :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) - :param options: Extra options (headers, retry_params) + :param options: Extra options e.g. {"retry_params": RetryParams(max_retry=3)} :return: RawResponse with status, headers, and body """ return await self._execute_api_request_internal( @@ -176,7 +177,6 @@ async def execute_api_request( query_params=query_params, headers=headers, options=options, - streaming=False, ) async def execute_streamed_api_request( @@ -189,15 +189,31 @@ async def execute_streamed_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> RawResponse: + options: dict[str, Any] | None = None, + ) -> AsyncIterator[dict[str, Any]]: """ Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. - Same interface as execute_api_request but for streaming endpoints. - See execute_api_request for full parameter documentation. + Yields parsed JSON objects as they arrive. Use with async for: + + async for chunk in api.execute_streamed_api_request(...): + process(chunk) + + :param operation_name: Operation name for telemetry (e.g., "StreamedListObjects") + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: API path, e.g. "/stores/{store_id}/streamed-list-objects". + :param path_params: Path parameter substitutions (URL-encoded automatically). + All path parameters, including store_id, must be provided explicitly. + :param body: Request body for POST/PUT/PATCH + :param query_params: Query string parameters + :param headers: Custom headers (SDK enforces Content-Type and Accept) + :param options: Extra options e.g. {"retry_params": RetryParams(max_retry=3)} """ - return await self._execute_api_request_internal( + from openfga_sdk.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ) + + builder = ExecuteApiRequestBuilder( operation_name=operation_name, method=method, path=path, @@ -205,9 +221,41 @@ async def execute_streamed_api_request( body=body, query_params=query_params, headers=headers, - options=options, - streaming=True, ) + builder.validate() + + resource_path = builder.build_path() + query_params_list = builder.build_query_params_list() + final_headers = builder.build_headers() + + retry_params = options.get("retry_params") if options else None + + telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.api_client.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( + self.api_client.get_store_id() + ) + + stream = await self.api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=final_headers, + body=body, + response_types_map={}, + auth_settings=[], + _return_http_data_only=True, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._oauth2_client, + _telemetry_attributes=telemetry_attributes, + _streaming=True, + ) + + async for chunk in stream: + yield chunk async def _execute_api_request_internal( self, @@ -219,10 +267,9 @@ async def _execute_api_request_internal( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - streaming: bool = False, + options: dict[str, Any] | None = None, ) -> RawResponse: - """Shared implementation for execute_api_request and execute_streamed_api_request.""" + """Implementation for execute_api_request.""" from openfga_sdk.client.execute_api_request_builder import ( ExecuteApiRequestBuilder, ResponseParser, @@ -242,15 +289,9 @@ async def _execute_api_request_internal( resource_path = builder.build_path() query_params_list = builder.build_query_params_list() + final_headers = builder.build_headers() - options_headers = None - if options and isinstance(options.get("headers"), dict): - options_headers = options["headers"] - final_headers = builder.build_headers(options_headers) - - retry_params = None - if options and options.get("retry_params"): - retry_params = options["retry_params"] + retry_params = options.get("retry_params") if options else None telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { TelemetryAttributes.fga_client_request_method: operation_name.lower(), @@ -273,7 +314,7 @@ async def _execute_api_request_internal( _retry_params=retry_params, _oauth2_client=self._oauth2_client, _telemetry_attributes=telemetry_attributes, - _streaming=streaming, + _streaming=False, ) rest_response = getattr(self.api_client, "last_response", None) diff --git a/openfga_sdk/client/client.py b/openfga_sdk/client/client.py index 01c69e8..b48a3ec 100644 --- a/openfga_sdk/client/client.py +++ b/openfga_sdk/client/client.py @@ -1,6 +1,7 @@ import asyncio import uuid +from collections.abc import AsyncIterator from typing import Any from openfga_sdk.api.open_fga_api import OpenFgaApi @@ -1113,7 +1114,7 @@ async def execute_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: dict[str, Any] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. @@ -1129,7 +1130,7 @@ async def execute_api_request( :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) - :param options: Extra options (headers, retry_params) + :param options: Extra options e.g. {"retry_params": RetryParams(max_retry=3)} :return: RawResponse with status, headers, and body """ return await self._api.execute_api_request( @@ -1153,15 +1154,17 @@ async def execute_streamed_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> RawResponse: + options: dict[str, Any] | None = None, + ) -> AsyncIterator[dict[str, Any]]: """ Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. Same interface as execute_api_request but for streaming endpoints. See execute_api_request for full parameter documentation. + + :return: AsyncIterator yielding parsed JSON chunks from the streaming response. """ - return await self._api.execute_streamed_api_request( + async for chunk in self._api.execute_streamed_api_request( operation_name=operation_name, method=method, path=path, @@ -1170,4 +1173,5 @@ async def execute_streamed_api_request( query_params=query_params, headers=headers, options=options, - ) + ): + yield chunk diff --git a/openfga_sdk/sync/client/client.py b/openfga_sdk/sync/client/client.py index 47d6c68..c736dae 100644 --- a/openfga_sdk/sync/client/client.py +++ b/openfga_sdk/sync/client/client.py @@ -1,5 +1,6 @@ import uuid +from collections.abc import Iterator from concurrent.futures import ThreadPoolExecutor from typing import Any @@ -1111,7 +1112,7 @@ def execute_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: dict[str, Any] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. @@ -1127,7 +1128,7 @@ def execute_api_request( :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) - :param options: Extra options (headers, retry_params) + :param options: Extra options e.g. {"retry_params": RetryParams(max_retry=3)} :return: RawResponse with status, headers, and body """ return self._api.execute_api_request( @@ -1151,15 +1152,17 @@ def execute_streamed_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> RawResponse: + options: dict[str, Any] | None = None, + ) -> Iterator[dict[str, Any]]: """ Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. Same interface as execute_api_request but for streaming endpoints. See execute_api_request for full parameter documentation. + + :return: Iterator yielding parsed JSON chunks from the streaming response. """ - return self._api.execute_streamed_api_request( + yield from self._api.execute_streamed_api_request( operation_name=operation_name, method=method, path=path, diff --git a/openfga_sdk/sync/open_fga_api.py b/openfga_sdk/sync/open_fga_api.py index b356578..4244192 100644 --- a/openfga_sdk/sync/open_fga_api.py +++ b/openfga_sdk/sync/open_fga_api.py @@ -14,6 +14,7 @@ import urllib.parse +from collections.abc import Iterator from typing import TYPE_CHECKING, Any from openfga_sdk.exceptions import ApiValueError, FgaValidationException @@ -148,7 +149,7 @@ def execute_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, + options: dict[str, Any] | None = None, ) -> RawResponse: """ Execute an arbitrary HTTP request to any OpenFGA API endpoint. @@ -164,7 +165,7 @@ def execute_api_request( :param body: Request body for POST/PUT/PATCH :param query_params: Query string parameters :param headers: Custom headers (SDK enforces Content-Type and Accept) - :param options: Extra options (headers, retry_params) + :param options: Extra options e.g. {"retry_params": RetryParams(max_retry=3)} :return: RawResponse with status, headers, and body """ return self._execute_api_request_internal( @@ -176,7 +177,6 @@ def execute_api_request( query_params=query_params, headers=headers, options=options, - streaming=False, ) def execute_streamed_api_request( @@ -189,15 +189,31 @@ def execute_streamed_api_request( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - ) -> RawResponse: + options: dict[str, Any] | None = None, + ) -> Iterator[dict[str, Any]]: """ Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint. - Same interface as execute_api_request but for streaming endpoints. - See execute_api_request for full parameter documentation. + Yields parsed JSON objects as they arrive. Use with for: + + for chunk in api.execute_streamed_api_request(...): + process(chunk) + + :param operation_name: Operation name for telemetry (e.g., "StreamedListObjects") + :param method: HTTP method (GET, POST, PUT, DELETE, PATCH) + :param path: API path, e.g. "/stores/{store_id}/streamed-list-objects". + :param path_params: Path parameter substitutions (URL-encoded automatically). + All path parameters, including store_id, must be provided explicitly. + :param body: Request body for POST/PUT/PATCH + :param query_params: Query string parameters + :param headers: Custom headers (SDK enforces Content-Type and Accept) + :param options: Extra options e.g. {"retry_params": RetryParams(max_retry=3)} """ - return self._execute_api_request_internal( + from openfga_sdk.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ) + + builder = ExecuteApiRequestBuilder( operation_name=operation_name, method=method, path=path, @@ -205,9 +221,40 @@ def execute_streamed_api_request( body=body, query_params=query_params, headers=headers, - options=options, - streaming=True, ) + builder.validate() + + resource_path = builder.build_path() + query_params_list = builder.build_query_params_list() + final_headers = builder.build_headers() + + retry_params = options.get("retry_params") if options else None + + telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { + TelemetryAttributes.fga_client_request_method: operation_name.lower(), + } + if self.api_client.get_store_id(): + telemetry_attributes[TelemetryAttributes.fga_client_request_store_id] = ( + self.api_client.get_store_id() + ) + + stream = self.api_client.call_api( + resource_path=resource_path, + method=method.upper(), + query_params=query_params_list if query_params_list else None, + header_params=final_headers, + body=body, + response_types_map={}, + auth_settings=[], + _return_http_data_only=True, + _preload_content=True, + _retry_params=retry_params, + _oauth2_client=self._oauth2_client, + _telemetry_attributes=telemetry_attributes, + _streaming=True, + ) + + yield from stream def _execute_api_request_internal( self, @@ -219,10 +266,9 @@ def _execute_api_request_internal( body: dict[str, Any] | list[Any] | str | bytes | None = None, query_params: dict[str, str | int | list[str | int]] | None = None, headers: dict[str, str] | None = None, - options: dict[str, int | str | dict[str, int | str]] | None = None, - streaming: bool = False, + options: dict[str, Any] | None = None, ) -> RawResponse: - """Shared implementation for execute_api_request and execute_streamed_api_request.""" + """Implementation for execute_api_request.""" from openfga_sdk.client.execute_api_request_builder import ( ExecuteApiRequestBuilder, ResponseParser, @@ -242,15 +288,9 @@ def _execute_api_request_internal( resource_path = builder.build_path() query_params_list = builder.build_query_params_list() + final_headers = builder.build_headers() - options_headers = None - if options and isinstance(options.get("headers"), dict): - options_headers = options["headers"] - final_headers = builder.build_headers(options_headers) - - retry_params = None - if options and options.get("retry_params"): - retry_params = options["retry_params"] + retry_params = options.get("retry_params") if options else None telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { TelemetryAttributes.fga_client_request_method: operation_name.lower(), @@ -273,7 +313,7 @@ def _execute_api_request_internal( _retry_params=retry_params, _oauth2_client=self._oauth2_client, _telemetry_attributes=telemetry_attributes, - _streaming=streaming, + _streaming=False, ) rest_response = getattr(self.api_client, "last_response", None) diff --git a/test/client/client_test.py b/test/client/client_test.py index 75ed301..5174423 100644 --- a/test/client/client_test.py +++ b/test/client/client_test.py @@ -4503,3 +4503,103 @@ async def test_raw_request_url_encoded_path_params(self, mock_request): _request_timeout=None, ) await api_client.close() + + @patch.object(rest.RESTClientObject, "stream") + @pytest.mark.asyncio + async def test_execute_streamed_api_request_basic(self, mock_stream): + """Test execute_streamed_api_request yields all chunks from the stream.""" + + async def mock_gen(): + yield {"result": {"object": "document:roadmap"}} + yield {"result": {"object": "document:budget"}} + + mock_stream.return_value = mock_gen() + + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + chunks = [] + async for chunk in api_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": store_id}, + body={ + "type": "document", + "relation": "viewer", + "user": "user:anne", + }, + ): + chunks.append(chunk) + + self.assertEqual(len(chunks), 2) + self.assertEqual(chunks[0], {"result": {"object": "document:roadmap"}}) + self.assertEqual(chunks[1], {"result": {"object": "document:budget"}}) + + mock_stream.assert_called_once_with( + "POST", + f"http://api.fga.example/stores/{store_id}/streamed-list-objects", + query_params=None, + headers=ANY, + post_params=None, + body={"type": "document", "relation": "viewer", "user": "user:anne"}, + _request_timeout=None, + ) + await api_client.close() + + @patch.object(rest.RESTClientObject, "stream") + @pytest.mark.asyncio + async def test_execute_streamed_api_request_empty_stream(self, mock_stream): + """Test execute_streamed_api_request handles an empty stream gracefully.""" + + async def mock_gen(): + return + yield # make it an async generator + + mock_stream.return_value = mock_gen() + + configuration = self.configuration + configuration.store_id = store_id + async with OpenFgaClient(configuration) as api_client: + chunks = [] + async for chunk in api_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": store_id}, + body={"type": "document", "relation": "viewer", "user": "user:nobody"}, + ): + chunks.append(chunk) + + self.assertEqual(len(chunks), 0) + await api_client.close() + + @patch.object(rest.RESTClientObject, "stream") + @pytest.mark.asyncio + async def test_execute_streamed_api_request_path_substitution(self, mock_stream): + """Test execute_streamed_api_request substitutes path params correctly.""" + + async def mock_gen(): + yield {"result": {"object": "document:roadmap"}} + + mock_stream.return_value = mock_gen() + + configuration = self.configuration + async with OpenFgaClient(configuration) as api_client: + chunks = [] + async for chunk in api_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": store_id}, + body={"type": "document", "relation": "viewer", "user": "user:anne"}, + ): + chunks.append(chunk) + + self.assertEqual(len(chunks), 1) + + # Verify store_id was substituted in the URL + call_args = mock_stream.call_args + self.assertIn(store_id, call_args[0][1]) + self.assertNotIn("{store_id}", call_args[0][1]) + await api_client.close() diff --git a/test/sync/client/client_test.py b/test/sync/client/client_test.py index 79ea467..f71ac96 100644 --- a/test/sync/client/client_test.py +++ b/test/sync/client/client_test.py @@ -4474,3 +4474,108 @@ def test_raw_request_url_encoded_path_params(self, mock_request): _request_timeout=None, ) api_client.close() + + @patch.object(rest.RESTClientObject, "stream") + def test_execute_streamed_api_request_basic(self, mock_stream): + """Test execute_streamed_api_request yields all chunks from the stream.""" + + def mock_gen(): + yield {"result": {"object": "document:roadmap"}} + yield {"result": {"object": "document:budget"}} + + mock_stream.return_value = mock_gen() + + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + chunks = list( + api_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": store_id}, + body={ + "type": "document", + "relation": "viewer", + "user": "user:anne", + }, + ) + ) + + self.assertEqual(len(chunks), 2) + self.assertEqual(chunks[0], {"result": {"object": "document:roadmap"}}) + self.assertEqual(chunks[1], {"result": {"object": "document:budget"}}) + + mock_stream.assert_called_once_with( + "POST", + f"http://api.fga.example/stores/{store_id}/streamed-list-objects", + query_params=None, + headers=ANY, + post_params=None, + body={"type": "document", "relation": "viewer", "user": "user:anne"}, + _request_timeout=None, + ) + api_client.close() + + @patch.object(rest.RESTClientObject, "stream") + def test_execute_streamed_api_request_empty_stream(self, mock_stream): + """Test execute_streamed_api_request handles an empty stream gracefully.""" + + def mock_gen(): + return + yield # make it a generator + + mock_stream.return_value = mock_gen() + + configuration = self.configuration + configuration.store_id = store_id + with OpenFgaClient(configuration) as api_client: + chunks = list( + api_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": store_id}, + body={ + "type": "document", + "relation": "viewer", + "user": "user:nobody", + }, + ) + ) + + self.assertEqual(len(chunks), 0) + api_client.close() + + @patch.object(rest.RESTClientObject, "stream") + def test_execute_streamed_api_request_path_substitution(self, mock_stream): + """Test execute_streamed_api_request substitutes path params correctly.""" + + def mock_gen(): + yield {"result": {"object": "document:roadmap"}} + + mock_stream.return_value = mock_gen() + + configuration = self.configuration + with OpenFgaClient(configuration) as api_client: + chunks = list( + api_client.execute_streamed_api_request( + operation_name="StreamedListObjects", + method="POST", + path="/stores/{store_id}/streamed-list-objects", + path_params={"store_id": store_id}, + body={ + "type": "document", + "relation": "viewer", + "user": "user:anne", + }, + ) + ) + + self.assertEqual(len(chunks), 1) + + # Verify store_id was substituted in the URL + call_args = mock_stream.call_args + self.assertIn(store_id, call_args[0][1]) + self.assertNotIn("{store_id}", call_args[0][1]) + api_client.close()