polyapi-python 0.3.7.dev2__tar.gz → 0.3.7.dev4__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 (47) hide show
  1. {polyapi_python-0.3.7.dev2/polyapi_python.egg-info → polyapi_python-0.3.7.dev4}/PKG-INFO +1 -1
  2. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/function_cli.py +1 -1
  3. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/generate.py +105 -28
  4. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/utils.py +58 -14
  5. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4/polyapi_python.egg-info}/PKG-INFO +1 -1
  6. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/pyproject.toml +1 -1
  7. polyapi_python-0.3.7.dev4/tests/test_generate.py +289 -0
  8. polyapi_python-0.3.7.dev2/tests/test_generate.py +0 -83
  9. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/LICENSE +0 -0
  10. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/README.md +0 -0
  11. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/__init__.py +0 -0
  12. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/__main__.py +0 -0
  13. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/api.py +0 -0
  14. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/auth.py +0 -0
  15. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/cli.py +0 -0
  16. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/client.py +0 -0
  17. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/config.py +0 -0
  18. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/constants.py +0 -0
  19. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/deployables.py +0 -0
  20. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/error_handler.py +0 -0
  21. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/exceptions.py +0 -0
  22. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/execute.py +0 -0
  23. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/parser.py +0 -0
  24. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/poly_schemas.py +0 -0
  25. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/prepare.py +0 -0
  26. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/py.typed +0 -0
  27. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/rendered_spec.py +0 -0
  28. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/schema.py +0 -0
  29. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/server.py +0 -0
  30. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/sync.py +0 -0
  31. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/typedefs.py +0 -0
  32. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/variables.py +0 -0
  33. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi/webhook.py +0 -0
  34. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi_python.egg-info/SOURCES.txt +0 -0
  35. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi_python.egg-info/dependency_links.txt +0 -0
  36. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi_python.egg-info/requires.txt +0 -0
  37. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/polyapi_python.egg-info/top_level.txt +0 -0
  38. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/setup.cfg +0 -0
  39. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_api.py +0 -0
  40. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_auth.py +0 -0
  41. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_deployables.py +0 -0
  42. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_parser.py +0 -0
  43. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_rendered_spec.py +0 -0
  44. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_schema.py +0 -0
  45. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_server.py +0 -0
  46. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/tests/test_utils.py +0 -0
  47. {polyapi_python-0.3.7.dev2 → polyapi_python-0.3.7.dev4}/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.7.dev2
