polyapi-python 0.3.9.dev8__tar.gz → 0.3.9.dev10__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.9.dev8 → polyapi_python-0.3.9.dev10}/PKG-INFO +1 -1
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/auth.py +3 -3
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/deployables.py +8 -8
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/generate.py +31 -16
- polyapi_python-0.3.9.dev10/polyapi/poly_tables.py +443 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/sync.py +9 -4
- polyapi_python-0.3.9.dev10/polyapi/typedefs.py +200 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/utils.py +6 -5
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/variables.py +1 -4
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/webhook.py +2 -3
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi_python.egg-info/PKG-INFO +1 -1
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi_python.egg-info/SOURCES.txt +2 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/pyproject.toml +1 -1
- polyapi_python-0.3.9.dev10/tests/test_tabi.py +619 -0
- polyapi_python-0.3.9.dev8/polyapi/typedefs.py +0 -93
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/LICENSE +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/README.md +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/__init__.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/api.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/cli.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/client.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/config.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/execute.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/parser.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/poly_schemas.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/prepare.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/rendered_spec.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/schema.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi/server.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi_python.egg-info/requires.txt +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/setup.cfg +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_api.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_deployables.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_generate.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_parser.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_rendered_spec.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_schema.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_server.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_utils.py +0 -0
- {polyapi_python-0.3.9.dev8 → polyapi_python-0.3.9.dev10}/tests/test_variables.py +0 -0
|
@@ -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":
|
|
@@ -65,20 +65,20 @@ class SyncDeployment(TypedDict, total=False):
|
|
|
65
65
|
context: str
|
|
66
66
|
name: str
|
|
67
67
|
description: str
|
|
68
|
-
type:
|
|
68
|
+
type: DeployableTypes
|
|
69
69
|
fileRevision: str
|
|
70
70
|
file: str
|
|
71
71
|
types: DeployableFunctionTypes
|
|
72
|
-
typeSchemas: Dict[str,
|
|
72
|
+
typeSchemas: Dict[str, Any]
|
|
73
73
|
dependencies: List[str]
|
|
74
|
-
config: Dict[str,
|
|
74
|
+
config: Dict[str, Any]
|
|
75
75
|
instance: str
|
|
76
|
-
id: Optional[str]
|
|
77
|
-
deployed: Optional[str]
|
|
76
|
+
id: Optional[str]
|
|
77
|
+
deployed: Optional[str]
|
|
78
78
|
|
|
79
79
|
DeployableTypeEntries: List[Tuple[DeployableTypeNames, DeployableTypes]] = [
|
|
80
|
-
("PolyServerFunction", "server-function"),
|
|
81
|
-
("PolyClientFunction", "client-function"),
|
|
80
|
+
("PolyServerFunction", "server-function"), # type: ignore
|
|
81
|
+
("PolyClientFunction", "client-function"), # type: ignore
|
|
82
82
|
]
|
|
83
83
|
|
|
84
84
|
DeployableTypeToName: Dict[DeployableTypeNames, DeployableTypes] = {name: type for name, type in DeployableTypeEntries}
|
|
@@ -175,7 +175,7 @@ def get_git_revision(branch_or_tag: str = "HEAD") -> str:
|
|
|
175
175
|
return check_output(["git", "rev-parse", "--short", branch_or_tag], text=True).strip()
|
|
176
176
|
except CalledProcessError:
|
|
177
177
|
# Return a random 7-character hash as a fallback
|
|
178
|
-
return "".join(format(ord(c), 'x') for c in os.urandom(4))[:7]
|
|
178
|
+
return "".join(format(ord(str(c)), 'x') for c in os.urandom(4))[:7]
|
|
179
179
|
|
|
180
180
|
def get_cache_deployments_revision() -> str:
|
|
181
181
|
"""Retrieve the cache deployments revision from a file."""
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
|
@@ -13,11 +14,12 @@ from .client import render_client_function
|
|
|
13
14
|
from .poly_schemas import generate_schemas
|
|
14
15
|
from .webhook import render_webhook_handle
|
|
15
16
|
|
|
16
|
-
from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto
|
|
17
|
+
from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto, TableSpecDto
|
|
17
18
|
from .api import render_api_function
|
|
18
19
|
from .server import render_server_function
|
|
19
20
|
from .utils import add_import_to_init, get_auth_headers, init_the_init, print_green, to_func_namespace
|
|
20
21
|
from .variables import generate_variables
|
|
22
|
+
from .poly_tables import generate_tables
|
|
21
23
|
from .config import get_api_key_and_url, get_direct_execute_config, get_cached_generate_args
|
|
22
24
|
|
|
23
25
|
SUPPORTED_FUNCTION_TYPES = {
|
|
@@ -28,7 +30,7 @@ SUPPORTED_FUNCTION_TYPES = {
|
|
|
28
30
|
"webhookHandle",
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable", "schema", "snippet"}
|
|
33
|
+
SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable", "schema", "snippet", "table"}
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
X_POLY_REF_WARNING = '''"""
|
|
@@ -195,16 +197,18 @@ def read_cached_specs() -> List[SpecificationDto]:
|
|
|
195
197
|
return json.loads(f.read())
|
|
196
198
|
|
|
197
199
|
|
|
198
|
-
def get_variables() -> List[VariableSpecDto]:
|
|
199
|
-
specs = read_cached_specs()
|
|
200
|
+
def get_variables(specs: List[SpecificationDto]) -> List[VariableSpecDto]:
|
|
200
201
|
return [cast(VariableSpecDto, spec) for spec in specs if spec["type"] == "serverVariable"]
|
|
201
202
|
|
|
202
203
|
|
|
203
|
-
def get_schemas() -> List[SchemaSpecDto]:
|
|
204
|
-
specs = read_cached_specs()
|
|
204
|
+
def get_schemas(specs: List[SpecificationDto]) -> List[SchemaSpecDto]:
|
|
205
205
|
return [cast(SchemaSpecDto, spec) for spec in specs if spec["type"] == "schema"]
|
|
206
206
|
|
|
207
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
|
+
|
|
208
212
|
def remove_old_library():
|
|
209
213
|
currdir = os.path.dirname(os.path.abspath(__file__))
|
|
210
214
|
path = os.path.join(currdir, "poly")
|
|
@@ -219,6 +223,10 @@ def remove_old_library():
|
|
|
219
223
|
if os.path.exists(path):
|
|
220
224
|
shutil.rmtree(path)
|
|
221
225
|
|
|
226
|
+
path = os.path.join(currdir, "tabi")
|
|
227
|
+
if os.path.exists(path):
|
|
228
|
+
shutil.rmtree(path)
|
|
229
|
+
|
|
222
230
|
|
|
223
231
|
def create_empty_schemas_module():
|
|
224
232
|
"""Create an empty schemas module for no-types mode so user code can still import from polyapi.schemas"""
|
|
@@ -277,6 +285,14 @@ sys.modules[__name__] = _SchemaModule()
|
|
|
277
285
|
''')
|
|
278
286
|
|
|
279
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
|
+
|
|
280
296
|
def generate_from_cache() -> None:
|
|
281
297
|
"""
|
|
282
298
|
Generate using cached values after non-explicit call.
|
|
@@ -333,9 +349,11 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
|
|
|
333
349
|
limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
|
|
334
350
|
functions = parse_function_specs(specs, limit_ids=limit_ids)
|
|
335
351
|
|
|
352
|
+
_generate_client_id()
|
|
353
|
+
|
|
336
354
|
# Only process schemas if no_types is False
|
|
337
355
|
if not no_types:
|
|
338
|
-
schemas = get_schemas()
|
|
356
|
+
schemas = get_schemas(specs)
|
|
339
357
|
schema_index = build_schema_index(schemas)
|
|
340
358
|
if schemas:
|
|
341
359
|
schema_limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
|
|
@@ -359,7 +377,11 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
|
|
|
359
377
|
)
|
|
360
378
|
exit()
|
|
361
379
|
|
|
362
|
-
|
|
380
|
+
tables = get_tables(specs)
|
|
381
|
+
if tables:
|
|
382
|
+
generate_tables(tables)
|
|
383
|
+
|
|
384
|
+
variables = get_variables(specs)
|
|
363
385
|
if variables:
|
|
364
386
|
generate_variables(variables)
|
|
365
387
|
|
|
@@ -371,14 +393,7 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
|
|
|
371
393
|
|
|
372
394
|
|
|
373
395
|
def clear() -> None:
|
|
374
|
-
|
|
375
|
-
poly_path = os.path.join(base, "poly")
|
|
376
|
-
if os.path.exists(poly_path):
|
|
377
|
-
shutil.rmtree(poly_path)
|
|
378
|
-
|
|
379
|
-
vari_path = os.path.join(base, "vari")
|
|
380
|
-
if os.path.exists(vari_path):
|
|
381
|
-
shutil.rmtree(vari_path)
|
|
396
|
+
remove_old_library()
|
|
382
397
|
print("Cleared!")
|
|
383
398
|
|
|
384
399
|
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
from typing_extensions import NotRequired, TypedDict
|
|
4
|
+
from typing import List, Union, Type, Dict, Any, Literal, Tuple, Optional, get_args, get_origin
|
|
5
|
+
from polyapi.utils import add_import_to_init, init_the_init
|
|
6
|
+
from polyapi.typedefs import TableSpecDto
|
|
7
|
+
from polyapi.constants import JSONSCHEMA_TO_PYTHON_TYPE_MAP
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def execute_query(table_id, method, query):
|
|
11
|
+
from polyapi import polyCustom
|
|
12
|
+
from polyapi.poly.client_id import client_id
|
|
13
|
+
try:
|
|
14
|
+
url = f"/tables/{table_id}/{method}?clientId={client_id}"
|
|
15
|
+
headers = {{
|
|
16
|
+
'x-poly-execution-id': polyCustom.get('executionId')
|
|
17
|
+
}}
|
|
18
|
+
response = requests.post(url, json=query, headers=headers)
|
|
19
|
+
response.raise_for_status()
|
|
20
|
+
return response.json()
|
|
21
|
+
except Exception as e:
|
|
22
|
+
return scrub_keys(e)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def first_result(rsp):
|
|
26
|
+
if isinstance(rsp, dict) and isinstance(rsp.get('results'), list):
|
|
27
|
+
return rsp['results'][0] if rsp['results'] else None
|
|
28
|
+
return rsp
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_key_transform_map = {
|
|
32
|
+
"not_": "not",
|
|
33
|
+
"in": "in",
|
|
34
|
+
"starts_with": "startsWith",
|
|
35
|
+
"ends_with": "startsWith",
|
|
36
|
+
"not_in": "notIn",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _transform_keys(obj: Any) -> Any:
|
|
41
|
+
if isinstance(obj, dict):
|
|
42
|
+
return {
|
|
43
|
+
_key_transform_map.get(k, k): _transform_keys(v)
|
|
44
|
+
for k, v in obj.items()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
elif isinstance(obj, list):
|
|
48
|
+
return [_transform_keys(v) for v in obj]
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
return obj
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def transform_query(query: dict) -> dict:
|
|
55
|
+
if query["where"] or query["order_by"]:
|
|
56
|
+
return {
|
|
57
|
+
**query,
|
|
58
|
+
"where": _transform_keys(query["where"]) if query["where"] else None,
|
|
59
|
+
"orderBy": query["order_by"] if query["order_by"] else None
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return query
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
TABI_TABLE_TEMPLATE = '''
|
|
66
|
+
{table_name}Columns = Literal[{table_columns}]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
{table_row_classes}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
{table_row_subset_class}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
{table_where_class}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class {table_name}SelectManyQuery(TypedDict):
|
|
83
|
+
where: NotRequired[{table_name}WhereFilter]
|
|
84
|
+
order_by: NotRequired[Dict[{table_name}Columns, SortOrder]]
|
|
85
|
+
limit: NotRequired[int]
|
|
86
|
+
offset: NotRequired[int]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class {table_name}SelectOneQuery(TypedDict):
|
|
91
|
+
where: NotRequired[{table_name}WhereFilter]
|
|
92
|
+
order_by: NotRequired[Dict[{table_name}Columns, SortOrder]]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class {table_name}InsertOneQuery(TypedDict):
|
|
97
|
+
data: {table_name}Subset
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class {table_name}InsertManyQuery(TypedDict):
|
|
102
|
+
data: List[{table_name}Subset]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class {table_name}UpdateManyQuery(TypedDict):
|
|
107
|
+
where: NotRequired[{table_name}WhereFilter]
|
|
108
|
+
data: {table_name}Subset
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class {table_name}DeleteQuery(TypedDict):
|
|
113
|
+
where: NotRequired[{table_name}WhereFilter]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class {table_name}QueryResults(TypedDict):
|
|
118
|
+
results: List[{table_name}Row]
|
|
119
|
+
pagination: None # Pagination not yet supported
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class {table_name}CountQuery(TypedDict):
|
|
124
|
+
where: NotRequired[{table_name}WhereFilter]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class {table_name}:{table_description}
|
|
129
|
+
table_id = "{table_id}"
|
|
130
|
+
|
|
131
|
+
@overload
|
|
132
|
+
@staticmethod
|
|
133
|
+
def count(query: {table_name}CountQuery) -> PolyCountResult: ...
|
|
134
|
+
@overload
|
|
135
|
+
@staticmethod
|
|
136
|
+
def count(*, where: Optional[{table_name}WhereFilter]) -> PolyCountResult: ...
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def count(*args, **kwargs) -> PolyCountResult:
|
|
140
|
+
if args:
|
|
141
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
142
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
143
|
+
query = args[0]
|
|
144
|
+
else:
|
|
145
|
+
query = kwargs
|
|
146
|
+
return execute_query({table_name}.table_id, "count", transform_query(query))
|
|
147
|
+
|
|
148
|
+
@overload
|
|
149
|
+
@staticmethod
|
|
150
|
+
def select_many(query: {table_name}SelectManyQuery) -> {table_name}QueryResults: ...
|
|
151
|
+
@overload
|
|
152
|
+
@staticmethod
|
|
153
|
+
def select_many(*, where: Optional[{table_name}WhereFilter], order_by: Optional[Dict[{table_name}Columns, SortOrder]], limit: Optional[int], offset: Optional[int]) -> {table_name}QueryResults: ...
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def select_many(*args, **kwargs) -> {table_name}QueryResults:
|
|
157
|
+
if args:
|
|
158
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
159
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
160
|
+
query = args[0]
|
|
161
|
+
else:
|
|
162
|
+
query = kwargs
|
|
163
|
+
if query.get('limit') is None:
|
|
164
|
+
query['limit'] = 1000
|
|
165
|
+
if query['limit'] > 1000:
|
|
166
|
+
raise ValueError("Cannot select more than 1000 rows at a time.")
|
|
167
|
+
return execute_query({table_name}.table_id, "select", transform_query(query))
|
|
168
|
+
|
|
169
|
+
@overload
|
|
170
|
+
@staticmethod
|
|
171
|
+
def select_one(query: {table_name}SelectOneQuery) -> {table_name}Row: ...
|
|
172
|
+
@overload
|
|
173
|
+
@staticmethod
|
|
174
|
+
def select_one(*, where: Optional[{table_name}WhereFilter], order_by: Optional[Dict[{table_name}Columns, SortOrder]]) -> {table_name}Row: ...
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def select_one(*args, **kwargs) -> {table_name}Row:
|
|
178
|
+
if args:
|
|
179
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
180
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
181
|
+
query = args[0]
|
|
182
|
+
else:
|
|
183
|
+
query = kwargs
|
|
184
|
+
query['limit'] = 1
|
|
185
|
+
return first_result(execute_query({table_name}.table_id, "select", transform_query(query)))
|
|
186
|
+
|
|
187
|
+
@overload
|
|
188
|
+
@staticmethod
|
|
189
|
+
def insert_many(query: {table_name}InsertManyQuery) -> {table_name}QueryResults: ...
|
|
190
|
+
@overload
|
|
191
|
+
@staticmethod
|
|
192
|
+
def insert_many(*, data: List[{table_name}Subset]) -> {table_name}QueryResults: ...
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def insert_many(*args, **kwargs) -> {table_name}QueryResults:
|
|
196
|
+
if args:
|
|
197
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
198
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
199
|
+
query = args[0]
|
|
200
|
+
else:
|
|
201
|
+
query = kwargs
|
|
202
|
+
if len(query['data']) > 1000:
|
|
203
|
+
raise ValueError("Cannot insert more than 1000 rows at a time.")
|
|
204
|
+
return execute_query({table_name}.table_id, "insert", query)
|
|
205
|
+
|
|
206
|
+
@overload
|
|
207
|
+
@staticmethod
|
|
208
|
+
def insert_one(query: {table_name}InsertOneQuery) -> {table_name}Row: ...
|
|
209
|
+
@overload
|
|
210
|
+
@staticmethod
|
|
211
|
+
def insert_one(*, data: {table_name}Subset) -> {table_name}Row: ...
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def insert_one(*args, **kwargs) -> {table_name}Row:
|
|
215
|
+
if args:
|
|
216
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
217
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
218
|
+
query = args[0]
|
|
219
|
+
else:
|
|
220
|
+
query = kwargs
|
|
221
|
+
return first_result(execute_query({table_name}.table_id, "insert", {{ 'data': [query['data']] }}))
|
|
222
|
+
|
|
223
|
+
@overload
|
|
224
|
+
@staticmethod
|
|
225
|
+
def upsert_many(query: {table_name}InsertManyQuery) -> {table_name}QueryResults: ...
|
|
226
|
+
@overload
|
|
227
|
+
@staticmethod
|
|
228
|
+
def upsert_many(*, data: List[{table_name}Subset]) -> {table_name}QueryResults: ...
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def upsert_many(*args, **kwargs) -> {table_name}QueryResults:
|
|
232
|
+
if args:
|
|
233
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
234
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
235
|
+
query = args[0]
|
|
236
|
+
else:
|
|
237
|
+
query = kwargs
|
|
238
|
+
if len(data) > 1000:
|
|
239
|
+
raise ValueError("Cannot upsert more than 1000 rows at a time.")
|
|
240
|
+
return execute_query({table_name}.table_id, "upsert", query)
|
|
241
|
+
|
|
242
|
+
@overload
|
|
243
|
+
@staticmethod
|
|
244
|
+
def upsert_one(query: {table_name}InsertOneQuery) -> {table_name}Row: ...
|
|
245
|
+
@overload
|
|
246
|
+
@staticmethod
|
|
247
|
+
def upsert_one(*, data: {table_name}Subset) -> {table_name}Row: ...
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def upsert_one(*args, **kwargs) -> {table_name}Row:
|
|
251
|
+
if args:
|
|
252
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
253
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
254
|
+
query = args[0]
|
|
255
|
+
else:
|
|
256
|
+
query = kwargs
|
|
257
|
+
return first_result(execute_query({table_name}.table_id, "upsert", {{ 'data': [query['data']] }}))
|
|
258
|
+
|
|
259
|
+
@overload
|
|
260
|
+
@staticmethod
|
|
261
|
+
def update_many(query: {table_name}UpdateManyQuery) -> {table_name}QueryResults: ...
|
|
262
|
+
@overload
|
|
263
|
+
@staticmethod
|
|
264
|
+
def update_many(*, where: Optional[{table_name}WhereFilter], data: {table_name}Subset) -> {table_name}QueryResults: ...
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def update_many(*args, **kwargs) -> {table_name}QueryResults:
|
|
268
|
+
if args:
|
|
269
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
270
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
271
|
+
query = args[0]
|
|
272
|
+
else:
|
|
273
|
+
query = kwargs
|
|
274
|
+
return execute_query({table_name}.table_id, "update", transform_query(query))
|
|
275
|
+
|
|
276
|
+
@overload
|
|
277
|
+
@staticmethod
|
|
278
|
+
def delete_many(query: {table_name}DeleteQuery) -> PolyDeleteResults: ...
|
|
279
|
+
@overload
|
|
280
|
+
@staticmethod
|
|
281
|
+
def delete_many(*, where: Optional[{table_name}WhereFilter]) -> PolyDeleteResults: ...
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def delete_many(*args, **kwargs) -> PolyDeleteResults:
|
|
285
|
+
if args:
|
|
286
|
+
if len(args) != 1 or not isinstance(args[0], dict):
|
|
287
|
+
raise TypeError("Expected query as a single argument or as kwargs")
|
|
288
|
+
query = args[0]
|
|
289
|
+
else:
|
|
290
|
+
query = kwargs
|
|
291
|
+
return execute_query({table_name}.table_id, "delete", query)
|
|
292
|
+
'''
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _get_column_type_str(name: str, schema: Dict[str, Any], is_required: bool) -> str:
|
|
296
|
+
result = ""
|
|
297
|
+
|
|
298
|
+
col_type = schema.get("type", "object")
|
|
299
|
+
if isinstance(col_type, list):
|
|
300
|
+
subtypes = [_get_column_type_str(name, { **schema, "type": t }, is_required) for t in col_type]
|
|
301
|
+
result = f"Union[{", ".join(subtypes)}]"
|
|
302
|
+
elif col_type == "array":
|
|
303
|
+
if isinstance(schema["items"], list):
|
|
304
|
+
subtypes = [_get_column_type_str(f"{name}{i}", s, True) for i, s in enumerate(schema["items"])]
|
|
305
|
+
result = f"Tuple[{", ".join(subtypes)}]"
|
|
306
|
+
elif isinstance(schema["items"], dict):
|
|
307
|
+
result = f"List[{_get_column_type_str(name, schema["items"], True)}]"
|
|
308
|
+
else:
|
|
309
|
+
result = "List[Any]"
|
|
310
|
+
elif col_type == "object":
|
|
311
|
+
if isinstance(schema.get("patternProperties"), dict):
|
|
312
|
+
# TODO: Handle multiple pattern properties
|
|
313
|
+
result = f"Dict[str, {_get_column_type_str(f"{name}_", schema["patternProperties"], True)}]"
|
|
314
|
+
elif isinstance(schema.get("properties"), dict) and len(schema["properties"].values()) > 0:
|
|
315
|
+
# TODO: Handle x-poly-refs
|
|
316
|
+
result = f'"{name}"'
|
|
317
|
+
else:
|
|
318
|
+
result = "Dict[str, Any]"
|
|
319
|
+
else:
|
|
320
|
+
result = JSONSCHEMA_TO_PYTHON_TYPE_MAP.get(schema["type"], "")
|
|
321
|
+
|
|
322
|
+
if result:
|
|
323
|
+
return result if is_required else f"Optional[{result}]"
|
|
324
|
+
|
|
325
|
+
return "Any"
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _render_table_row_classes(table_name: str, schema: Dict[str, Any]) -> str:
|
|
329
|
+
from polyapi.schema import wrapped_generate_schema_types
|
|
330
|
+
|
|
331
|
+
output = wrapped_generate_schema_types(schema, f"{table_name}Row", "Dict")
|
|
332
|
+
|
|
333
|
+
return output[1].split("\n", 1)[1].strip()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _render_table_subset_class(table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]) -> str:
|
|
337
|
+
# Generate class which can match any subset of a table row
|
|
338
|
+
lines = [f"class {table_name}Subset(TypedDict):"]
|
|
339
|
+
|
|
340
|
+
for name, schema in columns:
|
|
341
|
+
type_str = _get_column_type_str(f"_{table_name}Row{name}", schema, name in required)
|
|
342
|
+
lines.append(f" {name}: NotRequired[{type_str}]")
|
|
343
|
+
|
|
344
|
+
return "\n".join(lines)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _render_table_where_class(table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]) -> str:
|
|
348
|
+
# Generate class for the 'where' part of the query
|
|
349
|
+
lines = [f"class {table_name}WhereFilter(TypedDict):"]
|
|
350
|
+
|
|
351
|
+
for name, schema in columns:
|
|
352
|
+
ftype_str = ""
|
|
353
|
+
type_str = _get_column_type_str(f"_{table_name}Row{name}", schema, True) # force required to avoid wrapping type in Optional[]
|
|
354
|
+
is_required = name in required
|
|
355
|
+
if type_str == "bool":
|
|
356
|
+
ftype_str = "BooleanFilter" if is_required else "NullableBooleanFilter"
|
|
357
|
+
elif type_str == "str":
|
|
358
|
+
ftype_str = "StringFilter" if is_required else "NullableStringFilter"
|
|
359
|
+
elif type_str in ["int", "float"]:
|
|
360
|
+
ftype_str = "NumberFilter" if is_required else "NullableNumberFilter"
|
|
361
|
+
elif is_required == False:
|
|
362
|
+
type_str = "None"
|
|
363
|
+
ftype_str = "NullableObjectFilter"
|
|
364
|
+
|
|
365
|
+
if ftype_str:
|
|
366
|
+
lines.append(f" {name}: NotRequired[Union[{type_str}, {ftype_str}]]")
|
|
367
|
+
|
|
368
|
+
lines.append(f' AND: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]')
|
|
369
|
+
lines.append(f' OR: NotRequired[List["{table_name}WhereFilter"]]')
|
|
370
|
+
lines.append(f' NOT: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]')
|
|
371
|
+
|
|
372
|
+
return "\n".join(lines)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _render_table(table: TableSpecDto) -> str:
|
|
376
|
+
columns = list(table["schema"]["properties"].items())
|
|
377
|
+
required_colunms = table["schema"].get("required", [])
|
|
378
|
+
|
|
379
|
+
table_columns = ",".join([ f'"{k}"' for k,_ in columns])
|
|
380
|
+
table_row_classes = _render_table_row_classes(table["name"], table["schema"])
|
|
381
|
+
table_row_subset_class = _render_table_subset_class(table["name"], columns, required_colunms)
|
|
382
|
+
table_where_class = _render_table_where_class(table["name"], columns, required_colunms)
|
|
383
|
+
if table.get("description", ""):
|
|
384
|
+
table_description = '\n """'
|
|
385
|
+
table_description += '\n '.join(table["description"].replace('"', "'").split("\n"))
|
|
386
|
+
table_description += '\n """'
|
|
387
|
+
else:
|
|
388
|
+
table_description = ""
|
|
389
|
+
|
|
390
|
+
return TABI_TABLE_TEMPLATE.format(
|
|
391
|
+
table_name=table["name"],
|
|
392
|
+
table_id=table["id"],
|
|
393
|
+
table_description=table_description,
|
|
394
|
+
table_columns=table_columns,
|
|
395
|
+
table_row_classes=table_row_classes,
|
|
396
|
+
table_row_subset_class=table_row_subset_class,
|
|
397
|
+
table_where_class=table_where_class,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def generate_tables(tables: List[TableSpecDto]):
|
|
402
|
+
for table in tables:
|
|
403
|
+
_create_table(table)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _create_table(table: TableSpecDto) -> None:
|
|
407
|
+
folders = ["tabi"]
|
|
408
|
+
if table["context"]:
|
|
409
|
+
folders += table["context"].split(".")
|
|
410
|
+
|
|
411
|
+
# build up the full_path by adding all the folders
|
|
412
|
+
base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
|
413
|
+
full_path = base_path
|
|
414
|
+
|
|
415
|
+
for idx, folder in enumerate(folders):
|
|
416
|
+
full_path = os.path.join(full_path, folder)
|
|
417
|
+
if not os.path.exists(full_path):
|
|
418
|
+
os.makedirs(full_path)
|
|
419
|
+
next = folders[idx + 1] if idx + 1 < len(folders) else None
|
|
420
|
+
if next:
|
|
421
|
+
add_import_to_init(full_path, next, "")
|
|
422
|
+
|
|
423
|
+
init_path = os.path.join(full_path, "__init__.py")
|
|
424
|
+
|
|
425
|
+
imports = "\n".join([
|
|
426
|
+
"from typing_extensions import NotRequired, TypedDict",
|
|
427
|
+
"from typing import Union, List, Dict, Any, Literal, Optional, Required, overload",
|
|
428
|
+
"from polyapi.poly_tables import execute_query, first_result, transform_query",
|
|
429
|
+
"from polyapi.typedefs import Table, PolyCountResult, PolyDeleteResults, SortOrder, StringFilter, NullableStringFilter, NumberFilter, NullableNumberFilter, BooleanFilter, NullableBooleanFilter, NullableObjectFilter",
|
|
430
|
+
])
|
|
431
|
+
table_contents = _render_table(table)
|
|
432
|
+
|
|
433
|
+
file_contents = ""
|
|
434
|
+
if os.path.exists(init_path):
|
|
435
|
+
with open(init_path, "r") as f:
|
|
436
|
+
file_contents = f.read()
|
|
437
|
+
|
|
438
|
+
with open(init_path, "w") as f:
|
|
439
|
+
if not file_contents.startswith(imports):
|
|
440
|
+
f.write(imports + "\n\n\n")
|
|
441
|
+
if file_contents:
|
|
442
|
+
f.write(file_contents + "\n\n\n")
|
|
443
|
+
f.write(table_contents)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from typing import List, Dict
|
|
4
|
+
from typing_extensions import cast # type: ignore
|
|
4
5
|
import requests
|
|
5
6
|
|
|
6
7
|
from polyapi.utils import get_auth_headers
|
|
@@ -30,12 +31,14 @@ def group_by(items: List[Dict], key: str) -> Dict[str, List[Dict]]:
|
|
|
30
31
|
|
|
31
32
|
def remove_deployable_function(deployable: SyncDeployment) -> bool:
|
|
32
33
|
api_key, _ = get_api_key_and_url()
|
|
34
|
+
if not api_key:
|
|
35
|
+
raise Error("Missing api key!")
|
|
33
36
|
headers = get_auth_headers(api_key)
|
|
34
37
|
url = f'{deployable["instance"]}/functions/{deployable["type"].replace("-function", "")}/{deployable["id"]}'
|
|
35
38
|
response = requests.get(url, headers=headers)
|
|
36
39
|
if response.status_code != 200:
|
|
37
40
|
return False
|
|
38
|
-
requests.delete(url, headers)
|
|
41
|
+
requests.delete(url, headers=headers)
|
|
39
42
|
return True
|
|
40
43
|
|
|
41
44
|
def remove_deployable(deployable: SyncDeployment) -> bool:
|
|
@@ -47,6 +50,8 @@ def remove_deployable(deployable: SyncDeployment) -> bool:
|
|
|
47
50
|
|
|
48
51
|
def sync_function_and_get_id(deployable: SyncDeployment, code: str) -> str:
|
|
49
52
|
api_key, _ = get_api_key_and_url()
|
|
53
|
+
if not api_key:
|
|
54
|
+
raise Error("Missing api key!")
|
|
50
55
|
headers = get_auth_headers(api_key)
|
|
51
56
|
url = f'{deployable["instance"]}/functions/{deployable["type"].replace("-function", "")}'
|
|
52
57
|
payload = {
|
|
@@ -129,15 +134,15 @@ def sync_deployables(dry_run: bool, instance: str | None = None):
|
|
|
129
134
|
else:
|
|
130
135
|
sync_deployment = { **deployable, "instance": instance }
|
|
131
136
|
if git_revision == deployable['gitRevision']:
|
|
132
|
-
deployment = sync_deployable(sync_deployment)
|
|
137
|
+
deployment = sync_deployable(cast(SyncDeployment, sync_deployment))
|
|
133
138
|
if previous_deployment:
|
|
134
139
|
previous_deployment.update(deployment)
|
|
135
140
|
else:
|
|
136
141
|
deployable['deployments'].insert(0, deployment)
|
|
137
142
|
else:
|
|
138
|
-
found = remove_deployable(sync_deployment)
|
|
143
|
+
found = remove_deployable(cast(SyncDeployment, sync_deployment))
|
|
139
144
|
action = 'NOT FOUND' if not found else action
|
|
140
|
-
remove_index = all_deployables.index(deployable)
|
|
145
|
+
remove_index = all_deployables.index(cast(DeployableRecord, deployable))
|
|
141
146
|
to_remove.append(all_deployables.pop(remove_index))
|
|
142
147
|
|
|
143
148
|
print(f"{'Would sync' if dry_run else 'Synced'} {deployable['type'].replace('-', ' ')} {deployable['context']}.{deployable['name']}: {'TO BE ' if dry_run else ''}{action}")
|