diff --git a/polyapi/generate.py b/polyapi/generate.py index 883f0ca..78bfff2 100644 --- a/polyapi/generate.py +++ b/polyapi/generate.py @@ -399,7 +399,21 @@ def clear() -> None: def render_spec(spec: SpecificationDto) -> Tuple[str, str]: function_type = spec["type"] - function_description = spec["description"] + raw_description = spec.get("description", "") + def _flatten_description(value: Any) -> List[str]: + if value is None: + return [] + if isinstance(value, list): + flat: List[str] = [] + for item in value: + flat.extend(_flatten_description(item)) + return flat + return [str(value)] + + if isinstance(raw_description, str): + function_description = raw_description + else: + function_description = "\n".join(_flatten_description(raw_description)) function_name = spec["name"] function_context = spec["context"] function_id = spec["id"] diff --git a/polyapi/poly_tables.py b/polyapi/poly_tables.py index 4a391fa..c9fa561 100644 --- a/polyapi/poly_tables.py +++ b/polyapi/poly_tables.py @@ -531,10 +531,27 @@ def _render_table(table: TableSpecDto) -> str: table_where_class = _render_table_where_class( table["name"], columns, required_columns ) - if table.get("description", ""): + raw_description = table.get("description", "") + + def _flatten_description(value: Any) -> List[str]: + if value is None: + return [] + if isinstance(value, list): + flat: List[str] = [] + for item in value: + flat.extend(_flatten_description(item)) + return flat + return [str(value)] + + if isinstance(raw_description, str): + normalized_description = raw_description + else: + normalized_description = "\n".join(_flatten_description(raw_description)) + + if normalized_description: table_description = '\n """' table_description += "\n ".join( - table["description"].replace('"', "'").split("\n") + normalized_description.replace('"', "'").split("\n") ) table_description += '\n """' else: diff --git a/polyapi/schema.py b/polyapi/schema.py index 29ecbe3..590fa04 100644 --- a/polyapi/schema.py +++ b/polyapi/schema.py @@ -50,6 +50,13 @@ def wrapped_generate_schema_types(type_spec: dict, root, fallback_type): # if we have no root, just add "My" root = "My" + root + if isinstance(root, list): + root = "_".join([str(x) for x in root if x is not None]) or fallback_type + elif root is None: + root = fallback_type + elif not isinstance(root, str): + root = str(root) + root = clean_title(root) try: @@ -131,10 +138,19 @@ def clean_title(title: str) -> str: """ used by library generation, sometimes functions can be added with spaces in the title or other nonsense. fix them! """ + if isinstance(title, list): + title = "_".join([str(x) for x in title if x is not None]) + elif title is None: + title = "" + elif not isinstance(title, str): + title = str(title) + title = title.replace(" ", "") # certain reserved words cant be titles, let's replace them if title == "List": title = "List_" + if not title: + title = "Dict" return title diff --git a/polyapi/server.py b/polyapi/server.py index 9ee8ae0..40afee9 100644 --- a/polyapi/server.py +++ b/polyapi/server.py @@ -62,7 +62,7 @@ def render_server_function( return_type_def=return_type_def, ) func_str = SERVER_FUNCTION_TEMPLATE.format( - return_type_name=add_type_import_path(function_name, return_type_name), + return_type_name=_normalize_return_type_for_annotation(function_name, return_type_name), function_type="server", function_name=function_name, function_id=function_id, @@ -74,9 +74,15 @@ def render_server_function( return func_str, func_type_defs +def _normalize_return_type_for_annotation(function_name: str, return_type_name: str) -> str: + if return_type_name == "ReturnType": + return "ReturnType" + return add_type_import_path(function_name, return_type_name) + + def _get_server_return_action(return_type_name: str) -> str: if return_type_name == "str": return_action = "resp.text" else: return_action = "resp.json()" - return return_action \ No newline at end of file + return return_action diff --git a/tests/test_server.py b/tests/test_server.py index c8126be..e5e4492 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -66,6 +66,34 @@ } +RETURN_TYPE_NAMED_RETURN_TYPE = { + "id": "ret-1234", + "type": "serverFunction", + "context": "mixed", + "name": "fooFunc", + "description": "Return type name collision regression test.", + "requirements": [], + "function": { + "arguments": [], + "returnType": { + "kind": "object", + "schema": { + "title": "ReturnType", + "type": "object", + "properties": { + "value": {"type": "string"}, + }, + "required": ["value"], + }, + }, + "synchronous": True, + }, + "code": "", + "language": "javascript", + "visibilityMetadata": {"visibility": "ENVIRONMENT"}, +} + + class T(unittest.TestCase): def test_render_function_twilio_server(self): # same test but try it as a serverFunction rather than an apiFunction @@ -116,4 +144,18 @@ def test_render_function_list_recommendations(self): # stay_date: Required[str] # """ Required property """''' -# self.assertIn(expected_return_type, func_str) \ No newline at end of file +# self.assertIn(expected_return_type, func_str) + + def test_render_function_return_type_name_collision_does_not_reference_module_attr(self): + return_type = RETURN_TYPE_NAMED_RETURN_TYPE["function"]["returnType"] + func_str, func_type_defs = render_server_function( + RETURN_TYPE_NAMED_RETURN_TYPE["type"], + RETURN_TYPE_NAMED_RETURN_TYPE["name"], + RETURN_TYPE_NAMED_RETURN_TYPE["id"], + RETURN_TYPE_NAMED_RETURN_TYPE["description"], + RETURN_TYPE_NAMED_RETURN_TYPE["function"]["arguments"], + return_type, + ) + self.assertIn("-> dict", func_str) + self.assertNotIn(".returnType", func_str) + self.assertNotIn(".ReturnType", func_str)