polyapi-python 0.3.7.dev1__tar.gz → 0.3.7.dev3__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.
- {polyapi_python-0.3.7.dev1/polyapi_python.egg-info → polyapi_python-0.3.7.dev3}/PKG-INFO +1 -1
- polyapi_python-0.3.7.dev3/polyapi/__init__.py +92 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/function_cli.py +1 -1
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/generate.py +105 -28
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/utils.py +50 -12
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3/polyapi_python.egg-info}/PKG-INFO +1 -1
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/pyproject.toml +1 -1
- polyapi_python-0.3.7.dev3/tests/test_generate.py +289 -0
- polyapi_python-0.3.7.dev1/polyapi/__init__.py +0 -23
- polyapi_python-0.3.7.dev1/tests/test_generate.py +0 -83
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/LICENSE +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/README.md +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/api.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/auth.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/cli.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/client.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/config.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/deployables.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/execute.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/parser.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/poly_schemas.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/prepare.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/rendered_spec.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/schema.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/server.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/sync.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/typedefs.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/variables.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi/webhook.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi_python.egg-info/SOURCES.txt +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi_python.egg-info/requires.txt +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/setup.cfg +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_api.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_deployables.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_parser.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_rendered_spec.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_schema.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_server.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_utils.py +0 -0
- {polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/tests/test_variables.py +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import truststore
|
|
4
|
+
from typing import Any, Optional, overload, Literal
|
|
5
|
+
from typing_extensions import TypedDict
|
|
6
|
+
truststore.inject_into_ssl()
|
|
7
|
+
from .cli import CLI_COMMANDS
|
|
8
|
+
|
|
9
|
+
__all__ = ["poly"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if len(sys.argv) > 1 and sys.argv[1] not in CLI_COMMANDS:
|
|
13
|
+
currdir = os.path.dirname(os.path.abspath(__file__))
|
|
14
|
+
if not os.path.isdir(os.path.join(currdir, "poly")):
|
|
15
|
+
print("No 'poly' found. Please run 'python3 -m polyapi generate' to generate the 'poly' library for your tenant.")
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PolyCustomDict(TypedDict, total=False):
|
|
20
|
+
"""Type definition for polyCustom dictionary."""
|
|
21
|
+
executionId: Optional[str] # Read-only
|
|
22
|
+
executionApiKey: Optional[str]
|
|
23
|
+
responseStatusCode: int
|
|
24
|
+
responseContentType: Optional[str]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _PolyCustom:
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self._internal_store = {
|
|
30
|
+
"executionId": None,
|
|
31
|
+
"executionApiKey": None,
|
|
32
|
+
"responseStatusCode": 200,
|
|
33
|
+
"responseContentType": None,
|
|
34
|
+
}
|
|
35
|
+
self._execution_id_locked = False
|
|
36
|
+
|
|
37
|
+
def set_once(self, key: str, value: Any) -> None:
|
|
38
|
+
if key == "executionId" and self._execution_id_locked:
|
|
39
|
+
# Silently ignore attempts to overwrite locked executionId
|
|
40
|
+
return
|
|
41
|
+
self._internal_store[key] = value
|
|
42
|
+
if key == "executionId":
|
|
43
|
+
# Lock executionId after setting it
|
|
44
|
+
self.lock_execution_id()
|
|
45
|
+
|
|
46
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
47
|
+
return self._internal_store.get(key, default)
|
|
48
|
+
|
|
49
|
+
def lock_execution_id(self) -> None:
|
|
50
|
+
self._execution_id_locked = True
|
|
51
|
+
|
|
52
|
+
def unlock_execution_id(self) -> None:
|
|
53
|
+
self._execution_id_locked = False
|
|
54
|
+
|
|
55
|
+
@overload
|
|
56
|
+
def __getitem__(self, key: Literal["executionId"]) -> Optional[str]: ...
|
|
57
|
+
|
|
58
|
+
@overload
|
|
59
|
+
def __getitem__(self, key: Literal["executionApiKey"]) -> Optional[str]: ...
|
|
60
|
+
|
|
61
|
+
@overload
|
|
62
|
+
def __getitem__(self, key: Literal["responseStatusCode"]) -> int: ...
|
|
63
|
+
|
|
64
|
+
@overload
|
|
65
|
+
def __getitem__(self, key: Literal["responseContentType"]) -> Optional[str]: ...
|
|
66
|
+
|
|
67
|
+
def __getitem__(self, key: str) -> Any:
|
|
68
|
+
return self.get(key)
|
|
69
|
+
|
|
70
|
+
@overload
|
|
71
|
+
def __setitem__(self, key: Literal["executionApiKey"], value: Optional[str]) -> None: ...
|
|
72
|
+
|
|
73
|
+
@overload
|
|
74
|
+
def __setitem__(self, key: Literal["responseStatusCode"], value: int) -> None: ...
|
|
75
|
+
|
|
76
|
+
@overload
|
|
77
|
+
def __setitem__(self, key: Literal["responseContentType"], value: Optional[str]) -> None: ...
|
|
78
|
+
|
|
79
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
80
|
+
self.set_once(key, value)
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return f"PolyCustom({self._internal_store})"
|
|
84
|
+
|
|
85
|
+
def copy(self) -> '_PolyCustom':
|
|
86
|
+
new = _PolyCustom()
|
|
87
|
+
new._internal_store = self._internal_store.copy()
|
|
88
|
+
new._execution_id_locked = self._execution_id_locked
|
|
89
|
+
return new
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
polyCustom: PolyCustomDict = _PolyCustom()
|
|
@@ -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
|
|
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
|
|
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
|
|
145
|
-
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
237
|
-
if
|
|
238
|
-
|
|
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
|
|
270
|
-
arguments
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
180
|
+
return "Dict", ""
|
|
156
181
|
else:
|
|
157
182
|
return "Dict", ""
|
|
158
183
|
elif type_spec["kind"] == "function":
|
|
@@ -187,9 +212,13 @@ def get_type_and_def(
|
|
|
187
212
|
|
|
188
213
|
|
|
189
214
|
def _maybe_add_fallback_schema_name(a: PropertySpecification):
|
|
190
|
-
|
|
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
|
|
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
|
|
|
@@ -200,14 +229,23 @@ def parse_arguments(
|
|
|
200
229
|
arg_string = ""
|
|
201
230
|
for idx, a in enumerate(arguments):
|
|
202
231
|
_maybe_add_fallback_schema_name(a)
|
|
203
|
-
|
|
232
|
+
|
|
233
|
+
# Handle cases where type might be missing
|
|
234
|
+
arg_type_spec = a.get("type", {"kind": "any"})
|
|
235
|
+
arg_type, arg_def = get_type_and_def(arg_type_spec)
|
|
204
236
|
if arg_def:
|
|
205
237
|
args_def.append(arg_def)
|
|
206
|
-
|
|
238
|
+
|
|
239
|
+
# Handle cases where name might be missing
|
|
240
|
+
arg_name = a.get("name", f"arg{idx}")
|
|
241
|
+
a["name"] = rewrite_arg_name(arg_name)
|
|
242
|
+
|
|
207
243
|
arg_string += (
|
|
208
244
|
f" {a['name']}: {add_type_import_path(function_name, arg_type)}"
|
|
209
245
|
)
|
|
210
|
-
|
|
246
|
+
|
|
247
|
+
# Handle cases where required might be missing
|
|
248
|
+
if not a.get("required", True):
|
|
211
249
|
arg_string += " = None"
|
|
212
250
|
|
|
213
251
|
description = a.get("description", "")
|
|
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.2", "wheel"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "polyapi-python"
|
|
6
|
-
version = "0.3.7.
|
|
6
|
+
version = "0.3.7.dev3"
|
|
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,23 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
import truststore
|
|
4
|
-
from typing import Dict, Any
|
|
5
|
-
truststore.inject_into_ssl()
|
|
6
|
-
from .cli import CLI_COMMANDS
|
|
7
|
-
|
|
8
|
-
__all__ = ["poly"]
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if len(sys.argv) > 1 and sys.argv[1] not in CLI_COMMANDS:
|
|
12
|
-
currdir = os.path.dirname(os.path.abspath(__file__))
|
|
13
|
-
if not os.path.isdir(os.path.join(currdir, "poly")):
|
|
14
|
-
print("No 'poly' found. Please run 'python3 -m polyapi generate' to generate the 'poly' library for your tenant.")
|
|
15
|
-
sys.exit(1)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
polyCustom: Dict[str, Any] = {
|
|
19
|
-
"executionId": None,
|
|
20
|
-
"executionApiKey": None,
|
|
21
|
-
"responseStatusCode": 200,
|
|
22
|
-
"responseContentType": None,
|
|
23
|
-
}
|
|
@@ -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")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{polyapi_python-0.3.7.dev1 → polyapi_python-0.3.7.dev3}/polyapi_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|