3
+ Version: 0.3.7.dev4
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
@@ -86,7 +86,7 @@ def function_add_or_update(
86
86
 
87
87
  headers = get_auth_headers(api_key)
88
88
  resp = requests.post(url, headers=headers, json=data)
89
- if resp.status_code == 201:
89
+ if resp.status_code in [200, 201]:
90
90
  print_green("DEPLOYED")
91
91
  function_id = resp.json()["id"]
92
92
  print(f"Function ID: {function_id}")
@@ -129,24 +129,26 @@ def parse_function_specs(
129
129
  ) -> List[SpecificationDto]:
130
130
  functions = []
131
131
  for spec in specs:
132
- if not spec or "function" not in spec:
133
- continue
134
-
135
- if not spec["function"]:
136
- continue
137
-
138
- if limit_ids and spec["id"] not in limit_ids:
132
+ if not spec:
139
133
  continue
140
134
 
135
+ # For no_types mode, we might not have function data, but we still want to include the spec
136
+ # if it's a supported function type
141
137
  if spec["type"] not in SUPPORTED_FUNCTION_TYPES:
142
138
  continue
143
139
 
144
- if spec["type"] == "customFunction" and spec["language"] != "python":
145
- # poly libraries only support client functions of same language
140
+ # Skip if we have a limit and this spec is not in it
141
+ if limit_ids and spec.get("id") not in limit_ids:
146
142
  continue
147
143
 
144
+ # For customFunction, check language if we have function data
145
+ if spec["type"] == "customFunction":
146
+ if spec.get("language") and spec["language"] != "python":
147
+ # poly libraries only support client functions of same language
148
+ continue
149
+
148
150
  # Functions with serverSideAsync True will always return a Dict with execution ID
149
- if spec.get('serverSideAsync'):
151
+ if spec.get('serverSideAsync') and spec.get("function"):
150
152
  spec['function']['returnType'] = {'kind': 'plain', 'value': 'object'}
151
153
 
152
154
  functions.append(spec)
@@ -205,6 +207,63 @@ def remove_old_library():
205
207
  shutil.rmtree(path)
206
208
 
207
209
 
210
+ def create_empty_schemas_module():
211
+ """Create an empty schemas module for no-types mode so user code can still import from polyapi.schemas"""
212
+ currdir = os.path.dirname(os.path.abspath(__file__))
213
+ schemas_path = os.path.join(currdir, "schemas")
214
+
215
+ # Create the schemas directory
216
+ if not os.path.exists(schemas_path):
217
+ os.makedirs(schemas_path)
218
+
219
+ # Create an __init__.py file with dynamic schema resolution
220
+ init_path = os.path.join(schemas_path, "__init__.py")
221
+ with open(init_path, "w") as f:
222
+ f.write('''"""Empty schemas module for no-types mode"""
223
+ from typing import Any, Dict
224
+
225
+ class _GenericSchema(Dict[str, Any]):
226
+ """Generic schema type that acts like a Dict for no-types mode"""
227
+ def __init__(self, *args, **kwargs):
228
+ super().__init__(*args, **kwargs)
229
+
230
+ class _SchemaModule:
231
+ """Dynamic module that returns itself for attribute access, allowing infinite nesting"""
232
+
233
+ def __getattr__(self, name: str):
234
+ # For callable access (like schemas.Response()), return the generic schema class
235
+ # For further attribute access (like schemas.random.random2), return self to allow nesting
236
+ return _NestedSchemaAccess()
237
+
238
+ def __call__(self, *args, **kwargs):
239
+ # If someone tries to call the module itself, return a generic schema
240
+ return _GenericSchema(*args, **kwargs)
241
+
242
+ def __dir__(self):
243
+ # Return common schema names for introspection
244
+ return ['Response', 'Request', 'Error', 'Data', 'Result']
245
+
246
+ class _NestedSchemaAccess:
247
+ """Handles nested attribute access and final callable resolution"""
248
+
249
+ def __getattr__(self, name: str):
250
+ # Continue allowing nested access
251
+ return _NestedSchemaAccess()
252
+
253
+ def __call__(self, *args, **kwargs):
254
+ # When finally called, return a generic schema instance
255
+ return _GenericSchema(*args, **kwargs)
256
+
257
+ def __class_getitem__(cls, item):
258
+ # Support type annotations like schemas.Response[str]
259
+ return _GenericSchema
260
+
261
+ # Replace this module with our dynamic module
262
+ import sys
263
+ sys.modules[__name__] = _SchemaModule()
264
+ ''')
265
+
266
+
208
267
  def generate(contexts: Optional[List[str]] = None, no_types: bool = False) -> None:
209
268
  generate_msg = f"Generating Poly Python SDK for contexts ${contexts}..." if contexts else "Generating Poly Python SDK..."
210
269
  print(generate_msg, end="", flush=True)
@@ -216,14 +275,23 @@ def generate(contexts: Optional[List[str]] = None, no_types: bool = False) -> No
216
275
  limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
217
276
  functions = parse_function_specs(specs, limit_ids=limit_ids)
218
277
 
219
- schemas = get_schemas()
220
- schema_index = build_schema_index(schemas)
221
- if schemas:
222
- schema_limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
223
- schemas = replace_poly_refs_in_schemas(schemas, schema_index)
224
- generate_schemas(schemas, limit_ids=schema_limit_ids)
225
-
226
- functions = replace_poly_refs_in_functions(functions, schema_index)
278
+ # Only process schemas if no_types is False
279
+ if not no_types:
280
+ schemas = get_schemas()
281
+ schema_index = build_schema_index(schemas)
282
+ if schemas:
283
+ schema_limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
284
+ schemas = replace_poly_refs_in_schemas(schemas, schema_index)
285
+ generate_schemas(schemas, limit_ids=schema_limit_ids)
286
+
287
+ functions = replace_poly_refs_in_functions(functions, schema_index)
288
+ else:
289
+ # When no_types is True, we still need to process functions but without schema resolution
290
+ # Use an empty schema index to avoid poly-ref resolution
291
+ schema_index = {}
292
+
293
+ # Create an empty schemas module so user code can still import from polyapi.schemas
294
+ create_empty_schemas_module()
227
295
 
228
296
  if functions:
229
297
  generate_functions(functions)
@@ -233,10 +301,11 @@ def generate(contexts: Optional[List[str]] = None, no_types: bool = False) -> No
233
301
  )
