singlestoredb 1.15.0__py3-none-any.whl → 1.15.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.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- singlestoredb/__init__.py +1 -1
- singlestoredb/ai/chat.py +14 -0
- singlestoredb/config.py +6 -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/functions/decorator.py +19 -18
- singlestoredb/functions/ext/asgi.py +97 -11
- singlestoredb/functions/signature.py +374 -241
- singlestoredb/fusion/handlers/files.py +4 -4
- singlestoredb/fusion/handlers/models.py +1 -1
- singlestoredb/fusion/handlers/stage.py +4 -4
- singlestoredb/management/cluster.py +1 -1
- singlestoredb/management/manager.py +15 -5
- singlestoredb/management/region.py +12 -2
- singlestoredb/management/workspace.py +17 -25
- singlestoredb/tests/ext_funcs/__init__.py +39 -0
- singlestoredb/tests/test_connection.py +18 -8
- singlestoredb/tests/test_management.py +24 -57
- singlestoredb/tests/test_udf.py +43 -15
- {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/METADATA +1 -1
- {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/RECORD +42 -23
- {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/LICENSE +0 -0
- {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/WHEEL +0 -0
- {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/entry_points.txt +0 -0
- {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Utility functions for working with docstrings."""
|
|
2
|
+
import typing as T
|
|
3
|
+
from collections import ChainMap
|
|
4
|
+
from inspect import Signature
|
|
5
|
+
from itertools import chain
|
|
6
|
+
|
|
7
|
+
from .common import DocstringMeta
|
|
8
|
+
from .common import DocstringParam
|
|
9
|
+
from .common import DocstringReturns # noqa: F401
|
|
10
|
+
from .common import DocstringStyle
|
|
11
|
+
from .common import RenderingStyle
|
|
12
|
+
from .parser import compose
|
|
13
|
+
from .parser import parse
|
|
14
|
+
|
|
15
|
+
_Func = T.Callable[..., T.Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def combine_docstrings(
|
|
19
|
+
*others: _Func,
|
|
20
|
+
exclude: T.Iterable[T.Type[DocstringMeta]] = (),
|
|
21
|
+
style: DocstringStyle = DocstringStyle.AUTO,
|
|
22
|
+
rendering_style: RenderingStyle = RenderingStyle.COMPACT,
|
|
23
|
+
) -> _Func:
|
|
24
|
+
"""A function decorator that parses the docstrings from `others`,
|
|
25
|
+
programmatically combines them with the parsed docstring of the decorated
|
|
26
|
+
function, and replaces the docstring of the decorated function with the
|
|
27
|
+
composed result. Only parameters that are part of the decorated functions
|
|
28
|
+
signature are included in the combined docstring. When multiple sources for
|
|
29
|
+
a parameter or docstring metadata exists then the decorator will first
|
|
30
|
+
default to the wrapped function's value (when available) and otherwise use
|
|
31
|
+
the rightmost definition from ``others``.
|
|
32
|
+
|
|
33
|
+
The following example illustrates its usage:
|
|
34
|
+
|
|
35
|
+
>>> def fun1(a, b, c, d):
|
|
36
|
+
... '''short_description: fun1
|
|
37
|
+
...
|
|
38
|
+
... :param a: fun1
|
|
39
|
+
... :param b: fun1
|
|
40
|
+
... :return: fun1
|
|
41
|
+
... '''
|
|
42
|
+
>>> def fun2(b, c, d, e):
|
|
43
|
+
... '''short_description: fun2
|
|
44
|
+
...
|
|
45
|
+
... long_description: fun2
|
|
46
|
+
...
|
|
47
|
+
... :param b: fun2
|
|
48
|
+
... :param c: fun2
|
|
49
|
+
... :param e: fun2
|
|
50
|
+
... '''
|
|
51
|
+
>>> @combine_docstrings(fun1, fun2)
|
|
52
|
+
>>> def decorated(a, b, c, d, e, f):
|
|
53
|
+
... '''
|
|
54
|
+
... :param e: decorated
|
|
55
|
+
... :param f: decorated
|
|
56
|
+
... '''
|
|
57
|
+
>>> print(decorated.__doc__)
|
|
58
|
+
short_description: fun2
|
|
59
|
+
<BLANKLINE>
|
|
60
|
+
long_description: fun2
|
|
61
|
+
<BLANKLINE>
|
|
62
|
+
:param a: fun1
|
|
63
|
+
:param b: fun1
|
|
64
|
+
:param c: fun2
|
|
65
|
+
:param e: fun2
|
|
66
|
+
:param f: decorated
|
|
67
|
+
:returns: fun1
|
|
68
|
+
>>> @combine_docstrings(fun1, fun2, exclude=[DocstringReturns])
|
|
69
|
+
>>> def decorated(a, b, c, d, e, f): pass
|
|
70
|
+
>>> print(decorated.__doc__)
|
|
71
|
+
short_description: fun2
|
|
72
|
+
<BLANKLINE>
|
|
73
|
+
long_description: fun2
|
|
74
|
+
<BLANKLINE>
|
|
75
|
+
:param a: fun1
|
|
76
|
+
:param b: fun1
|
|
77
|
+
:param c: fun2
|
|
78
|
+
:param e: fun2
|
|
79
|
+
|
|
80
|
+
:param others: callables from which to parse docstrings.
|
|
81
|
+
:param exclude: an iterable of ``DocstringMeta`` subclasses to exclude when
|
|
82
|
+
combining docstrings.
|
|
83
|
+
:param style: style composed docstring. The default will infer the style
|
|
84
|
+
from the decorated function.
|
|
85
|
+
:param rendering_style: The rendering style used to compose a docstring.
|
|
86
|
+
:return: the decorated function with a modified docstring.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def wrapper(func: _Func) -> _Func:
|
|
90
|
+
sig = Signature.from_callable(func)
|
|
91
|
+
|
|
92
|
+
comb_doc = parse(func.__doc__ or '')
|
|
93
|
+
docs = [parse(other.__doc__ or '') for other in others] + [comb_doc]
|
|
94
|
+
params = dict(
|
|
95
|
+
ChainMap(
|
|
96
|
+
*(
|
|
97
|
+
{param.arg_name: param for param in doc.params}
|
|
98
|
+
for doc in docs
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
for doc in reversed(docs):
|
|
104
|
+
if not doc.short_description:
|
|
105
|
+
continue
|
|
106
|
+
comb_doc.short_description = doc.short_description
|
|
107
|
+
comb_doc.blank_after_short_description = (
|
|
108
|
+
doc.blank_after_short_description
|
|
109
|
+
)
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
for doc in reversed(docs):
|
|
113
|
+
if not doc.long_description:
|
|
114
|
+
continue
|
|
115
|
+
comb_doc.long_description = doc.long_description
|
|
116
|
+
comb_doc.blank_after_long_description = (
|
|
117
|
+
doc.blank_after_long_description
|
|
118
|
+
)
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
combined: T.Dict[T.Type[DocstringMeta], T.List[DocstringMeta]] = {}
|
|
122
|
+
for doc in docs:
|
|
123
|
+
metas: T.Dict[T.Type[DocstringMeta], T.List[DocstringMeta]] = {}
|
|
124
|
+
for meta in doc.meta:
|
|
125
|
+
meta_type = type(meta)
|
|
126
|
+
if meta_type in exclude:
|
|
127
|
+
continue
|
|
128
|
+
metas.setdefault(meta_type, []).append(meta)
|
|
129
|
+
for meta_type, meta_list in metas.items():
|
|
130
|
+
combined[meta_type] = meta_list
|
|
131
|
+
|
|
132
|
+
combined[DocstringParam] = [
|
|
133
|
+
params[name] for name in sig.parameters if name in params
|
|
134
|
+
]
|
|
135
|
+
comb_doc.meta = list(chain(*combined.values()))
|
|
136
|
+
func.__doc__ = compose(
|
|
137
|
+
comb_doc, style=style, rendering_style=rendering_style,
|
|
138
|
+
)
|
|
139
|
+
return func
|
|
140
|
+
|
|
141
|
+
return wrapper
|
|
@@ -45,23 +45,20 @@ def is_valid_type(obj: Any) -> bool:
|
|
|
45
45
|
return False
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
48
|
+
def is_sqlstr_callable(obj: Any) -> bool:
|
|
49
49
|
"""Check if the object is a valid callable for a parameter type."""
|
|
50
50
|
if not callable(obj):
|
|
51
51
|
return False
|
|
52
52
|
|
|
53
53
|
returns = utils.get_annotations(obj).get('return', None)
|
|
54
54
|
|
|
55
|
-
if inspect.isclass(returns) and issubclass(returns,
|
|
55
|
+
if inspect.isclass(returns) and issubclass(returns, SQLString):
|
|
56
56
|
return True
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
f'callable {obj} must return a str, '
|
|
60
|
-
f'but got {returns}',
|
|
61
|
-
)
|
|
58
|
+
return False
|
|
62
59
|
|
|
63
60
|
|
|
64
|
-
def expand_types(args: Any) -> Optional[
|
|
61
|
+
def expand_types(args: Any) -> Optional[List[Any]]:
|
|
65
62
|
"""Expand the types for the function arguments / return values."""
|
|
66
63
|
if args is None:
|
|
67
64
|
return None
|
|
@@ -70,28 +67,32 @@ def expand_types(args: Any) -> Optional[Union[List[str], Type[Any]]]:
|
|
|
70
67
|
if isinstance(args, str):
|
|
71
68
|
return [args]
|
|
72
69
|
|
|
73
|
-
# General way of accepting pydantic.BaseModel, NamedTuple, TypedDict
|
|
74
|
-
elif is_valid_type(args):
|
|
75
|
-
return args
|
|
76
|
-
|
|
77
70
|
# List of SQL strings or callables
|
|
78
71
|
elif isinstance(args, list):
|
|
79
|
-
new_args = []
|
|
72
|
+
new_args: List[Any] = []
|
|
80
73
|
for arg in args:
|
|
81
74
|
if isinstance(arg, str):
|
|
82
75
|
new_args.append(arg)
|
|
83
|
-
elif
|
|
76
|
+
elif is_sqlstr_callable(arg):
|
|
84
77
|
new_args.append(arg())
|
|
78
|
+
elif type(arg) is type:
|
|
79
|
+
new_args.append(arg)
|
|
80
|
+
elif is_valid_type(arg):
|
|
81
|
+
new_args.append(arg)
|
|
85
82
|
else:
|
|
86
83
|
raise TypeError(f'unrecognized type for parameter: {arg}')
|
|
87
84
|
return new_args
|
|
88
85
|
|
|
89
86
|
# Callable that returns a SQL string
|
|
90
|
-
elif
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
elif is_sqlstr_callable(args):
|
|
88
|
+
return [args()]
|
|
89
|
+
|
|
90
|
+
# General way of accepting pydantic.BaseModel, NamedTuple, TypedDict
|
|
91
|
+
elif is_valid_type(args):
|
|
92
|
+
return [args]
|
|
93
|
+
|
|
94
|
+
elif type(args) is type:
|
|
95
|
+
return [args]
|
|
95
96
|
|
|
96
97
|
raise TypeError(f'unrecognized type for parameter: {args}')
|
|
97
98
|
|
|
@@ -73,6 +73,8 @@ from ..signature import signature_to_sql
|
|
|
73
73
|
from ..typing import Masked
|
|
74
74
|
from ..typing import Table
|
|
75
75
|
from .timer import Timer
|
|
76
|
+
from singlestoredb.docstring.parser import parse
|
|
77
|
+
from singlestoredb.functions.dtypes import escape_name
|
|
76
78
|
|
|
77
79
|
try:
|
|
78
80
|
import cloudpickle
|
|
@@ -538,6 +540,8 @@ def make_func(
|
|
|
538
540
|
Name of the function to create
|
|
539
541
|
func : Callable
|
|
540
542
|
The function to call as the endpoint
|
|
543
|
+
database : str, optional
|
|
544
|
+
The database to use for the function definition
|
|
541
545
|
|
|
542
546
|
Returns
|
|
543
547
|
-------
|
|
@@ -615,7 +619,7 @@ async def cancel_on_disconnect(
|
|
|
615
619
|
"""Cancel request if client disconnects."""
|
|
616
620
|
while True:
|
|
617
621
|
message = await receive()
|
|
618
|
-
if message
|
|
622
|
+
if message.get('type', '') == 'http.disconnect':
|
|
619
623
|
raise asyncio.CancelledError(
|
|
620
624
|
'Function call was cancelled by client',
|
|
621
625
|
)
|
|
@@ -674,6 +678,8 @@ class Application(object):
|
|
|
674
678
|
link_credentials : Dict[str, Any], optional
|
|
675
679
|
The CREDENTIALS section of a LINK definition. This dictionary gets
|
|
676
680
|
converted to JSON for the CREATE LINK call.
|
|
681
|
+
function_database : str, optional
|
|
682
|
+
The database to use for external function definitions.
|
|
677
683
|
|
|
678
684
|
"""
|
|
679
685
|
|
|
@@ -816,6 +822,7 @@ class Application(object):
|
|
|
816
822
|
invoke_path = ('invoke',)
|
|
817
823
|
show_create_function_path = ('show', 'create_function')
|
|
818
824
|
show_function_info_path = ('show', 'function_info')
|
|
825
|
+
status = ('status',)
|
|
819
826
|
|
|
820
827
|
def __init__(
|
|
821
828
|
self,
|
|
@@ -838,6 +845,7 @@ class Application(object):
|
|
|
838
845
|
link_credentials: Optional[Dict[str, Any]] = None,
|
|
839
846
|
name_prefix: str = get_option('external_function.name_prefix'),
|
|
840
847
|
name_suffix: str = get_option('external_function.name_suffix'),
|
|
848
|
+
function_database: Optional[str] = None,
|
|
841
849
|
) -> None:
|
|
842
850
|
if link_name and (link_config or link_credentials):
|
|
843
851
|
raise ValueError(
|
|
@@ -944,6 +952,7 @@ class Application(object):
|
|
|
944
952
|
self.link_credentials = link_credentials
|
|
945
953
|
self.endpoints = endpoints
|
|
946
954
|
self.external_functions = external_functions
|
|
955
|
+
self.function_database = function_database
|
|
947
956
|
|
|
948
957
|
async def __call__(
|
|
949
958
|
self,
|
|
@@ -992,6 +1001,7 @@ class Application(object):
|
|
|
992
1001
|
accepts = headers.get(b'accepts', content_type)
|
|
993
1002
|
func_name = headers.get(b's2-ef-name', b'')
|
|
994
1003
|
func_endpoint = self.endpoints.get(func_name)
|
|
1004
|
+
ignore_cancel = headers.get(b's2-ef-ignore-cancel', b'false') == b'true'
|
|
995
1005
|
|
|
996
1006
|
timer.metadata['function'] = func_name.decode('utf-8') if func_name else ''
|
|
997
1007
|
call_timer.metadata['function'] = timer.metadata['function']
|
|
@@ -1021,7 +1031,7 @@ class Application(object):
|
|
|
1021
1031
|
with timer('receive_data'):
|
|
1022
1032
|
while more_body:
|
|
1023
1033
|
request = await receive()
|
|
1024
|
-
if request
|
|
1034
|
+
if request.get('type', '') == 'http.disconnect':
|
|
1025
1035
|
raise RuntimeError('client disconnected')
|
|
1026
1036
|
data.append(request['body'])
|
|
1027
1037
|
more_body = request.get('more_body', False)
|
|
@@ -1051,7 +1061,8 @@ class Application(object):
|
|
|
1051
1061
|
),
|
|
1052
1062
|
)
|
|
1053
1063
|
disconnect_task = asyncio.create_task(
|
|
1054
|
-
|
|
1064
|
+
asyncio.sleep(int(1e9))
|
|
1065
|
+
if ignore_cancel else cancel_on_disconnect(receive),
|
|
1055
1066
|
)
|
|
1056
1067
|
timeout_task = asyncio.create_task(
|
|
1057
1068
|
cancel_on_timeout(func_info['timeout']),
|
|
@@ -1130,6 +1141,7 @@ class Application(object):
|
|
|
1130
1141
|
endpoint_info['signature'],
|
|
1131
1142
|
url=self.url or reflected_url,
|
|
1132
1143
|
data_format=self.data_format,
|
|
1144
|
+
database=self.function_database or None,
|
|
1133
1145
|
),
|
|
1134
1146
|
)
|
|
1135
1147
|
body = '\n'.join(syntax).encode('utf-8')
|
|
@@ -1142,6 +1154,11 @@ class Application(object):
|
|
|
1142
1154
|
body = json.dumps(dict(functions=functions)).encode('utf-8')
|
|
1143
1155
|
await send(self.text_response_dict)
|
|
1144
1156
|
|
|
1157
|
+
# Return status
|
|
1158
|
+
elif method == 'GET' and path == self.status:
|
|
1159
|
+
body = json.dumps(dict(status='ok')).encode('utf-8')
|
|
1160
|
+
await send(self.text_response_dict)
|
|
1161
|
+
|
|
1145
1162
|
# Path not found
|
|
1146
1163
|
else:
|
|
1147
1164
|
body = b''
|
|
@@ -1184,20 +1201,27 @@ class Application(object):
|
|
|
1184
1201
|
def _locate_app_functions(self, cur: Any) -> Tuple[Set[str], Set[str]]:
|
|
1185
1202
|
"""Locate all current functions and links belonging to this app."""
|
|
1186
1203
|
funcs, links = set(), set()
|
|
1187
|
-
|
|
1204
|
+
if self.function_database:
|
|
1205
|
+
database_prefix = escape_name(self.function_database) + '.'
|
|
1206
|
+
cur.execute(f'SHOW FUNCTIONS IN {escape_name(self.function_database)}')
|
|
1207
|
+
else:
|
|
1208
|
+
database_prefix = ''
|
|
1209
|
+
cur.execute('SHOW FUNCTIONS')
|
|
1210
|
+
|
|
1188
1211
|
for row in list(cur):
|
|
1189
1212
|
name, ftype, link = row[0], row[1], row[-1]
|
|
1190
1213
|
# Only look at external functions
|
|
1191
1214
|
if 'external' not in ftype.lower():
|
|
1192
1215
|
continue
|
|
1193
1216
|
# See if function URL matches url
|
|
1194
|
-
cur.execute(f'SHOW CREATE FUNCTION
|
|
1217
|
+
cur.execute(f'SHOW CREATE FUNCTION {database_prefix}{escape_name(name)}')
|
|
1195
1218
|
for fname, _, code, *_ in list(cur):
|
|
1196
1219
|
m = re.search(r" (?:\w+) (?:SERVICE|MANAGED) '([^']+)'", code)
|
|
1197
1220
|
if m and m.group(1) == self.url:
|
|
1198
|
-
funcs.add(fname)
|
|
1221
|
+
funcs.add(f'{database_prefix}{escape_name(fname)}')
|
|
1199
1222
|
if link and re.match(r'^py_ext_func_link_\S{14}$', link):
|
|
1200
1223
|
links.add(link)
|
|
1224
|
+
|
|
1201
1225
|
return funcs, links
|
|
1202
1226
|
|
|
1203
1227
|
def get_function_info(
|
|
@@ -1220,20 +1244,66 @@ class Application(object):
|
|
|
1220
1244
|
sig = info['signature']
|
|
1221
1245
|
sql_map[sig['name']] = sql
|
|
1222
1246
|
|
|
1223
|
-
for key, (
|
|
1247
|
+
for key, (func, info) in self.endpoints.items():
|
|
1248
|
+
# Get info from docstring
|
|
1249
|
+
doc_summary = ''
|
|
1250
|
+
doc_long_description = ''
|
|
1251
|
+
doc_params = {}
|
|
1252
|
+
doc_returns = None
|
|
1253
|
+
doc_examples = []
|
|
1254
|
+
if func.__doc__:
|
|
1255
|
+
try:
|
|
1256
|
+
docs = parse(func.__doc__)
|
|
1257
|
+
doc_params = {p.arg_name: p for p in docs.params}
|
|
1258
|
+
doc_returns = docs.returns
|
|
1259
|
+
if not docs.short_description and docs.long_description:
|
|
1260
|
+
doc_summary = docs.long_description or ''
|
|
1261
|
+
else:
|
|
1262
|
+
doc_summary = docs.short_description or ''
|
|
1263
|
+
doc_long_description = docs.long_description or ''
|
|
1264
|
+
for ex in docs.examples:
|
|
1265
|
+
ex_dict: Dict[str, Any] = {
|
|
1266
|
+
'description': None,
|
|
1267
|
+
'code': None,
|
|
1268
|
+
'output': None,
|
|
1269
|
+
}
|
|
1270
|
+
if ex.description:
|
|
1271
|
+
ex_dict['description'] = ex.description
|
|
1272
|
+
if ex.snippet:
|
|
1273
|
+
code, output = [], []
|
|
1274
|
+
for line in ex.snippet.split('\n'):
|
|
1275
|
+
line = line.rstrip()
|
|
1276
|
+
if re.match(r'^(\w+>|>>>|\.\.\.)', line):
|
|
1277
|
+
code.append(line)
|
|
1278
|
+
else:
|
|
1279
|
+
output.append(line)
|
|
1280
|
+
ex_dict['code'] = '\n'.join(code) or None
|
|
1281
|
+
ex_dict['output'] = '\n'.join(output) or None
|
|
1282
|
+
if ex.post_snippet:
|
|
1283
|
+
ex_dict['postscript'] = ex.post_snippet
|
|
1284
|
+
doc_examples.append(ex_dict)
|
|
1285
|
+
|
|
1286
|
+
except Exception as e:
|
|
1287
|
+
logger.warning(
|
|
1288
|
+
f'Could not parse docstring for function {key}: {e}',
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1224
1291
|
if not func_name or key == func_name:
|
|
1225
1292
|
sig = info['signature']
|
|
1226
1293
|
args = []
|
|
1227
1294
|
|
|
1228
1295
|
# Function arguments
|
|
1229
|
-
for a in sig.get('args', []):
|
|
1296
|
+
for i, a in enumerate(sig.get('args', [])):
|
|
1297
|
+
name = a['name']
|
|
1230
1298
|
dtype = a['dtype']
|
|
1231
1299
|
nullable = '?' in dtype
|
|
1232
1300
|
args.append(
|
|
1233
1301
|
dict(
|
|
1234
|
-
name=
|
|
1302
|
+
name=name,
|
|
1235
1303
|
dtype=dtype.replace('?', ''),
|
|
1236
1304
|
nullable=nullable,
|
|
1305
|
+
description=(doc_params[name].description or '')
|
|
1306
|
+
if name in doc_params else '',
|
|
1237
1307
|
),
|
|
1238
1308
|
)
|
|
1239
1309
|
if a.get('default', no_default) is not no_default:
|
|
@@ -1250,6 +1320,8 @@ class Application(object):
|
|
|
1250
1320
|
dict(
|
|
1251
1321
|
dtype=dtype.replace('?', ''),
|
|
1252
1322
|
nullable=nullable,
|
|
1323
|
+
description=doc_returns.description
|
|
1324
|
+
if doc_returns else '',
|
|
1253
1325
|
),
|
|
1254
1326
|
)
|
|
1255
1327
|
if a.get('name', None):
|
|
@@ -1263,6 +1335,9 @@ class Application(object):
|
|
|
1263
1335
|
returns=returns,
|
|
1264
1336
|
function_type=info['function_type'],
|
|
1265
1337
|
sql_statement=sql,
|
|
1338
|
+
summary=doc_summary,
|
|
1339
|
+
long_description=doc_long_description,
|
|
1340
|
+
examples=doc_examples,
|
|
1266
1341
|
)
|
|
1267
1342
|
|
|
1268
1343
|
return functions
|
|
@@ -1303,6 +1378,7 @@ class Application(object):
|
|
|
1303
1378
|
app_mode=self.app_mode,
|
|
1304
1379
|
replace=replace,
|
|
1305
1380
|
link=link or None,
|
|
1381
|
+
database=self.function_database or None,
|
|
1306
1382
|
),
|
|
1307
1383
|
)
|
|
1308
1384
|
|
|
@@ -1332,7 +1408,7 @@ class Application(object):
|
|
|
1332
1408
|
if replace:
|
|
1333
1409
|
funcs, links = self._locate_app_functions(cur)
|
|
1334
1410
|
for fname in funcs:
|
|
1335
|
-
cur.execute(f'DROP FUNCTION IF EXISTS
|
|
1411
|
+
cur.execute(f'DROP FUNCTION IF EXISTS {fname}')
|
|
1336
1412
|
for link in links:
|
|
1337
1413
|
cur.execute(f'DROP LINK {link}')
|
|
1338
1414
|
for func in self.get_create_functions(replace=replace):
|
|
@@ -1358,7 +1434,7 @@ class Application(object):
|
|
|
1358
1434
|
with conn.cursor() as cur:
|
|
1359
1435
|
funcs, links = self._locate_app_functions(cur)
|
|
1360
1436
|
for fname in funcs:
|
|
1361
|
-
cur.execute(f'DROP FUNCTION IF EXISTS
|
|
1437
|
+
cur.execute(f'DROP FUNCTION IF EXISTS {fname}')
|
|
1362
1438
|
for link in links:
|
|
1363
1439
|
cur.execute(f'DROP LINK {link}')
|
|
1364
1440
|
|
|
@@ -1415,6 +1491,7 @@ class Application(object):
|
|
|
1415
1491
|
b'accepts': accepts[data_format.lower()],
|
|
1416
1492
|
b's2-ef-name': name.encode('utf-8'),
|
|
1417
1493
|
b's2-ef-version': data_version.encode('utf-8'),
|
|
1494
|
+
b's2-ef-ignore-cancel': b'true',
|
|
1418
1495
|
},
|
|
1419
1496
|
)
|
|
1420
1497
|
|
|
@@ -1679,6 +1756,14 @@ def main(argv: Optional[List[str]] = None) -> None:
|
|
|
1679
1756
|
),
|
|
1680
1757
|
help='Suffix to add to function names',
|
|
1681
1758
|
)
|
|
1759
|
+
parser.add_argument(
|
|
1760
|
+
'--function-database', metavar='function_database',
|
|
1761
|
+
default=defaults.get(
|
|
1762
|
+
'function_database',
|
|
1763
|
+
get_option('external_function.function_database'),
|
|
1764
|
+
),
|
|
1765
|
+
help='Database to use for the function definition',
|
|
1766
|
+
)
|
|
1682
1767
|
parser.add_argument(
|
|
1683
1768
|
'functions', metavar='module.or.func.path', nargs='*',
|
|
1684
1769
|
help='functions or modules to export in UDF server',
|
|
@@ -1778,6 +1863,7 @@ def main(argv: Optional[List[str]] = None) -> None:
|
|
|
1778
1863
|
app_mode='remote',
|
|
1779
1864
|
name_prefix=args.name_prefix,
|
|
1780
1865
|
name_suffix=args.name_suffix,
|
|
1866
|
+
function_database=args.function_database or None,
|
|
1781
1867
|
)
|
|
1782
1868
|
|
|
1783
1869
|
funcs = app.get_create_functions(replace=args.replace_existing)
|