singlestoredb 1.12.3__tar.gz → 1.13.0__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.

Potentially problematic release.


This version of singlestoredb might be problematic. Click here for more details.

Files changed (162) hide show
  1. {singlestoredb-1.12.3/singlestoredb.egg-info → singlestoredb-1.13.0}/PKG-INFO +1 -1
  2. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/accel.c +201 -37
  3. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/setup.cfg +2 -1
  4. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/__init__.py +1 -1
  5. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/__init__.py +1 -0
  6. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/_config.py +6 -0
  7. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/_connection_info.py +8 -0
  8. singlestoredb-1.13.0/singlestoredb/apps/_python_udfs.py +85 -0
  9. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/config.py +14 -2
  10. singlestoredb-1.13.0/singlestoredb/functions/__init__.py +12 -0
  11. singlestoredb-1.13.0/singlestoredb/functions/decorator.py +181 -0
  12. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/dtypes.py +545 -198
  13. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/ext/asgi.py +288 -90
  14. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/ext/json.py +29 -36
  15. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/ext/mmap.py +1 -1
  16. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/ext/rowdat_1.py +50 -70
  17. singlestoredb-1.13.0/singlestoredb/functions/signature.py +1445 -0
  18. singlestoredb-1.13.0/singlestoredb/functions/typing.py +41 -0
  19. singlestoredb-1.13.0/singlestoredb/functions/utils.py +342 -0
  20. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/http/connection.py +3 -1
  21. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/manager.py +6 -1
  22. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/utils.py +2 -2
  23. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/connection.py +17 -11
  24. singlestoredb-1.13.0/singlestoredb/tests/ext_funcs/__init__.py +624 -0
  25. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_basics.py +2 -0
  26. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_ext_func.py +192 -3
  27. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_udf.py +101 -131
  28. singlestoredb-1.13.0/singlestoredb/tests/test_udf_returns.py +459 -0
  29. {singlestoredb-1.12.3 → singlestoredb-1.13.0/singlestoredb.egg-info}/PKG-INFO +1 -1
  30. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb.egg-info/SOURCES.txt +4 -0
  31. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb.egg-info/requires.txt +1 -0
  32. singlestoredb-1.12.3/singlestoredb/functions/__init__.py +0 -2
  33. singlestoredb-1.12.3/singlestoredb/functions/decorator.py +0 -331
  34. singlestoredb-1.12.3/singlestoredb/functions/signature.py +0 -773
  35. singlestoredb-1.12.3/singlestoredb/tests/ext_funcs/__init__.py +0 -385
  36. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/LICENSE +0 -0
  37. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/README.md +0 -0
  38. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/setup.py +0 -0
  39. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/ai/__init__.py +0 -0
  40. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/ai/embeddings.py +0 -0
  41. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/alchemy/__init__.py +0 -0
  42. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/_cloud_functions.py +0 -0
  43. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/_dashboards.py +0 -0
  44. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/_process.py +0 -0
  45. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/_stdout_supress.py +0 -0
  46. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/apps/_uvicorn_util.py +0 -0
  47. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/auth.py +0 -0
  48. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/connection.py +0 -0
  49. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/converters.py +0 -0
  50. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/exceptions.py +0 -0
  51. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/ext/__init__.py +0 -0
  52. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/ext/arrow.py +0 -0
  53. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/functions/ext/utils.py +0 -0
  54. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/__init__.py +0 -0
  55. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/graphql.py +0 -0
  56. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handler.py +0 -0
  57. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/__init__.py +0 -0
  58. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/export.py +0 -0
  59. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/files.py +0 -0
  60. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/job.py +0 -0
  61. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/models.py +0 -0
  62. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/stage.py +0 -0
  63. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/utils.py +0 -0
  64. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/handlers/workspace.py +0 -0
  65. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/registry.py +0 -0
  66. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/fusion/result.py +0 -0
  67. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/http/__init__.py +0 -0
  68. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/magics/__init__.py +0 -0
  69. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/magics/run_personal.py +0 -0
  70. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/magics/run_shared.py +0 -0
  71. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/__init__.py +0 -0
  72. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/billing_usage.py +0 -0
  73. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/cluster.py +0 -0
  74. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/export.py +0 -0
  75. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/files.py +0 -0
  76. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/job.py +0 -0
  77. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/organization.py +0 -0
  78. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/region.py +0 -0
  79. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/management/workspace.py +0 -0
  80. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/__init__.py +0 -0
  81. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/_auth.py +0 -0
  82. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/charset.py +0 -0
  83. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/CLIENT.py +0 -0
  84. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/COMMAND.py +0 -0
  85. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/CR.py +0 -0
  86. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/ER.py +0 -0
  87. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/EXTENDED_TYPE.py +0 -0
  88. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/FIELD_TYPE.py +0 -0
  89. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/FLAG.py +0 -0
  90. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/SERVER_STATUS.py +0 -0
  91. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/VECTOR_TYPE.py +0 -0
  92. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/constants/__init__.py +0 -0
  93. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/converters.py +0 -0
  94. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/cursors.py +0 -0
  95. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/err.py +0 -0
  96. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/optionfile.py +0 -0
  97. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/protocol.py +0 -0
  98. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/__init__.py +0 -0
  99. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/base.py +0 -0
  100. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/conftest.py +0 -0
  101. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_DictCursor.py +0 -0
  102. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_SSCursor.py +0 -0
  103. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_basic.py +0 -0
  104. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_connection.py +0 -0
  105. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_converters.py +0 -0
  106. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_cursor.py +0 -0
  107. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_err.py +0 -0
  108. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_issues.py +0 -0
  109. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_load_local.py +0 -0
  110. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_nextset.py +0 -0
  111. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/test_optionfile.py +0 -0
  112. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/thirdparty/__init__.py +0 -0
  113. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +0 -0
  114. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +0 -0
  115. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +0 -0
  116. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +0 -0
  117. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +0 -0
  118. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +0 -0
  119. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/mysql/times.py +0 -0
  120. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/notebook/__init__.py +0 -0
  121. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/notebook/_objects.py +0 -0
  122. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/notebook/_portal.py +0 -0
  123. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/py.typed +0 -0
  124. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/pytest.py +0 -0
  125. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/server/__init__.py +0 -0
  126. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/server/docker.py +0 -0
  127. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/server/free_tier.py +0 -0
  128. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/__init__.py +0 -0
  129. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/empty.sql +0 -0
  130. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/local_infile.csv +0 -0
  131. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test.ipynb +0 -0
  132. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test.sql +0 -0
  133. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test2.ipynb +0 -0
  134. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test2.sql +0 -0
  135. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_config.py +0 -0
  136. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_connection.py +0 -0
  137. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_dbapi.py +0 -0
  138. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_exceptions.py +0 -0
  139. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_ext_func_data.py +0 -0
  140. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_fusion.py +0 -0
  141. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_http.py +0 -0
  142. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_management.py +0 -0
  143. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_plugin.py +0 -0
  144. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_results.py +0 -0
  145. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_types.py +0 -0
  146. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/test_xdict.py +0 -0
  147. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/tests/utils.py +0 -0
  148. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/types.py +0 -0
  149. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/__init__.py +0 -0
  150. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/config.py +0 -0
  151. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/convert_rows.py +0 -0
  152. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/debug.py +0 -0
  153. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/dtypes.py +0 -0
  154. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/events.py +0 -0
  155. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/mogrify.py +0 -0
  156. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/results.py +0 -0
  157. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb/utils/xdict.py +0 -0
  158. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb.egg-info/dependency_links.txt +0 -0
  159. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb.egg-info/entry_points.txt +0 -0
  160. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/singlestoredb.egg-info/top_level.txt +0 -0
  161. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/sqlx/__init__.py +0 -0
  162. {singlestoredb-1.12.3 → singlestoredb-1.13.0}/sqlx/magic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: singlestoredb
