diff --git a/gen-json.py b/gen-json.py new file mode 100644 index 0000000..1029ab2 --- /dev/null +++ b/gen-json.py @@ -0,0 +1,200 @@ +import inspect +import json +import importlib +import os +import re +from typing import get_origin, get_args, _GenericAlias +from typing import List, Tuple, Union +from enum import Enum +from docstring_parser import parse + +class CustomJSONEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, _GenericAlias): + return str(obj) + elif isinstance(obj, type): + return obj.__name__ + return json.JSONEncoder.default(self, obj) + +# Generate JSON documentation for a Python SDK module. +def gen_json_for_module(modules, directory_path): + for modulename in modules: + module = importlib.import_module(modulename) + + module_dict = {} + + class_list = [] + function_list = [] + + for name, member in inspect.getmembers(module): + + # Skip any members that are not functions or classes. + if not inspect.isfunction(member) and not inspect.isclass(member): + continue + + if 'skyflow' not in member.__module__ : + continue + + if inspect.isclass(member): + class_dict = {} + class_dict['name'] = name + class_dict['type'] = 'class' + + class_docstring = parse(inspect.getdoc(member)) + class_desc = get_desc(class_docstring) + class_dict['docstring'] = class_desc + + + enum_dict = None + if(str(member.__class__) == ""): + # enum_dict = {member:value for member, value in dict(member.__members__)} + enum_dict = {attr: get_param_type(value) for attr, value in dict(member.__members__).items()} + + class_dict['enum_values'] = enum_dict + + # Extracting class arguments + class_args = [] + for arg in inspect.getfullargspec(member.__init__).args[1:]: + arg_type = get_param_type(inspect.signature(member.__init__).parameters[arg].annotation) + arg_desc = get_param_desc(class_docstring, arg) + class_args.append({'name': arg, 'type': arg_type, 'desc': arg_desc}) + class_dict['args'] = class_args + + # Extracting class methods + class_method_list = [] + for method_name, method_obj in inspect.getmembers(member, predicate=inspect.isfunction): + if method_name != '__init__' and 'skyflow' in method_obj.__module__ and not method_name.startswith('_'): + class_method_dict = {} + class_method_dict['name'] = method_name + class_method_dict['type'] = 'method' + + method_docstring = parse(inspect.getdoc(method_obj)) + method_desc = get_desc(method_docstring) + class_method_dict['docstring'] = method_desc + method_args = [] + + + # Extracting method arguments + for arg in inspect.getfullargspec(method_obj).args[1:]: + arg_type = get_param_type(inspect.signature(method_obj).parameters[arg].annotation) + arg_desc = get_param_desc(method_docstring, arg) + method_args.append({'name': arg, 'type': arg_type, 'desc': arg_desc}) + class_method_dict['args'] = method_args + + # Extracting return type + return_type = get_param_type(inspect.signature(method_obj).return_annotation) + if method_docstring.returns: + return_desc = method_docstring.returns.description + else: + return_desc = None + + if return_type is not inspect._empty: + class_method_dict['return_type'] = return_type + class_method_dict['return_desc'] = return_desc + + class_method_list.append(class_method_dict) + + class_dict['methods'] = class_method_list + + class_list.append(class_dict) + # Generate JSON for the functions + elif inspect.isfunction(member) and not name.startswith('_'): + function_dict = {} + function_dict['name'] = name + function_dict['type'] = 'function' + + function_docstring = parse(inspect.getdoc(member)) + function_desc = get_desc(function_docstring) + function_dict['docstring'] = function_desc + function_args = [] + + # Extracting function arguments + for arg in inspect.getfullargspec(member).args: + arg_type = get_param_type(inspect.signature(member).parameters[arg].annotation) + arg_desc = get_param_desc(function_docstring, arg) + function_args.append({'name': arg, 'type': arg_type, 'desc': arg_desc}) + function_dict['args'] = function_args + + # Extracting return type + return_type = get_param_type(inspect.signature(member).return_annotation) + + if function_docstring.returns: + return_desc = function_docstring.returns.description + else: + return_desc = None + + if return_type is not inspect._empty: + function_dict['return_type'] = return_type + function_dict['return_desc'] = return_desc + + function_list.append(function_dict) + + if class_list: + module_dict['classes'] = class_list + + if function_list: + module_dict['functions'] = function_list + + # file_path = os.path.join(directory_path, f"{module.__name__}.json") + + file_path = os.path.join(directory_path, f"{module.__name__}.json") + with open(f"{file_path}", 'w') as f: + json.dump(module_dict, f, indent=2, cls=CustomJSONEncoder) + + print(f"JSON file generated at {file_path}") + +def get_desc(parsed_docstring): + if parsed_docstring.short_description and parsed_docstring.long_description: + return parsed_docstring.short_description + "\n" + parsed_docstring.long_description + elif parsed_docstring.short_description: + return parsed_docstring.short_description + else: + return None + +def get_param_desc(parsed_docstring, arg_name): + for param in parsed_docstring.params: + if param.arg_name == arg_name: + return param.description + return None + +def get_param_type(param_type): + if param_type == inspect._empty: + param_type = None + elif isinstance(param_type, type): + param_type = param_type.__name__ + elif isinstance(param_type, tuple): + param_type = tuple(get_type_name(t) for t in param_type) + elif get_origin(param_type) == Union: + param_type = " or ".join(get_type_name(t) for t in get_args(param_type)) + elif isinstance(param_type, List): + param_type = List[get_type_name(get_args(param_type)[0])] + elif isinstance(param_type, Enum): + param_type = param_type.value + + return param_type + +def get_type_name(param_type): + if isinstance(param_type, type): + return param_type.__name__ + elif isinstance(param_type, tuple): + return tuple(get_type_name(t) for t in param_type) + elif get_origin(param_type) == Union: + return " or ".join(get_type_name(t) for t in get_args(param_type)) + elif isinstance(param_type, List): + return List[get_type_name(get_args(param_type)[0])] + elif hasattr(param_type, "__name__"): + return param_type.__name__ + else: + type_str = str(param_type) + match = re.match(r"", type_str) + if match: + return match.group(1) + else: + return type_str + +modules = ['skyflow.vault.__init__', 'skyflow.service_account.__init__', 'skyflow.errors.__init__', 'skyflow.__init__'] +# modules = ['skyflow.vault.__init__'] +gen_json_for_module(modules, 'docs/json') + + + diff --git a/gen-markdown.py b/gen-markdown.py new file mode 100644 index 0000000..3ecba48 --- /dev/null +++ b/gen-markdown.py @@ -0,0 +1,176 @@ +# import json + +# def generate_markdown_from_json(json_file_path): +# with open(json_file_path, 'r') as f: +# module_dict = json.load(f) + +# markdown_str = f"# {json_file_path.split('/')[-1].split('.')[0]}\n\n" + + # if 'classes' in module_dict: + # markdown_str += "## Classes\n\n" + # for class_dict in module_dict['classes']: + # markdown_str += f"### {class_dict['name']}\n\n" + # if class_dict['docstring']: + # markdown_str += f"{class_dict['docstring']}\n\n" + # markdown_str += "```python\n" + # markdown_str += f"class {class_dict['name']}:\n" + # for arg_dict in class_dict['args']: + # markdown_str += f" {arg_dict['name']}: {arg_dict['type']}\n" + # for method_dict in class_dict['methods']: + # markdown_str += f"\n def {method_dict['name']}(" + # markdown_str += ', '.join([f"{arg['name']}: {arg['type']}" for arg in method_dict['args']]) + # markdown_str += f") -> {method_dict['return_type']}:\n" + # if method_dict['docstring']: + # markdown_str += f" {method_dict['docstring']}\n" + # markdown_str += "```\n\n" + + # if 'functions' in module_dict: + # markdown_str += "## Functions\n\n" + # for function_dict in module_dict['functions']: + # markdown_str += f"### {function_dict['name']}()\n\n" + # if function_dict['docstring']: + # markdown_str += f"{function_dict['docstring']}\n\n" + # markdown_str += "```python\n" + # markdown_str += f"def {function_dict['name']}(" + # markdown_str += ', '.join([f"{arg['name']}: {arg['type']}" for arg in function_dict['args']]) + # markdown_str += f") -> {function_dict['return_type']}:\n" + # markdown_str += "```\n\n" + +# return markdown_str + +# json_files +# json_file_path = 'docs/json/skyflow.vault._client.json' +# markdown_str = generate_markdown_from_json(json_file_path) +# print(markdown_str) + + +import os +import json + +def generate_markdown_from_json(directory_path): + for filename in os.listdir(directory_path): + if filename.endswith(".json"): + file_path = os.path.join(directory_path, filename) + with open(file_path) as f: + module_dict = json.load(f) + + path_arr = file_path.split('/')[-1].split('.') + module_name = f"{path_arr[0]}" + if path_arr[1] != "__init__": + module_name += f".{path_arr[1]}" + markdown_str = "{% env enable=\"pythonSdkRef\" %}\n\n" + markdown_str += f"# {module_name}\n" + + if 'classes' in module_dict: + # markdown_str += "## Classes\n\n" + for class_dict in module_dict['classes']: + markdown_str += f"\n## Class: {class_dict['name']}\n\n" + + if class_dict['docstring']: + markdown_str += f"{class_dict['docstring']}\n\n" + markdown_str += "```python\n" + markdown_str += f"class {class_dict['name']}:\n" + markdown_str += "```\n" + + if class_dict['args']: + markdown_str += "\n#### Intializer Parameters\n" + markdown_str += "\n| Name | Type | Description |\n| --- | --- | --- |" + for arg_dict in class_dict['args']: + markdown_str += f"\n| {arg_dict['name']} | {arg_dict['type']}| {arg_dict['desc']} |" + # if(arg_dict['type'] != 'NoneType'): + # markdown_str += f": {arg_dict['type']}\n" + # else: + markdown_str += "\n" + + if(class_dict['enum_values']): + values = class_dict['enum_values'] + markdown_str += "\n#### Enumeration Members\n" + markdown_str += "\n| Name | Type |\n| --- | --- |" + markdown_str += ''.join([f"\n| {attr} | {value} |" for attr, value in values.items()]) + markdown_str += "\n" + + if class_dict['methods']: + markdown_str += f"\n\n## {class_dict['name']} Class Methods\n" + + for method_dict in class_dict['methods']: + markdown_str += f"\n### {method_dict['name']}\n" + + if method_dict['docstring']: + markdown_str += f"\n{method_dict['docstring']}\n" + + markdown_str += "\n```python\n" + markdown_str += f"def {method_dict['name']}(" + + if method_dict['args']: + for i, arg in enumerate(method_dict['args']): + markdown_str += f"{arg['name']}" + if arg['type']: + markdown_str += f": {arg['type']}" + if i < len(method_dict['args']) - 1: + markdown_str += ", " + + markdown_str += ")" + + if method_dict['return_type']: + markdown_str += f" -> {method_dict['return_type']}:\n" + else: + markdown_str += ":\n" + markdown_str += "```\n" + + if method_dict['args']: + markdown_str += "\n#### Parameters\n" + markdown_str += "\n| Name | Type | Description |\n| --- | --- | --- |" + for i, arg in enumerate(method_dict['args']): + markdown_str += f"\n| {arg['name']} | {arg['type']}| {arg['desc']} |" + markdown_str += "\n" + + markdown_str += "\n#### Returns\n" + if method_dict['return_type']: + markdown_str += f"{method_dict['return_type']}\n" + markdown_str += f"\n{method_dict['return_desc']}\n" + + if 'functions' in module_dict: + markdown_str += "\n## Functions\n" + for function_dict in module_dict['functions']: + markdown_str += f"\n### {function_dict['name']}\n\n" + + if function_dict['docstring']: + markdown_str += f"{function_dict['docstring']}\n\n" + + markdown_str += "```python\n" + markdown_str += f"def {function_dict['name']}(" + # markdown_str += ', '.join([f"{arg['name']}: {arg['type']}" for arg in function_dict['args']]) + if function_dict['args']: + for i, arg in enumerate(function_dict['args']): + markdown_str += f"{arg['name']}" + if arg['type']: + markdown_str += f": {arg['type']}" + if i < len(function_dict['args']) - 1: + markdown_str += ", " + markdown_str += ")" + if function_dict['return_type']: + markdown_str += f" -> {function_dict['return_type']}:\n" + else: + markdown_str += ":\n" + markdown_str += "```\n" + + if function_dict['args']: + markdown_str += "\n#### Parameters\n" + markdown_str += "\n| Name | Type | Description |\n| --- | --- | --- |" + for i, arg in enumerate(function_dict['args']): + markdown_str += f" \n| {arg['name']} | {arg['type']}| {arg['desc']} |" + markdown_str += "\n" + + markdown_str += "\n#### Returns\n" + if function_dict['return_type']: + markdown_str += f"{function_dict['return_type']}\n" + markdown_str += f"\n{function_dict['return_desc']}\n" + + markdown_str += "\n{% /env %}" + markdown_filename = module_name + ".md" + markdown_file_path = os.path.join('docs/markdown', markdown_filename) + with open(markdown_file_path, 'w') as f: + f.write(markdown_str) + print(f"Markdown file generated at {markdown_file_path}") + +generate_markdown_from_json('docs/json') \ No newline at end of file diff --git a/skyflow/_utils.py b/skyflow/_utils.py index 1c56830..3debb31 100644 --- a/skyflow/_utils.py +++ b/skyflow/_utils.py @@ -21,6 +21,9 @@ class LogLevel(Enum): + ''' + Supported log levels. + ''' DEBUG = logging.DEBUG INFO = logging.INFO WARN = logging.WARN @@ -30,7 +33,9 @@ class LogLevel(Enum): def set_log_level(logLevel: LogLevel): ''' - Sets the Log Level for the Skyflow python SDK + Sets the log level. + + :param logLevel: Log level to set. ''' skyflowLog.setLevel(logLevel.value) diff --git a/skyflow/errors/_skyflow_errors.py b/skyflow/errors/_skyflow_errors.py index a2dbbf0..c6ef081 100644 --- a/skyflow/errors/_skyflow_errors.py +++ b/skyflow/errors/_skyflow_errors.py @@ -6,6 +6,9 @@ class SkyflowErrorCodes(Enum): + ''' + Supported error codes. + ''' INVALID_INPUT = 400 SERVER_ERROR = 500 PARTIAL_SUCCESS = 500 @@ -77,6 +80,14 @@ class SkyflowErrorMessages(Enum): EMPTY_UPSERT_OPTION_COLUMN = "upsert object column value is empty string at index %s, expected non-empty string" class SkyflowError(Exception): + ''' + The SkyflowError exception. + + :param code: Error associated with the exception. + :param message: Error describing the exception. + :param data: Additional data related to the exception. + :param interface: Interface where the exception happened. + ''' def __init__(self, code, message="An Error occured", data={}, interface: str = 'Unknown') -> None: if type(code) is SkyflowErrorCodes: self.code = code.value diff --git a/skyflow/service_account/_token.py b/skyflow/service_account/_token.py index f73191b..bef23b7 100644 --- a/skyflow/service_account/_token.py +++ b/skyflow/service_account/_token.py @@ -19,11 +19,14 @@ def generate_bearer_token(credentialsFilePath: str) -> ResponseToken: ''' This function is used to get the access token for skyflow Service Accounts. - `credentialsFilePath` is the file path in string of the credentials file that is downloaded after Service Account creation. + Gets a bearer token for a service account. `credentialsFilePath` is the file path (as a string) of the credentials file that your browser downloads after creating a service account. - Response Token is a named tupe with two attributes: - 1. AccessToken: The access token - 2. TokenType: The type of access token (eg: Bearer) + The response token is a named tuple with two attributes: + 1. AccessToken: The access token + 2. TokenType: The type of the token. For example, "Bearer". + + :param credentialsFilePath: File path (as a string) of the credentials file for a service account. + :returns: Named tuple with `AccessToken` and `TokenType` attributes. ''' log_info(InfoMessages.GENERATE_BEARER_TOKEN_TRIGGERED.value, @@ -53,11 +56,14 @@ def generate_bearer_token(credentialsFilePath: str) -> ResponseToken: def generate_bearer_token_from_creds(credentials: str) -> ResponseToken: ''' This function is used to get the access token for skyflow Service Accounts. - `credentials` arg takes the content of the credentials file that is downloaded after Service Account creation. - - Response Token is a named tupe with two attributes: - 1. AccessToken: The access token - 2. TokenType: The type of access token (eg: Bearer) + Gets a bearer token for a service account. `credentials` is the contents of the credentials file that your browser downloads after creating a service account. + + The response token is a named tuple with two attributes: + 1. AccessToken: The access token + 2. TokenType: The type of the token. For example, "Bearer". + + :param credentials: Contents of the credentials file for a service account. + :returns: Named tuple with `AccessToken` and `TokenType` attributes. ''' log_info(InfoMessages.GENERATE_BEARER_TOKEN_TRIGGERED.value, diff --git a/skyflow/service_account/_validity.py b/skyflow/service_account/_validity.py index 8b9229a..10aa3fb 100644 --- a/skyflow/service_account/_validity.py +++ b/skyflow/service_account/_validity.py @@ -10,8 +10,10 @@ def is_expired(token: str): ''' - Check if stored token is not expired, if not return a new token, - if the token has expiry time before 5min of current time, call returns False + Checks if a token is expired. If the token is expired or will expire within 5 minutes, returns True. Otherwise, returns False. + + :param token: Token to check. + :returns: If the token is expired or will expire within 5 minutes, returns `True`. Otherwise, returns `False`. ''' interface = InterfaceName.IS_EXPIRED.value log_info(InfoMessages.IS_EXPIRED_TRIGGERED.value, interface=interface) diff --git a/skyflow/vault/_client.py b/skyflow/vault/_client.py index a422b10..48a36be 100644 --- a/skyflow/vault/_client.py +++ b/skyflow/vault/_client.py @@ -4,14 +4,14 @@ import json import types import requests -from ._insert import getInsertRequestBody, processResponse, convertResponse -from ._update import sendUpdateRequests, createUpdateResponseBody -from ._config import Configuration -from ._config import InsertOptions, ConnectionConfig, UpdateOptions -from ._connection import createRequest -from ._detokenize import sendDetokenizeRequests, createDetokenizeResponseBody -from ._get_by_id import sendGetByIdRequests, createGetResponseBody -from ._get import sendGetRequests +from skyflow.vault._insert import getInsertRequestBody, processResponse, convertResponse +from skyflow.vault._update import sendUpdateRequests, createUpdateResponseBody +from skyflow.vault._config import Configuration +from skyflow.vault._config import InsertOptions, ConnectionConfig, UpdateOptions +from skyflow.vault._connection import createRequest +from skyflow.vault._detokenize import sendDetokenizeRequests, createDetokenizeResponseBody +from skyflow.vault._get_by_id import sendGetByIdRequests, createGetResponseBody +from skyflow.vault._get import sendGetRequests import asyncio from skyflow.errors._skyflow_errors import SkyflowError, SkyflowErrorCodes, SkyflowErrorMessages from skyflow._utils import log_info, InfoMessages, InterfaceName, getMetrics @@ -19,6 +19,11 @@ class Client: + ''' + Represents a client for interacting with Skyflow. + + :param config: Configuration for the Skyflow client. + ''' def __init__(self, config: Configuration): interface = InterfaceName.CLIENT.value @@ -43,6 +48,13 @@ def __init__(self, config: Configuration): log_info(InfoMessages.CLIENT_INITIALIZED.value, interface=interface) def insert(self, records: dict, options: InsertOptions = InsertOptions()): + ''' + Inserts data into the vault. + + :param records: Records to insert. + :param options: Options for the insertion. + :returns: Returns the insert response. + ''' interface = InterfaceName.INSERT.value log_info(InfoMessages.INSERT_TRIGGERED.value, interface=interface) @@ -65,6 +77,12 @@ def insert(self, records: dict, options: InsertOptions = InsertOptions()): return result def detokenize(self, records): + ''' + Returns values that correspond to the specified tokens. + + :param records: Dictionary that contains the `records` key, which is an array of records to fetch from the vault. + :returns: Tokens to return values for. + ''' interface = InterfaceName.DETOKENIZE.value log_info(InfoMessages.DETOKENIZE_TRIGGERED.value, interface) @@ -83,6 +101,12 @@ def detokenize(self, records): return result def get(self, records): + ''' + Returns records by Skyflow IDs or column values. + + :param records: Dictionary that contains either an array of Skyflow IDs or a the name of a unique column and associated values. + :returns: Returns the specified records and any errors. + ''' interface = InterfaceName.GET.value log_info(InfoMessages.GET_TRIGGERED.value, interface) @@ -102,6 +126,12 @@ def get(self, records): return result def get_by_id(self, records): + ''' + Reveals records by Skyflow ID. + + :param records: Dictionary that contains records to fetch. + :returns: Returns the specified records and any errors. + ''' interface = InterfaceName.GET_BY_ID.value log_info(InfoMessages.GET_BY_ID_TRIGGERED.value, interface) @@ -121,7 +151,12 @@ def get_by_id(self, records): return result def invoke_connection(self, config: ConnectionConfig): + ''' + Invokes a connection using the provided configuration. + :param config: Configuration for the connection. + :returns: Returns the response from the connection invocation. + ''' interface = InterfaceName.INVOKE_CONNECTION.value log_info(InfoMessages.INVOKE_CONNECTION_TRIGGERED.value, interface) @@ -140,9 +175,7 @@ def invoke_connection(self, config: ConnectionConfig): return processResponse(response, interface=interface) def _checkConfig(self, interface): - ''' - Performs basic check on the given client config - ''' + if not len(self.vaultID) > 0: raise SkyflowError(SkyflowErrorCodes.INVALID_INPUT, SkyflowErrorMessages.EMPTY_VAULT_ID, interface=interface) @@ -151,12 +184,17 @@ def _checkConfig(self, interface): SkyflowErrorMessages.EMPTY_VAULT_URL, interface=interface) def _get_complete_vault_url(self): - ''' - Get the complete vault url from given vault url and vault id - ''' + return self.vaultURL + "/v1/vaults/" + self.vaultID def update(self, updateInput, options: UpdateOptions = UpdateOptions()): + ''' + Updates the configuration of elements in the vault. + + :param updateInput: Input for updating elements in the vault. + :param options: Options for the container update. + :returns: Returns the result of the update operation. + ''' interface = InterfaceName.UPDATE.value log_info(InfoMessages.UPDATE_TRIGGERED.value, interface=interface) diff --git a/skyflow/vault/_config.py b/skyflow/vault/_config.py index 25fe3cd..fae4e09 100644 --- a/skyflow/vault/_config.py +++ b/skyflow/vault/_config.py @@ -7,7 +7,13 @@ class Configuration: + ''' + Configuration for interacting with Skyflow. + :param vaultID: ID of the vault to connect to. + :param vaultURL: URL of the vault to connect to. + :param tokenProvider: Token provider for authentication. + ''' def __init__(self, vaultID: str=None, vaultURL: str=None, tokenProvider: FunctionType=None): self.vaultID = '' @@ -25,20 +31,40 @@ def __init__(self, vaultID: str=None, vaultURL: str=None, tokenProvider: Functio self.tokenProvider = tokenProvider class UpsertOption: + ''' + Configuration for upsert. + + :param table: Table that the data belongs to. + :param column: Name of the unique column. + ''' def __init__(self,table: str,column: str): self.table = table self.column = column class InsertOptions: + ''' + Configuration for an insert operation. + + :param tokens: If `true`, returns tokens for the collected data. Defaults to `false`. + :param upsert: If specified, upserts data. If not specified, inserts data. + ''' def __init__(self, tokens: bool=True,upsert :List[UpsertOption]=None): self.tokens = tokens self.upsert = upsert class UpdateOptions: + ''' + Updates the configuration of elements in the vault. + + :param tokens: If `true`, returns tokens for the collected data. Defaults to `false`. + ''' def __init__(self, tokens: bool=True): self.tokens = tokens class RequestMethod(Enum): + ''' + Supported request methods. + ''' GET = 'GET' POST = 'POST' PUT = 'PUT' @@ -46,6 +72,16 @@ class RequestMethod(Enum): DELETE = 'DELETE' class ConnectionConfig: + ''' + Configuration for making a connection to an external service. + + :param connectionURL: URL for the connection. + :param methodName: HTTP request method to use. + :param pathParams: Parameters to include in the URL path. Defaults to an empty dictionary. + :param queryParams: Parameters to include in the URL query. Defaults to an empty dictionary. + :param requestHeader: Headers for the request. Defaults to an empty dictionary. + :param requestBody: The body of the request. Defaults to an empty dictionary. + ''' def __init__(self, connectionURL: str, methodName: RequestMethod, pathParams: dict={}, queryParams: dict={}, requestHeader: dict={}, requestBody: dict={}): self.connectionURL = connectionURL.rstrip("/") @@ -56,6 +92,9 @@ def __init__(self, connectionURL: str, methodName: RequestMethod, self.requestBody = requestBody class RedactionType(Enum): + ''' + Supported redaction types. + ''' PLAIN_TEXT = "PLAIN_TEXT" MASKED = "MASKED" REDACTED = "REDACTED"