polyapi-python 0.2.3.dev4__py3-none-any.whl → 0.2.3.dev6__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/client.py ADDED
@@ -0,0 +1,25 @@
1
+ from typing import Any, Dict, List, Tuple
2
+
3
+ from polyapi.typedefs import PropertySpecification
4
+ from polyapi.utils import camelCase, add_type_import_path, parse_arguments, get_type_and_def
5
+
6
+ DEFS_TEMPLATE = """
7
+ from typing import List, Dict, Any, TypedDict
8
+ {args_def}
9
+ {return_type_def}
10
+ """
11
+
12
+
13
+ def render_client_function(
14
+ function_name: str,
15
+ code: str,
16
+ arguments: List[PropertySpecification],
17
+ return_type: Dict[str, Any],
18
+ ) -> Tuple[str, str]:
19
+ args, args_def = parse_arguments(function_name, arguments)
20
+ return_type_name, return_type_def = get_type_and_def(return_type) # type: ignore
21
+ func_type_defs = DEFS_TEMPLATE.format(
22
+ args_def=args_def,
23
+ return_type_def=return_type_def,
24
+ )
25
+ return code, func_type_defs
polyapi/function_cli.py CHANGED
@@ -3,7 +3,7 @@ import argparse
3
3
  import json
4
4
  import types
5
5
  import sys
6
- from typing import Dict, List, Tuple
6
+ from typing import Dict, List, Mapping, Optional, Tuple
7
7
  from typing_extensions import _TypedDictMeta # type: ignore
8
8
  import requests
9
9
  from stdlib_list import stdlib_list
@@ -18,7 +18,7 @@ import importlib
18
18
 
19
19
  # these libraries are already installed in the base docker image
20
20
  # and shouldnt be included in additional requirements
21
- BASE_REQUIREMENTS = {"polyapi", "requests", "typing_extensions", "jsonschema-gentypes", "pydantic"}
21
+ BASE_REQUIREMENTS = {"polyapi", "requests", "typing_extensions", "jsonschema-gentypes", "pydantic", "cloudevents"}
22
22
  all_stdlib_symbols = stdlib_list('.'.join([str(v) for v in sys.version_info[0:2]]))
23
23
  BASE_REQUIREMENTS.update(all_stdlib_symbols) # dont need to pip install stuff in the python standard library
24
24
 