3
- Version: 1.12.3
3
+ Version: 1.13.0
4
4
  Summary: Interface to the SingleStoreDB database and workspace management APIs
5
5
  Home-page: https://github.com/singlestore-labs/singlestoredb-python
6
6
  Author: SingleStore
@@ -35,6 +35,8 @@
35
35
  #define NUMPY_TIMEDELTA 12
36
36
  #define NUMPY_DATETIME 13
37
37
  #define NUMPY_OBJECT 14
38
+ #define NUMPY_BYTES 15
39
+ #define NUMPY_FIXED_STRING 16
38
40
 
39
41
  #define MYSQL_FLAG_NOT_NULL 1
40
42
  #define MYSQL_FLAG_PRI_KEY 2
@@ -339,6 +341,11 @@
339
341
 
340
342
  #define CHECKRC(x) if ((x) < 0) goto error;
341
343
 
344
+ typedef struct {
345
+ int type;
346
+ Py_ssize_t length;
347
+ } NumpyColType;
348
+
342
349
  typedef struct {
343
350
  int results_type;
344
351
  int parse_json;
@@ -365,6 +372,83 @@ char *_PyUnicode_AsUTF8(PyObject *unicode) {
365
372
  return out;
366
373
  }
367
374
 
375
+ // Function to convert a UCS-4 string to a UTF-8 string
376
+ // Returns the length of the resulting UTF-8 string, or -1 on error
377
+ int ucs4_to_utf8(const uint32_t *ucs4_str, size_t ucs4_len, char **utf8_str) {
378
+ if (!ucs4_str || !utf8_str) {
379
+ return -1; // Invalid input
380
+ }
381
+
382
+ // Allocate a buffer for the UTF-8 string (worst-case: 4 bytes per UCS-4 character)
383
+ size_t utf8_max_len = ucs4_len * 4 + 1; // +1 for null terminator
384
+ *utf8_str = malloc(utf8_max_len);
385
+ if (!*utf8_str) {
386
+ return -1; // Memory allocation failed
387
+ }
388
+
389
+ char *utf8_ptr = *utf8_str;
390
+ size_t utf8_len = 0;
391
+
392
+ for (size_t i = 0; i < ucs4_len; i++) {
393
+ uint32_t codepoint = ucs4_str[i];
394
+
395
+ if (codepoint <= 0x7F) {
396
+ // 1-byte UTF-8
397
+ if (utf8_len + 1 > utf8_max_len) goto error; // Buffer overflow
398
+ *utf8_ptr++ = (char)codepoint;
399
+ utf8_len += 1;
400
+ } else if (codepoint <= 0x7FF) {
401
+ // 2-byte UTF-8
402
+ if (utf8_len + 2 > utf8_max_len) goto error; // Buffer overflow
403
+ *utf8_ptr++ = (char)(0xC0 | (codepoint >> 6));
404
+ *utf8_ptr++ = (char)(0x80 | (codepoint & 0x3F));
405
+ utf8_len += 2;
406
+ } else if (codepoint <= 0xFFFF) {
407
+ // 3-byte UTF-8
408
+ if (utf8_len + 3 > utf8_max_len) goto error; // Buffer overflow
409
+ *utf8_ptr++ = (char)(0xE0 | (codepoint >> 12));
410
+ *utf8_ptr++ = (char)(0x80 | ((codepoint >> 6) & 0x3F));
411
+ *utf8_ptr++ = (char)(0x80 | (codepoint & 0x3F));
412
+ utf8_len += 3;
413
+ } else if (codepoint <= 0x10FFFF) {
414
+ // 4-byte UTF-8
415
+ if (utf8_len + 4 > utf8_max_len) goto error; // Buffer overflow
416
+ *utf8_ptr++ = (char)(0xF0 | (codepoint >> 18));
417
+ *utf8_ptr++ = (char)(0x80 | ((codepoint >> 12) & 0x3F));
418
+ *utf8_ptr++ = (char)(0x80 | ((codepoint >> 6) & 0x3F));
419
+ *utf8_ptr++ = (char)(0x80 | (codepoint & 0x3F));
420
+ utf8_len += 4;
421
+ } else {
422
+ // Invalid codepoint
423
+ goto error;
424
+ }
425
+ }
426
+
427
+ // Null-terminate the UTF-8 string
428
+ if (utf8_len + 1 > utf8_max_len) goto error; // Buffer overflow
429
+ *utf8_ptr = '\0';
430
+
431
+ return (int)utf8_len;
432
+
433
+ error:
434
+ free(*utf8_str);
435
+ *utf8_str = NULL;
436
+ return -1;
437
+ }
438
+
439
+ size_t length_without_trailing_nulls(const char *str, size_t len) {
440
+ if (!str || len == 0) {
441
+ return 0; // Handle null or empty input
442
+ }
443
+
444
+ // Start from the end of the string and move backward
445
+ while (len > 0 && str[len - 1] == '\0') {
446
+ len--;
447
+ }
448
+
449
+ return len;
450
+ }
451
+
368
452
  //
369
453
  // Cached int values for date/time components
370
454
  //
@@ -2646,8 +2730,8 @@ error:
2646
2730
  }
