@@ -468,6 +468,7 @@ class Application(object):
468468 # Valid URL paths
469469 invoke_path = ('invoke' ,)
470470 show_create_function_path = ('show' , 'create_function' )
471+ show_function_info_path = ('show' , 'function_info' )
471472
472473 def __init__ (
473474 self ,
@@ -488,6 +489,8 @@ def __init__(
488489 link_name : Optional [str ] = get_option ('external_function.link_name' ),
489490 link_config : Optional [Dict [str , Any ]] = None ,
490491 link_credentials : Optional [Dict [str , Any ]] = None ,
492+ name_prefix : str = get_option ('external_function.name_prefix' ),
493+ name_suffix : str = get_option ('external_function.name_suffix' ),
491494 ) -> None :
492495 if link_name and (link_config or link_credentials ):
493496 raise ValueError (
@@ -544,6 +547,7 @@ def __init__(
544547 if not hasattr (x , '_singlestoredb_attrs' ):
545548 continue
546549 name = x ._singlestoredb_attrs .get ('name' , x .__name__ )
550+ name = f'{ name_prefix } { name } { name_suffix } '
547551 external_functions [x .__name__ ] = x
548552 func , info = make_func (name , x )
549553 endpoints [name .encode ('utf-8' )] = func , info
@@ -559,6 +563,7 @@ def __init__(
559563 # Add endpoint for each exported function
560564 for name , alias in get_func_names (func_names ):
561565 item = getattr (pkg , name )
566+ alias = f'{ name_prefix } { name } { name_suffix } '
562567 external_functions [name ] = item
563568 func , info = make_func (alias , item )
564569 endpoints [alias .encode ('utf-8' )] = func , info
@@ -571,12 +576,14 @@ def __init__(
571576 if not hasattr (x , '_singlestoredb_attrs' ):
572577 continue
573578 name = x ._singlestoredb_attrs .get ('name' , x .__name__ )
579+ name = f'{ name_prefix } { name } { name_suffix } '
574580 external_functions [x .__name__ ] = x
575581 func , info = make_func (name , x )
576582 endpoints [name .encode ('utf-8' )] = func , info
577583
578584 else :
579585 alias = funcs .__name__
586+ alias = f'{ name_prefix } { alias } { name_suffix } '
580587 external_functions [funcs .__name__ ] = funcs
581588 func , info = make_func (alias , funcs )
582589 endpoints [alias .encode ('utf-8' )] = func , info
@@ -671,6 +678,12 @@ async def __call__(
671678
672679 await send (self .text_response_dict )
673680
681+ # Return function info
682+ elif method == 'GET' and path == self .show_function_info_path :
683+ functions = self .get_function_info ()
684+ body = json .dumps (dict (functions = functions )).encode ('utf-8' )
685+ await send (self .text_response_dict )
686+
674687 # Path not found
675688 else :
676689 body = b''
@@ -715,14 +728,77 @@ def _locate_app_functions(self, cur: Any) -> Tuple[Set[str], Set[str]]:
715728 # See if function URL matches url
716729 cur .execute (f'SHOW CREATE FUNCTION `{ name } `' )
717730 for fname , _ , code , * _ in list (cur ):
718- m = re .search (r" (?:\w+) SERVICE '([^']+)'" , code )
731+ m = re .search (r" (?:\w+) (?: SERVICE|MANAGED) '([^']+)'" , code )
719732 if m and m .group (1 ) == self .url :
720733 funcs .add (fname )
721734 if link and re .match (r'^py_ext_func_link_\S{14}$' , link ):
722735 links .add (link )
723736 return funcs , links
724737
725- def show_create_functions (
738+ def get_function_info (
739+ self ,
740+ func_name : Optional [str ] = None ,
741+ ) -> Dict [str , Any ]:
742+ """
743+ Return the functions and function signature information.
744+
745+ Returns
746+ -------
747+ Dict[str, Any]
748+
749+ """
750+ returns : Dict [str , Any ] = {}
751+ functions = {}
752+
753+ for key , (_ , info ) in self .endpoints .items ():
754+ if not func_name or key == func_name :
755+ sig = info ['signature' ]
756+ args = []
757+
758+ # Function arguments
759+ for a in sig .get ('args' , []):
760+ dtype = a ['dtype' ]
761+ nullable = '?' in dtype
762+ args .append (
763+ dict (
764+ name = a ['name' ],
765+ dtype = dtype .replace ('?' , '' ),
766+ nullable = nullable ,
767+ ),
768+ )
769+
770+ # Record / table return types
771+ if sig ['returns' ]['dtype' ].startswith ('tuple[' ):
772+ fields = []
773+ dtypes = sig ['returns' ]['dtype' ][6 :- 1 ].split (',' )
774+ field_names = sig ['returns' ]['field_names' ]
775+ for i , dtype in enumerate (dtypes ):
776+ nullable = '?' in dtype
777+ dtype = dtype .replace ('?' , '' )
778+ fields .append (
779+ dict (
780+ name = field_names [i ],
781+ dtype = dtype ,
782+ nullable = nullable ,
783+ ),
784+ )
785+ returns = dict (
786+ dtype = 'table' if info ['function_type' ] == 'tvf' else 'struct' ,
787+ fields = fields ,
788+ )
789+
790+ # Atomic return types
791+ else :
792+ returns = dict (
793+ dtype = sig ['returns' ].get ('dtype' ).replace ('?' , '' ),
794+ nullable = '?' in sig ['returns' ].get ('dtype' , '' ),
795+ )
796+
797+ functions [sig ['name' ]] = dict (args = args , returns = returns )
798+
799+ return functions
800+
801+ def get_create_functions (
726802 self ,
727803 replace : bool = False ,
728804 ) -> List [str ]:
@@ -790,7 +866,7 @@ def register_functions(
790866 cur .execute (f'DROP FUNCTION IF EXISTS `{ fname } `' )
791867 for link in links :
792868 cur .execute (f'DROP LINK { link } ' )
793- for func in self .show_create_functions (replace = replace ):
869+ for func in self .get_create_functions (replace = replace ):
794870 cur .execute (func )
795871
796872 def drop_functions (
@@ -1118,6 +1194,22 @@ def main(argv: Optional[List[str]] = None) -> None:
11181194 ),
11191195 help = 'logging level' ,
11201196 )
1197+ parser .add_argument (
1198+ '--name-prefix' , metavar = 'name_prefix' ,
1199+ default = defaults .get (
1200+ 'name_prefix' ,
1201+ get_option ('external_function.name_prefix' ),
1202+ ),
1203+ help = 'Prefix to add to function names' ,
1204+ )
1205+ parser .add_argument (
1206+ '--name-suffix' , metavar = 'name_suffix' ,
1207+ default = defaults .get (
1208+ 'name_suffix' ,
1209+ get_option ('external_function.name_suffix' ),
1210+ ),
1211+ help = 'Suffix to add to function names' ,
1212+ )
11211213 parser .add_argument (
11221214 'functions' , metavar = 'module.or.func.path' , nargs = '*' ,
11231215 help = 'functions or modules to export in UDF server' ,
@@ -1210,9 +1302,11 @@ def main(argv: Optional[List[str]] = None) -> None:
12101302 link_config = json .loads (args .link_config ) or None ,
12111303 link_credentials = json .loads (args .link_credentials ) or None ,
12121304 app_mode = 'remote' ,
1305+ name_prefix = args .name_prefix ,
1306+ name_suffix = args .name_suffix ,
12131307 )
12141308
1215- funcs = app .show_create_functions (replace = args .replace_existing )
1309+ funcs = app .get_create_functions (replace = args .replace_existing )
12161310 if not funcs :
12171311 raise RuntimeError ('no functions specified' )
12181312
0 commit comments