polyapi-python 0.3.8.dev9__tar.gz → 0.3.9.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.8.dev9 → polyapi_python-0.3.9.dev1}/PKG-INFO +2 -1
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/README.md +2 -1
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/poly_schemas.py +41 -7
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/schema.py +16 -2
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/PKG-INFO +2 -1
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/pyproject.toml +1 -1
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_schema.py +16 -2
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/LICENSE +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/__init__.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/__main__.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/api.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/auth.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/cli.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/client.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/config.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/constants.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/deployables.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/error_handler.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/exceptions.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/execute.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/function_cli.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/generate.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/parser.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/prepare.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/py.typed +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/rendered_spec.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/server.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/sync.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/typedefs.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/utils.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/variables.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/webhook.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/SOURCES.txt +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/dependency_links.txt +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/requires.txt +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/top_level.txt +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/setup.cfg +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_api.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_auth.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_deployables.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_generate.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_parser.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_rendered_spec.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_server.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_utils.py +0 -0
- {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_variables.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9.dev1
|
|
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
|
|
@@ -206,3 +206,4 @@ Please ignore \[name-defined\] errors for now. This is a known bug we are workin
|
|
|
206
206
|
## Support
|
|
207
207
|
|
|
208
208
|
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
209
|
+
.
|
|
@@ -165,4 +165,5 @@ Please ignore \[name-defined\] errors for now. This is a known bug we are workin
|
|
|
165
165
|
|
|
166
166
|
## Support
|
|
167
167
|
|
|
168
|
-
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
168
|
+
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
169
|
+
.
|
|
@@ -25,11 +25,13 @@ FALLBACK_SPEC_TEMPLATE = """class {name}(TypedDict, total=False):
|
|
|
25
25
|
|
|
26
26
|
def generate_schemas(specs: List[SchemaSpecDto], limit_ids: List[str] = None):
|
|
27
27
|
failed_schemas = []
|
|
28
|
+
successful_schemas = []
|
|
28
29
|
if limit_ids:
|
|
29
30
|
for spec in specs:
|
|
30
31
|
if spec["id"] in limit_ids:
|
|
31
32
|
try:
|
|
32
33
|
create_schema(spec)
|
|
34
|
+
successful_schemas.append(f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}")
|
|
33
35
|
except Exception as e:
|
|
34
36
|
schema_path = f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}"
|
|
35
37
|
schema_id = spec.get('id', 'unknown')
|
|
@@ -40,6 +42,7 @@ def generate_schemas(specs: List[SchemaSpecDto], limit_ids: List[str] = None):
|
|
|
40
42
|
for spec in specs:
|
|
41
43
|
try:
|
|
42
44
|
create_schema(spec)
|
|
45
|
+
successful_schemas.append(f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}")
|
|
43
46
|
except Exception as e:
|
|
44
47
|
schema_path = f"{spec.get('context', 'unknown')}.{spec.get('name', 'unknown')}"
|
|
45
48
|
schema_id = spec.get('id', 'unknown')
|
|
@@ -51,6 +54,37 @@ def generate_schemas(specs: List[SchemaSpecDto], limit_ids: List[str] = None):
|
|
|
51
54
|
logging.warning(f"WARNING: {len(failed_schemas)} schema(s) failed to generate:")
|
|
52
55
|
for failed_schema in failed_schemas:
|
|
53
56
|
logging.warning(f" - {failed_schema}")
|
|
57
|
+
logging.warning(f"Successfully generated {len(successful_schemas)} schema(s)")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_schema_content(schema_content: str, schema_name: str) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Validate that the schema content is meaningful and not just imports.
|
|
63
|
+
Returns True if the schema is valid, False otherwise.
|
|
64
|
+
"""
|
|
65
|
+
if not schema_content or not schema_content.strip():
|
|
66
|
+
logging.debug(f"Schema {schema_name} failed validation: Empty content")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
lines = schema_content.strip().split('\n')
|
|
70
|
+
|
|
71
|
+
# Check if the content has any actual class definitions or type aliases
|
|
72
|
+
has_class_definition = any(line.strip().startswith('class ') for line in lines)
|
|
73
|
+
has_type_alias = any(schema_name in line and '=' in line and not line.strip().startswith('#') for line in lines)
|
|
74
|
+
|
|
75
|
+
# Check if it's essentially just imports (less than 5 lines and no meaningful definitions)
|
|
76
|
+
meaningful_lines = [line for line in lines if line.strip() and not line.strip().startswith('from ') and not line.strip().startswith('import ') and not line.strip().startswith('#')]
|
|
77
|
+
|
|
78
|
+
# Enhanced logging for debugging
|
|
79
|
+
if not (has_class_definition or has_type_alias) or len(meaningful_lines) < 1:
|
|
80
|
+
# Determine the specific reason for failure
|
|
81
|
+
if len(meaningful_lines) == 0:
|
|
82
|
+
logging.debug(f"Schema {schema_name} failed validation: No meaningful content (only imports) - likely empty object or unresolved reference")
|
|
83
|
+
elif not has_class_definition and not has_type_alias:
|
|
84
|
+
logging.debug(f"Schema {schema_name} failed validation: No class definition or type alias found")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
return True
|
|
54
88
|
|
|
55
89
|
|
|
56
90
|
def add_schema_file(
|
|
@@ -75,9 +109,9 @@ def add_schema_file(
|
|
|
75
109
|
|
|
76
110
|
schema_defs = render_poly_schema(spec)
|
|
77
111
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
raise Exception("Schema rendering failed
|
|
112
|
+
# Validate schema content before proceeding
|
|
113
|
+
if not validate_schema_content(schema_defs, schema_name):
|
|
114
|
+
raise Exception(f"Schema rendering failed or produced invalid content for {schema_name}")
|
|
81
115
|
|
|
82
116
|
# Prepare all content first before writing any files
|
|
83
117
|
schema_namespace = to_func_namespace(schema_name)
|
|
@@ -87,7 +121,7 @@ def add_schema_file(
|
|
|
87
121
|
# Read current __init__.py content if it exists
|
|
88
122
|
init_content = ""
|
|
89
123
|
if os.path.exists(init_path):
|
|
90
|
-
with open(init_path, "r") as f:
|
|
124
|
+
with open(init_path, "r", encoding='utf-8') as f:
|
|
91
125
|
init_content = f.read()
|
|
92
126
|
|
|
93
127
|
# Prepare new content to append to __init__.py
|
|
@@ -95,12 +129,12 @@ def add_schema_file(
|
|
|
95
129
|
|
|
96
130
|
# Use temporary files for atomic writes
|
|
97
131
|
# Write to __init__.py atomically
|
|
98
|
-
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp") as temp_init:
|
|
132
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp", encoding='utf-8') as temp_init:
|
|
99
133
|
temp_init.write(new_init_content)
|
|
100
134
|
temp_init_path = temp_init.name
|
|
101
135
|
|
|
102
136
|
# Write to schema file atomically
|
|
103
|
-
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp") as temp_schema:
|
|
137
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False, dir=full_path, suffix=".tmp", encoding='utf-8') as temp_schema:
|
|
104
138
|
temp_schema.write(schema_defs)
|
|
105
139
|
temp_schema_path = temp_schema.name
|
|
106
140
|
|
|
@@ -171,7 +205,7 @@ def create_schema(
|
|
|
171
205
|
def add_schema_to_init(full_path: str, spec: SchemaSpecDto):
|
|
172
206
|
init_the_init(full_path, code_imports="")
|
|
173
207
|
init_path = os.path.join(full_path, "__init__.py")
|
|
174
|
-
with open(init_path, "a") as f:
|
|
208
|
+
with open(init_path, "a", encoding='utf-8') as f:
|
|
175
209
|
f.write(render_poly_schema(spec) + "\n\n")
|
|
176
210
|
|
|
177
211
|
|
|
@@ -93,7 +93,7 @@ def generate_schema_types(input_data: Dict, root=None):
|
|
|
93
93
|
with contextlib.redirect_stdout(None):
|
|
94
94
|
process_config(config, [tmp_input])
|
|
95
95
|
|
|
96
|
-
with open(tmp_output) as f:
|
|
96
|
+
with open(tmp_output, encoding='utf-8') as f:
|
|
97
97
|
output = f.read()
|
|
98
98
|
|
|
99
99
|
output = clean_malformed_examples(output)
|
|
@@ -104,12 +104,26 @@ def generate_schema_types(input_data: Dict, root=None):
|
|
|
104
104
|
# Regex to match everything between "# example: {\n" and "^}$"
|
|
105
105
|
MALFORMED_EXAMPLES_PATTERN = re.compile(r"# example: \{\n.*?^\}$", flags=re.DOTALL | re.MULTILINE)
|
|
106
106
|
|
|
107
|
+
# Regex to fix invalid escape sequences in docstrings
|
|
108
|
+
INVALID_ESCAPE_PATTERNS = [
|
|
109
|
+
# Fix "\ " (backslash space) which is not a valid escape sequence
|
|
110
|
+
(re.compile(r'\\(\s)', re.DOTALL), r'\1'),
|
|
111
|
+
# Fix other common invalid escape sequences in docstrings
|
|
112
|
+
(re.compile(r'\\([^nrtbfav"\'\\])', re.DOTALL), r'\\\\\1'),
|
|
113
|
+
]
|
|
114
|
+
|
|
107
115
|
|
|
108
116
|
def clean_malformed_examples(example: str) -> str:
|
|
109
117
|
""" there is a bug in the `jsonschmea_gentypes` library where if an example from a jsonchema is an object,
|
|
110
|
-
it will break the code because the object won't be properly commented out
|
|
118
|
+
it will break the code because the object won't be properly commented out. Also fixes invalid escape sequences.
|
|
111
119
|
"""
|
|
120
|
+
# Remove malformed examples
|
|
112
121
|
cleaned_example = MALFORMED_EXAMPLES_PATTERN.sub("", example)
|
|
122
|
+
|
|
123
|
+
# Fix invalid escape sequences in docstrings
|
|
124
|
+
for pattern, replacement in INVALID_ESCAPE_PATTERNS:
|
|
125
|
+
cleaned_example = pattern.sub(replacement, cleaned_example)
|
|
126
|
+
|
|
113
127
|
return cleaned_example
|
|
114
128
|
|
|
115
129
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyapi-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9.dev1
|
|
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
|
|
@@ -206,3 +206,4 @@ Please ignore \[name-defined\] errors for now. This is a known bug we are workin
|
|
|
206
206
|
## Support
|
|
207
207
|
|
|
208
208
|
If you run into any issues or want help getting started with this project, please contact support@polyapi.io
|
|
209
|
+
.
|
|
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.2", "wheel"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "polyapi-python"
|
|
6
|
-
version = "0.3.
|
|
6
|
+
version = "0.3.9.dev1"
|
|
7
7
|
description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
|
|
8
8
|
authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }]
|
|
9
9
|
dependencies = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import unittest
|
|
2
|
-
from polyapi.schema import clean_malformed_examples, wrapped_generate_schema_types
|
|
2
|
+
from polyapi.schema import clean_malformed_examples, wrapped_generate_schema_types, generate_schema_types
|
|
3
3
|
|
|
4
4
|
SCHEMA = {
|
|
5
5
|
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
@@ -10,6 +10,14 @@ SCHEMA = {
|
|
|
10
10
|
"definitions": {},
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
CHARACTER_SCHEMA = {
|
|
14
|
+
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {"CHARACTER_SCHEMA_NAME": {"description": "This is — “bad”, right?", "type": "string"}},
|
|
17
|
+
"additionalProperties": False,
|
|
18
|
+
"definitions": {},
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
APALEO_MALFORMED_EXAMPLE = 'from typing import List, TypedDict, Union\nfrom typing_extensions import Required\n\n\n# Body.\n# \n# example: {\n "from": "2024-04-21",\n "to": "2024-04-24",\n "grossDailyRate": {\n "amount": 160.0,\n "currency": "EUR"\n },\n "timeSlices": [\n {\n "blockedUnits": 3\n },\n {\n "blockedUnits": 0\n },\n {\n "blockedUnits": 7\n }\n ]\n}\n# x-readme-ref-name: ReplaceBlockModel\nBody = TypedDict(\'Body\', {\n # Start date and time from which the inventory will be blockedSpecify either a pure date or a date and time (without fractional second part) in UTC or with UTC offset as defined in <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO8601:2004</a>\n # \n # Required property\n \'from\': Required[str],\n # End date and time until which the inventory will be blocked. Cannot be more than 5 years after the start date.Specify either a pure date or a date and time (without fractional second part) in UTC or with UTC offset as defined in <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO8601:2004</a>\n # \n # Required property\n \'to\': Required[str],\n # x-readme-ref-name: MonetaryValueModel\n # \n # Required property\n \'grossDailyRate\': Required["_BodygrossDailyRate"],\n # The list of time slices\n # \n # Required property\n \'timeSlices\': Required[List["_BodytimeSlicesitem"]],\n}, total=False)\n\n\nclass _BodygrossDailyRate(TypedDict, total=False):\n """ x-readme-ref-name: MonetaryValueModel """\n\n amount: Required[Union[int, float]]\n """\n format: double\n\n Required property\n """\n\n currency: Required[str]\n """ Required property """\n\n\n\nclass _BodytimeSlicesitem(TypedDict, total=False):\n """ x-readme-ref-name: CreateBlockTimeSliceModel """\n\n blockedUnits: Required[Union[int, float]]\n """\n Number of units blocked for the time slice\n\n format: int32\n\n Required property\n """\n\n'
|
|
14
22
|
|
|
15
23
|
|
|
@@ -23,4 +31,10 @@ class T(unittest.TestCase):
|
|
|
23
31
|
|
|
24
32
|
def test_clean_malformed_examples(self):
|
|
25
33
|
output = clean_malformed_examples(APALEO_MALFORMED_EXAMPLE)
|
|
26
|
-
self.assertNotIn("# example: {", output)
|
|
34
|
+
self.assertNotIn("# example: {", output)
|
|
35
|
+
|
|
36
|
+
def test_character_encoding(self):
|
|
37
|
+
output = generate_schema_types(CHARACTER_SCHEMA, "Dict")
|
|
38
|
+
expected = 'from typing import TypedDict\n\n\nclass Dict(TypedDict, total=False):\n CHARACTER_SCHEMA_NAME: str\n """ This is — “bad”, right? """\n\n'
|
|
39
|
+
self.assertEqual(output, expected)
|
|
40
|
+
|
|
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.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/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
|