2647
2731
 
2648
2732
 
2649
- static int get_numpy_col_type(PyObject *py_array) {
2650
- int out = 0;
2733
+ static NumpyColType get_numpy_col_type(PyObject *py_array) {
2734
+ NumpyColType out = {0};
2651
2735
  char *str = NULL;
2652
2736
  PyObject *py_array_interface = NULL;
2653
2737
  PyObject *py_typestr = NULL;
@@ -2665,58 +2749,86 @@ static int get_numpy_col_type(PyObject *py_array) {
2665
2749
 
2666
2750
  switch (str[1]) {
2667
2751
  case 'b':
2668
- out = NUMPY_BOOL;
2752
+ out.type = NUMPY_BOOL;
2753
+ out.length = 1;
2669
2754
  break;
2670
2755
  case 'i':
2671
2756
  switch (str[2]) {
2672
2757
  case '1':
2673
- out = NUMPY_INT8;
2758
+ out.type = NUMPY_INT8;
2759
+ out.length = 1;
2674
2760
  break;
2675
2761
  case '2':
2676
- out = NUMPY_INT16;
2762
+ out.type = NUMPY_INT16;
2763
+ out.length = 2;
2677
2764
  break;
2678
2765
  case '4':
2679
- out = NUMPY_INT32;
2766
+ out.type = NUMPY_INT32;
2767
+ out.length = 4;
2680
2768
  break;
2681
2769
  case '8':
2682
- out = NUMPY_INT64;
2770
+ out.type = NUMPY_INT64;
2771
+ out.length = 8;
2683
2772
  break;
2684
2773
  }
2685
2774
  break;
2686
2775
  case 'u':
2687
2776
  switch (str[2]) {
2688
2777
  case '1':
2689
- out = NUMPY_UINT8;
2778
+ out.type = NUMPY_UINT8;
2779
+ out.length = 1;
2690
2780
  break;
2691
2781
  case '2':
2692
- out = NUMPY_UINT16;
2782
+ out.type = NUMPY_UINT16;
2783
+ out.length = 2;
2693
2784
  break;
2694
2785
  case '4':
2695
- out = NUMPY_UINT32;
2786
+ out.type = NUMPY_UINT32;
2787
+ out.length = 4;
2696
2788
  break;
2697
2789
  case '8':
2698
- out = NUMPY_UINT64;
2790
+ out.type = NUMPY_UINT64;
2791
+ out.length = 8;
2699
2792
  break;
2700
2793
  }
2701
2794
  break;
2702
2795
  case 'f':
2703
2796
  switch (str[2]) {
2704
2797
  case '4':
2705
- out = NUMPY_FLOAT32;
2798
+ out.type = NUMPY_FLOAT32;
2799
+ out.length = 4;
2706
2800
  break;
2707
2801
  case '8':
2708
- out = NUMPY_FLOAT64;
2802
+ out.type = NUMPY_FLOAT64;
2803
+ out.length = 8;
2709
2804
  break;
2710
2805
  }
2711
2806
  break;
2712
2807
  case 'O':
2713
- out = NUMPY_OBJECT;
2808
+ out.type = NUMPY_OBJECT;
2809
+ out.length = 8;
2714
2810
  break;
2715
2811
  case 'm':
2716
- out = NUMPY_TIMEDELTA;
2812
+ out.type = NUMPY_TIMEDELTA;
2813
+ out.length = 8;
2717
2814
  break;
2718
2815
  case 'M':
2719
- out = NUMPY_DATETIME;
2816
+ out.type = NUMPY_DATETIME;
2817
+ out.length = 8;
2818
+ break;
2819
+ case 'S':
2820
+ out.type = NUMPY_BYTES;
2821
+ out.length = (Py_ssize_t)strtol(str + 2, NULL, 10);
2822
+ if (out.length < 0) {
2823
+ goto error;
2824
+ }
2825
+ break;
2826
+ case 'U':
2827
+ out.type = NUMPY_FIXED_STRING;
2828
+ out.length = (Py_ssize_t)strtol(str + 2, NULL, 10);
2829
+ if (out.length < 0) {
2830
+ goto error;
2831
+ }
2720
2832
  break;
2721
2833
  default:
2722
2834
  goto error;
@@ -2730,7 +2842,8 @@ exit:
2730
2842
  return out;
2731
2843
 
2732
2844
  error:
2733
- out = 0;
2845
+ out.type = 0;
2846
+ out.length = 0;
2734
2847
  goto exit;
2735
2848
  }
2736
2849
 
@@ -2774,7 +2887,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
2774
2887
  unsigned long long j = 0;
2775
2888
  char **cols = NULL;
2776
2889
  char **masks = NULL;
2777
- int *col_types = NULL;
2890
+ NumpyColType *col_types = NULL;
2778
2891
  int64_t *row_ids = NULL;
2779
2892
 
2780
2893
  // Parse function args.
@@ -2847,7 +2960,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
2847
2960
  // Get column array memory
2848
2961
  cols = calloc(sizeof(char*), n_cols);
2849
2962
  if (!cols) goto error;
2850
- col_types = calloc(sizeof(int), n_cols);
2963
+ col_types = calloc(sizeof(NumpyColType), n_cols);
2851
2964
  if (!col_types) goto error;
2852
2965
  masks = calloc(sizeof(char*), n_cols);
2853
2966
  if (!masks) goto error;
@@ -2865,7 +2978,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
2865
2978
  }
2866
2979
 
2867
2980
  col_types[i] = get_numpy_col_type(py_data);
2868
- if (!col_types[i]) {
2981
+ if (!col_types[i].type) {
2869
2982
  PyErr_SetString(PyExc_ValueError, "unable to get column type of data column");
2870
2983
  goto error;
2871
2984
  }
@@ -2874,7 +2987,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
2874
2987
  if (!py_mask) goto error;
2875
2988
 
2876
2989
  masks[i] = get_array_base_address(py_mask);
2877
- if (masks[i] && get_numpy_col_type(py_mask) != NUMPY_BOOL) {
2990
+ if (masks[i] && get_numpy_col_type(py_mask).type != NUMPY_BOOL) {
2878
2991
  PyErr_SetString(PyExc_ValueError, "mask must only contain boolean values");
2879
2992
  goto error;
2880
2993
  }
@@ -2958,7 +3071,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
2958
3071
 
2959
3072
  case MYSQL_TYPE_TINY:
2960
3073
  CHECKMEM(1);
2961
- switch (col_types[i]) {
3074
+ switch (col_types[i].type) {
2962
3075
  case NUMPY_BOOL:
2963
3076
  i8 = *(int8_t*)(cols[i] + j * 1);
2964
3077
  CHECK_TINYINT(i8, 0);
@@ -3025,7 +3138,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3025
3138
  // Use negative to indicate unsigned
3026
3139
  case -MYSQL_TYPE_TINY:
3027
3140
  CHECKMEM(1);
3028
- switch (col_types[i]) {
3141
+ switch (col_types[i].type) {
3029
3142
  case NUMPY_BOOL:
3030
3143
  i8 = *(int8_t*)(cols[i] + j * 1);
3031
3144
  CHECK_UNSIGNED_TINYINT(i8, 0);
@@ -3091,7 +3204,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3091
3204
 
3092
3205
  case MYSQL_TYPE_SHORT:
3093
3206
  CHECKMEM(2);
3094
- switch (col_types[i]) {
3207
+ switch (col_types[i].type) {
3095
3208
  case NUMPY_BOOL:
3096
3209
  i8 = *(int8_t*)(cols[i] + j * 1);
3097
3210
  CHECK_SMALLINT(i8, 0);
@@ -3158,7 +3271,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3158
3271
  // Use negative to indicate unsigned
3159
3272
  case -MYSQL_TYPE_SHORT:
3160
3273
  CHECKMEM(2);
3161
- switch (col_types[i]) {
3274
+ switch (col_types[i].type) {
3162
3275
  case NUMPY_BOOL:
3163
3276
  i8 = *(int8_t*)(cols[i] + j * 1);
3164
3277
  CHECK_UNSIGNED_SMALLINT(i8, 0);
@@ -3224,7 +3337,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3224
3337
 
3225
3338
  case MYSQL_TYPE_INT24:
3226
3339
  CHECKMEM(4);
3227
- switch (col_types[i]) {
3340
+ switch (col_types[i].type) {
3228
3341
  case NUMPY_BOOL:
3229
3342
  i8 = *(int8_t*)(cols[i] + j * 1);
3230
3343
  CHECK_MEDIUMINT(i8, 0);
@@ -3290,7 +3403,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3290
3403
 
3291
3404
  case MYSQL_TYPE_LONG:
3292
3405
  CHECKMEM(4);
3293
- switch (col_types[i]) {
3406
+ switch (col_types[i].type) {
3294
3407
  case NUMPY_BOOL:
3295
3408
  i8 = *(int8_t*)(cols[i] + j * 1);
3296
3409
  CHECK_INT(i8, 0);
@@ -3357,7 +3470,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3357
3470
  // Use negative to indicate unsigned
3358
3471
  case -MYSQL_TYPE_INT24:
3359
3472
  CHECKMEM(4);
3360
- switch (col_types[i]) {
3473
+ switch (col_types[i].type) {
3361
3474
  case NUMPY_BOOL:
3362
3475
  i8 = *(int8_t*)(cols[i] + j * 1);
3363
3476
  CHECK_UNSIGNED_MEDIUMINT(i8, 0);
@@ -3424,7 +3537,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3424
3537
  // Use negative to indicate unsigned
3425
3538
  case -MYSQL_TYPE_LONG:
3426
3539
  CHECKMEM(4);
3427
- switch (col_types[i]) {
3540
+ switch (col_types[i].type) {
3428
3541
  case NUMPY_BOOL:
3429
3542
  i8 = *(int8_t*)(cols[i] + j * 1);
3430
3543
  CHECK_UNSIGNED_INT(i8, 0);
@@ -3490,7 +3603,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3490
3603
 
3491
3604
  case MYSQL_TYPE_LONGLONG:
3492
3605
  CHECKMEM(8);
3493
- switch (col_types[i]) {
3606
+ switch (col_types[i].type) {
3494
3607
  case NUMPY_BOOL:
3495
3608
  i8 = *(int8_t*)(cols[i] + j * 1);
3496
3609
  CHECK_BIGINT(i8, 0);
@@ -3557,7 +3670,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3557
3670
  // Use negative to indicate unsigned
3558
3671
  case -MYSQL_TYPE_LONGLONG:
3559
3672
  CHECKMEM(8);
3560
- switch (col_types[i]) {
3673
+ switch (col_types[i].type) {
3561
3674
  case NUMPY_BOOL:
3562
3675
  i8 = *(int8_t*)(cols[i] + j * 1);
3563
3676
  CHECK_UNSIGNED_BIGINT(i8, 0);
@@ -3623,7 +3736,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3623
3736
 
3624
3737
  case MYSQL_TYPE_FLOAT:
3625
3738
  CHECKMEM(4);
3626
- switch (col_types[i]) {
3739
+ switch (col_types[i].type) {
3627
3740
  case NUMPY_BOOL:
3628
3741
  flt = (float)((is_null) ? 0 : *(int8_t*)(cols[i] + j * 1));
3629
3742
  break;
@@ -3667,7 +3780,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3667
3780
 
3668
3781
  case MYSQL_TYPE_DOUBLE:
3669
3782
  CHECKMEM(8);
3670
- switch (col_types[i]) {
3783
+ switch (col_types[i].type) {
3671
3784
  case NUMPY_BOOL:
3672
3785
  dbl = (double)((is_null) ? 0 : *(int8_t*)(cols[i] + j * 1));
3673
3786
  break;
@@ -3742,7 +3855,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3742
3855
 
3743
3856
  case MYSQL_TYPE_YEAR:
3744
3857
  CHECKMEM(2);
3745
- switch (col_types[i]) {
3858
+ switch (col_types[i].type) {
3746
3859
  case NUMPY_BOOL:
3747
3860
  i8 = *(int8_t*)(cols[i] + j * 1);
3748
3861
  CHECK_YEAR(i8);
@@ -3817,7 +3930,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3817
3930
  case MYSQL_TYPE_MEDIUM_BLOB:
3818
3931
  case MYSQL_TYPE_LONG_BLOB:
3819
3932
  case MYSQL_TYPE_BLOB:
3820
- if (col_types[i] != NUMPY_OBJECT) {
3933
+ if (col_types[i].type != NUMPY_OBJECT && col_types[i].type != NUMPY_FIXED_STRING) {
3821
3934
  PyErr_SetString(PyExc_ValueError, "unsupported numpy data type for character output types");
3822
3935
  goto error;
3823
3936
  }
@@ -3828,6 +3941,33 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3828
3941
  memcpy(out+out_idx, &i64, 8);
3829
3942
  out_idx += 8;
3830
3943
 
3944
+ } else if (col_types[i].type == NUMPY_FIXED_STRING) {
3945
+ // Jump to col_types[i].length * 4 for UCS4 fixed length string
3946
+ void *bytes = (void*)(cols[i] + j * col_types[i].length * 4);
3947
+
3948
+ if (bytes == NULL) {
3949
+ CHECKMEM(8);
3950
+ i64 = 0;
3951
+ memcpy(out+out_idx, &i64, 8);
3952
+ out_idx += 8;
3953
+ } else {
3954
+ char *utf8_str = NULL;
3955
+ Py_ssize_t str_l = ucs4_to_utf8(bytes, col_types[i].length, &utf8_str);
3956
+ if (str_l < 0) {
3957
+ PyErr_SetString(PyExc_ValueError, "invalid UCS4 string");
3958
+ if (utf8_str) free(utf8_str);
3959
+ goto error;
3960
+ }
3961
+ str_l = strnlen(utf8_str, str_l);
3962
+ CHECKMEM(8+str_l);
3963
+ i64 = str_l;
3964
+ memcpy(out+out_idx, &i64, 8);
3965
+ out_idx += 8;
3966
+ memcpy(out+out_idx, utf8_str, str_l);
3967
+ out_idx += str_l;
3968
+ free(utf8_str);
3969
+ }
3970
+
3831
3971
  } else {
3832
3972
  u64 = *(uint64_t*)(cols[i] + j * 8);
3833
3973
 
@@ -3873,7 +4013,7 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3873
4013
  case -MYSQL_TYPE_MEDIUM_BLOB:
3874
4014
  case -MYSQL_TYPE_LONG_BLOB:
3875
4015
  case -MYSQL_TYPE_BLOB:
3876
- if (col_types[i] != NUMPY_OBJECT) {
4016
+ if (col_types[i].type != NUMPY_OBJECT && col_types[i].type != NUMPY_BYTES) {
3877
4017
  PyErr_SetString(PyExc_ValueError, "unsupported numpy data type for binary output types");
3878
4018
  goto error;
3879
4019
  }
@@ -3884,6 +4024,27 @@ static PyObject *dump_rowdat_1_numpy(PyObject *self, PyObject *args, PyObject *k
3884
4024
  memcpy(out+out_idx, &i64, 8);
3885
4025
  out_idx += 8;
3886
4026
 
4027
+ } else if (col_types[i].type == NUMPY_BYTES) {
4028
+ void *bytes = (void*)(cols[i] + j * col_types[i].length);
4029
+
4030
+ if (bytes == NULL) {
4031
+ CHECKMEM(8);
4032
+ i64 = 0;
4033
+ memcpy(out+out_idx, &i64, 8);
4034
+ out_idx += 8;
4035
+ } else {
4036
+ Py_ssize_t str_l = col_types[i].length;
4037
+ CHECKMEM(8+str_l);
4038
+
4039
+ str_l = length_without_trailing_nulls(bytes, str_l);
4040
+
4041
+ i64 = str_l;
4042
+ memcpy(out+out_idx, &i64, 8);
4043
+ out_idx += 8;
4044
+ memcpy(out+out_idx, bytes, str_l);
4045
+ out_idx += str_l;
4046
+ }
4047
+
3887
4048
  } else {
3888
4049
  u64 = *(uint64_t*)(cols[i] + j * 8);
3889
4050
 
@@ -4291,7 +4452,10 @@ static PyObject *dump_rowdat_1(PyObject *self, PyObject *args, PyObject *kwargs)
4291
4452
 
4292
4453
  // Get return types
4293
4454
  n_cols = (unsigned long long)PyObject_Length(py_returns);
4294
- if (n_cols == 0) goto error;
4455
+ if (n_cols == 0) {
4456
+ PyErr_SetString(PyExc_ValueError, "no return values specified");
4457
+ goto error;
4458
+ }
4295
4459
 
4296
4460
  returns = malloc(sizeof(int) * n_cols);
4297
4461
  if (!returns) goto error;
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = singlestoredb
3
- version = 1.12.3
3
+ version = 1.13.0
4
4
  description = Interface to the SingleStoreDB database and workspace management APIs
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
@@ -27,6 +27,7 @@ install_requires =
27
27
  sqlparams
28
28
  wheel
29
29
  tomli>=1.1.0;python_version < '3.11'
30
+ typing-extensions<=4.13.2;python_version < '3.11'
30
31
  python_requires = >=3.8
31
32
  include_package_data = True
32
33
  tests_require =
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.12.3'
16
+ __version__ = '1.13.0'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -1,2 +1,3 @@
1
1
  from ._cloud_functions import run_function_app # noqa: F401
2
2
  from ._dashboards import run_dashboard_app # noqa: F401
3
+ from ._python_udfs import run_udf_app # noqa: F401
@@ -8,10 +8,12 @@ class AppConfig:
8
8
  listen_port: int
9
9
  base_url: str
10
10
  base_path: str
11
+ notebook_server_id: str
11
12
  app_token: Optional[str]
12
13
  user_token: Optional[str]
13
14
  running_interactively: bool
14
15
  is_gateway_enabled: bool
16
+ is_local_dev: bool
15
17
 
16
18
  @staticmethod
17
19
  def _read_variable(name: str) -> str:
@@ -28,6 +30,8 @@ class AppConfig:
28
30
  port = cls._read_variable('SINGLESTOREDB_APP_LISTEN_PORT')
29
31
  base_url = cls._read_variable('SINGLESTOREDB_APP_BASE_URL')
30
32
  base_path = cls._read_variable('SINGLESTOREDB_APP_BASE_PATH')
33
+ notebook_server_id = cls._read_variable('SINGLESTOREDB_NOTEBOOK_SERVER_ID')
34
+ is_local_dev_env_var = cls._read_variable('SINGLESTOREDB_IS_LOCAL_DEV')
31
35
 
32
36
  workload_type = os.environ.get('SINGLESTOREDB_WORKLOAD_TYPE')
33
37
  running_interactively = workload_type == 'InteractiveNotebook'
@@ -49,10 +53,12 @@ class AppConfig:
49
53
  listen_port=int(port),
50
54
  base_url=base_url,
51
55
  base_path=base_path,
56
+ notebook_server_id=notebook_server_id,
52
57
  app_token=app_token,
53
58
  user_token=user_token,
54
59
  running_interactively=running_interactively,
55
60
  is_gateway_enabled=is_gateway_enabled,
61
+ is_local_dev=is_local_dev_env_var == 'true',
56
62
  )
57
63
 
58
64
  @property
@@ -1,4 +1,6 @@
1
1
  from dataclasses import dataclass
2
+ from typing import Any
3
+ from typing import Dict
2
4
  from typing import Optional
3
5
 
4
6
 
@@ -8,3 +10,9 @@ class ConnectionInfo:
8
10
 
9
11
  # Only present in interactive mode
10
12
  token: Optional[str]
13
+
14
+
15
+ @dataclass
16
+ class UdfConnectionInfo:
17
+ url: str
18
+ functions: Dict[str, Any]
@@ -0,0 +1,85 @@
1
+ import asyncio
2
+ import os
3
+ import typing
4
+
5
+ from ..functions.ext.asgi import Application
6
+ from ._config import AppConfig
7
+ from ._connection_info import UdfConnectionInfo
8
+ from ._process import kill_process_by_port
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from ._uvicorn_util import AwaitableUvicornServer
12
+
13
+ # Keep track of currently running server
14
+ _running_server: 'typing.Optional[AwaitableUvicornServer]' = None
15
+
16
+
17
+ async def run_udf_app(
18
+ replace_existing: bool,
19
+ log_level: str = 'error',
20
+ kill_existing_app_server: bool = True,
21
+ ) -> UdfConnectionInfo:
22
+ global _running_server
23
+ from ._uvicorn_util import AwaitableUvicornServer
24
+
25
+ try:
26
+ import uvicorn
27
+ except ImportError:
28
+ raise ImportError('package uvicorn is required to run python udfs')
29
+
30
+ app_config = AppConfig.from_env()
31
+
32
+ if kill_existing_app_server:
33
+ # Shutdown the server gracefully if it was started by us.
34
+ # Since the uvicorn server doesn't start a new subprocess
35
+ # killing the process would result in kernel dying.
36
+ if _running_server is not None:
37
+ await _running_server.shutdown()
38
+ _running_server = None
39
+
40
+ # Kill if any other process is occupying the port
41
+ kill_process_by_port(app_config.listen_port)
42
+
43
+ base_url = generate_base_url(app_config)
44
+
45
+ udf_suffix = ''
46
+ if app_config.running_interactively:
47
+ udf_suffix = '_test'
48
+ app = Application(url=base_url, app_mode='managed', name_suffix=udf_suffix)
49
+
50
+ config = uvicorn.Config(
51
+ app,
52
+ host='0.0.0.0',
53
+ port=app_config.listen_port,
54
+ log_level=log_level,
55
+ )
56
+ _running_server = AwaitableUvicornServer(config)
57
+
58
+ # Register the functions
59
+ app.register_functions(replace=replace_existing)
60
+
61
+ asyncio.create_task(_running_server.serve())
62
+ await _running_server.wait_for_startup()
63
+
64
+ print(f'Python UDF registered at {base_url}')
65
+
66
+ return UdfConnectionInfo(base_url, app.get_function_info())
67
+
68
+
69
+ def generate_base_url(app_config: AppConfig) -> str:
70
+ if not app_config.is_gateway_enabled:
71
+ raise RuntimeError('Python UDFs are not available if Nova Gateway is not enabled')
72
+
73
+ if not app_config.running_interactively:
74
+ return app_config.base_url
75
+
76
+ # generate python udf endpoint for interactive notebooks
77
+ gateway_url = os.environ.get('SINGLESTOREDB_NOVA_GATEWAY_ENDPOINT')
78
+ if app_config.is_local_dev:
79
+ gateway_url = os.environ.get('SINGLESTOREDB_NOVA_GATEWAY_DEV_ENDPOINT')
80
+ if gateway_url is None:
81
+ raise RuntimeError(
82
+ 'Missing SINGLESTOREDB_NOVA_GATEWAY_DEV_ENDPOINT environment variable.',
83
+ )
84
+
85
+ return f'{gateway_url}/pythonudfs/{app_config.notebook_server_id}/interactive/'
@@ -317,7 +317,7 @@ register_option(
317
317
  'external_function.app_mode', 'string',
318
318
  functools.partial(
319
319
  check_str,
320
- valid_values=['remote', 'collocated'],
320
+ valid_values=['remote', 'collocated', 'managed'],
321
321
  ),
322
322
  'remote',
323
323
  'Specifies the mode of operation of the external function application.',
@@ -407,6 +407,18 @@ register_option(
407
407
  environ=['SINGLESTOREDB_EXT_FUNC_LOG_LEVEL'],
408
408
  )
409
409
 
410
+ register_option(
411
+ 'external_function.name_prefix', 'string', check_str, '',
412
+ 'Prefix to add to external function names.',
413
+ environ=['SINGLESTOREDB_EXT_FUNC_NAME_PREFIX'],
414
+ )
415
+
416
+ register_option(
417
+ 'external_function.name_suffix', 'string', check_str, '',
418
+ 'Suffix to add to external function names.',
419
+ environ=['SINGLESTOREDB_EXT_FUNC_NAME_SUFFIX'],
420
+ )
421
+
410
422
  register_option(
411
423
  'external_function.connection', 'string', check_str,
412
424
  os.environ.get('SINGLESTOREDB_URL') or None,
@@ -415,7 +427,7 @@ register_option(
415
427
  )
416
428
 
417
429
  register_option(
418
- 'external_function.host', 'string', check_str, '127.0.0.1',
430
+ 'external_function.host', 'string', check_str, 'localhost',
419
431
  'Specifies the host to bind the server to.',
420
432
  environ=['SINGLESTOREDB_EXT_FUNC_HOST'],
421
433
  )
@@ -0,0 +1,12 @@
1
+ from .decorator import udf # noqa: F401
2
+ from .typing import Masked # noqa: F401
3
+ from .typing import Table # noqa: F401
4
+ from .utils import VectorTypes
5
+
6
+
7
+ F32 = VectorTypes.F32
8
+ F64 = VectorTypes.F64
9
+ I8 = VectorTypes.I8
10
+ I16 = VectorTypes.I16
11
+ I32 = VectorTypes.I32
12
+ I64 = VectorTypes.I64