Skip to content

Commit 256d6e1

Browse files
committed
feat: refactor method to execute api method
1 parent 07e354d commit 256d6e1

File tree

5 files changed

+320
-223
lines changed

5 files changed

+320
-223
lines changed

README.md

Lines changed: 50 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,14 +1263,16 @@ response = await fga_client.write_assertions(body, options)
12631263

12641264
### Calling Other Endpoints
12651265

1266-
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).
1266+
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).
1267+
1268+
For streaming endpoints, use `execute_streamed_api_request` instead.
12671269

12681270
This is useful when:
12691271
- You want to call a new endpoint that is not yet supported by the SDK
12701272
- You are using an earlier version of the SDK that doesn't yet support a particular endpoint
12711273
- You have a custom endpoint deployed that extends the OpenFGA API
12721274

1273-
In all cases, you initialize the SDK the same way as usual, and then call `raw_request` on the `fga_client` instance.
1275+
In all cases, you initialize the SDK the same way as usual, and then call `execute_api_request` on the `fga_client` instance.
12741276

12751277
```python
12761278
from openfga_sdk import ClientConfiguration, OpenFgaClient
@@ -1287,82 +1289,57 @@ async with OpenFgaClient(configuration) as fga_client:
12871289
"resource": "resource:123",
12881290
}
12891291

1290-
response = await fga_client.raw_request(
1291-
operation_name="CustomEndpoint",
1292-
method="POST",
1293-
path="/stores/{store_id}/custom-endpoint",
1294-
path_params={"store_id": FGA_STORE_ID},
1295-
query_params={"page_size": "20"},
1296-
body=request_body,
1297-
headers={"X-Experimental-Feature": "enabled"},
1298-
)
1292+
response = await fga_client.execute_api_request({
1293+
"operation_name": "CustomEndpoint",
1294+
"method": "POST",
1295+
"path": "/stores/{store_id}/custom-endpoint",
1296+
"path_params": {"store_id": FGA_STORE_ID},
1297+
"query_params": {"page_size": "20"},
1298+
"body": request_body,
1299+
"headers": {"X-Experimental-Feature": "enabled"},
1300+
})
12991301
```
13001302

1301-
#### Example: Calling a new "Custom Endpoint" endpoint and handling raw response
1303+
#### Example: Calling a custom endpoint with POST
13021304

13031305
```python
1304-
# Get raw response without automatic decoding
1305-
raw_response = await fga_client.raw_request(
1306-
operation_name="CustomEndpoint",
1307-
method="POST",
1308-
path="/stores/{store_id}/custom-endpoint",
1309-
path_params={"store_id": FGA_STORE_ID},
1310-
body={"user": "user:bob", "action": "custom_action"},
1311-
)
1306+
# Call a custom endpoint using path parameters
1307+
response = await fga_client.execute_api_request({
1308+
"operation_name": "CustomEndpoint", # For telemetry/logging
1309+
"method": "POST",
1310+
"path": "/stores/{store_id}/custom-endpoint",
1311+
"path_params": {"store_id": FGA_STORE_ID},
1312+
"body": {
1313+
"user": "user:bob",
1314+
"action": "custom_action",
1315+
"resource": "resource:123",
1316+
},
1317+
"query_params": {
1318+
"page_size": 20,
1319+
},
1320+
})
13121321

13131322
# Access the response data
1314-
if raw_response.status == 200:
1315-
# Manually decode the response
1316-
result = raw_response.json()
1317-
if result:
1318-
print(f"Response: {result}")
1319-
1320-
# You can access fields like headers, status code, etc. from raw_response:
1321-
print(f"Status Code: {raw_response.status}")
1322-
print(f"Headers: {raw_response.headers}")
1323-
print(f"Body as text: {raw_response.text()}")
1324-
```
1325-
1326-
#### Example: Calling a new "Custom Endpoint" endpoint and decoding response into a dictionary
1327-
1328-
```python
1329-
# Get raw response decoded into a dictionary
1330-
response = await fga_client.raw_request(
1331-
operation_name="CustomEndpoint",
1332-
method="POST",
1333-
path="/stores/{store_id}/custom-endpoint",
1334-
path_params={"store_id": FGA_STORE_ID},
1335-
body={"user": "user:bob", "action": "custom_action"},
1336-
)
1337-
1338-
# The response body is automatically parsed as JSON if possible
1339-
result = response.json() # Returns dict or None if not JSON
1340-
1341-
if result:
1323+
if response.status == 200:
1324+
result = response.json()
13421325
print(f"Response: {result}")
1343-
# Access fields from the decoded response
1344-
if "allowed" in result:
1345-
print(f"Allowed: {result['allowed']}")
1346-
1347-
print(f"Status Code: {response.status}")
1348-
print(f"Headers: {response.headers}")
13491326
```
13501327

