polyapi-python 0.0.35__tar.gz → 0.1.0__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.
Files changed (38) hide show
  1. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/PKG-INFO +11 -1
  2. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/README.md +8 -0
  3. polyapi-python-0.1.0/polyapi/api.py +57 -0
  4. polyapi-python-0.1.0/polyapi/auth.py +151 -0
  5. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/cli.py +16 -7
  6. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/config.py +9 -0
  7. polyapi-python-0.1.0/polyapi/execute.py +46 -0
  8. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/function_cli.py +26 -10
  9. polyapi-python-0.1.0/polyapi/generate.py +243 -0
  10. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/schema.py +9 -2
  11. polyapi-python-0.1.0/polyapi/server.py +64 -0
  12. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/typedefs.py +18 -2
  13. polyapi-python-0.1.0/polyapi/utils.py +154 -0
  14. polyapi-python-0.1.0/polyapi/variables.py +102 -0
  15. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi_python.egg-info/PKG-INFO +11 -1
  16. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi_python.egg-info/SOURCES.txt +7 -2
  17. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi_python.egg-info/requires.txt +2 -0
  18. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/pyproject.toml +3 -3
  19. polyapi-python-0.0.35/tests/test_generate.py → polyapi-python-0.1.0/tests/test_api.py +108 -53
  20. polyapi-python-0.1.0/tests/test_auth.py +139 -0
  21. polyapi-python-0.1.0/tests/test_server.py +62 -0
  22. polyapi-python-0.1.0/tests/test_variables.py +28 -0
  23. polyapi-python-0.0.35/polyapi/api.py +0 -290
  24. polyapi-python-0.0.35/polyapi/execute.py +0 -15
  25. polyapi-python-0.0.35/polyapi/generate.py +0 -148
  26. polyapi-python-0.0.35/polyapi/utils.py +0 -26
  27. polyapi-python-0.0.35/polyapi/variables.py +0 -86
  28. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/LICENSE +0 -0
  29. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/__init__.py +0 -0
  30. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/__main__.py +0 -0
  31. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/constants.py +0 -0
  32. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/exceptions.py +0 -0
  33. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi/py.typed +0 -0
  34. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi_python.egg-info/dependency_links.txt +0 -0
  35. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/polyapi_python.egg-info/top_level.txt +0 -0
  36. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/setup.cfg +0 -0
  37. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/tests/test_function_cli.py +0 -0
  38. {polyapi-python-0.0.35 → polyapi-python-0.1.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: polyapi-python
3
- Version: 0.0.35
3
+ Version: 0.1.0
4
4
  Summary: The PolyAPI Python Client
5
5
  Author-email: Dan Fellin <dan@polyapi.io>
6
6
  License: MIT License
@@ -33,6 +33,8 @@ Requires-Dist: typing_extensions
33
33
  Requires-Dist: jsonschema-gentypes
34
34
  Requires-Dist: pydantic>=2.5.3
35
35
  Requires-Dist: stdlib_list
36
+ Requires-Dist: colorama
37
+ Requires-Dist: python-socketio[asyncio_client]
36
38
 
37
39
  # PolyAPI Python Library
38
40
 
@@ -145,6 +147,14 @@ To upgrade your library to the latest dev version, pass the `--pre` flag.
145
147
  pip install polyapi-python --pre --upgrade
146
148
  ```
147
149
 
150
+ ## Change Your API Key
151
+
152
+ If you need to change your API key or what server you are pointing to, you can run:
153
+
154
+ ```bash
155
+ python -m polyapi setup
156
+ ```
157
+
148
158
  ## Unit Tests
149
159
 
150
160
  To run this library's unit tests, please clone the repo then run:
@@ -109,6 +109,14 @@ To upgrade your library to the latest dev version, pass the `--pre` flag.
109
109
  pip install polyapi-python --pre --upgrade
110
110
  ```
111
111
 
112
+ ## Change Your API Key
113
+
114
+ If you need to change your API key or what server you are pointing to, you can run:
115
+
116
+ ```bash
117
+ python -m polyapi setup
118
+ ```
119
+
112
120
  ## Unit Tests
113
121
 
114
122
  To run this library's unit tests, please clone the repo then run:
@@ -0,0 +1,57 @@
1
+ from typing import Any, Dict, List, Tuple
2
+
3
+ from polyapi.typedefs import PropertySpecification
4
+ from polyapi.utils import add_type_import_path, camelCase, parse_arguments, get_type_and_def
5
+
6
+
7
+ API_DEFS_TEMPLATE = """
8
+ from typing import List, Dict, Any, TypedDict
9
+ {args_def}
10
+ {return_type_def}
11
+ class {api_response_type}(TypedDict):
12
+ status: int
13
+ headers: Dict
14
+ data: {return_type_name}
15
+ """
16
+
17
+ API_FUNCTION_TEMPLATE = """
18
+ def {function_name}(
19
+ {args}
20
+ ) -> {api_response_type}:
21
+ "{function_description}"
22
+ resp = execute("{function_type}", "{function_id}", {data})
23
+ return {api_response_type}(resp.json()) # type: ignore
24
+ """
25
+
26
+
27
+ def render_api_function(
28
+ function_type: str,
29
+ function_name: str,
30
+ function_id: str,
31
+ function_description: str,
32
+ arguments: List[PropertySpecification],
33
+ return_type: Dict[str, Any],
34
+ ) -> Tuple[str, str]:
35
+ assert function_type == "apiFunction"
36
+ arg_names = [a["name"] for a in arguments]
37
+ args, args_def = parse_arguments(function_name, arguments)
38
+ return_type_name, return_type_def = get_type_and_def(return_type) # type: ignore
39
+ data = "{" + ", ".join([f"'{arg}': {camelCase(arg)}" for arg in arg_names]) + "}"
40
+
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,
47
+ )
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
@@ -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
@@ -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 = ["generate", "config", "clear", "function", "help"]
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="PolyAPI Client"
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 if you want this to be a server function. By default, it will be a client function.")
18
- parser.add_argument("--logs", action="store_true", help="Pass --logs if you want to store and see the logs from this function executing")
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
- elif command == "config":
30
- print("Clearing old config...")
38
+ print_green("DONE")
39
+ elif command == "setup":
31
40
  clear_config()
