@@ -80,10 +80,6 @@ def _extract_param_names(func):
8080 sig = inspect .signature (func )
8181 param_names = list (sig .parameters .keys ())
8282
83- # Remove 'self' if present (for class methods)
84- if param_names and param_names [0 ] == 'self' :
85- param_names = param_names [1 :]
86-
8783 return param_names
8884
8985
@@ -154,6 +150,8 @@ def __call__(self, row):
154150 Func .__name__ = name if name is not None else func .__name__
155151 Func .__doc__ = func .__doc__
156152 query_compile .FUNCTIONS [Func .__name__ ].append (Func )
153+ _add_to_doc_groups (Func , intypes , groups )
154+
157155 return func
158156 return decorator
159157
@@ -173,6 +171,8 @@ def decorator(cls):
173171 if name is not None :
174172 cls .__name__ = name
175173 query_compile .FUNCTIONS [cls .__name__ ].append (cls )
174+ _add_to_doc_groups (cls , cls .__intypes__ , groups )
175+
176176 return cls
177177 return decorator
178178
@@ -857,11 +857,7 @@ def interval(x):
857857
858858@function ([relativedelta , datetime .date , datetime .date ], datetime .date , groups = ['date' ])
859859def date_bin (stride , source , origin ):
860- """Bin a date into the specified stride aligned with the specified origin.
861860
862- As an extension to the the SQL standard ``date_bin()`` function this
863- function also accepts strides containing units of months and years.
864- """
865861 if stride .months or stride .years :
866862 if origin + stride <= origin :
867863 # FIXME: this should raise and error: stride must be greater than zero
@@ -1067,3 +1063,57 @@ def update(self, store, context):
10671063 cur = store [self .handle ]
10681064 if cur is None or value > cur :
10691065 store [self .handle ] = value
1066+
1067+ def _describe_functions (functions , aggregates = False , type_filter = None ):
1068+ """Describe functions, optionally filtered by input type category.
1069+
1070+ Args:
1071+ functions: Dictionary of (function name: EvalFunction subclass),
1072+ the actual class, not an object, which would represent a particular
1073+ function call.
1074+ aggregates: If True, show aggregates; if False, show regular functions
1075+ type_filter: Optional filter by input type category (see TYPE_CATEGORIES)
1076+ """
1077+ # Determine which functions to iterate over
1078+ if type_filter :
1079+ # Use the pre-populated FUNCTION_DOC_GROUPS for filtering
1080+ funcs_to_process = FUNCTION_DOC_GROUPS .get (type_filter , [])
1081+ else :
1082+ # Collect all functions from all groups
1083+ funcs_to_process = []
1084+ for name , funcs in functions .items ():
1085+ funcs_to_process .extend (funcs )
1086+
1087+ entries = []
1088+ for func in funcs_to_process :
1089+ # Filter by aggregate vs non-aggregate
1090+ if aggregates != issubclass (func , query_compile .EvalAggregator ):
1091+ continue
1092+
1093+ # Get the function name
1094+ name = func .__name__ .lower ()
1095+
1096+ # Assemble function signature for output using parameter names
1097+ args = ', ' .join (f'{ param_name } : { types .name (dtype )} '
1098+ for param_name , dtype in zip (func .__param_names__ , func .__intypes__ ))
1099+
1100+ if func .__outtype__ :
1101+ outtype = types .name (func .__outtype__ )
1102+ else :
1103+ outtype = None
1104+
1105+ doc = func .__doc__ or ''
1106+ entries .append ((name , doc , args , outtype ))
1107+
1108+ entries .sort ()
1109+ return entries
1110+
1111+ def _format_description (entries ):
1112+ out = io .StringIO ()
1113+ wrapper = textwrap .TextWrapper (initial_indent = ' ' , subsequent_indent = ' ' , width = 80 )
1114+ for key , entries in itertools .groupby (entries , key = lambda x : x [:2 ]): # noqa: B020
1115+ for name , doc , args , outtype in entries :
1116+ print (f'{ name } ({ args } ) -> { outtype } ' , file = out )
1117+ print (wrapper .fill (re .sub (r'[ \n\t]+' , ' ' , doc or '' )), file = out )
1118+ print (file = out )
1119+ return out .getvalue ().rstrip ()
0 commit comments