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.
- singlestoredb/__init__.py +75 -0
- singlestoredb/ai/__init__.py +2 -0
- singlestoredb/ai/chat.py +139 -0
- singlestoredb/ai/embeddings.py +128 -0
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/apps/__init__.py +3 -0
- singlestoredb/apps/_cloud_functions.py +90 -0
- singlestoredb/apps/_config.py +72 -0
- singlestoredb/apps/_connection_info.py +18 -0
- singlestoredb/apps/_dashboards.py +47 -0
- singlestoredb/apps/_process.py +32 -0
- singlestoredb/apps/_python_udfs.py +100 -0
- singlestoredb/apps/_stdout_supress.py +30 -0
- singlestoredb/apps/_uvicorn_util.py +36 -0
- singlestoredb/auth.py +245 -0
- singlestoredb/config.py +484 -0
- singlestoredb/connection.py +1487 -0
- singlestoredb/converters.py +950 -0
- singlestoredb/docstring/__init__.py +33 -0
- singlestoredb/docstring/attrdoc.py +126 -0
- singlestoredb/docstring/common.py +230 -0
- singlestoredb/docstring/epydoc.py +267 -0
- singlestoredb/docstring/google.py +412 -0
- singlestoredb/docstring/numpydoc.py +562 -0
- singlestoredb/docstring/parser.py +100 -0
- singlestoredb/docstring/py.typed +1 -0
- singlestoredb/docstring/rest.py +256 -0
- singlestoredb/docstring/tests/__init__.py +1 -0
- singlestoredb/docstring/tests/_pydoctor.py +21 -0
- singlestoredb/docstring/tests/test_epydoc.py +729 -0
- singlestoredb/docstring/tests/test_google.py +1007 -0
- singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
- singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
- singlestoredb/docstring/tests/test_parser.py +248 -0
- singlestoredb/docstring/tests/test_rest.py +547 -0
- singlestoredb/docstring/tests/test_util.py +70 -0
- singlestoredb/docstring/util.py +141 -0
- singlestoredb/exceptions.py +120 -0
- singlestoredb/functions/__init__.py +16 -0
- singlestoredb/functions/decorator.py +201 -0
- singlestoredb/functions/dtypes.py +1793 -0
- singlestoredb/functions/ext/__init__.py +1 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +2133 -0
- singlestoredb/functions/ext/json.py +420 -0
- singlestoredb/functions/ext/mmap.py +413 -0
- singlestoredb/functions/ext/rowdat_1.py +724 -0
- singlestoredb/functions/ext/timer.py +89 -0
- singlestoredb/functions/ext/utils.py +218 -0
- singlestoredb/functions/signature.py +1578 -0
- singlestoredb/functions/typing/__init__.py +41 -0
- singlestoredb/functions/typing/numpy.py +20 -0
- singlestoredb/functions/typing/pandas.py +2 -0
- singlestoredb/functions/typing/polars.py +2 -0
- singlestoredb/functions/typing/pyarrow.py +2 -0
- singlestoredb/functions/utils.py +421 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +916 -0
- singlestoredb/fusion/handlers/__init__.py +0 -0
- singlestoredb/fusion/handlers/export.py +525 -0
- singlestoredb/fusion/handlers/files.py +690 -0
- singlestoredb/fusion/handlers/job.py +660 -0
- singlestoredb/fusion/handlers/models.py +250 -0
- singlestoredb/fusion/handlers/stage.py +502 -0
- singlestoredb/fusion/handlers/utils.py +324 -0
- singlestoredb/fusion/handlers/workspace.py +956 -0
- singlestoredb/fusion/registry.py +249 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/http/connection.py +1267 -0
- singlestoredb/magics/__init__.py +34 -0
- singlestoredb/magics/run_personal.py +137 -0
- singlestoredb/magics/run_shared.py +134 -0
- singlestoredb/management/__init__.py +9 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +462 -0
- singlestoredb/management/export.py +295 -0
- singlestoredb/management/files.py +1102 -0
- singlestoredb/management/inference_api.py +105 -0
- singlestoredb/management/job.py +887 -0
- singlestoredb/management/manager.py +373 -0
- singlestoredb/management/organization.py +226 -0
- singlestoredb/management/region.py +169 -0
- singlestoredb/management/utils.py +423 -0
- singlestoredb/management/workspace.py +1927 -0
- singlestoredb/mysql/__init__.py +177 -0
- singlestoredb/mysql/_auth.py +298 -0
- singlestoredb/mysql/charset.py +214 -0
- singlestoredb/mysql/connection.py +2032 -0
- singlestoredb/mysql/constants/CLIENT.py +38 -0
- singlestoredb/mysql/constants/COMMAND.py +32 -0
- singlestoredb/mysql/constants/CR.py +78 -0
- singlestoredb/mysql/constants/ER.py +474 -0
- singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
- singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
- singlestoredb/mysql/constants/FLAG.py +15 -0
- singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
- singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
- singlestoredb/mysql/constants/__init__.py +0 -0
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/mysql/cursors.py +896 -0
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/mysql/optionfile.py +20 -0
- singlestoredb/mysql/protocol.py +450 -0
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/mysql/tests/base.py +126 -0
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/mysql/tests/test_DictCursor.py +132 -0
- singlestoredb/mysql/tests/test_SSCursor.py +141 -0
- singlestoredb/mysql/tests/test_basic.py +452 -0
- singlestoredb/mysql/tests/test_connection.py +851 -0
- singlestoredb/mysql/tests/test_converters.py +58 -0
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/mysql/tests/test_err.py +16 -0
- singlestoredb/mysql/tests/test_issues.py +514 -0
- singlestoredb/mysql/tests/test_load_local.py +75 -0
- singlestoredb/mysql/tests/test_nextset.py +88 -0
- singlestoredb/mysql/tests/test_optionfile.py +27 -0
- singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
- singlestoredb/mysql/times.py +23 -0
- singlestoredb/notebook/__init__.py +16 -0
- singlestoredb/notebook/_objects.py +213 -0
- singlestoredb/notebook/_portal.py +352 -0
- singlestoredb/py.typed +0 -0
- singlestoredb/pytest.py +352 -0
- singlestoredb/server/__init__.py +0 -0
- singlestoredb/server/docker.py +452 -0
- singlestoredb/server/free_tier.py +267 -0
- singlestoredb/tests/__init__.py +0 -0
- singlestoredb/tests/alltypes.sql +307 -0
- singlestoredb/tests/alltypes_no_nulls.sql +208 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +702 -0
- singlestoredb/tests/local_infile.csv +3 -0
- singlestoredb/tests/test.ipynb +18 -0
- singlestoredb/tests/test.sql +680 -0
- singlestoredb/tests/test2.ipynb +18 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +1332 -0
- singlestoredb/tests/test_config.py +318 -0
- singlestoredb/tests/test_connection.py +3103 -0
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_exceptions.py +45 -0
- singlestoredb/tests/test_ext_func.py +1472 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +1527 -0
- singlestoredb/tests/test_http.py +288 -0
- singlestoredb/tests/test_management.py +1599 -0
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +171 -0
- singlestoredb/tests/test_types.py +132 -0
- singlestoredb/tests/test_udf.py +737 -0
- singlestoredb/tests/test_udf_returns.py +459 -0
- singlestoredb/tests/test_vectorstore.py +51 -0
- singlestoredb/tests/test_xdict.py +333 -0
- singlestoredb/tests/utils.py +141 -0
- singlestoredb/types.py +373 -0
- singlestoredb/utils/__init__.py +0 -0
- singlestoredb/utils/config.py +950 -0
- singlestoredb/utils/convert_rows.py +69 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/dtypes.py +205 -0
- singlestoredb/utils/events.py +65 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +585 -0
- singlestoredb/utils/xdict.py +425 -0
- singlestoredb/vectorstore.py +192 -0
- singlestoredb/warnings.py +5 -0
- singlestoredb-1.16.1.dist-info/METADATA +165 -0
- singlestoredb-1.16.1.dist-info/RECORD +183 -0
- singlestoredb-1.16.1.dist-info/WHEEL +5 -0
- singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
- singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
- singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
- sqlx/__init__.py +4 -0
- 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('')
|