polyapi-python 0.3.13__py3-none-any.whl → 0.3.13.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polyapi/poly_tables.py +41 -109
- {polyapi_python-0.3.13.dist-info → polyapi_python-0.3.13.dev2.dist-info}/METADATA +1 -9
- {polyapi_python-0.3.13.dist-info → polyapi_python-0.3.13.dev2.dist-info}/RECORD +6 -6
- {polyapi_python-0.3.13.dist-info → polyapi_python-0.3.13.dev2.dist-info}/WHEEL +0 -0
- {polyapi_python-0.3.13.dist-info → polyapi_python-0.3.13.dev2.dist-info}/licenses/LICENSE +0 -0
- {polyapi_python-0.3.13.dist-info → polyapi_python-0.3.13.dev2.dist-info}/top_level.txt +0 -0
polyapi/poly_tables.py
CHANGED
|
@@ -1,64 +1,27 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import requests
|
|
3
3
|
from typing_extensions import NotRequired, TypedDict
|
|
4
|
-
from typing import
|
|
5
|
-
List,
|
|
6
|
-
Union,
|
|
7
|
-
Type,
|
|
8
|
-
Dict,
|
|
9
|
-
Any,
|
|
10
|
-
Literal,
|
|
11
|
-
Tuple,
|
|
12
|
-
Optional,
|
|
13
|
-
get_args,
|
|
14
|
-
get_origin,
|
|
15
|
-
)
|
|
4
|
+
from typing import List, Union, Type, Dict, Any, Literal, Tuple, Optional, get_args, get_origin
|
|
16
5
|
from polyapi.utils import add_import_to_init, init_the_init
|
|
17
6
|
from polyapi.typedefs import TableSpecDto
|
|
18
7
|
from polyapi.constants import JSONSCHEMA_TO_PYTHON_TYPE_MAP
|
|
19
|
-
from polyapi.config import get_api_key_and_url
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"from typing_extensions import NotRequired, TypedDict",
|
|
24
|
-
"from typing import Union, List, Dict, Any, Literal, Optional, Required, overload",
|
|
25
|
-
"from polyapi.poly_tables import execute_query, first_result, transform_query, delete_one_response",
|
|
26
|
-
"from polyapi.typedefs import Table, PolyCountResult, PolyDeleteResult, PolyDeleteResults, SortOrder, StringFilter, NullableStringFilter, NumberFilter, NullableNumberFilter, BooleanFilter, NullableBooleanFilter, NullableObjectFilter",
|
|
27
|
-
]
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def scrub(data: Any) -> Any:
|
|
32
|
-
if not data or not isinstance(data, (Dict, List)):
|
|
33
|
-
return data
|
|
9
|
+
def scrub(data) -> Dict[str, Any]:
|
|
10
|
+
if (not data or not isinstance(data, (Dict, List))): return data
|
|
34
11
|
if isinstance(data, List):
|
|
35
12
|
return [scrub(item) for item in data]
|
|
36
13
|
else:
|
|
37
14
|
temp = {}
|
|
38
|
-
secrets = [
|
|
39
|
-
"x_api_key",
|
|
40
|
-
"x-api-key",
|
|
41
|
-
"access_token",
|
|
42
|
-
"access-token",
|
|
43
|
-
"authorization",
|
|
44
|
-
"api_key",
|
|
45
|
-
"api-key",
|
|
46
|
-
"apikey",
|
|
47
|
-
"accesstoken",
|
|
48
|
-
"token",
|
|
49
|
-
"password",
|
|
50
|
-
"key",
|
|
51
|
-
]
|
|
15
|
+
secrets = ["x_api_key", "x-api-key", "access_token", "access-token", "authorization", "api_key", "api-key", "apikey", "accesstoken", "token", "password", "key"]
|
|
52
16
|
for key, value in data.items():
|
|
53
17
|
if isinstance(value, (Dict, List)):
|
|
54
18
|
temp[key] = scrub(data[key])
|
|
55
19
|
elif key.lower() in secrets:
|
|
56
|
-
temp[key] =
|
|
20
|
+
temp[key] = '********'
|
|
57
21
|
else:
|
|
58
22
|
temp[key] = data[key]
|
|
59
23
|
return temp
|
|
60
24
|
|
|
61
|
-
|
|
62
25
|
def scrub_keys(e: Exception) -> Dict[str, Any]:
|
|
63
26
|
"""
|
|
64
27
|
Scrub the keys of an exception to remove sensitive information.
|
|
@@ -68,26 +31,18 @@ def scrub_keys(e: Exception) -> Dict[str, Any]:
|
|
|
68
31
|
"error": str(e),
|
|
69
32
|
"type": type(e).__name__,
|
|
70
33
|
"message": str(e),
|
|
71
|
-
"args": scrub(getattr(e,
|
|
34
|
+
"args": scrub(getattr(e, 'args', None))
|
|
72
35
|
}
|
|
73
36
|
|
|
74
37
|
|
|
75
38
|
def execute_query(table_id, method, query):
|
|
76
39
|
from polyapi import polyCustom
|
|
77
40
|
from polyapi.poly.client_id import client_id
|
|
78
|
-
|
|
79
41
|
try:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
auth_key = polyCustom.get("executionApiKey") or api_key
|
|
87
|
-
url = f"{base_url.rstrip('/')}/tables/{table_id}/{method}?clientId={client_id}"
|
|
88
|
-
headers = {"x-poly-execution-id": polyCustom.get("executionId")}
|
|
89
|
-
if auth_key:
|
|
90
|
-
headers["Authorization"] = f"Bearer {auth_key}"
|
|
42
|
+
url = f"/tables/{table_id}/{method}?clientId={client_id}"
|
|
43
|
+
headers = {{
|
|
44
|
+
'x-poly-execution-id': polyCustom.get('executionId')
|
|
45
|
+
}}
|
|
91
46
|
response = requests.post(url, json=query, headers=headers)
|
|
92
47
|
response.raise_for_status()
|
|
93
48
|
return response.json()
|
|
@@ -96,16 +51,14 @@ def execute_query(table_id, method, query):
|
|
|
96
51
|
|
|
97
52
|
|
|
98
53
|
def first_result(rsp):
|
|
99
|
-
if isinstance(rsp, dict) and isinstance(rsp.get(
|
|
100
|
-
return rsp[
|
|
54
|
+
if isinstance(rsp, dict) and isinstance(rsp.get('results'), list):
|
|
55
|
+
return rsp['results'][0] if rsp['results'] else None
|
|
101
56
|
return rsp
|
|
102
57
|
|
|
103
|
-
|
|
104
58
|
def delete_one_response(rsp):
|
|
105
|
-
if isinstance(rsp, dict) and isinstance(rsp.get(
|
|
106
|
-
return {
|
|
107
|
-
return {
|
|
108
|
-
|
|
59
|
+
if isinstance(rsp, dict) and isinstance(rsp.get('deleted'), int):
|
|
60
|
+
return { 'deleted': bool(rsp.get('deleted')) }
|
|
61
|
+
return { 'deleted': false }
|
|
109
62
|
|
|
110
63
|
_key_transform_map = {
|
|
111
64
|
"not_": "not",
|
|
@@ -119,7 +72,8 @@ _key_transform_map = {
|
|
|
119
72
|
def _transform_keys(obj: Any) -> Any:
|
|
120
73
|
if isinstance(obj, dict):
|
|
121
74
|
return {
|
|
122
|
-
_key_transform_map.get(k, k): _transform_keys(v)
|
|
75
|
+
_key_transform_map.get(k, k): _transform_keys(v)
|
|
76
|
+
for k, v in obj.items()
|
|
123
77
|
}
|
|
124
78
|
|
|
125
79
|
elif isinstance(obj, list):
|
|
@@ -134,13 +88,13 @@ def transform_query(query: dict) -> dict:
|
|
|
134
88
|
return {
|
|
135
89
|
**query,
|
|
136
90
|
"where": _transform_keys(query["where"]) if query["where"] else None,
|
|
137
|
-
"orderBy": query["order_by"] if query["order_by"] else None
|
|
91
|
+
"orderBy": query["order_by"] if query["order_by"] else None
|
|
138
92
|
}
|
|
139
93
|
|
|
140
94
|
return query
|
|
141
95
|
|
|
142
96
|
|
|
143
|
-
TABI_TABLE_TEMPLATE =
|
|
97
|
+
TABI_TABLE_TEMPLATE = '''
|
|
144
98
|
{table_name}Columns = Literal[{table_columns}]
|
|
145
99
|
|
|
146
100
|
|
|
@@ -415,7 +369,7 @@ class {table_name}:{table_description}
|
|
|
415
369
|
query["where"]["id"] = kwargs["id"]
|
|
416
370
|
query.pop("id", None)
|
|
417
371
|
return delete_one_response(execute_query({table_name}.table_id, "delete", transform_query(query)))
|
|
418
|
-
|
|
372
|
+
'''
|
|
419
373
|
|
|
420
374
|
|
|
421
375
|
def _get_column_type_str(name: str, schema: Dict[str, Any], is_required: bool) -> str:
|
|
@@ -423,17 +377,11 @@ def _get_column_type_str(name: str, schema: Dict[str, Any], is_required: bool) -
|
|
|
423
377
|
|
|
424
378
|
col_type = schema.get("type", "object")
|
|
425
379
|
if isinstance(col_type, list):
|
|
426
|
-
subtypes = [
|
|
427
|
-
_get_column_type_str(name, {**schema, "type": t}, is_required)
|
|
428
|
-
for t in col_type
|
|
429
|
-
]
|
|
380
|
+
subtypes = [_get_column_type_str(name, { **schema, "type": t }, is_required) for t in col_type]
|
|
430
381
|
result = f"Union[{', '.join(subtypes)}]"
|
|
431
382
|
elif col_type == "array":
|
|
432
383
|
if isinstance(schema["items"], list):
|
|
433
|
-
subtypes = [
|
|
434
|
-
_get_column_type_str(f"{name}{i}", s, True)
|
|
435
|
-
for i, s in enumerate(schema["items"])
|
|
436
|
-
]
|
|
384
|
+
subtypes = [_get_column_type_str(f"{name}{i}", s, True) for i, s in enumerate(schema["items"])]
|
|
437
385
|
result = f"Tuple[{', '.join(subtypes)}]"
|
|
438
386
|
elif isinstance(schema["items"], dict):
|
|
439
387
|
result = f"List[{_get_column_type_str(name, schema['items'], True)}]"
|
|
@@ -443,10 +391,7 @@ def _get_column_type_str(name: str, schema: Dict[str, Any], is_required: bool) -
|
|
|
443
391
|
if isinstance(schema.get("patternProperties"), dict):
|
|
444
392
|
# TODO: Handle multiple pattern properties
|
|
445
393
|
result = f"Dict[str, {_get_column_type_str(f'{name}_', schema['patternProperties'], True)}]"
|
|
446
|
-
elif (
|
|
447
|
-
isinstance(schema.get("properties"), dict)
|
|
448
|
-
and len(schema["properties"].values()) > 0
|
|
449
|
-
):
|
|
394
|
+
elif isinstance(schema.get("properties"), dict) and len(schema["properties"].values()) > 0:
|
|
450
395
|
# TODO: Handle x-poly-refs
|
|
451
396
|
result = f'"{name}"'
|
|
452
397
|
else:
|
|
@@ -468,32 +413,24 @@ def _render_table_row_classes(table_name: str, schema: Dict[str, Any]) -> str:
|
|
|
468
413
|
return output[1].split("\n", 1)[1].strip()
|
|
469
414
|
|
|
470
415
|
|
|
471
|
-
def _render_table_subset_class(
|
|
472
|
-
table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]
|
|
473
|
-
) -> str:
|
|
416
|
+
def _render_table_subset_class(table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]) -> str:
|
|
474
417
|
# Generate class which can match any subset of a table row
|
|
475
418
|
lines = [f"class {table_name}Subset(TypedDict):"]
|
|
476
419
|
|
|
477
420
|
for name, schema in columns:
|
|
478
|
-
type_str = _get_column_type_str(
|
|
479
|
-
f"_{table_name}Row{name}", schema, name in required
|
|
480
|
-
)
|
|
421
|
+
type_str = _get_column_type_str(f"_{table_name}Row{name}", schema, name in required)
|
|
481
422
|
lines.append(f" {name}: NotRequired[{type_str}]")
|
|
482
423
|
|
|
483
424
|
return "\n".join(lines)
|
|
484
425
|
|
|
485
426
|
|
|
486
|
-
def _render_table_where_class(
|
|
487
|
-
table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]
|
|
488
|
-
) -> str:
|
|
427
|
+
def _render_table_where_class(table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]) -> str:
|
|
489
428
|
# Generate class for the 'where' part of the query
|
|
490
429
|
lines = [f"class {table_name}WhereFilter(TypedDict):"]
|
|
491
430
|
|
|
492
431
|
for name, schema in columns:
|
|
493
432
|
ftype_str = ""
|
|
494
|
-
type_str = _get_column_type_str(
|
|
495
|
-
f"_{table_name}Row{name}", schema, True
|
|
496
|
-
) # force required to avoid wrapping type in Optional[]
|
|
433
|
+
type_str = _get_column_type_str(f"_{table_name}Row{name}", schema, True) # force required to avoid wrapping type in Optional[]
|
|
497
434
|
is_required = name in required
|
|
498
435
|
if type_str == "bool":
|
|
499
436
|
ftype_str = "BooleanFilter" if is_required else "NullableBooleanFilter"
|
|
@@ -508,34 +445,24 @@ def _render_table_where_class(
|
|
|
508
445
|
if ftype_str:
|
|
509
446
|
lines.append(f" {name}: NotRequired[Union[{type_str}, {ftype_str}]]")
|
|
510
447
|
|
|
511
|
-
lines.append(
|
|
512
|
-
f' AND: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]'
|
|
513
|
-
)
|
|
448
|
+
lines.append(f' AND: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]')
|
|
514
449
|
lines.append(f' OR: NotRequired[List["{table_name}WhereFilter"]]')
|
|
515
|
-
lines.append(
|
|
516
|
-
f' NOT: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]'
|
|
517
|
-
)
|
|
450
|
+
lines.append(f' NOT: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]')
|
|
518
451
|
|
|
519
452
|
return "\n".join(lines)
|
|
520
453
|
|
|
521
454
|
|
|
522
455
|
def _render_table(table: TableSpecDto) -> str:
|
|
523
456
|
columns = list(table["schema"]["properties"].items())
|
|
524
|
-
|
|
457
|
+
required_colunms = table["schema"].get("required", [])
|
|
525
458
|
|
|
526
|
-
table_columns = ",".join([f'"{k}"' for k,
|
|
459
|
+
table_columns = ",".join([ f'"{k}"' for k,_ in columns])
|
|
527
460
|
table_row_classes = _render_table_row_classes(table["name"], table["schema"])
|
|
528
|
-
table_row_subset_class = _render_table_subset_class(
|
|
529
|
-
|
|
530
|
-
)
|
|
531
|
-
table_where_class = _render_table_where_class(
|
|
532
|
-
table["name"], columns, required_columns
|
|
533
|
-
)
|
|
461
|
+
table_row_subset_class = _render_table_subset_class(table["name"], columns, required_colunms)
|
|
462
|
+
table_where_class = _render_table_where_class(table["name"], columns, required_colunms)
|
|
534
463
|
if table.get("description", ""):
|
|
535
|
-
table_description =
|
|
536
|
-
table_description +=
|
|
537
|
-
table["description"].replace('"', "'").split("\n")
|
|
538
|
-
)
|
|
464
|
+
table_description = '\n """'
|
|
465
|
+
table_description += '\n '.join(table["description"].replace('"', "'").split("\n"))
|
|
539
466
|
table_description += '\n """'
|
|
540
467
|
else:
|
|
541
468
|
table_description = ""
|
|
@@ -575,7 +502,12 @@ def _create_table(table: TableSpecDto) -> None:
|
|
|
575
502
|
|
|
576
503
|
init_path = os.path.join(full_path, "__init__.py")
|
|
577
504
|
|
|
578
|
-
imports =
|
|
505
|
+
imports = "\n".join([
|
|
506
|
+
"from typing_extensions import NotRequired, TypedDict",
|
|
507
|
+
"from typing import Union, List, Dict, Any, Literal, Optional, Required, overload",
|
|
508
|
+
"from polyapi.poly_tables import execute_query, first_result, transform_query",
|
|
509
|
+
"from polyapi.typedefs import Table, PolyCountResult, PolyDeleteResults, SortOrder, StringFilter, NullableStringFilter, NumberFilter, NullableNumberFilter, BooleanFilter, NullableBooleanFilter, NullableObjectFilter",
|
|
510
|
+
])
|
|
579
511
|
table_contents = _render_table(table)
|
|
580
512
|
|
|
581
513
|
file_contents = ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.3.13
|
|
3
|
+
Version: 0.3.13.dev2
|
|
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
|
|
@@ -185,14 +185,6 @@ This script is handy for checking for any mypy types:
|
|
|
185
185
|
|
|
186
186
|
Please ignore \[name-defined\] errors for now. This is a known bug we are working to fix!
|
|
187
187
|
|
|
188
|
-
## Strategies for QA'ing Changes To Generate Or Other Core Functionality
|
|
189
|
-
|
|
190
|
-
Our https://na1.polyapi.io has a large OOB catalog (as does eu1/na2). We also have several big internal PolyAPI projects with Python (message @eupharis if you need a pointer to which ones).
|
|
191
|
-
|
|
192
|
-
Running `python -m polyapi generate` in all these projects and then checking the flake8 and check_mypy steps above is a great way to build confidence that the `generate` changes has no gotchas.
|
|
193
|
-
|
|
194
|
-
Of course all this is in addition to the changes passing through normal unittests and integration tests!
|
|
195
|
-
|
|
196
188
|
## Support
|
|
197
189
|
|
|
198
190
|
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
@@ -14,7 +14,7 @@ polyapi/function_cli.py,sha256=W30yL2i152iZCr6zAU1CHKlDp_tt7kB2cGIXIxmWumk,4302
|
|
|
14
14
|
polyapi/generate.py,sha256=0zVDEnKVZnbMNZ6O0SWAuDLHrBrkW01vFkjpf9wrQZo,21046
|
|
15
15
|
polyapi/parser.py,sha256=pAkeuwp1Kn6bHXiIoj-DuXQ518PztQThIa8vztgsKLQ,21525
|
|
16
16
|
polyapi/poly_schemas.py,sha256=fZ6AGvHcOKQJtlrzSuzeBNed5DxPMA2dJGdJvuFCHWM,9066
|
|
17
|
-
polyapi/poly_tables.py,sha256=
|
|
17
|
+
polyapi/poly_tables.py,sha256=SrByuusS1aL7L7R8o28TEr4A-8AfMGGHCKLg-9vmnxc,18783
|
|
18
18
|
polyapi/prepare.py,sha256=NQzpMIoakNovStvOGOmqSYIpTwiWXaweNSE9se10A2E,7420
|
|
19
19
|
polyapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
polyapi/rendered_spec.py,sha256=nJEj2vRgG3N20fU4s-ThRtOIwAuTzXwXuOBIkXljDVc,2240
|
|
@@ -25,8 +25,8 @@ polyapi/typedefs.py,sha256=mfll-KrngOW0wzbvDNiIDaDkFMwbsT-MY5y5hzWj9RE,5642
|
|
|
25
25
|
polyapi/utils.py,sha256=CZdGusrLOA-e4zb9aZ4aI-UkDPBoyu0-euWj7EE0Vk8,13323
|
|
26
26
|
polyapi/variables.py,sha256=SJv106ePpQP5mx7Iiafl_shtFlE8FoaO9Q8lvw-3IRg,7270
|
|
27
27
|
polyapi/webhook.py,sha256=0ceLwHNjNd2Yx_8MX5jOIxiQ5Lwhy2pe81ProAuKon0,5108
|
|
28
|
-
polyapi_python-0.3.13.dist-info/licenses/LICENSE,sha256=6b_I7aPVp8JXhqQwdw7_B84Ca0S4JGjHj0sr_1VOdB4,1068
|
|
29
|
-
polyapi_python-0.3.13.dist-info/METADATA,sha256=
|
|
30
|
-
polyapi_python-0.3.13.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
31
|
-
polyapi_python-0.3.13.dist-info/top_level.txt,sha256=CEFllOnzowci_50RYJac-M54KD2IdAptFsayVVF_f04,8
|
|
32
|
-
polyapi_python-0.3.13.dist-info/RECORD,,
|
|
28
|
+
polyapi_python-0.3.13.dev2.dist-info/licenses/LICENSE,sha256=6b_I7aPVp8JXhqQwdw7_B84Ca0S4JGjHj0sr_1VOdB4,1068
|
|
29
|
+
polyapi_python-0.3.13.dev2.dist-info/METADATA,sha256=V7aP-XoLF773lEJkHtTwvl0yGWFpzuFkpQEHhKO-C10,5318
|
|
30
|
+
polyapi_python-0.3.13.dev2.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
31
|
+
polyapi_python-0.3.13.dev2.dist-info/top_level.txt,sha256=CEFllOnzowci_50RYJac-M54KD2IdAptFsayVVF_f04,8
|
|
32
|
+
polyapi_python-0.3.13.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|