polyapi-python 0.3.17.dev1__tar.gz → 0.3.18.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {polyapi_python-0.3.17.dev1/polyapi_python.egg-info → polyapi_python-0.3.18.dev1}/PKG-INFO +1 -1
  2. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/generate.py +15 -1
  3. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/poly_tables.py +19 -2
  4. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/schema.py +16 -0
  5. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/server.py +17 -3
  6. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/utils.py +58 -4
  7. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1/polyapi_python.egg-info}/PKG-INFO +1 -1
  8. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/pyproject.toml +1 -1
  9. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_server.py +92 -0
  10. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_utils.py +24 -1
  11. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/LICENSE +0 -0
  12. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/README.md +0 -0
  13. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/__init__.py +0 -0
  14. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/__main__.py +0 -0
  15. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/api.py +0 -0
  16. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/auth.py +0 -0
  17. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/cli.py +0 -0
  18. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/cli_constants.py +0 -0
  19. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/client.py +0 -0
  20. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/config.py +0 -0
  21. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/constants.py +0 -0
  22. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/deployables.py +0 -0
  23. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/error_handler.py +0 -0
  24. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/exceptions.py +0 -0
  25. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/execute.py +0 -0
  26. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/function_cli.py +0 -0
  27. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/http_client.py +0 -0
  28. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/parser.py +0 -0
  29. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/poly_schemas.py +0 -0
  30. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/prepare.py +0 -0
  31. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/py.typed +0 -0
  32. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/rendered_spec.py +0 -0
  33. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/sync.py +0 -0
  34. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/typedefs.py +0 -0
  35. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/variables.py +0 -0
  36. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi/webhook.py +0 -0
  37. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi_python.egg-info/SOURCES.txt +0 -0
  38. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi_python.egg-info/dependency_links.txt +0 -0
  39. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi_python.egg-info/requires.txt +0 -0
  40. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/polyapi_python.egg-info/top_level.txt +0 -0
  41. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/setup.cfg +0 -0
  42. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_api.py +0 -0
  43. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_async_proof.py +0 -0
  44. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_auth.py +0 -0
  45. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_deployables.py +0 -0
  46. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_generate.py +0 -0
  47. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_parser.py +0 -0
  48. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_poly_custom.py +0 -0
  49. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_rendered_spec.py +0 -0
  50. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_schema.py +0 -0
  51. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_tabi.py +0 -0
  52. {polyapi_python-0.3.17.dev1 → polyapi_python-0.3.18.dev1}/tests/test_variables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyapi-python
3
- Version: 0.3.17.dev1
3
+ Version: 0.3.18.dev1
4
4
  Summary: The Python Client for PolyAPI, the IPaaS by Developers for Developers
5
5
  Author-email: Dan Fellin <dan@polyapi.io>
6
6
  License: MIT License
@@ -399,7 +399,21 @@ def clear() -> None:
399
399
 
400
400
  def render_spec(spec: SpecificationDto) -> Tuple[str, str]:
401
401
  function_type = spec["type"]
402
- function_description = spec["description"]
402
+ raw_description = spec.get("description", "")
403
+ def _flatten_description(value: Any) -> List[str]:
404
+ if value is None:
405
+ return []
406
+ if isinstance(value, list):
407
+ flat: List[str] = []
408
+ for item in value:
409
+ flat.extend(_flatten_description(item))
410
+ return flat
411
+ return [str(value)]
412
+
413
+ if isinstance(raw_description, str):
414
+ function_description = raw_description
415
+ else:
416
+ function_description = "\n".join(_flatten_description(raw_description))
403
417
  function_name = spec["name"]
404
418
  function_context = spec["context"]
405
419
  function_id = spec["id"]
@@ -531,10 +531,27 @@ def _render_table(table: TableSpecDto) -> str:
531
531
  table_where_class = _render_table_where_class(
532
532
  table["name"], columns, required_columns
533
533
  )
534
- if table.get("description", ""):
534
+ raw_description = table.get("description", "")
535
+
536
+ def _flatten_description(value: Any) -> List[str]:
537
+ if value is None:
538
+ return []
539
+ if isinstance(value, list):
540
+ flat: List[str] = []
541
+ for item in value:
542
+ flat.extend(_flatten_description(item))
543
+ return flat
544
+ return [str(value)]
545
+
546
+ if isinstance(raw_description, str):
547
+ normalized_description = raw_description
548
+ else:
549
+ normalized_description = "\n".join(_flatten_description(raw_description))
550
+
551
+ if normalized_description:
535
552
  table_description = '\n """'
