diff --git a/config/clients/python/config.overrides.json b/config/clients/python/config.overrides.json index 027930c30..b18c098fe 100644 --- a/config/clients/python/config.overrides.json +++ b/config/clients/python/config.overrides.json @@ -12,6 +12,7 @@ "pythonMinimumRuntime": "3.10", "openTelemetryDocumentation": "docs/opentelemetry.md", "supportsStreamedListObjects": "streamed_list_objects", + "supportsExecuteApiRequest": true, "files": { "src/constants.mustache": { "destinationFilename": "openfga_sdk/constants.py", diff --git a/config/clients/python/patches/open_fga_api.py.patch b/config/clients/python/patches/open_fga_api.py.patch index d331bb20c..35d1844fc 100644 --- a/config/clients/python/patches/open_fga_api.py.patch +++ b/config/clients/python/patches/open_fga_api.py.patch @@ -1,6 +1,9 @@ ---- clients/fga-python-sdk/openfga_sdk/api/open_fga_api.py 2022-09-13 14:15:46.000000000 -0400 -+++ open_fga_api.py 2022-09-13 14:14:01.000000000 -0400 -@@ -229,8 +236,10 @@ +--- clients/fga-python-sdk/openfga_sdk/api/open_fga_api.py ++++ open_fga_api.py +@@ -501,13 +501,15 @@ + local_var_params=local_var_params, + ) + - async def create_store(self, **kwargs): + async def create_store(self, body, **kwargs): """Create a store @@ -8,48 +11,43 @@ Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. - >>> thread = await api.create_store() +- + >>> thread = await api.create_store(body) - ++ + :param body: (required) + :type body: CreateStoreRequest :param async_req: Whether to execute the request asynchronously. -@@ -216,15 +218,17 @@ + :type async_req: bool, optional + :param _preload_content: if False, the urllib3.HTTPResponse object will +@@ -524,15 +526,17 @@ :rtype: CreateStoreResponse """ kwargs["_return_http_data_only"] = True - return await self.create_store_with_http_info(**kwargs) -+ return await self.create_store_with_http_info(body, **kwargs) - +- - async def create_store_with_http_info(self, **kwargs): ++ return await self.create_store_with_http_info(body, **kwargs) ++ + async def create_store_with_http_info(self, body, **kwargs): """Create a store Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. - >>> thread = api.create_store_with_http_info() +- + >>> thread = api.create_store_with_http_info(body) - ++ + :param body: (required) + :type body: CreateStoreRequest :param async_req: Whether to execute the request asynchronously. :type async_req: bool, optional :param _return_http_data_only: response data without head status code -@@ -253,6 +257,8 @@ +@@ -561,7 +565,7 @@ + local_var_params = locals() all_params = [ - +- + 'body' -+ ] all_params.extend( [ -@@ -368,3 +370,3 @@ - return await (self.api_client.call_api( -- '/stores'.replace('{store_id}', store_id), 'POST', -+ '/stores', 'POST', - path_params, -@@ -1192,3 +1194,3 @@ - return await (self.api_client.call_api( -- '/stores'.replace('{store_id}', store_id), 'GET', -+ '/stores', 'GET', - path_params, diff --git a/config/clients/python/template/README_calling_other_endpoints.mustache b/config/clients/python/template/README_calling_other_endpoints.mustache new file mode 100644 index 000000000..5c5a0ad07 --- /dev/null +++ b/config/clients/python/template/README_calling_other_endpoints.mustache @@ -0,0 +1,78 @@ +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 `{{appShortName}}Client`. 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. + +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 + +#### 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={ + "user": "user:bob", + "action": "custom_action", + "resource": "resource:123", + }, + query_params={ + "page_size": 20, + }, +) + +# Access the response data +if response.status == 200: + result = response.json() + print(f"Response: {result}") +``` + +#### Example: Calling an existing endpoint with GET + +```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={ + "page_size": 10, + "continuation_token": "eyJwayI6...", + }, +) + +stores = 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.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={ + "model_id": "your-model-id", + }, +) +``` diff --git a/config/clients/python/template/src/api.py.mustache b/config/clients/python/template/src/api.py.mustache index 7ba58f994..de1614a0f 100644 --- a/config/clients/python/template/src/api.py.mustache +++ b/config/clients/python/template/src/api.py.mustache @@ -1,6 +1,14 @@ {{>partial_header}} +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + from {{packageName}}.api_client import ApiClient + + +if TYPE_CHECKING: + from {{packageName}}.client.models.raw_response import RawResponse from {{packageName}}.exceptions import ApiValueError, FgaValidationException from {{packageName}}.oauth2 import OAuth2Client from {{packageName}}.telemetry import Telemetry @@ -51,6 +59,219 @@ class {{classname}}: self.api_client.close() {{/asyncio}} + {{#asyncio}}async {{/asyncio}}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 {{#asyncio}}await {{/asyncio}}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), + ) + + {{#asyncio}}async {{/asyncio}}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 {{#asyncio}}await {{/asyncio}}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, + ) + + {{#asyncio}}async {{/asyncio}}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 {{#asyncio}}await {{/asyncio}}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, + ) + + {{#asyncio}}async {{/asyncio}}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 {{packageName}}.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ResponseParser, + ) + from {{packageName}}.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() + ) + + {{#asyncio}}await {{/asyncio}}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), + ) + {{#operation}} {{#asyncio}}async {{/asyncio}}def {{operationId}}(self, {{#sortParamsByRequiredFlag}}{{#allParams}}{{#required}}{{^-first}}{{paramName}}, {{/-first}}{{/required}}{{/allParams}}{{/sortParamsByRequiredFlag}}**kwargs): @@ -144,22 +365,6 @@ class {{classname}}: :rtype: {{#returnType}}tuple({{.}}, status_code(int), headers(HTTPHeaderDict)){{/returnType}}{{^returnType}}None{{/returnType}} """ - {{#servers.0}} - local_var_hosts = [ -{{#servers}} - '{{{url}}}'{{^-last}},{{/-last}} -{{/servers}} - ] - local_var_host = local_var_hosts[0] - if kwargs.get('_host_index'): - _host_index = int(kwargs.get('_host_index')) - if _host_index < 0 or _host_index >= len(local_var_hosts): - raise ApiValueError( - "Invalid host index. Must be 0 <= index < %s" - % len(local_var_host) - ) - local_var_host = local_var_hosts[_host_index] - {{/servers.0}} local_var_params = locals() all_params = [ @@ -185,7 +390,7 @@ class {{classname}}: ) for key, val in local_var_params['kwargs'].items(): - if key not in all_params{{#servers.0}} and key != "_host_index"{{/servers.0}}: + if key not in all_params: raise FgaValidationException( f"Got an unexpected keyword argument '{key}' to method {{operationId}}" ) @@ -195,7 +400,6 @@ class {{classname}}: {{^isNullable}} {{#required}} {{^-first}} - # verify the required parameter '{{paramName}}' is set if self.api_client.client_side_validation and local_var_params.get('{{paramName}}') is None: raise ApiValueError( "Missing the required parameter `{{paramName}}` when calling `{{operationId}}`") @@ -203,128 +407,43 @@ class {{classname}}: {{/required}} {{/isNullable}} {{/allParams}} - -{{#allParams}} -{{#-last}} -{{/-last}} -{{/allParams}} - collection_formats = {} - - path_params = {} - - store_id = None {{#pathParams}} -{{^-first}} - if '{{paramName}}' in local_var_params: - path_params['{{baseName}}'] = local_var_params['{{paramName}}']{{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} -{{/-first}} - {{#-first}} if self.api_client._get_store_id() is None: raise ApiValueError( "Store ID expected in api_client's configuration when calling `{{operationId}}`") store_id = self.api_client._get_store_id() {{/-first}} - {{/pathParams}} - - query_params = [] {{#queryParams}} +{{#-first}} + query_params = [] +{{/-first}} if local_var_params.get('{{paramName}}') is not None: - query_params.append(('{{baseName}}', local_var_params['{{paramName}}'])){{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} + query_params.append(('{{baseName}}', local_var_params['{{paramName}}'])) {{/queryParams}} - - header_params = dict(local_var_params.get('_headers', {})) -{{#headerParams}} - if '{{paramName}}' in local_var_params: - header_params['{{baseName}}'] = local_var_params['{{paramName}}']{{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} -{{/headerParams}} - - form_params = [] - local_var_files = {} -{{#formParams}} - if '{{paramName}}' in local_var_params: - {{^isFile}}form_params.append(('{{baseName}}', local_var_params['{{paramName}}'])){{/isFile}}{{#isFile}}local_var_files['{{baseName}}'] = local_var_params['{{paramName}}']{{/isFile}}{{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} -{{/formParams}} - - body_params = None + return {{#asyncio}}await {{/asyncio}}self._execute( + method="{{httpMethod}}", + path={{^pathParams}}"{{{path}}}"{{/pathParams}}{{#pathParams}}{{#-first}}f"{{{path}}}"{{/-first}}{{/pathParams}}, + method_name="{{operationId}}", + response_types_map={ +{{#returnType}} +{{#responses}} +{{^isWildcard}} + {{code}}: {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}}, +{{/isWildcard}} +{{/responses}} +{{/returnType}} + }, {{#bodyParam}} - if '{{paramName}}' in local_var_params: - body_params = local_var_params['{{paramName}}'] + body=local_var_params.get('{{paramName}}'), {{/bodyParam}} - {{#hasProduces}} - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - [{{#produces}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/produces}}]) - - {{/hasProduces}} - {{#hasConsumes}} - # HTTP header `Content-Type` - content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type([{{#consumes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/consumes}}],'{{httpMethod}}', body_params)) - if content_types_list: - header_params['Content-Type'] = content_types_list - - {{/hasConsumes}} - # Authentication setting - auth_settings = [{{#authMethods}}'{{name}}'{{^-last}}, {{/-last}}{{/authMethods}}] - - {{#returnType}} - {{#responses}} - {{#-first}} - response_types_map = { - {{/-first}} - {{^isWildcard}} - {{code}}: {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}}, - {{/isWildcard}} - {{#-last}} - } - {{/-last}} - {{/responses}} - {{/returnType}} - {{^returnType}} - response_types_map = {} - {{/returnType}} - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "{{operationId}}", - 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 {{#asyncio}}await ({{/asyncio}}self.api_client.call_api( - '{{{path}}}'.replace('{store_id}', store_id), '{{httpMethod}}', - 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'), - {{#servers.0}} - _host=local_var_host, - {{/servers.0}} - 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){{#asyncio}}){{/asyncio}} +{{#queryParams}} +{{#-first}} + query_params=query_params, +{{/-first}} +{{/queryParams}} + local_var_params=local_var_params, ) {{/operation}} {{/operations}} diff --git a/config/clients/python/template/src/sync/api.py.mustache b/config/clients/python/template/src/sync/api.py.mustache index 8437fd476..7b4280ffa 100644 --- a/config/clients/python/template/src/sync/api.py.mustache +++ b/config/clients/python/template/src/sync/api.py.mustache @@ -1,6 +1,14 @@ {{>partial_header}} +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + from {{packageName}}.exceptions import ApiValueError, FgaValidationException + + +if TYPE_CHECKING: + from {{packageName}}.client.models.raw_response import RawResponse from {{packageName}}.sync.api_client import ApiClient from {{packageName}}.sync.oauth2 import OAuth2Client from {{packageName}}.telemetry import Telemetry @@ -38,6 +46,219 @@ class {{classname}}: 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 {{packageName}}.client.execute_api_request_builder import ( + ExecuteApiRequestBuilder, + ResponseParser, + ) + from {{packageName}}.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), + ) + {{#operation}} def {{operationId}}(self, {{#sortParamsByRequiredFlag}}{{#allParams}}{{#required}}{{^-first}}{{paramName}}, {{/-first}}{{/required}}{{/allParams}}{{/sortParamsByRequiredFlag}}**kwargs): @@ -131,22 +352,6 @@ class {{classname}}: :rtype: {{#returnType}}tuple({{.}}, status_code(int), headers(HTTPHeaderDict)){{/returnType}}{{^returnType}}None{{/returnType}} """ - {{#servers.0}} - local_var_hosts = [ -{{#servers}} - '{{{url}}}'{{^-last}},{{/-last}} -{{/servers}} - ] - local_var_host = local_var_hosts[0] - if kwargs.get('_host_index'): - _host_index = int(kwargs.get('_host_index')) - if _host_index < 0 or _host_index >= len(local_var_hosts): - raise ApiValueError( - "Invalid host index. Must be 0 <= index < %s" - % len(local_var_host) - ) - local_var_host = local_var_hosts[_host_index] - {{/servers.0}} local_var_params = locals() all_params = [ @@ -159,20 +364,20 @@ class {{classname}}: ] all_params.extend( [ - "async_req", - "_return_http_data_only", - "_preload_content", - "_request_timeout", - "_request_auth", - "_content_type", - "_headers", - "_retry_params", - "_streaming", + '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{{#servers.0}} and key != "_host_index"{{/servers.0}}: + if key not in all_params: raise FgaValidationException( f"Got an unexpected keyword argument '{key}' to method {{operationId}}" ) @@ -182,7 +387,6 @@ class {{classname}}: {{^isNullable}} {{#required}} {{^-first}} - # verify the required parameter '{{paramName}}' is set if self.api_client.client_side_validation and local_var_params.get('{{paramName}}') is None: raise ApiValueError( "Missing the required parameter `{{paramName}}` when calling `{{operationId}}`") @@ -190,128 +394,43 @@ class {{classname}}: {{/required}} {{/isNullable}} {{/allParams}} - -{{#allParams}} -{{#-last}} -{{/-last}} -{{/allParams}} - collection_formats = {} - - path_params = {} - - store_id = None {{#pathParams}} -{{^-first}} - if '{{paramName}}' in local_var_params: - path_params['{{baseName}}'] = local_var_params['{{paramName}}']{{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} -{{/-first}} - {{#-first}} if self.api_client._get_store_id() is None: - raise ApiValueError("Store ID expected in api_client's configuration when calling `{{operationId}}`") + raise ApiValueError( + "Store ID expected in api_client's configuration when calling `{{operationId}}`") store_id = self.api_client._get_store_id() {{/-first}} - {{/pathParams}} - - query_params = [] {{#queryParams}} +{{#-first}} + query_params = [] +{{/-first}} if local_var_params.get('{{paramName}}') is not None: - query_params.append( - ('{{baseName}}', local_var_params['{{paramName}}'])){{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} + query_params.append(('{{baseName}}', local_var_params['{{paramName}}'])) {{/queryParams}} - - header_params = dict(local_var_params.get('_headers', {})) -{{#headerParams}} - if '{{paramName}}' in local_var_params: - header_params['{{baseName}}'] = local_var_params['{{paramName}}']{{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} -{{/headerParams}} - - form_params = [] - local_var_files = {} -{{#formParams}} - if '{{paramName}}' in local_var_params: - {{^isFile}}form_params.append(('{{baseName}}', local_var_params['{{paramName}}'])){{/isFile}}{{#isFile}}local_var_files['{{baseName}}'] = local_var_params['{{paramName}}']{{/isFile}}{{#isArray}} - collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isArray}} -{{/formParams}} - - body_params = None + return self._execute( + method="{{httpMethod}}", + path={{^pathParams}}"{{{path}}}"{{/pathParams}}{{#pathParams}}{{#-first}}f"{{{path}}}"{{/-first}}{{/pathParams}}, + method_name="{{operationId}}", + response_types_map={ +{{#returnType}} +{{#responses}} +{{^isWildcard}} + {{code}}: {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}}, +{{/isWildcard}} +{{/responses}} +{{/returnType}} + }, {{#bodyParam}} - if '{{paramName}}' in local_var_params: - body_params = local_var_params['{{paramName}}'] + body=local_var_params.get('{{paramName}}'), {{/bodyParam}} - {{#hasProduces}} - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - [{{#produces}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/produces}}]) - - {{/hasProduces}} - {{#hasConsumes}} - # HTTP header `Content-Type` - content_types_list = local_var_params.get('_content_type', self.api_client.select_header_content_type([{{#consumes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/consumes}}],'{{httpMethod}}', body_params)) - if content_types_list: - header_params['Content-Type'] = content_types_list - - {{/hasConsumes}} - # Authentication setting - auth_settings = [{{#authMethods}}'{{name}}'{{^-last}}, {{/-last}}{{/authMethods}}] - - {{#returnType}} - {{#responses}} - {{#-first}} - response_types_map = { - {{/-first}} - {{^isWildcard}} - {{code}}: {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}}, - {{/isWildcard}} - {{#-last}} - } - {{/-last}} - {{/responses}} - {{/returnType}} - {{^returnType}} - response_types_map = {} - {{/returnType}} - - telemetry_attributes: dict[TelemetryAttribute, str | bool | int | float] = { - TelemetryAttributes.fga_client_request_method: "{{operationId}}", - 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( - '{{{path}}}'.replace('{store_id}', store_id), '{{httpMethod}}', - 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'), - {{#servers.0}} - _host=local_var_host, - {{/servers.0}} - 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), +{{#queryParams}} +{{#-first}} + query_params=query_params, +{{/-first}} +{{/queryParams}} + local_var_params=local_var_params, ) {{/operation}} {{/operations}} diff --git a/config/common/files/README.mustache b/config/common/files/README.mustache index 06db0f2ce..bfe89e97b 100644 --- a/config/common/files/README.mustache +++ b/config/common/files/README.mustache @@ -49,6 +49,9 @@ - [Read Assertions](#read-assertions) - [Write Assertions](#write-assertions) - [Retries](#retries) +{{#supportsExecuteApiRequest}} + - [Calling Other Endpoints](#calling-other-endpoints) +{{/supportsExecuteApiRequest}} - [API Endpoints](#api-endpoints) - [Models](#models) {{#openTelemetryDocumentation}} @@ -104,8 +107,10 @@ If your server is configured with [authentication enabled]({{docsUrl}}/getting-s ### Calling the API {{>README_calling_api}} +{{#supportsExecuteApiRequest}}### Calling Other Endpoints -### Retries +{{>README_calling_other_endpoints}} +{{/supportsExecuteApiRequest}}### Retries {{>README_retries}}