polyapi-python 0.3.21.dev0__tar.gz → 0.3.21.dev1__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.21.dev0 → polyapi_python-0.3.21.dev1}/PKG-INFO +11 -3
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/cli.py +0 -17
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/cli_constants.py +0 -1
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/deployables.py +6 -24
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/error_handler.py +7 -5
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/generate.py +9 -9
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/parser.py +74 -18
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/poly_tables.py +9 -2
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/prepare.py +25 -7
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/sync.py +2 -2
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/utils.py +3 -1
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/variables.py +6 -4
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/webhook.py +2 -1
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi_python.egg-info/PKG-INFO +11 -3
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi_python.egg-info/SOURCES.txt +0 -2
- polyapi_python-0.3.21.dev1/polyapi_python.egg-info/requires.txt +18 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/pyproject.toml +14 -3
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_async_proof.py +4 -3
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_dependency_pins.py +3 -11
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_deployables.py +10 -2
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_generate.py +25 -13
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_parser.py +4 -2
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_utils.py +3 -1
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_variables.py +3 -1
- polyapi_python-0.3.21.dev0/polyapi/rendered_spec.py +0 -69
- polyapi_python-0.3.21.dev0/polyapi_python.egg-info/requires.txt +0 -9
- polyapi_python-0.3.21.dev0/tests/test_rendered_spec.py +0 -52
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/LICENSE +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/README.md +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/__init__.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/ai_function.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/api.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/auth.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/client.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/config.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/execute.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/http_client.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/poly_schemas.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/schema.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/server.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi/typedefs.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/setup.cfg +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_api.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_cli.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_generated_poly_boundary.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_poly_custom.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_poly_schemas.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_schema.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_server.py +0 -0
- {polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_tabi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.3.21.
|
|
3
|
+
Version: 0.3.21.dev1
|
|
4
4
|
Summary: The Python SDK for the integration platform PolyAPI
|
|
5
5
|
Author-email: PolyAPI <support@polyapi.io>
|
|
6
6
|
License: MIT License
|
|
@@ -28,15 +28,23 @@ Project-URL: Homepage, https://github.com/polyapi/polyapi-python
|
|
|
28
28
|
Requires-Python: >=3.10
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
|
-
Requires-Dist: requests==2.
|
|
31
|
+
Requires-Dist: requests==2.33.0
|
|
32
32
|
Requires-Dist: typing_extensions==4.15.0
|
|
33
33
|
Requires-Dist: jsonschema-gentypes==2.13.0
|
|
34
34
|
Requires-Dist: pydantic==2.13.4
|
|
35
35
|
Requires-Dist: stdlib_list==0.11.1
|
|
36
36
|
Requires-Dist: colorama==0.4.4
|
|
37
|
-
Requires-Dist: python-socketio[asyncio_client]==5.
|
|
37
|
+
Requires-Dist: python-socketio[asyncio_client]==5.16.2
|
|
38
38
|
Requires-Dist: truststore==0.9.1
|
|
39
39
|
Requires-Dist: httpx==0.28.1
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: mock==5.2.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest; extra == "dev"
|
|
43
|
+
Requires-Dist: flask==3.1.3; extra == "dev"
|
|
44
|
+
Requires-Dist: mypy; extra == "dev"
|
|
45
|
+
Requires-Dist: types-requests; extra == "dev"
|
|
46
|
+
Requires-Dist: types-colorama; extra == "dev"
|
|
47
|
+
Requires-Dist: pip-audit; extra == "dev"
|
|
40
48
|
Dynamic: license-file
|
|
41
49
|
|
|
42
50
|
# PolyAPI Python Library
|
|
@@ -9,7 +9,6 @@ from .cli_constants import CLI_COMMANDS
|
|
|
9
9
|
from .config import initialize_config, set_api_key_and_url
|
|
10
10
|
from .generate import generate, clear
|
|
11
11
|
from .function_cli import function_add_or_update, function_execute
|
|
12
|
-
from .rendered_spec import get_and_update_rendered_spec
|
|
13
12
|
from .prepare import prepare_deployables
|
|
14
13
|
from .sync import sync_deployables
|
|
15
14
|
|
|
@@ -196,22 +195,6 @@ def execute_from_cli():
|
|
|
196
195
|
clear_parser.set_defaults(command=clear_command)
|
|
197
196
|
|
|
198
197
|
|
|
199
|
-
###########################################################################
|
|
200
|
-
# Update rendered spec command
|
|
201
|
-
update_spec_parser = subparsers.add_parser("update_rendered_spec", help="Update the rendered spec file")
|
|
202
|
-
update_spec_parser.add_argument("spec", help="Specification file to update")
|
|
203
|
-
|
|
204
|
-
def update_rendered_spec(args):
|
|
205
|
-
updated = get_and_update_rendered_spec(args.spec)
|
|
206
|
-
if updated:
|
|
207
|
-
print("Updated rendered spec!")
|
|
208
|
-
else:
|
|
209
|
-
print("Failed to update rendered spec!")
|
|
210
|
-
exit(1)
|
|
211
|
-
|
|
212
|
-
update_spec_parser.set_defaults(command=update_rendered_spec)
|
|
213
|
-
|
|
214
|
-
|
|
215
198
|
###########################################################################
|
|
216
199
|
# Prepare command
|
|
217
200
|
prepare_parser = subparsers.add_parser('prepare', help="Find and prepare all Poly deployables")
|
|
@@ -51,7 +51,7 @@ class DeployableFunctionTypes(TypedDict):
|
|
|
51
51
|
params: List[DeployableFunctionParam]
|
|
52
52
|
returns: DeployableFunctionParamBase
|
|
53
53
|
|
|
54
|
-
class DeployableRecord(ParsedDeployableConfig
|
|
54
|
+
class DeployableRecord(ParsedDeployableConfig):
|
|
55
55
|
gitRevision: str
|
|
56
56
|
fileRevision: str
|
|
57
57
|
file: str
|
|
@@ -64,7 +64,7 @@ class DeployableRecord(ParsedDeployableConfig, total=False):
|
|
|
64
64
|
docEndIndex: int
|
|
65
65
|
dirty: Optional[bool]
|
|
66
66
|
|
|
67
|
-
class SyncDeployment(TypedDict
|
|
67
|
+
class SyncDeployment(TypedDict):
|
|
68
68
|
context: str
|
|
69
69
|
name: str
|
|
70
70
|
description: str
|
|
@@ -195,30 +195,12 @@ def write_cache_revision(git_revision: Optional[str] = None) -> None:
|
|
|
195
195
|
with open(CACHE_VERSION_FILE, 'w', encoding='utf-8') as file:
|
|
196
196
|
file.write(git_revision)
|
|
197
197
|
|
|
198
|
-
def is_cache_up_to_date() -> bool:
|
|
199
|
-
if not Path(CACHE_VERSION_FILE).exists():
|
|
200
|
-
return False
|
|
201
|
-
with open(CACHE_VERSION_FILE, 'r', encoding='utf-8') as file:
|
|
202
|
-
cached_revision = file.read().strip()
|
|
203
|
-
git_revision = get_git_revision()
|
|
204
|
-
return cached_revision == git_revision
|
|
205
|
-
|
|
206
198
|
def is_cache_up_to_date() -> bool:
|
|
207
199
|
"""Check if the cached revision matches the current Git revision."""
|
|
208
200
|
cached_revision = get_cache_deployments_revision()
|
|
209
201
|
git_revision = get_git_revision() # This function needs to be defined or imported
|
|
210
202
|
return cached_revision == git_revision
|
|
211
203
|
|
|
212
|
-
def write_deploy_comments(deployments: List[Dict]) -> str:
|
|
213
|
-
"""Generate a string of deployment comments for each deployment."""
|
|
214
|
-
canopy_path = 'polyui/collections' if 'localhost' in os.getenv('POLY_API_BASE_URL', '') else 'canopy/polyui/collections'
|
|
215
|
-
comments = []
|
|
216
|
-
for d in deployments:
|
|
217
|
-
instance_url = d['instance'].replace(':8000', ':3000') if d['instance'].endswith(':8000') else d['instance']
|
|
218
|
-
comment = f"# Poly deployed @ {d['deployed']} - {d['context']}.{d['name']} - {instance_url}/{canopy_path}/{d['type']}s/{d['id']} - {d['fileRevision']}"
|
|
219
|
-
comments.append(comment)
|
|
220
|
-
return '\n'.join(comments)
|
|
221
|
-
|
|
222
204
|
def print_docstring_function_comment(description: str, args: list, returns: dict) -> str:
|
|
223
205
|
docstring = f'"""{description}\n\n'
|
|
224
206
|
if args:
|
|
@@ -243,7 +225,7 @@ def print_docstring_function_comment(description: str, args: list, returns: dict
|
|
|
243
225
|
return docstring
|
|
244
226
|
|
|
245
227
|
|
|
246
|
-
def update_deployment_comments(file_content: str, deployable:
|
|
228
|
+
def update_deployment_comments(file_content: str, deployable: DeployableRecord) -> str:
|
|
247
229
|
"""
|
|
248
230
|
Remove old deployment comments based on the provided ranges and add new ones.
|
|
249
231
|
"""
|
|
@@ -255,7 +237,7 @@ def update_deployment_comments(file_content: str, deployable: dict) -> str:
|
|
|
255
237
|
file_content = f"{deployment_comments}\n{file_content}"
|
|
256
238
|
return file_content
|
|
257
239
|
|
|
258
|
-
def update_deployable_function_comments(file_content: str, deployable:
|
|
240
|
+
def update_deployable_function_comments(file_content: str, deployable: DeployableRecord, disable_docs: bool = False) -> str:
|
|
259
241
|
"""
|
|
260
242
|
Update the docstring in the file content based on the deployable's documentation data.
|
|
261
243
|
"""
|
|
@@ -263,7 +245,7 @@ def update_deployable_function_comments(file_content: str, deployable: dict, dis
|
|
|
263
245
|
docstring = print_docstring_function_comment(
|
|
264
246
|
deployable['types']['description'],
|
|
265
247
|
deployable['types']['params'],
|
|
266
|
-
deployable['types']['returns']
|
|
248
|
+
deployable['types']['returns'] # pyright: ignore[reportArgumentType]
|
|
267
249
|
)
|
|
268
250
|
if deployable["docStartIndex"] == deployable["docEndIndex"]:
|
|
269
251
|
# Function doesn't yet have any docstrings so we need to add additional whitespace
|
|
@@ -272,7 +254,7 @@ def update_deployable_function_comments(file_content: str, deployable: dict, dis
|
|
|
272
254
|
return f"{file_content[:deployable['docStartIndex']]}{docstring}{file_content[deployable['docEndIndex']:]}"
|
|
273
255
|
return file_content
|
|
274
256
|
|
|
275
|
-
def write_updated_deployable(deployable:
|
|
257
|
+
def write_updated_deployable(deployable: DeployableRecord, disable_docs: bool = False) -> DeployableRecord:
|
|
276
258
|
"""
|
|
277
259
|
Read the deployable's file, update its comments and docstring, and write back to the file.
|
|
278
260
|
"""
|
|
@@ -42,7 +42,8 @@ async def unregister_all():
|
|
|
42
42
|
_, base_url = get_api_key_and_url()
|
|
43
43
|
# need to reconnect because maybe socketio client disconnected after Ctrl+C?
|
|
44
44
|
try:
|
|
45
|
-
|
|
45
|
+
if client:
|
|
46
|
+
await client.connect(base_url, transports=["websocket"], namespaces=["/events"])
|
|
46
47
|
except ConnectionError:
|
|
47
48
|
pass
|
|
48
49
|
await asyncio.gather(*[unregister(handler) for handler in active_handlers])
|
|
@@ -63,10 +64,11 @@ async def on(
|
|
|
63
64
|
data["apiKey"] = api_key
|
|
64
65
|
|
|
65
66
|
def registerCallback(id: int):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if client:
|
|
68
|
+
nonlocal handler_id
|
|
69
|
+
handler_id = id
|
|
70
|
+
client.on(f"handleError:{handler_id}", callback, namespace="/events")
|
|
71
|
+
active_handlers.append({"path": path, "id": handler_id, "apiKey": api_key})
|
|
70
72
|
|
|
71
73
|
await client.emit("registerErrorHandler", data, "/events", registerCallback)
|
|
72
74
|
|
|
@@ -7,7 +7,7 @@ import tempfile
|
|
|
7
7
|
import stat
|
|
8
8
|
|
|
9
9
|
from copy import deepcopy
|
|
10
|
-
from typing import Any, List, Optional, Tuple, cast
|
|
10
|
+
from typing import Any, Dict, List, Optional, Tuple, cast
|
|
11
11
|
|
|
12
12
|
from .auth import render_auth_function
|
|
13
13
|
from .client import render_client_function
|
|
@@ -104,7 +104,7 @@ def replace_poly_refs_in_functions(specs: List[SpecificationDto], schema_index):
|
|
|
104
104
|
func = spec.get("function")
|
|
105
105
|
if func:
|
|
106
106
|
try:
|
|
107
|
-
spec["function"] = resolve_poly_refs(func, schema_index)
|
|
107
|
+
spec["function"] = resolve_poly_refs(func, schema_index) # pyright: ignore[reportGeneralTypeIssues]
|
|
108
108
|
except Exception:
|
|
109
109
|
# print()
|
|
110
110
|
# print(f"{spec['context']}.{spec['name']} (id: {spec['id']}) failed to resolve poly refs, skipping!")
|
|
@@ -123,7 +123,7 @@ def replace_poly_refs_in_schemas(specs: List[SchemaSpecDto], schema_index):
|
|
|
123
123
|
spec_idxs_to_remove = []
|
|
124
124
|
for idx, spec in enumerate(specs):
|
|
125
125
|
try:
|
|
126
|
-
spec["definition"] = resolve_poly_refs(spec["definition"], schema_index)
|
|
126
|
+
spec["definition"] = cast(Dict[str, Any], resolve_poly_refs(spec["definition"], schema_index))
|
|
127
127
|
except Exception:
|
|
128
128
|
# print()
|
|
129
129
|
print(f"{spec['context']}.{spec['name']} (id: {spec['id']}) failed to resolve poly refs, skipping!")
|
|
@@ -445,16 +445,16 @@ def render_spec(spec: SpecificationDto) -> Tuple[str, str]:
|
|
|
445
445
|
arguments: List[PropertySpecification] = []
|
|
446
446
|
return_type: Any = {}
|
|
447
447
|
if spec.get("function"):
|
|
448
|
-
|
|
448
|
+
function_spec = cast(Dict[str, Any], spec["function"]) # pyright: ignore[reportTypedDictNotRequiredAccess]
|
|
449
449
|
# Handle cases where arguments might be missing or None
|
|
450
|
-
if
|
|
450
|
+
if function_spec.get("arguments"):
|
|
451
451
|
arguments = [
|
|
452
|
-
arg for arg in
|
|
452
|
+
arg for arg in function_spec["arguments"]
|
|
453
453
|
]
|
|
454
|
-
|
|
454
|
+
|
|
455
455
|
# Handle cases where returnType might be missing or None
|
|
456
|
-
if
|
|
457
|
-
return_type =
|
|
456
|
+
if function_spec.get("returnType"):
|
|
457
|
+
return_type = function_spec["returnType"]
|
|
458
458
|
else:
|
|
459
459
|
# Provide a fallback return type when missing
|
|
460
460
|
return_type = {"kind": "any"}
|
|
@@ -5,13 +5,18 @@ import sys
|
|
|
5
5
|
import re
|
|
6
6
|
from typing import Dict, List, Mapping, Optional, Tuple, Any, Union
|
|
7
7
|
from typing import _TypedDictMeta as BaseTypedDict # type: ignore
|
|
8
|
-
from typing_extensions import _TypedDictMeta, cast # type: ignore
|
|
8
|
+
from typing_extensions import TypedDict, _TypedDictMeta, cast # type: ignore
|
|
9
9
|
from stdlib_list import stdlib_list
|
|
10
10
|
from pydantic import TypeAdapter
|
|
11
11
|
from importlib.metadata import packages_distributions
|
|
12
12
|
from polyapi.constants import PYTHON_TO_JSONSCHEMA_TYPE_MAP
|
|
13
13
|
from polyapi.utils import print_red
|
|
14
|
-
from polyapi.deployables import
|
|
14
|
+
from polyapi.deployables import (
|
|
15
|
+
Deployment,
|
|
16
|
+
DeployableRecord,
|
|
17
|
+
DeployableTypes,
|
|
18
|
+
get_deployable_file_revision,
|
|
19
|
+
)
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
# these libraries are already installed in the base docker image
|
|
@@ -116,15 +121,53 @@ def _parse_sphinx_docstring(docstring: str) -> Dict[str, Any]:
|
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
class _GoogleParamState(TypedDict):
|
|
125
|
+
name: str
|
|
126
|
+
type: str
|
|
127
|
+
description: List[str]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class _GoogleReturnsState(TypedDict):
|
|
131
|
+
type: str
|
|
132
|
+
description: List[str]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class GoogleDocstringParam(TypedDict):
|
|
136
|
+
name: str
|
|
137
|
+
type: str
|
|
138
|
+
typeSchema: Optional[Dict[str, Any]]
|
|
139
|
+
description: str
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class GoogleDocstringReturns(TypedDict):
|
|
143
|
+
type: str
|
|
144
|
+
typeSchema: Optional[Dict[str, Any]]
|
|
145
|
+
description: str
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class GoogleDocstring(TypedDict):
|
|
149
|
+
description: str
|
|
150
|
+
params: List[GoogleDocstringParam]
|
|
151
|
+
returns: GoogleDocstringReturns
|
|
152
|
+
raises: Dict[str, str]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class _GoogleDocstringState(TypedDict):
|
|
156
|
+
description: List[str]
|
|
157
|
+
params: Dict[str, _GoogleParamState]
|
|
158
|
+
returns: _GoogleReturnsState
|
|
159
|
+
raises: Dict[str, str]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _parse_google_docstring(docstring: str) -> GoogleDocstring:
|
|
120
163
|
import re
|
|
121
164
|
lines = docstring.split('\n')
|
|
122
165
|
mode = None
|
|
123
|
-
params = {}
|
|
124
|
-
parsed = {
|
|
166
|
+
params: Dict[str, _GoogleParamState] = {}
|
|
167
|
+
parsed: _GoogleDocstringState = {
|
|
125
168
|
'description': [],
|
|
126
|
-
'params':
|
|
127
|
-
'returns': {'description': []},
|
|
169
|
+
'params': params,
|
|
170
|
+
'returns': {'type': '', 'description': []},
|
|
128
171
|
'raises': {}
|
|
129
172
|
}
|
|
130
173
|
current_key = None
|
|
@@ -169,12 +212,25 @@ def _parse_google_docstring(docstring: str) -> Dict[str, Any]:
|
|
|
169
212
|
elif mode is None:
|
|
170
213
|
parsed['description'].append(line.strip())
|
|
171
214
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
215
|
+
parsed_result: GoogleDocstring = {
|
|
216
|
+
'description': ' '.join(parsed['description']).strip(),
|
|
217
|
+
'params': [
|
|
218
|
+
{
|
|
219
|
+
**value,
|
|
220
|
+
'typeSchema': None,
|
|
221
|
+
'description': ' '.join(value['description']).strip(),
|
|
222
|
+
}
|
|
223
|
+
for value in params.values()
|
|
224
|
+
],
|
|
225
|
+
'returns': {
|
|
226
|
+
'type': parsed['returns']['type'],
|
|
227
|
+
'typeSchema': None,
|
|
228
|
+
'description': ' '.join(parsed['returns']['description']).strip(),
|
|
229
|
+
},
|
|
230
|
+
'raises': parsed['raises'],
|
|
231
|
+
}
|
|
176
232
|
|
|
177
|
-
return
|
|
233
|
+
return parsed_result
|
|
178
234
|
|
|
179
235
|
|
|
180
236
|
def _get_schemas(code: str) -> List[Dict]:
|
|
@@ -299,12 +355,12 @@ def _parse_deploy_comment(comment: str) -> Optional[Deployment]:
|
|
|
299
355
|
|
|
300
356
|
# Local development puts canopy on a different port than the poly-server
|
|
301
357
|
if instance.endswith("localhost:3000"):
|
|
302
|
-
instance = instance.replace(":3000
|
|
358
|
+
instance = instance.replace(":3000", ":8000")
|
|
303
359
|
|
|
304
360
|
return {
|
|
305
361
|
"name": name,
|
|
306
362
|
"context": context,
|
|
307
|
-
"type": deploy_type,
|
|
363
|
+
"type": DeployableTypes(deploy_type),
|
|
308
364
|
"id": id,
|
|
309
365
|
"deployed": deployed,
|
|
310
366
|
"fileRevision": file_revision,
|
|
@@ -398,9 +454,9 @@ def parse_function_code(code: str, name: Optional[str] = "", context: Optional[s
|
|
|
398
454
|
):
|
|
399
455
|
# We've found a polyConfig dictionary assignment
|
|
400
456
|
if node.annotation.id == "PolyServerFunction":
|
|
401
|
-
deployable["type"] = "server-function"
|
|
457
|
+
deployable["type"] = "server-function" # pyright: ignore[reportGeneralTypeIssues]
|
|
402
458
|
elif node.annotation.id == "PolyClientFunction":
|
|
403
|
-
deployable["type"] = "client-function"
|
|
459
|
+
deployable["type"] = "client-function" # pyright: ignore[reportGeneralTypeIssues]
|
|
404
460
|
else:
|
|
405
461
|
print_red("ERROR")
|
|
406
462
|
print(f"Unsupported polyConfig type '${node.annotation.id}'")
|
|
@@ -466,7 +522,7 @@ def parse_function_code(code: str, name: Optional[str] = "", context: Optional[s
|
|
|
466
522
|
if deployment:
|
|
467
523
|
start = self._line_offsets[i]
|
|
468
524
|
deployable["deployments"].append(deployment)
|
|
469
|
-
deployable["deploymentCommentRanges"].append(
|
|
525
|
+
deployable["deploymentCommentRanges"].append((start, start + len(line)))
|
|
470
526
|
|
|
471
527
|
def visit_Import(self, node: ast.Import):
|
|
472
528
|
# TODO maybe handle `import foo.bar` case?
|
|
@@ -539,7 +595,7 @@ def parse_function_code(code: str, name: Optional[str] = "", context: Optional[s
|
|
|
539
595
|
|
|
540
596
|
def generic_visit(self, node):
|
|
541
597
|
if hasattr(node, 'lineno') and hasattr(node, 'col_offset'):
|
|
542
|
-
self._current_offset = self._line_offsets[node.lineno - 1] + node.col_offset
|
|
598
|
+
self._current_offset = self._line_offsets[node.lineno - 1] + node.col_offset # pyright: ignore[reportAttributeAccessIssue]
|
|
543
599
|
super().generic_visit(node)
|
|
544
600
|
|
|
545
601
|
tree = ast.parse(code)
|
|
@@ -74,7 +74,12 @@ def scrub_keys(e: Exception) -> Dict[str, Any]:
|
|
|
74
74
|
|
|
75
75
|
def execute_query(table_id, method, query):
|
|
76
76
|
from polyapi import polyCustom
|
|
77
|
-
|
|
77
|
+
|
|
78
|
+
client_id: Optional[str] = None
|
|
79
|
+
try:
|
|
80
|
+
from polyapi.poly.client_id import client_id # pyright: ignore[reportMissingImports]
|
|
81
|
+
except ImportError:
|
|
82
|
+
pass
|
|
78
83
|
|
|
79
84
|
try:
|
|
80
85
|
api_key, base_url = get_api_key_and_url()
|
|
@@ -84,7 +89,9 @@ def execute_query(table_id, method, query):
|
|
|
84
89
|
)
|
|
85
90
|
|
|
86
91
|
auth_key = polyCustom.get("executionApiKey") or api_key
|
|
87
|
-
url = f"{base_url.rstrip('/')}/tables/{table_id}/{method}
|
|
92
|
+
url = f"{base_url.rstrip('/')}/tables/{table_id}/{method}"
|
|
93
|
+
if client_id:
|
|
94
|
+
url += f"?clientId={client_id}"
|
|
88
95
|
headers = {"x-poly-execution-id": polyCustom.get("executionId") or ""}
|
|
89
96
|
if auth_key:
|
|
90
97
|
headers["Authorization"] = f"Bearer {auth_key}"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
import subprocess
|
|
4
|
-
from typing import List, Tuple, Literal
|
|
4
|
+
from typing import Any, Dict, List, Tuple, Literal, TypeGuard
|
|
5
5
|
from polyapi import http_client
|
|
6
6
|
from polyapi.utils import get_auth_headers
|
|
7
7
|
from polyapi.config import get_api_key_and_url
|
|
@@ -10,7 +10,7 @@ from polyapi.deployables import (
|
|
|
10
10
|
prepare_deployable_directory, write_cache_revision,
|
|
11
11
|
save_deployable_records, get_all_deployable_files,
|
|
12
12
|
is_cache_up_to_date, get_git_revision,
|
|
13
|
-
write_updated_deployable, DeployableRecord
|
|
13
|
+
write_updated_deployable, DeployableRecord, PolyDeployConfig
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
class FunctionArgumentDto:
|
|
@@ -19,7 +19,15 @@ class FunctionArgumentDto:
|
|
|
19
19
|
self.type = type
|
|
20
20
|
self.description = description
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
FunctionDeployType = Literal["server-function", "client-function"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _is_function_deploy_type(deploy_type: str) -> TypeGuard[FunctionDeployType]:
|
|
27
|
+
return deploy_type in ("server-function", "client-function")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_function_description(deploy_type: FunctionDeployType, description: str, arguments, code: str) -> Dict[str, Any]:
|
|
23
31
|
if deploy_type == "server-function":
|
|
24
32
|
return get_server_function_description(description, arguments, code)
|
|
25
33
|
elif deploy_type == "client-function":
|
|
@@ -27,14 +35,14 @@ def get_function_description(deploy_type: Literal["server-function", "client-fun
|
|
|
27
35
|
else:
|
|
28
36
|
raise ValueError("Unsupported deployable type")
|
|
29
37
|
|
|
30
|
-
def get_server_function_description(description: str, arguments, code: str) -> str:
|
|
38
|
+
def get_server_function_description(description: str, arguments, code: str) -> Dict[str, Any]:
|
|
31
39
|
api_key, api_url = get_api_key_and_url()
|
|
32
40
|
headers = get_auth_headers(api_key)
|
|
33
41
|
data = {"description": description, "arguments": arguments, "code": code}
|
|
34
42
|
response = http_client.post(f"{api_url}/functions/server/description-generation", headers=headers, json=data)
|
|
35
43
|
return response.json()
|
|
36
44
|
|
|
37
|
-
def get_client_function_description(description: str, arguments, code: str) -> str:
|
|
45
|
+
def get_client_function_description(description: str, arguments, code: str) -> Dict[str, Any]:
|
|
38
46
|
api_key, api_url = get_api_key_and_url()
|
|
39
47
|
headers = get_auth_headers(api_key)
|
|
40
48
|
# Simulated API call to generate client function descriptions
|
|
@@ -50,8 +58,12 @@ def fill_in_missing_function_details(deployable: DeployableRecord, code: str) ->
|
|
|
50
58
|
)
|
|
51
59
|
if is_missing_descriptions:
|
|
52
60
|
try:
|
|
61
|
+
deploy_type = deployable["type"]
|
|
62
|
+
if not _is_function_deploy_type(deploy_type):
|
|
63
|
+
raise ValueError(f'Unsupported deployable type: "{deploy_type}"')
|
|
64
|
+
|
|
53
65
|
ai_generated = get_function_description(
|
|
54
|
-
|
|
66
|
+
deploy_type,
|
|
55
67
|
deployable["types"]["description"],
|
|
56
68
|
[{"name": p["name"], "type": p["type"], "description": p.get("description")} for p in deployable["types"]["params"]],
|
|
57
69
|
code
|
|
@@ -84,7 +96,13 @@ def get_base_url() -> str:
|
|
|
84
96
|
def get_all_deployables(disable_docs: bool, disable_ai: bool, git_revision: str) -> List[DeployableRecord]:
|
|
85
97
|
print("Searching for poly deployables.")
|
|
86
98
|
base_url = get_base_url() or "."
|
|
87
|
-
|
|
99
|
+
deployable_search_config: PolyDeployConfig = {
|
|
100
|
+
"type_names": [],
|
|
101
|
+
"include_dirs": [base_url],
|
|
102
|
+
"include_files_or_extensions": [],
|
|
103
|
+
"exclude_dirs": [],
|
|
104
|
+
}
|
|
105
|
+
possible_deployables = get_all_deployable_files(deployable_search_config)
|
|
88
106
|
print(f'Found {len(possible_deployables)} possible deployable file{"s" if len(possible_deployables) != 1 else ""}.')
|
|
89
107
|
|
|
90
108
|
found = {}
|
|
@@ -22,7 +22,7 @@ def read_file(file_path: str) -> str:
|
|
|
22
22
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
23
23
|
return file.read()
|
|
24
24
|
|
|
25
|
-
def group_by(items: List[
|
|
25
|
+
def group_by(items: List[DeployableRecord], key: str) -> Dict[str, List[DeployableRecord]]:
|
|
26
26
|
grouped = {} # type: ignore
|
|
27
27
|
for item in items:
|
|
28
28
|
grouped.setdefault(item[key], []).append(item)
|
|
@@ -50,7 +50,7 @@ def remove_deployable(deployable: SyncDeployment) -> bool:
|
|
|
50
50
|
def sync_function_and_get_id(deployable: SyncDeployment, code: str) -> str:
|
|
51
51
|
api_key, _ = get_api_key_and_url()
|
|
52
52
|
if not api_key:
|
|
53
|
-
raise
|
|
53
|
+
raise Exception("Missing api key!")
|
|
54
54
|
headers = get_auth_headers(api_key)
|
|
55
55
|
url = f'{deployable["instance"]}/functions/{deployable["type"].replace("-function", "")}'
|
|
56
56
|
payload = {
|
|
@@ -41,7 +41,9 @@ def add_import_to_init(full_path: str, next: str, code_imports: Optional[str] =
|
|
|
41
41
|
f.write(import_stmt)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def get_auth_headers(api_key: str):
|
|
44
|
+
def get_auth_headers(api_key: Optional[str]) -> dict[str, str]:
|
|
45
|
+
if api_key is None:
|
|
46
|
+
raise ValueError("An API key is required to build authorization headers")
|
|
45
47
|
return {"Authorization": f"Bearer {api_key}"}
|
|
46
48
|
|
|
47
49
|
|
|
@@ -118,14 +118,16 @@ def render_variable(variable: VariableSpecDto):
|
|
|
118
118
|
def _get_variable_type(type_spec: PropertyType) -> str:
|
|
119
119
|
# simplified version of _get_type from api.py
|
|
120
120
|
if type_spec["kind"] == "plain":
|
|
121
|
-
value = type_spec
|
|
122
|
-
if value.endswith("[]"):
|
|
121
|
+
value = type_spec.get("value")
|
|
122
|
+
if value and value.endswith("[]"):
|
|
123
123
|
primitive = map_primitive_types(value[:-2])
|
|
124
124
|
return f"List[{primitive}]"
|
|
125
|
-
|
|
125
|
+
elif value:
|
|
126
126
|
return map_primitive_types(value)
|
|
127
|
+
else:
|
|
128
|
+
return "Any"
|
|
127
129
|
elif type_spec["kind"] == "primitive":
|
|
128
|
-
return map_primitive_types(type_spec
|
|
130
|
+
return map_primitive_types(type_spec.get("type", "not gonna find this so it will fallback to any"))
|
|
129
131
|
elif type_spec["kind"] == "array":
|
|
130
132
|
return "List"
|
|
131
133
|
elif type_spec["kind"] == "void":
|
|
@@ -107,7 +107,8 @@ async def unregister_all():
|
|
|
107
107
|
# maybe need to reconnect because maybe socketio client disconnected after Ctrl+C?
|
|
108
108
|
# feels like Linux disconnects but Windows stays connected
|
|
109
109
|
try:
|
|
110
|
-
|
|
110
|
+
if client:
|
|
111
|
+
await client.connect(base_url, transports=["websocket"], namespaces=["/events"])
|
|
111
112
|
except ConnectionError:
|
|
112
113
|
pass
|
|
113
114
|
await asyncio.gather(*[unregister(handler) for handler in active_handlers])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.3.21.
|
|
3
|
+
Version: 0.3.21.dev1
|
|
4
4
|
Summary: The Python SDK for the integration platform PolyAPI
|
|
5
5
|
Author-email: PolyAPI <support@polyapi.io>
|
|
6
6
|
License: MIT License
|
|
@@ -28,15 +28,23 @@ Project-URL: Homepage, https://github.com/polyapi/polyapi-python
|
|
|
28
28
|
Requires-Python: >=3.10
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
|
-
Requires-Dist: requests==2.
|
|
31
|
+
Requires-Dist: requests==2.33.0
|
|
32
32
|
Requires-Dist: typing_extensions==4.15.0
|
|
33
33
|
Requires-Dist: jsonschema-gentypes==2.13.0
|
|
34
34
|
Requires-Dist: pydantic==2.13.4
|
|
35
35
|
Requires-Dist: stdlib_list==0.11.1
|
|
36
36
|
Requires-Dist: colorama==0.4.4
|
|
37
|
-
Requires-Dist: python-socketio[asyncio_client]==5.
|
|
37
|
+
Requires-Dist: python-socketio[asyncio_client]==5.16.2
|
|
38
38
|
Requires-Dist: truststore==0.9.1
|
|
39
39
|
Requires-Dist: httpx==0.28.1
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: mock==5.2.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest; extra == "dev"
|
|
43
|
+
Requires-Dist: flask==3.1.3; extra == "dev"
|
|
44
|
+
Requires-Dist: mypy; extra == "dev"
|
|
45
|
+
Requires-Dist: types-requests; extra == "dev"
|
|
46
|
+
Requires-Dist: types-colorama; extra == "dev"
|
|
47
|
+
Requires-Dist: pip-audit; extra == "dev"
|
|
40
48
|
Dynamic: license-file
|
|
41
49
|
|
|
42
50
|
# PolyAPI Python Library
|
{polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi_python.egg-info/SOURCES.txt
RENAMED
|
@@ -23,7 +23,6 @@ polyapi/poly_schemas.py
|
|
|
23
23
|
polyapi/poly_tables.py
|
|
24
24
|
polyapi/prepare.py
|
|
25
25
|
polyapi/py.typed
|
|
26
|
-
polyapi/rendered_spec.py
|
|
27
26
|
polyapi/schema.py
|
|
28
27
|
polyapi/server.py
|
|
29
28
|
polyapi/sync.py
|
|
@@ -47,7 +46,6 @@ tests/test_generated_poly_boundary.py
|
|
|
47
46
|
tests/test_parser.py
|
|
48
47
|
tests/test_poly_custom.py
|
|
49
48
|
tests/test_poly_schemas.py
|
|
50
|
-
tests/test_rendered_spec.py
|
|
51
49
|
tests/test_schema.py
|
|
52
50
|
tests/test_server.py
|
|
53
51
|
tests/test_tabi.py
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
requests==2.33.0
|
|
2
|
+
typing_extensions==4.15.0
|
|
3
|
+
jsonschema-gentypes==2.13.0
|
|
4
|
+
pydantic==2.13.4
|
|
5
|
+
stdlib_list==0.11.1
|
|
6
|
+
colorama==0.4.4
|
|
7
|
+
python-socketio[asyncio_client]==5.16.2
|
|
8
|
+
truststore==0.9.1
|
|
9
|
+
httpx==0.28.1
|
|
10
|
+
|
|
11
|
+
[dev]
|
|
12
|
+
mock==5.2.0
|
|
13
|
+
pytest
|
|
14
|
+
flask==3.1.3
|
|
15
|
+
mypy
|
|
16
|
+
types-requests
|
|
17
|
+
types-colorama
|
|
18
|
+
pip-audit
|
|
@@ -4,17 +4,17 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "polyapi-python"
|
|
7
|
-
version = "0.3.21.
|
|
7
|
+
version = "0.3.21.dev1"
|
|
8
8
|
description = "The Python SDK for the integration platform PolyAPI"
|
|
9
9
|
authors = [{ name = "PolyAPI", email = "support@polyapi.io" }]
|
|
10
10
|
dependencies = [
|
|
11
|
-
"requests==2.
|
|
11
|
+
"requests==2.33.0",
|
|
12
12
|
"typing_extensions==4.15.0",
|
|
13
13
|
"jsonschema-gentypes==2.13.0",
|
|
14
14
|
"pydantic==2.13.4",
|
|
15
15
|
"stdlib_list==0.11.1",
|
|
16
16
|
"colorama==0.4.4",
|
|
17
|
-
"python-socketio[asyncio_client]==5.
|
|
17
|
+
"python-socketio[asyncio_client]==5.16.2",
|
|
18
18
|
"truststore==0.9.1",
|
|
19
19
|
"httpx==0.28.1"
|
|
20
20
|
]
|
|
@@ -22,6 +22,17 @@ readme = "README.md"
|
|
|
22
22
|
license = { file = "LICENSE" }
|
|
23
23
|
requires-python = ">=3.10"
|
|
24
24
|
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"mock==5.2.0",
|
|
28
|
+
"pytest",
|
|
29
|
+
"flask==3.1.3",
|
|
30
|
+
"mypy",
|
|
31
|
+
"types-requests",
|
|
32
|
+
"types-colorama",
|
|
33
|
+
"pip-audit",
|
|
34
|
+
]
|
|
35
|
+
|
|
25
36
|
[project.urls]
|
|
26
37
|
Homepage = "https://github.com/polyapi/polyapi-python"
|
|
27
38
|
|
|
@@ -23,6 +23,7 @@ import threading
|
|
|
23
23
|
import time
|
|
24
24
|
import unittest
|
|
25
25
|
import warnings
|
|
26
|
+
from typing import cast
|
|
26
27
|
from unittest.mock import patch, MagicMock, AsyncMock
|
|
27
28
|
|
|
28
29
|
import httpx
|
|
@@ -315,7 +316,7 @@ def _run_real_resource_soak():
|
|
|
315
316
|
|
|
316
317
|
# 1. http_client sync / async client pairing
|
|
317
318
|
|
|
318
|
-
class TestHttpClientPairing:
|
|
319
|
+
class TestHttpClientPairing(unittest.TestCase):
|
|
319
320
|
"""Verify that the sync helpers call httpx.Client and the async helpers
|
|
320
321
|
call httpx.AsyncClient."""
|
|
321
322
|
|
|
@@ -498,7 +499,7 @@ class TestHttpClientPairing:
|
|
|
498
499
|
raise AttributeError("close is read-only")
|
|
499
500
|
object.__setattr__(self, name, value)
|
|
500
501
|
|
|
501
|
-
loop = ReadOnlyCloseLoop()
|
|
502
|
+
loop = cast(asyncio.AbstractEventLoop, ReadOnlyCloseLoop())
|
|
502
503
|
with self.assertLogs("poly", level=logging.WARNING) as captured:
|
|
503
504
|
http_client._register_loop_close_hook(loop)
|
|
504
505
|
|
|
@@ -587,7 +588,7 @@ class TestHttpClientPairing:
|
|
|
587
588
|
async def _run():
|
|
588
589
|
client = http_client._get_async_client()
|
|
589
590
|
proxy_pools = [
|
|
590
|
-
type(transport
|
|
591
|
+
type(getattr(transport, "_pool", None)).__name__
|
|
591
592
|
for transport in client._mounts.values()
|
|
592
593
|
if transport is not None
|
|
593
594
|
]
|
|
@@ -13,21 +13,13 @@ def _parse_requirement_version(requirement: str) -> tuple[int, ...]:
|
|
|
13
13
|
return tuple(int(part) for part in match.groups())
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def
|
|
17
|
-
requirements_lines = (REPO_ROOT / "requirements.txt").read_text(encoding="utf-8").splitlines()
|
|
18
|
-
requirements_pin = next(line for line in requirements_lines if line.startswith("truststore=="))
|
|
19
|
-
|
|
16
|
+
def _truststore_pin() -> str:
|
|
20
17
|
pyproject_text = (REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")
|
|
21
18
|
pyproject_match = re.search(r'"(truststore==\d+\.\d+\.\d+)"', pyproject_text)
|
|
22
19
|
assert pyproject_match is not None
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
assert requirements_pin == pyproject_pin
|
|
20
|
+
return pyproject_match.group(1)
|
|
26
21
|
|
|
27
22
|
|
|
28
23
|
def test_truststore_pin_supports_python_313_ssl_chain_api():
|
|
29
|
-
requirements_lines = (REPO_ROOT / "requirements.txt").read_text(encoding="utf-8").splitlines()
|
|
30
|
-
requirements_pin = next(line for line in requirements_lines if line.startswith("truststore=="))
|
|
31
|
-
|
|
32
24
|
if sys.version_info >= (3, 13):
|
|
33
|
-
assert _parse_requirement_version(
|
|
25
|
+
assert _parse_requirement_version(_truststore_pin()) >= TRUSTSTORE_MINIMUM_PYTHON_313_VERSION
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import unittest
|
|
2
|
+
from typing import cast
|
|
2
3
|
|
|
3
4
|
from polyapi.parser import parse_function_code
|
|
4
|
-
from polyapi.deployables import
|
|
5
|
+
from polyapi.deployables import (
|
|
6
|
+
DeployableRecord,
|
|
7
|
+
update_deployable_function_comments,
|
|
8
|
+
update_deployment_comments,
|
|
9
|
+
)
|
|
5
10
|
|
|
6
11
|
|
|
7
12
|
INITIAL_SERVER_FN_DEPLOYMENTS = """
|
|
@@ -110,7 +115,10 @@ class T(unittest.TestCase):
|
|
|
110
115
|
],
|
|
111
116
|
"deploymentCommentRanges": [[0, 177]]
|
|
112
117
|
}
|
|
113
|
-
updated_file_contents = update_deployment_comments(
|
|
118
|
+
updated_file_contents = update_deployment_comments(
|
|
119
|
+
INITIAL_SERVER_FN_DEPLOYMENTS,
|
|
120
|
+
cast(DeployableRecord, test_deployable),
|
|
121
|
+
)
|
|
114
122
|
self.assertEqual(updated_file_contents, EXPECTED_SERVER_FN_DEPLOYMENTS)
|
|
115
123
|
|
|
116
124
|
def test_parse_and_write_deployable_docstring(self):
|
|
@@ -6,9 +6,14 @@ import importlib.util
|
|
|
6
6
|
import subprocess
|
|
7
7
|
import sys
|
|
8
8
|
import tempfile
|
|
9
|
-
from typing import cast
|
|
9
|
+
from typing import List, cast
|
|
10
10
|
from unittest.mock import patch, MagicMock
|
|
11
|
-
from polyapi.typedefs import
|
|
11
|
+
from polyapi.typedefs import (
|
|
12
|
+
PropertyType,
|
|
13
|
+
SchemaSpecDto,
|
|
14
|
+
SpecificationDto,
|
|
15
|
+
VariableSpecDto,
|
|
16
|
+
)
|
|
12
17
|
from polyapi.utils import get_type_and_def, rewrite_reserved, to_type_module_alias
|
|
13
18
|
from polyapi.generate import render_spec, create_empty_schemas_module, generate_functions, create_function, add_function_file, get_specs
|
|
14
19
|
from polyapi.poly_schemas import generate_schemas, create_schema
|
|
@@ -268,7 +273,7 @@ class T(unittest.TestCase):
|
|
|
268
273
|
self.assertEqual(params["contexts"], ",".join(contexts))
|
|
269
274
|
|
|
270
275
|
def test_get_type_and_def(self):
|
|
271
|
-
arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION)
|
|
276
|
+
arg_type, arg_def = get_type_and_def(cast(PropertyType, OPENAPI_FUNCTION))
|
|
272
277
|
self.assertEqual(arg_type, "Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]")
|
|
273
278
|
|
|
274
279
|
def test_rewrite_reserved(self):
|
|
@@ -277,7 +282,7 @@ class T(unittest.TestCase):
|
|
|
277
282
|
|
|
278
283
|
def test_render_spec_no_function_data(self):
|
|
279
284
|
"""Test that render_spec handles specs with no function data gracefully"""
|
|
280
|
-
func_str, func_type_defs = render_spec(NO_TYPES_SPEC)
|
|
285
|
+
func_str, func_type_defs = render_spec(cast(SpecificationDto, NO_TYPES_SPEC))
|
|
281
286
|
|
|
282
287
|
# Should generate a function even without function data
|
|
283
288
|
self.assertIsNotNone(func_str)
|
|
@@ -287,7 +292,7 @@ class T(unittest.TestCase):
|
|
|
287
292
|
|
|
288
293
|
def test_render_spec_minimal_function_data(self):
|
|
289
294
|
"""Test that render_spec handles specs with minimal function data"""
|
|
290
|
-
func_str, func_type_defs = render_spec(MINIMAL_FUNCTION_SPEC)
|
|
295
|
+
func_str, func_type_defs = render_spec(cast(SpecificationDto, MINIMAL_FUNCTION_SPEC))
|
|
291
296
|
|
|
292
297
|
# Should generate a function with fallback types
|
|
293
298
|
self.assertIsNotNone(func_str)
|
|
@@ -482,7 +487,7 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
482
487
|
|
|
483
488
|
# Capture logging output
|
|
484
489
|
with patch('polyapi.generate.logging.warning') as mock_warning:
|
|
485
|
-
generate_functions(specs)
|
|
490
|
+
generate_functions(cast(List[SpecificationDto], specs))
|
|
486
491
|
|
|
487
492
|
# Verify that create_function was called twice (once for each spec)
|
|
488
493
|
self.assertEqual(mock_create.call_count, 2)
|
|
@@ -522,7 +527,7 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
522
527
|
|
|
523
528
|
# Capture logging output
|
|
524
529
|
with patch('polyapi.poly_schemas.logging.warning') as mock_warning:
|
|
525
|
-
generate_schemas(specs)
|
|
530
|
+
generate_schemas(cast(List[SchemaSpecDto], specs))
|
|
526
531
|
|
|
527
532
|
# Verify that create_schema was called twice (once for each spec)
|
|
528
533
|
self.assertEqual(mock_create.call_count, 2)
|
|
@@ -568,7 +573,7 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
568
573
|
|
|
569
574
|
# Capture logging output
|
|
570
575
|
with patch('polyapi.variables.logging.warning') as mock_warning:
|
|
571
|
-
generate_variables(specs)
|
|
576
|
+
generate_variables(cast(List[VariableSpecDto], specs))
|
|
572
577
|
|
|
573
578
|
# Verify that create_variable was called twice (once for each spec)
|
|
574
579
|
self.assertEqual(mock_create.call_count, 2)
|
|
@@ -625,7 +630,11 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
625
630
|
|
|
626
631
|
# Verify that the function generation fails
|
|
627
632
|
with self.assertRaises(Exception):
|
|
628
|
-
add_function_file(
|
|
633
|
+
add_function_file(
|
|
634
|
+
temp_dir,
|
|
635
|
+
"failingFunction",
|
|
636
|
+
cast(SpecificationDto, failing_spec),
|
|
637
|
+
)
|
|
629
638
|
|
|
630
639
|
# Verify no partial files were left behind
|
|
631
640
|
files_in_dir = os.listdir(temp_dir)
|
|
@@ -666,7 +675,10 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
666
675
|
|
|
667
676
|
# Verify that the variable generation fails
|
|
668
677
|
with self.assertRaises(Exception):
|
|
669
|
-
add_variable_to_init(
|
|
678
|
+
add_variable_to_init(
|
|
679
|
+
temp_dir,
|
|
680
|
+
cast(VariableSpecDto, failing_spec),
|
|
681
|
+
)
|
|
670
682
|
|
|
671
683
|
# Verify no partial files were left behind and __init__.py wasn't corrupted
|
|
672
684
|
init_path = os.path.join(temp_dir, "__init__.py")
|
|
@@ -695,7 +707,7 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
695
707
|
"definition": {"type": "object", "properties": {"test": {"type": "string"}}}
|
|
696
708
|
}
|
|
697
709
|
]
|
|
698
|
-
generate_schemas(schemas)
|
|
710
|
+
generate_schemas(cast(List[SchemaSpecDto], schemas))
|
|
699
711
|
|
|
700
712
|
# Should have logged a warning about the failed schema
|
|
701
713
|
mock_warning.assert_called()
|
|
@@ -742,7 +754,7 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
742
754
|
|
|
743
755
|
# This should fail gracefully
|
|
744
756
|
try:
|
|
745
|
-
generate.create_function(spec)
|
|
757
|
+
generate.create_function(cast(SpecificationDto, spec))
|
|
746
758
|
except:
|
|
747
759
|
pass # Expected to fail
|
|
748
760
|
|
|
@@ -808,7 +820,7 @@ def test_nested_function() -> schemas.api.v1.user.profile:
|
|
|
808
820
|
|
|
809
821
|
# This should fail but handle cleanup gracefully
|
|
810
822
|
try:
|
|
811
|
-
generate.create_function(spec)
|
|
823
|
+
generate.create_function(cast(SpecificationDto, spec))
|
|
812
824
|
except:
|
|
813
825
|
pass # Expected to fail
|
|
814
826
|
|
|
@@ -157,7 +157,8 @@ class T(unittest.TestCase):
|
|
|
157
157
|
self.assertEqual(len(types["params"]), 1)
|
|
158
158
|
self.assertEqual(types["params"][0], {"name": "n", "type": "int", "typeSchema": None, "description": ""})
|
|
159
159
|
self.assertEqual(types["returns"]["type"], "Barbar")
|
|
160
|
-
|
|
160
|
+
typeSchema = types["returns"].get("typeSchema") or {}
|
|
161
|
+
self.assertEqual(typeSchema.get('title', "Barbar"), "Barbar")
|
|
161
162
|
|
|
162
163
|
def test_complex_arg_type(self):
|
|
163
164
|
deployable = parse_function_code(COMPLEX_ARG_TYPE, "foobar")
|
|
@@ -173,7 +174,7 @@ class T(unittest.TestCase):
|
|
|
173
174
|
self.assertEqual(len(types["params"]), 1)
|
|
174
175
|
self.assertEqual(types["params"][0], {"name": "n", "type": "int", "typeSchema": None, "description": ""})
|
|
175
176
|
self.assertEqual(types["returns"]["type"], "List[Barbar]")
|
|
176
|
-
self.assertEqual(types["returns"]["typeSchema"]["items"]['title'], "Barbar")
|
|
177
|
+
self.assertEqual(types["returns"]["typeSchema"]["items"]['title'], "Barbar") # pyright: ignore[reportOptionalSubscript]
|
|
177
178
|
|
|
178
179
|
def test_parse_import_basic(self):
|
|
179
180
|
code = "import flask\n\n\ndef foobar(n: int) -> int:\n return 9\n"
|
|
@@ -215,6 +216,7 @@ class T(unittest.TestCase):
|
|
|
215
216
|
})
|
|
216
217
|
self.assertEqual(deployable["types"]["returns"], {
|
|
217
218
|
"type": "Any",
|
|
219
|
+
"typeSchema": None,
|
|
218
220
|
"description": "import number please keep handy"
|
|
219
221
|
})
|
|
220
222
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import unittest
|
|
2
|
+
from typing import cast
|
|
3
|
+
from polyapi.typedefs import PropertyType
|
|
2
4
|
from polyapi.utils import add_type_import_path, get_type_and_def, rewrite_reserved
|
|
3
5
|
|
|
4
6
|
OPENAPI_FUNCTION = {
|
|
@@ -75,7 +77,7 @@ OPENAPI_FUNCTION = {
|
|
|
75
77
|
|
|
76
78
|
class T(unittest.TestCase):
|
|
77
79
|
def test_get_type_and_def(self):
|
|
78
|
-
arg_type, arg_def = get_type_and_def(OPENAPI_FUNCTION)
|
|
80
|
+
arg_type, arg_def = get_type_and_def(cast(PropertyType, OPENAPI_FUNCTION))
|
|
79
81
|
self.assertEqual(
|
|
80
82
|
arg_type,
|
|
81
83
|
"Callable[[List[WebhookEventTypeElement], Dict, Dict, Dict], None]",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import unittest
|
|
2
|
+
from typing import cast
|
|
3
|
+
from polyapi.typedefs import VariableSpecDto
|
|
2
4
|
from polyapi.variables import render_variable
|
|
3
5
|
|
|
4
6
|
EXAMPLE = {
|
|
@@ -24,5 +26,5 @@ EXAMPLE = {
|
|
|
24
26
|
|
|
25
27
|
class T(unittest.TestCase):
|
|
26
28
|
def test_render_variable(self):
|
|
27
|
-
variable_str = render_variable(EXAMPLE)
|
|
29
|
+
variable_str = render_variable(cast(VariableSpecDto, EXAMPLE))
|
|
28
30
|
self.assertIn("class test", variable_str)
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
from polyapi import http_client
|
|
5
|
-
from polyapi.config import get_api_key_and_url
|
|
6
|
-
from polyapi.generate import read_cached_specs, render_spec
|
|
7
|
-
from polyapi.typedefs import SpecificationDto
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def update_rendered_spec(spec: SpecificationDto):
|
|
11
|
-
print("Updating rendered spec...")
|
|
12
|
-
func_str, type_defs = render_spec(spec)
|
|
13
|
-
data = {
|
|
14
|
-
"language": "python",
|
|
15
|
-
"signature": func_str,
|
|
16
|
-
"typedefs": type_defs,
|
|
17
|
-
}
|
|
18
|
-
if spec["type"] == "apiFunction":
|
|
19
|
-
data["apiFunctionId"] = spec["id"]
|
|
20
|
-
elif spec["type"] == "serverFunction":
|
|
21
|
-
data["customFunctionId"] = spec["id"]
|
|
22
|
-
elif spec["type"] == "clientFunction":
|
|
23
|
-
data["customFunctionId"] = spec["id"]
|
|
24
|
-
elif spec["type"] == "webhookHandle":
|
|
25
|
-
data["webhookHandleId"] = spec["id"]
|
|
26
|
-
else:
|
|
27
|
-
raise NotImplementedError("todo")
|
|
28
|
-
|
|
29
|
-
# use super key on develop-k8s here!
|
|
30
|
-
api_key, base_url = get_api_key_and_url()
|
|
31
|
-
|
|
32
|
-
url = f"{base_url}/functions/rendered-specs"
|
|
33
|
-
headers = {"Authorization": f"Bearer {api_key}"}
|
|
34
|
-
resp = http_client.post(url, json=data, headers=headers)
|
|
35
|
-
assert resp.status_code == 201, (resp.text, resp.status_code)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _get_spec(spec_id: str, no_types: bool = False) -> Optional[SpecificationDto]:
|
|
39
|
-
api_key, base_url = get_api_key_and_url()
|
|
40
|
-
url = f"{base_url}/specs"
|
|
41
|
-
headers = {"Authorization": f"Bearer {api_key}"}
|
|
42
|
-
params = {"noTypes": str(no_types).lower()}
|
|
43
|
-
resp = http_client.get(url, headers=headers, params=params)
|
|
44
|
-
if resp.status_code == 200:
|
|
45
|
-
specs = resp.json()
|
|
46
|
-
for spec in specs:
|
|
47
|
-
if spec['id'] == spec_id:
|
|
48
|
-
return spec
|
|
49
|
-
return None
|
|
50
|
-
else:
|
|
51
|
-
raise NotImplementedError(resp.content)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def get_and_update_rendered_spec(spec_id: str) -> bool:
|
|
55
|
-
spec = _get_spec(spec_id)
|
|
56
|
-
if spec:
|
|
57
|
-
update_rendered_spec(spec)
|
|
58
|
-
return True
|
|
59
|
-
return False
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def save_rendered_specs() -> None:
|
|
63
|
-
specs = read_cached_specs()
|
|
64
|
-
# right now we just support rendered apiFunctions
|
|
65
|
-
api_specs = [spec for spec in specs if spec["type"] == "apiFunction"]
|
|
66
|
-
for spec in api_specs:
|
|
67
|
-
assert spec["function"]
|
|
68
|
-
print("adding", spec["context"], spec["name"])
|
|
69
|
-
update_rendered_spec(spec)
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from mock import patch, Mock
|
|
3
|
-
|
|
4
|
-
from polyapi.rendered_spec import get_and_update_rendered_spec
|
|
5
|
-
|
|
6
|
-
GET_PRODUCTS_COUNT = {
|
|
7
|
-
"id": "8f7d24b0-4a29-40c0-9091",
|
|
8
|
-
"type": "serverFunction",
|
|
9
|
-
"context": "test",
|
|
10
|
-
"name": "getProductsCount111",
|
|
11
|
-
"description": "An API call to retrieve the count of products in the product list.",
|
|
12
|
-
"requirements": ["snabbdom"],
|
|
13
|
-
"function": {
|
|
14
|
-
"arguments": [
|
|
15
|
-
{
|
|
16
|
-
"name": "products",
|
|
17
|
-
"required": False,
|
|
18
|
-
"type": {
|
|
19
|
-
"kind": "array",
|
|
20
|
-
"items": {"kind": "primitive", "type": "string"},
|
|
21
|
-
},
|
|
22
|
-
}
|
|
23
|
-
],
|
|
24
|
-
"returnType": {"kind": "plain", "value": "number"},
|
|
25
|
-
"synchronous": True,
|
|
26
|
-
},
|
|
27
|
-
"code": "",
|
|
28
|
-
"language": "javascript",
|
|
29
|
-
"visibilityMetadata": {"visibility": "ENVIRONMENT"},
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class T(unittest.TestCase):
|
|
34
|
-
@patch("polyapi.rendered_spec._get_spec")
|
|
35
|
-
def test_get_and_update_rendered_spec_fail(self, _get_spec):
|
|
36
|
-
""" pass in a bad id to update and make sure it returns False
|
|
37
|
-
"""
|
|
38
|
-
_get_spec.return_value = None
|
|
39
|
-
updated = get_and_update_rendered_spec("123")
|
|
40
|
-
self.assertEqual(_get_spec.call_count, 1)
|
|
41
|
-
self.assertFalse(updated)
|
|
42
|
-
|
|
43
|
-
@patch("polyapi.http_client.post")
|
|
44
|
-
@patch("polyapi.rendered_spec._get_spec")
|
|
45
|
-
def test_get_and_update_rendered_spec_success(self, _get_spec, post):
|
|
46
|
-
""" pass in a bad id to update and make sure it returns False
|
|
47
|
-
"""
|
|
48
|
-
_get_spec.return_value = GET_PRODUCTS_COUNT
|
|
49
|
-
post.return_value = Mock(status_code=201, text="Created")
|
|
50
|
-
updated = get_and_update_rendered_spec("123")
|
|
51
|
-
self.assertEqual(_get_spec.call_count, 1)
|
|
52
|
-
self.assertTrue(updated)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/polyapi_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{polyapi_python-0.3.21.dev0 → polyapi_python-0.3.21.dev1}/tests/test_generated_poly_boundary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|