536
553
  table_description += "\n ".join(
537
- table["description"].replace('"', "'").split("\n")
554
+ normalized_description.replace('"', "'").split("\n")
538
555
  )
539
556
  table_description += '\n """'
540
557
  else:
@@ -50,6 +50,13 @@ def wrapped_generate_schema_types(type_spec: dict, root, fallback_type):
50
50
  # if we have no root, just add "My"
51
51
  root = "My" + root
52
52
 
53
+ if isinstance(root, list):
54
+ root = "_".join([str(x) for x in root if x is not None]) or fallback_type
55
+ elif root is None:
56
+ root = fallback_type
57
+ elif not isinstance(root, str):
58
+ root = str(root)
59
+
53
60
  root = clean_title(root)
54
61
 
55
62
  try:
@@ -150,10 +157,19 @@ def clean_title(title: str) -> str:
150
157
  """ used by library generation, sometimes functions can be added with spaces in the title
151
158
  or other nonsense. fix them!
152
159
  """
160
+ if isinstance(title, list):
161
+ title = "_".join([str(x) for x in title if x is not None])
162
+ elif title is None:
163
+ title = ""
164
+ elif not isinstance(title, str):
165
+ title = str(title)
166
+
153
167
  title = title.replace(" ", "")
154
168
  # certain reserved words cant be titles, let's replace them
155
169
  if title == "List":
156
170
  title = "List_"
171
+ if not title:
172
+ title = "Dict"
157
173
  return title
158
174
 
159
175
 
@@ -62,7 +62,7 @@ def render_server_function(
62
62
  return_type_def=return_type_def,
63
63
  )
