Skip to content

Commit 3d5fd04

Browse files
refactor CallableExpression
1 parent 94ff174 commit 3d5fd04

1 file changed

Lines changed: 44 additions & 63 deletions

File tree

  • packages/bigframes/bigframes/operations

packages/bigframes/bigframes/operations/to_op.py

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@
1818
import typing
1919

2020
import bigframes.core.expression as ex
21-
import bigframes.core.identifiers as ids
22-
import bigframes.dtypes as dtypes
2321
from bigframes._config import options
2422
from bigframes.functions import Udf
2523
from bigframes.functions.udf_def import BigqueryUdf, PythonUdf
2624
from 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)
3036
class 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

186165
def 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

Comments
 (0)