13511328
#### Example: Calling an existing endpoint with GET
13521329

13531330
```python
13541331
# Get a list of stores with query parameters
1355-
response = await fga_client.raw_request(
1356-
operation_name="ListStores", # Required: descriptive name for the operation
1357-
method="GET",
1358-
path="/stores",
1359-
query_params={
1332+
stores_response = await fga_client.execute_api_request({
1333+
"operation_name": "ListStores",
1334+
"method": "GET",
1335+
"path": "/stores",
1336+
"query_params": {
13601337
"page_size": 10,
13611338
"continuation_token": "eyJwayI6...",
13621339
},
1363-
)
1340+
})
13641341

1365-
stores = response.json()
1342+
stores = stores_response.json()
13661343
print("Stores:", stores)
13671344
```
13681345

@@ -1372,25 +1349,25 @@ Path parameters are specified in the path using `{param_name}` syntax and are re
13721349

13731350
```python
13741351
# Using explicit path parameters
1375-
response = await fga_client.raw_request(
1376-
operation_name="ReadAuthorizationModel", # Required: descriptive name for the operation
1377-
method="GET",
1378-
path="/stores/{store_id}/authorization-models/{model_id}",
1379-
path_params={
1352+
response = await fga_client.execute_api_request({
1353+
"operation_name": "GetAuthorizationModel",
1354+
"method": "GET",
1355+
"path": "/stores/{store_id}/authorization-models/{model_id}",
1356+
"path_params": {
13801357
"store_id": "your-store-id",
13811358
"model_id": "your-model-id",
13821359
},
1383-
)
1360+
})
13841361

13851362
# Using automatic store_id substitution
1386-
response = await fga_client.raw_request(
1387-
operation_name="ReadAuthorizationModel", # Required: descriptive name for the operation
1388-
method="GET",
1389-
path="/stores/{store_id}/authorization-models/{model_id}",
1390-
path_params={
1363+
response = await fga_client.execute_api_request({
1364+
"operation_name": "GetAuthorizationModel",
1365+
"method": "GET",
1366+
"path": "/stores/{store_id}/authorization-models/{model_id}",
1367+
"path_params": {
13911368
"model_id": "your-model-id",
13921369
},
1393-
)
1370+
})
13941371
```
13951372

13961373
### Retries

openfga_sdk/client/client.py

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,47 +1107,89 @@ def map_to_assertion(client_assertion: ClientAssertion):
11071107
return api_response
11081108