234
302
  exit()
235
303
 
236
- variables = get_variables()
237
- if variables:
238
- generate_variables(variables)
239
-
304
+ # Only process variables if no_types is False
305
+ if not no_types:
306
+ variables = get_variables()
307
+ if variables:
308
+ generate_variables(variables)
240
309
 
241
310
  # indicator to vscode extension that this is a polyapi-python project
242
311
  file_path = os.path.join(os.getcwd(), ".polyapi-python")
@@ -266,11 +335,19 @@ def render_spec(spec: SpecificationDto) -> Tuple[str, str]:
266
335
 
267
336
  arguments: List[PropertySpecification] = []
268
337
  return_type = {}
269
- if spec["function"]:
270
- arguments = [
271
- arg for arg in spec["function"]["arguments"]
272
- ]
273
- return_type = spec["function"]["returnType"]
338
+ if spec.get("function"):
339
+ # Handle cases where arguments might be missing or None
340
+ if spec["function"].get("arguments"):
341
+ arguments = [
342
+ arg for arg in spec["function"]["arguments"]
343
+ ]
344
+
345
+ # Handle cases where returnType might be missing or None
346
+ if spec["function"].get("returnType"):
347
+ return_type = spec["function"]["returnType"]
348
+ else:
349
+ # Provide a fallback return type when missing
350
+ return_type = {"kind": "any"}
274
351
 
275
352
  if function_type == "apiFunction":
