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,950 @@
1
+ #!/usr/bin/env python
2
+ #
3
+ # Copyright SAS Institute
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the License);
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ # This file was originally copied from https://github.com/sassoftware/python-swat.
18
+ #
19
+ """
20
+ Generalized interface for configuring, setting, and getting options.
21
+
22
+ Options can be set and retrieved using set_option(...), get_option(...), and
23
+ reset_option(...). The describe_option(...) function can be used to display
24
+ a description of one or more options.
25
+
26
+ """
27
+ import contextlib
28
+ import os
29
+ import re
30
+ from collections.abc import Iterator
31
+ from collections.abc import Mapping
32
+ from typing import Any
33
+ from typing import Callable
34
+ from typing import Dict
35
+ from typing import List
36
+ from typing import Optional
37
+ from typing import Tuple
38
+ from typing import Union
39
+ from urllib.parse import urlparse
40
+
41
+ from .xdict import xdict
42
+
43
+
44
+ # Container for options
45
+ _config = xdict()
46
+
47
+ items_types = (list, tuple, set)
48
+
49
+
50
+ def _getenv(names: Union[str, List[str]], *args: Any) -> str:
51
+ """
52
+ Check for multiple environment variable values.
53
+
54
+ Two forms of the environment variable name will be checked,
55
+ both with and without underscores. This allows for aliases
56
+ such as CAS_HOST and CASHOST.
57
+
58
+ Parameters
59
+ ----------
60
+ names : str or list of str
61
+ Names of environment variables to look for
62
+ *args : any, optional
63
+ The default return value if no matching environment
64
+ variables exist
65
+
66
+ Returns
67
+ -------
68
+ string or default value
69
+
70
+ """
71
+ if not isinstance(names, items_types):
72
+ names = [names]
73
+ for name in names:
74
+ if name in os.environ:
75
+ return os.environ[name]
76
+ name = name.replace('_', '')
77
+ if name in os.environ:
78
+ return os.environ[name]
79
+ if args:
80
+ return args[0]
81
+ raise KeyError(names[0])
82
+
83
+
84
+ def _setenv(names: Union[str, List[str]], value: Any) -> None:
85
+ """
86
+ Set environment variable.
87
+
88
+ The environment is first checked for an existing variable
89
+ that is set. If it finds one, it uses that name.
90
+ If no variable is found, the first one in the `names`
91
+ list is used.
92
+
93
+ Just as with _getenv, the variable name is checked both
94
+ with and without underscores to allow aliases.
95
+
96
+ Parameters
97
+ ----------
98
+ names : str or list of str
99
+ Names of environment variable to look for
100
+ value : Any
101
+ The value to set
102
+
103
+ """
104
+ if not isinstance(names, items_types):
105
+ names = [names]
106
+ for name in names:
107
+ if name in os.environ:
108
+ os.environ[name] = value
109
+ name = name.replace('_', '')
110
+ if name in os.environ:
111
+ os.environ[name] = value
112
+
113
+
114
+ def _delenv(names: Union[str, List[str]]) -> None:
115
+ """Delete given environment variables."""
116
+ if not isinstance(names, items_types):
117
+ names = [names]
118
+ for name in names:
119
+ os.environ.pop(name, None)
120
+ os.environ.pop(name.replace('_', ''), None)
121
+
122
+
123
+ def iteroptions(*args: Any, **kwargs: Any) -> Iterator[Tuple[str, Any]]:
124
+ """
125
+ Iterate through name / value pairs of options
126
+
127
+ Options can come in several forms. They can be consecutive arguments
128
+ where the first argument is the name and the following argument is
129
+ the value. They can be two-element tuples (or lists) where the first
130
+ element is the name and the second element is the value. You can
131
+ also pass in a dictionary of key / value pairs. And finally, you can
132
+ use keyword arguments.
133
+
134
+ Parameters
135
+ ----------
136
+ *args : any, optional
137
+ See description above.
138
+ **kwargs : key / value pairs, optional
139
+ Arbitrary keyword arguments.
140
+
141
+ Returns
142
+ -------
143
+ generator
144
+ Each iteration returns a name / value pair in a tuple
145
+
146
+ """
147
+ items = list(args)
148
+ while items:
149
+ item = items.pop(0)
150
+ if isinstance(item, (list, tuple)):
151
+ yield item[0], item[1]
152
+ elif isinstance(item, dict):
153
+ for key, value in item.items():
154
+ yield key, value
155
+ else:
156
+ yield item, items.pop(0)
157
+ for key, value in kwargs.items():
158
+ yield key, value
159
+
160
+
161
+ @contextlib.contextmanager
162
+ def option_context(*args: Any, **kwargs: Any) -> Iterator[None]:
163
+ """
164
+ Create a context for setting option temporarily.
165
+
166
+ Parameters
167
+ ----------
168
+ *args : str / any pairs
169
+ Name / value pairs in consecutive arguments (not tuples)
170
+ **kwargs : dict
171
+ Key / value pairs of options
172
+
173
+ """
174
+ # Save old state and set new option values
175
+ oldstate = {}
176
+ for key, value in iteroptions(*args, **kwargs):
177
+ key = key.lower()
178
+ oldstate[key] = get_option(key)
179
+ set_option(key, value)
180
+
181
+ # Yield control
182
+ yield
183
+
184
+ # Set old state back
185
+ for key, value in oldstate.items():
186
+ set_option(key, value)
187
+
188
+
189
+ def _get_option_leaf_node(key: str) -> str:
190
+ """
191
+ Find full option name of given key.
192
+
193
+ Parameters
194
+ ----------
195
+ key : str
196
+ Either a partial key or full key name of an option
197
+
198
+ Returns
199
+ -------
200
+ str
201
+ The full key name of the option
202
+
203
+ Raises
204
+ ------
205
+ KeyError
206
+ If more than one option matches
207
+
208
+ """
209
+ flatkeys = list(_config.flatkeys())
210
+ key = key.lower()
211
+ if key in flatkeys:
212
+ return key
213
+ keys = [k for k in flatkeys if k.endswith('.' + key)]
214
+ if len(keys) > 1:
215
+ raise KeyError('There is more than one option with the name %s.' % key)
216
+ if not keys:
217
+ if '.' in key:
218
+ raise KeyError('%s is not a valid option name.' % key)
219
+ else:
220
+ raise TypeError('%s is not a valid option name.' % key)
221
+ return keys[0]
222
+
223
+
224
+ def set_option(*args: Any, **kwargs: Any) -> None:
225
+ """
226
+ Set the value of an option.
227
+
228
+ Parameters
229
+ ----------
230
+ *args : str or Any
231
+ The name and value of an option in consecutive arguments (not tuples)
232
+ **kwargs : dict
233
+ Arbitrary keyword / value pairs
234
+
235
+ """
236
+ for key, value in iteroptions(*args, **kwargs):
237
+ key = _get_option_leaf_node(key)
238
+ opt = _config[key]
239
+ if not isinstance(opt, Option):
240
+ raise TypeError('%s is not a valid option name' % key)
241
+ opt.set(value)
242
+
243
+
244
+ set_options = set_option
245
+
246
+
247
+ def get_option(key: str) -> Any:
248
+ """
249
+ Get the value of an option.
250
+
251
+ Parameters
252
+ ----------
253
+ key : str
254
+ The name of the option
255
+
256
+ Returns
257
+ -------
258
+ Any
259
+ The value of the option
260
+
261
+ """
262
+ key = _get_option_leaf_node(key)
263
+ opt = _config[key]
264
+ if not isinstance(opt, Option):
265
+ raise TypeError('%s is not a valid option name' % key)
266
+ return opt.get()
267
+
268
+
269
+ def get_suboptions(key: str) -> Dict[str, Any]:
270
+ """
271
+ Get the dictionary of options at the level `key`.
272
+
273
+ Parameters
274
+ ----------
275
+ key : str
276
+ The name of the option collection
277
+
278
+ Returns
279
+ -------
280
+ dict
281
+ The dictionary of options at level `key`
282
+
283
+ """
284
+ if key not in _config:
285
+ raise KeyError('%s is not a valid option name' % key)
286
+ opt = _config[key]
287
+ if isinstance(opt, Option):
288
+ raise TypeError('%s does not have sub-options' % key)
289
+ return opt
290
+
291
+
292
+ def get_default(key: str) -> Any:
293
+ """
294
+ Get the default value of an option.
295
+
296
+ Parameters
297
+ ----------
298
+ key : str
299
+ The name of the option
300
+
301
+ Returns
302
+ -------
303
+ Any
304
+ The default value of the option
305
+
306
+ """
307
+ key = _get_option_leaf_node(key)
308
+ opt = _config[key]
309
+ if not isinstance(opt, Option):
310
+ raise TypeError('%s is not a valid option name' % key)
311
+ return opt.get_default()
312
+
313
+
314
+ get_default_val = get_default
315
+
316
+
317
+ def describe_option(*keys: str, **kwargs: Any) -> Optional[str]:
318
+ """
319
+ Print the description of one or more options.
320
+
321
+ To print the descriptions of all options, execute this function
322
+ with no parameters.
323
+
324
+ Parameters
325
+ ----------
326
+ *keys : one or more strings
327
+ Names of the options
328
+
329
+ """
330
+ _print_desc = kwargs.get('_print_desc', True)
331
+
332
+ out = []
333
+
334
+ if not keys:
335
+ keys = tuple(sorted(_config.flatkeys()))
336
+ else:
337
+ newkeys = []
338
+ for k in keys:
339
+ try:
340
+ newkeys.append(_get_option_leaf_node(k))
341
+ except (KeyError, TypeError):
342
+ newkeys.append(k)
343
+
344
+ for key in keys:
345
+
346
+ if key not in _config:
347
+ raise KeyError('%s is not a valid option name' % key)
348
+
349
+ opt = _config[key]
350
+ if isinstance(opt, xdict):
351
+ desc = describe_option(
352
+ *[
353
+ '%s.%s' % (key, x)
354
+ for x in opt.flatkeys()
355
+ ], _print_desc=_print_desc,
356
+ )
357
+ if desc is not None:
358
+ out.append(desc)
359
+ continue
360
+
361
+ if _print_desc:
362
+ print(opt.__doc__)
363
+ print('')
364
+ else:
365
+ out.append(opt.__doc__)
366
+
367
+ if not _print_desc:
368
+ return '\n'.join(out)
369
+
370
+ return None
371
+
372
+
373
+ def reset_option(*keys: str) -> None:
374
+ """
375
+ Reset one or more options back to their default value.
376
+
377
+ Parameters
378
+ ----------
379
+ *keys : one or more strings
380
+ Names of options to reset
381
+
382
+ """
383
+ if not keys:
384
+ keys = tuple(sorted(_config.flatkeys()))
385
+ else:
386
+ keys = tuple([_get_option_leaf_node(k) for k in keys])
387
+
388
+ for key in keys:
389
+
390
+ if key not in _config:
391
+ raise KeyError('%s is not a valid option name' % key)
392
+
393
+ opt = _config[key]
394
+ if not isinstance(opt, Option):
395
+ raise TypeError('%s is not a valid option name' % key)
396
+
397
+ # Reset options
398
+ set_option(key, get_default(key))
399
+
400
+
401
+ def check_int(
402
+ value: Union[int, float, str],
403
+ minimum: Optional[int] = None,
404
+ maximum: Optional[int] = None,
405
+ exclusive_minimum: bool = False,
406
+ exclusive_maximum: bool = False,
407
+ multiple_of: Optional[int] = None,
408
+ ) -> int:
409
+ """
410
+ Validate an integer value.
411
+
412
+ Parameters
413
+ ----------
414
+ value : int or float
415
+ Value to validate
416
+ minimum : int, optional
417
+ The minimum value allowed
418
+ maximum : int, optional
419
+ The maximum value allowed
420
+ exclusive_minimum : bool, optional
421
+ Should the minimum value be excluded as an endpoint?
422
+ exclusive_maximum : bool, optional
423
+ Should the maximum value be excluded as an endpoint?
424
+ multiple_of : int, optional
425
+ If specified, the value must be a multple of it in order for
426
+ the value to be considered valid.
427
+
428
+ Returns
429
+ -------
430
+ int
431
+ The validated integer value
432
+
433
+ """
434
+ out = int(value)
435
+
436
+ if minimum is not None:
437
+ if out < minimum:
438
+ raise ValueError(
439
+ '%s is smaller than the minimum value of %s' %
440
+ (out, minimum),
441
+ )
442
+ if exclusive_minimum and out == minimum:
443
+ raise ValueError(
444
+ '%s is equal to the exclusive nimum value of %s' %
445
+ (out, minimum),
446
+ )
447
+
448
+ if maximum is not None:
449
+ if out > maximum:
450
+ raise ValueError(
451
+ '%s is larger than the maximum value of %s' %
452
+ (out, maximum),
453
+ )
454
+ if exclusive_maximum and out == maximum:
455
+ raise ValueError(
456
+ '%s is equal to the exclusive maximum value of %s' %
457
+ (out, maximum),
458
+ )
459
+
460
+ if multiple_of is not None and (out % int(multiple_of)) != 0:
461
+ raise ValueError('%s is not a multiple of %s' % (out, multiple_of))
462
+
463
+ return out
464
+
465
+
466
+ def check_float(
467
+ value: Union[float, int, str],
468
+ minimum: Optional[Union[float, int]] = None,
469
+ maximum: Optional[Union[float, int]] = None,
470
+ exclusive_minimum: bool = False,
471
+ exclusive_maximum: bool = False,
472
+ multiple_of: Optional[Union[float, int]] = None,
473
+ ) -> float:
474
+ """
475
+ Validate a floating point value.
476
+
477
+ Parameters
478
+ ----------
479
+ value : int or float
480
+ Value to validate
481
+ minimum : int or float, optional
482
+ The minimum value allowed
483
+ maximum : int or float, optional
484
+ The maximum value allowed
485
+ exclusive_minimum : bool, optional
486
+ Should the minimum value be excluded as an endpoint?
487
+ exclusive_maximum : bool, optional
488
+ Should the maximum value be excluded as an endpoint?
489
+ multiple_of : int or float, optional
490
+ If specified, the value must be a multple of it in order for
491
+ the value to be considered valid.
492
+
493
+ Returns
494
+ -------
495
+ float
496
+ The validated floating point value
497
+
498
+ """
499
+ out = float(value)
500
+
501
+ if minimum is not None:
502
+ if out < minimum:
503
+ raise ValueError(
504
+ '%s is smaller than the minimum value of %s' %
505
+ (out, minimum),
506
+ )
507
+ if exclusive_minimum and out == minimum:
508
+ raise ValueError(
509
+ '%s is equal to the exclusive nimum value of %s' %
510
+ (out, minimum),
511
+ )
512
+
513
+ if maximum is not None:
514
+ if out > maximum:
515
+ raise ValueError(
516
+ '%s is larger than the maximum value of %s' %
517
+ (out, maximum),
518
+ )
519
+ if exclusive_maximum and out == maximum:
520
+ raise ValueError(
521
+ '%s is equal to the exclusive maximum value of %s' %
522
+ (out, maximum),
523
+ )
524
+
525
+ if multiple_of is not None and (out % int(multiple_of)) != 0:
526
+ raise ValueError('%s is not a multiple of %s' % (out, multiple_of))
527
+
528
+ return out
529
+
530
+
531
+ def check_bool(value: Union[bool, int]) -> bool:
532
+ """
533
+ Validate a bool value.
534
+
535
+ Parameters
536
+ ----------
537
+ value : int or bool
538
+ The value to validate. If specified as an integer, it must
539
+ be either 0 for False or 1 for True.
540
+
541
+ Returns
542
+ -------
543
+ bool
544
+ The validated bool
545
+
546
+ """
547
+ if value is False or value is True:
548
+ return value
549
+
550
+ if isinstance(value, int):
551
+ if value == 1:
552
+ return True
553
+ if value == 0:
554
+ return False
555
+
556
+ if isinstance(value, (str, bytes)):
557
+ value = str(value)
558
+ if value.lower() in ['y', 'yes', 'on', 't', 'true', 'enable', 'enabled', '1']:
559
+ return True
560
+ if value.lower() in ['n', 'no', 'off', 'f', 'false', 'disable', 'disabled', '0']:
561
+ return False
562
+
563
+ raise ValueError('%s is not a recognized bool value')
564
+
565
+
566
+ def check_optional_bool(value: Optional[Union[bool, int]]) -> Optional[bool]:
567
+ """
568
+ Validate an optional bool value.
569
+
570
+ Parameters
571
+ ----------
572
+ value : int or bool or None
573
+ The value to validate. If specified as an integer, it must
574
+ be either 0 for False or 1 for True.
575
+
576
+ Returns
577
+ -------
578
+ bool
579
+ The validated bool
580
+
581
+ """
582
+ if value is None:
583
+ return None
584
+
585
+ return check_bool(value)
586
+
587
+
588
+ def check_str(
589
+ value: Any,
590
+ pattern: Optional[str] = None,
591
+ max_length: Optional[int] = None,
592
+ min_length: Optional[int] = None,
593
+ valid_values: Optional[List[str]] = None,
594
+ ) -> Optional[str]:
595
+ """
596
+ Validate a string value.
597
+
598
+ Parameters
599
+ ----------
600
+ value : string
601
+ The value to validate
602
+ pattern : regular expression string, optional
603
+ A regular expression used to validate string values
604
+ max_length : int, optional
605
+ The maximum length of the string
606
+ min_length : int, optional
607
+ The minimum length of the string
608
+ valid_values : list of strings, optional
609
+ List of the only possible values
610
+
611
+ Returns
612
+ -------
613
+ string
614
+ The validated string value
615
+
616
+ """
617
+ if value is None:
618
+ return None
619
+
620
+ if isinstance(value, str):
621
+ out = value
622
+ else:
623
+ out = str(value, 'utf-8')
624
+
625
+ if max_length is not None and len(out) > max_length:
626
+ raise ValueError(
627
+ '%s is longer than the maximum length of %s' %
628
+ (out, max_length),
629
+ )
630
+
631
+ if min_length is not None and len(out) < min_length:
632
+ raise ValueError(
633
+ '%s is shorter than the minimum length of %s' %
634
+ (out, min_length),
635
+ )
636
+
637
+ if pattern is not None and not re.search(pattern, out):
638
+ raise ValueError('%s does not match pattern %s' % (out, pattern))
639
+
640
+ if valid_values is not None and out not in valid_values:
641
+ raise ValueError(
642
+ '%s is not one of the possible values: %s' %
643
+ (out, ', '.join(valid_values)),
644
+ )
645
+
646
+ return out
647
+
648
+
649
+ def check_dict_str_str(
650
+ value: Any,
651
+ ) -> Optional[Dict[str, str]]:
652
+ """
653
+ Validate a string value.
654
+
655
+ Parameters
656
+ ----------
657
+ value : dict
658
+ The value to validate. Keys and values must be strings.
659
+
660
+ Returns
661
+ -------
662
+ dict
663
+ The validated dict value
664
+ """
665
+ if value is None:
666
+ return None
667
+
668
+ if not isinstance(value, Mapping):
669
+ raise ValueError(
670
+ 'value {} must be of type dict'.format(value),
671
+ )
672
+
673
+ out = {}
674
+ for k, v in value.items():
675
+ if not isinstance(k, str) or not isinstance(v, str):
676
+ raise ValueError(
677
+ 'keys and values in {} must be strings'.format(value),
678
+ )
679
+ out[k] = v
680
+
681
+ return out
682
+
683
+
684
+ def check_url(
685
+ value: str,
686
+ pattern: Optional[str] = None,
687
+ max_length: Optional[int] = None,
688
+ min_length: Optional[int] = None,
689
+ valid_values: Optional[List[str]] = None,
690
+ ) -> Optional[str]:
691
+ """
692
+ Validate a URL value.
693
+
694
+ Parameters
695
+ ----------
696
+ value : any
697
+ The value to validate. This value will be cast to a string
698
+ and converted to unicode.
699
+ pattern : regular expression string, optional
700
+ A regular expression used to validate string values
701
+ max_length : int, optional
702
+ The maximum length of the string
703
+ min_length : int, optional
704
+ The minimum length of the string
705
+ valid_values : list of strings, optional
706
+ List of the only possible values
707
+
708
+ Returns
709
+ -------
710
+ string
711
+ The validated URL value
712
+
713
+ """
714
+ if value is None:
715
+ return None
716
+
717
+ out = check_str(
718
+ value, pattern=pattern, max_length=max_length,
719
+ min_length=min_length, valid_values=valid_values,
720
+ )
721
+ try:
722
+ urlparse(out)
723
+ except Exception:
724
+ raise TypeError('%s is not a valid URL' % value)
725
+ return out
726
+
727
+
728
+ class Option(object):
729
+ """
730
+ Configuration option.
731
+
732
+ Parameters
733
+ ----------
734
+ name : str
735
+ The name of the option
736
+ typedesc : str
737
+ Description of the option data type (e.g., int, float, string)
738
+ validator : callable
739
+ A callable object that validates the option value and returns
740
+ the validated value.
741
+ default : any
742
+ The default value of the option
743
+ doc : str
744
+ The documentation string for the option
745
+ environ : str or list of strs, optional
746
+ If specified, the value should be specified in an environment
747
+ variable of that name.
748
+
749
+ """
750
+
751
+ def __init__(
752
+ self,
753
+ name: str,
754
+ typedesc: str,
755
+ validator: Callable[[str], Any],
756
+ default: Any,
757
+ doc: str,
758
+ environ: Optional[Union[str, List[str]]] = None,
759
+ ):
760
+ self._name = name
761
+ self._typedesc = typedesc
762
+ self._validator = validator
763
+ if environ is not None:
764
+ self._default = validator(_getenv(environ, default))
765
+ else:
766
+ self._default = validator(default)
767
+ self._environ = environ
768
+ self._value = self._default
769
+ self._doc = doc
770
+
771
+ @property
772
+ def __doc__(self) -> str: # type: ignore
773
+ """Generate documentation string."""
774
+ separator = ' '
775
+ if isinstance(self._value, (str, bytes)) and len(self._value) > 40:
776
+ separator = '\n '
777
+ return '''%s : %s\n %s\n [default: %s]%s[currently: %s]\n''' % \
778
+ (
779
+ self._name, self._typedesc, self._doc.rstrip().replace('\n', '\n '),
780
+ self._default, separator, str(self._value),
781
+ )
782
+
783
+ def set(self, value: Any) -> None:
784
+ """
785
+ Set the value of the option.
786
+
787
+ Parameters
788
+ ----------
789
+ value : any
790
+ The value to set
791
+
792
+ """
793
+ value = self._validator(value)
794
+ _config[self._name]._value = value
795
+
796
+ if self._environ is not None:
797
+ if value is None:
798
+ _delenv(self._environ)
799
+ else:
800
+ _setenv(self._environ, str(value))
801
+
802
+ def get(self) -> Any:
803
+ """
804
+ Get the value of the option.
805
+
806
+ Returns
807
+ -------
808
+ Any
809
+ The value of the option
810
+
811
+ """
812
+ if self._environ is not None:
813
+ try:
814
+ _config[self._name]._value = self._validator(_getenv(self._environ))
815
+ except KeyError:
816
+ pass
817
+ return _config[self._name]._value
818
+
819
+ def get_default(self) -> Any:
820
+ """
821
+ Get the default value of the option.
822
+
823
+ Returns
824
+ -------
825
+ Any
826
+ The default value of the option
827
+
828
+ """
829
+ return _config[self._name]._default
830
+
831
+
832
+ def register_option(
833
+ key: str,
834
+ typedesc: str,
835
+ validator: Callable[[Any], Any],
836
+ default: Any,
837
+ doc: str,
838
+ environ: Optional[Union[str, List[str]]] = None,
839
+ ) -> None:
840
+ """
841
+ Register a new option.
842
+
843
+ Parameters
844
+ ----------
845
+ key : str
846
+ The name of the option
847
+ typedesc : str
848
+ Description of option data type (e.g., int, float, string)
849
+ validator : callable
850
+ A callable object that validates the value and returns
851
+ a validated value.
852
+ default : any
853
+ The default value of the option
854
+ doc : str
855
+ The documentation string for the option
856
+ environ : str or list of strs, optional
857
+ If specified, the value should be specified in an environment
858
+ variable of that name.
859
+
860
+ """
861
+ import warnings
862
+ with warnings.catch_warnings():
863
+ warnings.simplefilter('ignore')
864
+ _config[key] = Option(key, typedesc, validator, default, doc, environ=environ)
865
+
866
+
867
+ class AttrOption(object):
868
+ """
869
+ Attribute-style access of options.
870
+
871
+ Parameters
872
+ ----------
873
+ name : str
874
+ Name of the option
875
+
876
+ """
877
+
878
+ def __init__(self, name: str):
879
+ object.__setattr__(self, '_name', name)
880
+
881
+ def __dir__(self) -> List[str]:
882
+ """Return list of flattened keys."""
883
+ if self._name in _config:
884
+ return _config[self._name].flatkeys()
885
+ return _config.flatkeys()
886
+
887
+ @property
888
+ def __doc__(self) -> Optional[str]: # type: ignore
889
+ if self._name:
890
+ return describe_option(self._name, _print_desc=False)
891
+ return describe_option(_print_desc=False)
892
+
893
+ def __getattr__(self, name: str) -> Any:
894
+ """
895
+ Retieve option as an attribute.
896
+
897
+ Parameters
898
+ ----------
899
+ name : str
900
+ Name of the option
901
+
902
+ Returns
903
+ -------
904
+ Any
905
+
906
+ """
907
+ name = name.lower()
908
+ if self._name:
909
+ fullname = self._name + '.' + name
910
+ else:
911
+ fullname = name
912
+ if fullname not in _config:
913
+ fullname = _get_option_leaf_node(fullname)
914
+ out = _config[fullname]
915
+ if not isinstance(out, Option):
916
+ return type(self)(fullname)
917
+ return out.get()
918
+
919
+ def __setattr__(self, name: str, value: Any) -> Any:
920
+ """
921
+ Set an attribute value.
922
+
923
+ Parameters
924
+ ----------
925
+ name : str
926
+ Name of the option
927
+ value : Any
928
+ Value of the option
929
+
930
+ """
931
+ name = name.lower()
932
+ if self._name:
933
+ fullname = self._name + '.' + name
934
+ else:
935
+ fullname = name
936
+ if fullname not in _config:
937
+ fullname = _get_option_leaf_node(fullname)
938
+ out = _config[fullname]
939
+ if not isinstance(out, Option):
940
+ return type(self)(fullname)
941
+ _config[fullname].set(value)
942
+ return
943
+
944
+ def __call__(self, *args: Any, **kwargs: Any) -> Iterator[None]:
945
+ """Shortcut for option context."""
946
+ return option_context(*args, **kwargs) # type: ignore
947
+
948
+
949
+ # Object for setting and getting options using attribute syntax
950
+ options = AttrOption('')