From e6c26a747ae138b74956217feda45d69ed8b9f5d Mon Sep 17 00:00:00 2001 From: philip-ellis-sp Date: Fri, 30 Oct 2026 16:40:54 -0400 Subject: [PATCH] Added generic API endpoints Co-Authored-By: Claude Opus 5.5 --- .github/actions/build-sdk/action.yaml | 1 + Makefile | 9 +- README.md | 24 + generic_test.py | 106 ++ package.json | 3 +- sailpoint/__init__.py | 5 + sailpoint/generic/__init__.py | 33 + sailpoint/generic/api/__init__.py | 5 + sailpoint/generic/api/generic_api.py | 1612 +++++++++++++++++ sailpoint/generic/api_client.py | 869 +++++++++ sailpoint/generic/api_response.py | 21 + sailpoint/generic/configuration.py | 581 ++++++ .../python_code_examples_overlay.yaml | 139 ++ sailpoint/generic/docs/Methods/GenericApi.md | 377 ++++ sailpoint/generic/docs/Methods/Index.md | 30 + sailpoint/generic/docs/Models/Index.md | 19 + sailpoint/generic/exceptions.py | 216 +++ sailpoint/generic/models/__init__.py | 16 + sailpoint/generic/rest.py | 257 +++ sailpoint/generic/test/__init__.py | 0 sailpoint/generic/test/test_generic_api.py | 66 + sailpoint/generic_README.md | 90 + sdk-resources/build-versioned-sdk.js | 147 +- sdk-resources/generic-api.yaml | 223 +++ .../resources/docs_methods_index.mustache | 2 +- validation_test.py | 20 +- 26 files changed, 4863 insertions(+), 8 deletions(-) create mode 100644 generic_test.py create mode 100644 sailpoint/generic/__init__.py create mode 100644 sailpoint/generic/api/__init__.py create mode 100644 sailpoint/generic/api/generic_api.py create mode 100644 sailpoint/generic/api_client.py create mode 100644 sailpoint/generic/api_response.py create mode 100644 sailpoint/generic/configuration.py create mode 100644 sailpoint/generic/docs/Examples/python_code_examples_overlay.yaml create mode 100644 sailpoint/generic/docs/Methods/GenericApi.md create mode 100644 sailpoint/generic/docs/Methods/Index.md create mode 100644 sailpoint/generic/docs/Models/Index.md create mode 100644 sailpoint/generic/exceptions.py create mode 100644 sailpoint/generic/models/__init__.py create mode 100644 sailpoint/generic/rest.py create mode 100644 sailpoint/generic/test/__init__.py create mode 100644 sailpoint/generic/test/test_generic_api.py create mode 100644 sailpoint/generic_README.md create mode 100644 sdk-resources/generic-api.yaml diff --git a/.github/actions/build-sdk/action.yaml b/.github/actions/build-sdk/action.yaml index 5f785c6eb..66f18c7a0 100644 --- a/.github/actions/build-sdk/action.yaml +++ b/.github/actions/build-sdk/action.yaml @@ -75,5 +75,6 @@ runs: pip install -r requirements.txt pip install -e . if [ "${{ inputs.skip-tests }}" != "true" ]; then + python generic_test.py -v python validation_test.py -v fi diff --git a/Makefile b/Makefile index cddb9adc7..8ec5247c3 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ specs: clean-specs: rm -rf ./api-specs -APIS_DIR ?= api-specs/idn/src/main/yaml/apis +APIS_DIR ?= api-specs/idn/apis .PHONY: build build: @@ -16,8 +16,15 @@ build: build-partition: node sdk-resources/build-versioned-sdk.js $(APIS_DIR) --partition $(PARTITION) +# Builds only sailpoint/generic from sdk-resources/generic-api.yaml. +# Needs no api-specs checkout. +.PHONY: build-generic +build-generic: + node sdk-resources/build-versioned-sdk.js --generic-only + .PHONY: test test: pip install -r requirements.txt pip install -e . + python generic_test.py -v python validation_test.py -v diff --git a/README.md b/README.md index 2cfd1cbb2..8a7660024 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,30 @@ The Python SDK allows you to use Python to interact with the SailPoint APIs and To get started with the Python SDK and learn about its various functionalities, refer to the [documentation](https://developer.sailpoint.com/docs/tools/sdk/python) on . +## Calling any API with the generic client + +The generic client calls any SailPoint versioned API by its path, including APIs +that do not have a resource-specific client or documentation yet. The path +includes the API version, for example `accounts/v1`. The client uses the same +configuration, authentication and retries as the other clients. + +- Each path segment is URL-encoded. Pass the segments unencoded. +- Anything after a `?` in the path is sent as query parameters. +- A PATCH body is a JSON Patch list, sent as `application/json-patch+json`. + +```python +from sailpoint import ApiClient, Configuration, GenericApi + +with ApiClient(Configuration()) as api_client: + api = GenericApi(api_client) + + accounts = api.generic_get(path="accounts/v1", limit=10, filters='sourceId eq "2c91808a"') + api.generic_patch( + path="transforms/v1/2c91808a", + request_body=[{"op": "replace", "path": "/attributes/input", "value": "lower"}], + ) +``` + ## Contributing The SailPoint Developer Relations Team is always actively working to improve the SailPoint Python SDK to make it as useful as possible to the SailPoint Developer Community. diff --git a/generic_test.py b/generic_test.py new file mode 100644 index 000000000..d0dbbbcd7 --- /dev/null +++ b/generic_test.py @@ -0,0 +1,106 @@ +"""Offline tests for the generic API client. + +The requests go to a local HTTP server, so these tests need no tenant. They +require the SDK to be built first: + node sdk-resources/build-versioned-sdk.js --generic-only +""" + +import json +import threading +import unittest +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import parse_qs, urlsplit + +from sailpoint import ApiClient, GenericApi +from sailpoint.configuration import Configuration, ConfigurationParams + + +class _CapturingHandler(BaseHTTPRequestHandler): + """Records each request and answers with the server's canned JSON body.""" + + def _handle(self): + length = int(self.headers.get("Content-Length") or 0) + self.server.captured = { + "method": self.command, + "url": urlsplit(self.path), + "content_type": self.headers.get("Content-Type"), + "auth": self.headers.get("Authorization"), + "body": self.rfile.read(length).decode() if length else "", + } + payload = self.server.response.encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + do_GET = do_POST = do_PUT = do_PATCH = do_DELETE = _handle + + def log_message(self, format, *args): + pass + + +class TestGenericApi(unittest.TestCase): + + def setUp(self): + self.server = HTTPServer(("127.0.0.1", 0), _CapturingHandler) + self.server.response = "{}" + threading.Thread(target=self.server.serve_forever, daemon=True).start() + + params = ConfigurationParams() + params.base_url = f"http://127.0.0.1:{self.server.server_port}" + params.access_token = "token-1" + self.api = GenericApi(ApiClient(Configuration(params))) + + def tearDown(self): + self.server.shutdown() + self.server.server_close() + + def test_keeps_slashes_and_adds_no_version(self): + self.server.response = '[{"id": "1"}]' + + result = self.api.generic_get( + path="/entitlements/v1/identities/2c91808a/entitlements", limit=10, filters='name eq "x"' + ) + + captured = self.server.captured + self.assertEqual(captured["url"].path, "/entitlements/v1/identities/2c91808a/entitlements") + query = parse_qs(captured["url"].query) + self.assertEqual(query["limit"], ["10"]) + self.assertEqual(query["filters"], ['name eq "x"']) + self.assertEqual(captured["auth"], "Bearer token-1") + self.assertEqual(result, [{"id": "1"}]) + + def test_encodes_segments_and_keeps_query_from_path(self): + self.api.generic_get(path="accounts/v1/a b?detailLevel=SLIM", limit=5) + + captured = self.server.captured + self.assertEqual(captured["url"].path, "/accounts/v1/a%20b") + query = parse_qs(captured["url"].query) + self.assertEqual(query["detailLevel"], ["SLIM"]) + self.assertEqual(query["limit"], ["5"]) + + def test_patch_sends_json_patch_list(self): + patch = [{"op": "replace", "path": "/description", "value": "New"}] + + self.api.generic_patch(path="transforms/v1/1234", request_body=patch) + + captured = self.server.captured + self.assertEqual(captured["method"], "PATCH") + self.assertEqual(captured["content_type"], "application/json-patch+json") + self.assertEqual(json.loads(captured["body"]), patch) + + def test_post_sends_json_body_to_v2_path(self): + self.server.response = '{"id": "1"}' + + result = self.api.generic_post(path="sources/v2/2c91808a/provisioning-policies", request_body={"name": "Test", "type": "lower"}) + + captured = self.server.captured + self.assertEqual(captured["method"], "POST") + self.assertEqual(captured["url"].path, "/sources/v2/2c91808a/provisioning-policies") + self.assertEqual(json.loads(captured["body"]), {"name": "Test", "type": "lower"}) + self.assertEqual(result, {"id": "1"}) + + +if __name__ == "__main__": + unittest.main() diff --git a/package.json b/package.json index 9da47a3d8..bcd715197 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "private": true, "description": "Build tooling for the SailPoint Python SDK", "scripts": { - "build": "node sdk-resources/build-versioned-sdk.js" + "build": "node sdk-resources/build-versioned-sdk.js", + "build:generic": "node sdk-resources/build-versioned-sdk.js --generic-only" }, "dependencies": { "@redocly/cli": "^2.32.2" diff --git a/sailpoint/__init__.py b/sailpoint/__init__.py index a61c570d6..f6f805e28 100644 --- a/sailpoint/__init__.py +++ b/sailpoint/__init__.py @@ -71,6 +71,7 @@ "DimensionsApi": (("sailpoint.dimensions.api.dimensions_api", "DimensionsApi"),), "EntitlementConnectionsApi": (("sailpoint.entitlement_connections.api.entitlement_connections_api", "EntitlementConnectionsApi"),), "EntitlementsApi": (("sailpoint.entitlements.api.entitlements_api", "EntitlementsApi"),), + "GenericApi": (("sailpoint.generic.api.generic_api", "GenericApi"),), "GenericRequestApprovalConfigApi": (("sailpoint.generic_request_approval_config.api.generic_request_approval_config_api", "GenericRequestApprovalConfigApi"),), "GlobalTenantSecuritySettingsApi": (("sailpoint.global_tenant_security_settings.api.global_tenant_security_settings_api", "GlobalTenantSecuritySettingsApi"),), "NextGenerationCertificationCampaignsApi": (("sailpoint.gov_certification.api.next_generation_certification_campaigns_api", "NextGenerationCertificationCampaignsApi"),), @@ -237,6 +238,7 @@ "DimensionsApi", "EntitlementConnectionsApi", "EntitlementsApi", + "GenericApi", "GenericRequestApprovalConfigApi", "GlobalTenantSecuritySettingsApi", "NextGenerationCertificationCampaignsApi", @@ -431,6 +433,7 @@ def __dir__(cls) -> List[str]: from sailpoint.dimensions.api.dimensions_api import DimensionsApi as _dimensions__DimensionsApi from sailpoint.entitlement_connections.api.entitlement_connections_api import EntitlementConnectionsApi as _entitlement_connections__EntitlementConnectionsApi from sailpoint.entitlements.api.entitlements_api import EntitlementsApi as _entitlements__EntitlementsApi + from sailpoint.generic.api.generic_api import GenericApi as _generic__GenericApi from sailpoint.generic_request_approval_config.api.generic_request_approval_config_api import GenericRequestApprovalConfigApi as _generic_request_approval_config__GenericRequestApprovalConfigApi from sailpoint.global_tenant_security_settings.api.global_tenant_security_settings_api import GlobalTenantSecuritySettingsApi as _global_tenant_security_settings__GlobalTenantSecuritySettingsApi from sailpoint.gov_certification.api.next_generation_certification_campaigns_api import NextGenerationCertificationCampaignsApi as _gov_certification__NextGenerationCertificationCampaignsApi @@ -597,6 +600,7 @@ class _WorkflowsApiCombined(_workflows__WorkflowsApi, _nerm__WorkflowsApi): ... DimensionsApi = _dimensions__DimensionsApi EntitlementConnectionsApi = _entitlement_connections__EntitlementConnectionsApi EntitlementsApi = _entitlements__EntitlementsApi + GenericApi = _generic__GenericApi GenericRequestApprovalConfigApi = _generic_request_approval_config__GenericRequestApprovalConfigApi GlobalTenantSecuritySettingsApi = _global_tenant_security_settings__GlobalTenantSecuritySettingsApi NextGenerationCertificationCampaignsApi = _gov_certification__NextGenerationCertificationCampaignsApi @@ -758,6 +762,7 @@ class SailPoint: DimensionsApi = _dimensions__DimensionsApi EntitlementConnectionsApi = _entitlement_connections__EntitlementConnectionsApi EntitlementsApi = _entitlements__EntitlementsApi + GenericApi = _generic__GenericApi GenericRequestApprovalConfigApi = _generic_request_approval_config__GenericRequestApprovalConfigApi GlobalTenantSecuritySettingsApi = _global_tenant_security_settings__GlobalTenantSecuritySettingsApi NextGenerationCertificationCampaignsApi = _gov_certification__NextGenerationCertificationCampaignsApi diff --git a/sailpoint/generic/__init__.py b/sailpoint/generic/__init__.py new file mode 100644 index 000000000..694eceb23 --- /dev/null +++ b/sailpoint/generic/__init__.py @@ -0,0 +1,33 @@ +# coding: utf-8 + +# flake8: noqa + +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +__version__ = "1.0.0" + +# import apis into sdk package +from sailpoint.generic.api.generic_api import GenericApi + +# import ApiClient +from sailpoint.generic.api_response import ApiResponse +from sailpoint.generic.api_client import ApiClient +from sailpoint.generic.configuration import Configuration +from sailpoint.generic.exceptions import OpenApiException +from sailpoint.generic.exceptions import ApiTypeError +from sailpoint.generic.exceptions import ApiValueError +from sailpoint.generic.exceptions import ApiKeyError +from sailpoint.generic.exceptions import ApiAttributeError +from sailpoint.generic.exceptions import ApiException + +# import models into sdk package diff --git a/sailpoint/generic/api/__init__.py b/sailpoint/generic/api/__init__.py new file mode 100644 index 000000000..b4e0da1b1 --- /dev/null +++ b/sailpoint/generic/api/__init__.py @@ -0,0 +1,5 @@ +# flake8: noqa + +# import apis into api package +from sailpoint.generic.api.generic_api import GenericApi + diff --git a/sailpoint/generic/api/generic_api.py b/sailpoint/generic/api/generic_api.py new file mode 100644 index 000000000..2caa79120 --- /dev/null +++ b/sailpoint/generic/api/generic_api.py @@ -0,0 +1,1612 @@ +# coding: utf-8 + +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +import warnings +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Any, Dict, List, Optional, Tuple, Union +from typing_extensions import Annotated + +from pydantic import Field, StrictBool, StrictStr +from typing import Any, Optional +from typing_extensions import Annotated + +from sailpoint.generic.api_client import ApiClient, RequestSerialized +from sailpoint.generic.api_response import ApiResponse +from sailpoint.generic.rest import RESTResponseType +from urllib.parse import parse_qsl, quote + + +def _generic_resource_path(path: str) -> Tuple[str, List[Tuple[str, str]]]: + """Split the ``path`` of a generic request into a resource path and query parameters. + + ``path`` is everything after the tenant base URL, including the API version, + for example ``accounts/v1`` or ``sources/v1/2c918085/schemas``. Leading and + trailing slashes are optional. Each segment is URL-encoded on its own, so the + slashes between segments are kept. Anything after a ``?`` is returned as query + parameters, for example ``accounts/v1?detailLevel=SLIM``. + """ + path_part, _, query = str(path).partition('?') + segments = [quote(segment, safe='') for segment in path_part.split('/') if segment] + return '/' + '/'.join(segments), parse_qsl(query, keep_blank_values=True) + + +class GenericApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + def generic_delete( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> object: + """Generic DELETE request + + Sends a DELETE request to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_delete_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def generic_delete_with_http_info( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[object]: + """Generic DELETE request + + Sends a DELETE request to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_delete_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def generic_delete_without_preload_content( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Generic DELETE request + + Sends a DELETE request to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_delete_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _generic_delete_serialize( + self, + path, + x_sail_point_experimental, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + _resource_path, _path_query = _generic_resource_path(path) + _query_params.extend(_path_query) + # process the query parameters + # process the header parameters + if x_sail_point_experimental is not None: + _header_params['X-SailPoint-Experimental'] = x_sail_point_experimental + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( _query_params, + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + ] + + return self.api_client.param_serialize( + method='DELETE', + resource_path=_resource_path, + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def generic_get( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + limit: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information.")] = None, + offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Offset into the full result set. Usually specified with *limit* to paginate through the results.")] = None, + count: Annotated[Optional[StrictBool], Field(description="If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored.")] = None, + filters: Annotated[Optional[StrictStr], Field(description="Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results).")] = None, + sorters: Annotated[Optional[StrictStr], Field(description="Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results).")] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> object: + """Generic GET request + + Sends a GET request to any versioned API path, for example `sources/v1/{id}/schemas` or `sources/v2/{sourceId}/provisioning-policies`. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param limit: Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. + :type limit: int + :param offset: Offset into the full result set. Usually specified with *limit* to paginate through the results. + :type offset: int + :param count: If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. + :type count: bool + :param filters: Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). + :type filters: str + :param sorters: Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). + :type sorters: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_get_serialize( + path=path, + limit=limit, + offset=offset, + count=count, + filters=filters, + sorters=sorters, + x_sail_point_experimental=x_sail_point_experimental, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def generic_get_with_http_info( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + limit: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information.")] = None, + offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Offset into the full result set. Usually specified with *limit* to paginate through the results.")] = None, + count: Annotated[Optional[StrictBool], Field(description="If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored.")] = None, + filters: Annotated[Optional[StrictStr], Field(description="Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results).")] = None, + sorters: Annotated[Optional[StrictStr], Field(description="Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results).")] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[object]: + """Generic GET request + + Sends a GET request to any versioned API path, for example `sources/v1/{id}/schemas` or `sources/v2/{sourceId}/provisioning-policies`. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param limit: Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. + :type limit: int + :param offset: Offset into the full result set. Usually specified with *limit* to paginate through the results. + :type offset: int + :param count: If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. + :type count: bool + :param filters: Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). + :type filters: str + :param sorters: Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). + :type sorters: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_get_serialize( + path=path, + limit=limit, + offset=offset, + count=count, + filters=filters, + sorters=sorters, + x_sail_point_experimental=x_sail_point_experimental, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def generic_get_without_preload_content( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + limit: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information.")] = None, + offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Offset into the full result set. Usually specified with *limit* to paginate through the results.")] = None, + count: Annotated[Optional[StrictBool], Field(description="If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored.")] = None, + filters: Annotated[Optional[StrictStr], Field(description="Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results).")] = None, + sorters: Annotated[Optional[StrictStr], Field(description="Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results).")] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Generic GET request + + Sends a GET request to any versioned API path, for example `sources/v1/{id}/schemas` or `sources/v2/{sourceId}/provisioning-policies`. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param limit: Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. + :type limit: int + :param offset: Offset into the full result set. Usually specified with *limit* to paginate through the results. + :type offset: int + :param count: If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. + :type count: bool + :param filters: Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). + :type filters: str + :param sorters: Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). + :type sorters: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_get_serialize( + path=path, + limit=limit, + offset=offset, + count=count, + filters=filters, + sorters=sorters, + x_sail_point_experimental=x_sail_point_experimental, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _generic_get_serialize( + self, + path, + limit, + offset, + count, + filters, + sorters, + x_sail_point_experimental, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + _resource_path, _path_query = _generic_resource_path(path) + _query_params.extend(_path_query) + # process the query parameters + if limit is not None: + + _query_params.append(('limit', limit)) + + if offset is not None: + + _query_params.append(('offset', offset)) + + if count is not None: + + _query_params.append(('count', count)) + + if filters is not None: + + _query_params.append(('filters', filters)) + + if sorters is not None: + + _query_params.append(('sorters', sorters)) + + # process the header parameters + if x_sail_point_experimental is not None: + _header_params['X-SailPoint-Experimental'] = x_sail_point_experimental + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( _query_params, + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + ] + + return self.api_client.param_serialize( + method='GET', + resource_path=_resource_path, + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def generic_patch( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> object: + """Generic PATCH request + + Sends a PATCH request to any versioned API path. The body is a JSON Patch document (RFC 6902): a list of operations such as `[{\"op\": \"replace\", \"path\": \"/description\", \"value\": \"New description\"}]`. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_patch_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def generic_patch_with_http_info( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[object]: + """Generic PATCH request + + Sends a PATCH request to any versioned API path. The body is a JSON Patch document (RFC 6902): a list of operations such as `[{\"op\": \"replace\", \"path\": \"/description\", \"value\": \"New description\"}]`. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_patch_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def generic_patch_without_preload_content( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Generic PATCH request + + Sends a PATCH request to any versioned API path. The body is a JSON Patch document (RFC 6902): a list of operations such as `[{\"op\": \"replace\", \"path\": \"/description\", \"value\": \"New description\"}]`. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_patch_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _generic_patch_serialize( + self, + path, + x_sail_point_experimental, + request_body, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + _resource_path, _path_query = _generic_resource_path(path) + _query_params.extend(_path_query) + # process the query parameters + # process the header parameters + if x_sail_point_experimental is not None: + _header_params['X-SailPoint-Experimental'] = x_sail_point_experimental + # process the form parameters + # process the body parameter + if request_body is not None: + _body_params = request_body + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( _query_params, + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json-patch+json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + ] + + return self.api_client.param_serialize( + method='PATCH', + resource_path=_resource_path, + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def generic_post( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> object: + """Generic POST request + + Sends a POST request with a JSON body to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_post_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def generic_post_with_http_info( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[object]: + """Generic POST request + + Sends a POST request with a JSON body to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_post_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def generic_post_without_preload_content( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Generic POST request + + Sends a POST request with a JSON body to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_post_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _generic_post_serialize( + self, + path, + x_sail_point_experimental, + request_body, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + _resource_path, _path_query = _generic_resource_path(path) + _query_params.extend(_path_query) + # process the query parameters + # process the header parameters + if x_sail_point_experimental is not None: + _header_params['X-SailPoint-Experimental'] = x_sail_point_experimental + # process the form parameters + # process the body parameter + if request_body is not None: + _body_params = request_body + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( _query_params, + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + ] + + return self.api_client.param_serialize( + method='POST', + resource_path=_resource_path, + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def generic_put( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> object: + """Generic PUT request + + Sends a PUT request with a JSON body to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_put_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def generic_put_with_http_info( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[object]: + """Generic PUT request + + Sends a PUT request with a JSON body to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_put_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def generic_put_without_preload_content( + self, + path: Annotated[StrictStr, Field(description="API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`.")], + request_body: Optional[Any] = None, + x_sail_point_experimental: Annotated[Optional[StrictStr], Field(description="Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs.")] = None, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Generic PUT request + + Sends a PUT request with a JSON body to any versioned API path. + + :param path: API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. (required) + :type path: str + :param x_sail_point_experimental: Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + :type x_sail_point_experimental: str + :param request_body: + :type request_body: object + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._generic_put_serialize( + path=path, + x_sail_point_experimental=x_sail_point_experimental, + request_body=request_body, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "object", + '204': None, + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _generic_put_serialize( + self, + path, + x_sail_point_experimental, + request_body, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + _resource_path, _path_query = _generic_resource_path(path) + _query_params.extend(_path_query) + # process the query parameters + # process the header parameters + if x_sail_point_experimental is not None: + _header_params['X-SailPoint-Experimental'] = x_sail_point_experimental + # process the form parameters + # process the body parameter + if request_body is not None: + _body_params = request_body + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( _query_params, + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + ] + + return self.api_client.param_serialize( + method='PUT', + resource_path=_resource_path, + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/sailpoint/generic/api_client.py b/sailpoint/generic/api_client.py new file mode 100644 index 000000000..7f3273978 --- /dev/null +++ b/sailpoint/generic/api_client.py @@ -0,0 +1,869 @@ +# coding: utf-8 + +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import datetime +from dateutil.parser import parse +from enum import Enum +import decimal +import json +import mimetypes +import os +import platform +import re +import sys +import tempfile +import xmltodict + +from urllib.parse import quote +from typing import Tuple, Optional, List, Dict, Union +from pydantic import SecretStr + +from sailpoint.configuration import Configuration +from sailpoint.generic.api_response import ApiResponse, T as ApiResponseT +import sailpoint.generic.models +from sailpoint.generic import rest +from sailpoint.generic.exceptions import ( + ApiValueError, + ApiException, + BadRequestException, + UnauthorizedException, + ForbiddenException, + NotFoundException, + ServiceException +) + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +RequestSerialized = Tuple[str, str, Dict[str, str], Optional[str], List[str]] + +class ApiClient: + """Generic API client for OpenAPI client library builds. + + OpenAPI generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the OpenAPI + templates. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + """ + + PRIMITIVE_TYPES = (float, bool, bytes, str, int) + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int, # TODO remove as only py3 is supported? + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'decimal': decimal.Decimal, + 'object': object, + } + _pool = None + + def __init__( + self, + configuration=None, + header_name=None, + header_value=None, + cookie=None + ) -> None: + # use default configuration if none is provided + if configuration is None: + configuration = Configuration.get_default() + self.configuration = configuration + + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + self.cookie = cookie + # Set default User-Agent. + self.user_agent = 'SailPoint-SDK-Python/1.0.0' + if configuration and getattr(configuration, 'consumer_identifier', None) and getattr(configuration, 'consumer_version', None): + self.user_agent = f'{self.user_agent} ({configuration.consumer_identifier}/{configuration.consumer_version})' + self.user_agent = f'{self.user_agent} ({sys.platform}; {platform.machine()}) Python/{sys.version.split()[0]} (openapi-generator/7.12.0)' + self.client_side_validation = configuration.client_side_validation + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + + _default = None + + @classmethod + def get_default(cls): + """Return new instance of ApiClient. + + This method returns newly created, based on default constructor, + object of ApiClient class or returns a copy of default + ApiClient. + + :return: The ApiClient object. + """ + if cls._default is None: + cls._default = ApiClient() + return cls._default + + @classmethod + def set_default(cls, default): + """Set default instance of ApiClient. + + It stores default ApiClient. + + :param default: object of ApiClient. + """ + cls._default = default + + def param_serialize( + self, + method, + resource_path, + path_params=None, + query_params=None, + header_params=None, + body=None, + post_params=None, + files=None, auth_settings=None, + collection_formats=None, + _host=None, + _request_auth=None + ) -> RequestSerialized: + + """Builds the HTTP request params needed by the request. + :param method: Method to call. + :param resource_path: Path to method endpoint. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param files dict: key -> filename, value -> filepath, + for `multipart/form-data`. + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :return: tuple of form (path, http_method, query_params, header_params, + body, post_params, files) + """ + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict( + self.parameters_to_tuples(header_params,collection_formats) + ) + # Add Authentication header to request + header_params['Authorization'] = "Bearer " + self.configuration.access_token + + if not self.configuration.experimental and 'X-SailPoint-Experimental' in header_params: + raise Exception(f"{bcolors.WARNING}You are using Experimental APIs. Set configuration.experimental = True to enable these APIs in the SDK.{bcolors.ENDC}") + elif self.configuration.experimental == True and 'X-SailPoint-Experimental' in header_params: + if not self.configuration.suppress_experimental_warnings: + print(f"{bcolors.WARNING}Warning: You are using Experimental APIs ({resource_path}){bcolors.ENDC}") + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples( + path_params, + collection_formats + ) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # post parameters + if post_params or files: + post_params = post_params if post_params else [] + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples( + post_params, + collection_formats + ) + if files: + post_params.extend(self.files_parameters(files)) + + # auth setting + self.update_params_for_auth( + header_params, + query_params, + auth_settings, + resource_path, + method, + body, + request_auth=_request_auth + ) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # request url + if _host is None: + url = self.configuration.base_url + resource_path + else: + # use server/host defined in path or operation instead + url = _host + resource_path + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + url_query = self.parameters_to_url_query( + query_params, + collection_formats + ) + url += "?" + url_query + + return method, url, header_params, body, post_params + + + def call_api( + self, + method, + url, + header_params=None, + body=None, + post_params=None, + _request_timeout=None + ) -> rest.RESTResponse: + """Makes the HTTP request (synchronous) + :param method: Method to call. + :param url: Path to method endpoint. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param _request_timeout: timeout setting for this request. + :return: RESTResponse + """ + + try: + # perform request and return response + response_data = self.rest_client.request( + method, url, + headers=header_params, + body=body, post_params=post_params, + _request_timeout=_request_timeout + ) + + except ApiException as e: + raise e + + return response_data + + def response_deserialize( + self, + response_data: rest.RESTResponse, + response_types_map: Optional[Dict[str, ApiResponseT]]=None + ) -> ApiResponse[ApiResponseT]: + """Deserializes response into an object. + :param response_data: RESTResponse object to be deserialized. + :param response_types_map: dict of response types. + :return: ApiResponse + """ + + msg = "RESTResponse.read() must be called before passing it to response_deserialize()" + assert response_data.data is not None, msg + + response_type = response_types_map.get(str(response_data.status), None) + if not response_type and isinstance(response_data.status, int) and 100 <= response_data.status <= 599: + # if not found, look for '1XX', '2XX', etc. + response_type = response_types_map.get(str(response_data.status)[0] + "XX", None) + + # deserialize response data + response_text = None + return_data = None + try: + if response_type == "bytearray": + return_data = self.save_response_to_file(response_data.data, response_data.getheaders()) + elif response_type == "file": + return_data = self.__deserialize_file(response_data) + elif response_type is not None: + match = None + content_type = response_data.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type) + encoding = match.group(1) if match else "utf-8" + response_text = response_data.data.decode(encoding) + return_data = self.deserialize(response_text, response_type, content_type) + finally: + if not 200 <= response_data.status <= 299: + raise ApiException.from_response( + http_resp=response_data, + body=response_text, + data=return_data, + ) + + return ApiResponse( + status_code = response_data.status, + data = return_data, + headers = response_data.getheaders(), + raw_data = response_data.data + ) + + def save_response_to_file(self, file_data: bytes, headers: Dict[str, str]) -> str: + """Saves binary response content as a file and returns the file path.""" + + content_disposition = headers.get('Content-Disposition', None) + filename = "downloaded_file" + + if content_disposition: + filename_match = re.search(r'filename="(.+?)"', content_disposition) + filename = filename_match.group(1) if filename_match else "downloaded_file" + + content_type = headers.get('Content-Type', '').lower() + + # Extract file extension if it exists + existing_extension = os.path.splitext(filename)[1] # Extracts ".zip", ".csv", etc. + + # If there's no existing extension, determine it from Content-Type + if not existing_extension: + if "application/zip" in content_type: + filename += ".zip" + elif "application/pdf" in content_type: + filename += ".pdf" + elif "image/png" in content_type: + filename += ".png" + elif "image/jpeg" in content_type: + filename += ".jpg" + elif "text/csv" in content_type: + filename += ".csv" + + file_path = os.path.join(os.getcwd(), filename) + + with open(file_path, "wb") as file: + file.write(file_data) # Write bytes directly + + print(f"File has been saved to: {file_path}") + return file_path + + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is SecretStr, return obj.get_secret_value() + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is decimal.Decimal return string representation. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, Enum): + return obj.value + elif isinstance(obj, SecretStr): + return obj.get_secret_value() + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, list): + return [ + self.sanitize_for_serialization(sub_obj) for sub_obj in obj + ] + elif isinstance(obj, tuple): + return tuple( + self.sanitize_for_serialization(sub_obj) for sub_obj in obj + ) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + elif isinstance(obj, decimal.Decimal): + return str(obj) + + elif isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `openapi_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')): + obj_dict = obj.to_dict() + else: + obj_dict = obj.__dict__ + + return { + key: self.sanitize_for_serialization(val) + for key, val in obj_dict.items() + } + + def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + :param content_type: content type of response. + + :return: deserialized object. + """ + + # fetch data from response object + if content_type is None: + try: + data = json.loads(response_text) + except ValueError: + data = response_text + elif content_type.startswith("application/json"): + if response_text == "": + data = "" + else: + data = json.loads(response_text) + elif content_type.startswith("application/xml") or content_type.startswith("text/xml"): + data = xmltodict.parse(response_text) + elif content_type.startswith("text/plain"): + data = response_text + else: + raise ApiException( + status=0, + reason="Unsupported content type: {0}".format(content_type) + ) + + return self.__deserialize(data, response_type) + + def __deserialize(self, data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if isinstance(klass, str): + if klass.startswith('List['): + m = re.match(r'List\[(.*)]', klass) + assert m is not None, "Malformed List type definition" + sub_kls = m.group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('Dict['): + m = re.match(r'Dict\[([^,]*), (.*)]', klass) + assert m is not None, "Malformed Dict type definition" + sub_kls = m.group(2) + return {k: self.__deserialize(v, sub_kls) + for k, v in data.items()} + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr(sailpoint.generic.models, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass == object: + return self.__deserialize_object(data) + elif klass == datetime.date: + return self.__deserialize_date(data) + elif klass == datetime.datetime: + return self.__deserialize_datetime(data) + elif klass == decimal.Decimal: + return decimal.Decimal(data) + elif issubclass(klass, Enum): + return self.__deserialize_enum(data, klass) + else: + return self.__deserialize_model(data, klass) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params: List[Tuple[str, str]] = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def parameters_to_url_query(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: URL query string (e.g. a=Hello%20World&b=123) + """ + new_params: List[Tuple[str, str]] = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: + if isinstance(v, bool): + v = str(v).lower() + if isinstance(v, (int, float)): + v = str(v) + if isinstance(v, dict): + v = json.dumps(v) + + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, quote(str(value))) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(quote(str(value)) for value in v)) + ) + else: + new_params.append((k, quote(str(v)))) + + return "&".join(["=".join(map(str, item)) for item in new_params]) + + def files_parameters( + self, + files: Dict[str, Union[str, bytes, List[str], List[bytes], Tuple[str, bytes]]], + ): + """Builds form parameters. + + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + for k, v in files.items(): + if isinstance(v, str): + with open(v, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + elif isinstance(v, bytes): + filename = k + filedata = v + elif isinstance(v, tuple): + filename, filedata = v + elif isinstance(v, list): + for file_param in v: + params.extend(self.files_parameters({k: file_param})) + continue + else: + raise ValueError("Unsupported file value") + mimetype = ( + mimetypes.guess_type(filename)[0] + or 'application/octet-stream' + ) + params.append( + tuple([k, tuple([filename, filedata, mimetype])]) + ) + return params + + def select_header_accept(self, _query_params: List[Tuple[str, str]], accepts: List[str]) -> Optional[str]: + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return None + + file_format = next((value for key, value in _query_params if key.lower() == 'fileformat'), None) + + format_to_accept = { + "csv": "application/csv", + "json": "application/json", + "xml": "application/xml", + "pdf": "application/pdf", + } + + if file_format and file_format.lower() in format_to_accept: + return format_to_accept[file_format.lower()] + + return accepts[0] + + def select_header_content_type(self, content_types): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return None + + for content_type in content_types: + if re.search('json', content_type, re.IGNORECASE): + return content_type + + return content_types[0] + + def update_params_for_auth( + self, + headers, + queries, + auth_settings, + resource_path, + method, + body, + request_auth=None + ) -> None: + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + :resource_path: A string representation of the HTTP request resource path. + :method: A string representation of the HTTP request method. + :body: A object representing the body of the HTTP request. + The object type is the return value of sanitize_for_serialization(). + :param request_auth: if set, the provided settings will + override the token in the configuration. + """ + if not auth_settings: + return + + if request_auth: + self._apply_auth_params( + headers, + queries, + resource_path, + method, + body, + request_auth + ) + else: + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + self._apply_auth_params( + headers, + queries, + resource_path, + method, + body, + auth_setting + ) + + def _apply_auth_params( + self, + headers, + queries, + resource_path, + method, + body, + auth_setting + ) -> None: + """Updates the request parameters based on a single auth_setting + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :resource_path: A string representation of the HTTP request resource path. + :method: A string representation of the HTTP request method. + :body: A object representing the body of the HTTP request. + The object type is the return value of sanitize_for_serialization(). + :param auth_setting: auth settings for the endpoint + """ + if auth_setting['in'] == 'cookie': + headers['Cookie'] = auth_setting['value'] + elif auth_setting['in'] == 'header': + if auth_setting['type'] != 'http-signature': + headers[auth_setting['key']] = auth_setting['value'] + elif auth_setting['in'] == 'query': + queries.append((auth_setting['key'], auth_setting['value'])) + else: + raise ApiValueError( + 'Authentication token must be in `query` or `header`' + ) + + def __deserialize_file(self, response): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + handle file downloading + save response body into a tmp file and return the instance + + :param response: RESTResponse. + :return: file path. + """ + fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + content_disposition = response.getheader("Content-Disposition") + if content_disposition: + m = re.search( + r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition + ) + assert m is not None, "Unexpected 'content-disposition' header value" + filename = m.group(1) + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + f.write(response.data) + + return path + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return str(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return an original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + return parse(string).date() + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason="Failed to parse `{0}` as date object".format(string) + ) + + def __deserialize_datetime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + return parse(string) + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as datetime object" + .format(string) + ) + ) + + def __deserialize_enum(self, data, klass): + """Deserializes primitive type to enum. + + :param data: primitive type. + :param klass: class literal. + :return: enum value. + """ + try: + return klass(data) + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as `{1}`" + .format(data, klass) + ) + ) + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + + return klass.from_dict(data) diff --git a/sailpoint/generic/api_response.py b/sailpoint/generic/api_response.py new file mode 100644 index 000000000..9bc7c11f6 --- /dev/null +++ b/sailpoint/generic/api_response.py @@ -0,0 +1,21 @@ +"""API response object.""" + +from __future__ import annotations +from typing import Optional, Generic, Mapping, TypeVar +from pydantic import Field, StrictInt, StrictBytes, BaseModel + +T = TypeVar("T") + +class ApiResponse(BaseModel, Generic[T]): + """ + API response object + """ + + status_code: StrictInt = Field(description="HTTP status code") + headers: Optional[Mapping[str, str]] = Field(None, description="HTTP headers") + data: T = Field(description="Deserialized data given the data type") + raw_data: StrictBytes = Field(description="Raw data (HTTP response body)") + + model_config = { + "arbitrary_types_allowed": True + } diff --git a/sailpoint/generic/configuration.py b/sailpoint/generic/configuration.py new file mode 100644 index 000000000..de00b0f8e --- /dev/null +++ b/sailpoint/generic/configuration.py @@ -0,0 +1,581 @@ +# coding: utf-8 + +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import copy +import http.client as httplib +import logging +from logging import FileHandler +import multiprocessing +import sys +from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict +from typing_extensions import NotRequired, Self + +import urllib3 + + +JSON_SCHEMA_VALIDATION_KEYWORDS = { + 'multipleOf', 'maximum', 'exclusiveMaximum', + 'minimum', 'exclusiveMinimum', 'maxLength', + 'minLength', 'pattern', 'maxItems', 'minItems' +} + +ServerVariablesT = Dict[str, str] + +GenericAuthSetting = TypedDict( + "GenericAuthSetting", + { + "type": str, + "in": str, + "key": str, + "value": str, + }, +) + + +OAuth2AuthSetting = TypedDict( + "OAuth2AuthSetting", + { + "type": Literal["oauth2"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": str, + }, +) + + +APIKeyAuthSetting = TypedDict( + "APIKeyAuthSetting", + { + "type": Literal["api_key"], + "in": str, + "key": str, + "value": Optional[str], + }, +) + + +BasicAuthSetting = TypedDict( + "BasicAuthSetting", + { + "type": Literal["basic"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": Optional[str], + }, +) + + +BearerFormatAuthSetting = TypedDict( + "BearerFormatAuthSetting", + { + "type": Literal["bearer"], + "in": Literal["header"], + "format": Literal["JWT"], + "key": Literal["Authorization"], + "value": str, + }, +) + + +BearerAuthSetting = TypedDict( + "BearerAuthSetting", + { + "type": Literal["bearer"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": str, + }, +) + + +HTTPSignatureAuthSetting = TypedDict( + "HTTPSignatureAuthSetting", + { + "type": Literal["http-signature"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": None, + }, +) + + +AuthSettings = TypedDict( + "AuthSettings", + { + }, + total=False, +) + + +class HostSettingVariable(TypedDict): + description: str + default_value: str + enum_values: List[str] + + +class HostSetting(TypedDict): + url: str + description: str + variables: NotRequired[Dict[str, HostSettingVariable]] + + +class Configuration: + """This class contains various settings of the API client. + + :param host: Base url. + :param ignore_operation_servers + Boolean to ignore operation servers for the API client. + Config will use `host` as the base url regardless of the operation servers. + :param api_key: Dict to store API key(s). + Each entry in the dict specifies an API key. + The dict key is the name of the security scheme in the OAS specification. + The dict value is the API key secret. + :param api_key_prefix: Dict to store API prefix (e.g. Bearer). + The dict key is the name of the security scheme in the OAS specification. + The dict value is an API key prefix when generating the auth data. + :param username: Username for HTTP basic authentication. + :param password: Password for HTTP basic authentication. + :param access_token: Access token. + :param server_index: Index to servers configuration. + :param server_variables: Mapping with string values to replace variables in + templated server configuration. The validation of enums is performed for + variables with defined enum values before. + :param server_operation_index: Mapping from operation ID to an index to server + configuration. + :param server_operation_variables: Mapping from operation ID to a mapping with + string values to replace variables in templated server configuration. + The validation of enums is performed for variables with defined enum + values before. + :param ssl_ca_cert: str - the path to a file of concatenated CA certificates + in PEM format. + :param retries: Number of retries for API requests. + + """ + + _default: ClassVar[Optional[Self]] = None + + def __init__( + self, + host: Optional[str]=None, + api_key: Optional[Dict[str, str]]=None, + api_key_prefix: Optional[Dict[str, str]]=None, + username: Optional[str]=None, + password: Optional[str]=None, + access_token: Optional[str]=None, + server_index: Optional[int]=None, + server_variables: Optional[ServerVariablesT]=None, + server_operation_index: Optional[Dict[int, int]]=None, + server_operation_variables: Optional[Dict[int, ServerVariablesT]]=None, + ignore_operation_servers: bool=False, + ssl_ca_cert: Optional[str]=None, + retries: Optional[int] = None, + *, + debug: Optional[bool] = None, + ) -> None: + """Constructor + """ + self._base_path = "https://sailpoint.api.identitynow.com" if host is None else host + """Default Base url + """ + self.server_index = 0 if server_index is None and host is None else server_index + self.server_operation_index = server_operation_index or {} + """Default server index + """ + self.server_variables = server_variables or {} + self.server_operation_variables = server_operation_variables or {} + """Default server variables + """ + self.ignore_operation_servers = ignore_operation_servers + """Ignore operation servers + """ + self.temp_folder_path = None + """Temp file folder for downloading files + """ + # Authentication Settings + self.api_key = {} + if api_key: + self.api_key = api_key + """dict to store API key(s) + """ + self.api_key_prefix = {} + if api_key_prefix: + self.api_key_prefix = api_key_prefix + """dict to store API prefix (e.g. Bearer) + """ + self.refresh_api_key_hook = None + """function hook to refresh API key if expired + """ + self.username = username + """Username for HTTP basic authentication + """ + self.password = password + """Password for HTTP basic authentication + """ + self.access_token = access_token + """Access token + """ + self.logger = {} + """Logging Settings + """ + self.logger["package_logger"] = logging.getLogger("sailpoint.generic") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + """Log format + """ + self.logger_stream_handler = None + """Log stream handler + """ + self.logger_file_handler: Optional[FileHandler] = None + """Log file handler + """ + self.logger_file = None + """Debug file location + """ + if debug is not None: + self.debug = debug + else: + self.__debug = False + """Debug switch + """ + + self.verify_ssl = True + """SSL/TLS verification + Set this to false to skip verifying SSL certificate when calling API + from https server. + """ + self.ssl_ca_cert = ssl_ca_cert + """Set this to customize the certificate file to verify the peer. + """ + self.cert_file = None + """client certificate file + """ + self.key_file = None + """client key file + """ + self.assert_hostname = None + """Set this to True/False to enable/disable SSL hostname verification. + """ + self.tls_server_name = None + """SSL/TLS Server Name Indication (SNI) + Set this to the SNI value expected by the server. + """ + + self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 + """urllib3 connection pool's maximum number of connections saved + per pool. urllib3 uses 1 connection as default value, but this is + not the best value when you are making a lot of possibly parallel + requests to the same host, which is often the case here. + cpu_count * 5 is used as default value to increase performance. + """ + + self.proxy: Optional[str] = None + """Proxy URL + """ + self.proxy_headers = None + """Proxy headers + """ + self.safe_chars_for_path_param = '' + """Safe chars for path_param + """ + self.retries = retries + """Adding retries to override urllib3 default value 3 + """ + # Enable client side validation + self.client_side_validation = True + + self.socket_options = None + """Options to pass down to the underlying urllib3 socket + """ + + self.datetime_format = "%Y-%m-%dT%H:%M:%S.%f%z" + """datetime format + """ + + self.date_format = "%Y-%m-%d" + """date format + """ + + def __deepcopy__(self, memo: Dict[int, Any]) -> Self: + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + for k, v in self.__dict__.items(): + if k not in ('logger', 'logger_file_handler'): + setattr(result, k, copy.deepcopy(v, memo)) + # shallow copy of loggers + result.logger = copy.copy(self.logger) + # use setters to configure loggers + result.logger_file = self.logger_file + result.debug = self.debug + return result + + def __setattr__(self, name: str, value: Any) -> None: + object.__setattr__(self, name, value) + + @classmethod + def set_default(cls, default: Optional[Self]) -> None: + """Set default instance of configuration. + + It stores default configuration, which can be + returned by get_default_copy method. + + :param default: object of Configuration + """ + cls._default = default + + @classmethod + def get_default_copy(cls) -> Self: + """Deprecated. Please use `get_default` instead. + + Deprecated. Please use `get_default` instead. + + :return: The configuration object. + """ + return cls.get_default() + + @classmethod + def get_default(cls) -> Self: + """Return the default configuration. + + This method returns newly created, based on default constructor, + object of Configuration class or returns a copy of default + configuration. + + :return: The configuration object. + """ + if cls._default is None: + cls._default = cls() + return cls._default + + @property + def logger_file(self) -> Optional[str]: + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value: Optional[str]) -> None: + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in self.logger.items(): + logger.addHandler(self.logger_file_handler) + + @property + def debug(self) -> bool: + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value: bool) -> None: + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in self.logger.items(): + logger.setLevel(logging.DEBUG) + # turn on httplib debug + httplib.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in self.logger.items(): + logger.setLevel(logging.WARNING) + # turn off httplib debug + httplib.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self) -> str: + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value: str) -> None: + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier: str, alias: Optional[str]=None) -> Optional[str]: + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :param alias: The alternative identifier of apiKey. + :return: The token for api key authentication. + """ + if self.refresh_api_key_hook is not None: + self.refresh_api_key_hook(self) + key = self.api_key.get(identifier, self.api_key.get(alias) if alias is not None else None) + if key: + prefix = self.api_key_prefix.get(identifier) + if prefix: + return "%s %s" % (prefix, key) + else: + return key + + return None + + def get_basic_auth_token(self) -> Optional[str]: + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + username = "" + if self.username is not None: + username = self.username + password = "" + if self.password is not None: + password = self.password + return urllib3.util.make_headers( + basic_auth=username + ':' + password + ).get('authorization') + + def auth_settings(self)-> AuthSettings: + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + auth: AuthSettings = {} + return auth + + def to_debug_report(self) -> str: + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: any (set in the path)\n"\ + "SDK Package Version: 1.0.0".\ + format(env=sys.platform, pyversion=sys.version) + + def get_host_settings(self) -> List[HostSetting]: + """Gets an array of host settings + + :return: An array of host settings + """ + return [ + { + 'url': "https://{tenant}.api.identitynow.com", + 'description': "This is the production API server.", + 'variables': { + 'tenant': { + 'description': "This is the name of your tenant, typically your company's name.", + 'default_value': "sailpoint", + } + } + }, + { + 'url': "https://{apiUrl}", + 'description': "This is the versioned API server.", + 'variables': { + 'apiUrl': { + 'description': "This is the api url of your tenant", + 'default_value': "sailpoint.api.identitynow.com", + } + } + } + ] + + def get_host_from_settings( + self, + index: Optional[int], + variables: Optional[ServerVariablesT]=None, + servers: Optional[List[HostSetting]]=None, + ) -> str: + """Gets host URL based on the index and variables + :param index: array index of the host settings + :param variables: hash of variable and the corresponding value + :param servers: an array of host settings or None + :return: URL based on host settings + """ + if index is None: + return self._base_path + + variables = {} if variables is None else variables + servers = self.get_host_settings() if servers is None else servers + + try: + server = servers[index] + except IndexError: + raise ValueError( + "Invalid index {0} when selecting the host settings. " + "Must be less than {1}".format(index, len(servers))) + + url = server['url'] + + # go through variables and replace placeholders + for variable_name, variable in server.get('variables', {}).items(): + used_value = variables.get( + variable_name, variable['default_value']) + + if 'enum_values' in variable \ + and used_value not in variable['enum_values']: + raise ValueError( + "The variable `{0}` in the host URL has invalid value " + "{1}. Must be {2}.".format( + variable_name, variables[variable_name], + variable['enum_values'])) + + url = url.replace("{" + variable_name + "}", used_value) + + return url + + @property + def host(self) -> str: + """Return generated host.""" + return self.get_host_from_settings(self.server_index, variables=self.server_variables) + + @host.setter + def host(self, value: str) -> None: + """Fix base path.""" + self._base_path = value + self.server_index = None diff --git a/sailpoint/generic/docs/Examples/python_code_examples_overlay.yaml b/sailpoint/generic/docs/Examples/python_code_examples_overlay.yaml new file mode 100644 index 000000000..f061cef36 --- /dev/null +++ b/sailpoint/generic/docs/Examples/python_code_examples_overlay.yaml @@ -0,0 +1,139 @@ +- path: /{path} + method: DELETE + xCodeSample: + - lang: Python + label: SDK_tools/sdk/python/generic/methods/generic#generic-delete + source: | + from sailpoint.generic.api.generic_api import GenericApi + from sailpoint.generic.api_client import ApiClient + from sailpoint.configuration import Configuration + configuration = Configuration() + + configuration.experimental = True + + with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + try: + # Generic DELETE request + + results = GenericApi(api_client).generic_delete(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_delete(path, x_sail_point_experimental) + print("The response of GenericApi->generic_delete:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_delete: %s\n" % e) +- path: /{path} + method: GET + xCodeSample: + - lang: Python + label: SDK_tools/sdk/python/generic/methods/generic#generic-get + source: | + from sailpoint.generic.api.generic_api import GenericApi + from sailpoint.generic.api_client import ApiClient + from sailpoint.configuration import Configuration + configuration = Configuration() + + configuration.experimental = True + + with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + limit = 250 # int | Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. (optional) # int | Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. (optional) + offset = 0 # int | Offset into the full result set. Usually specified with *limit* to paginate through the results. (optional) # int | Offset into the full result set. Usually specified with *limit* to paginate through the results. (optional) + count = true # bool | If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. (optional) # bool | If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. (optional) + filters = 'name eq \"Example\"' # str | Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). (optional) # str | Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). (optional) + sorters = 'name,-created' # str | Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). (optional) # str | Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). (optional) + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + try: + # Generic GET request + + results = GenericApi(api_client).generic_get(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_get(path, limit, offset, count, filters, sorters, x_sail_point_experimental) + print("The response of GenericApi->generic_get:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_get: %s\n" % e) +- path: /{path} + method: PATCH + xCodeSample: + - lang: Python + label: SDK_tools/sdk/python/generic/methods/generic#generic-patch + source: | + from sailpoint.generic.api.generic_api import GenericApi + from sailpoint.generic.api_client import ApiClient + from sailpoint.configuration import Configuration + configuration = Configuration() + + configuration.experimental = True + + with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + request_body = None # object | (optional) # object | (optional) + try: + # Generic PATCH request + + results = GenericApi(api_client).generic_patch(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_patch(path, x_sail_point_experimental, new_request_body) + print("The response of GenericApi->generic_patch:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_patch: %s\n" % e) +- path: /{path} + method: POST + xCodeSample: + - lang: Python + label: SDK_tools/sdk/python/generic/methods/generic#generic-post + source: | + from sailpoint.generic.api.generic_api import GenericApi + from sailpoint.generic.api_client import ApiClient + from sailpoint.configuration import Configuration + configuration = Configuration() + + configuration.experimental = True + + with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + request_body = None # object | (optional) # object | (optional) + try: + # Generic POST request + + results = GenericApi(api_client).generic_post(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_post(path, x_sail_point_experimental, new_request_body) + print("The response of GenericApi->generic_post:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_post: %s\n" % e) +- path: /{path} + method: PUT + xCodeSample: + - lang: Python + label: SDK_tools/sdk/python/generic/methods/generic#generic-put + source: | + from sailpoint.generic.api.generic_api import GenericApi + from sailpoint.generic.api_client import ApiClient + from sailpoint.configuration import Configuration + configuration = Configuration() + + configuration.experimental = True + + with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + request_body = None # object | (optional) # object | (optional) + try: + # Generic PUT request + + results = GenericApi(api_client).generic_put(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_put(path, x_sail_point_experimental, new_request_body) + print("The response of GenericApi->generic_put:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_put: %s\n" % e) + diff --git a/sailpoint/generic/docs/Methods/GenericApi.md b/sailpoint/generic/docs/Methods/GenericApi.md new file mode 100644 index 000000000..f866ab3a4 --- /dev/null +++ b/sailpoint/generic/docs/Methods/GenericApi.md @@ -0,0 +1,377 @@ +--- +id: generic +title: Generic +pagination_label: Generic +sidebar_label: Generic +sidebar_class_name: pythonsdk +keywords: ['python', 'Python', 'sdk', 'Generic', 'Generic'] +slug: /tools/sdk/python/generic/methods/generic +tags: ['SDK', 'Software Development Kit', 'Generic', 'Generic'] +--- + +# sailpoint.generic.GenericApi + Call any SailPoint Identity Security versioned API by its path. The path includes the API version, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`. +All URIs are relative to *https://sailpoint.api.identitynow.com* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**generic-delete**](#generic-delete) | **DELETE** `/{path}` | Generic DELETE request +[**generic-get**](#generic-get) | **GET** `/{path}` | Generic GET request +[**generic-patch**](#generic-patch) | **PATCH** `/{path}` | Generic PATCH request +[**generic-post**](#generic-post) | **POST** `/{path}` | Generic POST request +[**generic-put**](#generic-put) | **PUT** `/{path}` | Generic PUT request + + +## generic-delete +:::warning experimental +This API is currently in an experimental state. The API is subject to change based on feedback and further testing. You must include the X-SailPoint-Experimental header and set it to `true` to use this endpoint. +::: +:::tip setting x-sailpoint-experimental header + on the configuration object you can set the `x-sailpoint-experimental` header to `true' to enable all experimantl endpoints within the SDK. + Example: + ```python + configuration = Configuration() + configuration.experimental = True + ``` +::: +Generic DELETE request +Sends a DELETE request to any versioned API path. + +[API Spec](https://developer.sailpoint.com/docs/api/generic-delete) + +### Parameters + +Param Type | Name | Data Type | Required | Description +------------- | ------------- | ------------- | ------------- | ------------- +Path | path | **str** | True | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + | x_sail_point_experimental | **str** | (optional) | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + +### Return type +**object** + +### Responses +Code | Description | Data Type | Response headers | +------------- | ------------- | ------------- |------------------| +200 | Successful response. The body is the JSON returned by the API. | object | - | +204 | No content. | | - | + +### HTTP request headers + - **Content-Type**: Not defined + - **Accept**: application/json + +### Example + +```python +from sailpoint.generic.api.generic_api import GenericApi +from sailpoint.generic.api_client import ApiClient +from sailpoint.configuration import Configuration +configuration = Configuration() + +configuration.experimental = True + +with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + + try: + # Generic DELETE request + + results = GenericApi(api_client).generic_delete(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_delete(path, x_sail_point_experimental) + print("The response of GenericApi->generic_delete:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_delete: %s\n" % e) +``` + + + +[[Back to top]](#) + +## generic-get +:::warning experimental +This API is currently in an experimental state. The API is subject to change based on feedback and further testing. You must include the X-SailPoint-Experimental header and set it to `true` to use this endpoint. +::: +:::tip setting x-sailpoint-experimental header + on the configuration object you can set the `x-sailpoint-experimental` header to `true' to enable all experimantl endpoints within the SDK. + Example: + ```python + configuration = Configuration() + configuration.experimental = True + ``` +::: +Generic GET request +Sends a GET request to any versioned API path, for example `sources/v1/{id}/schemas` or `sources/v2/{sourceId}/provisioning-policies`. + +[API Spec](https://developer.sailpoint.com/docs/api/generic-get) + +### Parameters + +Param Type | Name | Data Type | Required | Description +------------- | ------------- | ------------- | ------------- | ------------- +Path | path | **str** | True | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + Query | limit | **int** | (optional) | Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. + Query | offset | **int** | (optional) | Offset into the full result set. Usually specified with *limit* to paginate through the results. + Query | count | **bool** | (optional) | If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. + Query | filters | **str** | (optional) | Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). + Query | sorters | **str** | (optional) | Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). + | x_sail_point_experimental | **str** | (optional) | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + +### Return type +**object** + +### Responses +Code | Description | Data Type | Response headers | +------------- | ------------- | ------------- |------------------| +200 | Successful response. The body is the JSON returned by the API. | object | - | +204 | No content. | | - | + +### HTTP request headers + - **Content-Type**: Not defined + - **Accept**: application/json + +### Example + +```python +from sailpoint.generic.api.generic_api import GenericApi +from sailpoint.generic.api_client import ApiClient +from sailpoint.configuration import Configuration +configuration = Configuration() + +configuration.experimental = True + +with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + limit = 250 # int | Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. (optional) # int | Max number of results to return. See [Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. (optional) + offset = 0 # int | Offset into the full result set. Usually specified with *limit* to paginate through the results. (optional) # int | Offset into the full result set. Usually specified with *limit* to paginate through the results. (optional) + count = true # bool | If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. (optional) # bool | If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. (optional) + filters = 'name eq \"Example\"' # str | Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). (optional) # str | Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). (optional) + sorters = 'name,-created' # str | Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). (optional) # str | Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). (optional) + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + + try: + # Generic GET request + + results = GenericApi(api_client).generic_get(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_get(path, limit, offset, count, filters, sorters, x_sail_point_experimental) + print("The response of GenericApi->generic_get:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_get: %s\n" % e) +``` + + + +[[Back to top]](#) + +## generic-patch +:::warning experimental +This API is currently in an experimental state. The API is subject to change based on feedback and further testing. You must include the X-SailPoint-Experimental header and set it to `true` to use this endpoint. +::: +:::tip setting x-sailpoint-experimental header + on the configuration object you can set the `x-sailpoint-experimental` header to `true' to enable all experimantl endpoints within the SDK. + Example: + ```python + configuration = Configuration() + configuration.experimental = True + ``` +::: +Generic PATCH request +Sends a PATCH request to any versioned API path. The body is a JSON Patch document (RFC 6902): a list of operations such as `[{"op": "replace", "path": "/description", "value": "New description"}]`. + +[API Spec](https://developer.sailpoint.com/docs/api/generic-patch) + +### Parameters + +Param Type | Name | Data Type | Required | Description +------------- | ------------- | ------------- | ------------- | ------------- +Path | path | **str** | True | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + | x_sail_point_experimental | **str** | (optional) | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + Body | request_body | **object** | (optional) | + +### Return type +**object** + +### Responses +Code | Description | Data Type | Response headers | +------------- | ------------- | ------------- |------------------| +200 | Successful response. The body is the JSON returned by the API. | object | - | +204 | No content. | | - | + +### HTTP request headers + - **Content-Type**: application/json-patch+json + - **Accept**: application/json + +### Example + +```python +from sailpoint.generic.api.generic_api import GenericApi +from sailpoint.generic.api_client import ApiClient +from sailpoint.configuration import Configuration +configuration = Configuration() + +configuration.experimental = True + +with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + request_body = None # object | (optional) # object | (optional) + + try: + # Generic PATCH request + + results = GenericApi(api_client).generic_patch(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_patch(path, x_sail_point_experimental, new_request_body) + print("The response of GenericApi->generic_patch:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_patch: %s\n" % e) +``` + + + +[[Back to top]](#) + +## generic-post +:::warning experimental +This API is currently in an experimental state. The API is subject to change based on feedback and further testing. You must include the X-SailPoint-Experimental header and set it to `true` to use this endpoint. +::: +:::tip setting x-sailpoint-experimental header + on the configuration object you can set the `x-sailpoint-experimental` header to `true' to enable all experimantl endpoints within the SDK. + Example: + ```python + configuration = Configuration() + configuration.experimental = True + ``` +::: +Generic POST request +Sends a POST request with a JSON body to any versioned API path. + +[API Spec](https://developer.sailpoint.com/docs/api/generic-post) + +### Parameters + +Param Type | Name | Data Type | Required | Description +------------- | ------------- | ------------- | ------------- | ------------- +Path | path | **str** | True | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + | x_sail_point_experimental | **str** | (optional) | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + Body | request_body | **object** | (optional) | + +### Return type +**object** + +### Responses +Code | Description | Data Type | Response headers | +------------- | ------------- | ------------- |------------------| +200 | Successful response. The body is the JSON returned by the API. | object | - | +204 | No content. | | - | + +### HTTP request headers + - **Content-Type**: application/json + - **Accept**: application/json + +### Example + +```python +from sailpoint.generic.api.generic_api import GenericApi +from sailpoint.generic.api_client import ApiClient +from sailpoint.configuration import Configuration +configuration = Configuration() + +configuration.experimental = True + +with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + request_body = None # object | (optional) # object | (optional) + + try: + # Generic POST request + + results = GenericApi(api_client).generic_post(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_post(path, x_sail_point_experimental, new_request_body) + print("The response of GenericApi->generic_post:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_post: %s\n" % e) +``` + + + +[[Back to top]](#) + +## generic-put +:::warning experimental +This API is currently in an experimental state. The API is subject to change based on feedback and further testing. You must include the X-SailPoint-Experimental header and set it to `true` to use this endpoint. +::: +:::tip setting x-sailpoint-experimental header + on the configuration object you can set the `x-sailpoint-experimental` header to `true' to enable all experimantl endpoints within the SDK. + Example: + ```python + configuration = Configuration() + configuration.experimental = True + ``` +::: +Generic PUT request +Sends a PUT request with a JSON body to any versioned API path. + +[API Spec](https://developer.sailpoint.com/docs/api/generic-put) + +### Parameters + +Param Type | Name | Data Type | Required | Description +------------- | ------------- | ------------- | ------------- | ------------- +Path | path | **str** | True | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + | x_sail_point_experimental | **str** | (optional) | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. + Body | request_body | **object** | (optional) | + +### Return type +**object** + +### Responses +Code | Description | Data Type | Response headers | +------------- | ------------- | ------------- |------------------| +200 | Successful response. The body is the JSON returned by the API. | object | - | +204 | No content. | | - | + +### HTTP request headers + - **Content-Type**: application/json + - **Accept**: application/json + +### Example + +```python +from sailpoint.generic.api.generic_api import GenericApi +from sailpoint.generic.api_client import ApiClient +from sailpoint.configuration import Configuration +configuration = Configuration() + +configuration.experimental = True + +with ApiClient(configuration) as api_client: + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + request_body = None # object | (optional) # object | (optional) + + try: + # Generic PUT request + + results = GenericApi(api_client).generic_put(path=path) + # Below is a request that includes all optional parameters + # results = GenericApi(api_client).generic_put(path, x_sail_point_experimental, new_request_body) + print("The response of GenericApi->generic_put:\n") + print(results.model_dump_json(by_alias=True, indent=4)) + except Exception as e: + print("Exception when calling GenericApi->generic_put: %s\n" % e) +``` + + + +[[Back to top]](#) + + + diff --git a/sailpoint/generic/docs/Methods/Index.md b/sailpoint/generic/docs/Methods/Index.md new file mode 100644 index 000000000..0df119fca --- /dev/null +++ b/sailpoint/generic/docs/Methods/Index.md @@ -0,0 +1,30 @@ +--- +id: generic-methods +title: Methods +pagination_label: Methods +sidebar_label: Methods +sidebar_key: generic-python-methods +sidebar_position: 3 +sidebar_class_name: methods +keywords: ['python', 'Python', 'sdk', 'generic', 'methods'] +slug: /tools/sdk/python/generic/methods +tags: ['SDK', 'Software Development Kit', 'generic', 'methods'] +--- + +Method documents provide detailed information about each API operation (or method). They describe what the method does and details its input parameters, expected return values, and any considerations to be aware of when using it. +## Key Features +- Purpose & Overview: Explains the purpose of the method and its role in the API. +- Parameters: Describe the required input parameters, including their data types. +- Response Format: Details the expected return format or structure. +- Error Scenarios: Outline potential errors or issues that may arise during method execution. +- Example: Provides a sample of how the API uses the method. + +## Available Methods +This is a list of the core methods available in the Python SDK: + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + + +``` \ No newline at end of file diff --git a/sailpoint/generic/docs/Models/Index.md b/sailpoint/generic/docs/Models/Index.md new file mode 100644 index 000000000..c0edd8e0b --- /dev/null +++ b/sailpoint/generic/docs/Models/Index.md @@ -0,0 +1,19 @@ +--- +id: generic-models +title: Models +pagination_label: Models +sidebar_label: Models +sidebar_key: generic-python-models +sidebar_position: 3 +sidebar_class_name: models +keywords: ['python', 'Python', 'sdk', 'generic', 'models'] +slug: /tools/sdk/python/generic/models +tags: ['SDK', 'Software Development Kit', 'generic', 'models'] +--- + +The Python SDK uses data models to structure and manage data within the API. These models provide essential details about the data, including their attributes, data types, and how the models relate to each other. Understanding these models is crucial to effectively interact with the API. + +## Key Features +- Attributes: Describe each attribute, including its name, data type, and whether it's required. +- Validation & Constraints: Highlight any rules or limitations for the attributes, such as format or length limits. +- Example: Provides a sample of how the API uses the model. \ No newline at end of file diff --git a/sailpoint/generic/exceptions.py b/sailpoint/generic/exceptions.py new file mode 100644 index 000000000..7e3b6506e --- /dev/null +++ b/sailpoint/generic/exceptions.py @@ -0,0 +1,216 @@ +# coding: utf-8 + +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +from typing import Any, Optional +from typing_extensions import Self + +class OpenApiException(Exception): + """The base exception class for all OpenAPIExceptions""" + + +class ApiTypeError(OpenApiException, TypeError): + def __init__(self, msg, path_to_item=None, valid_classes=None, + key_type=None) -> None: + """ Raises an exception for TypeErrors + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list): a list of keys an indices to get to the + current_item + None if unset + valid_classes (tuple): the primitive classes that current item + should be an instance of + None if unset + key_type (bool): False if our value is a value in a dict + True if it is a key in a dict + False if our item is an item in a list + None if unset + """ + self.path_to_item = path_to_item + self.valid_classes = valid_classes + self.key_type = key_type + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiTypeError, self).__init__(full_msg) + + +class ApiValueError(OpenApiException, ValueError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list) the path to the exception in the + received_data dict. None if unset + """ + + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiValueError, self).__init__(full_msg) + + +class ApiAttributeError(OpenApiException, AttributeError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Raised when an attribute reference or assignment fails. + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiAttributeError, self).__init__(full_msg) + + +class ApiKeyError(OpenApiException, KeyError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiKeyError, self).__init__(full_msg) + + +class ApiException(OpenApiException): + + def __init__( + self, + status=None, + reason=None, + http_resp=None, + *, + body: Optional[str] = None, + data: Optional[Any] = None, + ) -> None: + self.status = status + self.reason = reason + self.body = body + self.data = data + self.headers = None + + if http_resp: + if self.status is None: + self.status = http_resp.status + if self.reason is None: + self.reason = http_resp.reason + if self.body is None: + try: + self.body = http_resp.data.decode('utf-8') + except Exception: + pass + self.headers = http_resp.getheaders() + + @classmethod + def from_response( + cls, + *, + http_resp, + body: Optional[str], + data: Optional[Any], + ) -> Self: + if http_resp.status == 400: + raise BadRequestException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 401: + raise UnauthorizedException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 403: + raise ForbiddenException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 404: + raise NotFoundException(http_resp=http_resp, body=body, data=data) + + # Added new conditions for 409 and 422 + if http_resp.status == 409: + raise ConflictException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 422: + raise UnprocessableEntityException(http_resp=http_resp, body=body, data=data) + + if 500 <= http_resp.status <= 599: + raise ServiceException(http_resp=http_resp, body=body, data=data) + raise ApiException(http_resp=http_resp, body=body, data=data) + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\n"\ + "Reason: {1}\n".format(self.status, self.body) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.data or self.body: + error_message += "HTTP response body: {0}\n".format(self.data or self.body) + + return error_message + + +class BadRequestException(ApiException): + pass + + +class NotFoundException(ApiException): + pass + + +class UnauthorizedException(ApiException): + pass + + +class ForbiddenException(ApiException): + pass + + +class ServiceException(ApiException): + pass + + +class ConflictException(ApiException): + """Exception for HTTP 409 Conflict.""" + pass + + +class UnprocessableEntityException(ApiException): + """Exception for HTTP 422 Unprocessable Entity.""" + pass + + +def render_path(path_to_item): + """Returns a string representation of a path""" + result = "" + for pth in path_to_item: + if isinstance(pth, int): + result += "[{0}]".format(pth) + else: + result += "['{0}']".format(pth) + return result diff --git a/sailpoint/generic/models/__init__.py b/sailpoint/generic/models/__init__.py new file mode 100644 index 000000000..6de4db067 --- /dev/null +++ b/sailpoint/generic/models/__init__.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +# flake8: noqa +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +# import models into model package diff --git a/sailpoint/generic/rest.py b/sailpoint/generic/rest.py new file mode 100644 index 000000000..a3d9882b1 --- /dev/null +++ b/sailpoint/generic/rest.py @@ -0,0 +1,257 @@ +# coding: utf-8 + +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import io +import json +import re +import ssl + +import urllib3 + +from sailpoint.generic.exceptions import ApiException, ApiValueError + +SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"} +RESTResponseType = urllib3.HTTPResponse + + +def is_socks_proxy_url(url): + if url is None: + return False + split_section = url.split("://") + if len(split_section) < 2: + return False + else: + return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES + + +class RESTResponse(io.IOBase): + + def __init__(self, resp) -> None: + self.response = resp + self.status = resp.status + self.reason = resp.reason + self.data = None + + def read(self): + if self.data is None: + self.data = self.response.data + return self.data + + def getheaders(self): + """Returns a dictionary of the response headers.""" + return self.response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.response.headers.get(name, default) + + +class RESTClientObject: + + def __init__(self, configuration) -> None: + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + pool_args = { + "cert_reqs": cert_reqs, + "ca_certs": configuration.ssl_ca_cert, + "cert_file": configuration.cert_file, + "key_file": configuration.key_file, + } + if configuration.assert_hostname is not None: + pool_args['assert_hostname'] = ( + configuration.assert_hostname + ) + + if configuration.retries is not None: + pool_args['retries'] = configuration.retries + + if configuration.tls_server_name: + pool_args['server_hostname'] = configuration.tls_server_name + + + if configuration.socket_options is not None: + pool_args['socket_options'] = configuration.socket_options + + if configuration.connection_pool_maxsize is not None: + pool_args['maxsize'] = configuration.connection_pool_maxsize + + # https pool manager + self.pool_manager: urllib3.PoolManager + + if configuration.proxy: + if is_socks_proxy_url(configuration.proxy): + from urllib3.contrib.socks import SOCKSProxyManager + pool_args["proxy_url"] = configuration.proxy + pool_args["headers"] = configuration.proxy_headers + self.pool_manager = SOCKSProxyManager(**pool_args) + else: + pool_args["proxy_url"] = configuration.proxy + pool_args["proxy_headers"] = configuration.proxy_headers + self.pool_manager = urllib3.ProxyManager(**pool_args) + else: + self.pool_manager = urllib3.PoolManager(**pool_args) + + def request( + self, + method, + url, + headers=None, + body=None, + post_params=None, + _request_timeout=None + ): + """Perform requests. + + :param method: http request method + :param url: http request url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in [ + 'GET', + 'HEAD', + 'DELETE', + 'POST', + 'PUT', + 'PATCH', + 'OPTIONS' + ] + + if post_params and body: + raise ApiValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + + timeout = None + if _request_timeout: + if isinstance(_request_timeout, (int, float)): + timeout = urllib3.Timeout(total=_request_timeout) + elif ( + isinstance(_request_timeout, tuple) + and len(_request_timeout) == 2 + ): + timeout = urllib3.Timeout( + connect=_request_timeout[0], + read=_request_timeout[1] + ) + + try: + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + + # no content type provided or payload is json + content_type = headers.get('Content-Type') + if ( + not content_type + or re.search('json', content_type, re.IGNORECASE) + ): + request_body = None + if body is not None: + request_body = json.dumps(body) + r = self.pool_manager.request( + method, + url, + body=request_body, + timeout=timeout, + headers=headers, + preload_content=False + ) + elif content_type == 'application/x-www-form-urlencoded': + r = self.pool_manager.request( + method, + url, + fields=post_params, + encode_multipart=False, + timeout=timeout, + headers=headers, + preload_content=False + ) + elif content_type == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + # Ensures that dict objects are serialized + post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params] + r = self.pool_manager.request( + method, + url, + fields=post_params, + encode_multipart=True, + timeout=timeout, + headers=headers, + preload_content=False + ) + # Pass a `string` parameter directly in the body to support + # other content types than JSON when `body` argument is + # provided in serialized form. + elif isinstance(body, str) or isinstance(body, bytes): + r = self.pool_manager.request( + method, + url, + body=body, + timeout=timeout, + headers=headers, + preload_content=False + ) + elif headers['Content-Type'].startswith('text/') and isinstance(body, bool): + request_body = "true" if body else "false" + r = self.pool_manager.request( + method, + url, + body=request_body, + preload_content=False, + timeout=timeout, + headers=headers) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + # For `GET`, `HEAD` + else: + r = self.pool_manager.request( + method, + url, + fields={}, + timeout=timeout, + headers=headers, + preload_content=False + ) + except urllib3.exceptions.SSLError as e: + msg = "\n".join([type(e).__name__, str(e)]) + raise ApiException(status=0, reason=msg) + + return RESTResponse(r) diff --git a/sailpoint/generic/test/__init__.py b/sailpoint/generic/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sailpoint/generic/test/test_generic_api.py b/sailpoint/generic/test/test_generic_api.py new file mode 100644 index 000000000..f2910ea76 --- /dev/null +++ b/sailpoint/generic/test/test_generic_api.py @@ -0,0 +1,66 @@ +# coding: utf-8 + +""" + SailPoint Identity Security API - Generic + + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + + The version of the OpenAPI document: any (set in the path) + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest + +from sailpoint.generic.api.generic_api import GenericApi + + +class TestGenericApi(unittest.TestCase): + """GenericApi unit test stubs""" + + def setUp(self) -> None: + self.api = GenericApi() + + def tearDown(self) -> None: + pass + + def test_generic_delete(self) -> None: + """Test case for generic_delete + + Generic DELETE request + """ + pass + + def test_generic_get(self) -> None: + """Test case for generic_get + + Generic GET request + """ + pass + + def test_generic_patch(self) -> None: + """Test case for generic_patch + + Generic PATCH request + """ + pass + + def test_generic_post(self) -> None: + """Test case for generic_post + + Generic POST request + """ + pass + + def test_generic_put(self) -> None: + """Test case for generic_put + + Generic PUT request + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/sailpoint/generic_README.md b/sailpoint/generic_README.md new file mode 100644 index 000000000..280c9ab6d --- /dev/null +++ b/sailpoint/generic_README.md @@ -0,0 +1,90 @@ +# sailpoint.generic +Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do not have a resource-specific client or documentation yet. The version is part of the path, so this client is not tied to one API version and the base URL has no version segment. + +The `sailpoint.generic` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: + +- API version: any (set in the path) +- Package version: 1.0.0 +- Generator version: 7.12.0 +- Build package: org.openapitools.codegen.languages.PythonClientCodegen +For more information, please visit [https://developer.sailpoint.com/discuss/api-help](https://developer.sailpoint.com/discuss/api-help) + +## Requirements. + +Python 3.8+ + +## Installation & Usage + +This python library package is generated without supporting files like setup.py or requirements files + +To be able to use it, you will need these dependencies in your own package that uses this library: + +* urllib3 >= 1.25.3, < 3.0.0 +* python-dateutil >= 2.8.2 +* pydantic >= 2 +* typing-extensions >= 4.7.1 + +## Getting Started + +In your own code, to use this library to connect and interact with sailpoint.generic, +you can run the following: + +```python + +import sailpoint.generic +from sailpoint.generic.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to https://sailpoint.api.identitynow.com +# See configuration.py for a list of all supported configuration parameters. +configuration = sailpoint.generic.Configuration( + host = "https://sailpoint.api.identitynow.com" +) + + + +# Enter a context with an instance of the API client +with sailpoint.generic.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = sailpoint.generic.GenericApi(api_client) + path = 'accounts/v1' # str | API path relative to the tenant base URL, including the API version, for example `accounts/v1`, `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query parameters, for example `accounts/v1?detailLevel=SLIM`. + x_sail_point_experimental = 'true' # str | Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental APIs. (optional) + + try: + # Generic DELETE request + api_response = api_instance.generic_delete(path, x_sail_point_experimental=x_sail_point_experimental) + print("The response of GenericApi->generic_delete:\n") + pprint(api_response) + except ApiException as e: + print("Exception when calling GenericApi->generic_delete: %s\n" % e) + +``` + +## Documentation for API Endpoints + +All URIs are relative to *https://sailpoint.api.identitynow.com* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*GenericApi* | [**generic_delete**](sailpoint/generic/docs/GenericApi.md#generic_delete) | **DELETE** /{path} | Generic DELETE request +*GenericApi* | [**generic_get**](sailpoint/generic/docs/GenericApi.md#generic_get) | **GET** /{path} | Generic GET request +*GenericApi* | [**generic_patch**](sailpoint/generic/docs/GenericApi.md#generic_patch) | **PATCH** /{path} | Generic PATCH request +*GenericApi* | [**generic_post**](sailpoint/generic/docs/GenericApi.md#generic_post) | **POST** /{path} | Generic POST request +*GenericApi* | [**generic_put**](sailpoint/generic/docs/GenericApi.md#generic_put) | **PUT** /{path} | Generic PUT request + + +## Documentation For Models + + + + +## Documentation For Authorization + +Endpoints do not require authorization. + + +## Author + + + + diff --git a/sdk-resources/build-versioned-sdk.js b/sdk-resources/build-versioned-sdk.js index fae27b1cd..4a47254b5 100644 --- a/sdk-resources/build-versioned-sdk.js +++ b/sdk-resources/build-versioned-sdk.js @@ -58,17 +58,26 @@ const PACKAGE_NAME_PREFIX = "sailpoint"; const PACKAGE_VERSION = "1.0.0"; const API_VERSION = "v1"; +// The generic API is built from a standalone spec in sdk-resources/, not from a +// partition under apis/. Its package has an api/ directory like a partition, so +// generateInitPy() exports GenericApi from the root package, and stale cleanup +// leaves it alone because it is never one of this run's partitions. +const GENERIC_SPEC = path.join(__dirname, "generic-api.yaml"); +const GENERIC_DIR = "generic"; + // --------------------------------------------------------------------------- // CLI args // --------------------------------------------------------------------------- const args = process.argv.slice(2); -if (args.length === 0 || args[0].startsWith("--")) { +const genericOnly = args.includes("--generic-only"); +if (!genericOnly && (args.length === 0 || args[0].startsWith("--"))) { console.error("Usage: node sdk-resources/build-versioned-sdk.js [--partition ] [--keep-tmp]"); + console.error(" node sdk-resources/build-versioned-sdk.js --generic-only"); process.exit(1); } -const apisDir = path.resolve(args[0]); +const apisDir = genericOnly ? null : path.resolve(args[0]); const keepTmp = args.includes("--keep-tmp"); const partitionIdx = args.indexOf("--partition"); const onlyPartition = partitionIdx !== -1 ? args[partitionIdx + 1] : null; @@ -393,7 +402,9 @@ function normalizeSchemaNames(bundledJsonPath, idnRoot) { // Generate per-partition config YAML (Python-specific) // --------------------------------------------------------------------------- -function writePartitionConfig(partitionName) { +// apiVersion labels the docs and builds the NERM URL. The generic API passes +// null, because it is not tied to one API version: the version is in the path. +function writePartitionConfig(partitionName, apiVersion = API_VERSION) { const packageDir = partitionName.replaceAll("-", "_"); const packageName = `${PACKAGE_NAME_PREFIX}.${packageDir}`; @@ -401,7 +412,7 @@ function writePartitionConfig(partitionName) { `templateDir: ${TEMPLATE_DIR}`, `packageName: ${packageName}`, `packageVersion: ${PACKAGE_VERSION}`, - `apiVersion: ${API_VERSION}`, + ...(apiVersion ? [`apiVersion: ${apiVersion}`] : []), `generateSourceCodeOnly: true`, `subModuleName: ${partitionName}`, `files:`, @@ -563,6 +574,105 @@ function runPostscript(outputDir) { }; } +// --------------------------------------------------------------------------- +// Generic API package (sailpoint/generic) +// +// The generic API calls any versioned endpoint by path, including endpoints +// that have no resource-specific client yet. It uses the same templates and +// generator options as the partitions, so it shares their configuration, auth, +// retry and experimental handling. The spec has no version segment in its +// server URL, because the version is part of the path (for example "accounts/v1"). +// --------------------------------------------------------------------------- + +// The generator substitutes the `path` parameter with quote(..., safe=""), which +// turns the slashes of a multi-segment path ("accounts/v1/1234") into %2F. This +// helper builds the resource path itself, and sends anything after a "?" as query +// parameters, so callers can pass any query parameter. +const GENERIC_PATH_HELPER = ` + +def _generic_resource_path(path: str) -> Tuple[str, List[Tuple[str, str]]]: + """Split the \`\`path\`\` of a generic request into a resource path and query parameters. + + \`\`path\`\` is everything after the tenant base URL, including the API version, + for example \`\`accounts/v1\`\` or \`\`sources/v1/2c918085/schemas\`\`. Leading and + trailing slashes are optional. Each segment is URL-encoded on its own, so the + slashes between segments are kept. Anything after a \`\`?\`\` is returned as query + parameters, for example \`\`accounts/v1?detailLevel=SLIM\`\`. + """ + path_part, _, query = str(path).partition('?') + segments = [quote(segment, safe='') for segment in path_part.split('/') if segment] + return '/' + '/'.join(segments), parse_qsl(query, keep_blank_values=True) +`; + +function patchGenericPathEncoding(outputDir) { + const apiPath = path.join(outputDir, "api", "generic_api.py"); + let content = readFileSafe(apiPath); + + const pathParam = / if path is not None:\n _path_params\['path'\] = path\n/g; + const found = (content.match(pathParam) || []).length; + const resourcePaths = content.split("resource_path='/{path}',").length - 1; + if (found === 0 || found !== resourcePaths) { + throw new Error(`expected matching path parameter and resource_path lines in generic_api.py, found ${found} and ${resourcePaths}`); + } + content = content + .replace(pathParam, [ + " _resource_path, _path_query = _generic_resource_path(path)", + " _query_params.extend(_path_query)", + "", + ].join("\n")) + .replaceAll("resource_path='/{path}',", "resource_path=_resource_path,"); + + const importAnchor = "from sailpoint.generic.rest import RESTResponseType\n"; + if (!content.includes(importAnchor)) { + throw new Error("rest import anchor not found in generic_api.py"); + } + content = content.replace(importAnchor, importAnchor + "from urllib.parse import parse_qsl, quote\n" + GENERIC_PATH_HELPER); + + fs.writeFileSync(apiPath, content, "utf8"); + return { patched: found }; +} + +function buildGenericPackage() { + if (!fs.existsSync(GENERIC_SPEC)) { + return { ok: false, step: "setup", output: `generic spec not found at ${GENERIC_SPEC}` }; + } + fs.mkdirSync(TEMP_DIR, { recursive: true }); + + // Every generator run rewrites the root .openapi-generator/FILES. Keep the one + // the partitions wrote, so a generic-only build does not change it. + const filesManifest = path.join(SDK_ROOT, ".openapi-generator", "FILES"); + const savedManifest = fs.existsSync(filesManifest) ? fs.readFileSync(filesManifest) : null; + + const gen = generatePartition(GENERIC_DIR, GENERIC_SPEC, writePartitionConfig(GENERIC_DIR, null)); + if (savedManifest !== null) fs.writeFileSync(filesManifest, savedManifest); + if (!gen.ok) { + return { ok: false, step: "generation", output: [gen.stdout, gen.stderr].filter(Boolean).join("\n") }; + } + + const post = runPostscript(gen.outputDir); + if (!post.ok) { + return { ok: false, step: "postscript", output: [post.stdout, post.stderr].filter(Boolean).join("\n") }; + } + + try { + const { patched } = patchGenericPathEncoding(gen.outputDir); + return { ok: true, outputDir: gen.outputDir, patched }; + } catch (err) { + return { ok: false, step: "path-encoding", output: String(err.stack || err) }; + } +} + +// Print the outcome of buildGenericPackage() and return whether it succeeded. +function reportGenericBuild(built) { + if (built.ok) { + console.log(` ✓ generic → sailpoint/${GENERIC_DIR}/ (${built.patched} path encoder(s) patched)`); + return true; + } + console.error(` ✗ generic API failed at ${built.step}`); + console.error(built.output); + return false; +} + // --------------------------------------------------------------------------- // Error logging // --------------------------------------------------------------------------- @@ -1001,6 +1111,20 @@ else: // --------------------------------------------------------------------------- function main() { + if (genericOnly) { + if (!fs.existsSync(JAR)) { + console.error(`Error: openapi-generator-cli.jar not found at ${JAR}`); + process.exit(1); + } + console.log("\n[GENERIC] Building the generic API package ..."); + const ok = reportGenericBuild(buildGenericPackage()); + if (!keepTmp) fs.rmSync(TEMP_DIR, { recursive: true, force: true }); + console.log("\n[INDEX] Regenerating sailpoint/__init__.py ..."); + generateInitPy(); + if (!ok) process.exit(1); + return; + } + if (!fs.existsSync(apisDir)) { console.error(`Error: apis directory not found: ${apisDir}`); process.exit(1); @@ -1156,6 +1280,21 @@ function main() { console.log(` ✓ ${partition} → sailpoint/${gen.packageDir}/`); } + // Build the generic API package (independent of the apis/ partitions). + // A single-partition rebuild leaves it as it is. + if (!onlyPartition) { + console.log(`\n${"=".repeat(60)}`); + console.log(" Building: generic API"); + console.log(`${"=".repeat(60)}`); + results.total += 1; + if (reportGenericBuild(buildGenericPackage())) { + results.success.push("generic"); + } else { + // The generic spec is not under apis/, so there is no partition report to write. + results.failed.push({ partition: "generic", step: "generic", reportPath: GENERIC_SPEC }); + } + } + // Cleanup if (!keepTmp) { console.log("\n[CLEANUP] Removing .sdk-build-tmp/ ..."); diff --git a/sdk-resources/generic-api.yaml b/sdk-resources/generic-api.yaml new file mode 100644 index 000000000..ae5ac7d30 --- /dev/null +++ b/sdk-resources/generic-api.yaml @@ -0,0 +1,223 @@ +openapi: 3.0.1 +info: + title: SailPoint Identity Security API - Generic + description: >- + Generic client for the SailPoint Identity Security versioned APIs. Use it to call any endpoint, in any version, by + its path, for example `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`, including endpoints that do + not have a resource-specific client or documentation yet. The version is part of the path, so this client is not + tied to one API version and the base URL has no version segment. + termsOfService: https://developer.sailpoint.com/discuss/tos + contact: + name: Developer Relations + url: https://developer.sailpoint.com/discuss/api-help + license: + name: MIT + url: https://opensource.org/licenses/MIT + version: any (set in the path) +servers: + - url: https://{tenant}.api.identitynow.com + description: This is the production API server. + variables: + tenant: + default: sailpoint + description: This is the name of your tenant, typically your company's name. + - url: https://{apiUrl} + description: This is the versioned API server. + variables: + apiUrl: + default: sailpoint.api.identitynow.com + description: This is the api url of your tenant + +# Same security block as the partition specs, which declare no securitySchemes: +# the SDK configuration adds the bearer token to every request, so the generic +# client authenticates exactly like the resource-specific clients. +security: + - userAuth: + - "sp:scopes:all" + +tags: + - name: Generic + description: >- + Call any SailPoint Identity Security versioned API by its path. The path includes the API version, for example + `sources/v1` or `sources/v2/{sourceId}/provisioning-policies`. + +paths: + /{path}: + get: + tags: + - Generic + summary: Generic GET request + description: >- + Sends a GET request to any versioned API path, for example `sources/v1/{id}/schemas` or + `sources/v2/{sourceId}/provisioning-policies`. + operationId: genericGet + parameters: + - $ref: '#/components/parameters/path' + - name: limit + in: query + description: >- + Max number of results to return. See [Standard Collection + Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters) for more information. + required: false + example: 250 + schema: + type: integer + format: int32 + minimum: 0 + - name: offset + in: query + description: >- + Offset into the full result set. Usually specified with *limit* to paginate through the results. + required: false + example: 0 + schema: + type: integer + format: int32 + minimum: 0 + - name: count + in: query + description: >- + If *true* it will populate the *X-Total-Count* response header with the number of results that would be + returned if *limit* and *offset* were ignored. + required: false + example: true + schema: + type: boolean + - name: filters + in: query + description: >- + Filter results using the standard syntax described in [V3 API Standard Collection + Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#filtering-results). + required: false + example: name eq "Example" + schema: + type: string + - name: sorters + in: query + description: >- + Sort results using the standard syntax described in [V3 API Standard Collection + Parameters](https://developer.sailpoint.com/docs/standard-collection-parameters#sorting-results). + required: false + example: name,-created + schema: + type: string + - $ref: '#/components/parameters/experimental' + responses: + '200': + $ref: '#/components/responses/GenericResponse' + '204': + description: No content. + post: + tags: + - Generic + summary: Generic POST request + description: Sends a POST request with a JSON body to any versioned API path. + operationId: genericPost + x-codegen-request-body-name: requestBody + parameters: + - $ref: '#/components/parameters/path' + - $ref: '#/components/parameters/experimental' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GenericRequest' + responses: + '200': + $ref: '#/components/responses/GenericResponse' + '204': + description: No content. + put: + tags: + - Generic + summary: Generic PUT request + description: Sends a PUT request with a JSON body to any versioned API path. + operationId: genericPut + x-codegen-request-body-name: requestBody + parameters: + - $ref: '#/components/parameters/path' + - $ref: '#/components/parameters/experimental' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GenericRequest' + responses: + '200': + $ref: '#/components/responses/GenericResponse' + '204': + description: No content. + patch: + tags: + - Generic + summary: Generic PATCH request + description: >- + Sends a PATCH request to any versioned API path. The body is a JSON Patch document (RFC 6902): a list of + operations such as `[{"op": "replace", "path": "/description", "value": "New description"}]`. + operationId: genericPatch + x-codegen-request-body-name: requestBody + parameters: + - $ref: '#/components/parameters/path' + - $ref: '#/components/parameters/experimental' + requestBody: + content: + application/json-patch+json: + schema: + $ref: '#/components/schemas/GenericRequest' + responses: + '200': + $ref: '#/components/responses/GenericResponse' + '204': + description: No content. + delete: + tags: + - Generic + summary: Generic DELETE request + description: Sends a DELETE request to any versioned API path. + operationId: genericDelete + parameters: + - $ref: '#/components/parameters/path' + - $ref: '#/components/parameters/experimental' + responses: + '200': + $ref: '#/components/responses/GenericResponse' + '204': + description: No content. + +components: + parameters: + path: + name: path + in: path + required: true + description: >- + API path relative to the tenant base URL, including the API version, for example `accounts/v1`, + `sources/v1/2c9180835d2e5168015d32f890ca1581` or `sources/v2/2c9180835d2e5168015d32f890ca1581/provisioning-policies`. + Use whichever version the endpoint has. A leading slash is optional. Slashes separate path segments, + and the SDK URL-encodes each segment, so pass the segments unencoded. Anything after a `?` is sent as query + parameters, for example `accounts/v1?detailLevel=SLIM`. + example: accounts/v1 + schema: + type: string + experimental: + name: X-SailPoint-Experimental + in: header + description: >- + Set this header to `true` to call an experimental API. The SDK configuration must also enable experimental + APIs. + required: false + example: "true" + schema: + type: string + responses: + GenericResponse: + description: Successful response. The body is the JSON returned by the API. + content: + application/json: + schema: + $ref: '#/components/schemas/GenericResponse' + schemas: + GenericRequest: + description: Any JSON value. Most endpoints take an object. PATCH endpoints take a list of JSON Patch operations. + GenericResponse: + description: Any JSON value returned by the API, usually an object or a list of objects. diff --git a/sdk-resources/resources/docs_methods_index.mustache b/sdk-resources/resources/docs_methods_index.mustache index 3a2b2d16d..9986932b2 100644 --- a/sdk-resources/resources/docs_methods_index.mustache +++ b/sdk-resources/resources/docs_methods_index.mustache @@ -20,7 +20,7 @@ Method documents provide detailed information about each API operation (or metho - Example: Provides a sample of how the API uses the method. ## Available Methods -This is a list of the core methods available in the Python SDK for **{{#lambda.titlecase}}{{apiVersion}}{{/lambda.titlecase}}** endpoints: +This is a list of the core methods available in the Python SDK{{#apiVersion}} for **{{#lambda.titlecase}}{{apiVersion}}{{/lambda.titlecase}}** endpoints{{/apiVersion}}: ```mdx-code-block import DocCardList from '@theme/DocCardList'; diff --git a/validation_test.py b/validation_test.py index 43190fc46..13569c9f0 100644 --- a/validation_test.py +++ b/validation_test.py @@ -15,7 +15,7 @@ ApiClient, AccountsApi, SearchApi, TransformsApi, ConnectorsApi, SourcesApi, IdentityProfilesApi, - IdentitiesApi, TaskManagementApi, + IdentitiesApi, TaskManagementApi, GenericApi, ) HAS_V1 = True except ImportError: @@ -166,6 +166,24 @@ def test_list_sources_v1(self): self.assertIsNotNone(result.data) self.assertEqual(200, result.status_code) + # The generic client calls any versioned endpoint by path, with the same + # configuration and token as the resource clients. + def test_generic_get_v1(self): + result = GenericApi(self.api_client).generic_get_with_http_info(path="sources/v1", limit=10) + self.assertEqual(200, result.status_code) + self.assertIsInstance(result.data, list) + self.assertLessEqual(len(result.data), 10) + + # access-request-config/v2 is experimental, so this also covers the + # experimental opt-in. + def test_generic_get_v2(self): + self.configuration.experimental = True + result = GenericApi(self.api_client).generic_get_with_http_info( + path="access-request-config/v2", x_sail_point_experimental="true" + ) + self.assertEqual(200, result.status_code) + self.assertIsInstance(result.data, dict) + def test_list_identity_profiles_v1(self): result = Paginator.paginate( IdentityProfilesApi(self.api_client).list_identity_profiles_v1_with_http_info,