@@ -104,6 +104,19 @@ def _get_type(expr: ast.expr | None, schemas: List[Dict]) -> Tuple[str, Dict | N
104
104
  return json_type, _get_type_schema(json_type, python_type, schemas)
105
105
 
106
106
 
107
+ def _get_req_name_if_not_in_base(n: Optional[str], pip_name_lookup: Mapping[str, List[str]]) -> Optional[str]:
108
+ if not n:
109
+ return None
110
+
111
+ if "." in n:
112
+ n = n.split(".")[0]
113
+
114
+ if n in BASE_REQUIREMENTS:
115
+ return None
116
+ else:
117
+ return pip_name_lookup[n][0]
118
+
119
+
107
120
  def _parse_code(code: str, function_name: str):
108
121
  parsed_args = []
109
122
  return_type = None
@@ -121,13 +134,16 @@ def _parse_code(code: str, function_name: str):
121
134
 
122
135
  for node in ast.iter_child_nodes(parsed_code):
123
136
  if isinstance(node, ast.Import):
137
+ # TODO maybe handle `import foo.bar` case?
124
138
  for name in node.names:
125
- if name.name not in BASE_REQUIREMENTS:
126
- requirements.append(name.name)
139
+ req = _get_req_name_if_not_in_base(name.name, pip_name_lookup)
140
+ if req:
141
+ requirements.append(req)
127
142
  elif isinstance(node, ast.ImportFrom):
128
- if node.module and node.module not in BASE_REQUIREMENTS:
129
- req = pip_name_lookup[node.module][0]
130
- requirements.append(req)
143
+ if node.module:
144
+ req = _get_req_name_if_not_in_base(node.module, pip_name_lookup)
145
+ if req:
146
+ requirements.append(req)
131
147
 
132
148
  elif isinstance(node, ast.FunctionDef) and node.name == function_name:
133
149
  function_args = [arg for arg in node.args.args]
@@ -168,7 +184,8 @@ def function_add_or_update(
168
184
  args = parser.parse_args(subcommands)
169
185
 
170
186
  verb = "Updating" if _func_already_exists(context, args.function_name) else "Adding"
171
- print(f"{verb} custom server side function...", end="")
187
+ ftype = "server" if server else "client"
188
+ print(f"{verb} custom {ftype} function...", end="")
172
189
 
173
190
  with open(args.filename, "r") as f:
174
191
  code = f.read()
@@ -186,9 +203,6 @@ def function_add_or_update(
186
203
  print(f"Function {args.function_name} not found as top-level function in {args.filename}")
187
204
  sys.exit(1)
188
205
 
189
- if requirements:
190
- print_yellow('\nPlease note that deploying your functions will take a few minutes because it makes use of libraries other than polyapi.')
191
-
192
206
  data = {
193
207
  "context": context,
194
208
  "name": args.function_name,
@@ -197,18 +211,20 @@ def function_add_or_update(
197
211
  "language": "python",
198
212
  "returnType": return_type,
199
213
  "returnTypeSchema": return_type_schema,
200
- "requirements": requirements,
201
214
  "arguments": arguments,
202
215
  "logsEnabled": logs_enabled,
203
216
  }
204
217
 
218
+ if server and requirements:
219
+ print_yellow('\nPlease note that deploying your functions will take a few minutes because it makes use of libraries other than polyapi.')
220
+ data["requirements"] = requirements
221
+
205
222
  api_key, api_url = get_api_key_and_url()
206
223
  assert api_key
207
224
  if server:
208
225
  url = f"{api_url}/functions/server"
209
226
  else:
210
- raise NotImplementedError("Client functions not yet implemented.")
211
- # url = f"{base_url}/functions/client"
227
+ url = f"{api_url}/functions/client"
212
228
 
213
229
  headers = get_auth_headers(api_key)
214
230
  resp = requests.post(url, headers=headers, json=data)
polyapi/generate.py CHANGED
@@ -2,9 +2,10 @@ import json
2
2
  import requests
3
3
  import os
4
4
  import shutil
5
- from typing import Any, Dict, List, Tuple
5
+ from typing import List
6
6
 
7
7
  from polyapi.auth import render_auth_function
8
+ from polyapi.client import render_client_function
8
9
  from polyapi.execute import execute_post
9
10
  from polyapi.webhook import render_webhook_handle
10
11
 
@@ -18,6 +19,7 @@ from .config import get_api_key_and_url, initialize_config
18
19
  SUPPORTED_FUNCTION_TYPES = {
19
20
  "apiFunction",
20
21
  "authFunction",
22
+ "customFunction",
21
23
  "serverFunction",
22
24
  "webhookHandle",
23
25
  }
@@ -38,32 +40,29 @@ def get_specs() -> List:
38
40
 
39
41
 
40
42
  def parse_function_specs(
41
- specs: List, limit_ids: List[str] | None # optional list of ids to limit to
42
- ) -> List[Tuple[str, str, str, str, List[PropertySpecification], Dict[str, Any]]]:
43
+ specs: List[SpecificationDto],
44
+ limit_ids: List[str] | None, # optional list of ids to limit to
45
+ ) -> List[SpecificationDto]:
43
46
  functions = []
44
47
  for spec in specs:
48
+ if not spec or "function" not in spec:
49
+ continue
50
+
51
+ if not spec["function"]:
52
+ continue
53
+
45
54
  if limit_ids and spec["id"] not in limit_ids:
46
55
  continue
47
56
 
48
57
  if spec["type"] not in SUPPORTED_FUNCTION_TYPES:
49
58
  continue
50
59
 
51
- function_type = spec["type"]
52
- function_name = f"poly.{spec['context']}.{spec['name']}"
53
- function_id = spec["id"]
54
- arguments: List[PropertySpecification] = [
55
- arg for arg in spec["function"]["arguments"]
56
- ]
57
- functions.append(
58
- (
59
- function_type,
60
- function_name,
61
- function_id,
62
- spec["description"],
63
- arguments,
64
- spec["function"]["returnType"],
65
- )
66
- )
60
+ if spec["type"] == "customFunction" and spec["language"] != "python":
61
+ # poly libraries only support client functions of same language
62
+ continue
63
+
64
+ functions.append(spec)
65
+
67
66
  return functions
68
67
 
69
68
 
@@ -90,11 +89,10 @@ def read_cached_specs() -> List[SpecificationDto]:
90
89
  return json.loads(f.read())
91
90
 
92
91
 
93
- def get_functions_and_parse(limit_ids: List[str] | None = None):
92
+ def get_functions_and_parse(limit_ids: List[str] | None = None) -> List[SpecificationDto]:
94
93
  specs = get_specs()
95
94
  cache_specs(specs)
96
- functions = parse_function_specs(specs, limit_ids=limit_ids)
97
- return functions
95
+ return parse_function_specs(specs, limit_ids=limit_ids)
98
96
 
99
97
 
100
98
  def get_variables() -> List[VariableSpecDto]:
@@ -162,14 +160,7 @@ def save_rendered_specs() -> None:
162
160
  api_specs = [spec for spec in specs if spec["type"] == "apiFunction"]
163
161
  for spec in api_specs:
164
162
  assert spec["function"]
165
- func_str, type_defs = render_spec(
166
- spec["type"],
167
- spec["name"],
168
- spec["id"],
169
- spec["description"],
170
- spec["function"]["arguments"],
171
- spec["function"]["returnType"],
172
- )
163
+ func_str, type_defs = render_spec(spec)
173
164
  data = {
174
165
  "language": "python",
175
166
  "apiFunctionId": spec["id"],
@@ -181,14 +172,20 @@ def save_rendered_specs() -> None:
181
172
  assert resp.status_code == 201, (resp.text, resp.status_code)
182
173
 
183
174
 
184
- def render_spec(
185
- function_type: str,
186
- function_name: str,
187
- function_id: str,
188
- function_description: str,
189
- arguments: List[PropertySpecification],
190
- return_type: Dict[str, Any],
191
- ):
175
+ def render_spec(spec: SpecificationDto):
176
+ function_type = spec["type"]
177
+ function_description = spec["description"]
178
+ function_name = spec["name"]
179
+ function_id = spec["id"]
180
+
181
+ arguments: List[PropertySpecification] = []
182
+ return_type = {}
183
+ if spec["function"]:
184
+ arguments = [
185
+ arg for arg in spec["function"]["arguments"]
186
+ ]
187
+ return_type = spec["function"]["returnType"]
188
+
192
189
  if function_type == "apiFunction":
193
190
  func_str, func_type_defs = render_api_function(
194
191
  function_type,
@@ -198,6 +195,13 @@ def render_spec(
198
195
  arguments,
199
196
  return_type,
200
197
  )
198
+ elif function_type == "customFunction":
199
+ func_str, func_type_defs = render_client_function(
200
+ function_name,
201
+ spec["code"],
202
+ arguments,
203
+ return_type,
204
+ )
201
205
  elif function_type == "serverFunction":
202
206
  func_str, func_type_defs = render_server_function(
203
207
  function_type,
@@ -229,25 +233,14 @@ def render_spec(
229
233
 
230
234
 
231
235
  def add_function_file(
232
- function_type: str,
233
236
  full_path: str,
234
237
  function_name: str,
235
- function_id: str,
236
- function_description: str,
237
- arguments: List[PropertySpecification],
238
- return_type: Dict[str, Any],
238
+ spec: SpecificationDto,
239
239
  ):
240
240
  # first lets add the import to the __init__
241
241
  init_the_init(full_path)
242
242
 
243
- func_str, func_type_defs = render_spec(
244
- function_type,
245
- function_name,
246
- function_id,
247
- function_description,
248
- arguments,
249
- return_type,
250
- )
243
+ func_str, func_type_defs = render_spec(spec)
251
244
 
252
245
  if func_str:
253
246
  # add function to init
@@ -262,27 +255,17 @@ def add_function_file(
262
255
 
263
256
 
264
257
  def create_function(
265
- function_type: str,
266
- path: str,
267
- function_id: str,
268
- function_description: str,
269
- arguments: List[PropertySpecification],
270
- return_type: Dict[str, Any],
258
+ spec: SpecificationDto
271
259
  ) -> None:
272
260
  full_path = os.path.dirname(os.path.abspath(__file__))
273
-
274
- folders = path.split(".")
261
+ folders = f"poly.{spec['context']}.{spec['name']}".split(".")
275
262
  for idx, folder in enumerate(folders):
276
263
  if idx + 1 == len(folders):
277
264
  # special handling for final level
278
265
  add_function_file(
279
- function_type,
280
266
  full_path,
281
267
  folder,
282
- function_id,
283
- function_description,
284
- arguments,
285
- return_type,
268
+ spec,
286
269
  )
287
270
  else:
288
271
  full_path = os.path.join(full_path, folder)
@@ -296,9 +279,6 @@ def create_function(
296
279
  add_import_to_init(full_path, next)
297
280
 
298
281
 
299
- # TODO create the socket and pass to create_function?
300
-
301
-
302
- def generate_functions(functions: List) -> None:
282
+ def generate_functions(functions: List[SpecificationDto]) -> None:
303
283
  for func in functions:
304
- create_function(*func)
284
+ create_function(func)
polyapi/typedefs.py CHANGED
@@ -32,9 +32,11 @@ class SpecificationDto(TypedDict):
32
32
  context: str
33
33
  name: str
34
34
  description: str
35
- # function is none if this is actually VariableSpecDto
36
- function: FunctionSpecification | None
35
+ # function is none (or function key not present) if this is actually VariableSpecDto
36
+ function: NotRequired[FunctionSpecification | None]
37
37
  type: Literal['apiFunction', 'customFunction', 'serverFunction', 'authFunction', 'webhookHandle', 'serverVariable']
38
+ code: NotRequired[str]
39
+ language: str
38
40
 
39
41
 
40
42
  class VariableSpecification(TypedDict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: polyapi-python
3
- Version: 0.2.3.dev4
3
+ Version: 0.2.3.dev6
4
4
  Summary: The PolyAPI Python Client
5
5
  Author-email: Dan Fellin <dan@polyapi.io>
6
6
  License: MIT License
@@ -3,22 +3,23 @@ polyapi/__main__.py,sha256=V4zhAh_YGxno5f_KSrlkELxcuDh9bR3WSd0n-2r-qQQ,93
3
3
  polyapi/api.py,sha256=Pq_OT8egmtlzMjovN5GGZXWnF5oWMkrgR0rmrzJ6ifM,1861
4
4
  polyapi/auth.py,sha256=p2KSLt6q52t9gnqPmgXTOQ2_lmdFilZkIoGmQrRTPLQ,5330
5
5
  polyapi/cli.py,sha256=wMv4RpNDEz2qIQ5NjlRTop-ImfeeqCSkx22ZIrzwA50,2086
6
+ polyapi/client.py,sha256=JIHZaun5aebPA_Fv8-wTsOys_Q5RN_tZp-lZG52Ureg,752
6
7
  polyapi/config.py,sha256=S8TU10upy5OW1_vX-CqQTJD-ZOB6329aMjiUCmukfUI,2292
7
8
  polyapi/constants.py,sha256=NGjso6K5rGnE8TGdrXmdEfvvr-HI3DTVGwOYiWO68LM,511
8
9
  polyapi/error_handler.py,sha256=vl6ZBtsHmC3eu13IMpmZEXBTDJbrPrzmViBCCrEspd4,1621
9
10
  polyapi/exceptions.py,sha256=Zh7i7eCUhDuXEdUYjatkLFTeZkrx1BJ1P5ePgbJ9eIY,89
10
11
  polyapi/execute.py,sha256=06XWTxGJqtsDiLY10RjRebIMFRfhtAIMmBRbuWu3e8A,1873
11
- polyapi/function_cli.py,sha256=x1nAnBQVpjhCDUd_eazQ7HTLgpsNFcY9p6YHy-8iv5E,7660
12
- polyapi/generate.py,sha256=ZZVkA3uL5yPWisnFhIBjoFAYfr96h9m6I0XOCbEpiyk,8970
12
+ polyapi/function_cli.py,sha256=DPaxCYFausf01solU4pHYVp4ePO_ecg10jP-XW_zCmc,8077
13
+ polyapi/generate.py,sha256=O1lb7rLsce7tItIrMx1EbWzRlAKKOp-7upNM80cMmMw,8499
13
14
  polyapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
15
  polyapi/schema.py,sha256=1a7nIO867Xy3bagmPUNdYMxtS5OoCAv8oKIbYgj55dk,2957
15
16
  polyapi/server.py,sha256=mcbH8pG16fFMiFL52wdii0217ZLsznQnElAFEvXZ7IA,2211
16
- polyapi/typedefs.py,sha256=RZ3I6sgJm_5MuuORG1QjUE-UJy_z2WRXNdiWjEdLvQg,1371
17
+ polyapi/typedefs.py,sha256=a5WfHaAvjeql3y1iA3_SkpUztTbKvS5bPqkVxkCvr9E,1459
17
18
  polyapi/utils.py,sha256=wZQrwSOst5wyoYYLoMk1DnAEbVsJ0-2YkmmDQ9gI3FY,5399
18
19
  polyapi/variables.py,sha256=d36-trnfTL_8m2NkorMiImb4O3UrJbiFV38CHxV5i0A,4200
19
20
  polyapi/webhook.py,sha256=A89eNnYcVpVe9doJPDLfscIhF-C7Q2AI3vu-SzGxMBg,2923
20
- polyapi_python-0.2.3.dev4.dist-info/LICENSE,sha256=Hi0kDr56Dsy0uYIwNt4r9G7tI8x8miXRTlyvbeplCP8,1068
21
- polyapi_python-0.2.3.dev4.dist-info/METADATA,sha256=hD2Xr1MamfbMNVt43IcGERa9LfV1sIOTT1SXVhK5rao,4823
22
- polyapi_python-0.2.3.dev4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
23
- polyapi_python-0.2.3.dev4.dist-info/top_level.txt,sha256=CEFllOnzowci_50RYJac-M54KD2IdAptFsayVVF_f04,8
24
- polyapi_python-0.2.3.dev4.dist-info/RECORD,,
21
+ polyapi_python-0.2.3.dev6.dist-info/LICENSE,sha256=Hi0kDr56Dsy0uYIwNt4r9G7tI8x8miXRTlyvbeplCP8,1068
22
+ polyapi_python-0.2.3.dev6.dist-info/METADATA,sha256=6sys65C7Lq9DT1EVLph-fVGdmjBOvm6FzmXEkvfzJqY,4823
23
+ polyapi_python-0.2.3.dev6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
24
+ polyapi_python-0.2.3.dev6.dist-info/top_level.txt,sha256=CEFllOnzowci_50RYJac-M54KD2IdAptFsayVVF_f04,8
25
+ polyapi_python-0.2.3.dev6.dist-info/RECORD,,