64
64
  func_str = SERVER_FUNCTION_TEMPLATE.format(
65
- return_type_name=add_type_import_path(function_name, return_type_name),
65
+ return_type_name=_normalize_return_type_for_annotation(function_name, return_type_name),
66
66
  function_type="server",
67
67
  function_name=function_name,
68
68
  function_id=function_id,
@@ -74,9 +74,23 @@ def render_server_function(
74
74
  return func_str, func_type_defs
75
75
 
76
76
 
77
+ def _normalize_return_type_for_annotation(function_name: str, return_type_name: str) -> str:
78
+ if return_type_name == "ReturnType":
79
+ return "ReturnType"
80
+ return add_type_import_path(function_name, return_type_name)
81
+
82
+
77
83
  def _get_server_return_action(return_type_name: str) -> str:
78
- if return_type_name == "str":
84
+ normalized_type = return_type_name.replace(" ", "")
85
+
86
+ if normalized_type in {"str", "Optional[str]"}:
79
87
  return_action = "resp.text"
88
+ elif "|" in normalized_type:
89
+ union_parts = {part for part in normalized_type.split("|") if part}
90
+ if union_parts == {"str", "None"}:
91
+ return_action = "resp.text"
92
+ else:
93
+ return_action = "resp.json()"
80
94
  else:
81
95
  return_action = "resp.json()"
82
- return return_action
96
+ return return_action
@@ -71,6 +71,43 @@ def print_red(s: str):
71
71
  print(Fore.RED + s + Style.RESET_ALL)
72
72
 
73
73
 
74
+ def normalize_cross_language_type(type_name: str) -> str:
75
+ value = (type_name or "").strip()
76
+ if not value:
77
+ return "Any"
78
+
79
+ primitive_map = {
80
+ "string": "str",
81
+ "number": "float",
82
+ "integer": "int",
83
+ "boolean": "bool",
84
+ "null": "None",
85
+ "void": "None",
86
+ "any": "Any",
87
+ "object": "Dict",
88
+ }
89
+
90
+ if value.startswith("Promise<") and value.endswith(">"):
91
+ return normalize_cross_language_type(value[len("Promise<"):-1])
92
+
93
+ if value.startswith("Awaited<") and value.endswith(">"):
94
+ return normalize_cross_language_type(value[len("Awaited<"):-1])
95
+
96
+ if value.endswith("[]"):
97
+ item_type = normalize_cross_language_type(value[:-2])
98
+ return f"List[{item_type}]"
99
+
100
+ if "|" in value:
101
+ parts = [p.strip() for p in value.split("|") if p.strip()]
102
+ normalized = [normalize_cross_language_type(part) for part in parts]
103
+ return " | ".join(normalized) if normalized else "Any"
104
+
105
+ if value == "ReturnType" or value.startswith("ReturnType<") or "typeof" in value:
106
+ return "Any"
107
+
108
+ return primitive_map.get(value, value)
109
+
110
+
74
111
  def to_type_module_alias(function_name: str) -> str:
75
112
  """Return the internal alias used for a function's generated type module."""
76
113
  return f"_{to_func_namespace(function_name)}_types"
@@ -81,6 +118,14 @@ def add_type_import_path(function_name: str, arg: str) -> str:
81
118
  # from now, we start qualifying non-basic types :))
82
119
  # e.g. Callable[[EmailAddress, Dict, Dict, Dict], None]
83
120
  # becomes Callable[[Set_profile_email.EmailAddress, Dict, Dict, Dict], None]
121
+ arg = normalize_cross_language_type(arg)
122
+
123
+ if "|" in arg:
124
+ return " | ".join(
125
+ add_type_import_path(function_name, token.strip())
126
+ for token in arg.split("|")
127
+ if token.strip()
128
+ )
84
129
  type_module_alias = to_type_module_alias(function_name)
85
130
 
86
131
  if arg.startswith("Callable"):
@@ -96,7 +141,7 @@ def add_type_import_path(function_name: str, arg: str) -> str:
96
141
  return "Callable[" + ",".join(qualified) + "]"
97
142
  # return arg
98
143
 
99
- if arg in BASIC_PYTHON_TYPES:
144
+ if arg == "Any" or arg in BASIC_PYTHON_TYPES:
100
145
  return arg
101
146
 
102
147
  if arg.startswith("List["):
@@ -127,14 +172,23 @@ def get_type_and_def(
127
172
  return "Any", ""
128
173
 
129
174
  if type_spec["kind"] == "plain":
130
- value = type_spec.get("value", "")
175
+ value = normalize_cross_language_type(type_spec.get("value", ""))
176
+
177
+ if "|" in value or value in BASIC_PYTHON_TYPES:
178
+ return value, ""
179
+
131
180
  if value.endswith("[]"):
132
- primitive = map_primitive_types(value[:-2])
181
+ primitive = normalize_cross_language_type(value[:-2])
182
+ if primitive not in BASIC_PYTHON_TYPES:
183
+ primitive = map_primitive_types(primitive)
133
184
  return f"List[{primitive}]", ""
134
185
  else:
135
186
  return map_primitive_types(value), ""
136
187
  elif type_spec["kind"] == "primitive":
137
- return map_primitive_types(type_spec.get("type", "any")), ""
188
+ primitive = normalize_cross_language_type(type_spec.get("type", "any"))
189
+ if primitive in BASIC_PYTHON_TYPES:
190
+ return primitive, ""
191
+ return map_primitive_types(primitive), ""
138
192
  elif type_spec["kind"] == "array":
139
193
  if type_spec.get("items"):
140
194
  items = type_spec["items"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyapi-python
3
- Version: 0.3.17.dev1
3
+ Version: 0.3.18.dev1
4
4
  Summary: The Python Client for PolyAPI, the IPaaS by Developers for Developers
5
5
  Author-email: Dan Fellin <dan@polyapi.io>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "polyapi-python"
7
- version = "0.3.17.dev1"
7
+ version = "0.3.18.dev1"
8
8
  description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
9
9
  authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }]
10
10
  dependencies = [
@@ -66,6 +66,34 @@ LIST_RECOMMENDATIONS = {
66
66
  }
67
67
 
68
68
 
69
+ RETURN_TYPE_NAMED_RETURN_TYPE = {
70
+ "id": "ret-1234",
71
+ "type": "serverFunction",
72
+ "context": "mixed",
73
+ "name": "fooFunc",
74
+ "description": "Return type name collision regression test.",
75
+ "requirements": [],
76
+ "function": {
77
+ "arguments": [],
78
+ "returnType": {
79
+ "kind": "object",
80
+ "schema": {
81
+ "title": "ReturnType",
82
+ "type": "object",
83
+ "properties": {
84
+ "value": {"type": "string"},
85
+ },
86
+ "required": ["value"],
87
+ },
88
+ },
89
+ "synchronous": True,
90
+ },
91
+ "code": "",
92
+ "language": "javascript",
93
+ "visibilityMetadata": {"visibility": "ENVIRONMENT"},
94
+ }
95
+
96
+
69
97
  class T(unittest.TestCase):
70
98
  def test_render_function_twilio_server(self):
71
99
  # same test but try it as a serverFunction rather than an apiFunction
@@ -117,3 +145,67 @@ class T(unittest.TestCase):
117
145
  # stay_date: Required[str]
118
146
  # """ Required property """'''
119
147
  # self.assertIn(expected_return_type, func_str)
148
+
149
+
150
+ def test_render_function_return_type_name_collision_does_not_reference_module_attr(self):
151
+ return_type = RETURN_TYPE_NAMED_RETURN_TYPE["function"]["returnType"]
152
+ func_str, func_type_defs = render_server_function(
153
+ RETURN_TYPE_NAMED_RETURN_TYPE["type"],
154
+ RETURN_TYPE_NAMED_RETURN_TYPE["name"],
155
+ RETURN_TYPE_NAMED_RETURN_TYPE["id"],
156
+ RETURN_TYPE_NAMED_RETURN_TYPE["description"],
157
+ RETURN_TYPE_NAMED_RETURN_TYPE["function"]["arguments"],
158
+ return_type,
159
+ )
160
+ self.assertIn("-> dict", func_str)
161
+ self.assertNotIn(".returnType", func_str)
162
+ self.assertNotIn(".ReturnType", func_str)
163
+
164
+ def test_render_function_string_union_returns_text(self):
165
+ function_name = "getMaybeName"
166
+ return_type = {"kind": "plain", "value": "Promise<string | null>"}
167
+
168
+ func_str, _ = render_server_function(
169
+ "serverFunction",
170
+ function_name,
171
+ "ret-str-null-1",
172
+ "",
173
+ [],
174
+ return_type,
175
+ )
176
+
177
+ self.assertIn("-> str | None", func_str)
178
+ self.assertIn("try:\n return resp.text", func_str)
179
+ self.assertNotIn("return resp.json()", func_str)
180
+
181
+ def test_render_function_mixed_string_union_returns_json(self):
182
+ function_name = "getMaybePayload"
183
+ return_type = {"kind": "plain", "value": "string | object"}
184
+
185
+ func_str, _ = render_server_function(
186
+ "serverFunction",
187
+ function_name,
188
+ "ret-str-obj-1",
189
+ "",
190
+ [],
191
+ return_type,
192
+ )
193
+
194
+ self.assertIn("-> str | Dict", func_str)
195
+ self.assertIn("try:\n return resp.json()", func_str)
196
+
197
+ def test_render_function_number_nullable_returns_json(self):
198
+ function_name = "getMaybeCount"
199
+ return_type = {"kind": "plain", "value": "number | null"}
200
+
201
+ func_str, _ = render_server_function(
202
+ "serverFunction",
203
+ function_name,
204
+ "ret-num-null-1",
205
+ "",
206
+ [],
207
+ return_type,
208
+ )
209
+
210
+ self.assertIn("-> float | None", func_str)
211
+ self.assertIn("try:\n return resp.json()", func_str)
@@ -1,5 +1,5 @@
1
1
  import unittest
2
- from polyapi.utils import get_type_and_def, rewrite_reserved
2
+ from polyapi.utils import add_type_import_path, get_type_and_def, rewrite_reserved
3
3
 
4
4
  OPENAPI_FUNCTION = {
5
5
  "kind": "function",
@@ -84,3 +84,26 @@ class T(unittest.TestCase):
84
84
  def test_rewrite_reserved(self):
85
85
  rv = rewrite_reserved("from")
86
86
  self.assertEqual(rv, "_from")
87
+
88
+ def test_plain_return_type_utility_normalizes_to_any(self):
89
+ arg_type, arg_def = get_type_and_def({"kind": "plain", "value": "ReturnType<typeof fooFunc>"})
90
+ self.assertEqual(arg_type, "Any")
91
+ self.assertEqual(arg_def, "")
92
+
93
+ def test_plain_promise_union_normalizes_to_python_union(self):
94
+ arg_type, arg_def = get_type_and_def({"kind": "plain", "value": "Promise<string | null>"})
95
+ self.assertEqual(arg_type, "str | None")
96
+ self.assertEqual(arg_def, "")
97
+
98
+ def test_add_type_import_path_never_qualifies_return_type_utility(self):
99
+ arg_type = add_type_import_path("fooFunc", "ReturnType<typeof fooFunc>")
100
+ self.assertEqual(arg_type, "Any")
101
+
102
+ def test_plain_promise_array_union_normalizes_to_python_union(self):
103
+ arg_type, arg_def = get_type_and_def({"kind": "plain", "value": "Promise<string[] | null>"})
104
+ self.assertEqual(arg_type, "List[str] | None")
105
+ self.assertEqual(arg_def, "")
106
+
107
+ def test_add_type_import_path_keeps_array_union_primitives_valid(self):
108
+ arg_type = add_type_import_path("fooFunc", "Promise<string[] | null>")
109
+ self.assertEqual(arg_type, "List[str] | None")