-
Notifications
You must be signed in to change notification settings - Fork 33
feat: add raw requestor for calling arbitrary endpoints #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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,13 +36,15 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CLIENT_MAX_METHOD_PARALLEL_REQUESTS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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,161 @@ 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) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resource_path = resource_path.replace(placeholder, encoded_value) | |
| resource_path = resource_path.replace(placeholder, encoded_value) |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exception handling catches ApiException only to re-raise it without modification. This catch-and-re-raise pattern serves no purpose and can be removed entirely for cleaner code.
| 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 | |
| _, 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, | |
| ) |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message 'Failed to get response from API client' is vague. Consider providing more context about why this might happen and what the user should check, such as 'Failed to get response from API client. This may indicate an internal SDK error or configuration issue.'
| 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." | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace on line 1148 after the dictionary assignment. Remove the extra whitespace for consistency with the codebase style.