276
353
  func_str, func_type_defs = render_api_function(
@@ -284,7 +361,7 @@ def render_spec(spec: SpecificationDto) -> Tuple[str, str]:
284
361
  elif function_type == "customFunction":
285
362
  func_str, func_type_defs = render_client_function(
286
363
  function_name,
287
- spec["code"],
364
+ spec.get("code", ""),
288
365
  arguments,
289
366
  return_type,
290
367
  )
@@ -97,20 +97,32 @@ def get_type_and_def(
97
97
  ) -> Tuple[str, str]:
98
98
  """ returns type and type definition for a given PropertyType
99
99
  """
100
+ # Handle cases where type_spec might be None or empty
101
+ if not type_spec:
102
+ return "Any", ""
103
+
104
+ # Handle cases where kind might be missing
105
+ if "kind" not in type_spec:
106
+ return "Any", ""
107
+
100
108
  if type_spec["kind"] == "plain":
101
- value = type_spec["value"]
109
+ value = type_spec.get("value", "")
102
110
  if value.endswith("[]"):
103
111
  primitive = map_primitive_types(value[:-2])
104
112
  return f"List[{primitive}]", ""
105
113
  else:
106
114
  return map_primitive_types(value), ""
107
115
  elif type_spec["kind"] == "primitive":
108
- return map_primitive_types(type_spec["type"]), ""
116
+ return map_primitive_types(type_spec.get("type", "any")), ""
109
117
  elif type_spec["kind"] == "array":
110
118
  if type_spec.get("items"):
111
119
  items = type_spec["items"]
112
120
  if items.get("$ref"):
113
- return wrapped_generate_schema_types(type_spec, "ResponseType", "Dict") # type: ignore
121
+ # For no-types mode, avoid complex schema generation
122
+ try:
123
+ return wrapped_generate_schema_types(type_spec, "ResponseType", "Dict") # type: ignore
124
+ except:
125
+ return "List[Dict]", ""
114
126
  else:
115
127
  item_type, _ = get_type_and_def(items)
116
128
  title = f"List[{item_type}]"
@@ -130,13 +142,20 @@ def get_type_and_def(
130
142
  return "List", ""
131
143
  elif title:
132
144
  assert isinstance(title, str)
133
- return wrapped_generate_schema_types(schema, title, "Dict") # type: ignore
145
+ # For no-types mode, avoid complex schema generation
146
+ try:
147
+ return wrapped_generate_schema_types(schema, title, "Dict") # type: ignore
148
+ except:
149
+ return "Dict", ""
134
150
  elif schema.get("allOf") and len(schema["allOf"]):
135
151
  # we are in a case of a single allOf, lets strip off the allOf and move on!
136
152
  # our library doesn't handle allOf well yet
137
153
  allOf = schema["allOf"][0]
138
154
  title = allOf.get("title", allOf.get("name", title_fallback))
139
- return wrapped_generate_schema_types(allOf, title, "Dict")
155
+ try:
156
+ return wrapped_generate_schema_types(allOf, title, "Dict")
157
+ except:
158
+ return "Dict", ""
140
159
  elif schema.get("items"):
141
160
  # fallback to schema $ref name if no explicit title
142
161
  items = schema.get("items") # type: ignore
@@ -150,9 +169,15 @@ def get_type_and_def(
150
169
  return "List", ""
151
170
 
152
171
  title = f"List[{title}]"
153
- return wrapped_generate_schema_types(schema, title, "List")
172
+ try:
173
+ return wrapped_generate_schema_types(schema, title, "List")
174
+ except:
175
+ return "List[Dict]", ""
176
+ elif schema.get("properties"):
177
+ result = wrapped_generate_schema_types(schema, "ResponseType", "Dict") # type: ignore
178
+ return result
154
179
  else:
155
- return "Any", ""
180
+ return "Dict", ""
156
181
  else:
157
182
  return "Dict", ""
158
183
  elif type_spec["kind"] == "function":
@@ -187,12 +212,22 @@ def get_type_and_def(
187
212
 
188
213
 
189
214
  def _maybe_add_fallback_schema_name(a: PropertySpecification):
190
- if a["type"]["kind"] == "object" and a["type"].get("schema"):
215
+ # Handle cases where type might be missing
216
+ if not a.get("type"):
217
+ return
218
+
219
+ if a["type"].get("kind") == "object" and a["type"].get("schema"):
191
220
  schema = a["type"].get("schema", {})
192
- if not schema.get("title") and not schema.get("name") and a["name"]:
221
+ if not schema.get("title") and not schema.get("name") and a.get("name"):
193
222
  schema["title"] = a["name"].title()
194
223
 
195
224
 
225
+ def _clean_description(text: str) -> str:
226
+ """Flatten new-lines and collapse excess whitespace."""
227
+ text = text.replace("\\n", " ").replace("\n", " ")
228
+ return re.sub(r"\s+", " ", text).strip()
229
+
230
+
196
231
  def parse_arguments(
197
232
  function_name: str, arguments: List[PropertySpecification]
198
233
  ) -> Tuple[str, str]:
@@ -200,18 +235,27 @@ def parse_arguments(
200
235
  arg_string = ""
201
236
  for idx, a in enumerate(arguments):
202
237
  _maybe_add_fallback_schema_name(a)
203
- arg_type, arg_def = get_type_and_def(a["type"])
238
+
239
+ # Handle cases where type might be missing
240
+ arg_type_spec = a.get("type", {"kind": "any"})
241
+ arg_type, arg_def = get_type_and_def(arg_type_spec)
204
242
  if arg_def:
205
243
  args_def.append(arg_def)
206
- a["name"] = rewrite_arg_name(a["name"])
244
+
245
+ # Handle cases where name might be missing
246
+ arg_name = a.get("name", f"arg{idx}")
247
+ a["name"] = rewrite_arg_name(arg_name)
248
+
207
249
  arg_string += (
208
250
  f" {a['name']}: {add_type_import_path(function_name, arg_type)}"
209
251
  )
210
- if not a["required"]:
252
+
253
+ # Handle cases where required might be missing
254
+ if not a.get("required", True):
211
255
  arg_string += " = None"
212
256
 
213
- description = a.get("description", "")
214
- description = description.replace("\n", " ")
257
+ description = _clean_description(a.get("description", ""))
258
+
215
259
  if description:
216
260
  if idx == len(arguments) - 1:
217
261
  arg_string += f" # {description}\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyapi-python
3
- Version: 0.3.7.dev2
3
+ Version: 0.3.7.dev4
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
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.2", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "polyapi-python"
6
- version = "0.3.7.dev2"
6
+ version = "0.3.7.dev4"
7
7
  description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
8
8
  authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }]
9
9
  dependencies = [
@@ -0,0 +1,289 @@
1
+ import unittest
2
+ import os
3
+ import shutil
4
+ import importlib.util
5
+ from polyapi.utils import get_type_and_def, rewrite_reserved
6
+ from polyapi.generate import render_spec, create_empty_schemas_module
7
+
8
+ OPENAPI_FUNCTION = {
9
+ "kind": "function",
10
+ "spec": {
11
+ "arguments": [
12
+ {
13
+ "name": "event",
14
+ "required": False,
15
+ "type": {
16
+ "kind": "object",
17
+ "schema": {
18
+ "$schema": "http://json-schema.org/draft-06/schema#",
19
+ "type": "array",
20
+ "items": {"$ref": "#/definitions/WebhookEventTypeElement"},
21
+ "definitions": {
22
+ "WebhookEventTypeElement": {
23
+ "type": "object",
24
+ "additionalProperties": False,
25
+ "properties": {
26
+ "title": {"type": "string"},
27
+ "manufacturerName": {"type": "string"},
28
+ "carType": {"type": "string"},
29
+ "id": {"type": "integer"},
30
+ },
31
+ "required": [
32
+ "carType",
33
+ "id",
34
+ "manufacturerName",
35
+ "title",
36
+ ],
37
+ "title": "WebhookEventTypeElement",
38
+ }
39
+ },
40
+ },
41
+ },
42
+ },
43
+ {
44
+ "name": "headers",
45
+ "required": False,
46
+ "type": {"kind": "object", "typeName": "Record<string, any>"},
47
+ },
48
+ {
49
+ "name": "params",
50
+ "required": False,
51
+ "type": {"kind": "object", "typeName": "Record<string, any>"},
52
+ },
53
+ {
54
+ "name": "polyCustom",
55
+ "required": False,
56
+ "type": {
57
+ "kind": "object",
58
+ "properties": [
59
+ {
60
+ "name": "responseStatusCode",
61
+ "type": {"type": "number", "kind": "primitive"},
62
+ "required": True,
63
+ },
64
+ {
65
+ "name": "responseContentType",
66
+ "type": {"type": "string", "kind": "primitive"},
67
+ "required": True,
68
+ "nullable": True,
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ ],
74
+ "returnType": {"kind": "void"},
75
+ "synchronous": True,
76
+ },
77
+ }
78
+
79
+ # Test spec with missing function data (simulating no_types=true)
80
+ NO_TYPES_SPEC = {
81
+ "id": "test-id-123",
82
+ "type": "serverFunction",
83
+ "context": "test",
84
+ "name": "testFunction",
85
+ "description": "A test function for no-types mode",
86
+ # Note: no "function" field, simulating no_types=true response
87
+ }
88
+
89
+ # Test spec with minimal function data
90
+ MINIMAL_FUNCTION_SPEC = {
91
+ "id": "test-id-456",
92
+ "type": "apiFunction",
93
+ "context": "test",
94
+ "name": "minimalFunction",
95
+ "description": "A minimal function spec",
96
+ "function": {
97
+ # Note: no "arguments" or "returnType" fields
98
+ }
99
+ }
100
+
101
+
102
+ class T(unittest.TestCase):
103
+ def test_get_type_and_def(self):
104
+ arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION)
105
+ self.assertEqual(arg_type, "Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]")
106
+
107
+ def test_rewrite_reserved(self):
108
+ rv = rewrite_reserved("from")
109
+ self.assertEqual(rv, "_from")
110
+
111
+ def test_render_spec_no_function_data(self):
112
+ """Test that render_spec handles specs with no function data gracefully"""
113
+ func_str, func_type_defs = render_spec(NO_TYPES_SPEC)
114
+
115
+ # Should generate a function even without function data
116
+ self.assertIsNotNone(func_str)
117
+ self.assertIsNotNone(func_type_defs)
118
+ self.assertIn("testFunction", func_str)
119
+ self.assertIn("test-id-123", func_str)
120
+
121
+ def test_render_spec_minimal_function_data(self):
122
+ """Test that render_spec handles specs with minimal function data"""
123
+ func_str, func_type_defs = render_spec(MINIMAL_FUNCTION_SPEC)
124
+
125
+ # Should generate a function with fallback types
126
+ self.assertIsNotNone(func_str)
127
+ self.assertIsNotNone(func_type_defs)
128
+ self.assertIn("minimalFunction", func_str)
129
+ self.assertIn("test-id-456", func_str)
130
+ # Should use Any as fallback return type in the type definitions
131
+ self.assertIn("Any", func_type_defs)
132
+
133
+ def test_create_empty_schemas_module(self):
134
+ """Test that create_empty_schemas_module creates the necessary files"""
135
+ # Clean up any existing schemas directory
136
+ schemas_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "polyapi", "schemas")
137
+ if os.path.exists(schemas_path):
138
+ shutil.rmtree(schemas_path)
139
+
140
+ # Create empty schemas module
141
+ create_empty_schemas_module()
142
+
143
+ # Verify the directory and __init__.py file were created
144
+ self.assertTrue(os.path.exists(schemas_path))
145
+ init_path = os.path.join(schemas_path, "__init__.py")
146
+ self.assertTrue(os.path.exists(init_path))
147
+
148
+ # Verify the content of __init__.py includes dynamic schema handling
149
+ with open(init_path, "r") as f:
150
+ content = f.read()
151
+ self.assertIn("Empty schemas module for no-types mode", content)
152
+ self.assertIn("_GenericSchema", content)
153
+ self.assertIn("_SchemaModule", content)
154
+ self.assertIn("__getattr__", content)
155
+
156
+ # Clean up
157
+ shutil.rmtree(schemas_path)
158
+
159
+ def test_no_types_workflow(self):
160
+ """Test the complete no-types workflow including schema imports and function parsing"""
161
+ import tempfile
162
+ import sys
163
+ from unittest.mock import patch
164
+
165
+ # Clean up any existing schemas directory
166
+ schemas_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "polyapi", "schemas")
167
+ if os.path.exists(schemas_path):
168
+ shutil.rmtree(schemas_path)
169
+
170
+ # Mock get_specs to return empty list (simulating no functions)
171
+ with patch('polyapi.generate.get_specs', return_value=[]):
172
+ try:
173
+ # This should exit with SystemExit due to no functions
174
+ from polyapi.generate import generate
175
+ generate(no_types=True)
176
+ except SystemExit:
177
+ pass # Expected when no functions exist
178
+
179
+ # Verify schemas module was created
180
+ self.assertTrue(os.path.exists(schemas_path))
181
+ init_path = os.path.join(schemas_path, "__init__.py")
182
+ self.assertTrue(os.path.exists(init_path))
183
+
184
+ # Test that we can import schemas and use arbitrary schema names
185
+ from polyapi import schemas
186
+
187
+ # Test various schema access
188
+ Response = schemas.Response
189
+ CustomType = schemas.CustomType
190
+ AnyName = schemas.SomeArbitrarySchemaName
191
+
192
+ # All should return the same generic schema class type
193
+ self.assertEqual(type(Response).__name__, '_NestedSchemaAccess')
194
+ self.assertEqual(type(CustomType).__name__, '_NestedSchemaAccess')
195
+ self.assertEqual(type(AnyName).__name__, '_NestedSchemaAccess')
196
+
197
+ # Test creating instances
198
+ response_instance = Response()
199
+ custom_instance = CustomType()
200
+
201
+ self.assertIsInstance(response_instance, dict)
202
+ self.assertIsInstance(custom_instance, dict)
203
+
204
+ # Test that function code with schema references can be parsed
205
+ test_code = '''
206
+ from polyapi import polyCustom, schemas
207
+
208
+ def test_function() -> schemas.Response:
209
+ polyCustom["executionId"] = "123"
210
+ return polyCustom
211
+ '''
212
+
213
+ # Create a temporary file with the test code
214
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
215
+ f.write(test_code)
216
+ temp_file = f.name
217
+
218
+ try:
219
+ # Test that the parser can handle this code
220
+ from polyapi.parser import parse_function_code
221
+ result = parse_function_code(test_code, 'test_function', 'test_context')
222
+
223
+ self.assertEqual(result['name'], 'test_function')
224
+ self.assertEqual(result['context'], 'test_context')
225
+ # Return type should be Any since we're in no-types mode
226
+ self.assertEqual(result['types']['returns']['type'], 'Any')
227
+
228
+ finally:
229
+ # Clean up temp file
230
+ os.unlink(temp_file)
231
+
232
+ # Clean up schemas directory
233
+ shutil.rmtree(schemas_path)
234
+
235
+ def test_nested_schema_access(self):
236
+ """Test that nested schema access like schemas.random.random2.random3 works"""
237
+ # Clean up any existing schemas directory
238
+ schemas_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "polyapi", "schemas")
239
+ if os.path.exists(schemas_path):
240
+ shutil.rmtree(schemas_path)
241
+
242
+ # Create empty schemas module
243
+ create_empty_schemas_module()
244
+
245
+ # Test that we can import and use nested schemas
246
+ from polyapi import schemas
247
+
248
+ # Test various levels of nesting
249
+ simple = schemas.Response
250
+ nested = schemas.random.random2
251
+ deep_nested = schemas.api.v1.user.profile.data
252
+ very_deep = schemas.some.very.deep.nested.schema.access
253
+
254
+ # All should be _NestedSchemaAccess instances
255
+ self.assertEqual(type(simple).__name__, '_NestedSchemaAccess')
256
+ self.assertEqual(type(nested).__name__, '_NestedSchemaAccess')
257
+ self.assertEqual(type(deep_nested).__name__, '_NestedSchemaAccess')
258
+ self.assertEqual(type(very_deep).__name__, '_NestedSchemaAccess')
259
+
260
+ # Test that they can be called and return generic schemas
261
+ simple_instance = simple()
262
+ nested_instance = nested()
263
+ deep_instance = deep_nested()
264
+ very_deep_instance = very_deep()
265
+
266
+ # All should be dictionaries
267
+ self.assertIsInstance(simple_instance, dict)
268
+ self.assertIsInstance(nested_instance, dict)
269
+ self.assertIsInstance(deep_instance, dict)
270
+ self.assertIsInstance(very_deep_instance, dict)
271
+
272
+ # Test that function code with nested schemas can be parsed
273
+ test_code = '''
274
+ from polyapi import polyCustom, schemas
275
+
276
+ def test_nested_function() -> schemas.api.v1.user.profile:
277
+ return schemas.api.v1.user.profile()
278
+ '''
279
+
280
+ from polyapi.parser import parse_function_code
281
+ result = parse_function_code(test_code, 'test_nested_function', 'test_context')
282
+
283
+ self.assertEqual(result['name'], 'test_nested_function')
284
+ self.assertEqual(result['context'], 'test_context')
285
+ # Return type should be Any since we're in no-types mode
286
+ self.assertEqual(result['types']['returns']['type'], 'Any')
287
+
288
+ # Clean up schemas directory
289
+ shutil.rmtree(schemas_path)
@@ -1,83 +0,0 @@
1
- import unittest
2
- from polyapi.utils import get_type_and_def, rewrite_reserved
3
-
4
- OPENAPI_FUNCTION = {
5
- "kind": "function",
6
- "spec": {
7
- "arguments": [
8
- {
9
- "name": "event",
10
- "required": False,
11
- "type": {
12
- "kind": "object",
13
- "schema": {
14
- "$schema": "http://json-schema.org/draft-06/schema#",
15
- "type": "array",
16
- "items": {"$ref": "#/definitions/WebhookEventTypeElement"},
17
- "definitions": {
18
- "WebhookEventTypeElement": {
19
- "type": "object",
20
- "additionalProperties": False,
21
- "properties": {
22
- "title": {"type": "string"},
23
- "manufacturerName": {"type": "string"},
24
- "carType": {"type": "string"},
25
- "id": {"type": "integer"},
26
- },
27
- "required": [
28
- "carType",
29
- "id",
30
- "manufacturerName",
31
- "title",
32
- ],
33
- "title": "WebhookEventTypeElement",
34
- }
35
- },
36
- },
37
- },
38
- },
39
- {
40
- "name": "headers",
41
- "required": False,
42
- "type": {"kind": "object", "typeName": "Record<string, any>"},
43
- },
44
- {
45
- "name": "params",
46
- "required": False,
47
- "type": {"kind": "object", "typeName": "Record<string, any>"},
48
- },
49
- {
50
- "name": "polyCustom",
51
- "required": False,
52
- "type": {
53
- "kind": "object",
54
- "properties": [
55
- {
56
- "name": "responseStatusCode",
57
- "type": {"type": "number", "kind": "primitive"},
58
- "required": True,
59
- },
60
- {
61
- "name": "responseContentType",
62
- "type": {"type": "string", "kind": "primitive"},
63
- "required": True,
64
- "nullable": True,
65
- },
66
- ],
67
- },
68
- },
69
- ],
70
- "returnType": {"kind": "void"},
71
- "synchronous": True,
72
- },
73
- }
74
-
75
-
76
- class T(unittest.TestCase):
77
- def test_get_type_and_def(self):
78
- arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION)
79
- self.assertEqual(arg_type, "Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]")
80
-
81
- def test_rewrite_reserved(self):
82
- rv = rewrite_reserved("from")
83
- self.assertEqual(rv, "_from")