polyapi-python 0.3.19.dev0__tar.gz → 0.3.19.dev2__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 (54) hide show
  1. {polyapi_python-0.3.19.dev0/polyapi_python.egg-info → polyapi_python-0.3.19.dev2}/PKG-INFO +4 -4
  2. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/config.py +0 -3
  3. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/execute.py +8 -10
  4. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/generate.py +30 -19
  5. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/http_client.py +1 -1
  6. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/parser.py +29 -8
  7. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/poly_tables.py +4 -4
  8. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/schema.py +25 -11
  9. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/utils.py +10 -7
  10. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2/polyapi_python.egg-info}/PKG-INFO +4 -4
  11. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi_python.egg-info/requires.txt +3 -3
  12. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/pyproject.toml +4 -4
  13. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_server.py +5 -1
  14. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/LICENSE +0 -0
  15. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/README.md +0 -0
  16. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/__init__.py +0 -0
  17. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/__main__.py +0 -0
  18. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/api.py +0 -0
  19. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/auth.py +0 -0
  20. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/cli.py +0 -0
  21. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/cli_constants.py +0 -0
  22. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/client.py +0 -0
  23. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/constants.py +0 -0
  24. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/deployables.py +0 -0
  25. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/error_handler.py +0 -0
  26. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/exceptions.py +0 -0
  27. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/function_cli.py +0 -0
  28. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/poly_schemas.py +0 -0
  29. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/prepare.py +0 -0
  30. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/py.typed +0 -0
  31. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/rendered_spec.py +0 -0
  32. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/server.py +0 -0
  33. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/sync.py +0 -0
  34. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/typedefs.py +0 -0
  35. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/variables.py +0 -0
  36. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi/webhook.py +0 -0
  37. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi_python.egg-info/SOURCES.txt +0 -0
  38. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi_python.egg-info/dependency_links.txt +0 -0
  39. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/polyapi_python.egg-info/top_level.txt +0 -0
  40. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/setup.cfg +0 -0
  41. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_api.py +0 -0
  42. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_async_proof.py +0 -0
  43. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_auth.py +0 -0
  44. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_dependency_pins.py +0 -0
  45. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_deployables.py +0 -0
  46. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_generate.py +0 -0
  47. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_parser.py +0 -0
  48. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_poly_custom.py +0 -0
  49. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_poly_schemas.py +0 -0
  50. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_rendered_spec.py +0 -0
  51. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_schema.py +0 -0
  52. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_tabi.py +0 -0
  53. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/tests/test_utils.py +0 -0
  54. {polyapi_python-0.3.19.dev0 → polyapi_python-0.3.19.dev2}/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.19.dev0
3
+ Version: 0.3.19.dev2
4
4
  Summary: The Python Client for PolyAPI, the IPaaS by Developers for Developers
5
5
  Author-email: PolyAPI <support@polyapi.io>
6
6
  License: MIT License
@@ -29,9 +29,9 @@ Requires-Python: >=3.10
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: requests==2.32.3
32
- Requires-Dist: typing_extensions==4.12.2
33
- Requires-Dist: jsonschema-gentypes==2.10.0
34
- Requires-Dist: pydantic==2.8.0
32
+ Requires-Dist: typing_extensions==4.15.0
33
+ Requires-Dist: jsonschema-gentypes==2.13.0
34
+ Requires-Dist: pydantic==2.13.4
35
35
  Requires-Dist: stdlib_list==0.11.1
36
36
  Requires-Dist: colorama==0.4.4
37
37
  Requires-Dist: python-socketio[asyncio_client]==5.11.1
@@ -135,7 +135,6 @@ def clear_config():
135
135
 
136
136
  def get_mtls_config() -> Tuple[bool, str | None, str | None, str | None]:
137
137
  """Return MTLS configuration settings"""
138
- global MTLS_CERT_PATH, MTLS_KEY_PATH, MTLS_CA_PATH
139
138
  if MTLS_CERT_PATH is None or MTLS_KEY_PATH is None or MTLS_CA_PATH is None:
140
139
  # Force a config read if values aren't cached
