singlestoredb 1.16.1__py3-none-any.whl

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 (183) hide show
  1. singlestoredb/__init__.py +75 -0
  2. singlestoredb/ai/__init__.py +2 -0
  3. singlestoredb/ai/chat.py +139 -0
  4. singlestoredb/ai/embeddings.py +128 -0
  5. singlestoredb/alchemy/__init__.py +90 -0
  6. singlestoredb/apps/__init__.py +3 -0
  7. singlestoredb/apps/_cloud_functions.py +90 -0
  8. singlestoredb/apps/_config.py +72 -0
  9. singlestoredb/apps/_connection_info.py +18 -0
  10. singlestoredb/apps/_dashboards.py +47 -0
  11. singlestoredb/apps/_process.py +32 -0
  12. singlestoredb/apps/_python_udfs.py +100 -0
  13. singlestoredb/apps/_stdout_supress.py +30 -0
  14. singlestoredb/apps/_uvicorn_util.py +36 -0
  15. singlestoredb/auth.py +245 -0
  16. singlestoredb/config.py +484 -0
  17. singlestoredb/connection.py +1487 -0
  18. singlestoredb/converters.py +950 -0
  19. singlestoredb/docstring/__init__.py +33 -0
  20. singlestoredb/docstring/attrdoc.py +126 -0
  21. singlestoredb/docstring/common.py +230 -0
  22. singlestoredb/docstring/epydoc.py +267 -0
  23. singlestoredb/docstring/google.py +412 -0
  24. singlestoredb/docstring/numpydoc.py +562 -0
  25. singlestoredb/docstring/parser.py +100 -0
  26. singlestoredb/docstring/py.typed +1 -0
  27. singlestoredb/docstring/rest.py +256 -0
  28. singlestoredb/docstring/tests/__init__.py +1 -0
  29. singlestoredb/docstring/tests/_pydoctor.py +21 -0
  30. singlestoredb/docstring/tests/test_epydoc.py +729 -0
  31. singlestoredb/docstring/tests/test_google.py +1007 -0
  32. singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
  33. singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
  34. singlestoredb/docstring/tests/test_parser.py +248 -0
  35. singlestoredb/docstring/tests/test_rest.py +547 -0
  36. singlestoredb/docstring/tests/test_util.py +70 -0
  37. singlestoredb/docstring/util.py +141 -0
  38. singlestoredb/exceptions.py +120 -0
  39. singlestoredb/functions/__init__.py +16 -0
  40. singlestoredb/functions/decorator.py +201 -0
  41. singlestoredb/functions/dtypes.py +1793 -0
  42. singlestoredb/functions/ext/__init__.py +1 -0
  43. singlestoredb/functions/ext/arrow.py +375 -0
  44. singlestoredb/functions/ext/asgi.py +2133 -0
  45. singlestoredb/functions/ext/json.py +420 -0
  46. singlestoredb/functions/ext/mmap.py +413 -0
  47. singlestoredb/functions/ext/rowdat_1.py +724 -0
  48. singlestoredb/functions/ext/timer.py +89 -0
  49. singlestoredb/functions/ext/utils.py +218 -0
  50. singlestoredb/functions/signature.py +1578 -0
  51. singlestoredb/functions/typing/__init__.py +41 -0
  52. singlestoredb/functions/typing/numpy.py +20 -0
  53. singlestoredb/functions/typing/pandas.py +2 -0
  54. singlestoredb/functions/typing/polars.py +2 -0
  55. singlestoredb/functions/typing/pyarrow.py +2 -0
  56. singlestoredb/functions/utils.py +421 -0
  57. singlestoredb/fusion/__init__.py +11 -0
  58. singlestoredb/fusion/graphql.py +213 -0
  59. singlestoredb/fusion/handler.py +916 -0
  60. singlestoredb/fusion/handlers/__init__.py +0 -0
  61. singlestoredb/fusion/handlers/export.py +525 -0
  62. singlestoredb/fusion/handlers/files.py +690 -0
  63. singlestoredb/fusion/handlers/job.py +660 -0
  64. singlestoredb/fusion/handlers/models.py +250 -0
  65. singlestoredb/fusion/handlers/stage.py +502 -0
  66. singlestoredb/fusion/handlers/utils.py +324 -0
  67. singlestoredb/fusion/handlers/workspace.py +956 -0
  68. singlestoredb/fusion/registry.py +249 -0
  69. singlestoredb/fusion/result.py +399 -0
  70. singlestoredb/http/__init__.py +27 -0
  71. singlestoredb/http/connection.py +1267 -0
  72. singlestoredb/magics/__init__.py +34 -0
  73. singlestoredb/magics/run_personal.py +137 -0
  74. singlestoredb/magics/run_shared.py +134 -0
  75. singlestoredb/management/__init__.py +9 -0
  76. singlestoredb/management/billing_usage.py +148 -0
  77. singlestoredb/management/cluster.py +462 -0
  78. singlestoredb/management/export.py +295 -0
  79. singlestoredb/management/files.py +1102 -0
  80. singlestoredb/management/inference_api.py +105 -0
  81. singlestoredb/management/job.py +887 -0
  82. singlestoredb/management/manager.py +373 -0
  83. singlestoredb/management/organization.py +226 -0
  84. singlestoredb/management/region.py +169 -0
  85. singlestoredb/management/utils.py +423 -0
  86. singlestoredb/management/workspace.py +1927 -0
  87. singlestoredb/mysql/__init__.py +177 -0
  88. singlestoredb/mysql/_auth.py +298 -0
  89. singlestoredb/mysql/charset.py +214 -0
  90. singlestoredb/mysql/connection.py +2032 -0
  91. singlestoredb/mysql/constants/CLIENT.py +38 -0
  92. singlestoredb/mysql/constants/COMMAND.py +32 -0
  93. singlestoredb/mysql/constants/CR.py +78 -0
  94. singlestoredb/mysql/constants/ER.py +474 -0
  95. singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
  96. singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
  97. singlestoredb/mysql/constants/FLAG.py +15 -0
  98. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  99. singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
  100. singlestoredb/mysql/constants/__init__.py +0 -0
  101. singlestoredb/mysql/converters.py +271 -0
  102. singlestoredb/mysql/cursors.py +896 -0
  103. singlestoredb/mysql/err.py +92 -0
  104. singlestoredb/mysql/optionfile.py +20 -0
  105. singlestoredb/mysql/protocol.py +450 -0
  106. singlestoredb/mysql/tests/__init__.py +19 -0
  107. singlestoredb/mysql/tests/base.py +126 -0
  108. singlestoredb/mysql/tests/conftest.py +37 -0
  109. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  110. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  111. singlestoredb/mysql/tests/test_basic.py +452 -0
  112. singlestoredb/mysql/tests/test_connection.py +851 -0
  113. singlestoredb/mysql/tests/test_converters.py +58 -0
  114. singlestoredb/mysql/tests/test_cursor.py +141 -0
  115. singlestoredb/mysql/tests/test_err.py +16 -0
  116. singlestoredb/mysql/tests/test_issues.py +514 -0
  117. singlestoredb/mysql/tests/test_load_local.py +75 -0
  118. singlestoredb/mysql/tests/test_nextset.py +88 -0
  119. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  120. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  121. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  122. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  123. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  124. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  125. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  126. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  127. singlestoredb/mysql/times.py +23 -0
  128. singlestoredb/notebook/__init__.py +16 -0
  129. singlestoredb/notebook/_objects.py +213 -0
  130. singlestoredb/notebook/_portal.py +352 -0
  131. singlestoredb/py.typed +0 -0
  132. singlestoredb/pytest.py +352 -0
  133. singlestoredb/server/__init__.py +0 -0
  134. singlestoredb/server/docker.py +452 -0
  135. singlestoredb/server/free_tier.py +267 -0
  136. singlestoredb/tests/__init__.py +0 -0
  137. singlestoredb/tests/alltypes.sql +307 -0
  138. singlestoredb/tests/alltypes_no_nulls.sql +208 -0
  139. singlestoredb/tests/empty.sql +0 -0
  140. singlestoredb/tests/ext_funcs/__init__.py +702 -0
  141. singlestoredb/tests/local_infile.csv +3 -0
  142. singlestoredb/tests/test.ipynb +18 -0
  143. singlestoredb/tests/test.sql +680 -0
  144. singlestoredb/tests/test2.ipynb +18 -0
  145. singlestoredb/tests/test2.sql +1 -0
  146. singlestoredb/tests/test_basics.py +1332 -0
  147. singlestoredb/tests/test_config.py +318 -0
  148. singlestoredb/tests/test_connection.py +3103 -0
  149. singlestoredb/tests/test_dbapi.py +27 -0
  150. singlestoredb/tests/test_exceptions.py +45 -0
  151. singlestoredb/tests/test_ext_func.py +1472 -0
  152. singlestoredb/tests/test_ext_func_data.py +1101 -0
  153. singlestoredb/tests/test_fusion.py +1527 -0
  154. singlestoredb/tests/test_http.py +288 -0
  155. singlestoredb/tests/test_management.py +1599 -0
  156. singlestoredb/tests/test_plugin.py +33 -0
  157. singlestoredb/tests/test_results.py +171 -0
  158. singlestoredb/tests/test_types.py +132 -0
  159. singlestoredb/tests/test_udf.py +737 -0
  160. singlestoredb/tests/test_udf_returns.py +459 -0
  161. singlestoredb/tests/test_vectorstore.py +51 -0
  162. singlestoredb/tests/test_xdict.py +333 -0
  163. singlestoredb/tests/utils.py +141 -0
  164. singlestoredb/types.py +373 -0
  165. singlestoredb/utils/__init__.py +0 -0
  166. singlestoredb/utils/config.py +950 -0
  167. singlestoredb/utils/convert_rows.py +69 -0
  168. singlestoredb/utils/debug.py +13 -0
  169. singlestoredb/utils/dtypes.py +205 -0
  170. singlestoredb/utils/events.py +65 -0
  171. singlestoredb/utils/mogrify.py +151 -0
  172. singlestoredb/utils/results.py +585 -0
  173. singlestoredb/utils/xdict.py +425 -0
  174. singlestoredb/vectorstore.py +192 -0
  175. singlestoredb/warnings.py +5 -0
  176. singlestoredb-1.16.1.dist-info/METADATA +165 -0
  177. singlestoredb-1.16.1.dist-info/RECORD +183 -0
  178. singlestoredb-1.16.1.dist-info/WHEEL +5 -0
  179. singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
  180. singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
  181. singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
  182. sqlx/__init__.py +4 -0
  183. sqlx/magic.py +113 -0
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python
2
+ """SingleStoreDB Cluster Management."""
3
+ from typing import Dict
4
+ from typing import Optional
5
+
6
+ from .manager import Manager
7
+ from .utils import NamedList
8
+ from .utils import vars_to_str
9
+
10
+
11
+ class Region(object):
12
+ """
13
+ Cluster region information.
14
+
15
+ This object is not directly instantiated. It is used in results
16
+ of ``WorkspaceManager`` API calls.
17
+
18
+ See Also
19
+ --------
20
+ :attr:`WorkspaceManager.regions`
21
+
22
+ """
23
+
24
+ def __init__(
25
+ self, name: str, provider: str, id: Optional[str] = None,
26
+ region_name: Optional[str] = None,
27
+ ) -> None:
28
+ """Use :attr:`WorkspaceManager.regions` instead."""
29
+ #: Unique ID of the region
30
+ self.id = id
31
+
32
+ #: Name of the region
33
+ self.name = name
34
+
35
+ #: Name of the cloud provider
36
+ self.provider = provider
37
+
38
+ #: Name of the provider region
39
+ self.region_name = region_name
40
+
41
+ self._manager: Optional[Manager] = None
42
+
43
+ def __str__(self) -> str:
44
+ """Return string representation."""
45
+ return vars_to_str(self)
46
+
47
+ def __repr__(self) -> str:
48
+ """Return string representation."""
49
+ return str(self)
50
+
51
+ @classmethod
52
+ def from_dict(cls, obj: Dict[str, str], manager: Manager) -> 'Region':
53
+ """
54
+ Convert dictionary to a ``Region`` object.
55
+
56
+ Parameters
57
+ ----------
58
+ obj : dict
59
+ Key-value pairs to retrieve region information from
60
+ manager : WorkspaceManager, optional
61
+ The WorkspaceManager the Region belongs to
62
+
63
+ Returns
64
+ -------
65
+ :class:`Region`
66
+
67
+ """
68
+ id = obj.get('regionID', None)
69
+ region_name = obj.get('regionName', None)
70
+
71
+ out = cls(
72
+ id=id,
73
+ name=obj['region'],
74
+ provider=obj['provider'],
75
+ region_name=region_name,
76
+ )
77
+ out._manager = manager
78
+ return out
79
+
80
+
81
+ class RegionManager(Manager):
82
+ """
83
+ SingleStoreDB region manager.
84
+
85
+ This class should be instantiated using :func:`singlestoredb.manage_regions`.
86
+
87
+ Parameters
88
+ ----------
89
+ access_token : str, optional
90
+ The API key or other access token for the workspace management API
91
+ version : str, optional
92
+ Version of the API to use
93
+ base_url : str, optional
94
+ Base URL of the workspace management API
95
+
96
+ See Also
97
+ --------
98
+ :func:`singlestoredb.manage_regions`
99
+ """
100
+
101
+ #: Object type
102
+ obj_type = 'region'
103
+
104
+ def list_regions(self) -> NamedList[Region]:
105
+ """
106
+ List all available regions.
107
+
108
+ Returns
109
+ -------
110
+ NamedList[Region]
111
+ List of available regions
112
+
113
+ Raises
114
+ ------
115
+ ManagementError
116
+ If there is an error getting the regions
117
+ """
118
+ res = self._get('regions')
119
+ return NamedList(
120
+ [Region.from_dict(item, self) for item in res.json()],
121
+ )
122
+
123
+ def list_shared_tier_regions(self) -> NamedList[Region]:
124
+ """
125
+ List regions that support shared tier workspaces.
126
+
127
+ Returns
128
+ -------
129
+ NamedList[Region]
130
+ List of regions that support shared tier workspaces
131
+
132
+ Raises
133
+ ------
134
+ ManagementError
135
+ If there is an error getting the regions
136
+ """
137
+ res = self._get('regions/sharedtier')
138
+ return NamedList(
139
+ [Region.from_dict(item, self) for item in res.json()],
140
+ )
141
+
142
+
143
+ def manage_regions(
144
+ access_token: Optional[str] = None,
145
+ version: Optional[str] = None,
146
+ base_url: Optional[str] = None,
147
+ ) -> RegionManager:
148
+ """
149
+ Retrieve a SingleStoreDB region manager.
150
+
151
+ Parameters
152
+ ----------
153
+ access_token : str, optional
154
+ The API key or other access token for the workspace management API
155
+ version : str, optional
156
+ Version of the API to use
157
+ base_url : str, optional
158
+ Base URL of the workspace management API
159
+
160
+ Returns
161
+ -------
162
+ :class:`RegionManager`
163
+
164
+ """
165
+ return RegionManager(
166
+ access_token=access_token,
167
+ version=version,
168
+ base_url=base_url,
169
+ )
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env python
2
+ """SingleStoreDB Cluster Management."""
3
+ import datetime
4
+ import functools
5
+ import itertools
6
+ import os
7
+ import re
8
+ import sys
9
+ from collections.abc import Mapping
10
+ from typing import Any
11
+ from typing import Callable
12
+ from typing import Dict
13
+ from typing import List
14
+ from typing import Optional
15
+ from typing import SupportsIndex
16
+ from typing import Tuple
17
+ from typing import TypeVar
18
+ from typing import Union
19
+ from urllib.parse import urlparse
20
+
21
+ import jwt
22
+
23
+ from .. import converters
24
+ from ..config import get_option
25
+ from ..utils import events
26
+
27
+ JSON = Union[str, List[str], Dict[str, 'JSON']]
28
+ JSONObj = Dict[str, JSON]
29
+ JSONList = List[JSON]
30
+ T = TypeVar('T')
31
+
32
+ if sys.version_info < (3, 10):
33
+ PathLike = Union[str, os.PathLike] # type: ignore
34
+ PathLikeABC = os.PathLike
35
+ else:
36
+ PathLike = Union[str, os.PathLike[str]]
37
+ PathLikeABC = os.PathLike[str]
38
+
39
+
40
+ class TTLProperty(object):
41
+ """Property with time limit."""
42
+
43
+ def __init__(self, fget: Callable[[Any], Any], ttl: datetime.timedelta):
44
+ self.fget = fget
45
+ self.ttl = ttl
46
+ self._last_executed = datetime.datetime(2000, 1, 1)
47
+ self._last_result = None
48
+ self.__doc__ = fget.__doc__
49
+ self._name = ''
50
+
51
+ def reset(self) -> None:
52
+ self._last_executed = datetime.datetime(2000, 1, 1)
53
+ self._last_result = None
54
+
55
+ def __set_name__(self, owner: Any, name: str) -> None:
56
+ self._name = name
57
+
58
+ def __get__(self, obj: Any, objtype: Any = None) -> Any:
59
+ if obj is None:
60
+ return self
61
+
62
+ if self._last_result is not None \
63
+ and (datetime.datetime.now() - self._last_executed) < self.ttl:
64
+ return self._last_result
65
+
66
+ self._last_result = self.fget(obj)
67
+ self._last_executed = datetime.datetime.now()
68
+
69
+ return self._last_result
70
+
71
+
72
+ def ttl_property(ttl: datetime.timedelta) -> Callable[[Any], Any]:
73
+ """Property with a time-to-live."""
74
+ def wrapper(func: Callable[[Any], Any]) -> Any:
75
+ out = TTLProperty(func, ttl=ttl)
76
+ return functools.wraps(func)(out) # type: ignore
77
+ return wrapper
78
+
79
+
80
+ class NamedList(List[T]):
81
+ """List class which also allows selection by ``name`` and ``id`` attribute."""
82
+
83
+ def _find_item(self, key: str) -> T:
84
+ for item in self:
85
+ if getattr(item, 'name', '') == key:
86
+ return item
87
+ if getattr(item, 'id', '') == key:
88
+ return item
89
+ raise KeyError(key)
90
+
91
+ def __getitem__(self, key: Union[SupportsIndex, slice, str]) -> Any:
92
+ if isinstance(key, str):
93
+ return self._find_item(key)
94
+ return super().__getitem__(key)
95
+
96
+ def __contains__(self, key: Any) -> bool:
97
+ if isinstance(key, str):
98
+ try:
99
+ self._find_item(key)
100
+ return True
101
+ except KeyError:
102
+ return False
103
+ return super().__contains__(key)
104
+
105
+ def names(self) -> List[str]:
106
+ """Return ``name`` attribute of each item."""
107
+ return [y for y in [getattr(x, 'name', None) for x in self] if y is not None]
108
+
109
+ def ids(self) -> List[str]:
110
+ """Return ``id`` attribute of each item."""
111
+ return [y for y in [getattr(x, 'id', None) for x in self] if y is not None]
112
+
113
+ def get(self, name_or_id: str, *default: Any) -> Any:
114
+ """Return object with name / ID if it exists, or return default value."""
115
+ try:
116
+ return self._find_item(name_or_id)
117
+ except KeyError:
118
+ if default:
119
+ return default[0]
120
+ raise
121
+
122
+
123
+ def _setup_authentication_info_handler() -> Callable[..., Dict[str, Any]]:
124
+ """Setup authentication info event handler."""
125
+
126
+ authentication_info: Dict[str, Any] = {}
127
+
128
+ def handle_authentication_info(msg: Dict[str, Any]) -> None:
129
+ """Handle authentication info events."""
130
+ nonlocal authentication_info
131
+ if msg.get('name', '') != 'singlestore.portal.authentication_updated':
132
+ return
133
+ authentication_info = dict(msg.get('data', {}))
134
+
135
+ events.subscribe(handle_authentication_info)
136
+
137
+ def handle_connection_info(msg: Dict[str, Any]) -> None:
138
+ """Handle connection info events."""
139
+ nonlocal authentication_info
140
+ if msg.get('name', '') != 'singlestore.portal.connection_updated':
141
+ return
142
+ data = msg.get('data', {})
143
+ out = {}
144
+ if 'user' in data:
145
+ out['user'] = data['user']
146
+ if 'password' in data:
147
+ out['password'] = data['password']
148
+ authentication_info = out
149
+
150
+ events.subscribe(handle_authentication_info)
151
+
152
+ def retrieve_current_authentication_info() -> List[Tuple[str, Any]]:
153
+ """Retrieve JWT if not expired."""
154
+ nonlocal authentication_info
155
+ password = authentication_info.get('password')
156
+ if password:
157
+ expires = datetime.datetime.fromtimestamp(
158
+ jwt.decode(
159
+ password,
160
+ options={'verify_signature': False},
161
+ )['exp'],
162
+ )
163
+ if datetime.datetime.now() > expires:
164
+ authentication_info = {}
165
+ return list(authentication_info.items())
166
+
167
+ def get_env() -> List[Tuple[str, Any]]:
168
+ """Retrieve JWT from environment."""
169
+ conn = {}
170
+ url = os.environ.get('SINGLESTOREDB_URL') or get_option('host')
171
+ if url:
172
+ urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
173
+ conn = dict(
174
+ user=urlp.username or None,
175
+ password=urlp.password or None,
176
+ )
177
+
178
+ return [
179
+ x for x in dict(
180
+ **conn,
181
+ ).items() if x[1] is not None
182
+ ]
183
+
184
+ def get_authentication_info(include_env: bool = True) -> Dict[str, Any]:
185
+ """Return authentication info from event."""
186
+ return dict(
187
+ itertools.chain(
188
+ (get_env() if include_env else []),
189
+ retrieve_current_authentication_info(),
190
+ ),
191
+ )
192
+
193
+ return get_authentication_info
194
+
195
+
196
+ get_authentication_info = _setup_authentication_info_handler()
197
+
198
+
199
+ def get_token() -> Optional[str]:
200
+ """Return the token for the Management API."""
201
+ # See if an API key is configured
202
+ tok = get_option('management.token')
203
+ if tok:
204
+ return tok
205
+
206
+ tok = get_authentication_info(include_env=True).get('password')
207
+ if tok:
208
+ try:
209
+ jwt.decode(tok, options={'verify_signature': False})
210
+ return tok
211
+ except jwt.DecodeError:
212
+ pass
213
+
214
+ # Didn't find a key anywhere
215
+ return None
216
+
217
+
218
+ def get_cluster_id() -> Optional[str]:
219
+ """Return the cluster id for the current token or environment."""
220
+ return os.environ.get('SINGLESTOREDB_CLUSTER') or None
221
+
222
+
223
+ def get_workspace_id() -> Optional[str]:
224
+ """Return the workspace id for the current token or environment."""
225
+ return os.environ.get('SINGLESTOREDB_WORKSPACE') or None
226
+
227
+
228
+ def get_virtual_workspace_id() -> Optional[str]:
229
+ """Return the virtual workspace id for the current token or environment."""
230
+ return os.environ.get('SINGLESTOREDB_VIRTUAL_WORKSPACE') or None
231
+
232
+
233
+ def get_database_name() -> Optional[str]:
234
+ """Return the default database name for the current token or environment."""
235
+ return os.environ.get('SINGLESTOREDB_DEFAULT_DATABASE') or None
236
+
237
+
238
+ def enable_http_tracing() -> None:
239
+ """Enable tracing of HTTP requests."""
240
+ import logging
241
+ import http.client as http_client
242
+ http_client.HTTPConnection.debuglevel = 1
243
+ logging.basicConfig()
244
+ logging.getLogger().setLevel(logging.DEBUG)
245
+ requests_log = logging.getLogger('requests.packages.urllib3')
246
+ requests_log.setLevel(logging.DEBUG)
247
+ requests_log.propagate = True
248
+
249
+
250
+ def to_datetime(
251
+ obj: Optional[Union[str, datetime.datetime]],
252
+ ) -> Optional[datetime.datetime]:
253
+ """Convert string to datetime."""
254
+ if not obj:
255
+ return None
256
+ if isinstance(obj, datetime.datetime):
257
+ return obj
258
+ if obj == '0001-01-01T00:00:00Z':
259
+ return None
260
+ obj = obj.replace('Z', '')
261
+ # Fix datetimes with truncated zeros
262
+ if '.' in obj:
263
+ obj, micros = obj.split('.', 1)
264
+ micros = micros + '0' * (6 - len(micros))
265
+ obj = obj + '.' + micros
266
+ out = converters.datetime_fromisoformat(obj)
267
+ if isinstance(out, str):
268
+ return None
269
+ if isinstance(out, datetime.date) and not isinstance(out, datetime.datetime):
270
+ return datetime.datetime(out.year, out.month, out.day)
271
+ return out
272
+
273
+
274
+ def to_datetime_strict(
275
+ obj: Optional[Union[str, datetime.datetime]],
276
+ ) -> datetime.datetime:
277
+ """Convert string to datetime."""
278
+ if not obj:
279
+ raise TypeError('not possible to convert None to datetime')
280
+ if isinstance(obj, datetime.datetime):
281
+ return obj
282
+ if obj == '0001-01-01T00:00:00Z':
283
+ raise ValueError('not possible to convert 0001-01-01T00:00:00Z to datetime')
284
+ obj = obj.replace('Z', '')
285
+ # Fix datetimes with truncated zeros
286
+ if '.' in obj:
287
+ obj, micros = obj.split('.', 1)
288
+ micros = micros + '0' * (6 - len(micros))
289
+ obj = obj + '.' + micros
290
+ out = converters.datetime_fromisoformat(obj)
291
+ if not out:
292
+ raise TypeError('not possible to convert None to datetime')
293
+ if isinstance(out, str):
294
+ raise ValueError('value cannot be str')
295
+ if isinstance(out, datetime.date) and not isinstance(out, datetime.datetime):
296
+ return datetime.datetime(out.year, out.month, out.day)
297
+ return out
298
+
299
+
300
+ def from_datetime(
301
+ obj: Union[str, datetime.datetime],
302
+ ) -> Optional[str]:
303
+ """Convert datetime to string."""
304
+ if not obj:
305
+ return None
306
+ if isinstance(obj, str):
307
+ return obj
308
+ out = obj.isoformat()
309
+ if not re.search(r'[A-Za-z]$', out):
310
+ out = f'{out}Z'
311
+ return out
312
+
313
+
314
+ def vars_to_str(obj: Any) -> str:
315
+ """Render a string representation of vars(obj)."""
316
+ attrs = []
317
+ obj_vars = vars(obj)
318
+ if 'name' in obj_vars:
319
+ attrs.append('name={}'.format(repr(obj_vars['name'])))
320
+ if 'id' in obj_vars:
321
+ attrs.append('id={}'.format(repr(obj_vars['id'])))
322
+ for name, value in sorted(obj_vars.items()):
323
+ if name in ('name', 'id'):
324
+ continue
325
+ if not value or name.startswith('_'):
326
+ continue
327
+ attrs.append('{}={}'.format(name, repr(value)))
328
+ return '{}({})'.format(type(obj).__name__, ', '.join(attrs))
329
+
330
+
331
+ def single_item(s: Any) -> Any:
332
+ """Return only item if ``s`` is a list, otherwise return ``s``."""
333
+ if isinstance(s, list):
334
+ if len(s) != 1:
335
+ raise ValueError('list must only contain a singleitem')
336
+ return s[0]
337
+ return s
338
+
339
+
340
+ def stringify(s: JSON) -> str:
341
+ """Convert list of strings to single string."""
342
+ if isinstance(s, (tuple, list)):
343
+ if len(s) > 1:
344
+ raise ValueError('list contains more than one item')
345
+ return s[0]
346
+ if isinstance(s, dict):
347
+ raise TypeError('only strings and lists are valid arguments')
348
+ return s
349
+
350
+
351
+ def listify(s: JSON) -> List[str]:
352
+ """Convert string to list of strings."""
353
+ if isinstance(s, (tuple, list)):
354
+ return list(s)
355
+ if isinstance(s, dict):
356
+ raise TypeError('only strings and lists are valid arguments')
357
+ return [s]
358
+
359
+
360
+ def listify_obj(s: JSON) -> List[JSONObj]:
361
+ """Convert object to list of objects."""
362
+ if isinstance(s, (tuple, list)):
363
+ for item in s:
364
+ if not isinstance(item, dict):
365
+ raise TypeError('only dicts and lists of dicts are valid parameters')
366
+ return list(s) # type: ignore
367
+ if not isinstance(s, dict):
368
+ raise TypeError('only dicts and lists of dicts are valid parameters')
369
+ return [s]
370
+
371
+
372
+ def _upper_match(m: Any) -> str:
373
+ """Upper-case the first match group."""
374
+ return m.group(1).upper()
375
+
376
+
377
+ def snake_to_camel(s: Optional[str], cap_first: bool = False) -> Optional[str]:
378
+ """Convert snake-case to camel-case."""
379
+ if s is None:
380
+ return None
381
+ out = re.sub(r'_([A-Za-z])', _upper_match, s.lower())
382
+ if cap_first and out:
383
+ return out[0].upper() + out[1:]
384
+ return out
385
+
386
+
387
+ def camel_to_snake(s: Optional[str]) -> Optional[str]:
388
+ """Convert camel-case to snake-case."""
389
+ if s is None:
390
+ return None
391
+ out = re.sub(r'([A-Z]+)', r'_\1', s).lower()
392
+ if out and out[0] == '_':
393
+ return out[1:]
394
+ return out
395
+
396
+
397
+ def snake_to_camel_dict(
398
+ s: Optional[Mapping[str, Any]],
399
+ cap_first: bool = False,
400
+ ) -> Optional[Dict[str, Any]]:
401
+ """Convert snake-case keys to camel-case keys."""
402
+ if s is None:
403
+ return None
404
+ out = {}
405
+ for k, v in s.items():
406
+ if isinstance(v, Mapping):
407
+ out[str(snake_to_camel(k))] = snake_to_camel_dict(v, cap_first=cap_first)
408
+ else:
409
+ out[str(snake_to_camel(k))] = v
410
+ return out
411
+
412
+
413
+ def camel_to_snake_dict(s: Optional[Mapping[str, Any]]) -> Optional[Dict[str, Any]]:
414
+ """Convert camel-case keys to snake-case keys."""
415
+ if s is None:
416
+ return None
417
+ out = {}
418
+ for k, v in s.items():
419
+ if isinstance(v, Mapping):
420
+ out[str(camel_to_snake(k))] = camel_to_snake_dict(v)
421
+ else:
422
+ out[str(camel_to_snake(k))] = v
423
+ return out