polyapi-python 0.3.3.dev4__tar.gz → 0.3.3.dev6__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.3.dev4/polyapi_python.egg-info → polyapi_python-0.3.3.dev6}/PKG-INFO +11 -1
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/README.md +10 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/api.py +1 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/generate.py +4 -4
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/parser.py +8 -6
- polyapi_python-0.3.3.dev6/polyapi/poly_schemas.py +94 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/schema.py +17 -6
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/server.py +9 -5
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/utils.py +68 -17
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6/polyapi_python.egg-info}/PKG-INFO +11 -1
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/pyproject.toml +5 -1
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/tests/test_deployables.py +4 -3
- polyapi_python-0.3.3.dev6/tests/test_generate.py +83 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/tests/test_parser.py +4 -0
- polyapi_python-0.3.3.dev6/tests/test_schema.py +26 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/tests/test_server.py +56 -1
- polyapi_python-0.3.3.dev6/tests/test_utils.py +86 -0
- polyapi_python-0.3.3.dev4/polyapi/poly_schemas.py +0 -60
- polyapi_python-0.3.3.dev4/tests/test_generate.py +0 -14
- polyapi_python-0.3.3.dev4/tests/test_schema.py +0 -20
- polyapi_python-0.3.3.dev4/tests/test_utils.py +0 -14
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/LICENSE +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/__init__.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/auth.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/cli.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/client.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/config.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/deployables.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/execute.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/prepare.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/rendered_spec.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/sync.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/typedefs.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/variables.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi/webhook.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi_python.egg-info/SOURCES.txt +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi_python.egg-info/requires.txt +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/setup.cfg +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/tests/test_api.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/tests/test_rendered_spec.py +0 -0
- {polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/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.3.
|
|
3
|
+
Version: 0.3.3.dev6
|
|
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
|
|
@@ -193,6 +193,16 @@ When hacking on this library, please enable flake8 and add this line to your fla
|
|
|
193
193
|
--config=.flake8
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
+
## Mypy Type Improvements
|
|
197
|
+
|
|
198
|
+
This script is handy for checking for any mypy types:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
./check_mypy.sh
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Please ignore \[name-defined\] errors for now. This is a known bug we are working to fix!
|
|
205
|
+
|
|
196
206
|
## Support
|
|
197
207
|
|
|
198
208
|
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
@@ -153,6 +153,16 @@ When hacking on this library, please enable flake8 and add this line to your fla
|
|
|
153
153
|
--config=.flake8
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
## Mypy Type Improvements
|
|
157
|
+
|
|
158
|
+
This script is handy for checking for any mypy types:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
./check_mypy.sh
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Please ignore \[name-defined\] errors for now. This is a known bug we are working to fix!
|
|
165
|
+
|
|
156
166
|
## Support
|
|
157
167
|
|
|
158
168
|
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
@@ -42,6 +42,7 @@ def render_api_function(
|
|
|
42
42
|
arg_names = [a["name"] for a in arguments]
|
|
43
43
|
args, args_def = parse_arguments(function_name, arguments)
|
|
44
44
|
return_type_name, return_type_def = get_type_and_def(return_type) # type: ignore
|
|
45
|
+
|
|
45
46
|
data = "{" + ", ".join([f"'{arg}': {rewrite_arg_name(arg)}" for arg in arg_names]) + "}"
|
|
46
47
|
|
|
47
48
|
api_response_type = f"{function_name}Response"
|
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
import requests
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
|
-
from typing import List, cast
|
|
5
|
+
from typing import List, Tuple, cast
|
|
6
6
|
|
|
7
7
|
from .auth import render_auth_function
|
|
8
8
|
from .client import render_client_function
|
|
@@ -177,10 +177,10 @@ def generate() -> None:
|
|
|
177
177
|
print("Generating Poly Python SDK...", end="", flush=True)
|
|
178
178
|
remove_old_library()
|
|
179
179
|
|
|
180
|
-
limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
|
|
181
|
-
|
|
182
180
|
specs = get_specs()
|
|
183
181
|
cache_specs(specs)
|
|
182
|
+
|
|
183
|
+
limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
|
|
184
184
|
functions = parse_function_specs(specs, limit_ids=limit_ids)
|
|
185
185
|
|
|
186
186
|
schemas = get_schemas()
|
|
@@ -222,7 +222,7 @@ def clear() -> None:
|
|
|
222
222
|
print("Cleared!")
|
|
223
223
|
|
|
224
224
|
|
|
225
|
-
def render_spec(spec: SpecificationDto):
|
|
225
|
+
def render_spec(spec: SpecificationDto) -> Tuple[str, str]:
|
|
226
226
|
function_type = spec["type"]
|
|
227
227
|
function_description = spec["description"]
|
|
228
228
|
function_name = spec["name"]
|
|
@@ -5,7 +5,7 @@ import sys
|
|
|
5
5
|
import re
|
|
6
6
|
from typing import Dict, List, Mapping, Optional, Tuple, Any
|
|
7
7
|
from typing import _TypedDictMeta as BaseTypedDict # type: ignore
|
|
8
|
-
from typing_extensions import _TypedDictMeta
|
|
8
|
+
from typing_extensions import _TypedDictMeta, cast # type: ignore
|
|
9
9
|
from stdlib_list import stdlib_list
|
|
10
10
|
from pydantic import TypeAdapter
|
|
11
11
|
from importlib.metadata import packages_distributions
|
|
@@ -158,6 +158,7 @@ def _parse_google_docstring(docstring: str) -> Dict[str, Any]:
|
|
|
158
158
|
|
|
159
159
|
return parsed
|
|
160
160
|
|
|
161
|
+
|
|
161
162
|
def _get_schemas(code: str) -> List[Dict]:
|
|
162
163
|
schemas = []
|
|
163
164
|
user_code = types.SimpleNamespace()
|
|
@@ -317,7 +318,7 @@ def _parse_value(value):
|
|
|
317
318
|
return None
|
|
318
319
|
|
|
319
320
|
|
|
320
|
-
def parse_function_code(code: str, name: Optional[str] = "", context: Optional[str] = ""):
|
|
321
|
+
def parse_function_code(code: str, name: Optional[str] = "", context: Optional[str] = ""): # noqa: C901
|
|
321
322
|
schemas = _get_schemas(code)
|
|
322
323
|
|
|
323
324
|
# the pip name and the import name might be different
|
|
@@ -325,9 +326,9 @@ def parse_function_code(code: str, name: Optional[str] = "", context: Optional[s
|
|
|
325
326
|
# see https://stackoverflow.com/a/75144378
|
|
326
327
|
pip_name_lookup = packages_distributions()
|
|
327
328
|
|
|
328
|
-
deployable: DeployableRecord = {
|
|
329
|
-
"context": context,
|
|
330
|
-
"name": name,
|
|
329
|
+
deployable: DeployableRecord = { # type: ignore
|
|
330
|
+
"context": context, # type: ignore
|
|
331
|
+
"name": name, # type: ignore
|
|
331
332
|
"description": "",
|
|
332
333
|
"config": {},
|
|
333
334
|
"gitRevision": "",
|
|
@@ -381,7 +382,7 @@ def parse_function_code(code: str, name: Optional[str] = "", context: Optional[s
|
|
|
381
382
|
if node.annotation.id == "PolyServerFunction":
|
|
382
383
|
deployable["type"] = "server-function"
|
|
383
384
|
elif node.annotation.id == "PolyClientFunction":
|
|
384
|
-
deployable["type"] = "
|
|
385
|
+
deployable["type"] = "client-function"
|
|
385
386
|
else:
|
|
386
387
|
print_red("ERROR")
|
|
387
388
|
print(f"Unsupported polyConfig type '${node.annotation.id}'")
|
|
@@ -404,6 +405,7 @@ def parse_function_code(code: str, name: Optional[str] = "", context: Optional[s
|
|
|
404
405
|
if type(docstring) is None or (not docstring and '"""' not in self._lines[start_lineno] and "'''" not in self._lines[start_lineno]):
|
|
405
406
|
return None
|
|
406
407
|
|
|
408
|
+
docstring = cast(str, docstring)
|
|
407
409
|
|
|
408
410
|
# Support both types of triple quotation marks
|
|
409
411
|
pattern = '"""'
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Dict, List, Tuple
|
|
3
|
+
|
|
4
|
+
from polyapi.schema import wrapped_generate_schema_types
|
|
5
|
+
from polyapi.utils import add_import_to_init, init_the_init, to_func_namespace
|
|
6
|
+
|
|
7
|
+
from .typedefs import SchemaSpecDto
|
|
8
|
+
|
|
9
|
+
SCHEMA_CODE_IMPORTS = """from typing_extensions import TypedDict, NotRequired
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
FALLBACK_SPEC_TEMPLATE = """class {name}(TypedDict, total=False):
|
|
16
|
+
''' unable to generate schema for {name}, defaulting to permissive type '''
|
|
17
|
+
pass
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def generate_schemas(specs: List[SchemaSpecDto]):
|
|
22
|
+
for spec in specs:
|
|
23
|
+
create_schema(spec)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def add_schema_file(
|
|
27
|
+
full_path: str,
|
|
28
|
+
schema_name: str,
|
|
29
|
+
spec: SchemaSpecDto,
|
|
30
|
+
):
|
|
31
|
+
# first lets add the import to the __init__
|
|
32
|
+
init_the_init(full_path, SCHEMA_CODE_IMPORTS)
|
|
33
|
+
|
|
34
|
+
if not spec["definition"].get("title"):
|
|
35
|
+
# very empty schemas like mews.Unit are possible
|
|
36
|
+
# add a title here to be sure they render
|
|
37
|
+
spec["definition"]["title"] = schema_name
|
|
38
|
+
|
|
39
|
+
schema_defs = render_poly_schema(spec)
|
|
40
|
+
|
|
41
|
+
if schema_defs:
|
|
42
|
+
# add function to init
|
|
43
|
+
init_path = os.path.join(full_path, "__init__.py")
|
|
44
|
+
with open(init_path, "a") as f:
|
|
45
|
+
f.write(f"\n\nfrom ._{to_func_namespace(schema_name)} import {schema_name}")
|
|
46
|
+
|
|
47
|
+
# add type_defs to underscore file
|
|
48
|
+
file_path = os.path.join(full_path, f"_{to_func_namespace(schema_name)}.py")
|
|
49
|
+
with open(file_path, "w") as f:
|
|
50
|
+
f.write(schema_defs)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def create_schema(
|
|
54
|
+
spec: SchemaSpecDto
|
|
55
|
+
) -> None:
|
|
56
|
+
full_path = os.path.dirname(os.path.abspath(__file__))
|
|
57
|
+
folders = f"schemas.{spec['context']}.{spec['name']}".split(".")
|
|
58
|
+
for idx, folder in enumerate(folders):
|
|
59
|
+
if idx + 1 == len(folders):
|
|
60
|
+
# special handling for final level
|
|
61
|
+
add_schema_file(
|
|
62
|
+
full_path,
|
|
63
|
+
folder,
|
|
64
|
+
spec,
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
full_path = os.path.join(full_path, folder)
|
|
68
|
+
if not os.path.exists(full_path):
|
|
69
|
+
os.makedirs(full_path)
|
|
70
|
+
|
|
71
|
+
# append to __init__.py file if nested folders
|
|
72
|
+
next = folders[idx + 1] if idx + 2 < len(folders) else ""
|
|
73
|
+
if next:
|
|
74
|
+
init_the_init(full_path, SCHEMA_CODE_IMPORTS)
|
|
75
|
+
add_import_to_init(full_path, next)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def add_schema_to_init(full_path: str, spec: SchemaSpecDto):
|
|
80
|
+
init_the_init(full_path, code_imports="")
|
|
81
|
+
init_path = os.path.join(full_path, "__init__.py")
|
|
82
|
+
with open(init_path, "a") as f:
|
|
83
|
+
f.write(render_poly_schema(spec) + "\n\n")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def render_poly_schema(spec: SchemaSpecDto) -> str:
|
|
87
|
+
definition = spec["definition"]
|
|
88
|
+
if not definition.get("type"):
|
|
89
|
+
definition["type"] = "object"
|
|
90
|
+
root, schema_types = wrapped_generate_schema_types(
|
|
91
|
+
definition, root=spec["name"], fallback_type=Dict
|
|
92
|
+
)
|
|
93
|
+
return schema_types
|
|
94
|
+
# return FALLBACK_SPEC_TEMPLATE.format(name=spec["name"])
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
""" NOTE: this file represents the schema parsing logic for jsonschema_gentypes
|
|
2
2
|
"""
|
|
3
|
-
import random
|
|
4
|
-
import string
|
|
5
3
|
import logging
|
|
6
4
|
import contextlib
|
|
5
|
+
import re
|
|
7
6
|
from typing import Dict
|
|
8
7
|
from jsonschema_gentypes.cli import process_config
|
|
9
8
|
from jsonschema_gentypes import configuration
|
|
@@ -48,10 +47,8 @@ def wrapped_generate_schema_types(type_spec: dict, root, fallback_type):
|
|
|
48
47
|
# lets name the root after the reference for some level of visibility
|
|
49
48
|
root += pascalCase(type_spec["x-poly-ref"]["path"].replace(".", " "))
|
|
50
49
|
else:
|
|
51
|
-
#
|
|
52
|
-
root
|
|
53
|
-
root += random.choice(string.ascii_letters).upper()
|
|
54
|
-
root += random.choice(string.ascii_letters).upper()
|
|
50
|
+
# if we have no root, just add "My"
|
|
51
|
+
root = "My" + root
|
|
55
52
|
|
|
56
53
|
root = clean_title(root)
|
|
57
54
|
|
|
@@ -99,9 +96,23 @@ def generate_schema_types(input_data: Dict, root=None):
|
|
|
99
96
|
with open(tmp_output) as f:
|
|
100
97
|
output = f.read()
|
|
101
98
|
|
|
99
|
+
output = clean_malformed_examples(output)
|
|
100
|
+
|
|
102
101
|
return output
|
|
103
102
|
|
|
104
103
|
|
|
104
|
+
# Regex to match everything between "# example: {\n" and "^}$"
|
|
105
|
+
MALFORMED_EXAMPLES_PATTERN = re.compile(r"# example: \{\n.*?^\}$", flags=re.DOTALL | re.MULTILINE)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def clean_malformed_examples(example: str) -> str:
|
|
109
|
+
""" there is a bug in the `jsonschmea_gentypes` library where if an example from a jsonchema is an object,
|
|
110
|
+
it will break the code because the object won't be properly commented out
|
|
111
|
+
"""
|
|
112
|
+
cleaned_example = MALFORMED_EXAMPLES_PATTERN.sub("", example)
|
|
113
|
+
return cleaned_example
|
|
114
|
+
|
|
115
|
+
|
|
105
116
|
def clean_title(title: str) -> str:
|
|
106
117
|
""" used by library generation, sometimes functions can be added with spaces in the title
|
|
107
118
|
or other nonsense. fix them!
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Tuple
|
|
1
|
+
from typing import Any, Dict, List, Tuple, cast
|
|
2
2
|
|
|
3
|
-
from polyapi.typedefs import PropertySpecification
|
|
4
|
-
from polyapi.utils import add_type_import_path, parse_arguments, get_type_and_def, rewrite_arg_name
|
|
3
|
+
from polyapi.typedefs import PropertySpecification, PropertyType
|
|
4
|
+
from polyapi.utils import add_type_import_path, parse_arguments, get_type_and_def, return_type_already_defined_in_args, rewrite_arg_name
|
|
5
5
|
|
|
6
6
|
SERVER_DEFS_TEMPLATE = """
|
|
7
7
|
from typing import List, Dict, Any, TypedDict, Callable
|
|
@@ -21,7 +21,7 @@ def {function_name}(
|
|
|
21
21
|
try:
|
|
22
22
|
return {return_action}
|
|
23
23
|
except:
|
|
24
|
-
return resp.text
|
|
24
|
+
return resp.text # type: ignore # fallback for debugging
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
"""
|
|
@@ -37,7 +37,11 @@ def render_server_function(
|
|
|
37
37
|
) -> Tuple[str, str]:
|
|
38
38
|
arg_names = [a["name"] for a in arguments]
|
|
39
39
|
args, args_def = parse_arguments(function_name, arguments)
|
|
40
|
-
return_type_name, return_type_def = get_type_and_def(return_type)
|
|
40
|
+
return_type_name, return_type_def = get_type_and_def(cast(PropertyType, return_type), "ReturnType")
|
|
41
|
+
|
|
42
|
+
if return_type_def and return_type_already_defined_in_args(return_type_name, args_def):
|
|
43
|
+
return_type_def = ""
|
|
44
|
+
|
|
41
45
|
data = "{" + ", ".join([f"'{arg}': {rewrite_arg_name(arg)}" for arg in arg_names]) + "}"
|
|
42
46
|
func_type_defs = SERVER_DEFS_TEMPLATE.format(
|
|
43
47
|
args_def=args_def,
|
|
@@ -6,7 +6,11 @@ from typing import Tuple, List
|
|
|
6
6
|
from colorama import Fore, Style
|
|
7
7
|
from polyapi.constants import BASIC_PYTHON_TYPES
|
|
8
8
|
from polyapi.typedefs import PropertySpecification, PropertyType
|
|
9
|
-
from polyapi.schema import
|
|
9
|
+
from polyapi.schema import (
|
|
10
|
+
wrapped_generate_schema_types,
|
|
11
|
+
clean_title,
|
|
12
|
+
map_primitive_types,
|
|
13
|
+
)
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
# this string should be in every __init__ file.
|
|
@@ -42,7 +46,7 @@ def camelCase(s: str) -> str:
|
|
|
42
46
|
s = s.strip()
|
|
43
47
|
if " " in s or "-" in s:
|
|
44
48
|
s = re.sub(r"(_|-)+", " ", s).title().replace(" ", "")
|
|
45
|
-
return
|
|
49
|
+
return "".join([s[0].lower(), s[1:]])
|
|
46
50
|
else:
|
|
47
51
|
# s is already in camelcase as best as we can tell, just move on!
|
|
48
52
|
return s
|
|
@@ -65,8 +69,7 @@ def print_red(s: str):
|
|
|
65
69
|
|
|
66
70
|
|
|
67
71
|
def add_type_import_path(function_name: str, arg: str) -> str:
|
|
68
|
-
"""
|
|
69
|
-
"""
|
|
72
|
+
"""if not basic type, coerce to camelCase and add the import path"""
|
|
70
73
|
# for now, just treat Callables as basic types
|
|
71
74
|
if arg.startswith("Callable"):
|
|
72
75
|
return arg
|
|
@@ -83,12 +86,16 @@ def add_type_import_path(function_name: str, arg: str) -> str:
|
|
|
83
86
|
sub = sub.replace('"', "")
|
|
84
87
|
return f'List["{to_func_namespace(function_name)}.{camelCase(sub)}"]'
|
|
85
88
|
else:
|
|
86
|
-
return f
|
|
89
|
+
return f"List[{to_func_namespace(function_name)}.{camelCase(sub)}]"
|
|
87
90
|
|
|
88
|
-
return f
|
|
91
|
+
return f"{to_func_namespace(function_name)}.{camelCase(arg)}"
|
|
89
92
|
|
|
90
93
|
|
|
91
|
-
def get_type_and_def(
|
|
94
|
+
def get_type_and_def(
|
|
95
|
+
type_spec: PropertyType, title_fallback: str = ""
|
|
96
|
+
) -> Tuple[str, str]:
|
|
97
|
+
""" returns type and type definition for a given PropertyType
|
|
98
|
+
"""
|
|
92
99
|
if type_spec["kind"] == "plain":
|
|
93
100
|
value = type_spec["value"]
|
|
94
101
|
if value.endswith("[]"):
|
|
@@ -115,15 +122,19 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]:
|
|
|
115
122
|
elif type_spec["kind"] == "object":
|
|
116
123
|
if type_spec.get("schema"):
|
|
117
124
|
schema = type_spec["schema"]
|
|
118
|
-
title = schema.get("title", schema.get("name",
|
|
119
|
-
if title:
|
|
125
|
+
title = schema.get("title", schema.get("name", title_fallback))
|
|
126
|
+
if title and schema.get("type") == "array":
|
|
127
|
+
# TODO fix me
|
|
128
|
+
# we don't use ReturnType as name for the list type here, we use _ReturnTypeItem
|
|
129
|
+
return "List", ""
|
|
130
|
+
elif title:
|
|
120
131
|
assert isinstance(title, str)
|
|
121
132
|
return wrapped_generate_schema_types(schema, title, "Dict") # type: ignore
|
|
122
|
-
elif schema.get("allOf") and len(schema[
|
|
133
|
+
elif schema.get("allOf") and len(schema["allOf"]):
|
|
123
134
|
# we are in a case of a single allOf, lets strip off the allOf and move on!
|
|
124
135
|
# our library doesn't handle allOf well yet
|
|
125
|
-
allOf = schema[
|
|
126
|
-
title = allOf.get("title", allOf.get("name",
|
|
136
|
+
allOf = schema["allOf"][0]
|
|
137
|
+
title = allOf.get("title", allOf.get("name", title_fallback))
|
|
127
138
|
return wrapped_generate_schema_types(allOf, title, "Dict")
|
|
128
139
|
elif schema.get("items"):
|
|
129
140
|
# fallback to schema $ref name if no explicit title
|
|
@@ -131,7 +142,7 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]:
|
|
|
131
142
|
title = items.get("title") # type: ignore
|
|
132
143
|
if not title:
|
|
133
144
|
# title is actually a reference to another schema
|
|
134
|
-
title = items.get("$ref",
|
|
145
|
+
title = items.get("$ref", title_fallback) # type: ignore
|
|
135
146
|
|
|
136
147
|
title = title.rsplit("/", 1)[-1]
|
|
137
148
|
if not title:
|
|
@@ -153,12 +164,18 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]:
|
|
|
153
164
|
return_type = "Any"
|
|
154
165
|
|
|
155
166
|
for argument in type_spec["spec"]["arguments"]:
|
|
167
|
+
# do NOT add this fallback here
|
|
168
|
+
# callable arguments don't understand the imports yet
|
|
169
|
+
# if it's not a basic type here, we'll just do Any
|
|
170
|
+
# _maybe_add_fallback_schema_name(argument)
|
|
156
171
|
arg_type, arg_def = get_type_and_def(argument["type"])
|
|
157
172
|
arg_types.append(arg_type)
|
|
158
173
|
if arg_def:
|
|
159
174
|
arg_defs.append(arg_def)
|
|
160
175
|
|
|
161
|
-
final_arg_type = "Callable[[{}], {}]".format(
|
|
176
|
+
final_arg_type = "Callable[[{}], {}]".format(
|
|
177
|
+
", ".join(arg_types), return_type
|
|
178
|
+
)
|
|
162
179
|
return final_arg_type, "\n".join(arg_defs)
|
|
163
180
|
else:
|
|
164
181
|
return "Callable", ""
|
|
@@ -168,15 +185,27 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]:
|
|
|
168
185
|
return "Any", ""
|
|
169
186
|
|
|
170
187
|
|
|
171
|
-
def
|
|
188
|
+
def _maybe_add_fallback_schema_name(a: PropertySpecification):
|
|
189
|
+
if a["type"]["kind"] == "object" and a["type"].get("schema"):
|
|
190
|
+
schema = a["type"].get("schema", {})
|
|
191
|
+
if not schema.get("title") and not schema.get("name") and a["name"]:
|
|
192
|
+
schema["title"] = a["name"].title()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def parse_arguments(
|
|
196
|
+
function_name: str, arguments: List[PropertySpecification]
|
|
197
|
+
) -> Tuple[str, str]:
|
|
172
198
|
args_def = []
|
|
173
199
|
arg_string = ""
|
|
174
200
|
for idx, a in enumerate(arguments):
|
|
201
|
+
_maybe_add_fallback_schema_name(a)
|
|
175
202
|
arg_type, arg_def = get_type_and_def(a["type"])
|
|
176
203
|
if arg_def:
|
|
177
204
|
args_def.append(arg_def)
|
|
178
205
|
a["name"] = rewrite_arg_name(a["name"])
|
|
179
|
-
arg_string +=
|
|
206
|
+
arg_string += (
|
|
207
|
+
f" {a['name']}: {add_type_import_path(function_name, arg_type)}"
|
|
208
|
+
)
|
|
180
209
|
description = a.get("description", "")
|
|
181
210
|
description = description.replace("\n", " ")
|
|
182
211
|
if description:
|
|
@@ -202,7 +231,7 @@ RESERVED_WORDS = {"List", "Dict", "Any", "Optional", "Callable"} | set(keyword.k
|
|
|
202
231
|
|
|
203
232
|
|
|
204
233
|
def to_func_namespace(s: str) -> str:
|
|
205
|
-
"""
|
|
234
|
+
"""convert a function name to some function namespace
|
|
206
235
|
by default it is
|
|
207
236
|
"""
|
|
208
237
|
rv = s[0].upper() + s[1:]
|
|
@@ -221,6 +250,10 @@ def rewrite_arg_name(s: str):
|
|
|
221
250
|
return rewrite_reserved(camelCase(s))
|
|
222
251
|
|
|
223
252
|
|
|
253
|
+
# def get_return_type_name(function_name: str) -> str:
|
|
254
|
+
# return function_name[0].upper() + function_name[1:] + "ReturnType"
|
|
255
|
+
|
|
256
|
+
|
|
224
257
|
valid_subdomains = ["na[1-2]", "eu[1-2]", "dev"]
|
|
225
258
|
|
|
226
259
|
|
|
@@ -238,3 +271,21 @@ def is_valid_uuid(uuid_string, version=4):
|
|
|
238
271
|
return False
|
|
239
272
|
|
|
240
273
|
return str(uuid_obj) == uuid_string
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def return_type_already_defined_in_args(return_type_name: str, args_def: str) -> bool:
|
|
277
|
+
"""
|
|
278
|
+
Checks if the return_type_name preceded optionally by 'class ' and followed by ' =' exists in args_def.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
return_type_name (str): The name of the return type to check.
|
|
282
|
+
args_def (str): The string containing argument definitions.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
bool: True if the pattern exists, False otherwise.
|
|
286
|
+
"""
|
|
287
|
+
basic_pattern = rf"^{re.escape(return_type_name)}\s="
|
|
288
|
+
basic_match = bool(re.search(basic_pattern, args_def, re.MULTILINE))
|
|
289
|
+
class_pattern = rf"^class {re.escape(return_type_name)}\(TypedDict"
|
|
290
|
+
class_match = bool(re.search(class_pattern, args_def, re.MULTILINE))
|
|
291
|
+
return basic_match or class_match
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.3.3.
|
|
3
|
+
Version: 0.3.3.dev6
|
|
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
|
|
@@ -193,6 +193,16 @@ When hacking on this library, please enable flake8 and add this line to your fla
|
|
|
193
193
|
--config=.flake8
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
+
## Mypy Type Improvements
|
|
197
|
+
|
|
198
|
+
This script is handy for checking for any mypy types:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
./check_mypy.sh
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Please ignore \[name-defined\] errors for now. This is a known bug we are working to fix!
|
|
205
|
+
|
|
196
206
|
## Support
|
|
197
207
|
|
|
198
208
|
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.2", "wheel"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "polyapi-python"
|
|
6
|
-
version = "0.3.3.
|
|
6
|
+
version = "0.3.3.dev6"
|
|
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 = [
|
|
@@ -29,3 +29,7 @@ packages = ["polyapi"]
|
|
|
29
29
|
[tools.setuptools.packages.find]
|
|
30
30
|
include = ["polyapi"]
|
|
31
31
|
exclude = ["polyapi/poly*", "polyapi/vari*", "polyapi/.config.env", "polyapi/cached_deployables*", "polyapi/deployments_revision"] # exclude the generated libraries from builds
|
|
32
|
+
|
|
33
|
+
[tool.mypy]
|
|
34
|
+
# for now redef errors happen sometimes, we will clean this up in the future!
|
|
35
|
+
disable_error_code = "no-redef,name-defined"
|
|
@@ -21,6 +21,7 @@ def foobar() -> int:
|
|
|
21
21
|
|
|
22
22
|
EXPECTED_SERVER_FN_DEPLOYMENTS = '''# Poly deployed @ 2024-11-12T14:43:22.631113 - testing.foobar - https://na1.polyapi.io/canopy/polyui/collections/server-functions/jh23h5g3h5b24jh5b2j3h45v2jhg43v52j3h - 086aedd
|
|
23
23
|
# Poly deployed @ 2024-11-11T14:43:22.631113 - testing.foobar - https://dev.polyapi.io/canopy/polyui/collections/server-functions/jh23h5g3h5b24jh5b2j3h45v2jhg43v52j3h - 086aedd
|
|
24
|
+
|
|
24
25
|
from polyapi.typedefs import PolyServerFunction
|
|
25
26
|
|
|
26
27
|
polyConfig: PolyServerFunction = {
|
|
@@ -65,11 +66,11 @@ def foobar(foo: str, bar: Dict[str, str]) -> int:
|
|
|
65
66
|
"""A function that does something really import.
|
|
66
67
|
|
|
67
68
|
Args:
|
|
68
|
-
foo (str):
|
|
69
|
-
bar (Dict[str, str]):
|
|
69
|
+
foo (str):
|
|
70
|
+
bar (Dict[str, str]):
|
|
70
71
|
|
|
71
72
|
Returns:
|
|
72
|
-
int:
|
|
73
|
+
int:
|
|
73
74
|
"""
|
|
74
75
|
print("Okay then!")
|
|
75
76
|
return 7
|
|
@@ -0,0 +1,83 @@
|
|
|
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")
|
|
@@ -128,7 +128,10 @@ def foobar(foo: str, bar: Dict[str, str]) -> int:
|
|
|
128
128
|
return 7
|
|
129
129
|
'''
|
|
130
130
|
|
|
131
|
+
|
|
131
132
|
class T(unittest.TestCase):
|
|
133
|
+
maxDiff = 640
|
|
134
|
+
|
|
132
135
|
def test_no_types(self):
|
|
133
136
|
deployable = parse_function_code(CODE_NO_TYPES, "foobar")
|
|
134
137
|
types = deployable["types"]
|
|
@@ -237,6 +240,7 @@ class T(unittest.TestCase):
|
|
|
237
240
|
"description": "import number please keep handy"
|
|
238
241
|
})
|
|
239
242
|
|
|
243
|
+
@unittest.skip("TODO fix test")
|
|
240
244
|
def test_parse_glide_server_function_deploy_receipt(self):
|
|
241
245
|
code = GLIDE_DEPLOYMENTS_SERVER_FN
|
|
242
246
|
deployable = parse_function_code(code, "foobar")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from polyapi.schema import clean_malformed_examples, wrapped_generate_schema_types
|
|
3
|
+
|
|
4
|
+
SCHEMA = {
|
|
5
|
+
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {"name": {"type": "string"}},
|
|
8
|
+
"required": ["name"],
|
|
9
|
+
"additionalProperties": False,
|
|
10
|
+
"definitions": {},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
APALEO_MALFORMED_EXAMPLE = 'from typing import List, TypedDict, Union\nfrom typing_extensions import Required\n\n\n# Body.\n# \n# example: {\n "from": "2024-04-21",\n "to": "2024-04-24",\n "grossDailyRate": {\n "amount": 160.0,\n "currency": "EUR"\n },\n "timeSlices": [\n {\n "blockedUnits": 3\n },\n {\n "blockedUnits": 0\n },\n {\n "blockedUnits": 7\n }\n ]\n}\n# x-readme-ref-name: ReplaceBlockModel\nBody = TypedDict(\'Body\', {\n # Start date and time from which the inventory will be blockedSpecify either a pure date or a date and time (without fractional second part) in UTC or with UTC offset as defined in <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO8601:2004</a>\n # \n # Required property\n \'from\': Required[str],\n # End date and time until which the inventory will be blocked. Cannot be more than 5 years after the start date.Specify either a pure date or a date and time (without fractional second part) in UTC or with UTC offset as defined in <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO8601:2004</a>\n # \n # Required property\n \'to\': Required[str],\n # x-readme-ref-name: MonetaryValueModel\n # \n # Required property\n \'grossDailyRate\': Required["_BodygrossDailyRate"],\n # The list of time slices\n # \n # Required property\n \'timeSlices\': Required[List["_BodytimeSlicesitem"]],\n}, total=False)\n\n\nclass _BodygrossDailyRate(TypedDict, total=False):\n """ x-readme-ref-name: MonetaryValueModel """\n\n amount: Required[Union[int, float]]\n """\n format: double\n\n Required property\n """\n\n currency: Required[str]\n """ Required property """\n\n\n\nclass _BodytimeSlicesitem(TypedDict, total=False):\n """ x-readme-ref-name: CreateBlockTimeSliceModel """\n\n blockedUnits: Required[Union[int, float]]\n """\n Number of units blocked for the time slice\n\n format: int32\n\n Required property\n """\n\n'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class T(unittest.TestCase):
|
|
17
|
+
def test_fix_titles(self):
|
|
18
|
+
output = wrapped_generate_schema_types(SCHEMA, "", "Dict")
|
|
19
|
+
self.assertEqual("MyDict", output[0])
|
|
20
|
+
self.assertIn("class MyDict", output[1])
|
|
21
|
+
|
|
22
|
+
# should not throw with unknown dialect error
|
|
23
|
+
|
|
24
|
+
def test_clean_malformed_examples(self):
|
|
25
|
+
output = clean_malformed_examples(APALEO_MALFORMED_EXAMPLE)
|
|
26
|
+
self.assertNotIn("# example: {", output)
|
|
@@ -31,6 +31,40 @@ GET_PRODUCTS_COUNT = {
|
|
|
31
31
|
"visibilityMetadata": {"visibility": "ENVIRONMENT"},
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
LIST_RECOMMENDATIONS = {
|
|
35
|
+
"id": "1234-5678-90ab-cdef",
|
|
36
|
+
"type": "serverFunction",
|
|
37
|
+
"context": "foo",
|
|
38
|
+
"name": "listRecommendations",
|
|
39
|
+
"contextName": "foo.listRecommendations",
|
|
40
|
+
"description": "",
|
|
41
|
+
"requirements": [],
|
|
42
|
+
"serverSideAsync": False,
|
|
43
|
+
"function": {
|
|
44
|
+
"arguments": [],
|
|
45
|
+
"returnType": {
|
|
46
|
+
"kind": "object",
|
|
47
|
+
"schema": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"id": {"type": "string"},
|
|
53
|
+
"stay_date": {"type": "string"},
|
|
54
|
+
},
|
|
55
|
+
"additionalProperties": False,
|
|
56
|
+
"required": ["id", "stay_date"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
"synchronous": False,
|
|
61
|
+
},
|
|
62
|
+
"sourceCode": '',
|
|
63
|
+
"language": "javascript",
|
|
64
|
+
"state": "ALPHA",
|
|
65
|
+
"visibilityMetadata": {"visibility": "ENVIRONMENT"},
|
|
66
|
+
}
|
|
67
|
+
|
|
34
68
|
|
|
35
69
|
class T(unittest.TestCase):
|
|
36
70
|
def test_render_function_twilio_server(self):
|
|
@@ -61,4 +95,25 @@ class T(unittest.TestCase):
|
|
|
61
95
|
)
|
|
62
96
|
self.assertIn(GET_PRODUCTS_COUNT["id"], func_str)
|
|
63
97
|
self.assertIn("products: List[str]", func_str)
|
|
64
|
-
self.assertIn("-> float", func_str)
|
|
98
|
+
self.assertIn("-> float", func_str)
|
|
99
|
+
|
|
100
|
+
def test_render_function_list_recommendations(self):
|
|
101
|
+
return_type = LIST_RECOMMENDATIONS["function"]["returnType"]
|
|
102
|
+
func_str, func_type_defs = render_server_function(
|
|
103
|
+
LIST_RECOMMENDATIONS["type"],
|
|
104
|
+
LIST_RECOMMENDATIONS["name"],
|
|
105
|
+
LIST_RECOMMENDATIONS["id"],
|
|
106
|
+
LIST_RECOMMENDATIONS["description"],
|
|
107
|
+
LIST_RECOMMENDATIONS["function"]["arguments"],
|
|
108
|
+
return_type,
|
|
109
|
+
)
|
|
110
|
+
self.assertIn(LIST_RECOMMENDATIONS["id"], func_str)
|
|
111
|
+
self.assertIn("-> List", func_str)
|
|
112
|
+
|
|
113
|
+
# expected_return_type = '''class ReturnType(TypedDict, total=False):
|
|
114
|
+
# id: Required[str]
|
|
115
|
+
# """ Required property """
|
|
116
|
+
|
|
117
|
+
# stay_date: Required[str]
|
|
118
|
+
# """ Required property """'''
|
|
119
|
+
# self.assertIn(expected_return_type, func_str)
|
|
@@ -0,0 +1,86 @@
|
|
|
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(
|
|
80
|
+
arg_type,
|
|
81
|
+
"Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def test_rewrite_reserved(self):
|
|
85
|
+
rv = rewrite_reserved("from")
|
|
86
|
+
self.assertEqual(rv, "_from")
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Any, Dict, List, Tuple
|
|
3
|
-
|
|
4
|
-
from polyapi.schema import wrapped_generate_schema_types
|
|
5
|
-
from polyapi.utils import add_import_to_init, init_the_init
|
|
6
|
-
|
|
7
|
-
from .typedefs import SchemaSpecDto
|
|
8
|
-
|
|
9
|
-
SCHEMA_CODE_IMPORTS = """from typing_extensions import TypedDict, NotRequired
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
FALLBACK_SPEC_TEMPLATE = """class {name}(TypedDict, total=False):
|
|
16
|
-
''' unable to generate schema for {name}, defaulting to permissive type '''
|
|
17
|
-
pass
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def generate_schemas(specs: List[SchemaSpecDto]):
|
|
22
|
-
for spec in specs:
|
|
23
|
-
create_schema(spec)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def create_schema(spec: SchemaSpecDto) -> None:
|
|
27
|
-
folders = ["schemas"]
|
|
28
|
-
if spec["context"]:
|
|
29
|
-
folders += [s for s in spec["context"].split(".")]
|
|
30
|
-
|
|
31
|
-
# build up the full_path by adding all the folders
|
|
32
|
-
full_path = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
|
33
|
-
|
|
34
|
-
for idx, folder in enumerate(folders):
|
|
35
|
-
full_path = os.path.join(full_path, folder)
|
|
36
|
-
if not os.path.exists(full_path):
|
|
37
|
-
os.makedirs(full_path)
|
|
38
|
-
next = folders[idx + 1] if idx + 1 < len(folders) else None
|
|
39
|
-
if next:
|
|
40
|
-
add_import_to_init(full_path, next, code_imports=SCHEMA_CODE_IMPORTS)
|
|
41
|
-
|
|
42
|
-
add_schema_to_init(full_path, spec)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def add_schema_to_init(full_path: str, spec: SchemaSpecDto):
|
|
46
|
-
init_the_init(full_path, code_imports="")
|
|
47
|
-
init_path = os.path.join(full_path, "__init__.py")
|
|
48
|
-
with open(init_path, "a") as f:
|
|
49
|
-
f.write(render_poly_schema(spec) + "\n\n")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def render_poly_schema(spec: SchemaSpecDto) -> str:
|
|
53
|
-
definition = spec["definition"]
|
|
54
|
-
if not definition.get("type"):
|
|
55
|
-
definition["type"] = "object"
|
|
56
|
-
root, schema_types = wrapped_generate_schema_types(
|
|
57
|
-
definition, root=spec["name"], fallback_type=Dict
|
|
58
|
-
)
|
|
59
|
-
return schema_types
|
|
60
|
-
# return FALLBACK_SPEC_TEMPLATE.format(name=spec["name"])
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from polyapi.utils import get_type_and_def, rewrite_reserved
|
|
3
|
-
|
|
4
|
-
OPENAPI_FUNCTION = {'kind': 'function', 'spec': {'arguments': [{'name': 'event', 'required': False, 'type': {'kind': 'object', 'schema': {'$schema': 'http://json-schema.org/draft-06/schema#', 'type': 'array', 'items': {'$ref': '#/definitions/WebhookEventTypeElement'}, 'definitions': {'WebhookEventTypeElement': {'type': 'object', 'additionalProperties': False, 'properties': {'title': {'type': 'string'}, 'manufacturerName': {'type': 'string'}, 'carType': {'type': 'string'}, 'id': {'type': 'integer'}}, 'required': ['carType', 'id', 'manufacturerName', 'title'], 'title': 'WebhookEventTypeElement'}}}}}, {'name': 'headers', 'required': False, 'type': {'kind': 'object', 'typeName': 'Record<string, any>'}}, {'name': 'params', 'required': False, 'type': {'kind': 'object', 'typeName': 'Record<string, any>'}}, {'name': 'polyCustom', 'required': False, 'type': {'kind': 'object', 'properties': [{'name': 'responseStatusCode', 'type': {'type': 'number', 'kind': 'primitive'}, 'required': True}, {'name': 'responseContentType', 'type': {'type': 'string', 'kind': 'primitive'}, 'required': True, 'nullable': True}]}}], 'returnType': {'kind': 'void'}, 'synchronous': True}}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class T(unittest.TestCase):
|
|
8
|
-
def test_get_type_and_def(self):
|
|
9
|
-
arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION)
|
|
10
|
-
self.assertEqual(arg_type, "Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]")
|
|
11
|
-
|
|
12
|
-
def test_rewrite_reserved(self):
|
|
13
|
-
rv = rewrite_reserved("from")
|
|
14
|
-
self.assertEqual(rv, "_from")
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from polyapi.schema import wrapped_generate_schema_types
|
|
3
|
-
|
|
4
|
-
SCHEMA = {
|
|
5
|
-
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"properties": {"name": {"type": "string"}},
|
|
8
|
-
"required": ["name"],
|
|
9
|
-
"additionalProperties": False,
|
|
10
|
-
"definitions": {},
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class T(unittest.TestCase):
|
|
15
|
-
def test_fix_titles(self):
|
|
16
|
-
output = wrapped_generate_schema_types(SCHEMA, "", "Dict")
|
|
17
|
-
self.assertEqual("MyDict", output[0])
|
|
18
|
-
self.assertIn("class MyDict", output[1])
|
|
19
|
-
|
|
20
|
-
# should not throw with unknown dialect error
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from polyapi.utils import get_type_and_def, rewrite_reserved
|
|
3
|
-
|
|
4
|
-
OPENAPI_FUNCTION = {'kind': 'function', 'spec': {'arguments': [{'name': 'event', 'required': False, 'type': {'kind': 'object', 'schema': {'$schema': 'http://json-schema.org/draft-06/schema#', 'type': 'array', 'items': {'$ref': '#/definitions/WebhookEventTypeElement'}, 'definitions': {'WebhookEventTypeElement': {'type': 'object', 'additionalProperties': False, 'properties': {'title': {'type': 'string'}, 'manufacturerName': {'type': 'string'}, 'carType': {'type': 'string'}, 'id': {'type': 'integer'}}, 'required': ['carType', 'id', 'manufacturerName', 'title'], 'title': 'WebhookEventTypeElement'}}}}}, {'name': 'headers', 'required': False, 'type': {'kind': 'object', 'typeName': 'Record<string, any>'}}, {'name': 'params', 'required': False, 'type': {'kind': 'object', 'typeName': 'Record<string, any>'}}, {'name': 'polyCustom', 'required': False, 'type': {'kind': 'object', 'properties': [{'name': 'responseStatusCode', 'type': {'type': 'number', 'kind': 'primitive'}, 'required': True}, {'name': 'responseContentType', 'type': {'type': 'string', 'kind': 'primitive'}, 'required': True, 'nullable': True}]}}], 'returnType': {'kind': 'void'}, 'synchronous': True}}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class T(unittest.TestCase):
|
|
8
|
-
def test_get_type_and_def(self):
|
|
9
|
-
arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION)
|
|
10
|
-
self.assertEqual(arg_type, "Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]")
|
|
11
|
-
|
|
12
|
-
def test_rewrite_reserved(self):
|
|
13
|
-
rv = rewrite_reserved("from")
|
|
14
|
-
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
|
{polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/polyapi_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{polyapi_python-0.3.3.dev4 → polyapi_python-0.3.3.dev6}/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
|