141
140
  get_api_key_and_url()
@@ -144,7 +143,6 @@ def get_mtls_config() -> Tuple[bool, str | None, str | None, str | None]:
144
143
 
145
144
  def get_direct_execute_config() -> bool:
146
145
  """Return whether direct execute is enabled"""
147
- global API_FUNCTION_DIRECT_EXECUTE
148
146
  if API_FUNCTION_DIRECT_EXECUTE is None:
149
147
  # Force a config read if value isn't cached
150
148
  get_api_key_and_url()
@@ -153,7 +151,6 @@ def get_direct_execute_config() -> bool:
153
151
 
154
152
  def get_cached_generate_args() -> Tuple[list | None, list | None, list | None, bool]:
155
153
  """Return cached generate command arguments"""
156
- global LAST_GENERATE_CONTEXTS, LAST_GENERATE_NAMES, LAST_GENERATE_IDS, LAST_GENERATE_NO_TYPES
157
154
  if LAST_GENERATE_CONTEXTS is None and LAST_GENERATE_NAMES is None and LAST_GENERATE_IDS is None and LAST_GENERATE_NO_TYPES is None:
158
155
  # Force a config read if values aren't cached
159
156
  get_api_key_and_url()
@@ -1,6 +1,7 @@
1
1
  import httpx
2
2
  import os
3
3
  import logging
4
+ from typing import Any
4
5
  from polyapi.config import get_api_key_and_url, get_mtls_config
5
6
  from polyapi.exceptions import PolyApiException
6
7
  from polyapi import http_client
@@ -49,14 +50,10 @@ def _sync_direct_execute(function_type, function_id, data) -> httpx.Response:
49
50
  # Direct-execute hits URL that may need custom TLS
50
51
  # settings (mTLS certs or disabled verification). httpx Client.request()
51
52
  # doesn't accept per-request transport kwargs, so use one-off calls.
52
- if has_mtls:
53
- resp = httpx.request(
54
- url=endpoint_info_data["url"],
55
- cert=(cert_path, key_path),
56
- verify=ca_path,
57
- timeout=None,
58
- **request_params
59
- )
53
+ if has_mtls and cert_path and key_path:
54
+ verify_value: bool | str = ca_path if ca_path else True
55
+ with httpx.Client(cert=(cert_path, key_path), verify=verify_value, timeout=None) as client:
56
+ resp = client.request(url=endpoint_info_data["url"], **request_params)
60
57
  else:
