polyapi-python 0.2.4.dev12__tar.gz → 0.2.5__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.2.4.dev12/polyapi_python.egg-info → polyapi_python-0.2.5}/PKG-INFO +2 -2
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/api.py +2 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/auth.py +3 -3
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/cli.py +12 -5
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/client.py +19 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/constants.py +4 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/generate.py +7 -23
- polyapi_python-0.2.5/polyapi/rendered_spec.py +73 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/schema.py +22 -5
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/server.py +3 -1
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/typedefs.py +1 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/utils.py +29 -20
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/webhook.py +19 -4
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5/polyapi_python.egg-info}/PKG-INFO +2 -2
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi_python.egg-info/SOURCES.txt +3 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi_python.egg-info/requires.txt +1 -1
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/pyproject.toml +2 -2
- polyapi_python-0.2.5/tests/test_rendered_spec.py +52 -0
- polyapi_python-0.2.5/tests/test_schema.py +20 -0
- polyapi_python-0.2.5/tests/test_utils.py +17 -0
- polyapi_python-0.2.4.dev12/tests/test_utils.py +0 -10
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/LICENSE +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/README.md +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/__init__.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/__main__.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/config.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/execute.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/py.typed +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi/variables.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/setup.cfg +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/tests/test_api.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/tests/test_auth.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/tests/test_function_cli.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/tests/test_server.py +0 -0
- {polyapi_python-0.2.4.dev12 → polyapi_python-0.2.5}/tests/test_variables.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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
|
|
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
License-File: LICENSE
|
|
31
31
|
Requires-Dist: requests==2.31.0
|
|
32
32
|
Requires-Dist: typing_extensions==4.10.0
|
|
33
|
-
Requires-Dist: jsonschema-gentypes==2.
|
|
33
|
+
Requires-Dist: jsonschema-gentypes==2.6.0
|
|
34
34
|
Requires-Dist: pydantic==2.6.4
|
|
35
35
|
Requires-Dist: stdlib_list==0.10.0
|
|
36
36
|
Requires-Dist: colorama==0.4.4
|
|
@@ -16,9 +16,9 @@ import asyncio
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class AuthFunctionResponse(TypedDict):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
status: int
|
|
20
|
+
data: Any
|
|
21
|
+
headers: Dict[str, str]
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
async def getToken(clientId: str, clientSecret: str, scopes: List[str], callback, options: Optional[Dict[str, Any]] = None):
|
|
@@ -3,11 +3,12 @@ import argparse
|
|
|
3
3
|
from polyapi.utils import print_green
|
|
4
4
|
|
|
5
5
|
from .config import clear_config, set_api_key_and_url
|
|
6
|
-
from .generate import generate, clear
|
|
6
|
+
from .generate import generate, clear
|
|
7
7
|
from .function_cli import function_add_or_update
|
|
8
|
+
from .rendered_spec import get_and_update_rendered_spec
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
CLI_COMMANDS = ["setup", "generate", "function", "clear", "help", "
|
|
11
|
+
CLI_COMMANDS = ["setup", "generate", "function", "clear", "help", "update_rendered_spec"]
|
|
11
12
|
|
|
12
13
|
CLIENT_DESC = """Commands
|
|
13
14
|
python -m polyapi setup Setup your Poly connection
|
|
@@ -17,7 +18,7 @@ CLIENT_DESC = """Commands
|
|
|
17
18
|
"""
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def execute_from_cli():
|
|
21
|
+
def execute_from_cli() -> None:
|
|
21
22
|
parser = argparse.ArgumentParser(
|
|
22
23
|
prog="python -m polyapi", description=CLIENT_DESC, formatter_class=argparse.RawTextHelpFormatter
|
|
23
24
|
)
|
|
@@ -42,8 +43,14 @@ def execute_from_cli():
|
|
|
42
43
|
elif command == "setup":
|
|
43
44
|
clear_config()
|
|
44
45
|
generate()
|
|
45
|
-
elif command == "
|
|
46
|
-
|
|
46
|
+
elif command == "update_rendered_spec":
|
|
47
|
+
assert len(args.subcommands) == 2
|
|
48
|
+
updated = get_and_update_rendered_spec(args.subcommands[0], args.subcommands[1])
|
|
49
|
+
if updated:
|
|
50
|
+
print("Updated rendered spec!")
|
|
51
|
+
else:
|
|
52
|
+
print("Failed to update rendered spec!")
|
|
53
|
+
exit(1)
|
|
47
54
|
elif command == "clear":
|
|
48
55
|
print("Clearing the generated library...")
|
|
49
56
|
clear()
|
|
@@ -10,6 +10,22 @@ from typing import List, Dict, Any, TypedDict
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _wrap_code_in_try_except(code: str) -> str:
|
|
14
|
+
""" this is necessary because client functions with imports will blow up ALL server functions,
|
|
15
|
+
even if they don't use them.
|
|
16
|
+
because the server function will try to load all client functions when loading the library
|
|
17
|
+
"""
|
|
18
|
+
prefix = """logger = logging.getLogger("poly")
|
|
19
|
+
try:
|
|
20
|
+
"""
|
|
21
|
+
suffix = """except ImportError as e:
|
|
22
|
+
logger.debug(e)"""
|
|
23
|
+
|
|
24
|
+
lines = code.split("\n")
|
|
25
|
+
code = "\n ".join(lines)
|
|
26
|
+
return "".join([prefix, code, "\n", suffix])
|
|
27
|
+
|
|
28
|
+
|
|
13
29
|
def render_client_function(
|
|
14
30
|
function_name: str,
|
|
15
31
|
code: str,
|
|
@@ -22,4 +38,7 @@ def render_client_function(
|
|
|
22
38
|
args_def=args_def,
|
|
23
39
|
return_type_def=return_type_def,
|
|
24
40
|
)
|
|
41
|
+
|
|
42
|
+
code = _wrap_code_in_try_except(code)
|
|
43
|
+
|
|
25
44
|
return code + "\n\n", func_type_defs
|
|
@@ -5,6 +5,8 @@ JSONSCHEMA_TO_PYTHON_TYPE_MAP = {
|
|
|
5
5
|
"boolean": "bool",
|
|
6
6
|
"array": "List",
|
|
7
7
|
"object": "Dict",
|
|
8
|
+
"function": "Callable",
|
|
9
|
+
"void": "None",
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
|
|
@@ -15,6 +17,8 @@ PYTHON_TO_JSONSCHEMA_TYPE_MAP = {
|
|
|
15
17
|
"bool": "boolean",
|
|
16
18
|
"List": "array",
|
|
17
19
|
"Dict": "object",
|
|
20
|
+
"Callable": "function",
|
|
21
|
+
"None": "void",
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
BASIC_PYTHON_TYPES = set(PYTHON_TO_JSONSCHEMA_TYPE_MAP.keys())
|
|
@@ -6,7 +6,6 @@ from typing import List
|
|
|
6
6
|
|
|
7
7
|
from polyapi.auth import render_auth_function
|
|
8
8
|
from polyapi.client import render_client_function
|
|
9
|
-
from polyapi.execute import execute_post
|
|
10
9
|
from polyapi.webhook import render_webhook_handle
|
|
11
10
|
|
|
12
11
|
from .typedefs import PropertySpecification, SpecificationDto, VariableSpecDto
|
|
@@ -75,11 +74,14 @@ def cache_specs(specs: List[SpecificationDto]):
|
|
|
75
74
|
|
|
76
75
|
full_path = os.path.dirname(os.path.abspath(__file__))
|
|
77
76
|
full_path = os.path.join(full_path, "poly")
|
|
78
|
-
|
|
79
|
-
os.
|
|
77
|
+
try:
|
|
78
|
+
if not os.path.exists(full_path):
|
|
79
|
+
os.makedirs(full_path)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
with open(os.path.join(full_path, "specs.json"), "w") as f:
|
|
82
|
+
f.write(json.dumps(supported))
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print("Failed to cache specs", e)
|
|
83
85
|
|
|
84
86
|
|
|
85
87
|
def read_cached_specs() -> List[SpecificationDto]:
|
|
@@ -154,24 +156,6 @@ def clear() -> None:
|
|
|
154
156
|
print("Cleared!")
|
|
155
157
|
|
|
156
158
|
|
|
157
|
-
def save_rendered_specs() -> None:
|
|
158
|
-
specs = read_cached_specs()
|
|
159
|
-
# right now we just support rendered apiFunctions
|
|
160
|
-
api_specs = [spec for spec in specs if spec["type"] == "apiFunction"]
|
|
161
|
-
for spec in api_specs:
|
|
162
|
-
assert spec["function"]
|
|
163
|
-
func_str, type_defs = render_spec(spec)
|
|
164
|
-
data = {
|
|
165
|
-
"language": "python",
|
|
166
|
-
"apiFunctionId": spec["id"],
|
|
167
|
-
"signature": func_str,
|
|
168
|
-
"typedefs": type_defs,
|
|
169
|
-
}
|
|
170
|
-
resp = execute_post("/functions/rendered-specs", data)
|
|
171
|
-
print("adding", spec["context"], spec["name"])
|
|
172
|
-
assert resp.status_code == 201, (resp.text, resp.status_code)
|
|
173
|
-
|
|
174
|
-
|
|
175
159
|
def render_spec(spec: SpecificationDto):
|
|
176
160
|
function_type = spec["type"]
|
|
177
161
|
function_description = spec["description"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
from polyapi.config import get_api_key_and_url
|
|
6
|
+
from polyapi.generate import read_cached_specs, render_spec
|
|
7
|
+
from polyapi.typedefs import SpecificationDto
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def update_rendered_spec(api_key: str, spec: SpecificationDto):
|
|
11
|
+
print("Updating rendered spec...")
|
|
12
|
+
func_str, type_defs = render_spec(spec)
|
|
13
|
+
data = {
|
|
14
|
+
"language": "python",
|
|
15
|
+
"signature": func_str,
|
|
16
|
+
"typedefs": type_defs,
|
|
17
|
+
}
|
|
18
|
+
if spec["type"] == "apiFunction":
|
|
19
|
+
data["apiFunctionId"] = spec["id"]
|
|
20
|
+
elif spec["type"] == "serverFunction":
|
|
21
|
+
data["customFunctionId"] = spec["id"]
|
|
22
|
+
elif spec["type"] == "clientFunction":
|
|
23
|
+
data["customFunctionId"] = spec["id"]
|
|
24
|
+
elif spec["type"] == "webhookHandle":
|
|
25
|
+
data["webhookHandleId"] = spec["id"]
|
|
26
|
+
else:
|
|
27
|
+
raise NotImplementedError("todo")
|
|
28
|
+
|
|
29
|
+
# use super key on develop-k8s here!
|
|
30
|
+
_, base_url = get_api_key_and_url()
|
|
31
|
+
if not base_url:
|
|
32
|
+
base_url = os.environ.get("HOST_URL")
|
|
33
|
+
|
|
34
|
+
url = f"{base_url}/functions/rendered-specs"
|
|
35
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
36
|
+
resp = requests.post(url, json=data, headers=headers)
|
|
37
|
+
assert resp.status_code == 201, (resp.text, resp.status_code)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_spec(api_key: str, spec_id: str) -> Optional[SpecificationDto]:
|
|
41
|
+
_, base_url = get_api_key_and_url()
|
|
42
|
+
if not base_url:
|
|
43
|
+
base_url = os.environ.get("HOST_URL")
|
|
44
|
+
|
|
45
|
+
url = f"{base_url}/specs"
|
|
46
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
47
|
+
resp = requests.get(url, headers=headers)
|
|
48
|
+
if resp.status_code == 200:
|
|
49
|
+
specs = resp.json()
|
|
50
|
+
for spec in specs:
|
|
51
|
+
if spec['id'] == spec_id:
|
|
52
|
+
return spec
|
|
53
|
+
return None
|
|
54
|
+
else:
|
|
55
|
+
raise NotImplementedError(resp.content)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_and_update_rendered_spec(api_key: str, spec_id: str) -> bool:
|
|
59
|
+
spec = _get_spec(api_key, spec_id)
|
|
60
|
+
if spec:
|
|
61
|
+
update_rendered_spec(api_key, spec)
|
|
62
|
+
return True
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def save_rendered_specs() -> None:
|
|
67
|
+
specs = read_cached_specs()
|
|
68
|
+
# right now we just support rendered apiFunctions
|
|
69
|
+
api_specs = [spec for spec in specs if spec["type"] == "apiFunction"]
|
|
70
|
+
for spec in api_specs:
|
|
71
|
+
assert spec["function"]
|
|
72
|
+
print("adding", spec["context"], spec["name"])
|
|
73
|
+
update_rendered_spec("FIXME", spec)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import contextlib
|
|
2
3
|
from typing import Dict
|
|
3
4
|
from jsonschema_gentypes.cli import process_config
|
|
@@ -18,10 +19,8 @@ def _cleanup_input_for_gentypes(input_data: Dict):
|
|
|
18
19
|
# jsonschema_gentypes doesn't like double quotes in enums
|
|
19
20
|
# TODO fix this upstream
|
|
20
21
|
for idx, enum in enumerate(v):
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if isinstance(enum, str):
|
|
23
|
+
v[idx] = enum.replace('"', "'")
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
def _temp_store_input_data(input_data: Dict) -> str:
|
|
@@ -33,6 +32,23 @@ def _temp_store_input_data(input_data: Dict) -> str:
|
|
|
33
32
|
return temp_file.name
|
|
34
33
|
|
|
35
34
|
|
|
35
|
+
def wrapped_generate_schema_types(type_spec: dict, root, fallback_type):
|
|
36
|
+
if not root:
|
|
37
|
+
root = "MyList" if fallback_type == "List" else "MyDict"
|
|
38
|
+
|
|
39
|
+
root = clean_title(root)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
return root, generate_schema_types(type_spec, root=root)
|
|
43
|
+
except RecursionError:
|
|
44
|
+
# some schemas are so huge, our library cant handle it
|
|
45
|
+
# TODO identify critical recursion penalty and maybe switch underlying logic to iterative?
|
|
46
|
+
return fallback_type, ""
|
|
47
|
+
except:
|
|
48
|
+
logging.exception(f"Error when generating schema type: {type_spec}")
|
|
49
|
+
return fallback_type, ""
|
|
50
|
+
|
|
51
|
+
|
|
36
52
|
def generate_schema_types(input_data: Dict, root=None):
|
|
37
53
|
"""takes in a Dict representing a schema as input then appends the resulting python code to the output file"""
|
|
38
54
|
_cleanup_input_for_gentypes(input_data)
|
|
@@ -48,6 +64,7 @@ def generate_schema_types(input_data: Dict, root=None):
|
|
|
48
64
|
"source": tmp_input,
|
|
49
65
|
"destination": tmp_output,
|
|
50
66
|
"root_name": root,
|
|
67
|
+
"api_arguments": {"get_name_properties": "UpperFirst"},
|
|
51
68
|
}
|
|
52
69
|
],
|
|
53
70
|
}
|
|
@@ -55,7 +72,7 @@ def generate_schema_types(input_data: Dict, root=None):
|
|
|
55
72
|
# jsonschema_gentypes prints source to stdout
|
|
56
73
|
# no option to surpress so we do this
|
|
57
74
|
with contextlib.redirect_stdout(None):
|
|
58
|
-
process_config(config)
|
|
75
|
+
process_config(config, [tmp_input])
|
|
59
76
|
|
|
60
77
|
with open(tmp_output) as f:
|
|
61
78
|
output = f.read()
|
|
@@ -4,7 +4,7 @@ from polyapi.typedefs import PropertySpecification
|
|
|
4
4
|
from polyapi.utils import camelCase, add_type_import_path, parse_arguments, get_type_and_def
|
|
5
5
|
|
|
6
6
|
SERVER_DEFS_TEMPLATE = """
|
|
7
|
-
from typing import List, Dict, Any, TypedDict
|
|
7
|
+
from typing import List, Dict, Any, TypedDict, Callable
|
|
8
8
|
{args_def}
|
|
9
9
|
{return_type_def}
|
|
10
10
|
"""
|
|
@@ -22,6 +22,8 @@ def {function_name}(
|
|
|
22
22
|
return {return_action}
|
|
23
23
|
except:
|
|
24
24
|
return resp.text
|
|
25
|
+
|
|
26
|
+
|
|
25
27
|
"""
|
|
26
28
|
|
|
27
29
|
|
|
@@ -12,6 +12,7 @@ class PropertySpecification(TypedDict):
|
|
|
12
12
|
|
|
13
13
|
class PropertyType(TypedDict):
|
|
14
14
|
kind: Literal['void', 'primitive', 'array', 'object', 'function', 'plain']
|
|
15
|
+
spec: NotRequired[Dict]
|
|
15
16
|
name: NotRequired[str]
|
|
16
17
|
type: NotRequired[str]
|
|
17
18
|
items: NotRequired['PropertyType']
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import os
|
|
3
|
-
import logging
|
|
4
3
|
from typing import Tuple, List
|
|
5
4
|
from colorama import Fore, Style
|
|
6
5
|
from polyapi.constants import BASIC_PYTHON_TYPES
|
|
7
6
|
from polyapi.typedefs import PropertySpecification, PropertyType
|
|
8
|
-
from polyapi.schema import
|
|
7
|
+
from polyapi.schema import wrapped_generate_schema_types, clean_title, map_primitive_types
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
# this string should be in every __init__ file.
|
|
12
11
|
# it contains all the imports needed for the function or variable code to run
|
|
13
|
-
CODE_IMPORTS = "from typing import List, Dict, Any, TypedDict, Optional\nimport requests\nimport socketio # type: ignore\nfrom polyapi.config import get_api_key_and_url\nfrom polyapi.execute import execute, execute_post, variable_get, variable_update\n\n"
|
|
12
|
+
CODE_IMPORTS = "from typing import List, Dict, Any, TypedDict, Optional, Callable\nimport logging\nimport requests\nimport socketio # type: ignore\nfrom polyapi.config import get_api_key_and_url\nfrom polyapi.execute import execute, execute_post, variable_get, variable_update\n\n"
|
|
13
|
+
FALLBACK_TYPES = {"Dict", "List"}
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def init_the_init(full_path: str) -> None:
|
|
@@ -61,6 +61,10 @@ def print_red(s: str):
|
|
|
61
61
|
def add_type_import_path(function_name: str, arg: str) -> str:
|
|
62
62
|
""" if not basic type, coerce to camelCase and add the import path
|
|
63
63
|
"""
|
|
64
|
+
# for now, just treat Callables as basic types
|
|
65
|
+
if arg.startswith("Callable"):
|
|
66
|
+
return arg
|
|
67
|
+
|
|
64
68
|
if arg in BASIC_PYTHON_TYPES:
|
|
65
69
|
return arg
|
|
66
70
|
|
|
@@ -92,11 +96,7 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]:
|
|
|
92
96
|
if type_spec.get("items"):
|
|
93
97
|
items = type_spec["items"]
|
|
94
98
|
if items.get("$ref"):
|
|
95
|
-
|
|
96
|
-
return "ResponseType", generate_schema_types(type_spec, root="ResponseType") # type: ignore
|
|
97
|
-
except:
|
|
98
|
-
logging.exception(f"Error when generating schema type: {type_spec}")
|
|
99
|
-
return "Dict", ""
|
|
99
|
+
return wrapped_generate_schema_types(type_spec, "ResponseType", "Dict") # type: ignore
|
|
100
100
|
else:
|
|
101
101
|
item_type, _ = get_type_and_def(items)
|
|
102
102
|
title = f"List[{item_type}]"
|
|
@@ -112,12 +112,7 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]:
|
|
|
112
112
|
title = schema.get("title", "")
|
|
113
113
|
if title:
|
|
114
114
|
assert isinstance(title, str)
|
|
115
|
-
|
|
116
|
-
try:
|
|
117
|
-
return title, generate_schema_types(schema, root=title) # type: ignore
|
|
118
|
-
except:
|
|
119
|
-
logging.exception(f"Error when generating schema type: {schema}")
|
|
120
|
-
return "Dict", ""
|
|
115
|
+
return wrapped_generate_schema_types(schema, title, "Dict") # type: ignore
|
|
121
116
|
|
|
122
117
|
elif schema.get("items"):
|
|
123
118
|
# fallback to schema $ref name if no explicit title
|
|
@@ -128,20 +123,34 @@ def get_type_and_def(type_spec: PropertyType) -> Tuple[str, str]:
|
|
|
128
123
|
title = items.get("$ref", "") # type: ignore
|
|
129
124
|
|
|
130
125
|
title = title.rsplit("/", 1)[-1]
|
|
131
|
-
title = clean_title(title)
|
|
132
126
|
if not title:
|
|
133
127
|
return "List", ""
|
|
134
128
|
|
|
135
129
|
title = f"List[{title}]"
|
|
136
|
-
|
|
137
|
-
return title, generate_schema_types(schema, root=title)
|
|
138
|
-
except:
|
|
139
|
-
logging.exception(f"Error when generating schema type: {schema}")
|
|
140
|
-
return "List", ""
|
|
130
|
+
return wrapped_generate_schema_types(schema, title, "List")
|
|
141
131
|
else:
|
|
142
132
|
return "Any", ""
|
|
143
133
|
else:
|
|
144
134
|
return "Dict", ""
|
|
135
|
+
elif type_spec["kind"] == "function":
|
|
136
|
+
arg_types = []
|
|
137
|
+
arg_defs = []
|
|
138
|
+
if "spec" in type_spec:
|
|
139
|
+
return_type, _ = get_type_and_def(type_spec["spec"]["returnType"])
|
|
140
|
+
if return_type not in BASIC_PYTHON_TYPES:
|
|
141
|
+
# for now only Python only supports basic types as return types
|
|
142
|
+
return_type = "Any"
|
|
143
|
+
|
|
144
|
+
for argument in type_spec["spec"]["arguments"]:
|
|
145
|
+
arg_type, arg_def = get_type_and_def(argument["type"])
|
|
146
|
+
arg_types.append(arg_type)
|
|
147
|
+
if arg_def:
|
|
148
|
+
arg_defs.append(arg_def)
|
|
149
|
+
|
|
150
|
+
final_arg_type = "Callable[[{}], {}]".format(", ".join(arg_types), return_type)
|
|
151
|
+
return final_arg_type, "\n".join(arg_defs)
|
|
152
|
+
else:
|
|
153
|
+
return "Callable", ""
|
|
145
154
|
elif type_spec["kind"] == "any":
|
|
146
155
|
return "Any", ""
|
|
147
156
|
else:
|
|
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Tuple
|
|
|
6
6
|
|
|
7
7
|
from polyapi.config import get_api_key_and_url
|
|
8
8
|
from polyapi.typedefs import PropertySpecification
|
|
9
|
-
from polyapi.utils import poly_full_path
|
|
9
|
+
from polyapi.utils import parse_arguments, poly_full_path
|
|
10
10
|
|
|
11
11
|
# all active webhook handlers, used by unregister_all to cleanup
|
|
12
12
|
active_handlers: List[Dict[str, Any]] = []
|
|
@@ -15,10 +15,18 @@ active_handlers: List[Dict[str, Any]] = []
|
|
|
15
15
|
client = None
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
WEBHOOK_DEFS_TEMPLATE = """
|
|
19
|
+
from typing import List, Dict, Any, TypedDict, Callable
|
|
20
|
+
{function_args_def}
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
WEBHOOK_TEMPLATE = """
|
|
19
25
|
|
|
20
26
|
|
|
21
|
-
async def {function_name}(
|
|
27
|
+
async def {function_name}(
|
|
28
|
+
{function_args}
|
|
29
|
+
):
|
|
22
30
|
\"""{description}
|
|
23
31
|
|
|
24
32
|
Function ID: {function_id}
|
|
@@ -112,15 +120,22 @@ def render_webhook_handle(
|
|
|
112
120
|
arguments: List[PropertySpecification],
|
|
113
121
|
return_type: Dict[str, Any],
|
|
114
122
|
) -> Tuple[str, str]:
|
|
123
|
+
function_args, function_args_def = parse_arguments(function_name, arguments)
|
|
124
|
+
|
|
125
|
+
if "WebhookEventType" in function_args:
|
|
126
|
+
# let's add the function name import!
|
|
127
|
+
function_args = function_args.replace("WebhookEventType", f"_{function_name}.WebhookEventType")
|
|
128
|
+
|
|
115
129
|
func_str = WEBHOOK_TEMPLATE.format(
|
|
116
130
|
description=function_description,
|
|
117
131
|
client_id=uuid.uuid4().hex,
|
|
118
132
|
function_id=function_id,
|
|
119
133
|
function_name=function_name,
|
|
134
|
+
function_args=function_args,
|
|
120
135
|
function_path=poly_full_path(function_context, function_name),
|
|
121
136
|
)
|
|
122
|
-
|
|
123
|
-
return func_str,
|
|
137
|
+
func_defs = WEBHOOK_DEFS_TEMPLATE.format(function_args_def=function_args_def)
|
|
138
|
+
return func_str, func_defs
|
|
124
139
|
|
|
125
140
|
|
|
126
141
|
def start(*args):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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
|
|
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
License-File: LICENSE
|
|
31
31
|
Requires-Dist: requests==2.31.0
|
|
32
32
|
Requires-Dist: typing_extensions==4.10.0
|
|
33
|
-
Requires-Dist: jsonschema-gentypes==2.
|
|
33
|
+
Requires-Dist: jsonschema-gentypes==2.6.0
|
|
34
34
|
Requires-Dist: pydantic==2.6.4
|
|
35
35
|
Requires-Dist: stdlib_list==0.10.0
|
|
36
36
|
Requires-Dist: colorama==0.4.4
|
|
@@ -15,6 +15,7 @@ polyapi/execute.py
|
|
|
15
15
|
polyapi/function_cli.py
|
|
16
16
|
polyapi/generate.py
|
|
17
17
|
polyapi/py.typed
|
|
18
|
+
polyapi/rendered_spec.py
|
|
18
19
|
polyapi/schema.py
|
|
19
20
|
polyapi/server.py
|
|
20
21
|
polyapi/typedefs.py
|
|
@@ -29,6 +30,8 @@ polyapi_python.egg-info/top_level.txt
|
|
|
29
30
|
tests/test_api.py
|
|
30
31
|
tests/test_auth.py
|
|
31
32
|
tests/test_function_cli.py
|
|
33
|
+
tests/test_rendered_spec.py
|
|
34
|
+
tests/test_schema.py
|
|
32
35
|
tests/test_server.py
|
|
33
36
|
tests/test_utils.py
|
|
34
37
|
tests/test_variables.py
|
|
@@ -3,13 +3,13 @@ requires = ["setuptools>=61.2", "wheel"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "polyapi-python"
|
|
6
|
-
version = "0.2.
|
|
6
|
+
version = "0.2.5"
|
|
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 = [
|
|
10
10
|
"requests==2.31.0",
|
|
11
11
|
"typing_extensions==4.10.0",
|
|
12
|
-
"jsonschema-gentypes==2.
|
|
12
|
+
"jsonschema-gentypes==2.6.0",
|
|
13
13
|
"pydantic==2.6.4",
|
|
14
14
|
"stdlib_list==0.10.0",
|
|
15
15
|
"colorama==0.4.4",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from mock import patch, Mock
|
|
3
|
+
|
|
4
|
+
from polyapi.rendered_spec import get_and_update_rendered_spec
|
|
5
|
+
|
|
6
|
+
GET_PRODUCTS_COUNT = {
|
|
7
|
+
"id": "8f7d24b0-4a29-40c0-9091",
|
|
8
|
+
"type": "serverFunction",
|
|
9
|
+
"context": "test",
|
|
10
|
+
"name": "getProductsCount111",
|
|
11
|
+
"description": "An API call to retrieve the count of products in the product list.",
|
|
12
|
+
"requirements": ["snabbdom"],
|
|
13
|
+
"function": {
|
|
14
|
+
"arguments": [
|
|
15
|
+
{
|
|
16
|
+
"name": "products",
|
|
17
|
+
"required": False,
|
|
18
|
+
"type": {
|
|
19
|
+
"kind": "array",
|
|
20
|
+
"items": {"kind": "primitive", "type": "string"},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"returnType": {"kind": "plain", "value": "number"},
|
|
25
|
+
"synchronous": True,
|
|
26
|
+
},
|
|
27
|
+
"code": "",
|
|
28
|
+
"language": "javascript",
|
|
29
|
+
"visibilityMetadata": {"visibility": "ENVIRONMENT"},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class T(unittest.TestCase):
|
|
34
|
+
@patch("polyapi.rendered_spec._get_spec")
|
|
35
|
+
def test_get_and_update_rendered_spec_fail(self, _get_spec):
|
|
36
|
+
""" pass in a bad id to update and make sure it returns False
|
|
37
|
+
"""
|
|
38
|
+
_get_spec.return_value = None
|
|
39
|
+
updated = get_and_update_rendered_spec("abc", "123")
|
|
40
|
+
self.assertEqual(_get_spec.call_count, 1)
|
|
41
|
+
self.assertFalse(updated)
|
|
42
|
+
|
|
43
|
+
@patch("polyapi.rendered_spec.requests.post")
|
|
44
|
+
@patch("polyapi.rendered_spec._get_spec")
|
|
45
|
+
def test_get_and_update_rendered_spec_success(self, _get_spec, post):
|
|
46
|
+
""" pass in a bad id to update and make sure it returns False
|
|
47
|
+
"""
|
|
48
|
+
_get_spec.return_value = GET_PRODUCTS_COUNT
|
|
49
|
+
post.return_value = Mock(status_code=201, text="Created")
|
|
50
|
+
updated = get_and_update_rendered_spec("abc", "123")
|
|
51
|
+
self.assertEqual(_get_spec.call_count, 1)
|
|
52
|
+
self.assertTrue(updated)
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from polyapi.schema import _fix_title
|
|
3
|
+
from polyapi.utils import get_type_and_def
|
|
4
|
+
|
|
5
|
+
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}}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class T(unittest.TestCase):
|
|
9
|
+
def test_fix_titles(self):
|
|
10
|
+
input_data = {'properties': {'requestNumber': {'title': 'Requestnumber', 'type': 'integer'}}, 'required': ['requestNumber'], 'title': 'numOfCars', 'type': 'object', 'metadata': {'pydantic.internal.needs_apply_discriminated_union': False}}
|
|
11
|
+
output = 'from typing import TypedDict\nfrom typing_extensions import Required\n\n\nclass Numofcars(TypedDict, total=False):\n """ numOfCars. """\n\n requestNumber: Required[int]\n """\n Requestnumber.\n\n Required property\n """\n\n'
|
|
12
|
+
fixed = _fix_title(input_data, output)
|
|
13
|
+
self.assertIn("class numOfCars", fixed)
|
|
14
|
+
|
|
15
|
+
def test_get_type_and_def(self):
|
|
16
|
+
arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION)
|
|
17
|
+
self.assertEqual(arg_type, "Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]")
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from polyapi.schema import _fix_title
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class T(unittest.TestCase):
|
|
6
|
-
def test_fix_titles(self):
|
|
7
|
-
input_data = {'properties': {'requestNumber': {'title': 'Requestnumber', 'type': 'integer'}}, 'required': ['requestNumber'], 'title': 'numOfCars', 'type': 'object', 'metadata': {'pydantic.internal.needs_apply_discriminated_union': False}}
|
|
8
|
-
output = 'from typing import TypedDict\nfrom typing_extensions import Required\n\n\nclass Numofcars(TypedDict, total=False):\n """ numOfCars. """\n\n requestNumber: Required[int]\n """\n Requestnumber.\n\n Required property\n """\n\n'
|
|
9
|
-
fixed = _fix_title(input_data, output)
|
|
10
|
-
self.assertIn("class numOfCars", fixed)
|
|
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.2.4.dev12 → polyapi_python-0.2.5}/polyapi_python.egg-info/dependency_links.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
|