polyapi-python 0.3.8.dev9__tar.gz → 0.3.9__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 (49) hide show
  1. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/PKG-INFO +4 -21
  2. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/README.md +2 -19
  3. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/auth.py +3 -3
  4. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/deployables.py +13 -10
  5. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/execute.py +19 -5
  6. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/generate.py +71 -20
  7. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/poly_schemas.py +41 -7
  8. polyapi_python-0.3.9/polyapi/poly_tables.py +456 -0
  9. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/prepare.py +5 -3
  10. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/schema.py +16 -2
  11. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/sync.py +10 -5
  12. polyapi_python-0.3.9/polyapi/typedefs.py +200 -0
  13. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/utils.py +6 -5
  14. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/variables.py +1 -4
  15. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/webhook.py +2 -3
  16. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/PKG-INFO +4 -21
  17. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/SOURCES.txt +2 -0
  18. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/requires.txt +2 -2
  19. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/pyproject.toml +3 -3
  20. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_schema.py +16 -2
  21. polyapi_python-0.3.9/tests/test_tabi.py +621 -0
  22. polyapi_python-0.3.8.dev9/polyapi/typedefs.py +0 -93
  23. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/LICENSE +0 -0
  24. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/__init__.py +0 -0
  25. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/__main__.py +0 -0
  26. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/api.py +0 -0
  27. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/cli.py +0 -0
  28. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/client.py +0 -0
  29. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/config.py +0 -0
  30. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/constants.py +0 -0
  31. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/error_handler.py +0 -0
  32. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/exceptions.py +0 -0
  33. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/function_cli.py +0 -0
  34. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/parser.py +0 -0
  35. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/py.typed +0 -0
  36. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/rendered_spec.py +0 -0
  37. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi/server.py +0 -0
  38. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/dependency_links.txt +0 -0
  39. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/polyapi_python.egg-info/top_level.txt +0 -0
  40. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/setup.cfg +0 -0
  41. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_api.py +0 -0
  42. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_auth.py +0 -0
  43. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_deployables.py +0 -0
  44. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_generate.py +0 -0
  45. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_parser.py +0 -0
  46. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_rendered_spec.py +0 -0
  47. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_server.py +0 -0
  48. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/tests/test_utils.py +0 -0
  49. {polyapi_python-0.3.8.dev9 → polyapi_python-0.3.9}/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
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
@@ -32,10 +32,10 @@ Requires-Dist: requests>=2.32.3
32
32
  Requires-Dist: typing_extensions>=4.12.2
33
33
  Requires-Dist: jsonschema-gentypes==2.6.0
34
34
  Requires-Dist: pydantic>=2.8.0
35
- Requires-Dist: stdlib_list==0.10.0
35
+ Requires-Dist: stdlib_list>=0.10.0
36
36
  Requires-Dist: colorama==0.4.4
37
37
  Requires-Dist: python-socketio[asyncio_client]==5.11.1
38
- Requires-Dist: truststore==0.8.0
38
+ Requires-Dist: truststore>=0.8.0
39
39
  Dynamic: license-file
40
40
 
41
41
  # PolyAPI Python Library
@@ -110,24 +110,6 @@ def bar():
110
110
  return "Hello World"
111
111
  ```
112
112
 
113
- ## See Server Function Logs
114
-
115
- In order to see function logs, please first set `logsEnabled` to `true` in Canopy for the function.
116
-
117
- https://na1.polyapi.io/canopy/polyui/collections/server-functions
118
-
119
- Then in your code, get the poly logger and log with it like so:
120
-
121
- ```python
122
- logger = logging.getLogger("poly")
123
- def bar():
124
- logger.warning("I AM THE LOG")
125
- return "Hello World"
126
- ```
127
-
128
- Finally, click the "Show Logs" button to see your server function logs in Canopy!
129
-
130
-
131
113
  ## Complex Types In Server Functions
132
114
 
133
115
  You can define arbitrarily complex argument and return types using TypedDicts.
@@ -206,3 +188,4 @@ Please ignore \[name-defined\] errors for now. This is a known bug we are workin
206
188
  ## Support
207
189
 
208
190
  If you run into any issues or want help getting started with this project, please contact support@polyapi.io
191
+ .
@@ -70,24 +70,6 @@ def bar():
70
70
  return "Hello World"
71
71
  ```