61
58
  resp = httpx.request(
62
59
  url=endpoint_info_data["url"],
@@ -83,9 +80,10 @@ async def _async_direct_execute(function_type, function_id, data) -> httpx.Respo
83
80
  has_mtls, cert_path, key_path, ca_path = get_mtls_config()
84
81
 
85
82
  # One-off async client for custom TLS settings on external URLs.
86
- if has_mtls:
83
+ if has_mtls and cert_path and key_path:
84
+ verify_value: bool | str = ca_path if ca_path else True
87
85
  async with httpx.AsyncClient(
88
- cert=(cert_path, key_path), verify=ca_path, timeout=None
86
+ cert=(cert_path, key_path), verify=verify_value, timeout=None
89
87
  ) as client:
90
88
  resp = await client.request(
91
89
  url=endpoint_info_data["url"], **request_params
@@ -241,6 +241,9 @@ def remove_old_library():
241
241
 
242
242
  def create_empty_schemas_module():
243
243
  """Create an empty schemas module for no-types mode so user code can still import from polyapi.schemas"""
244
+ import importlib
245
+ import sys
246
+
244
247
  currdir = os.path.dirname(os.path.abspath(__file__))
245
248
  schemas_path = os.path.join(currdir, "schemas")
246
249
 
@@ -251,50 +254,57 @@ def create_empty_schemas_module():
251
254
  # Create an __init__.py file with dynamic schema resolution
252
255
  init_path = os.path.join(schemas_path, "__init__.py")
253
256
  with open(init_path, "w") as f:
254
- f.write('''"""Empty schemas module for no-types mode"""
257
+ f.write('''"""Empty schemas module for no-types mode."""
255
258
  from typing import Any, Dict
256
259
 
257
260
  class _GenericSchema(Dict[str, Any]):
258
261
  """Generic schema type that acts like a Dict for no-types mode"""
262
+
259
263
  def __init__(self, *args, **kwargs):
260
264
  super().__init__(*args, **kwargs)
261
265
 
262
266
  class _SchemaModule:
263
- """Dynamic module that returns itself for attribute access, allowing infinite nesting"""
264
-
267
+ """Compatibility shim retained for tests that assert this symbol exists."""
268
+
265
269
  def __getattr__(self, name: str):
266
- # For callable access (like schemas.Response()), return the generic schema class
267
- # For further attribute access (like schemas.random.random2), return self to allow nesting
270
+ # For callable access (like schemas.Response()), return nested access.
268
271
  return _NestedSchemaAccess()
269
-
272
+
270
273
  def __call__(self, *args, **kwargs):
271
- # If someone tries to call the module itself, return a generic schema
272
274
  return _GenericSchema(*args, **kwargs)
273
-
275
+
274
276
  def __dir__(self):
275
- # Return common schema names for introspection
276
- return ['Response', 'Request', 'Error', 'Data', 'Result']
277
+ return ["Response", "Request", "Error", "Data", "Result"]
277
278
 
278
279
  class _NestedSchemaAccess:
279
280
  """Handles nested attribute access and final callable resolution"""
280
-
281
+
281
282
  def __getattr__(self, name: str):
282
- # Continue allowing nested access
283
283
  return _NestedSchemaAccess()
284
-
284
+
285
285
  def __call__(self, *args, **kwargs):
286
- # When finally called, return a generic schema instance
287
286
  return _GenericSchema(*args, **kwargs)
288
-
287
+
289
288
  def __class_getitem__(cls, item):
290
- # Support type annotations like schemas.Response[str]
291
289
  return _GenericSchema
292
290
 
293
- # Replace this module with our dynamic module
294
- import sys
295
- sys.modules[__name__] = _SchemaModule()
291
+ def __getattr__(name: str):
292
+ if name.startswith("__") or name == "load_tests":
293
+ raise AttributeError(name)
294
+ return _NestedSchemaAccess()
295
+
296
+
297
+ def __dir__():
298
+ return ["Response", "Request", "Error", "Data", "Result"]
296
299
  ''')
297
300
 
301
+ # Ensure subsequent imports pick up the newly generated no-types module.
302
+ sys.modules.pop("polyapi.schemas", None)
303
+ parent_module = sys.modules.get("polyapi")
304
+ if parent_module is not None and hasattr(parent_module, "schemas"):
305
+ delattr(parent_module, "schemas")
306
+ importlib.invalidate_caches()
307
+
298
308
 
299
309
  def _generate_client_id() -> None:
300
310
  full_path = os.path.dirname(os.path.abspath(__file__))
@@ -411,6 +421,7 @@ def clear() -> None:
411
421
  def render_spec(spec: SpecificationDto) -> Tuple[str, str]:
412
422
  function_type = spec["type"]
413
423
  raw_description = spec.get("description", "")
424
+
414
425
  def _flatten_description(value: Any) -> List[str]:
415
426
  if value is None:
416
427
  return []
@@ -69,7 +69,7 @@ def close():
69
69
  _sync_client = None
70
70
 
71
71
  async def close_async():
72
- global _sync_client, _async_client, _async_client_loop
72
+ global _async_client, _async_client_loop
73
73
  close()
74
74
  client = _async_client
75
75
  client_loop = _async_client_loop
@@ -22,9 +22,27 @@ BASE_REQUIREMENTS = {
22
22
  "typing_extensions",
23
23
  "jsonschema-gentypes",
24
24
  "pydantic",
25
+
25
26
  "cloudevents",
26
27
  }
27
- all_stdlib_symbols = stdlib_list(".".join([str(v) for v in sys.version_info[0:2]]))
28
+
29
+
30
+ def _safe_stdlib_symbols() -> List[str]:
31
+ major = sys.version_info[0]
32
+ minor = sys.version_info[1]
33
+
34
+ # `stdlib_list` may lag behind the newest Python minor; fall back to the
35
+ # nearest supported version so importing this module never crashes.
36
+ while minor >= 0:
37
+ try:
38
+ return stdlib_list(f"{major}.{minor}")
39
+ except ValueError:
40
+ minor -= 1
41
+
42
+ return []
43
+
44
+
45
+ all_stdlib_symbols = _safe_stdlib_symbols()
28
46
  BASE_REQUIREMENTS.update(
29
47
  all_stdlib_symbols
30
48
  ) # dont need to pip install stuff in the python standard library
@@ -397,13 +415,16 @@ def parse_function_code(code: str, name: Optional[str] = "", context: Optional[s
397
415
  deployable["docStartIndex"] = start_offset
398
416
  deployable["docEndIndex"] = end_offset
399
417
 
400
- try:
401
- docstring = ast.get_docstring(node)
402
- finally:
403
- # Handle case where there is no doc string
404
- # Also handle case where docstring exists but is empty
405
- if type(docstring) is None or (not docstring and '"""' not in self._lines[start_lineno] and "'''" not in self._lines[start_lineno]):
406
- return None
418
+ docstring = ast.get_docstring(node)
419
+
420
+ # Handle case where there is no doc string.
421
+ # Also handle case where docstring exists but is empty.
422
+ if docstring is None or (
423
+ not docstring
424
+ and '"""' not in self._lines[start_lineno]
425
+ and "'''" not in self._lines[start_lineno]
426
+ ):
427
+ return None
407
428
 
408
429
  docstring = cast(str, docstring)
409
430
 
@@ -85,7 +85,7 @@ def execute_query(table_id, method, query):
85
85
 
86
86
  auth_key = polyCustom.get("executionApiKey") or api_key
87
87
  url = f"{base_url.rstrip('/')}/tables/{table_id}/{method}?clientId={client_id}"
88
- headers = {"x-poly-execution-id": polyCustom.get("executionId")}
88
+ headers = {"x-poly-execution-id": polyCustom.get("executionId") or ""}
89
89
  if auth_key:
90
90
  headers["Authorization"] = f"Bearer {auth_key}"
91
91
  response = http_client.post(url, json=query, headers=headers)
@@ -130,11 +130,11 @@ def _transform_keys(obj: Any) -> Any:
130
130
 
131
131
 
132
132
  def transform_query(query: dict) -> dict:
133
- if query["where"] or query["order_by"]:
133
+ if query.get("where") or query.get("order_by"):
134
134
  return {
135
135
  **query,
136
- "where": _transform_keys(query["where"]) if query["where"] else None,
137
- "orderBy": query["order_by"] if query["order_by"] else None,
136
+ "where": _transform_keys(query["where"]) if query.get("where") else None,
137
+ "orderBy": query["order_by"] if query.get("order_by") else None,
138
138
  }
139
139
 
140
140
  return query
@@ -2,6 +2,7 @@
2
2
  """
3
3
  import logging
4
4
  import contextlib
5
+ import os
5
6
  import re
6
7
  from typing import Dict
7
8
  from jsonschema_gentypes.cli import process_config
@@ -79,9 +80,10 @@ def generate_schema_types(input_data: Dict, root=None):
79
80
  """takes in a Dict representing a schema as input then appends the resulting python code to the output file"""
80
81
  _cleanup_input_for_gentypes(input_data)
81
82
  tmp_input = _temp_store_input_data(input_data)
82
- tmp_output = tempfile.NamedTemporaryFile(
83
+ with tempfile.NamedTemporaryFile(
83
84
  mode="w", delete=False, prefix="polyapi_", suffix=".py"
84
- ).name
85
+ ) as tmp_output_file:
86
+ tmp_output = tmp_output_file.name
85
87
 
86
88
  config: configuration.Configuration = {
87
89
  "python_version": None, # type: ignore
@@ -95,17 +97,23 @@ def generate_schema_types(input_data: Dict, root=None):
95
97
  ],
96
98
  }
97
99
 
98
- # jsonschema_gentypes prints source to stdout
99
- # no option to surpress so we do this
100
- with contextlib.redirect_stdout(None):
101
- process_config(config, [tmp_input])
102
-
103
- with open(tmp_output, encoding='utf-8') as f:
104
- output = f.read()
100
+ try:
101
+ # jsonschema_gentypes prints source to stdout
102
+ # no option to surpress so we do this
103
+ with contextlib.redirect_stdout(None):
104
+ process_config(config, [tmp_input])
105
105
 
106
- output = clean_malformed_examples(output)
106
+ with open(tmp_output, encoding='utf-8') as f:
107
+ output = f.read()
107
108
 
108
- return output
109
+ output = clean_malformed_examples(output)
110
+ return output
111
+ finally:
112
+ for temp_path in (tmp_input, tmp_output):
113
+ try:
114
+ os.unlink(temp_path)
115
+ except OSError:
116
+ pass
109
117
 
110
118
 
111
119
  # Matches commented example headers emitted by jsonschema-gentypes before a raw
@@ -122,6 +130,10 @@ INVALID_ESCAPE_PATTERNS = [
122
130
  (re.compile(r'\\([^nrtbfav"\'\\])', re.DOTALL), r'\\\\\1'),
123
131
  ]
124
132
 
133
+ # jsonschema_gentypes may emit raw triple-quoted docstrings (r"""...""").
134
+ # Normalize these to plain docstrings to keep generated output stable.
135
+ RAW_TRIPLE_QUOTE_PATTERN = re.compile(r'(^\s*)r(["\']{3})', re.MULTILINE)
136
+
125
137
 
126
138
  def clean_malformed_examples(example: str) -> str:
127
139
  """ there is a bug in the `jsonschmea_gentypes` library where if an example from a jsonchema is an object,
@@ -149,6 +161,8 @@ def clean_malformed_examples(example: str) -> str:
149
161
  # Fix invalid escape sequences in docstrings
150
162
  for pattern, replacement in INVALID_ESCAPE_PATTERNS:
151
163
  cleaned_example = pattern.sub(replacement, cleaned_example)
164
+
165
+ cleaned_example = RAW_TRIPLE_QUOTE_PATTERN.sub(r'\1\2', cleaned_example)
152
166
 
153
167
  return cleaned_example
154
168
 
@@ -2,7 +2,7 @@ import keyword
2
2
  import re
3
3
  import os
4
4
  from urllib.parse import urlparse
5
- from typing import Tuple, List, Optional
5
+ from typing import Tuple, List, Optional, cast
6
6
  from colorama import Fore, Style
7
7
  from polyapi.constants import BASIC_PYTHON_TYPES
8
8
  from polyapi.typedefs import PropertySpecification, PropertyType
@@ -115,9 +115,9 @@ def to_type_module_alias(function_name: str) -> str:
115
115
 
116
116
  def add_type_import_path(function_name: str, arg: str) -> str:
117
117
  """if not basic type, coerce to camelCase and add the import path"""
118
- # from now, we start qualifying non-basic types :))
118
+ # from now, we start qualifying non-basic types :))
119
119
  # e.g. Callable[[EmailAddress, Dict, Dict, Dict], None]
120
- # becomes Callable[[Set_profile_email.EmailAddress, Dict, Dict, Dict], None]
120
+ # becomes Callable[[Set_profile_email.EmailAddress, Dict, Dict, Dict], None]
121
121
  arg = normalize_cross_language_type(arg)
122
122
 
123
123
  if "|" in arg:
@@ -158,7 +158,7 @@ def add_type_import_path(function_name: str, arg: str) -> str:
158
158
  return f"{type_module_alias}.{camelCase(arg)}"
159
159
 
160
160
 
161
- def get_type_and_def(
161
+ def get_type_and_def( # noqa: C901
162
162
  type_spec: PropertyType, title_fallback: str = ""
163
163
  ) -> Tuple[str, str]:
164
164
  """ returns type and type definition for a given PropertyType
@@ -190,7 +190,7 @@ def get_type_and_def(
190
190
  return primitive, ""
191
191
  return map_primitive_types(primitive), ""
192
192
  elif type_spec["kind"] == "array":
193
- if type_spec.get("items"):
193
+ if "items" in type_spec and isinstance(type_spec["items"], dict):
194
194
  items = type_spec["items"]
195
195
  if items.get("$ref"):
196
196
  # For no-types mode, avoid complex schema generation
@@ -208,9 +208,12 @@ def get_type_and_def(
208
208
  elif type_spec["kind"] == "void":
209
209
  return "None", ""
210
210
  elif type_spec["kind"] == "object":
211
- if type_spec.get("schema"):
211
+ if "schema" in type_spec and isinstance(type_spec["schema"], dict):
212
212
  schema = type_spec["schema"]
213
- title = schema.get("title", schema.get("name", title_fallback))
213
+ title = cast(str, schema.get("title", schema.get("name", title_fallback)))
214
+ if not isinstance(title, str):
215
+ raise ValueError("Title must be a string, this should never happen due to the fallback, but got: {}".format(title))
216
+
214
217
  if title and schema.get("type") == "array":
215
218
  # TODO fix me
216
219
  # we don't use ReturnType as name for the list type here, we use _ReturnTypeItem
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyapi-python
3
- Version: 0.3.19.dev0
3
+ Version: 0.3.19.dev2
4
4
  Summary: The Python Client for PolyAPI, the IPaaS by Developers for Developers
5
5
  Author-email: PolyAPI <support@polyapi.io>
6
6
  License: MIT License
@@ -29,9 +29,9 @@ Requires-Python: >=3.10
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Requires-Dist: requests==2.32.3
32
- Requires-Dist: typing_extensions==4.12.2
33
- Requires-Dist: jsonschema-gentypes==2.10.0
34
- Requires-Dist: pydantic==2.8.0
32
+ Requires-Dist: typing_extensions==4.15.0
33
+ Requires-Dist: jsonschema-gentypes==2.13.0
34
+ Requires-Dist: pydantic==2.13.4
35
35
  Requires-Dist: stdlib_list==0.11.1
36
36
  Requires-Dist: colorama==0.4.4
37
37
  Requires-Dist: python-socketio[asyncio_client]==5.11.1
@@ -1,7 +1,7 @@
1
1
  requests==2.32.3
2
- typing_extensions==4.12.2
3
- jsonschema-gentypes==2.10.0
4
- pydantic==2.8.0
2
+ typing_extensions==4.15.0
3
+ jsonschema-gentypes==2.13.0
4
+ pydantic==2.13.4
5
5
  stdlib_list==0.11.1
6
6
  colorama==0.4.4
7
7
  python-socketio[asyncio_client]==5.11.1
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "polyapi-python"
7
- version = "0.3.19.dev0"
7
+ version = "0.3.19.dev2"
8
8
  description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
9
9
  authors = [{ name = "PolyAPI", email = "support@polyapi.io" }]
10
10
  dependencies = [
11
11
  "requests==2.32.3",
12
- "typing_extensions==4.12.2",
13
- "jsonschema-gentypes==2.10.0",
14
- "pydantic==2.8.0",
12
+ "typing_extensions==4.15.0",
13
+ "jsonschema-gentypes==2.13.0",
14
+ "pydantic==2.13.4",
15
15
  "stdlib_list==0.11.1",
16
16
  "colorama==0.4.4",
17
17
  "python-socketio[asyncio_client]==5.11.1",
@@ -2,7 +2,11 @@ import unittest
2
2
 
3
3
  from polyapi.utils import to_type_module_alias
4
4
 
5
- from .test_api import TWILIO
5
+ try:
6
+ from .test_api import TWILIO
7
+ except ImportError:
8
+ # Supports `python -m unittest discover tests` where modules are imported top-level.
9
+ from test_api import TWILIO
6
10
  from polyapi.server import render_server_function
7
11
 
8
12
  GET_PRODUCTS_COUNT = {