diff --git a/polyapi/api.py b/polyapi/api.py index e8018f9..8cceff3 100644 --- a/polyapi/api.py +++ b/polyapi/api.py @@ -24,6 +24,8 @@ def {function_name}( \""" resp = execute("{function_type}", "{function_id}", {data}) return {api_response_type}(resp.json()) # type: ignore + + """ diff --git a/polyapi/auth.py b/polyapi/auth.py index a405510..199cfef 100644 --- a/polyapi/auth.py +++ b/polyapi/auth.py @@ -16,9 +16,9 @@ class AuthFunctionResponse(TypedDict): - status: int - data: Any - headers: Dict[str, str] + status: int + data: Any + headers: Dict[str, str] async def getToken(clientId: str, clientSecret: str, scopes: List[str], callback, options: Optional[Dict[str, Any]] = None): diff --git a/polyapi/constants.py b/polyapi/constants.py index a58b64d..16bbeaf 100644 --- a/polyapi/constants.py +++ b/polyapi/constants.py @@ -5,6 +5,8 @@ "boolean": "bool", "array": "List", "object": "Dict", + "function": "Callable", + "void": "None", } @@ -15,6 +17,8 @@ "bool": "boolean", "List": "array", "Dict": "object", + "Callable": "function", + "None": "void", } BASIC_PYTHON_TYPES = set(PYTHON_TO_JSONSCHEMA_TYPE_MAP.keys()) diff --git a/polyapi/server.py b/polyapi/server.py index 6a359bf..f616f26 100644 --- a/polyapi/server.py +++ b/polyapi/server.py @@ -4,7 +4,7 @@ from polyapi.utils import camelCase, add_type_import_path, parse_arguments, get_type_and_def SERVER_DEFS_TEMPLATE = """ -from typing import List, Dict, Any, TypedDict +from typing import List, Dict, Any, TypedDict, Callable {args_def} {return_type_def} """ @@ -22,6 +22,8 @@ def {function_name}( return {return_action} except: return resp.text + + """ diff --git a/polyapi/typedefs.py b/polyapi/typedefs.py index 544f3c1..e23113a 100644 --- a/polyapi/typedefs.py +++ b/polyapi/typedefs.py @@ -12,6 +12,7 @@ class PropertySpecification(TypedDict): class PropertyType(TypedDict): kind: Literal['void', 'primitive', 'array', 'object', 'function', 'plain'] + spec: NotRequired[Dict] name: NotRequired[str] type: NotRequired[str] items: NotRequired['PropertyType'] diff --git a/polyapi/utils.py b/polyapi/utils.py index 604a700..bba3463 100644 --- a/polyapi/utils.py +++ b/polyapi/utils.py @@ -10,7 +10,7 @@ # this string should be in every __init__ file. # it contains all the imports needed for the function or variable code to run -CODE_IMPORTS = "from typing import List, Dict, Any, TypedDict, Optional\nimport logging\nimport requests\nimport socketio # type: ignore\nfrom polyapi.config import get_api_key_and_url\nfrom polyapi.execute import execute, execute_post, variable_get, variable_update\n\n" +CODE_IMPORTS = "from typing import List, Dict, Any, TypedDict, Optional, Callable\nimport logging\nimport requests\nimport socketio # type: ignore\nfrom polyapi.config import get_api_key_and_url\nfrom polyapi.execute import execute, execute_post, variable_get, variable_update\n\n" def init_the_init(full_path: str) -> None: @@ -61,6 +61,10 @@ def print_red(s: str): def add_type_import_path(function_name: str, arg: str) -> str: """ if not basic type, coerce to camelCase and add the import path """ + # for now, just treat Callables as basic types + if arg.startswith("Callable"): + return arg + if arg in BASIC_PYTHON_TYPES: return arg @@ -142,6 +146,25 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]: return "Any", "" else: return "Dict", "" + elif type_spec["kind"] == "function": + arg_types = [] + arg_defs = [] + if "spec" in type_spec: + return_type, _ = get_type_and_def(type_spec["spec"]["returnType"]) + if return_type not in BASIC_PYTHON_TYPES: + # for now only Python only supports basic types as return types + return_type = "Any" + + for argument in type_spec["spec"]["arguments"]: + arg_type, arg_def = get_type_and_def(argument["type"]) + arg_types.append(arg_type) + if arg_def: + arg_defs.append(arg_def) + + final_arg_type = "Callable[[{}], {}]".format(", ".join(arg_types), return_type) + return final_arg_type, "\n".join(arg_defs) + else: + return "Callable", "" elif type_spec["kind"] == "any": return "Any", "" else: diff --git a/polyapi/webhook.py b/polyapi/webhook.py index e4bdf5c..1524655 100644 --- a/polyapi/webhook.py +++ b/polyapi/webhook.py @@ -6,7 +6,7 @@ from polyapi.config import get_api_key_and_url from polyapi.typedefs import PropertySpecification -from polyapi.utils import poly_full_path +from polyapi.utils import parse_arguments, poly_full_path # all active webhook handlers, used by unregister_all to cleanup active_handlers: List[Dict[str, Any]] = [] @@ -15,10 +15,18 @@ client = None +WEBHOOK_DEFS_TEMPLATE = """ +from typing import List, Dict, Any, TypedDict, Callable +{function_args_def} +""" + + WEBHOOK_TEMPLATE = """ -async def {function_name}(callback, options=None): +async def {function_name}( +{function_args} +): \"""{description} Function ID: {function_id} @@ -112,15 +120,22 @@ def render_webhook_handle( arguments: List[PropertySpecification], return_type: Dict[str, Any], ) -> Tuple[str, str]: + function_args, function_args_def = parse_arguments(function_name, arguments) + + if "WebhookEventType" in function_args: + # let's add the function name import! + function_args = function_args.replace("WebhookEventType", f"_{function_name}.WebhookEventType") + func_str = WEBHOOK_TEMPLATE.format( description=function_description, client_id=uuid.uuid4().hex, function_id=function_id, function_name=function_name, + function_args=function_args, function_path=poly_full_path(function_context, function_name), ) - - return func_str, "" + func_defs = WEBHOOK_DEFS_TEMPLATE.format(function_args_def=function_args_def) + return func_str, func_defs def start(*args): diff --git a/pyproject.toml b/pyproject.toml index e889d20..7cff949 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=61.2", "wheel"] [project] name = "polyapi-python" -version = "0.2.4" +version = "0.2.5.dev1" description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers" authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }] dependencies = [ diff --git a/tests/test_utils.py b/tests/test_utils.py index 7e920bb..c3662a5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,8 @@ import unittest from polyapi.schema import _fix_title +from polyapi.utils import get_type_and_def + +OPENAPI_FUNCTION = {'kind': 'function', 'spec': {'arguments': [{'name': 'event', 'required': False, 'type': {'kind': 'object', 'schema': {'$schema': 'http://json-schema.org/draft-06/schema#', 'type': 'array', 'items': {'$ref': '#/definitions/WebhookEventTypeElement'}, 'definitions': {'WebhookEventTypeElement': {'type': 'object', 'additionalProperties': False, 'properties': {'title': {'type': 'string'}, 'manufacturerName': {'type': 'string'}, 'carType': {'type': 'string'}, 'id': {'type': 'integer'}}, 'required': ['carType', 'id', 'manufacturerName', 'title'], 'title': 'WebhookEventTypeElement'}}}}}, {'name': 'headers', 'required': False, 'type': {'kind': 'object', 'typeName': 'Record'}}, {'name': 'params', 'required': False, 'type': {'kind': 'object', 'typeName': 'Record'}}, {'name': 'polyCustom', 'required': False, 'type': {'kind': 'object', 'properties': [{'name': 'responseStatusCode', 'type': {'type': 'number', 'kind': 'primitive'}, 'required': True}, {'name': 'responseContentType', 'type': {'type': 'string', 'kind': 'primitive'}, 'required': True, 'nullable': True}]}}], 'returnType': {'kind': 'void'}, 'synchronous': True}} class T(unittest.TestCase): @@ -8,3 +11,7 @@ def test_fix_titles(self): output = 'from typing import TypedDict\nfrom typing_extensions import Required\n\n\nclass Numofcars(TypedDict, total=False):\n """ numOfCars. """\n\n requestNumber: Required[int]\n """\n Requestnumber.\n\n Required property\n """\n\n' fixed = _fix_title(input_data, output) self.assertIn("class numOfCars", fixed) + + def test_get_type_and_def(self): + arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION) + self.assertEqual(arg_type, "Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]")