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,89 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RoundedFloatEncoder(json.JSONEncoder):
|
|
9
|
+
|
|
10
|
+
def encode(self, obj: Any) -> str:
|
|
11
|
+
if isinstance(obj, dict):
|
|
12
|
+
return '{' + ', '.join(
|
|
13
|
+
f'"{k}": {self._format_value(v)}'
|
|
14
|
+
for k, v in obj.items()
|
|
15
|
+
) + '}'
|
|
16
|
+
return super().encode(obj)
|
|
17
|
+
|
|
18
|
+
def _format_value(self, value: Any) -> str:
|
|
19
|
+
if isinstance(value, float):
|
|
20
|
+
return f'{value:.2f}'
|
|
21
|
+
return json.dumps(value)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Timer:
|
|
25
|
+
"""
|
|
26
|
+
Timer context manager that supports nested timing using a stack.
|
|
27
|
+
|
|
28
|
+
Example
|
|
29
|
+
-------
|
|
30
|
+
timer = Timer()
|
|
31
|
+
|
|
32
|
+
with timer('total'):
|
|
33
|
+
with timer('receive_data'):
|
|
34
|
+
time.sleep(0.1)
|
|
35
|
+
with timer('parse_input'):
|
|
36
|
+
time.sleep(0.2)
|
|
37
|
+
with timer('call_function'):
|
|
38
|
+
with timer('inner_operation'):
|
|
39
|
+
time.sleep(0.05)
|
|
40
|
+
time.sleep(0.3)
|
|
41
|
+
|
|
42
|
+
print(timer.metrics)
|
|
43
|
+
# {'receive_data': 0.1, 'parse_input': 0.2, 'inner_operation': 0.05,
|
|
44
|
+
# 'call_function': 0.35, 'total': 0.65}
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
49
|
+
self.metadata: Dict[str, Any] = kwargs
|
|
50
|
+
self.metrics: Dict[str, float] = dict()
|
|
51
|
+
self.entries: Dict[str, float] = dict()
|
|
52
|
+
self._current_key: Optional[str] = None
|
|
53
|
+
self.start_time = time.perf_counter()
|
|
54
|
+
|
|
55
|
+
def __call__(self, key: str) -> 'Timer':
|
|
56
|
+
self._current_key = key
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def __enter__(self) -> 'Timer':
|
|
60
|
+
if self._current_key is None:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
"No key specified. Use timer('key_name') as context manager.",
|
|
63
|
+
)
|
|
64
|
+
self.entries[self._current_key] = time.perf_counter()
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
68
|
+
key = self._current_key
|
|
69
|
+
if key and key in self.entries:
|
|
70
|
+
start = self.entries.pop(key)
|
|
71
|
+
elapsed = time.perf_counter() - start
|
|
72
|
+
self.metrics[key] = elapsed
|
|
73
|
+
self._current_key = None
|
|
74
|
+
|
|
75
|
+
async def __aenter__(self) -> 'Timer':
|
|
76
|
+
return self.__enter__()
|
|
77
|
+
|
|
78
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
79
|
+
self.__exit__(exc_type, exc_val, exc_tb)
|
|
80
|
+
|
|
81
|
+
def reset(self) -> None:
|
|
82
|
+
self.metrics.clear()
|
|
83
|
+
self.entries.clear()
|
|
84
|
+
self._current_key = None
|
|
85
|
+
|
|
86
|
+
def finish(self) -> Dict[str, Any]:
|
|
87
|
+
"""Finish the current timing context and store the elapsed time."""
|
|
88
|
+
self.metrics['total'] = time.perf_counter() - self.start_time
|
|
89
|
+
return dict(type='function_metrics', **self.metadata, **self.metrics)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
import zipfile
|
|
8
|
+
from copy import copy
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import Dict
|
|
11
|
+
from typing import List
|
|
12
|
+
from typing import Union
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import tomllib
|
|
16
|
+
except ImportError:
|
|
17
|
+
import tomli as tomllib # type: ignore
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from uvicorn.logging import DefaultFormatter
|
|
21
|
+
|
|
22
|
+
except ImportError:
|
|
23
|
+
|
|
24
|
+
class DefaultFormatter(logging.Formatter): # type: ignore
|
|
25
|
+
|
|
26
|
+
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
27
|
+
recordcopy = copy(record)
|
|
28
|
+
levelname = recordcopy.levelname
|
|
29
|
+
seperator = ' ' * (8 - len(recordcopy.levelname))
|
|
30
|
+
recordcopy.__dict__['levelprefix'] = levelname + ':' + seperator
|
|
31
|
+
return super().formatMessage(recordcopy)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class JSONFormatter(logging.Formatter):
|
|
35
|
+
"""Custom JSON formatter for structured logging."""
|
|
36
|
+
|
|
37
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
38
|
+
# Create proper ISO timestamp with microseconds
|
|
39
|
+
timestamp = datetime.datetime.fromtimestamp(
|
|
40
|
+
record.created, tz=datetime.timezone.utc,
|
|
41
|
+
)
|
|
42
|
+
# Keep only 3 digits for milliseconds
|
|
43
|
+
iso_timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
|
|
44
|
+
|
|
45
|
+
log_entry = {
|
|
46
|
+
'timestamp': iso_timestamp,
|
|
47
|
+
'level': record.levelname,
|
|
48
|
+
'logger': record.name,
|
|
49
|
+
'message': record.getMessage(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Add extra fields if present
|
|
53
|
+
allowed_fields = [
|
|
54
|
+
'app_name', 'request_id', 'function_name',
|
|
55
|
+
'content_type', 'accepts', 'metrics',
|
|
56
|
+
]
|
|
57
|
+
for field in allowed_fields:
|
|
58
|
+
if hasattr(record, field):
|
|
59
|
+
log_entry[field] = getattr(record, field)
|
|
60
|
+
|
|
61
|
+
# Add exception info if present
|
|
62
|
+
if record.exc_info:
|
|
63
|
+
log_entry['exception'] = self.formatException(record.exc_info)
|
|
64
|
+
|
|
65
|
+
return json.dumps(log_entry)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_logger(name: str) -> logging.Logger:
|
|
69
|
+
"""Return a logger with JSON formatting."""
|
|
70
|
+
logger = logging.getLogger(name)
|
|
71
|
+
|
|
72
|
+
# Only configure if not already configured with JSON formatter
|
|
73
|
+
has_json_formatter = any(
|
|
74
|
+
isinstance(getattr(handler, 'formatter', None), JSONFormatter)
|
|
75
|
+
for handler in logger.handlers
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if not logger.handlers or not has_json_formatter:
|
|
79
|
+
# Clear handlers only if we need to reconfigure
|
|
80
|
+
logger.handlers.clear()
|
|
81
|
+
handler = logging.StreamHandler()
|
|
82
|
+
formatter = JSONFormatter()
|
|
83
|
+
handler.setFormatter(formatter)
|
|
84
|
+
logger.addHandler(handler)
|
|
85
|
+
logger.setLevel(logging.INFO)
|
|
86
|
+
|
|
87
|
+
# Prevent propagation to avoid duplicate messages or different formatting
|
|
88
|
+
logger.propagate = False
|
|
89
|
+
|
|
90
|
+
return logger
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def read_config(
|
|
94
|
+
archive: str,
|
|
95
|
+
keys: Union[str, List[str]],
|
|
96
|
+
config_file: str = 'pyproject.toml',
|
|
97
|
+
) -> Dict[str, Any]:
|
|
98
|
+
"""
|
|
99
|
+
Read a key from a Toml config file.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
archive : str
|
|
104
|
+
Path to an environment file
|
|
105
|
+
keys : str or List[str]
|
|
106
|
+
Period-separated paths to the desired keys
|
|
107
|
+
config_file : str, optional
|
|
108
|
+
Name of the config file in the zip file
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
Dict[str, Any]
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
defaults = {}
|
|
116
|
+
keys = [keys] if isinstance(keys, str) else list(keys)
|
|
117
|
+
with zipfile.ZipFile(archive) as arc:
|
|
118
|
+
try:
|
|
119
|
+
orig_options = tomllib.loads(arc.read(config_file).decode('utf8'))
|
|
120
|
+
verify_python_version(orig_options)
|
|
121
|
+
for key in keys:
|
|
122
|
+
path = key.split('.')
|
|
123
|
+
options = orig_options
|
|
124
|
+
while path:
|
|
125
|
+
options = options.get(path.pop(0), {})
|
|
126
|
+
for k, v in options.items():
|
|
127
|
+
defaults[k.lower().replace('-', '_')] = v
|
|
128
|
+
except KeyError:
|
|
129
|
+
pass
|
|
130
|
+
return defaults
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def verify_python_version(options: Dict[str, Any]) -> None:
|
|
134
|
+
"""Verify the version of Python matches the pyproject.toml requirement."""
|
|
135
|
+
requires_python = options.get('project', {}).get('requires_python', None)
|
|
136
|
+
if not requires_python:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
m = re.match(r'\s*([<=>])+\s*((?:\d+\.)+\d+)\s*', requires_python)
|
|
140
|
+
if not m:
|
|
141
|
+
raise ValueError(f'python version string is not valid: {requires_python}')
|
|
142
|
+
|
|
143
|
+
operator = m.group(1)
|
|
144
|
+
version_info = tuple(int(x) for x in m.group(2))
|
|
145
|
+
|
|
146
|
+
if operator == '<=':
|
|
147
|
+
if not (sys.version_info <= version_info):
|
|
148
|
+
raise RuntimeError(
|
|
149
|
+
'python version is not compatible: ' +
|
|
150
|
+
f'{sys.version_info} > {m.group(2)}',
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
elif operator == '>=':
|
|
154
|
+
if not (sys.version_info >= version_info):
|
|
155
|
+
raise RuntimeError(
|
|
156
|
+
'python version is not compatible: ' +
|
|
157
|
+
f'{sys.version_info} < {m.group(2)}',
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
elif operator in ['==', '=']:
|
|
161
|
+
if not (sys.version_info == version_info):
|
|
162
|
+
raise RuntimeError(
|
|
163
|
+
'python version is not compatible: ' +
|
|
164
|
+
f'{sys.version_info} != {m.group(2)}',
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
elif operator == '>':
|
|
168
|
+
if not (sys.version_info > version_info):
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
'python version is not compatible: ' +
|
|
171
|
+
f'{sys.version_info} <= {m.group(2)}',
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
elif operator == '<':
|
|
175
|
+
if not (sys.version_info < version_info):
|
|
176
|
+
raise RuntimeError(
|
|
177
|
+
'python version is not compatible: ' +
|
|
178
|
+
f'{sys.version_info} >= {m.group(2)}',
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError(f'invalid python_version operator: {operator}')
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def to_toml(data: Dict[str, Any]) -> str:
|
|
186
|
+
"""Dump data to a pyproject.toml."""
|
|
187
|
+
out = []
|
|
188
|
+
for top_k, top_v in data.items():
|
|
189
|
+
if top_v is None:
|
|
190
|
+
continue
|
|
191
|
+
top_k = top_k.replace('_', '-')
|
|
192
|
+
out.append('')
|
|
193
|
+
out.append(f'[{top_k}]')
|
|
194
|
+
for k, v in top_v.items():
|
|
195
|
+
if v is None:
|
|
196
|
+
continue
|
|
197
|
+
k = k.replace('_', '-')
|
|
198
|
+
if isinstance(v, (tuple, list)):
|
|
199
|
+
out.append(f'{k} = [')
|
|
200
|
+
items = []
|
|
201
|
+
for item in v:
|
|
202
|
+
if item is None:
|
|
203
|
+
pass
|
|
204
|
+
elif isinstance(item, (tuple, list)):
|
|
205
|
+
items.append(f' {json.dumps(item)}')
|
|
206
|
+
elif isinstance(item, dict):
|
|
207
|
+
items.append(
|
|
208
|
+
re.sub(r'"([^"]+)":', r'\1 =', f' {json.dumps(item)}'),
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
items.append(f' {json.dumps([item])[1:-1]}')
|
|
212
|
+
out.append(',\n'.join(items))
|
|
213
|
+
out.append(']')
|
|
214
|
+
elif isinstance(v, dict):
|
|
215
|
+
out.append(re.sub(r'"([^"]+)":', r'\1 =', f' {json.dumps(v)}'))
|
|
216
|
+
else:
|
|
217
|
+
out.append(f'{k} = {json.dumps([v])[1:-1]}')
|
|
218
|
+
return '\n'.join(out).strip()
|