32
41
  generate()
33
42
  elif command == "clear":
@@ -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)
@@ -0,0 +1,46 @@
1
+ import requests
2
+ from requests import Response
3
+ from polyapi.config import get_api_key_and_url
4
+ from polyapi.exceptions import PolyApiException
5
+
6
+
7
+ def execute(function_type, function_id, data) -> Response:
8
+ """ execute a specific function id/type
9
+ """
10
+ api_key, api_url = get_api_key_and_url()
11
+ headers = {"Authorization": f"Bearer {api_key}"}
12
+ url = f"{api_url}/functions/{function_type}/{function_id}/execute"
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)
43
+ if resp.status_code != 200 and resp.status_code != 201:
44
+ error_content = resp.content.decode("utf-8", errors="ignore")
45
+ raise PolyApiException(f"{resp.status_code}: {error_content}")
46
+ return resp
@@ -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 generate
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
- print(
166
- f"Error: function named {args.function_name} not found as top-level function in file. Exiting."
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 added successfully. Function id is {function_id}")
197
- print("Regenerating library...")
198
- generate()
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)
@@ -0,0 +1,243 @@
1
+ import json
2
+ import requests
3
+ import os
4
+ import shutil
5
+ from typing import Any, Dict, List, Tuple
6
+
7
+ from polyapi.auth import render_auth_function
8
+
9
+ from .typedefs import PropertySpecification, SpecificationDto, VariableSpecDto
10
+ from .api import render_api_function
11
+ from .server import render_server_function
12
+ from .utils import add_import_to_init, get_auth_headers, init_the_init
13
+ from .variables import generate_variables
14
+ from .config import get_api_key_and_url, initialize_config
15
+
16
+ SUPPORTED_FUNCTION_TYPES = {
17
+ "apiFunction",
18
+ "authFunction",
19
+ "serverFunction",
20
+ }
21
+
22
+ SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable"}
23
+
24
+
25
+ def get_specs() -> List:
26
+ api_key, api_url = get_api_key_and_url()
27
+ assert api_key
28
+ headers = get_auth_headers(api_key)
29
+ url = f"{api_url}/specs"
30
+ resp = requests.get(url, headers=headers)
31
+ if resp.status_code == 200:
32
+ return resp.json()
33
+ else:
34
+ raise NotImplementedError(resp.content)
35
+
36
+
37
+ def parse_function_specs(
38
+ specs: List,
39
+ limit_ids: List[str] | None # optional list of ids to limit to
40
+ ) -> List[Tuple[str, str, str, str, List[PropertySpecification], Dict[str, Any]]]:
41
+ functions = []
42
+ for spec in specs:
43
+ if limit_ids and spec["id"] not in limit_ids:
44
+ continue
45
+
46
+ if spec["type"] not in SUPPORTED_FUNCTION_TYPES:
47
+ continue
48
+
49
+ function_type = spec["type"]
50
+ function_name = f"poly.{spec['context']}.{spec['name']}"
51
+ function_id = spec["id"]
52
+ arguments: List[PropertySpecification] = [
53
+ arg for arg in spec["function"]["arguments"]
54
+ ]
55
+ functions.append(
56
+ (
57
+ function_type,
58
+ function_name,
59
+ function_id,
60
+ spec["description"],
61
+ arguments,
62
+ spec["function"]["returnType"],
63
+ )
64
+ )
65
+ return functions
66
+
67
+
68
+ def cache_specs(specs: List[SpecificationDto]):
69
+ supported = []
70
+ for spec in specs:
71
+ # this needs to stay in sync with logic in parse_specs
72
+ if spec["type"] in SUPPORTED_TYPES:
73
+ supported.append(spec)
74
+
75
+ full_path = os.path.dirname(os.path.abspath(__file__))
76
+ full_path = os.path.join(full_path, "poly")
77
+ if not os.path.exists(full_path):
78
+ os.makedirs(full_path)
79
+
80
+ with open(os.path.join(full_path, "specs.json"), "w") as f:
81
+ f.write(json.dumps(supported))
82
+
83
+
84
+ def get_functions_and_parse(limit_ids: List[str] | None = None):
85
+ specs = get_specs()
86
+ cache_specs(specs)
87
+ functions = parse_function_specs(specs, limit_ids=limit_ids)
88
+ return functions
89
+
90
+
91
+ def get_variables() -> List[VariableSpecDto]:
92
+ api_key, api_url = get_api_key_and_url()
93
+ headers = {"Authorization": f"Bearer {api_key}"}
94
+ # TODO do some caching so this and get_functions just do 1 function call
95
+ url = f"{api_url}/specs"
96
+ resp = requests.get(url, headers=headers)
97
+ if resp.status_code == 200:
98
+ specs = resp.json()
99
+ return [spec for spec in specs if spec['type'] == "serverVariable"]
100
+ else:
101
+ raise NotImplementedError(resp.content)
102
+
103
+
104
+ def remove_old_library():
105
+ currdir = os.path.dirname(os.path.abspath(__file__))
106
+ path = os.path.join(currdir, "poly")
107
+ if os.path.exists(path):
108
+ shutil.rmtree(path)
109
+
110
+ path = os.path.join(currdir, "vari")
111
+ if os.path.exists(path):
112
+ shutil.rmtree(path)
113
+
114
+
115
+ def generate() -> None:
116
+ initialize_config()
117
+
118
+ remove_old_library()
119
+
120
+ functions = get_functions_and_parse()
121
+ if functions:
122
+ generate_functions(functions)
123
+ else:
124
+ print(
125
+ "No functions exist yet in this tenant! Empty library initialized. Let's add some functions!"
126
+ )
127
+ exit()
128
+
129
+ variables = get_variables()
130
+ if variables:
131
+ generate_variables(variables)
132
+
133
+ # indicator to vscode extension that this is a polyapi-python project
134
+ file_path = os.path.join(os.getcwd(), '.polyapi-python')
135
+ open(file_path, 'w').close()
136
+
137
+
138
+
139
+ def clear() -> None:
140
+ base = os.path.dirname(os.path.abspath(__file__))
141
+ poly_path = os.path.join(base, "poly")
142
+ if os.path.exists(poly_path):
143
+ shutil.rmtree(poly_path)
144
+
145
+ vari_path = os.path.join(base, "vari")
146
+ if os.path.exists(vari_path):
147
+ shutil.rmtree(vari_path)
148
+ print("Cleared!")
149
+
150
+
151
+ def add_function_file(
152
+ function_type: str,
153
+ full_path: str,
154
+ function_name: str,
155
+ function_id: str,
156
+ function_description: str,
157
+ arguments: List[PropertySpecification],
158
+ return_type: Dict[str, Any],
159
+ ):
160
+ # first lets add the import to the __init__
161
+ init_the_init(full_path)
162
+
163
+ if function_type == "apiFunction":
164
+ func_str, func_type_defs = render_api_function(
165
+ function_type,
166
+ function_name,
167
+ function_id,
168
+ function_description,
169
+ arguments,
170
+ return_type,
171
+ )
172
+ elif function_type == "serverFunction":
173
+ func_str, func_type_defs = render_server_function(
174
+ function_type,
175
+ function_name,
176
+ function_id,
177
+ function_description,
178
+ arguments,
179
+ return_type,
180
+ )
181
+ elif function_type == "authFunction":
182
+ func_str, func_type_defs = render_auth_function(
183
+ function_type,
184
+ function_name,
185
+ function_id,
186
+ function_description,
187
+ arguments,
188
+ return_type,
189
+ )
190
+
191
+ if func_str:
192
+ # add function to init
193
+ init_path = os.path.join(full_path, "__init__.py")
194
+ with open(init_path, "a") as f:
195
+ f.write(f"\n\nfrom . import _{function_name}\n\n{func_str}")
196
+
197
+ # add type_defs to underscore file
198
+ file_path = os.path.join(full_path, f"_{function_name}.py")
199
+ with open(file_path, "w") as f:
200
+ f.write(func_type_defs)
201
+
202
+
203
+ def create_function(
204
+ function_type: str,
205
+ path: str,
206
+ function_id: str,
207
+ function_description: str,
208
+ arguments: List[PropertySpecification],
209
+ return_type: Dict[str, Any],
210
+ ) -> None:
211
+ full_path = os.path.dirname(os.path.abspath(__file__))
212
+
213
+ folders = path.split(".")
214
+ for idx, folder in enumerate(folders):
215
+ if idx + 1 == len(folders):
216
+ # special handling for final level
217
+ add_function_file(
218
+ function_type,
219
+ full_path,
220
+ folder,
221
+ function_id,
222
+ function_description,
223
+ arguments,
224
+ return_type,
225
+ )
226
+ else:
227
+ full_path = os.path.join(full_path, folder)
228
+ if not os.path.exists(full_path):
229
+ os.makedirs(full_path)
230
+
231
+ # append to __init__.py file if nested folders
232
+ next = folders[idx + 1] if idx + 2 < len(folders) else ""
233
+ if next:
234
+ init_the_init(full_path)
235
+ add_import_to_init(full_path, next)
236
+
237
+
238
+ # TODO create the socket and pass to create_function?
239
+
240
+
241
+ def generate_functions(functions: List) -> None:
242
+ for func in functions:
243
+ create_function(*func)