11091109
#######################
1110-
# Raw Request
1110+
# Execute API Request
11111111
#######################
1112-
async def raw_request(
1112+
async def execute_api_request(
11131113
self,
1114-
method: str,
1115-
path: str,
1116-
query_params: dict[str, str | int | list[str | int]] | None = None,
1117-
path_params: dict[str, str] | None = None,
1118-
headers: dict[str, str] | None = None,
1119-
body: dict[str, Any] | list[Any] | str | bytes | None = None,
1120-
operation_name: str | None = None,
1114+
request: dict[str, Any],
11211115
options: dict[str, int | str | dict[str, int | str]] | None = None,
11221116
) -> RawResponse:
11231117
"""
1124-
Make a raw HTTP request to any OpenFGA API endpoint.
1118+
Execute an arbitrary HTTP request to any OpenFGA API endpoint.
11251119
1126-
:param method: HTTP method (GET, POST, PUT, DELETE, PATCH, etc.)
1127-
:param path: API endpoint path (e.g., "/stores/{store_id}/check" or "/stores")
1128-
:param query_params: Optional query parameters as a dictionary
1129-
:param path_params: Optional path parameters to replace placeholders in path
1130-
(e.g., {"store_id": "abc", "model_id": "xyz"})
1131-
:param headers: Optional request headers (will be merged with default headers)
1132-
:param body: Optional request body (dict/list will be JSON serialized, str/bytes sent as-is)
1133-
:param operation_name: Required operation name for telemetry/logging (e.g., "Check", "Write", "CustomEndpoint")
1120+
Useful when you need to call a new or experimental API that doesn't yet have a built-in method in the SDK.
1121+
You still get the benefits of the SDK, like authentication, configuration, and consistent error handling.
1122+
1123+
:param request: Request parameters dict with the following keys:
1124+
- operation_name (str): Required. Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint")
1125+
- method (str): Required. HTTP method (GET, POST, PUT, DELETE, PATCH)
1126+
- path (str): Required. API path (e.g., "/stores/{store_id}/my-endpoint")
1127+
- path_params (dict[str, str], optional): Path parameters to replace template variables in the path
1128+
- body (dict/list/str/bytes, optional): Request body for POST/PUT/PATCH requests
1129+
- query_params (dict[str, ...], optional): Query parameters
1130+
- headers (dict[str, str], optional): Custom request headers
11341131
:param options: Optional request options:
1135-
- headers: Additional headers (merged with headers parameter)
1132+
- headers: Additional headers (merged with request['headers']; options['headers'] takes precedence)
11361133
- retry_params: Override retry parameters for this request
1137-
- authorization_model_id: Not used in raw_request, but kept for consistency
11381134
:return: RawResponse object with status, headers, and body
1139-
:raises FgaValidationException: If path contains {store_id} but store_id is not configured
1135+
:raises FgaValidationException: If required parameters are missing or invalid
1136+
:raises ApiException: For HTTP errors (with SDK error handling applied)
1137+
"""
1138+
return await self._execute_api_request_internal(
1139+
request, options, streaming=False
1140+
)
1141+
1142+
async def execute_streamed_api_request(
1143+
self,
1144+
request: dict[str, Any],
1145+
options: dict[str, int | str | dict[str, int | str]] | None = None,
1146+
) -> RawResponse:
1147+
"""
1148+
Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint.
1149+
1150+
Similar to execute_api_request but for streaming endpoints.
1151+
1152+
:param request: Request parameters dict (see execute_api_request for details)
1153+
:param options: Optional request options (see execute_api_request for details)
1154+
:return: RawResponse object with status, headers, and body
1155+
:raises FgaValidationException: If required parameters are missing or invalid
11401156
:raises ApiException: For HTTP errors (with SDK error handling applied)
11411157
"""
1158+
return await self._execute_api_request_internal(
1159+
request, options, streaming=True
1160+
)
1161+
1162+
async def _execute_api_request_internal(
1163+
self,
1164+
request: dict[str, Any],
1165+
options: dict[str, int | str | dict[str, int | str]] | None = None,
1166+
streaming: bool = False,
1167+
) -> RawResponse:
1168+
"""Internal implementation for execute_api_request and execute_streamed_api_request."""
1169+
# Extract request parameters
1170+
operation_name = request.get("operation_name")
1171+
method = request.get("method")
1172+
path = request.get("path")
1173+
query_params = request.get("query_params")
1174+
path_params = request.get("path_params")
1175+
headers = request.get("headers")
1176+
body = request.get("body")
1177+
1178+
# Validate required parameters
1179+
if not operation_name:
1180+
raise FgaValidationException(
1181+
"operation_name is required for execute_api_request"
1182+
)
1183+
if not method:
1184+
raise FgaValidationException("method is required for execute_api_request")
1185+
if not path:
1186+
raise FgaValidationException("path is required for execute_api_request")
11421187

11431188
request_headers = dict(headers) if headers else {}
11441189
if options and options.get("headers"):
11451190
if isinstance(options["headers"], dict):
11461191
request_headers.update(options["headers"])
11471192

1148-
if not operation_name:
1149-
raise FgaValidationException("operation_name is required for raw_request")
1150-
11511193
resource_path = path
11521194
path_params_dict = dict(path_params) if path_params else {}
11531195

openfga_sdk/sync/client/client.py

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,47 +1105,85 @@ def map_to_assertion(client_assertion: ClientAssertion) -> Assertion:
11051105
return api_response
11061106

11071107
#######################
1108-
# Raw Request
1108+
# Execute API Request
11091109
#######################
1110-
def raw_request(
1110+
def execute_api_request(
11111111
self,
1112-
method: str,
1113-
path: str,
1114-
query_params: dict[str, str | int | list[str | int]] | None = None,
1115-
path_params: dict[str, str] | None = None,
1116-
headers: dict[str, str] | None = None,
1117-
body: dict[str, Any] | list[Any] | str | bytes | None = None,
1118-
operation_name: str | None = None,
1112+
request: dict[str, Any],
11191113
options: dict[str, int | str | dict[str, int | str]] | None = None,
11201114
) -> RawResponse:
11211115
"""
1122-
Make a raw HTTP request to any OpenFGA API endpoint.
1116+
Execute an arbitrary HTTP request to any OpenFGA API endpoint.
11231117
1124-
:param method: HTTP method (GET, POST, PUT, DELETE, PATCH, etc.)
1125-
:param path: API endpoint path (e.g., "/stores/{store_id}/check" or "/stores")
1126-
:param query_params: Optional query parameters as a dictionary
1127-
:param path_params: Optional path parameters to replace placeholders in path
1128-
(e.g., {"store_id": "abc", "model_id": "xyz"})
1129-
:param headers: Optional request headers (will be merged with default headers)
1130-
:param body: Optional request body (dict/list will be JSON serialized, str/bytes sent as-is)
1131-
:param operation_name: Required operation name for telemetry/logging (e.g., "Check", "Write", "CustomEndpoint")
1118+
Useful when you need to call a new or experimental API that doesn't yet have a built-in method in the SDK.
1119+
You still get the benefits of the SDK, like authentication, configuration, and consistent error handling.
1120+
1121+
:param request: Request parameters dict with the following keys:
1122+
- operation_name (str): Required. Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint")
1123+
- method (str): Required. HTTP method (GET, POST, PUT, DELETE, PATCH)
1124+
- path (str): Required. API path (e.g., "/stores/{store_id}/my-endpoint")
1125+
- path_params (dict[str, str], optional): Path parameters to replace template variables in the path
1126+
- body (dict/list/str/bytes, optional): Request body for POST/PUT/PATCH requests
1127+
- query_params (dict[str, ...], optional): Query parameters
1128+
- headers (dict[str, str], optional): Custom request headers
11321129
:param options: Optional request options:
1133-
- headers: Additional headers (merged with headers parameter)
1130+
- headers: Additional headers (merged with request['headers']; options['headers'] takes precedence)
11341131
- retry_params: Override retry parameters for this request
1135-
- authorization_model_id: Not used in raw_request, but kept for consistency
11361132
:return: RawResponse object with status, headers, and body
1137-
:raises FgaValidationException: If path contains {store_id} but store_id is not configured
1133+
:raises FgaValidationException: If required parameters are missing or invalid
1134+
:raises ApiException: For HTTP errors (with SDK error handling applied)
1135+
"""
1136+
return self._execute_api_request_internal(request, options, streaming=False)
1137+
1138+
def execute_streamed_api_request(
1139+
self,
1140+
request: dict[str, Any],
1141+
options: dict[str, int | str | dict[str, int | str]] | None = None,
1142+
) -> RawResponse:
1143+
"""
1144+
Execute an arbitrary HTTP request to a streaming OpenFGA API endpoint.
1145+
1146+
Similar to execute_api_request but for streaming endpoints.
1147+
1148+
:param request: Request parameters dict (see execute_api_request for details)
1149+
:param options: Optional request options (see execute_api_request for details)
1150+
:return: RawResponse object with status, headers, and body
1151+
:raises FgaValidationException: If required parameters are missing or invalid
11381152
:raises ApiException: For HTTP errors (with SDK error handling applied)
11391153
"""
1154+
return self._execute_api_request_internal(request, options, streaming=True)
1155+
1156+
def _execute_api_request_internal(
1157+
self,
1158+
request: dict[str, Any],
1159+
options: dict[str, int | str | dict[str, int | str]] | None = None,
1160+
streaming: bool = False,
1161+
) -> RawResponse:
1162+
"""Internal implementation for execute_api_request and execute_streamed_api_request."""
1163+
# Extract request parameters
1164+
operation_name = request.get("operation_name")
1165+
method = request.get("method")
1166+
path = request.get("path")
1167+
query_params = request.get("query_params")
1168+
path_params = request.get("path_params")
1169+
headers = request.get("headers")
1170+
body = request.get("body")
1171+
1172+
# Validate required parameters
1173+
if not operation_name:
1174+
raise FgaValidationException(
1175+
"operation_name is required for execute_api_request"
1176+
)
1177+
if not method:
1178+
raise FgaValidationException("method is required for execute_api_request")
1179+
if not path:
1180+
raise FgaValidationException("path is required for execute_api_request")
11401181

11411182
request_headers = dict(headers) if headers else {}
11421183
if options and options.get("headers"):
11431184
if isinstance(options["headers"], dict):
11441185
request_headers.update(options["headers"])
11451186

1146-
if not operation_name:
1147-
raise FgaValidationException("operation_name is required for raw_request")
1148-
11491187
resource_path = path
11501188
path_params_dict = dict(path_params) if path_params else {}
11511189

0 commit comments

Comments
 (0)