72
72
 
73
- ## See Server Function Logs
74
-
75
- In order to see function logs, please first set `logsEnabled` to `true` in Canopy for the function.
76
-
77
- https://na1.polyapi.io/canopy/polyui/collections/server-functions
78
-
79
- Then in your code, get the poly logger and log with it like so:
80
-
81
- ```python
82
- logger = logging.getLogger("poly")
83
- def bar():
84
- logger.warning("I AM THE LOG")
85
- return "Hello World"
86
- ```
87
-
88
- Finally, click the "Show Logs" button to see your server function logs in Canopy!
89
-
90
-
91
73
  ## Complex Types In Server Functions
92
74
 
93
75
  You can define arbitrarily complex argument and return types using TypedDicts.
@@ -165,4 +147,5 @@ Please ignore \[name-defined\] errors for now. This is a known bug we are workin
165
147
 
166
148
  ## Support
167
149
 
168
- If you run into any issues or want help getting started with this project, please contact support@polyapi.io
150
+ If you run into any issues or want help getting started with this project, please contact support@polyapi.io
151
+ .
@@ -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
- eventsClientId = "{client_id}"
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, client_id=uuid.uuid4().hex)
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":
@@ -1,4 +1,6 @@
1
1
  import os
2
+ import string
3
+ import random
2
4
  import subprocess
3
5
  import json
4
6
  import hashlib
@@ -65,20 +67,21 @@ class SyncDeployment(TypedDict, total=False):
65
67
  context: str
66
68
  name: str
67
69
  description: str
68
- type: str
70
+ type: DeployableTypes
69
71
  fileRevision: str
70
72
  file: str
71
73
  types: DeployableFunctionTypes
72
- typeSchemas: Dict[str, any]
74
+ typeSchemas: Dict[str, Any]
73
75
  dependencies: List[str]
74
- config: Dict[str, any]
76
+ config: Dict[str, Any]
75
77
  instance: str
76
- id: Optional[str] = None
77
- deployed: Optional[str] = None
78
+ id: Optional[str]
79
+ deployed: Optional[str]
80
+
78
81
 
79
82
  DeployableTypeEntries: List[Tuple[DeployableTypeNames, DeployableTypes]] = [
80
- ("PolyServerFunction", "server-function"),
81
- ("PolyClientFunction", "client-function"),
83
+ ("PolyServerFunction", "server-function"), # type: ignore
84
+ ("PolyClientFunction", "client-function"), # type: ignore
82
85
  ]
83
86
 
84
87
  DeployableTypeToName: Dict[DeployableTypeNames, DeployableTypes] = {name: type for name, type in DeployableTypeEntries}
@@ -118,13 +121,13 @@ def get_all_deployable_files_windows(config: PolyDeployConfig) -> List[str]:
118
121
  pattern = ' '.join(f"/C:\"polyConfig: {name}\"" for name in config["type_names"]) or '/C:"polyConfig"'
119
122
 
120
123
  exclude_command = f" | findstr /V /I \"{exclude_pattern}\"" if exclude_pattern else ''
121
- search_command = f" | findstr /S /M /I /F:/ {pattern} *.*"
124
+ search_command = f" | findstr /M /I /F:/ {pattern}"
122
125
 
123
126
  result = []
124
127
  for dir_path in config["include_dirs"]:
125
128
  if dir_path != '.':
126
129
  include_pattern = " ".join(f"{dir_path}*.{f}" for f in config["include_files_or_extensions"]) or "*"
