polyapi-python 0.2.3.dev9__py3-none-any.whl → 0.2.4__py3-none-any.whl
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/__init__.py +10 -1
- polyapi/client.py +19 -0
- polyapi/error_handler.py +67 -34
- polyapi/execute.py +3 -1
- polyapi/function_cli.py +46 -14
- polyapi/generate.py +2 -0
- polyapi/server.py +4 -9
- polyapi/utils.py +11 -2
- polyapi/webhook.py +97 -52
- {polyapi_python-0.2.3.dev9.dist-info → polyapi_python-0.2.4.dist-info}/METADATA +2 -2
- polyapi_python-0.2.4.dist-info/RECORD +25 -0
- polyapi_python-0.2.3.dev9.dist-info/RECORD +0 -25
- {polyapi_python-0.2.3.dev9.dist-info → polyapi_python-0.2.4.dist-info}/LICENSE +0 -0
- {polyapi_python-0.2.3.dev9.dist-info → polyapi_python-0.2.4.dist-info}/WHEEL +0 -0
- {polyapi_python-0.2.3.dev9.dist-info → polyapi_python-0.2.4.dist-info}/top_level.txt +0 -0
polyapi/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
import truststore
|
|
4
|
+
from typing import Dict, Any
|
|
4
5
|
truststore.inject_into_ssl()
|
|
5
6
|
from .cli import CLI_COMMANDS
|
|
6
7
|
|
|
@@ -11,4 +12,12 @@ if len(sys.argv) > 1 and sys.argv[1] not in CLI_COMMANDS:
|
|
|
11
12
|
currdir = os.path.dirname(os.path.abspath(__file__))
|
|
12
13
|
if not os.path.isdir(os.path.join(currdir, "poly")):
|
|
13
14
|
print("No 'poly' found. Please run 'python3 -m polyapi generate' to generate the 'poly' library for your tenant.")
|
|
14
|
-
sys.exit(1)
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
polyCustom: Dict[str, Any] = {
|
|
19
|
+
"executionId": None,
|
|
20
|
+
"executionApiKey": None,
|
|
21
|
+
"responseStatusCode": 200,
|
|
22
|
+
"responseContentType": None,
|
|
23
|
+
}
|
polyapi/client.py
CHANGED
|
@@ -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
|
polyapi/error_handler.py
CHANGED
|
@@ -1,52 +1,85 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import copy
|
|
3
3
|
import socketio # type: ignore
|
|
4
|
-
from
|
|
4
|
+
from socketio.exceptions import ConnectionError # type: ignore
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
5
6
|
|
|
6
7
|
from polyapi.config import get_api_key_and_url
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
# all active webhook handlers, used by unregister_all to cleanup
|
|
11
|
+
active_handlers: List[Dict[str, Any]] = []
|
|
10
12
|
|
|
13
|
+
# global client shared by all error handlers, will be initialized by webhook.start
|
|
14
|
+
client = None
|
|
11
15
|
|
|
12
|
-
def on(path: str, callback: Callable, options: Optional[Dict[str, Any]] = None) -> Callable:
|
|
13
|
-
assert not local_error_handlers
|
|
14
|
-
socket = socketio.AsyncClient()
|
|
15
|
-
api_key, base_url = get_api_key_and_url()
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
def prepare():
|
|
18
|
+
loop = asyncio.get_event_loop()
|
|
19
|
+
loop.run_until_complete(get_client_and_connect())
|
|
20
|
+
print("Client initialized!")
|
|
19
21
|
|
|
20
|
-
handler_id = None
|
|
21
|
-
data = copy.deepcopy(options or {})
|
|
22
|
-
data["path"] = path
|
|
23
|
-
data["apiKey"] = api_key
|
|
24
22
|
|
|
25
|
-
def registerCallback(id: int):
|
|
26
|
-
nonlocal handler_id, socket
|
|
27
|
-
handler_id = id
|
|
28
|
-
socket.on(f"handleError:{handler_id}", callback, namespace="/events")
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
async def get_client_and_connect():
|
|
25
|
+
_, base_url = get_api_key_and_url()
|
|
26
|
+
global client
|
|
27
|
+
client = socketio.AsyncClient()
|
|
28
|
+
await client.connect(base_url, transports=["websocket"], namespaces=["/events"])
|
|
35
29
|
|
|
36
|
-
async def unregister():
|
|
37
|
-
nonlocal handler_id, socket
|
|
38
|
-
if handler_id and socket:
|
|
39
|
-
await socket.emit(
|
|
40
|
-
"unregisterErrorHandler",
|
|
41
|
-
{"id": handler_id, "path": path, "apiKey": api_key},
|
|
42
|
-
namespace="/events",
|
|
43
|
-
)
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
async def unregister(data: Dict[str, Any]):
|
|
32
|
+
print(f"Stopping error handler for {data['path']}...")
|
|
33
|
+
assert client
|
|
34
|
+
await client.emit(
|
|
35
|
+
"unregisterErrorHandler",
|
|
36
|
+
data,
|
|
37
|
+
"/events",
|
|
38
|
+
)
|
|
47
39
|
|
|
48
|
-
await socket.wait()
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
async def unregister_all():
|
|
42
|
+
_, base_url = get_api_key_and_url()
|
|
43
|
+
# need to reconnect because maybe socketio client disconnected after Ctrl+C?
|
|
44
|
+
try:
|
|
45
|
+
await client.connect(base_url, transports=["websocket"], namespaces=["/events"])
|
|
46
|
+
except ConnectionError:
|
|
47
|
+
pass
|
|
48
|
+
await asyncio.gather(*[unregister(handler) for handler in active_handlers])
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
|
|
51
|
+
async def on(
|
|
52
|
+
path: str, callback: Callable, options: Optional[Dict[str, Any]] = None
|
|
53
|
+
) -> None:
|
|
54
|
+
print(f"Starting error handler for {path}...")
|
|
55
|
+
|
|
56
|
+
if not client:
|
|
57
|
+
raise Exception("Client not initialized. Abort!")
|
|
58
|
+
|
|
59
|
+
api_key, _ = get_api_key_and_url()
|
|
60
|
+
handler_id = None
|
|
61
|
+
data = copy.deepcopy(options or {})
|
|
62
|
+
data["path"] = path
|
|
63
|
+
data["apiKey"] = api_key
|
|
64
|
+
|
|
65
|
+
def registerCallback(id: int):
|
|
66
|
+
nonlocal handler_id
|
|
67
|
+
handler_id = id
|
|
68
|
+
client.on(f"handleError:{handler_id}", callback, namespace="/events")
|
|
69
|
+
active_handlers.append({"path": path, "id": handler_id, "apiKey": api_key})
|
|
70
|
+
|
|
71
|
+
await client.emit("registerErrorHandler", data, "/events", registerCallback)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def start(*args):
|
|
75
|
+
loop = asyncio.get_event_loop()
|
|
76
|
+
loop.run_until_complete(get_client_and_connect())
|
|
77
|
+
asyncio.gather(*args)
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
loop.run_forever()
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
pass
|
|
83
|
+
finally:
|
|
84
|
+
loop.run_until_complete(unregister_all())
|
|
85
|
+
loop.stop()
|
polyapi/execute.py
CHANGED
|
@@ -11,7 +11,9 @@ def execute(function_type, function_id, data) -> Response:
|
|
|
11
11
|
headers = {"Authorization": f"Bearer {api_key}"}
|
|
12
12
|
url = f"{api_url}/functions/{function_type}/{function_id}/execute"
|
|
13
13
|
resp = requests.post(url, json=data, headers=headers)
|
|
14
|
-
|
|
14
|
+
# print(resp.status_code)
|
|
15
|
+
# print(resp.headers["content-type"])
|
|
16
|
+
if resp.status_code < 200 or resp.status_code >= 300:
|
|
15
17
|
error_content = resp.content.decode("utf-8", errors="ignore")
|
|
16
18
|
raise PolyApiException(f"{resp.status_code}: {error_content}")
|
|
17
19
|
return resp
|
polyapi/function_cli.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import types
|
|
5
5
|
import sys
|
|
6
6
|
from typing import Dict, List, Mapping, Optional, Tuple
|
|
7
|
+
from typing import _TypedDictMeta as BaseTypedDict # type: ignore
|
|
7
8
|
from typing_extensions import _TypedDictMeta # type: ignore
|
|
8
9
|
import requests
|
|
9
10
|
from stdlib_list import stdlib_list
|
|
@@ -18,9 +19,18 @@ import importlib
|
|
|
18
19
|
|
|
19
20
|
# these libraries are already installed in the base docker image
|
|
20
21
|
# and shouldnt be included in additional requirements
|
|
21
|
-
BASE_REQUIREMENTS = {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
BASE_REQUIREMENTS = {
|
|
23
|
+
"polyapi",
|
|
24
|
+
"requests",
|
|
25
|
+
"typing_extensions",
|
|
26
|
+
"jsonschema-gentypes",
|
|
27
|
+
"pydantic",
|
|
28
|
+
"cloudevents",
|
|
29
|
+
}
|
|
30
|
+
all_stdlib_symbols = stdlib_list(".".join([str(v) for v in sys.version_info[0:2]]))
|
|
31
|
+
BASE_REQUIREMENTS.update(
|
|
32
|
+
all_stdlib_symbols
|
|
33
|
+
) # dont need to pip install stuff in the python standard library
|
|
24
34
|
|
|
25
35
|
|
|
26
36
|
def _get_schemas(code: str) -> List[Dict]:
|
|
@@ -28,7 +38,14 @@ def _get_schemas(code: str) -> List[Dict]:
|
|
|
28
38
|
user_code = types.SimpleNamespace()
|
|
29
39
|
exec(code, user_code.__dict__)
|
|
30
40
|
for name, obj in user_code.__dict__.items():
|
|
31
|
-
if (
|
|
41
|
+
if isinstance(obj, BaseTypedDict):
|
|
42
|
+
print_red("ERROR")
|
|
43
|
+
print_red("\nERROR DETAILS: ")
|
|
44
|
+
print(
|
|
45
|
+
"It looks like you have used TypedDict in a custom function. Please use `from typing_extensions import TypedDict` instead. The `typing_extensions` version is more powerful and better allows us to provide rich types for your function."
|
|
46
|
+
)
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
elif (
|
|
32
49
|
isinstance(obj, type)
|
|
33
50
|
and isinstance(obj, _TypedDictMeta)
|
|
34
51
|
and name != "TypedDict"
|
|
@@ -76,6 +93,13 @@ def get_python_type_from_ast(expr: ast.expr) -> str:
|
|
|
76
93
|
if name == "List":
|
|
77
94
|
slice = getattr(expr.slice, "id", "Any")
|
|
78
95
|
return f"List[{slice}]"
|
|
96
|
+
elif name == "Dict":
|
|
97
|
+
if expr.slice and isinstance(expr.slice, ast.Tuple):
|
|
98
|
+
key = get_python_type_from_ast(expr.slice.dims[0])
|
|
99
|
+
value = get_python_type_from_ast(expr.slice.dims[1])
|
|
100
|
+
return f"Dict[{key}, {value}]"
|
|
101
|
+
else:
|
|
102
|
+
return "Dict"
|
|
79
103
|
return "Any"
|
|
80
104
|
else:
|
|
81
105
|
return "Any"
|
|
@@ -104,7 +128,9 @@ def _get_type(expr: ast.expr | None, schemas: List[Dict]) -> Tuple[str, Dict | N
|
|
|
104
128
|
return json_type, _get_type_schema(json_type, python_type, schemas)
|
|
105
129
|
|
|
106
130
|
|
|
107
|
-
def _get_req_name_if_not_in_base(
|
|
131
|
+
def _get_req_name_if_not_in_base(
|
|
132
|
+
n: Optional[str], pip_name_lookup: Mapping[str, List[str]]
|
|
133
|
+
) -> Optional[str]:
|
|
108
134
|
if not n:
|
|
109
135
|
return None
|
|
110
136
|
|
|
@@ -175,7 +201,12 @@ def _func_already_exists(context: str, function_name: str) -> bool:
|
|
|
175
201
|
|
|
176
202
|
|
|
177
203
|
def function_add_or_update(
|
|
178
|
-
context: str,
|
|
204
|
+
context: str,
|
|
205
|
+
description: str,
|
|
206
|
+
client: bool,
|
|
207
|
+
server: bool,
|
|
208
|
+
logs_enabled: bool,
|
|
209
|
+
subcommands: List,
|
|
179
210
|
):
|
|
180
211
|
parser = argparse.ArgumentParser()
|
|
181
212
|
parser.add_argument("subcommand", choices=["add"])
|
|
@@ -191,16 +222,15 @@ def function_add_or_update(
|
|
|
191
222
|
code = f.read()
|
|
192
223
|
|
|
193
224
|
# OK! let's parse the code and generate the arguments
|
|
194
|
-
(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return_type_schema,
|
|
198
|
-
requirements
|
|
199
|
-
) = _parse_code(code, args.function_name)
|
|
225
|
+
(arguments, return_type, return_type_schema, requirements) = _parse_code(
|
|
226
|
+
code, args.function_name
|
|
227
|
+
)
|
|
200
228
|
|
|
201
229
|
if not return_type:
|
|
202
230
|
print_red("ERROR")
|
|
203
|
-
print(
|
|
231
|
+
print(
|
|
232
|
+
f"Function {args.function_name} not found as top-level function in {args.filename}"
|
|
233
|
+
)
|
|
204
234
|
sys.exit(1)
|
|
205
235
|
|
|
206
236
|
data = {
|
|
@@ -216,7 +246,9 @@ def function_add_or_update(
|
|
|
216
246
|
}
|
|
217
247
|
|
|
218
248
|
if server and requirements:
|
|
219
|
-
print_yellow(
|
|
249
|
+
print_yellow(
|
|
250
|
+
"\nPlease note that deploying your functions will take a few minutes because it makes use of libraries other than polyapi."
|
|
251
|
+
)
|
|
220
252
|
data["requirements"] = requirements
|
|
221
253
|
|
|
222
254
|
api_key, api_url = get_api_key_and_url()
|
polyapi/generate.py
CHANGED
|
@@ -176,6 +176,7 @@ def render_spec(spec: SpecificationDto):
|
|
|
176
176
|
function_type = spec["type"]
|
|
177
177
|
function_description = spec["description"]
|
|
178
178
|
function_name = spec["name"]
|
|
179
|
+
function_context = spec["context"]
|
|
179
180
|
function_id = spec["id"]
|
|
180
181
|
|
|
181
182
|
arguments: List[PropertySpecification] = []
|
|
@@ -223,6 +224,7 @@ def render_spec(spec: SpecificationDto):
|
|
|
223
224
|
elif function_type == "webhookHandle":
|
|
224
225
|
func_str, func_type_defs = render_webhook_handle(
|
|
225
226
|
function_type,
|
|
227
|
+
function_context,
|
|
226
228
|
function_name,
|
|
227
229
|
function_id,
|
|
228
230
|
function_description,
|
polyapi/server.py
CHANGED
|
@@ -18,7 +18,10 @@ def {function_name}(
|
|
|
18
18
|
Function ID: {function_id}
|
|
19
19
|
\"""
|
|
20
20
|
resp = execute("{function_type}", "{function_id}", {data})
|
|
21
|
-
|
|
21
|
+
try:
|
|
22
|
+
return {return_action}
|
|
23
|
+
except:
|
|
24
|
+
return resp.text
|
|
22
25
|
"""
|
|
23
26
|
|
|
24
27
|
|
|
@@ -54,14 +57,6 @@ def render_server_function(
|
|
|
54
57
|
def _get_server_return_action(return_type_name: str) -> str:
|
|
55
58
|
if return_type_name == "str":
|
|
56
59
|
return_action = "resp.text"
|
|
57
|
-
elif return_type_name == "Any":
|
|
58
|
-
return_action = "resp.text"
|
|
59
|
-
elif return_type_name == "int":
|
|
60
|
-
return_action = "int(resp.text.replace('(int) ', ''))"
|
|
61
|
-
elif return_type_name == "float":
|
|
62
|
-
return_action = "float(resp.text.replace('(float) ', ''))"
|
|
63
|
-
elif return_type_name == "bool":
|
|
64
|
-
return_action = "False if resp.text == 'False' else True"
|
|
65
60
|
else:
|
|
66
61
|
return_action = "resp.json()"
|
|
67
62
|
return return_action
|
polyapi/utils.py
CHANGED
|
@@ -10,7 +10,7 @@ from polyapi.schema import generate_schema_types, clean_title, map_primitive_typ
|
|
|
10
10
|
|
|
11
11
|
# this string should be in every __init__ file.
|
|
12
12
|
# 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"
|
|
13
|
+
CODE_IMPORTS = "from typing import List, Dict, Any, TypedDict, Optional\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"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def init_the_init(full_path: str) -> None:
|
|
@@ -165,4 +165,13 @@ def parse_arguments(function_name: str, arguments: List[PropertySpecification])
|
|
|
165
165
|
arg_string += f", # {description}\n"
|
|
166
166
|
else:
|
|
167
167
|
arg_string += ",\n"
|
|
168
|
-
return arg_string.rstrip("\n"), "\n\n".join(args_def)
|
|
168
|
+
return arg_string.rstrip("\n"), "\n\n".join(args_def)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def poly_full_path(context, name) -> str:
|
|
172
|
+
"""get the functions path as it will be exposed in the poly library"""
|
|
173
|
+
if context:
|
|
174
|
+
path = context + "." + name
|
|
175
|
+
else:
|
|
176
|
+
path = name
|
|
177
|
+
return f"poly.{path}"
|
polyapi/webhook.py
CHANGED
|
@@ -1,81 +1,111 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import socketio # type: ignore
|
|
3
|
+
from socketio.exceptions import ConnectionError # type: ignore
|
|
1
4
|
import uuid
|
|
2
5
|
from typing import Any, Dict, List, Tuple
|
|
3
6
|
|
|
7
|
+
from polyapi.config import get_api_key_and_url
|
|
4
8
|
from polyapi.typedefs import PropertySpecification
|
|
9
|
+
from polyapi.utils import poly_full_path
|
|
10
|
+
|
|
11
|
+
# all active webhook handlers, used by unregister_all to cleanup
|
|
12
|
+
active_handlers: List[Dict[str, Any]] = []
|
|
13
|
+
|
|
14
|
+
# global client shared by all webhooks, will be initialized by webhook.start
|
|
15
|
+
client = None
|
|
16
|
+
|
|
5
17
|
|
|
6
18
|
WEBHOOK_TEMPLATE = """
|
|
7
|
-
import asyncio
|
|
8
19
|
|
|
9
20
|
|
|
10
|
-
def {function_name}(callback, options=None):
|
|
21
|
+
async def {function_name}(callback, options=None):
|
|
11
22
|
\"""{description}
|
|
12
23
|
|
|
13
24
|
Function ID: {function_id}
|
|
14
25
|
\"""
|
|
26
|
+
from polyapi.webhook import client, active_handlers
|
|
27
|
+
|
|
28
|
+
print("Starting webhook handler for {function_path}...")
|
|
29
|
+
|
|
30
|
+
if not client:
|
|
31
|
+
raise Exception("Client not initialized. Abort!")
|
|
32
|
+
|
|
15
33
|
options = options or {{}}
|
|
16
34
|
eventsClientId = "{client_id}"
|
|
17
35
|
function_id = "{function_id}"
|
|
18
36
|
|
|
19
37
|
api_key, base_url = get_api_key_and_url()
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
nonlocal options
|
|
35
|
-
polyCustom = {{}}
|
|
36
|
-
resp = await callback(data.get("body"), data.get("headers"), data.get("params"), polyCustom)
|
|
37
|
-
if options.get("waitForResponse"):
|
|
38
|
-
await socket.emit('setWebhookListenerResponse', {{
|
|
39
|
-
"webhookHandleID": function_id,
|
|
40
|
-
"apiKey": api_key,
|
|
41
|
-
"clientID": eventsClientId,
|
|
42
|
-
"executionId": data.get("executionId"),
|
|
43
|
-
"response": {{
|
|
44
|
-
"data": resp,
|
|
45
|
-
"statusCode": polyCustom.get("responseStatusCode", 200),
|
|
46
|
-
"contentType": polyCustom.get("responseContentType", None),
|
|
47
|
-
}},
|
|
48
|
-
}}, namespace="/events")
|
|
49
|
-
|
|
50
|
-
data = {{
|
|
51
|
-
"clientID": eventsClientId,
|
|
52
|
-
"webhookHandleID": function_id,
|
|
53
|
-
"apiKey": api_key,
|
|
54
|
-
"waitForResponse": options.get("waitForResponse"),
|
|
55
|
-
}}
|
|
56
|
-
await socket.emit('registerWebhookEventHandler', data, namespace="/events", callback=registerCallback)
|
|
57
|
-
|
|
58
|
-
async def closeEventHandler():
|
|
59
|
-
nonlocal socket
|
|
60
|
-
if not socket:
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
await socket.emit('unregisterWebhookEventHandler', {{
|
|
64
|
-
"clientID": eventsClientId,
|
|
39
|
+
def registerCallback(registered: bool):
|
|
40
|
+
if registered:
|
|
41
|
+
client.on('handleWebhookEvent:{function_id}', handleEvent, namespace="/events")
|
|
42
|
+
else:
|
|
43
|
+
print("Could not set register webhook event handler for {function_id}")
|
|
44
|
+
|
|
45
|
+
async def handleEvent(data):
|
|
46
|
+
nonlocal api_key
|
|
47
|
+
nonlocal options
|
|
48
|
+
polyCustom = {{}}
|
|
49
|
+
resp = callback(data.get("body"), data.get("headers"), data.get("params"), polyCustom)
|
|
50
|
+
if options.get("waitForResponse"):
|
|
51
|
+
await client.emit('setWebhookListenerResponse', {{
|
|
65
52
|
"webhookHandleID": function_id,
|
|
66
|
-
"apiKey": api_key
|
|
53
|
+
"apiKey": api_key,
|
|
54
|
+
"clientID": eventsClientId,
|
|
55
|
+
"executionId": data.get("executionId"),
|
|
56
|
+
"response": {{
|
|
57
|
+
"data": resp,
|
|
58
|
+
"statusCode": polyCustom.get("responseStatusCode", 200),
|
|
59
|
+
"contentType": polyCustom.get("responseContentType", None),
|
|
60
|
+
}},
|
|
67
61
|
}}, namespace="/events")
|
|
68
62
|
|
|
69
|
-
|
|
63
|
+
data = {{
|
|
64
|
+
"clientID": eventsClientId,
|
|
65
|
+
"webhookHandleID": function_id,
|
|
66
|
+
"apiKey": api_key,
|
|
67
|
+
"waitForResponse": options.get("waitForResponse"),
|
|
68
|
+
}}
|
|
69
|
+
await client.emit('registerWebhookEventHandler', data, namespace="/events", callback=registerCallback)
|
|
70
|
+
active_handlers.append({{"clientID": eventsClientId, "webhookHandleID": function_id, "apiKey": api_key, "path": "{function_path}"}})
|
|
71
|
+
"""
|
|
70
72
|
|
|
71
|
-
return closeEventHandler
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
async def get_client_and_connect():
|
|
75
|
+
_, base_url = get_api_key_and_url()
|
|
76
|
+
global client
|
|
77
|
+
client = socketio.AsyncClient()
|
|
78
|
+
await client.connect(base_url, transports=["websocket"], namespaces=["/events"])
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def unregister(data: Dict[str, Any]):
|
|
82
|
+
print(f"Stopping webhook handler for {data['path']}...")
|
|
83
|
+
assert client
|
|
84
|
+
await client.emit(
|
|
85
|
+
"unregisterWebhookEventHandler",
|
|
86
|
+
{
|
|
87
|
+
"clientID": data["clientID"],
|
|
88
|
+
"webhookHandleID": data["webhookHandleID"],
|
|
89
|
+
"apiKey": data["apiKey"],
|
|
90
|
+
},
|
|
91
|
+
"/events",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def unregister_all():
|
|
96
|
+
_, base_url = get_api_key_and_url()
|
|
97
|
+
# maybe need to reconnect because maybe socketio client disconnected after Ctrl+C?
|
|
98
|
+
# feels like Linux disconnects but Windows stays connected
|
|
99
|
+
try:
|
|
100
|
+
await client.connect(base_url, transports=["websocket"], namespaces=["/events"])
|
|
101
|
+
except ConnectionError:
|
|
102
|
+
pass
|
|
103
|
+
await asyncio.gather(*[unregister(handler) for handler in active_handlers])
|
|
75
104
|
|
|
76
105
|
|
|
77
106
|
def render_webhook_handle(
|
|
78
107
|
function_type: str,
|
|
108
|
+
function_context: str,
|
|
79
109
|
function_name: str,
|
|
80
110
|
function_id: str,
|
|
81
111
|
function_description: str,
|
|
@@ -87,6 +117,21 @@ def render_webhook_handle(
|
|
|
87
117
|
client_id=uuid.uuid4().hex,
|
|
88
118
|
function_id=function_id,
|
|
89
119
|
function_name=function_name,
|
|
120
|
+
function_path=poly_full_path(function_context, function_name),
|
|
90
121
|
)
|
|
91
122
|
|
|
92
|
-
return func_str, ""
|
|
123
|
+
return func_str, ""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def start(*args):
|
|
127
|
+
loop = asyncio.get_event_loop()
|
|
128
|
+
loop.run_until_complete(get_client_and_connect())
|
|
129
|
+
asyncio.gather(*args)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
loop.run_forever()
|
|
133
|
+
except KeyboardInterrupt:
|
|
134
|
+
pass
|
|
135
|
+
finally:
|
|
136
|
+
loop.run_until_complete(unregister_all())
|
|
137
|
+
loop.stop()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: The PolyAPI
|
|
3
|
+
Version: 0.2.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
|
|
7
7
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
polyapi/__init__.py,sha256=5iujRodfgRyLxT-zY0L3xax3rKRvfSt4NZlZYKOz03w,608
|
|
2
|
+
polyapi/__main__.py,sha256=V4zhAh_YGxno5f_KSrlkELxcuDh9bR3WSd0n-2r-qQQ,93
|
|
3
|
+
polyapi/api.py,sha256=Pq_OT8egmtlzMjovN5GGZXWnF5oWMkrgR0rmrzJ6ifM,1861
|
|
4
|
+
polyapi/auth.py,sha256=p2KSLt6q52t9gnqPmgXTOQ2_lmdFilZkIoGmQrRTPLQ,5330
|
|
5
|
+
polyapi/cli.py,sha256=xlKH4cSmSo7eXbyXCLWyL4rXM1QFsltC_MxoxMPgt6I,2187
|
|
6
|
+
polyapi/client.py,sha256=w15XOABkwdL4V4r2iWY_nypzLjvoKVuux8jUKbA16pQ,1329
|
|
7
|
+
polyapi/config.py,sha256=S8TU10upy5OW1_vX-CqQTJD-ZOB6329aMjiUCmukfUI,2292
|
|
8
|
+
polyapi/constants.py,sha256=NGjso6K5rGnE8TGdrXmdEfvvr-HI3DTVGwOYiWO68LM,511
|
|
9
|
+
polyapi/error_handler.py,sha256=I_e0iz6VM23FLVQWJljxs2NGcl_OODbi43OcbnqBlp8,2398
|
|
10
|
+
polyapi/exceptions.py,sha256=Zh7i7eCUhDuXEdUYjatkLFTeZkrx1BJ1P5ePgbJ9eIY,89
|
|
11
|
+
polyapi/execute.py,sha256=kXnvlNQ7nz9cRlV2_5gXH09UCmyiDP5zi3wiAw0uDuk,1943
|
|
12
|
+
polyapi/function_cli.py,sha256=F0cb5MGcmFvspgwxptWq_2e50X5NNno0B0p8Vgu_oNI,9129
|
|
13
|
+
polyapi/generate.py,sha256=fNEBy3ALEu4m1GEEcly-5u-Y1i3G6EWMKKgCyBBJsqc,8568
|
|
14
|
+
polyapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
polyapi/schema.py,sha256=1a7nIO867Xy3bagmPUNdYMxtS5OoCAv8oKIbYgj55dk,2957
|
|
16
|
+
polyapi/server.py,sha256=iXUR1Kd5TnWK-V5qHhvFvQuHx-3IcTv8WChXVY5Mbog,1882
|
|
17
|
+
polyapi/typedefs.py,sha256=a5WfHaAvjeql3y1iA3_SkpUztTbKvS5bPqkVxkCvr9E,1459
|
|
18
|
+
polyapi/utils.py,sha256=9dFboLurZwBBtFZxXBDNri4QvSJAyZhkNUWK5LZh6ME,6180
|
|
19
|
+
polyapi/variables.py,sha256=d36-trnfTL_8m2NkorMiImb4O3UrJbiFV38CHxV5i0A,4200
|
|
20
|
+
polyapi/webhook.py,sha256=KidW6J1R4pWsgJ9duPiG-kzO8S28zfVyPhSn8ypD30Y,4325
|
|
21
|
+
polyapi_python-0.2.4.dist-info/LICENSE,sha256=Hi0kDr56Dsy0uYIwNt4r9G7tI8x8miXRTlyvbeplCP8,1068
|
|
22
|
+
polyapi_python-0.2.4.dist-info/METADATA,sha256=kKKRqtXYD-PhlLUa0GZoAXACMsUsOtq3gcMsZuOcrjM,4862
|
|
23
|
+
polyapi_python-0.2.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
24
|
+
polyapi_python-0.2.4.dist-info/top_level.txt,sha256=CEFllOnzowci_50RYJac-M54KD2IdAptFsayVVF_f04,8
|
|
25
|
+
polyapi_python-0.2.4.dist-info/RECORD,,
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
polyapi/__init__.py,sha256=VRAaN5WZPDmj7AnprtJ3szHXWlqWInKXBfEmfpyXTK8,434
|
|
2
|
-
polyapi/__main__.py,sha256=V4zhAh_YGxno5f_KSrlkELxcuDh9bR3WSd0n-2r-qQQ,93
|
|
3
|
-
polyapi/api.py,sha256=Pq_OT8egmtlzMjovN5GGZXWnF5oWMkrgR0rmrzJ6ifM,1861
|
|
4
|
-
polyapi/auth.py,sha256=p2KSLt6q52t9gnqPmgXTOQ2_lmdFilZkIoGmQrRTPLQ,5330
|
|
5
|
-
polyapi/cli.py,sha256=xlKH4cSmSo7eXbyXCLWyL4rXM1QFsltC_MxoxMPgt6I,2187
|
|
6
|
-
polyapi/client.py,sha256=8k50Vwg9HnmHHTyfKH1vfMJqF0jnnVMsWuWI9AfASkM,761
|
|
7
|
-
polyapi/config.py,sha256=S8TU10upy5OW1_vX-CqQTJD-ZOB6329aMjiUCmukfUI,2292
|
|
8
|
-
polyapi/constants.py,sha256=NGjso6K5rGnE8TGdrXmdEfvvr-HI3DTVGwOYiWO68LM,511
|
|
9
|
-
polyapi/error_handler.py,sha256=vl6ZBtsHmC3eu13IMpmZEXBTDJbrPrzmViBCCrEspd4,1621
|
|
10
|
-
polyapi/exceptions.py,sha256=Zh7i7eCUhDuXEdUYjatkLFTeZkrx1BJ1P5ePgbJ9eIY,89
|
|
11
|
-
polyapi/execute.py,sha256=06XWTxGJqtsDiLY10RjRebIMFRfhtAIMmBRbuWu3e8A,1873
|
|
12
|
-
polyapi/function_cli.py,sha256=KkFvnGSSCwYPMV4cX4yMQmXKyLtgYR64Fz5BfjGv3qY,8230
|
|
13
|
-
polyapi/generate.py,sha256=O1lb7rLsce7tItIrMx1EbWzRlAKKOp-7upNM80cMmMw,8499
|
|
14
|
-
polyapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
polyapi/schema.py,sha256=1a7nIO867Xy3bagmPUNdYMxtS5OoCAv8oKIbYgj55dk,2957
|
|
16
|
-
polyapi/server.py,sha256=mcbH8pG16fFMiFL52wdii0217ZLsznQnElAFEvXZ7IA,2211
|
|
17
|
-
polyapi/typedefs.py,sha256=a5WfHaAvjeql3y1iA3_SkpUztTbKvS5bPqkVxkCvr9E,1459
|
|
18
|
-
polyapi/utils.py,sha256=UD3uV3kzt2w23zxAdHCUROhdvtdO44KCJIpqnFwEhqE,5937
|
|
19
|
-
polyapi/variables.py,sha256=d36-trnfTL_8m2NkorMiImb4O3UrJbiFV38CHxV5i0A,4200
|
|
20
|
-
polyapi/webhook.py,sha256=A89eNnYcVpVe9doJPDLfscIhF-C7Q2AI3vu-SzGxMBg,2923
|
|
21
|
-
polyapi_python-0.2.3.dev9.dist-info/LICENSE,sha256=Hi0kDr56Dsy0uYIwNt4r9G7tI8x8miXRTlyvbeplCP8,1068
|
|
22
|
-
polyapi_python-0.2.3.dev9.dist-info/METADATA,sha256=3pEducW3Aaun4Vy6JgbJ1SksuPLLbWSMXAlYPyg--4A,4823
|
|
23
|
-
polyapi_python-0.2.3.dev9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
24
|
-
polyapi_python-0.2.3.dev9.dist-info/top_level.txt,sha256=CEFllOnzowci_50RYJac-M54KD2IdAptFsayVVF_f04,8
|
|
25
|
-
polyapi_python-0.2.3.dev9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|