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.
Files changed (46) hide show
  1. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/PKG-INFO +2 -1
  2. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/README.md +2 -1
  3. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/poly_schemas.py +41 -7
  4. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/schema.py +16 -2
  5. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/PKG-INFO +2 -1
  6. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/pyproject.toml +1 -1
  7. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_schema.py +16 -2
  8. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/LICENSE +0 -0
  9. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/__init__.py +0 -0
  10. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/__main__.py +0 -0
  11. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/api.py +0 -0
  12. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/auth.py +0 -0
  13. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/cli.py +0 -0
  14. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/client.py +0 -0
  15. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/config.py +0 -0
  16. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/constants.py +0 -0
  17. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/deployables.py +0 -0
  18. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/error_handler.py +0 -0
  19. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/exceptions.py +0 -0
  20. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/execute.py +0 -0
  21. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/function_cli.py +0 -0
  22. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/generate.py +0 -0
  23. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/parser.py +0 -0
  24. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/prepare.py +0 -0
  25. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/py.typed +0 -0
  26. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/rendered_spec.py +0 -0
  27. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/server.py +0 -0
  28. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/sync.py +0 -0
  29. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/typedefs.py +0 -0
  30. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/utils.py +0 -0
  31. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/variables.py +0 -0
  32. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi/webhook.py +0 -0
  33. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/SOURCES.txt +0 -0
  34. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/dependency_links.txt +0 -0
  35. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/requires.txt +0 -0
  36. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/polyapi_python.egg-info/top_level.txt +0 -0
  37. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/setup.cfg +0 -0
  38. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_api.py +0 -0
  39. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_auth.py +0 -0
  40. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_deployables.py +0 -0
  41. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_generate.py +0 -0
  42. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_parser.py +0 -0
  43. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_rendered_spec.py +0 -0
  44. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_server.py +0 -0
  45. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9.dev1}/tests/test_utils.py +0 -0
  46. {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.8.dev9
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
- if not schema_defs:
79
- # If render_poly_schema failed and returned empty string, don't create any files
80
- raise Exception("Schema rendering failed - empty schema content returned")
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.8.dev9
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.8.dev9"
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
+