127
- dir_command = f"dir {include_pattern} /S /P /B > NUL"
130
+ dir_command = f"dir {include_pattern} /S /P /B"
128
131
  full_command = f"{dir_command}{exclude_command}{search_command}"
129
132
  try:
130
133
  output = subprocess.check_output(full_command, shell=True, text=True)
@@ -175,7 +178,7 @@ def get_git_revision(branch_or_tag: str = "HEAD") -> str:
175
178
  return check_output(["git", "rev-parse", "--short", branch_or_tag], text=True).strip()
176
179
  except CalledProcessError:
177
180
  # Return a random 7-character hash as a fallback
178
- return "".join(format(ord(c), 'x') for c in os.urandom(4))[:7]
181
+ return "".join([random.choice(string.ascii_letters + string.digits) for _ in range(7)])
179
182
 
180
183
  def get_cache_deployments_revision() -> str:
181
184
  """Retrieve the cache deployments revision from a file."""
@@ -1,9 +1,13 @@
1
1
  from typing import Dict, Optional
2
2
  import requests
3
+ import os
4
+ import logging
3
5
  from requests import Response
4
6
  from polyapi.config import get_api_key_and_url, get_mtls_config
5
7
  from polyapi.exceptions import PolyApiException
6
8
 
9
+ logger = logging.getLogger("poly")
10
+
7
11
  def direct_execute(function_type, function_id, data) -> Response:
8
12
  """ execute a specific function id/type
9
13
  """
@@ -13,7 +17,11 @@ def direct_execute(function_type, function_id, data) -> Response:
13
17
 
14
18
  endpoint_info = requests.post(url, json=data, headers=headers)
15
19
  if endpoint_info.status_code < 200 or endpoint_info.status_code >= 300:
16
- raise PolyApiException(f"{endpoint_info.status_code}: {endpoint_info.content.decode('utf-8', errors='ignore')}")
20
+ error_content = endpoint_info.content.decode("utf-8", errors="ignore")
21
+ if function_type == 'api' and os.getenv("LOGS_ENABLED"):
22
+ raise PolyApiException(f"Error executing api function with id: {function_id}. Status code: {endpoint_info.status_code}. Request data: {data}, Response: {error_content}")
23
+ elif function_type != 'api':
24
+ raise PolyApiException(f"{endpoint_info.status_code}: {error_content}")
17
25
 
18
26
  endpoint_info_data = endpoint_info.json()
19
27
  request_params = endpoint_info_data.copy()
@@ -38,9 +46,12 @@ def direct_execute(function_type, function_id, data) -> Response:
38
46
  **request_params
39
47
  )
40
48
 
41
- if resp.status_code < 200 or resp.status_code >= 300:
49
+ if (resp.status_code < 200 or resp.status_code >= 300):
42
50
  error_content = resp.content.decode("utf-8", errors="ignore")
43
- raise PolyApiException(f"{resp.status_code}: {error_content}")
51
+ if function_type == 'api' and os.getenv("LOGS_ENABLED"):
52
+ logger.error(f"Error executing api function with id: {function_id}. Status code: {resp.status_code}. Request data: {data}, Response: {error_content}")
53
+ elif function_type != 'api':
54
+ raise PolyApiException(f"{resp.status_code}: {error_content}")
44
55
 
45
56
  return resp
46
57
 
@@ -59,9 +70,12 @@ def execute(function_type, function_id, data) -> Response:
59
70
  headers=headers,
60
71
  )
61
72
 
62
- if resp.status_code < 200 or resp.status_code >= 300:
73
+ if (resp.status_code < 200 or resp.status_code >= 300) and os.getenv("LOGS_ENABLED"):
63
74
  error_content = resp.content.decode("utf-8", errors="ignore")
