polyapi-python 0.3.13.dev2__tar.gz → 0.3.13.dev3__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.13.dev2/polyapi_python.egg-info → polyapi_python-0.3.13.dev3}/PKG-INFO +1 -1
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/poly_tables.py +109 -41
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3/polyapi_python.egg-info}/PKG-INFO +1 -1
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/pyproject.toml +1 -1
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_tabi.py +86 -40
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/LICENSE +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/README.md +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/__init__.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/api.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/auth.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/cli.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/client.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/config.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/deployables.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/execute.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/generate.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/parser.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/poly_schemas.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/prepare.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/rendered_spec.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/schema.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/server.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/sync.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/typedefs.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/utils.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/variables.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi/webhook.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi_python.egg-info/SOURCES.txt +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi_python.egg-info/requires.txt +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/setup.cfg +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_api.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_deployables.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_generate.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_parser.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_rendered_spec.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_schema.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_server.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_utils.py +0 -0
- {polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/tests/test_variables.py +0 -0
|
@@ -1,27 +1,64 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import requests
|
|
3
3
|
from typing_extensions import NotRequired, TypedDict
|
|
4
|
-
from typing import
|
|
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
|
+
)
|
|
5
16
|
from polyapi.utils import add_import_to_init, init_the_init
|
|
6
17
|
from polyapi.typedefs import TableSpecDto
|
|
7
18
|
from polyapi.constants import JSONSCHEMA_TO_PYTHON_TYPE_MAP
|
|
19
|
+
from polyapi.config import get_api_key_and_url
|
|
8
20
|
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
TABI_MODULE_IMPORTS = "\n".join(
|
|
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
|
|
11
34
|
if isinstance(data, List):
|
|
12
35
|
return [scrub(item) for item in data]
|
|
13
36
|
else:
|
|
14
37
|
temp = {}
|
|
15
|
-
secrets = [
|
|
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
|
+
]
|
|
16
52
|
for key, value in data.items():
|
|
17
53
|
if isinstance(value, (Dict, List)):
|
|
18
54
|
temp[key] = scrub(data[key])
|
|
19
55
|
elif key.lower() in secrets:
|
|
20
|
-
temp[key] =
|
|
56
|
+
temp[key] = "********"
|
|
21
57
|
else:
|
|
22
58
|
temp[key] = data[key]
|
|
23
59
|
return temp
|
|
24
60
|
|
|
61
|
+
|
|
25
62
|
def scrub_keys(e: Exception) -> Dict[str, Any]:
|
|
26
63
|
"""
|
|
27
64
|
Scrub the keys of an exception to remove sensitive information.
|
|
@@ -31,18 +68,26 @@ def scrub_keys(e: Exception) -> Dict[str, Any]:
|
|
|
31
68
|
"error": str(e),
|
|
32
69
|
"type": type(e).__name__,
|
|
33
70
|
"message": str(e),
|
|
34
|
-
"args": scrub(getattr(e,
|
|
71
|
+
"args": scrub(getattr(e, "args", None)),
|
|
35
72
|
}
|
|
36
73
|
|
|
37
74
|
|
|
38
75
|
def execute_query(table_id, method, query):
|
|
39
76
|
from polyapi import polyCustom
|
|
40
77
|
from polyapi.poly.client_id import client_id
|
|
78
|
+
|
|
41
79
|
try:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
80
|
+
api_key, base_url = get_api_key_and_url()
|
|
81
|
+
if not base_url:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"PolyAPI Instance URL is not configured, run `python -m polyapi setup`."
|
|
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}"
|
|
46
91
|
response = requests.post(url, json=query, headers=headers)
|
|
47
92
|
response.raise_for_status()
|
|
48
93
|
return response.json()
|
|
@@ -51,14 +96,16 @@ def execute_query(table_id, method, query):
|
|
|
51
96
|
|
|
52
97
|
|
|
53
98
|
def first_result(rsp):
|
|
54
|
-
if isinstance(rsp, dict) and isinstance(rsp.get(
|
|
55
|
-
return rsp[
|
|
99
|
+
if isinstance(rsp, dict) and isinstance(rsp.get("results"), list):
|
|
100
|
+
return rsp["results"][0] if rsp["results"] else None
|
|
56
101
|
return rsp
|
|
57
102
|
|
|
103
|
+
|
|
58
104
|
def delete_one_response(rsp):
|
|
59
|
-
if isinstance(rsp, dict) and isinstance(rsp.get(
|
|
60
|
-
return {
|
|
61
|
-
return {
|
|
105
|
+
if isinstance(rsp, dict) and isinstance(rsp.get("deleted"), int):
|
|
106
|
+
return {"deleted": bool(rsp.get("deleted"))}
|
|
107
|
+
return {"deleted": False}
|
|
108
|
+
|
|
62
109
|
|
|
63
110
|
_key_transform_map = {
|
|
64
111
|
"not_": "not",
|
|
@@ -72,8 +119,7 @@ _key_transform_map = {
|
|
|
72
119
|
def _transform_keys(obj: Any) -> Any:
|
|
73
120
|
if isinstance(obj, dict):
|
|
74
121
|
return {
|
|
75
|
-
_key_transform_map.get(k, k): _transform_keys(v)
|
|
76
|
-
for k, v in obj.items()
|
|
122
|
+
_key_transform_map.get(k, k): _transform_keys(v) for k, v in obj.items()
|
|
77
123
|
}
|
|
78
124
|
|
|
79
125
|
elif isinstance(obj, list):
|
|
@@ -88,13 +134,13 @@ def transform_query(query: dict) -> dict:
|
|
|
88
134
|
return {
|
|
89
135
|
**query,
|
|
90
136
|
"where": _transform_keys(query["where"]) if query["where"] else None,
|
|
91
|
-
"orderBy": query["order_by"] if query["order_by"] else None
|
|
137
|
+
"orderBy": query["order_by"] if query["order_by"] else None,
|
|
92
138
|
}
|
|
93
139
|
|
|
94
140
|
return query
|
|
95
141
|
|
|
96
142
|
|
|
97
|
-
TABI_TABLE_TEMPLATE =
|
|
143
|
+
TABI_TABLE_TEMPLATE = """
|
|
98
144
|
{table_name}Columns = Literal[{table_columns}]
|
|
99
145
|
|
|
100
146
|
|
|
@@ -369,7 +415,7 @@ class {table_name}:{table_description}
|
|
|
369
415
|
query["where"]["id"] = kwargs["id"]
|
|
370
416
|
query.pop("id", None)
|
|
371
417
|
return delete_one_response(execute_query({table_name}.table_id, "delete", transform_query(query)))
|
|
372
|
-
|
|
418
|
+
"""
|
|
373
419
|
|
|
374
420
|
|
|
375
421
|
def _get_column_type_str(name: str, schema: Dict[str, Any], is_required: bool) -> str:
|
|
@@ -377,11 +423,17 @@ def _get_column_type_str(name: str, schema: Dict[str, Any], is_required: bool) -
|
|
|
377
423
|
|
|
378
424
|
col_type = schema.get("type", "object")
|
|
379
425
|
if isinstance(col_type, list):
|
|
380
|
-
subtypes = [
|
|
426
|
+
subtypes = [
|
|
427
|
+
_get_column_type_str(name, {**schema, "type": t}, is_required)
|
|
428
|
+
for t in col_type
|
|
429
|
+
]
|
|
381
430
|
result = f"Union[{', '.join(subtypes)}]"
|
|
382
431
|
elif col_type == "array":
|
|
383
432
|
if isinstance(schema["items"], list):
|
|
384
|
-
subtypes = [
|
|
433
|
+
subtypes = [
|
|
434
|
+
_get_column_type_str(f"{name}{i}", s, True)
|
|
435
|
+
for i, s in enumerate(schema["items"])
|
|
436
|
+
]
|
|
385
437
|
result = f"Tuple[{', '.join(subtypes)}]"
|
|
386
438
|
elif isinstance(schema["items"], dict):
|
|
387
439
|
result = f"List[{_get_column_type_str(name, schema['items'], True)}]"
|
|
@@ -391,7 +443,10 @@ def _get_column_type_str(name: str, schema: Dict[str, Any], is_required: bool) -
|
|
|
391
443
|
if isinstance(schema.get("patternProperties"), dict):
|
|
392
444
|
# TODO: Handle multiple pattern properties
|
|
393
445
|
result = f"Dict[str, {_get_column_type_str(f'{name}_', schema['patternProperties'], True)}]"
|
|
394
|
-
elif
|
|
446
|
+
elif (
|
|
447
|
+
isinstance(schema.get("properties"), dict)
|
|
448
|
+
and len(schema["properties"].values()) > 0
|
|
449
|
+
):
|
|
395
450
|
# TODO: Handle x-poly-refs
|
|
396
451
|
result = f'"{name}"'
|
|
397
452
|
else:
|
|
@@ -413,24 +468,32 @@ def _render_table_row_classes(table_name: str, schema: Dict[str, Any]) -> str:
|
|
|
413
468
|
return output[1].split("\n", 1)[1].strip()
|
|
414
469
|
|
|
415
470
|
|
|
416
|
-
def _render_table_subset_class(
|
|
471
|
+
def _render_table_subset_class(
|
|
472
|
+
table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]
|
|
473
|
+
) -> str:
|
|
417
474
|
# Generate class which can match any subset of a table row
|
|
418
475
|
lines = [f"class {table_name}Subset(TypedDict):"]
|
|
419
476
|
|
|
420
477
|
for name, schema in columns:
|
|
421
|
-
type_str = _get_column_type_str(
|
|
478
|
+
type_str = _get_column_type_str(
|
|
479
|
+
f"_{table_name}Row{name}", schema, name in required
|
|
480
|
+
)
|
|
422
481
|
lines.append(f" {name}: NotRequired[{type_str}]")
|
|
423
482
|
|
|
424
483
|
return "\n".join(lines)
|
|
425
484
|
|
|
426
485
|
|
|
427
|
-
def _render_table_where_class(
|
|
486
|
+
def _render_table_where_class(
|
|
487
|
+
table_name: str, columns: List[Tuple[str, Dict[str, Any]]], required: List[str]
|
|
488
|
+
) -> str:
|
|
428
489
|
# Generate class for the 'where' part of the query
|
|
429
490
|
lines = [f"class {table_name}WhereFilter(TypedDict):"]
|
|
430
491
|
|
|
431
492
|
for name, schema in columns:
|
|
432
493
|
ftype_str = ""
|
|
433
|
-
type_str = _get_column_type_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[]
|
|
434
497
|
is_required = name in required
|
|
435
498
|
if type_str == "bool":
|
|
436
499
|
ftype_str = "BooleanFilter" if is_required else "NullableBooleanFilter"
|
|
@@ -445,24 +508,34 @@ def _render_table_where_class(table_name: str, columns: List[Tuple[str, Dict[str
|
|
|
445
508
|
if ftype_str:
|
|
446
509
|
lines.append(f" {name}: NotRequired[Union[{type_str}, {ftype_str}]]")
|
|
447
510
|
|
|
448
|
-
lines.append(
|
|
511
|
+
lines.append(
|
|
512
|
+
f' AND: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]'
|
|
513
|
+
)
|
|
449
514
|
lines.append(f' OR: NotRequired[List["{table_name}WhereFilter"]]')
|
|
450
|
-
lines.append(
|
|
515
|
+
lines.append(
|
|
516
|
+
f' NOT: NotRequired[Union["{table_name}WhereFilter", List["{table_name}WhereFilter"]]]'
|
|
517
|
+
)
|
|
451
518
|
|
|
452
519
|
return "\n".join(lines)
|
|
453
520
|
|
|
454
521
|
|
|
455
522
|
def _render_table(table: TableSpecDto) -> str:
|
|
456
523
|
columns = list(table["schema"]["properties"].items())
|
|
457
|
-
|
|
524
|
+
required_columns = table["schema"].get("required", [])
|
|
458
525
|
|
|
459
|
-
table_columns = ",".join([
|
|
526
|
+
table_columns = ",".join([f'"{k}"' for k, _ in columns])
|
|
460
527
|
table_row_classes = _render_table_row_classes(table["name"], table["schema"])
|
|
461
|
-
table_row_subset_class = _render_table_subset_class(
|
|
462
|
-
|
|
528
|
+
table_row_subset_class = _render_table_subset_class(
|
|
529
|
+
table["name"], columns, required_columns
|
|
530
|
+
)
|
|
531
|
+
table_where_class = _render_table_where_class(
|
|
532
|
+
table["name"], columns, required_columns
|
|
533
|
+
)
|
|
463
534
|
if table.get("description", ""):
|
|
464
|
-
table_description =
|
|
465
|
-
table_description +=
|
|
535
|
+
table_description = '\n """'
|
|
536
|
+
table_description += "\n ".join(
|
|
537
|
+
table["description"].replace('"', "'").split("\n")
|
|
538
|
+
)
|
|
466
539
|
table_description += '\n """'
|
|
467
540
|
else:
|
|
468
541
|
table_description = ""
|
|
@@ -502,12 +575,7 @@ def _create_table(table: TableSpecDto) -> None:
|
|
|
502
575
|
|
|
503
576
|
init_path = os.path.join(full_path, "__init__.py")
|
|
504
577
|
|
|
505
|
-
imports =
|
|
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
|
-
])
|
|
578
|
+
imports = TABI_MODULE_IMPORTS
|
|
511
579
|
table_contents = _render_table(table)
|
|
512
580
|
|
|
513
581
|
file_contents = ""
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "polyapi-python"
|
|
7
|
-
version = "0.3.13.
|
|
7
|
+
version = "0.3.13.dev3"
|
|
8
8
|
description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
|
|
9
9
|
authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }]
|
|
10
10
|
dependencies = [
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import unittest
|
|
2
|
-
from
|
|
2
|
+
from unittest.mock import Mock, patch
|
|
3
|
+
from polyapi.poly_tables import _render_table, TABI_MODULE_IMPORTS, execute_query
|
|
4
|
+
from polyapi.typedefs import TableSpecDto
|
|
3
5
|
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
def _normalize_type_notation(value: str) -> str:
|
|
8
|
+
# Python/runtime/tooling versions may emit either built-in generic style
|
|
9
|
+
# (dict[str, Any]) or typing style (Dict[str, Any]) for the same schema.
|
|
10
|
+
return value.replace("dict[str, Any]", "Dict[str, Any]")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
TABLE_SPEC_SIMPLE: TableSpecDto = {
|
|
6
14
|
"type": "table",
|
|
7
15
|
"id": "123456789",
|
|
8
16
|
"name": "MyTable",
|
|
@@ -13,24 +21,18 @@ TABLE_SPEC_SIMPLE = {
|
|
|
13
21
|
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
14
22
|
"type": "object",
|
|
15
23
|
"properties": {
|
|
16
|
-
"id": {
|
|
17
|
-
"createdAt": {
|
|
18
|
-
"updatedAt": {
|
|
19
|
-
"name": {
|
|
20
|
-
"age": {
|
|
21
|
-
"active": {
|
|
22
|
-
"optional": {
|
|
24
|
+
"id": {"type": "string"},
|
|
25
|
+
"createdAt": {"type": "string"},
|
|
26
|
+
"updatedAt": {"type": "string"},
|
|
27
|
+
"name": {"type": "string"},
|
|
28
|
+
"age": {"type": "integer"},
|
|
29
|
+
"active": {"type": "boolean"},
|
|
30
|
+
"optional": {"type": "object"},
|
|
23
31
|
},
|
|
24
|
-
"required": [
|
|
25
|
-
"id",
|
|
26
|
-
"createdAt",
|
|
27
|
-
"updatedAt",
|
|
28
|
-
"name",
|
|
29
|
-
"age",
|
|
30
|
-
"active"
|
|
31
|
-
],
|
|
32
|
+
"required": ["id", "createdAt", "updatedAt", "name", "age", "active"],
|
|
32
33
|
"additionalProperties": False,
|
|
33
|
-
}
|
|
34
|
+
},
|
|
35
|
+
"unresolvedPolySchemaRefs": [],
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
EXPECTED_SIMPLE = '''
|
|
@@ -57,7 +59,7 @@ class MyTableRow(TypedDict, total=False):
|
|
|
57
59
|
active: Required[bool]
|
|
58
60
|
""" Required property """
|
|
59
61
|
|
|
60
|
-
optional:
|
|
62
|
+
optional: Dict[str, Any]
|
|
61
63
|
|
|
62
64
|
|
|
63
65
|
|
|
@@ -351,43 +353,40 @@ class MyTable:
|
|
|
351
353
|
return delete_one_response(execute_query(MyTable.table_id, "delete", transform_query(query)))
|
|
352
354
|
'''
|
|
353
355
|
|
|
354
|
-
TABLE_SPEC_COMPLEX = {
|
|
356
|
+
TABLE_SPEC_COMPLEX: TableSpecDto = {
|
|
355
357
|
"type": "table",
|
|
356
358
|
"id": "123456789",
|
|
357
359
|
"name": "MyTable",
|
|
358
360
|
"context": "some.context.here",
|
|
359
361
|
"contextName": "some.context.here.MyTable",
|
|
362
|
+
"description": "",
|
|
360
363
|
"schema": {
|
|
361
364
|
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
362
365
|
"type": "object",
|
|
363
366
|
"properties": {
|
|
364
|
-
"id": {
|
|
365
|
-
"createdAt": {
|
|
366
|
-
"updatedAt": {
|
|
367
|
+
"id": {"type": "string"},
|
|
368
|
+
"createdAt": {"type": "string"},
|
|
369
|
+
"updatedAt": {"type": "string"},
|
|
367
370
|
"data": {
|
|
368
371
|
"type": "object",
|
|
369
372
|
"properties": {
|
|
370
|
-
"foo": {
|
|
373
|
+
"foo": {"type": "string"},
|
|
371
374
|
"nested": {
|
|
372
375
|
"type": "array",
|
|
373
376
|
"items": {
|
|
374
377
|
"type": "object",
|
|
375
|
-
"properties": {
|
|
376
|
-
"required": ["name"]
|
|
377
|
-
}
|
|
378
|
+
"properties": {"name": {"type": "string"}},
|
|
379
|
+
"required": ["name"],
|
|
380
|
+
},
|
|
378
381
|
},
|
|
379
|
-
"other": {
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
+
"other": {"x-poly-ref": {"path": "some.other.Schema"}},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
382
385
|
},
|
|
383
|
-
"required": [
|
|
384
|
-
"id",
|
|
385
|
-
"createdAt",
|
|
386
|
-
"updatedAt",
|
|
387
|
-
"data"
|
|
388
|
-
],
|
|
386
|
+
"required": ["id", "createdAt", "updatedAt", "data"],
|
|
389
387
|
"additionalProperties": False,
|
|
390
|
-
}
|
|
388
|
+
},
|
|
389
|
+
"unresolvedPolySchemaRefs": [],
|
|
391
390
|
}
|
|
392
391
|
|
|
393
392
|
EXPECTED_COMPLEX = '''
|
|
@@ -657,14 +656,61 @@ class MyTable:
|
|
|
657
656
|
return execute_query(MyTable.table_id, "delete", query)
|
|
658
657
|
'''
|
|
659
658
|
|
|
659
|
+
|
|
660
660
|
class T(unittest.TestCase):
|
|
661
661
|
def test_render_simple(self):
|
|
662
662
|
self.maxDiff = 20000
|
|
663
663
|
output = _render_table(TABLE_SPEC_SIMPLE)
|
|
664
|
-
self.assertEqual(
|
|
665
|
-
|
|
664
|
+
self.assertEqual(
|
|
665
|
+
_normalize_type_notation(output),
|
|
666
|
+
_normalize_type_notation(EXPECTED_SIMPLE),
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
def test_execute_query_does_not_return_unhashable_dict_error(self):
|
|
670
|
+
result = execute_query("test-table", "select", {})
|
|
671
|
+
self.assertIsInstance(result, dict)
|
|
672
|
+
self.assertNotIn("unhashable type: 'dict'", str(result))
|
|
673
|
+
|
|
674
|
+
def test_execute_query_uses_absolute_url_and_auth_header(self):
|
|
675
|
+
response = Mock()
|
|
676
|
+
response.raise_for_status.return_value = None
|
|
677
|
+
response.json.return_value = {"ok": True}
|
|
678
|
+
|
|
679
|
+
with (
|
|
680
|
+
patch(
|
|
681
|
+
"polyapi.poly_tables.get_api_key_and_url",
|
|
682
|
+
return_value=("test-api-key", "https://na1.polyapi.io"),
|
|
683
|
+
),
|
|
684
|
+
patch(
|
|
685
|
+
"polyapi.poly_tables.requests.post", return_value=response
|
|
686
|
+
) as post_mock,
|
|
687
|
+
):
|
|
688
|
+
result = execute_query("table-id-123", "select", {"where": {"id": "abc"}})
|
|
689
|
+
|
|
690
|
+
self.assertEqual(result, {"ok": True})
|
|
691
|
+
post_mock.assert_called_once()
|
|
692
|
+
called_url = (
|
|
693
|
+
post_mock.call_args.kwargs["url"]
|
|
694
|
+
if "url" in post_mock.call_args.kwargs
|
|
695
|
+
else post_mock.call_args.args[0]
|
|
696
|
+
)
|
|
697
|
+
called_headers = post_mock.call_args.kwargs["headers"]
|
|
698
|
+
self.assertEqual(
|
|
699
|
+
called_url.split("?")[0],
|
|
700
|
+
"https://na1.polyapi.io/tables/table-id-123/select",
|
|
701
|
+
)
|
|
702
|
+
self.assertEqual(called_headers["Authorization"], "Bearer test-api-key")
|
|
703
|
+
|
|
704
|
+
def test_generated_module_executes_with_delete_one_types(self):
|
|
705
|
+
source = f"{TABI_MODULE_IMPORTS}\n\n\n{_render_table(TABLE_SPEC_SIMPLE)}"
|
|
706
|
+
generated_scope = {}
|
|
707
|
+
exec(source, generated_scope)
|
|
708
|
+
self.assertIn("MyTable", generated_scope)
|
|
709
|
+
self.assertIn("PolyDeleteResult", generated_scope)
|
|
710
|
+
self.assertIn("delete_one_response", generated_scope)
|
|
711
|
+
|
|
666
712
|
@unittest.skip("too brittle, will restore later")
|
|
667
713
|
def test_render_complex(self):
|
|
668
714
|
self.maxDiff = 20000
|
|
669
715
|
output = _render_table(TABLE_SPEC_COMPLEX)
|
|
670
|
-
self.assertEqual(output, EXPECTED_COMPLEX)
|
|
716
|
+
self.assertEqual(output, EXPECTED_COMPLEX)
|
|
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
|
|
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.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{polyapi_python-0.3.13.dev2 → polyapi_python-0.3.13.dev3}/polyapi_python.egg-info/top_level.txt
RENAMED
|
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
|