1818import typing
1919
2020import bigframes .core .expression as ex
21- import bigframes .core .identifiers as ids
22- import bigframes .dtypes as dtypes
2321from bigframes ._config import options
2422from bigframes .functions import Udf
2523from bigframes .functions .udf_def import BigqueryUdf , PythonUdf
2624from bigframes .operations import base_ops , remote_function_ops
2725
26+ _ARGKIND_MAP = {
27+ inspect .Parameter .POSITIONAL_ONLY : "positional_only" ,
28+ inspect .Parameter .POSITIONAL_OR_KEYWORD : "positional_or_keyword" ,
29+ inspect .Parameter .VAR_POSITIONAL : "var_positional" ,
30+ inspect .Parameter .KEYWORD_ONLY : "keyword_only" ,
31+ inspect .Parameter .VAR_KEYWORD : "var_keyword" ,
32+ }
33+
2834
2935@dataclasses .dataclass (frozen = True )
3036class ArgumentSpec :
@@ -34,11 +40,41 @@ class ArgumentSpec:
3440
3541 name : str
3642 default_value : typing .Any
37- is_varargs : bool
43+ argkind : typing .Literal [
44+ "positional_only" ,
45+ "positional_or_keyword" ,
46+ "keyword_only" ,
47+ "var_positional" ,
48+ "var_keyword" ,
49+ ]
50+
51+ @property
52+ def is_positional (self ) -> bool :
53+ return self .argkind in ["positional_only" , "positional_or_keyword" ]
54+
55+ @property
56+ def is_keyword (self ) -> bool :
57+ return self .argkind in ["keyword_only" , "positional_or_keyword" ]
58+
59+ @property
60+ def is_var_positional (self ) -> bool :
61+ return self .argkind == "var_positional"
62+
63+ @property
64+ def is_var_keyword (self ) -> bool :
65+ return self .argkind == "var_keyword"
66+
67+ @property
68+ def is_varargs (self ) -> bool :
69+ return self .is_var_positional
70+
71+ def __post_init__ (self ):
72+ if self .argkind == "positional_only" and self .default_value is not None :
73+ raise ValueError ("positional-only arguments cannot have default values" )
3874
3975
4076@dataclasses .dataclass (frozen = True )
41- class CallableExpression ( ex . Expression ) :
77+ class CallableExpression :
4278 """
4379 Encodes a calling convention and an expression to bind arguments to.
4480 """
@@ -51,12 +87,11 @@ def from_callable(cls, func: typing.Callable) -> CallableExpression:
5187 sig = inspect .signature (func )
5288 arg_specs = []
5389 for name , param in sig .parameters .items ():
54- is_varargs = param .kind == inspect .Parameter .VAR_POSITIONAL
5590 arg_specs .append (
5691 ArgumentSpec (
5792 name = name ,
5893 default_value = param .default ,
59- is_varargs = is_varargs ,
94+ argkind = _ARGKIND_MAP [ param . kind ] ,
6095 )
6196 )
6297
@@ -126,62 +161,6 @@ def to_expr(val):
126161 remaining_specs = list (self .arg_specs [:_offset ])
127162 return CallableExpression (expr = new_expr , arg_specs = remaining_specs )
128163
129- @property
130- def column_references (self ) -> typing .Tuple [ids .ColumnId , ...]:
131- return self .expr .column_references
132-
133- @property
134- def free_variables (self ) -> typing .Tuple [typing .Hashable , ...]:
135- return self .expr .free_variables
136-
137- @property
138- def is_const (self ) -> bool :
139- return self .expr .is_const
140-
141- @property
142- def is_resolved (self ) -> bool :
143- return False
144-
145- @property
146- def output_type (self ) -> dtypes .ExpressionType :
147- raise ValueError (
148- "CallableExpression does not have a fixed output type until arguments are applied."
149- )
150-
151- def bind_refs (
152- self ,
153- bindings : typing .Mapping [ids .ColumnId , ex .Expression ],
154- allow_partial_bindings : bool = False ,
155- ) -> CallableExpression :
156- return dataclasses .replace (
157- self ,
158- expr = self .expr .bind_refs (
159- bindings , allow_partial_bindings = allow_partial_bindings
160- ),
161- )
162-
163- def bind_variables (
164- self ,
165- bindings : typing .Mapping [typing .Hashable , ex .Expression ],
166- allow_partial_bindings : bool = False ,
167- ) -> CallableExpression :
168- arg_names = {spec .name for spec in self .arg_specs }
169- filtered_bindings = {k : v for k , v in bindings .items () if k not in arg_names }
170- return dataclasses .replace (
171- self ,
172- expr = self .expr .bind_variables (
173- filtered_bindings , allow_partial_bindings = allow_partial_bindings
174- ),
175- )
176-
177- def transform_children (
178- self , t : typing .Callable [[ex .Expression ], ex .Expression ]
179- ) -> ex .Expression :
180- new_expr = t (self .expr )
181- if new_expr != self .expr :
182- return dataclasses .replace (self , expr = new_expr )
183- return self
184-
185164
186165def func_to_expr (op ) -> CallableExpression :
187166 """
@@ -205,7 +184,9 @@ def func_to_expr(op) -> CallableExpression:
205184 ArgumentSpec (
206185 name = arg .name ,
207186 default_value = inspect .Parameter .empty ,
208- is_varargs = False ,
187+ # Udf specs don't have concept of positional only or keyword only yet,
188+ # so default to positional_or_keyword.
189+ argkind = "positional_or_keyword" ,
209190 )
210191 for arg in op .udf_def .signature .inputs
211192 ]
0 commit comments