64
- raise PolyApiException(f"{resp.status_code}: {error_content}")
75
+ if function_type == 'api' and os.getenv("LOGS_ENABLED"):
76
+ logger.error(f"Error executing api function with id: {function_id}. Status code: {resp.status_code}. Request data: {data}, Response: {error_content}")
77
+ elif function_type != 'api':
78
+ raise PolyApiException(f"{resp.status_code}: {error_content}")
65
79
 
66
80
  return resp
67
81
 
@@ -1,9 +1,12 @@
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
8
+
9
+ from copy import deepcopy
7
10
  from typing import Any, List, Optional, Tuple, cast
8
11
 
9
12
  from .auth import render_auth_function
@@ -11,11 +14,12 @@ from .client import render_client_function
11
14
  from .poly_schemas import generate_schemas
12
15
  from .webhook import render_webhook_handle
13
16
 
14
- from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto
17
+ from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto, TableSpecDto
15
18
  from .api import render_api_function
16
19
  from .server import render_server_function
17
20
  from .utils import add_import_to_init, get_auth_headers, init_the_init, print_green, to_func_namespace
18
21
  from .variables import generate_variables
22
+ from .poly_tables import generate_tables
19
23
  from .config import get_api_key_and_url, get_direct_execute_config, get_cached_generate_args
20
24
 
21
25
  SUPPORTED_FUNCTION_TYPES = {
@@ -26,7 +30,7 @@ SUPPORTED_FUNCTION_TYPES = {
26
30
  "webhookHandle",
27
31
  }
28
32
 
29
- SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable", "schema", "snippet"}
33
+ SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable", "schema", "snippet", "table"}
30
34
 
31
35
 
32
36
  X_POLY_REF_WARNING = '''"""
@@ -136,19 +140,22 @@ def parse_function_specs(
136
140
  limit_ids: List[str] | None = None, # optional list of ids to limit to
137
141
  ) -> List[SpecificationDto]:
138
142
  functions = []
139
- for spec in specs:
140
- if not spec:
143
+ for raw_spec in specs:
144
+ if not raw_spec:
141
145
  continue
142
146
 
143
147
  # For no_types mode, we might not have function data, but we still want to include the spec
144
148
  # if it's a supported function type
145
- if spec["type"] not in SUPPORTED_FUNCTION_TYPES:
149
+ if raw_spec["type"] not in SUPPORTED_FUNCTION_TYPES:
146
150
  continue
147
151
 
148
152
  # Skip if we have a limit and this spec is not in it
149
- if limit_ids and spec.get("id") not in limit_ids:
153
+ if limit_ids and raw_spec.get("id") not in limit_ids:
150
154
  continue
151
155
 
156
+ # Should really be fixed in specs api, but for now handle json strings in arg schemas
157
+ spec = normalize_args_schema(raw_spec)
158
+
152
159
  # For customFunction, check language if we have function data
153
160
  if spec["type"] == "customFunction":
154
161
  if spec.get("language") and spec["language"] != "python":
@@ -190,16 +197,18 @@ def read_cached_specs() -> List[SpecificationDto]:
190
197
  return json.loads(f.read())
191
198
 
192
199
 
193
- def get_variables() -> List[VariableSpecDto]:
194
- specs = read_cached_specs()
200
+ def get_variables(specs: List[SpecificationDto]) -> List[VariableSpecDto]:
195
201
  return [cast(VariableSpecDto, spec) for spec in specs if spec["type"] == "serverVariable"]
196
202
 
197
203
 
198
- def get_schemas() -> List[SchemaSpecDto]:
199
- specs = read_cached_specs()
204
+ def get_schemas(specs: List[SpecificationDto]) -> List[SchemaSpecDto]:
200
205
  return [cast(SchemaSpecDto, spec) for spec in specs if spec["type"] == "schema"]
201
206
 
202
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
+
203
212
  def remove_old_library():
204
213
  currdir = os.path.dirname(os.path.abspath(__file__))
205
214
  path = os.path.join(currdir, "poly")
@@ -214,6 +223,10 @@ def remove_old_library():
214
223
  if os.path.exists(path):
215
224
  shutil.rmtree(path)
216
225
 
226
+ path = os.path.join(currdir, "tabi")
227
+ if os.path.exists(path):
228
+ shutil.rmtree(path)
229
+
217
230
 
218
231
  def create_empty_schemas_module():
219
232
  """Create an empty schemas module for no-types mode so user code can still import from polyapi.schemas"""
@@ -272,6 +285,14 @@ sys.modules[__name__] = _SchemaModule()
272
285
  ''')
