polyapi-python 0.0.35__py3-none-any.whl → 0.1.0__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/api.py +22 -255
- polyapi/auth.py +151 -0
- polyapi/cli.py +16 -7
- polyapi/config.py +9 -0
- polyapi/execute.py +31 -0
- polyapi/function_cli.py +26 -10
- polyapi/generate.py +132 -37
- polyapi/schema.py +9 -2
- polyapi/server.py +64 -0
- polyapi/typedefs.py +18 -2
- polyapi/utils.py +130 -2
- polyapi/variables.py +80 -64
- {polyapi_python-0.0.35.dist-info → polyapi_python-0.1.0.dist-info}/METADATA +11 -1
- polyapi_python-0.1.0.dist-info/RECORD +22 -0
- polyapi_python-0.0.35.dist-info/RECORD +0 -20
- {polyapi_python-0.0.35.dist-info → polyapi_python-0.1.0.dist-info}/LICENSE +0 -0
- {polyapi_python-0.0.35.dist-info → polyapi_python-0.1.0.dist-info}/WHEEL +0 -0
- {polyapi_python-0.0.35.dist-info → polyapi_python-0.1.0.dist-info}/top_level.txt +0 -0
polyapi/api.py
CHANGED
|
@@ -1,31 +1,8 @@
|
|
|
1
|
-
import os
|
|
2
1
|
from typing import Any, Dict, List, Tuple
|
|
3
2
|
|
|
4
|
-
from polyapi.
|
|
5
|
-
from polyapi.
|
|
6
|
-
from polyapi.utils import append_init, camelCase
|
|
7
|
-
from polyapi.schema import generate_schema_types, clean_title
|
|
3
|
+
from polyapi.typedefs import PropertySpecification
|
|
4
|
+
from polyapi.utils import add_type_import_path, camelCase, parse_arguments, get_type_and_def
|
|
8
5
|
|
|
9
|
-
# map the function type from the spec type to the function execute type
|
|
10
|
-
TEMPLATE_FUNCTION_TYPE_MAP = {
|
|
11
|
-
"apiFunction": "api",
|
|
12
|
-
"serverFunction": "server",
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
SERVER_DEFS_TEMPLATE = """
|
|
16
|
-
from typing import List, Dict, Any, TypedDict
|
|
17
|
-
{args_def}
|
|
18
|
-
{return_type_def}
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
SERVER_FUNCTION_TEMPLATE = """
|
|
22
|
-
def {function_name}(
|
|
23
|
-
{args}
|
|
24
|
-
) -> {return_type_name}:
|
|
25
|
-
"{function_description}"
|
|
26
|
-
resp = execute("{function_type}", "{function_id}", {data})
|
|
27
|
-
return {return_action}
|
|
28
|
-
"""
|
|
29
6
|
|
|
30
7
|
API_DEFS_TEMPLATE = """
|
|
31
8
|
from typing import List, Dict, Any, TypedDict
|
|
@@ -47,109 +24,7 @@ def {function_name}(
|
|
|
47
24
|
"""
|
|
48
25
|
|
|
49
26
|
|
|
50
|
-
def
|
|
51
|
-
# Define your mapping logic here
|
|
52
|
-
return JSONSCHEMA_TO_PYTHON_TYPE_MAP.get(type_, "Any")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _get_type(type_spec: PropertyType) -> Tuple[str, str]:
|
|
56
|
-
if type_spec["kind"] == "plain":
|
|
57
|
-
value = type_spec["value"]
|
|
58
|
-
if value.endswith("[]"):
|
|
59
|
-
primitive = _map_primitive_types(value[:-2])
|
|
60
|
-
return f"List[{primitive}]", ""
|
|
61
|
-
else:
|
|
62
|
-
return _map_primitive_types(value), ""
|
|
63
|
-
elif type_spec["kind"] == "primitive":
|
|
64
|
-
return _map_primitive_types(type_spec["type"]), ""
|
|
65
|
-
elif type_spec["kind"] == "array":
|
|
66
|
-
if type_spec.get("items"):
|
|
67
|
-
items = type_spec["items"]
|
|
68
|
-
if items.get("$ref"):
|
|
69
|
-
return "ResponseType", generate_schema_types(type_spec, root="ResponseType") # type: ignore
|
|
70
|
-
else:
|
|
71
|
-
item_type, _ = _get_type(items)
|
|
72
|
-
title = f"List[{item_type}]"
|
|
73
|
-
title = clean_title(title)
|
|
74
|
-
return title, ""
|
|
75
|
-
else:
|
|
76
|
-
return "List", ""
|
|
77
|
-
elif type_spec["kind"] == "void":
|
|
78
|
-
return "None", ""
|
|
79
|
-
elif type_spec["kind"] == "object":
|
|
80
|
-
if type_spec.get("schema"):
|
|
81
|
-
schema = type_spec["schema"]
|
|
82
|
-
title = schema.get("title", "")
|
|
83
|
-
if title:
|
|
84
|
-
assert isinstance(title, str)
|
|
85
|
-
title = clean_title(title)
|
|
86
|
-
return title, generate_schema_types(schema, root=title) # type: ignore
|
|
87
|
-
elif schema.get("items"):
|
|
88
|
-
# fallback to schema $ref name if no explicit title
|
|
89
|
-
items = schema.get("items") # type: ignore
|
|
90
|
-
title = items.get("title", "") # type: ignore
|
|
91
|
-
if not title:
|
|
92
|
-
# title is actually a reference to another schema
|
|
93
|
-
title = items.get("$ref", "") # type: ignore
|
|
94
|
-
|
|
95
|
-
title = title.rsplit("/", 1)[-1]
|
|
96
|
-
title = clean_title(title)
|
|
97
|
-
if not title:
|
|
98
|
-
return "List", ""
|
|
99
|
-
|
|
100
|
-
title = f"List[{title}]"
|
|
101
|
-
return title, generate_schema_types(schema, root=title)
|
|
102
|
-
else:
|
|
103
|
-
return "Any", ""
|
|
104
|
-
else:
|
|
105
|
-
return "Dict", ""
|
|
106
|
-
elif type_spec["kind"] == "any":
|
|
107
|
-
return "Any", ""
|
|
108
|
-
else:
|
|
109
|
-
return "Any", ""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def _parse_arguments(function_name: str, arguments: List[PropertySpecification]) -> Tuple[str, str]:
|
|
113
|
-
args_def = []
|
|
114
|
-
arg_string = ""
|
|
115
|
-
for idx, a in enumerate(arguments):
|
|
116
|
-
arg_type, arg_def = _get_type(a["type"])
|
|
117
|
-
if arg_def:
|
|
118
|
-
args_def.append(arg_def)
|
|
119
|
-
a["name"] = camelCase(a["name"])
|
|
120
|
-
arg_string += f" {a['name']}: {_add_type_import_path(function_name, arg_type)}"
|
|
121
|
-
description = a.get("description", "")
|
|
122
|
-
if description:
|
|
123
|
-
if idx == len(arguments) - 1:
|
|
124
|
-
arg_string += f" # {description}\n"
|
|
125
|
-
else:
|
|
126
|
-
arg_string += f", # {description}\n"
|
|
127
|
-
else:
|
|
128
|
-
arg_string += ",\n"
|
|
129
|
-
return arg_string.rstrip("\n"), "\n\n".join(args_def)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _add_type_import_path(function_name: str, arg: str) -> str:
|
|
133
|
-
""" if not basic type, coerce to camelCase and add the import path
|
|
134
|
-
"""
|
|
135
|
-
if arg in BASIC_PYTHON_TYPES:
|
|
136
|
-
return arg
|
|
137
|
-
|
|
138
|
-
if arg.startswith("List["):
|
|
139
|
-
sub = arg[5:-1]
|
|
140
|
-
if sub in BASIC_PYTHON_TYPES:
|
|
141
|
-
return arg
|
|
142
|
-
else:
|
|
143
|
-
if '"' in sub:
|
|
144
|
-
sub = sub.replace('"', "")
|
|
145
|
-
return f'List["_{function_name}.{camelCase(sub)}"]'
|
|
146
|
-
else:
|
|
147
|
-
return f'List[_{function_name}.{camelCase(sub)}]'
|
|
148
|
-
|
|
149
|
-
return f'_{function_name}.{camelCase(arg)}'
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def render_function(
|
|
27
|
+
def render_api_function(
|
|
153
28
|
function_type: str,
|
|
154
29
|
function_name: str,
|
|
155
30
|
function_id: str,
|
|
@@ -157,134 +32,26 @@ def render_function(
|
|
|
157
32
|
arguments: List[PropertySpecification],
|
|
158
33
|
return_type: Dict[str, Any],
|
|
159
34
|
) -> Tuple[str, str]:
|
|
35
|
+
assert function_type == "apiFunction"
|
|
160
36
|
arg_names = [a["name"] for a in arguments]
|
|
161
|
-
args, args_def =
|
|
162
|
-
return_type_name, return_type_def =
|
|
37
|
+
args, args_def = parse_arguments(function_name, arguments)
|
|
38
|
+
return_type_name, return_type_def = get_type_and_def(return_type) # type: ignore
|
|
163
39
|
data = "{" + ", ".join([f"'{arg}': {camelCase(arg)}" for arg in arg_names]) + "}"
|
|
164
|
-
if function_type == "apiFunction":
|
|
165
|
-
api_response_type = f"{function_name}Response"
|
|
166
|
-
func_type_defs = API_DEFS_TEMPLATE.format(
|
|
167
|
-
args_def=args_def,
|
|
168
|
-
api_response_type=api_response_type,
|
|
169
|
-
return_type_name=return_type_name,
|
|
170
|
-
return_type_def=return_type_def,
|
|
171
|
-
)
|
|
172
|
-
func_str = API_FUNCTION_TEMPLATE.format(
|
|
173
|
-
function_type=TEMPLATE_FUNCTION_TYPE_MAP[function_type],
|
|
174
|
-
function_name=function_name,
|
|
175
|
-
function_id=function_id,
|
|
176
|
-
function_description=function_description.replace('"', "'"),
|
|
177
|
-
args=args,
|
|
178
|
-
data=data,
|
|
179
|
-
api_response_type=_add_type_import_path(function_name, api_response_type),
|
|
180
|
-
)
|
|
181
|
-
else:
|
|
182
|
-
func_type_defs = SERVER_DEFS_TEMPLATE.format(
|
|
183
|
-
args_def=args_def,
|
|
184
|
-
return_type_def=return_type_def,
|
|
185
|
-
)
|
|
186
|
-
func_str = SERVER_FUNCTION_TEMPLATE.format(
|
|
187
|
-
return_type_name=_add_type_import_path(function_name, return_type_name),
|
|
188
|
-
function_type=TEMPLATE_FUNCTION_TYPE_MAP[function_type],
|
|
189
|
-
function_name=function_name,
|
|
190
|
-
function_id=function_id,
|
|
191
|
-
function_description=function_description.replace('"', "'"),
|
|
192
|
-
args=args,
|
|
193
|
-
return_action=_get_server_return_action(return_type_name),
|
|
194
|
-
data=data,
|
|
195
|
-
)
|
|
196
|
-
return func_str, func_type_defs
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def _get_server_return_action(return_type_name: str) -> str:
|
|
200
|
-
if return_type_name == "str":
|
|
201
|
-
return_action = "resp.text"
|
|
202
|
-
elif return_type_name == "Any":
|
|
203
|
-
return_action = "resp.text"
|
|
204
|
-
elif return_type_name == "int":
|
|
205
|
-
return_action = "int(resp.text.replace('(int) ', ''))"
|
|
206
|
-
elif return_type_name == "float":
|
|
207
|
-
return_action = "float(resp.text.replace('(float) ', ''))"
|
|
208
|
-
elif return_type_name == "bool":
|
|
209
|
-
return_action = "False if resp.text == 'False' else True"
|
|
210
|
-
else:
|
|
211
|
-
return_action = "resp.json()"
|
|
212
|
-
return return_action
|
|
213
40
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
function_description: str,
|
|
221
|
-
arguments: List[PropertySpecification],
|
|
222
|
-
return_type: Dict[str, Any],
|
|
223
|
-
):
|
|
224
|
-
# first lets add the import to the __init__
|
|
225
|
-
init_path = os.path.join(full_path, "__init__.py")
|
|
226
|
-
_init_the_init(init_path)
|
|
227
|
-
|
|
228
|
-
func_str, func_type_defs = render_function(
|
|
229
|
-
function_type,
|
|
230
|
-
function_name,
|
|
231
|
-
function_id,
|
|
232
|
-
function_description,
|
|
233
|
-
arguments,
|
|
234
|
-
return_type,
|
|
41
|
+
api_response_type = f"{function_name}Response"
|
|
42
|
+
func_type_defs = API_DEFS_TEMPLATE.format(
|
|
43
|
+
args_def=args_def,
|
|
44
|
+
api_response_type=api_response_type,
|
|
45
|
+
return_type_name=return_type_name,
|
|
46
|
+
return_type_def=return_type_def,
|
|
235
47
|
)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def _init_the_init(init_path: str) -> None:
|
|
247
|
-
if not os.path.exists(init_path):
|
|
248
|
-
with open(init_path, "w") as f:
|
|
249
|
-
f.write("from typing import List, Dict, Any, TypedDict\nfrom polyapi.execute import execute\nfrom polyapi.exceptions import PolyApiException\n")
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def create_function(
|
|
253
|
-
function_type: str,
|
|
254
|
-
path: str,
|
|
255
|
-
function_id: str,
|
|
256
|
-
function_description: str,
|
|
257
|
-
arguments: List[PropertySpecification],
|
|
258
|
-
return_type: Dict[str, Any],
|
|
259
|
-
) -> None:
|
|
260
|
-
full_path = os.path.dirname(os.path.abspath(__file__))
|
|
261
|
-
|
|
262
|
-
folders = path.split(".")
|
|
263
|
-
for idx, folder in enumerate(folders):
|
|
264
|
-
if idx + 1 == len(folders):
|
|
265
|
-
# special handling for final level
|
|
266
|
-
add_function_file(
|
|
267
|
-
function_type,
|
|
268
|
-
full_path,
|
|
269
|
-
folder,
|
|
270
|
-
function_id,
|
|
271
|
-
function_description,
|
|
272
|
-
arguments,
|
|
273
|
-
return_type,
|
|
274
|
-
)
|
|
275
|
-
else:
|
|
276
|
-
full_path = os.path.join(full_path, folder)
|
|
277
|
-
if not os.path.exists(full_path):
|
|
278
|
-
os.makedirs(full_path)
|
|
279
|
-
|
|
280
|
-
# append to __init__.py file if nested folders
|
|
281
|
-
next = folders[idx + 1] if idx + 2 < len(folders) else ""
|
|
282
|
-
if next:
|
|
283
|
-
_init_the_init(os.path.join(full_path, "__init__.py"))
|
|
284
|
-
append_init(full_path, next)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def generate_api(api_functions: List) -> None:
|
|
288
|
-
for func in api_functions:
|
|
289
|
-
create_function(*func)
|
|
290
|
-
print("API functions generated!")
|
|
48
|
+
func_str = API_FUNCTION_TEMPLATE.format(
|
|
49
|
+
function_type="api",
|
|
50
|
+
function_name=function_name,
|
|
51
|
+
function_id=function_id,
|
|
52
|
+
function_description=function_description.replace('"', "'"),
|
|
53
|
+
args=args,
|
|
54
|
+
data=data,
|
|
55
|
+
api_response_type=add_type_import_path(function_name, api_response_type),
|
|
56
|
+
)
|
|
57
|
+
return func_str, func_type_defs
|
polyapi/auth.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Tuple
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from polyapi.typedefs import PropertySpecification
|
|
5
|
+
from polyapi.utils import parse_arguments, get_type_and_def
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
AUTH_DEFS_TEMPLATE = """
|
|
9
|
+
from typing import List, Dict, Any, TypedDict, Optional
|
|
10
|
+
{args_def}
|
|
11
|
+
{return_type_def}
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
GET_TOKEN_TEMPLATE = """
|
|
15
|
+
import asyncio
|
|
16
|
+
import socketio # type: ignore
|
|
17
|
+
from polyapi.config import get_api_key_and_url
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def getToken(clientId: str, clientSecret: str, scopes: List[str], callback, options: Optional[Dict[str, Any]] = None):
|
|
21
|
+
{description}
|
|
22
|
+
eventsClientId = "{client_id}"
|
|
23
|
+
function_id = "{function_id}"
|
|
24
|
+
|
|
25
|
+
options = options or {{}}
|
|
26
|
+
path = "/auth-providers/{function_id}/execute"
|
|
27
|
+
data = {{
|
|
28
|
+
"clientId": clientId,
|
|
29
|
+
"clientSecret": clientSecret,
|
|
30
|
+
"scopes": scopes,
|
|
31
|
+
"audience": options.get("audience"),
|
|
32
|
+
"callbackUrl": options.get("callbackUrl"),
|
|
33
|
+
"userId": options.get("userId"),
|
|
34
|
+
}}
|
|
35
|
+
resp = execute_post(path, data)
|
|
36
|
+
data = resp.json()
|
|
37
|
+
assert resp.status_code == 201, (resp.status_code, resp.content)
|
|
38
|
+
|
|
39
|
+
token = data.get("token")
|
|
40
|
+
url = data.get("url")
|
|
41
|
+
error = data.get("error")
|
|
42
|
+
if token:
|
|
43
|
+
return callback(token, url, error)
|
|
44
|
+
elif url and options.get("autoCloseOnUrl"):
|
|
45
|
+
return callback(token, url, error)
|
|
46
|
+
|
|
47
|
+
timeout = options.get("timeout", 120)
|
|
48
|
+
|
|
49
|
+
api_key, base_url = get_api_key_and_url()
|
|
50
|
+
socket = socketio.AsyncClient()
|
|
51
|
+
await socket.connect(base_url, transports=['websocket'], namespaces=['/events'])
|
|
52
|
+
|
|
53
|
+
async def closeEventHandler():
|
|
54
|
+
nonlocal socket
|
|
55
|
+
if not socket:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
del socket.handlers['/events']['handleAuthFunctionEvent:{function_id}']
|
|
59
|
+
await socket.emit('unregisterAuthFunctionEventHandler', {{
|
|
60
|
+
"clientID": eventsClientId,
|
|
61
|
+
"functionId": function_id,
|
|
62
|
+
"apiKey": api_key
|
|
63
|
+
}}, namespace="/events")
|
|
64
|
+
await socket.disconnect()
|
|
65
|
+
socket = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def waitUntilTimeout(timeout):
|
|
69
|
+
await asyncio.sleep(timeout)
|
|
70
|
+
await closeEventHandler()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def handleEvent(data):
|
|
74
|
+
nonlocal options
|
|
75
|
+
callback(data.get('token'), data.get('url'), data.get('error'))
|
|
76
|
+
if data.get('token') and options.get("autoCloseOnToken", True):
|
|
77
|
+
await closeEventHandler()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def registerCallback(registered: bool):
|
|
81
|
+
nonlocal socket
|
|
82
|
+
if registered:
|
|
83
|
+
socket.on('handleAuthFunctionEvent:{function_id}', handleEvent, namespace="/events")
|
|
84
|
+
callback(data.get('token'), data.get('url'), data.get('error'))
|
|
85
|
+
|
|
86
|
+
data2 = {{
|
|
87
|
+
"clientID": eventsClientId,
|
|
88
|
+
"functionId": function_id,
|
|
89
|
+
"apiKey": api_key
|
|
90
|
+
}}
|
|
91
|
+
await socket.emit('registerAuthFunctionEventHandler', data2, namespace="/events", callback=registerCallback)
|
|
92
|
+
|
|
93
|
+
# run timeout task in background
|
|
94
|
+
timeout = options.get("timeout", 120)
|
|
95
|
+
timeout_task = asyncio.create_task(waitUntilTimeout(timeout))
|
|
96
|
+
|
|
97
|
+
# cancel timeout task if socket.wait finishes before timeout up
|
|
98
|
+
await socket.wait()
|
|
99
|
+
timeout_task.cancel()
|
|
100
|
+
|
|
101
|
+
return {{"close": closeEventHandler}}
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
REFRESH_TOKEN_TEMPLATE = """
|
|
105
|
+
def refreshToken(token: str) -> str:
|
|
106
|
+
{description}
|
|
107
|
+
url = "/auth-providers/{function_id}/refresh"
|
|
108
|
+
resp = execute_post(url, {{"token": token}})
|
|
109
|
+
assert resp.status_code == 201, (resp.status_code, resp.content)
|
|
110
|
+
return resp.text
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
REVOKE_TOKEN_TEMPLATE = """
|
|
114
|
+
def revokeToken(token: str) -> None:
|
|
115
|
+
{description}
|
|
116
|
+
url = "/auth-providers/{function_id}/revoke"
|
|
117
|
+
resp = execute_post(url, {{"token": token}})
|
|
118
|
+
assert resp.status_code == 201, (resp.status_code, resp.content)
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def render_auth_function(
|
|
123
|
+
function_type: str,
|
|
124
|
+
function_name: str,
|
|
125
|
+
function_id: str,
|
|
126
|
+
function_description: str,
|
|
127
|
+
arguments: List[PropertySpecification],
|
|
128
|
+
return_type: Dict[str, Any],
|
|
129
|
+
) -> Tuple[str, str]:
|
|
130
|
+
""" renders getToken, revokeToken, refreshToken as appropriate
|
|
131
|
+
"""
|
|
132
|
+
args, args_def = parse_arguments(function_name, arguments)
|
|
133
|
+
return_type_name, return_type_def = get_type_and_def(return_type) # type: ignore
|
|
134
|
+
func_type_defs = AUTH_DEFS_TEMPLATE.format(
|
|
135
|
+
args_def=args_def,
|
|
136
|
+
return_type_def=return_type_def,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
func_str = ""
|
|
140
|
+
|
|
141
|
+
if function_description:
|
|
142
|
+
function_description = f'"""{function_description}"""'
|
|
143
|
+
|
|
144
|
+
if function_name == "getToken":
|
|
145
|
+
func_str = GET_TOKEN_TEMPLATE.format(function_id=function_id, description=function_description, client_id=uuid.uuid4().hex)
|
|
146
|
+
elif function_name == "refreshToken":
|
|
147
|
+
func_str = REFRESH_TOKEN_TEMPLATE.format(function_id=function_id, description=function_description)
|
|
148
|
+
elif function_name == "revokeToken":
|
|
149
|
+
func_str = REVOKE_TOKEN_TEMPLATE.format(function_id=function_id, description=function_description)
|
|
150
|
+
|
|
151
|
+
return func_str, func_type_defs
|
polyapi/cli.py
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
|
|
3
|
+
from polyapi.utils import print_green
|
|
4
|
+
|
|
3
5
|
from .config import clear_config
|
|
4
6
|
from .generate import generate, clear
|
|
5
7
|
from .function_cli import function_add_or_update
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
CLI_COMMANDS = ["
|
|
10
|
+
CLI_COMMANDS = ["setup", "generate", "function", "clear", "help"]
|
|
11
|
+
|
|
12
|
+
CLIENT_DESC = """Commands
|
|
13
|
+
python -m polyapi setup Setup your Poly connection
|
|
14
|
+
python -m polyapi generate Generates Poly library
|
|
15
|
+
python -m polyapi function <command> Manages functions
|
|
16
|
+
python -m polyapi clear Clear current generated Poly library
|
|
17
|
+
"""
|
|
9
18
|
|
|
10
19
|
|
|
11
20
|
def execute_from_cli():
|
|
12
21
|
parser = argparse.ArgumentParser(
|
|
13
|
-
prog="python -m polyapi", description=
|
|
22
|
+
prog="python -m polyapi", description=CLIENT_DESC, formatter_class=argparse.RawTextHelpFormatter
|
|
14
23
|
)
|
|
15
24
|
parser.add_argument("--context", required=False, default="")
|
|
16
25
|
parser.add_argument("--description", required=False, default="")
|
|
17
|
-
parser.add_argument("--server", action="store_true", help="Pass --server
|
|
18
|
-
parser.add_argument("--logs", action="store_true", help="Pass --logs if you want to store and see the
|
|
26
|
+
parser.add_argument("--server", action="store_true", help="Pass --server when adding function to add a server function. By default, new functions are client.")
|
|
27
|
+
parser.add_argument("--logs", action="store_true", help="Pass --logs when adding function if you want to store and see the function logs.")
|
|
19
28
|
parser.add_argument("command", choices=CLI_COMMANDS)
|
|
20
29
|
parser.add_argument("subcommands", nargs="*")
|
|
21
30
|
args = parser.parse_args()
|
|
@@ -24,10 +33,10 @@ def execute_from_cli():
|
|
|
24
33
|
if command == "help":
|
|
25
34
|
parser.print_help()
|
|
26
35
|
elif command == "generate":
|
|
27
|
-
print("Generating...")
|
|
36
|
+
print("Generating Poly functions...", end="")
|
|
28
37
|
generate()
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
print_green("DONE")
|
|
39
|
+
elif command == "setup":
|
|
31
40
|
clear_config()
|
|
32
41
|
generate()
|
|
33
42
|
elif command == "clear":
|
polyapi/config.py
CHANGED
|
@@ -69,6 +69,15 @@ def initialize_config():
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def clear_config():
|
|
72
|
+
if os.environ.get("POLY_API_KEY"):
|
|
73
|
+
print("Using POLY_API_KEY from environment. Please unset environment variable to manually set api key.")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
global API_KEY
|
|
77
|
+
global API_URL
|
|
78
|
+
API_KEY = None
|
|
79
|
+
API_URL = None
|
|
80
|
+
|
|
72
81
|
path = get_config_file_path()
|
|
73
82
|
if os.path.exists(path):
|
|
74
83
|
os.remove(path)
|
polyapi/execute.py
CHANGED
|
@@ -5,10 +5,41 @@ from polyapi.exceptions import PolyApiException
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def execute(function_type, function_id, data) -> Response:
|
|
8
|
+
""" execute a specific function id/type
|
|
9
|
+
"""
|
|
8
10
|
api_key, api_url = get_api_key_and_url()
|
|
9
11
|
headers = {"Authorization": f"Bearer {api_key}"}
|
|
10
12
|
url = f"{api_url}/functions/{function_type}/{function_id}/execute"
|
|
11
13
|
resp = requests.post(url, json=data, headers=headers)
|
|
14
|
+
if resp.status_code != 200 and resp.status_code != 201:
|
|
15
|
+
error_content = resp.content.decode("utf-8", errors="ignore")
|
|
16
|
+
raise PolyApiException(f"{resp.status_code}: {error_content}")
|
|
17
|
+
return resp
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def execute_post(path, data):
|
|
21
|
+
api_key, api_url = get_api_key_and_url()
|
|
22
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
23
|
+
resp = requests.post(api_url + path, json=data, headers=headers)
|
|
24
|
+
return resp
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def variable_get(variable_id: str) -> Response:
|
|
28
|
+
api_key, base_url = get_api_key_and_url()
|
|
29
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
30
|
+
url = f"{base_url}/variables/{variable_id}/value"
|
|
31
|
+
resp = requests.get(url, headers=headers)
|
|
32
|
+
if resp.status_code != 200 and resp.status_code != 201:
|
|
33
|
+
error_content = resp.content.decode("utf-8", errors="ignore")
|
|
34
|
+
raise PolyApiException(f"{resp.status_code}: {error_content}")
|
|
35
|
+
return resp
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def variable_update(variable_id: str, value) -> Response:
|
|
39
|
+
api_key, base_url = get_api_key_and_url()
|
|
40
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
41
|
+
url = f"{base_url}/variables/{variable_id}/value"
|
|
42
|
+
resp = requests.patch(url, data={"value": value}, headers=headers)
|
|
12
43
|
if resp.status_code != 200 and resp.status_code != 201:
|
|
13
44
|
error_content = resp.content.decode("utf-8", errors="ignore")
|
|
14
45
|
raise PolyApiException(f"{resp.status_code}: {error_content}")
|
polyapi/function_cli.py
CHANGED
|
@@ -8,15 +8,16 @@ from typing_extensions import _TypedDictMeta # type: ignore
|
|
|
8
8
|
import requests
|
|
9
9
|
from stdlib_list import stdlib_list
|
|
10
10
|
from pydantic import TypeAdapter
|
|
11
|
-
from polyapi.generate import
|
|
11
|
+
from polyapi.generate import get_functions_and_parse, generate_functions
|
|
12
12
|
from polyapi.config import get_api_key_and_url
|
|
13
13
|
from polyapi.constants import PYTHON_TO_JSONSCHEMA_TYPE_MAP
|
|
14
|
-
from polyapi.utils import get_auth_headers
|
|
14
|
+
from polyapi.utils import get_auth_headers, print_green, print_red, print_yellow
|
|
15
|
+
import importlib
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
# these libraries are already installed in the base docker image
|
|
18
19
|
# and shouldnt be included in additional requirements
|
|
19
|
-
BASE_REQUIREMENTS = {"requests", "typing_extensions", "jsonschema-gentypes", "pydantic"}
|
|
20
|
+
BASE_REQUIREMENTS = {"polyapi", "requests", "typing_extensions", "jsonschema-gentypes", "pydantic"}
|
|
20
21
|
all_stdlib_symbols = stdlib_list('.'.join([str(v) for v in sys.version_info[0:2]]))
|
|
21
22
|
BASE_REQUIREMENTS.update(all_stdlib_symbols) # dont need to pip install stuff in the python standard library
|
|
22
23
|
|
|
@@ -141,6 +142,14 @@ def _parse_code(code: str, function_name: str):
|
|
|
141
142
|
return parsed_args, return_type, return_type_schema, requirements
|
|
142
143
|
|
|
143
144
|
|
|
145
|
+
def _func_already_exists(context: str, function_name: str) -> bool:
|
|
146
|
+
try:
|
|
147
|
+
module = importlib.import_module(f"polyapi.poly.{context}")
|
|
148
|
+
return bool(getattr(module, function_name, False))
|
|
149
|
+
except ModuleNotFoundError:
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
144
153
|
def function_add_or_update(
|
|
145
154
|
context: str, description: str, server: bool, logs_enabled: bool, subcommands: List
|
|
146
155
|
):
|
|
@@ -150,6 +159,9 @@ def function_add_or_update(
|
|
|
150
159
|
parser.add_argument("filename")
|
|
151
160
|
args = parser.parse_args(subcommands)
|
|
152
161
|
|
|
162
|
+
verb = "Updating" if _func_already_exists(context, args.function_name) else "Adding"
|
|
163
|
+
print(f"{verb} custom server side function...", end="")
|
|
164
|
+
|
|
153
165
|
with open(args.filename, "r") as f:
|
|
154
166
|
code = f.read()
|
|
155
167
|
|
|
@@ -162,11 +174,13 @@ def function_add_or_update(
|
|
|
162
174
|
) = _parse_code(code, args.function_name)
|
|
163
175
|
|
|
164
176
|
if not return_type:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
177
|
+
print_red("ERROR")
|
|
178
|
+
print(f"Function {args.function_name} not found as top-level function in {args.filename}")
|
|
168
179
|
sys.exit(1)
|
|
169
180
|
|
|
181
|
+
if requirements:
|
|
182
|
+
print_yellow('\nPlease note that deploying your functions will take a few minutes because it makes use of libraries other than polyapi.')
|
|
183
|
+
|
|
170
184
|
data = {
|
|
171
185
|
"context": context,
|
|
172
186
|
"name": args.function_name,
|
|
@@ -189,13 +203,15 @@ def function_add_or_update(
|
|
|
189
203
|
# url = f"{base_url}/functions/client"
|
|
190
204
|
|
|
191
205
|
headers = get_auth_headers(api_key)
|
|
192
|
-
print("Adding function...")
|
|
193
206
|
resp = requests.post(url, headers=headers, json=data)
|
|
194
207
|
if resp.status_code == 201:
|
|
208
|
+
print_green("DEPLOYED")
|
|
195
209
|
function_id = resp.json()["id"]
|
|
196
|
-
print(f"Function
|
|
197
|
-
print("
|
|
198
|
-
|
|
210
|
+
print(f"Function ID: {function_id}")
|
|
211
|
+
print("Generating new custom function...", end="")
|
|
212
|
+
functions = get_functions_and_parse(limit_ids=[function_id])
|
|
213
|
+
generate_functions(functions)
|
|
214
|
+
print_green("DONE")
|
|
199
215
|
else:
|
|
200
216
|
print("Error adding function.")
|
|
201
217
|
print(resp.status_code)
|