polyapi-python 0.3.8.dev9__tar.gz → 0.3.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/PKG-INFO +4 -21
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/README.md +2 -19
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/auth.py +3 -3
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/deployables.py +13 -10
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/execute.py +19 -5
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/generate.py +71 -20
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/poly_schemas.py +41 -7
- polyapi_python-0.3.9/polyapi/poly_tables.py +456 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/prepare.py +5 -3
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/schema.py +16 -2
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/sync.py +10 -5
- polyapi_python-0.3.9/polyapi/typedefs.py +200 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/utils.py +6 -5
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/variables.py +1 -4
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/webhook.py +2 -3
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/PKG-INFO +4 -21
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/SOURCES.txt +2 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/requires.txt +2 -2
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/pyproject.toml +3 -3
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_schema.py +16 -2
- polyapi_python-0.3.9/tests/test_tabi.py +621 -0
- polyapi_python-0.3.8.dev9/polyapi/typedefs.py +0 -93
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/LICENSE +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/__init__.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/api.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/cli.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/client.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/config.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/parser.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/rendered_spec.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/server.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/setup.cfg +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_api.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_deployables.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_generate.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_parser.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_rendered_spec.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_server.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_utils.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_variables.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
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
|
|
@@ -32,10 +32,10 @@ Requires-Dist: requests>=2.32.3
|
|
|
32
32
|
Requires-Dist: typing_extensions>=4.12.2
|
|
33
33
|
Requires-Dist: jsonschema-gentypes==2.6.0
|
|
34
34
|
Requires-Dist: pydantic>=2.8.0
|
|
35
|
-
Requires-Dist: stdlib_list
|
|
35
|
+
Requires-Dist: stdlib_list>=0.10.0
|
|
36
36
|
Requires-Dist: colorama==0.4.4
|
|
37
37
|
Requires-Dist: python-socketio[asyncio_client]==5.11.1
|
|
38
|
-
Requires-Dist: truststore
|
|
38
|
+
Requires-Dist: truststore>=0.8.0
|
|
39
39
|
Dynamic: license-file
|
|
40
40
|
|
|
41
41
|
# PolyAPI Python Library
|
|
@@ -110,24 +110,6 @@ def bar():
|
|
|
110
110
|
return "Hello World"
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
## See Server Function Logs
|
|
114
|
-
|
|
115
|
-
In order to see function logs, please first set `logsEnabled` to `true` in Canopy for the function.
|
|
116
|
-
|
|
117
|
-
https://na1.polyapi.io/canopy/polyui/collections/server-functions
|
|
118
|
-
|
|
119
|
-
Then in your code, get the poly logger and log with it like so:
|
|
120
|
-
|
|
121
|
-
```python
|
|
122
|
-
logger = logging.getLogger("poly")
|
|
123
|
-
def bar():
|
|
124
|
-
logger.warning("I AM THE LOG")
|
|
125
|
-
return "Hello World"
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Finally, click the "Show Logs" button to see your server function logs in Canopy!
|
|
129
|
-
|
|
130
|
-
|
|
131
113
|
## Complex Types In Server Functions
|
|
132
114
|
|
|
133
115
|
You can define arbitrarily complex argument and return types using TypedDicts.
|
|
@@ -206,3 +188,4 @@ Please ignore \[name-defined\] errors for now. This is a known bug we are workin
|
|
|
206
188
|
## Support
|
|
207
189
|
|
|
208
190
|
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
191
|
+
.
|
|
@@ -70,24 +70,6 @@ def bar():
|
|
|
70
70
|
return "Hello World"
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
## See Server Function Logs
|
|
74
|
-
|
|
75
|
-
In order to see function logs, please first set `logsEnabled` to `true` in Canopy for the function.
|
|
76
|
-
|
|
77
|
-
https://na1.polyapi.io/canopy/polyui/collections/server-functions
|
|
78
|
-
|
|
79
|
-
Then in your code, get the poly logger and log with it like so:
|
|
80
|
-
|
|
81
|
-
```python
|
|
82
|
-
logger = logging.getLogger("poly")
|
|
83
|
-
def bar():
|
|
84
|
-
logger.warning("I AM THE LOG")
|
|
85
|
-
return "Hello World"
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Finally, click the "Show Logs" button to see your server function logs in Canopy!
|
|
89
|
-
|
|
90
|
-
|
|
91
73
|
## Complex Types In Server Functions
|
|
92
74
|
|
|
93
75
|
You can define arbitrarily complex argument and return types using TypedDicts.
|
|
@@ -165,4 +147,5 @@ Please ignore \[name-defined\] errors for now. This is a known bug we are workin
|
|
|
165
147
|
|
|
166
148
|
## Support
|
|
167
149
|
|
|
168
|
-
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
150
|
+
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
151
|
+
.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from typing import List, Dict, Any, Tuple
|
|
2
|
-
import uuid
|
|
3
2
|
|
|
4
3
|
from polyapi.typedefs import PropertySpecification
|
|
5
4
|
from polyapi.utils import parse_arguments, get_type_and_def
|
|
@@ -26,7 +25,8 @@ async def getToken(clientId: str, clientSecret: str, scopes: List[str], callback
|
|
|
26
25
|
|
|
27
26
|
Function ID: {function_id}
|
|
28
27
|
\"""
|
|
29
|
-
|
|
28
|
+
from polyapi.poly.client_id import client_id
|
|
29
|
+
eventsClientId = client_id
|
|
30
30
|
function_id = "{function_id}"
|
|
31
31
|
|
|
32
32
|
options = options or {{}}
|
|
@@ -165,7 +165,7 @@ def render_auth_function(
|
|
|
165
165
|
func_str = ""
|
|
166
166
|
|
|
167
167
|
if function_name == "getToken":
|
|
168
|
-
func_str = GET_TOKEN_TEMPLATE.format(function_id=function_id, description=function_description
|
|
168
|
+
func_str = GET_TOKEN_TEMPLATE.format(function_id=function_id, description=function_description)
|
|
169
169
|
elif function_name == "introspectToken":
|
|
170
170
|
func_str = INTROSPECT_TOKEN_TEMPLATE.format(function_id=function_id, description=function_description)
|
|
171
171
|
elif function_name == "refreshToken":
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import string
|
|
3
|
+
import random
|
|
2
4
|
import subprocess
|
|
3
5
|
import json
|
|
4
6
|
import hashlib
|
|
@@ -65,20 +67,21 @@ class SyncDeployment(TypedDict, total=False):
|
|
|
65
67
|
context: str
|
|
66
68
|
name: str
|
|
67
69
|
description: str
|
|
68
|
-
type:
|
|
70
|
+
type: DeployableTypes
|
|
69
71
|
fileRevision: str
|
|
70
72
|
file: str
|
|
71
73
|
types: DeployableFunctionTypes
|
|
72
|
-
typeSchemas: Dict[str,
|
|
74
|
+
typeSchemas: Dict[str, Any]
|
|
73
75
|
dependencies: List[str]
|
|
74
|
-
config: Dict[str,
|
|
76
|
+
config: Dict[str, Any]
|
|
75
77
|
instance: str
|
|
76
|
-
id: Optional[str]
|
|
77
|
-
deployed: Optional[str]
|
|
78
|
+
id: Optional[str]
|
|
79
|
+
deployed: Optional[str]
|
|
80
|
+
|
|
78
81
|
|
|
79
82
|
DeployableTypeEntries: List[Tuple[DeployableTypeNames, DeployableTypes]] = [
|
|
80
|
-
("PolyServerFunction", "server-function"),
|
|
81
|
-
("PolyClientFunction", "client-function"),
|
|
83
|
+
("PolyServerFunction", "server-function"), # type: ignore
|
|
84
|
+
("PolyClientFunction", "client-function"), # type: ignore
|
|
82
85
|
]
|
|
83
86
|
|
|
84
87
|
DeployableTypeToName: Dict[DeployableTypeNames, DeployableTypes] = {name: type for name, type in DeployableTypeEntries}
|
|
@@ -118,13 +121,13 @@ def get_all_deployable_files_windows(config: PolyDeployConfig) -> List[str]:
|
|
|
118
121
|
pattern = ' '.join(f"/C:\"polyConfig: {name}\"" for name in config["type_names"]) or '/C:"polyConfig"'
|
|
119
122
|
|
|
120
123
|
exclude_command = f" | findstr /V /I \"{exclude_pattern}\"" if exclude_pattern else ''
|
|
121
|
-
search_command = f" | findstr /
|
|
124
|
+
search_command = f" | findstr /M /I /F:/ {pattern}"
|
|
122
125
|
|
|
123
126
|
result = []
|
|
124
127
|
for dir_path in config["include_dirs"]:
|
|
125
128
|
if dir_path != '.':
|
|
126
129
|
include_pattern = " ".join(f"{dir_path}*.{f}" for f in config["include_files_or_extensions"]) or "*"
|
|
127
|
-
dir_command = f"dir {include_pattern} /S /P /B
|
|
130
|
+
dir_command = f"dir {include_pattern} /S /P /B"
|
|
128
131
|
full_command = f"{dir_command}{exclude_command}{search_command}"
|
|
129
132
|
try:
|
|
130
133
|
output = subprocess.check_output(full_command, shell=True, text=True)
|
|
@@ -175,7 +178,7 @@ def get_git_revision(branch_or_tag: str = "HEAD") -> str:
|
|
|
175
178
|
return check_output(["git", "rev-parse", "--short", branch_or_tag], text=True).strip()
|
|
176
179
|
except CalledProcessError:
|
|
177
180
|
# Return a random 7-character hash as a fallback
|
|
178
|
-
return "".join(
|
|
181
|
+
return "".join([random.choice(string.ascii_letters + string.digits) for _ in range(7)])
|
|
179
182
|
|
|
180
183
|
def get_cache_deployments_revision() -> str:
|
|
181
184
|
"""Retrieve the cache deployments revision from a file."""
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
from typing import Dict, Optional
|
|
2
2
|
import requests
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
3
5
|
from requests import Response
|
|
4
6
|
from polyapi.config import get_api_key_and_url, get_mtls_config
|
|
5
7
|
from polyapi.exceptions import PolyApiException
|
|
6
8
|
|
|
9
|
+
logger = logging.getLogger("poly")
|
|
10
|
+
|
|
7
11
|
def direct_execute(function_type, function_id, data) -> Response:
|
|
8
12
|
""" execute a specific function id/type
|
|
9
13
|
"""
|
|
@@ -13,7 +17,11 @@ def direct_execute(function_type, function_id, data) -> Response:
|
|
|
13
17
|
|
|
14
18
|
endpoint_info = requests.post(url, json=data, headers=headers)
|
|
15
19
|
if endpoint_info.status_code < 200 or endpoint_info.status_code >= 300:
|
|
16
|
-
|
|
20
|
+
error_content = endpoint_info.content.decode("utf-8", errors="ignore")
|
|
21
|
+
if function_type == 'api' and os.getenv("LOGS_ENABLED"):
|
|
22
|
+
raise PolyApiException(f"Error executing api function with id: {function_id}. Status code: {endpoint_info.status_code}. Request data: {data}, Response: {error_content}")
|
|
23
|
+
elif function_type != 'api':
|
|
24
|
+
raise PolyApiException(f"{endpoint_info.status_code}: {error_content}")
|
|
17
25
|
|
|
18
26
|
endpoint_info_data = endpoint_info.json()
|
|
19
27
|
request_params = endpoint_info_data.copy()
|
|
@@ -38,9 +46,12 @@ def direct_execute(function_type, function_id, data) -> Response:
|
|
|
38
46
|
**request_params
|
|
39
47
|
)
|
|
40
48
|
|
|
41
|
-
if resp.status_code < 200 or resp.status_code >= 300:
|
|
49
|
+
if (resp.status_code < 200 or resp.status_code >= 300):
|
|
42
50
|
error_content = resp.content.decode("utf-8", errors="ignore")
|
|
43
|
-
|
|
51
|
+
if function_type == 'api' and os.getenv("LOGS_ENABLED"):
|
|
52
|
+
logger.error(f"Error executing api function with id: {function_id}. Status code: {resp.status_code}. Request data: {data}, Response: {error_content}")
|
|
53
|
+
elif function_type != 'api':
|
|
54
|
+
raise PolyApiException(f"{resp.status_code}: {error_content}")
|
|
44
55
|
|
|
45
56
|
return resp
|
|
46
57
|
|
|
@@ -59,9 +70,12 @@ def execute(function_type, function_id, data) -> Response:
|
|
|
59
70
|
headers=headers,
|
|
60
71
|
)
|
|
61
72
|
|
|
62
|
-
if resp.status_code < 200 or resp.status_code >= 300:
|
|
73
|
+
if (resp.status_code < 200 or resp.status_code >= 300) and os.getenv("LOGS_ENABLED"):
|
|
63
74
|
error_content = resp.content.decode("utf-8", errors="ignore")
|
|
64
|
-
|
|
75
|
+
if function_type == 'api' and os.getenv("LOGS_ENABLED"):
|
|
76
|
+
logger.error(f"Error executing api function with id: {function_id}. Status code: {resp.status_code}. Request data: {data}, Response: {error_content}")
|
|
77
|
+
elif function_type != 'api':
|
|
78
|
+
raise PolyApiException(f"{resp.status_code}: {error_content}")
|
|
65
79
|
|
|
66
80
|
return resp
|
|
67
81
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import requests
|
|
3
3
|
import os
|
|
4
|
+
import uuid
|
|
4
5
|
import shutil
|
|
5
6
|
import logging
|
|
6
7
|
import tempfile
|
|
8
|
+
|
|
9
|
+
from copy import deepcopy
|
|
7
10
|
from typing import Any, List, Optional, Tuple, cast
|
|
8
11
|
|
|
9
12
|
from .auth import render_auth_function
|
|
@@ -11,11 +14,12 @@ from .client import render_client_function
|
|
|
11
14
|
from .poly_schemas import generate_schemas
|
|
12
15
|
from .webhook import render_webhook_handle
|
|
13
16
|
|
|
14
|
-
from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto
|
|
17
|
+
from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto, TableSpecDto
|
|
15
18
|
from .api import render_api_function
|
|
16
19
|
from .server import render_server_function
|
|
17
20
|
from .utils import add_import_to_init, get_auth_headers, init_the_init, print_green, to_func_namespace
|
|
18
21
|
from .variables import generate_variables
|
|
22
|
+
from .poly_tables import generate_tables
|
|
19
23
|
from .config import get_api_key_and_url, get_direct_execute_config, get_cached_generate_args
|
|
20
24
|
|
|
21
25
|
SUPPORTED_FUNCTION_TYPES = {
|
|
@@ -26,7 +30,7 @@ SUPPORTED_FUNCTION_TYPES = {
|
|
|
26
30
|
"webhookHandle",
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable", "schema", "snippet"}
|
|
33
|
+
SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable", "schema", "snippet", "table"}
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
X_POLY_REF_WARNING = '''"""
|
|
@@ -136,19 +140,22 @@ def parse_function_specs(
|
|
|
136
140
|
limit_ids: List[str] | None = None, # optional list of ids to limit to
|
|
137
141
|
) -> List[SpecificationDto]:
|
|
138
142
|
functions = []
|
|
139
|
-
for
|
|
140
|
-
if not
|
|
143
|
+
for raw_spec in specs:
|
|
144
|
+
if not raw_spec:
|
|
141
145
|
continue
|
|
142
146
|
|
|
143
147
|
# For no_types mode, we might not have function data, but we still want to include the spec
|
|
144
148
|
# if it's a supported function type
|
|
145
|
-
if
|
|
149
|
+
if raw_spec["type"] not in SUPPORTED_FUNCTION_TYPES:
|
|
146
150
|
continue
|
|
147
151
|
|
|
148
152
|
# Skip if we have a limit and this spec is not in it
|
|
149
|
-
if limit_ids and
|
|
153
|
+
if limit_ids and raw_spec.get("id") not in limit_ids:
|
|
150
154
|
continue
|
|
151
155
|
|
|
156
|
+
# Should really be fixed in specs api, but for now handle json strings in arg schemas
|
|
157
|
+
spec = normalize_args_schema(raw_spec)
|
|
158
|
+
|
|
152
159
|
# For customFunction, check language if we have function data
|
|
153
160
|
if spec["type"] == "customFunction":
|
|
154
161
|
if spec.get("language") and spec["language"] != "python":
|
|
@@ -190,16 +197,18 @@ def read_cached_specs() -> List[SpecificationDto]:
|
|
|
190
197
|
return json.loads(f.read())
|
|
191
198
|
|
|
192
199
|
|
|
193
|
-
def get_variables() -> List[VariableSpecDto]:
|
|
194
|
-
specs = read_cached_specs()
|
|
200
|
+
def get_variables(specs: List[SpecificationDto]) -> List[VariableSpecDto]:
|
|
195
201
|
return [cast(VariableSpecDto, spec) for spec in specs if spec["type"] == "serverVariable"]
|
|
196
202
|
|
|
197
203
|
|
|
198
|
-
def get_schemas() -> List[SchemaSpecDto]:
|
|
199
|
-
specs = read_cached_specs()
|
|
204
|
+
def get_schemas(specs: List[SpecificationDto]) -> List[SchemaSpecDto]:
|
|
200
205
|
return [cast(SchemaSpecDto, spec) for spec in specs if spec["type"] == "schema"]
|
|
201
206
|
|
|
202
207
|
|
|
208
|
+
def get_tables(specs: List[SpecificationDto]) -> List[TableSpecDto]:
|
|
209
|
+
return [cast(TableSpecDto, spec) for spec in specs if spec["type"] == "table"]
|
|
210
|
+
|
|
211
|
+
|
|
203
212
|
def remove_old_library():
|
|
204
213
|
currdir = os.path.dirname(os.path.abspath(__file__))
|
|
205
214
|
path = os.path.join(currdir, "poly")
|
|
@@ -214,6 +223,10 @@ def remove_old_library():
|
|
|
214
223
|
if os.path.exists(path):
|
|
215
224
|
shutil.rmtree(path)
|
|
216
225
|
|
|
226
|
+
path = os.path.join(currdir, "tabi")
|
|
227
|
+
if os.path.exists(path):
|
|
228
|
+
shutil.rmtree(path)
|
|
229
|
+
|
|
217
230
|
|
|
218
231
|
def create_empty_schemas_module():
|
|
219
232
|
"""Create an empty schemas module for no-types mode so user code can still import from polyapi.schemas"""
|
|
@@ -272,6 +285,14 @@ sys.modules[__name__] = _SchemaModule()
|
|
|
272
285
|
''')
|
|
273
286
|
|
|
274
287
|
|
|
288
|
+
def _generate_client_id() -> None:
|
|
289
|
+
full_path = os.path.dirname(os.path.abspath(__file__))
|
|
290
|
+
full_path = os.path.join(full_path, "poly", "client_id.py")
|
|
291
|
+
with open(full_path, "w") as f:
|
|
292
|
+
f.write(f'client_id = "{uuid.uuid4().hex}"')
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
275
296
|
def generate_from_cache() -> None:
|
|
276
297
|
"""
|
|
277
298
|
Generate using cached values after non-explicit call.
|
|
@@ -286,6 +307,37 @@ def generate_from_cache() -> None:
|
|
|
286
307
|
)
|
|
287
308
|
|
|
288
309
|
|
|
310
|
+
def _parse_arg_schema(value: Any) -> Any:
|
|
311
|
+
if isinstance(value, str):
|
|
312
|
+
text = value.strip()
|
|
313
|
+
if text and text[0] in "{[":
|
|
314
|
+
try:
|
|
315
|
+
return json.loads(text)
|
|
316
|
+
except json.JSONDecodeError:
|
|
317
|
+
logging.warning("Could not parse function argument schema (leaving as str): %s", text[:200])
|
|
318
|
+
return value
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def normalize_args_schema(
|
|
322
|
+
raw_spec: SpecificationDto
|
|
323
|
+
) -> SpecificationDto:
|
|
324
|
+
spec = deepcopy(raw_spec)
|
|
325
|
+
|
|
326
|
+
function_block = spec.get("function")
|
|
327
|
+
if not isinstance(function_block, dict):
|
|
328
|
+
return spec
|
|
329
|
+
arguments_block = function_block.get("arguments")
|
|
330
|
+
if not isinstance(arguments_block, list):
|
|
331
|
+
return spec
|
|
332
|
+
|
|
333
|
+
for argument in arguments_block:
|
|
334
|
+
arg_type = argument.get("type")
|
|
335
|
+
if isinstance(arg_type, dict) and "schema" in arg_type:
|
|
336
|
+
arg_type["schema"] = _parse_arg_schema(arg_type["schema"])
|
|
337
|
+
|
|
338
|
+
return spec
|
|
339
|
+
|
|
340
|
+
|
|
289
341
|
def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] = None, function_ids: Optional[List[str]] = None, no_types: bool = False) -> None:
|
|
290
342
|
generate_msg = f"Generating Poly Python SDK for contexts ${contexts}..." if contexts else "Generating Poly Python SDK..."
|
|
291
343
|
print(generate_msg, end="", flush=True)
|
|
@@ -297,9 +349,11 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
|
|
|
297
349
|
limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
|
|
298
350
|
functions = parse_function_specs(specs, limit_ids=limit_ids)
|
|
299
351
|
|
|
352
|
+
_generate_client_id()
|
|
353
|
+
|
|
300
354
|
# Only process schemas if no_types is False
|
|
301
355
|
if not no_types:
|
|
302
|
-
schemas = get_schemas()
|
|
356
|
+
schemas = get_schemas(specs)
|
|
303
357
|
schema_index = build_schema_index(schemas)
|
|
304
358
|
if schemas:
|
|
305
359
|
schema_limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
|
|
@@ -323,7 +377,11 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
|
|
|
323
377
|
)
|
|
324
378
|
exit()
|
|
325
379
|
|
|
326
|
-
|
|
380
|
+
tables = get_tables(specs)
|
|
381
|
+
if tables:
|
|
382
|
+
generate_tables(tables)
|
|
383
|
+
|
|
384
|
+
variables = get_variables(specs)
|
|
327
385
|
if variables:
|
|
328
386
|
generate_variables(variables)
|
|
329
387
|
|
|
@@ -335,14 +393,7 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
|
|
|
335
393
|
|
|
336
394
|
|
|
337
395
|
def clear() -> None:
|
|
338
|
-
|
|
339
|
-
poly_path = os.path.join(base, "poly")
|
|
340
|
-
if os.path.exists(poly_path):
|
|
341
|
-
shutil.rmtree(poly_path)
|
|
342
|
-
|
|
343
|
-
vari_path = os.path.join(base, "vari")
|
|
344
|
-
if os.path.exists(vari_path):
|
|
345
|
-
shutil.rmtree(vari_path)
|
|
396
|
+
remove_old_library()
|
|
346
397
|
print("Cleared!")
|
|
347
398
|
|
|
348
399
|
|
|
@@ -25,11 +25,13 @@ FALLBACK_SPEC_TEMPLATE = """class {name}(TypedDict, total=False):
|
|
|
25
25
|
|
|
26
26
|
def generate_schemas(specs: List[SchemaSpecDto], limit_ids: List[str] = None):
|
|
27
27
|
failed_schemas = []
|
|
28
|
+
successful_schemas = []
|
|
28
29
|
if limit_ids:
|
|
29
30
|
for spec in specs:
|
|
30
31
|
if spec["id"] in limit_ids:
|
|
31
32
|
try:
|
|
32
33
|
create_schema(spec)
|
|
34
|
+
successful_schemas.append(f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}")
|
|
33
35
|
except Exception as e:
|
|
34
36
|
schema_path = f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}"
|
|
35
37
|
schema_id = spec.get('id', 'unknown')
|
|
@@ -40,6 +42,7 @@ def generate_schemas(specs: List[SchemaSpecDto], limit_ids: List[str] = None):
|
|
|
40
42
|
for spec in specs:
|
|
41
43
|
try:
|
|
42
44
|
create_schema(spec)
|
|
45
|
+
successful_schemas.append(f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}")
|
|
43
46
|
except Exception as e:
|
|
44
47
|
schema_path = f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}"
|
|
45
48
|
schema_id = spec.get('id', 'unknown')
|
|
@@ -51,6 +54,37 @@ def generate_schemas(specs: List[SchemaSpecDto], limit_ids: List[str] = None):
|
|
|
51
54
|
logging.warning(f"WARNING: {len(failed_schemas)} schema(s) failed to generate:")
|
|
52
55
|
for failed_schema in failed_schemas:
|
|
53
56
|
logging.warning(f" - {failed_schema}")
|
|
57
|
+
logging.warning(f"Successfully generated {len(successful_schemas)} schema(s)")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_schema_content(schema_content: str, schema_name: str) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Validate that the schema content is meaningful and not just imports.
|
|
63
|
+
Returns True if the schema is valid, False otherwise.
|
|
64
|
+
"""
|
|
65
|
+
if not schema_content or not schema_content.strip():
|
|
66
|
+
logging.debug(f"Schema {schema_name} failed validation: Empty content")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
lines = schema_content.strip().split('\n')
|
|
70
|
+
|
|
71
|
+
# Check if the content has any actual class definitions or type aliases
|
|
72
|
+
has_class_definition = any(line.strip().startswith('class ') for line in lines)
|
|
73
|
+
has_type_alias = any(schema_name in line and '=' in line and not line.strip().startswith('#') for line in lines)
|
|
74
|
+
|
|
75
|
+
# Check if it's essentially just imports (less than 5 lines and no meaningful definitions)
|
|
76
|
+
meaningful_lines = [line for line in lines if line.strip() and not line.strip().startswith('from ') and not line.strip().startswith('import ') and not line.strip().startswith('#')]
|
|
77
|
+
|
|
78
|
+
# Enhanced logging for debugging
|
|
79
|
+
if not (has_class_definition or has_type_alias) or len(meaningful_lines) < 1:
|
|
80
|
+
# Determine the specific reason for failure
|
|
81
|
+
if len(meaningful_lines) == 0:
|
|
82
|
+
logging.debug(f"Schema {schema_name} failed validation: No meaningful content (only imports) - likely empty object or unresolved reference")
|
|
83
|
+
elif not has_class_definition and not has_type_alias:
|
|
84
|
+
logging.debug(f"Schema {schema_name} failed validation: No class definition or type alias found")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
return True
|
|
54
88
|
|
|
55
89
|
|
|
56
90
|
def add_schema_file(
|
|
@@ -75,9 +109,9 @@ def add_schema_file(
|
|
|
75
109
|
|
|
76
110
|
schema_defs = render_poly_schema(spec)
|
|
77
111
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
raise Exception("Schema rendering failed
|
|
112
|
+
# Validate schema content before proceeding
|
|
113
|
+
if not validate_schema_content(schema_defs, schema_name):
|
|
114
|
+
raise Exception(f"Schema rendering failed or produced invalid content for {schema_name}")
|
|
81
115
|
|
|
82
116
|
# Prepare all content first before writing any files
|
|
83
117
|
schema_namespace = to_func_namespace(schema_name)
|
|
@@ -87,7 +121,7 @@ def add_schema_file(
|
|
|
87
121
|
# Read current __init__.py content if it exists
|
|
88
122
|
init_content = ""
|
|
89
123
|
if os.path.exists(init_path):
|
|
90
|
-
with open(init_path, "r") as f:
|
|
124
|
+
with open(init_path, "r", encoding='utf-8') as f:
|
|
91
125
|
init_content = f.read()
|
|
92
126
|
|
|
93
127
|
# Prepare new content to append to __init__.py
|
|
@@ -95,12 +129,12 @@ def add_schema_file(
|
|
|
95
129
|
|
|
96
130
|
# Use temporary files for atomic writes
|
|
97
131
|
# Write to __init__.py atomically
|
|
98
|
-
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp") as temp_init:
|
|
132
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp", encoding='utf-8') as temp_init:
|
|
99
133
|
temp_init.write(new_init_content)
|
|
100
134
|
temp_init_path = temp_init.name
|
|
101
135
|
|
|
102
136
|
# Write to schema file atomically
|
|
103
|
-
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp") as temp_schema:
|
|
137
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp", encoding='utf-8') as temp_schema:
|
|
104
138
|
temp_schema.write(schema_defs)
|
|
105
139
|
temp_schema_path = temp_schema.name
|
|
106
140
|
|
|
@@ -171,7 +205,7 @@ def create_schema(
|
|
|
171
205
|
def add_schema_to_init(full_path: str, spec: SchemaSpecDto):
|
|
172
206
|
init_the_init(full_path, code_imports="")
|
|
173
207
|
init_path = os.path.join(full_path, "__init__.py")
|
|
174
|
-
with open(init_path, "a") as f:
|
|
208
|
+
with open(init_path, "a", encoding='utf-8') as f:
|
|
175
209
|
f.write(render_poly_schema(spec) + "\n\n")
|
|
176
210
|
|
|
177
211
|
|