273
286
 
274
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
+
275
296
  def generate_from_cache() -> None:
276
297
  """
277
298
  Generate using cached values after non-explicit call.
@@ -286,6 +307,37 @@ def generate_from_cache() -> None:
286
307
  )
287
308
 
288
309
 
310
+ def _parse_arg_schema(value: Any) -> Any:
311
+ if isinstance(value, str):
312
+ text = value.strip()
313
+ if text and text[0] in "{[":
314
+ try:
315
+ return json.loads(text)
316
+ except json.JSONDecodeError:
317
+ logging.warning("Could not parse function argument schema (leaving as str): %s", text[:200])
318
+ return value
319
+
320
+
321
+ def normalize_args_schema(
322
+ raw_spec: SpecificationDto
323
+ ) -> SpecificationDto:
324
+ spec = deepcopy(raw_spec)
325
+
326
+ function_block = spec.get("function")
327
+ if not isinstance(function_block, dict):
328
+ return spec
329
+ arguments_block = function_block.get("arguments")
330
+ if not isinstance(arguments_block, list):
331
+ return spec
332
+
333
+ for argument in arguments_block:
334
+ arg_type = argument.get("type")
335
+ if isinstance(arg_type, dict) and "schema" in arg_type:
336
+ arg_type["schema"] = _parse_arg_schema(arg_type["schema"])
337
+
338
+ return spec
339
+
340
+
289
341
  def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] = None, function_ids: Optional[List[str]] = None, no_types: bool = False) -> None:
290
342
  generate_msg = f"Generating Poly Python SDK for contexts ${contexts}..." if contexts else "Generating Poly Python SDK..."
291
343
  print(generate_msg, end="", flush=True)
@@ -297,9 +349,11 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
297
349
  limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
298
350
  functions = parse_function_specs(specs, limit_ids=limit_ids)
299
351
 
352
+ _generate_client_id()
353
+
300
354
  # Only process schemas if no_types is False
301
355
  if not no_types:
302
- schemas = get_schemas()
356
+ schemas = get_schemas(specs)
303
357
  schema_index = build_schema_index(schemas)
304
358
  if schemas:
305
359
  schema_limit_ids: List[str] = [] # useful for narrowing down generation to a single function to debug
@@ -323,7 +377,11 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
323
377
  )
324
378
  exit()
325
379
 
326
- variables = get_variables()
380
+ tables = get_tables(specs)
381
+ if tables:
382
+ generate_tables(tables)
383
+
384
+ variables = get_variables(specs)
327
385
  if variables:
328
386
  generate_variables(variables)
329
387
 
@@ -335,14 +393,7 @@ def generate(contexts: Optional[List[str]] = None, names: Optional[List[str]] =
335
393
 
336
394
 
337
395
  def clear() -> None:
338
- base = os.path.dirname(os.path.abspath(__file__))
339
- poly_path = os.path.join(base, "poly")
340
- if os.path.exists(poly_path):
341
- shutil.rmtree(poly_path)
342
-
343
- vari_path = os.path.join(base, "vari")
344
- if os.path.exists(vari_path):
345
- shutil.rmtree(vari_path)
396
+ remove_old_library()
346
397
  print("Cleared!")
347
398
 
348
399
 
@@ -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