Skip to content

Commit 8980fde

Browse files
fix messaging
1 parent 02a970a commit 8980fde

4 files changed

Lines changed: 57 additions & 36 deletions

File tree

packages/bigframes/bigframes/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ class MaximumResultRowsExceeded(RuntimeError):
7575
"""Maximum number of rows in the result was exceeded."""
7676

7777

78+
class TranspilationError(RuntimeError):
79+
"""Failed to transpile a Python function to BigFrames Expression."""
80+
81+
7882
class TimeTravelDisabledWarning(Warning):
7983
"""A query was reattempted without time travel."""
8084

packages/bigframes/bigframes/operations/to_op.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import bigframes.core.expression as ex
2121
from bigframes._config import options
22+
from bigframes.exceptions import TranspilationError
2223
from bigframes.functions import Udf
2324
from bigframes.functions.udf_def import BigqueryUdf, PythonUdf
2425
from bigframes.operations import base_ops, remote_function_ops
@@ -95,7 +96,10 @@ def from_callable(cls, func: typing.Callable) -> CallableExpression:
9596

9697
from bigframes.core.bytecode import py_to_expression
9798

98-
expr = py_to_expression(func)
99+
try:
100+
expr = py_to_expression(func)
101+
except Exception as ex:
102+
raise TranspilationError(f"Failed to transpile function {func}") from ex
99103
return cls(expr=expr, arg_specs=arg_specs)
100104

101105
def apply(self, *args, **kwargs) -> ex.Expression:

packages/bigframes/bigframes/series.py

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,33 +2065,19 @@ def apply(
20652065
" are supported."
20662066
)
20672067

2068-
20692068
# Highest priority: try to map directly to an operator, for eg numpy
20702069
# ufuncs, or simple arithmetic/logic operators.
20712070
bf_op = python_ops.python_callable_to_op(func)
20722071
if bf_op and isinstance(bf_op, ops.UnaryOp):
20732072
return self._apply_unary_op(bf_op)
20742073

2075-
# "compat": try by row, and then fall back to passing whole series if that fails
2076-
if by_row:
2077-
try:
2078-
return self._apply_by_row(func)
2079-
except Exception as ex:
2080-
raise ValueError(
2081-
"You have passed a function as-is. If your intention is to "
2082-
"apply this function in a vectorized way (i.e. to the "
2083-
"entire Series as a whole, and you are sure that it "
2084-
"performs only the operations that are implemented for a "
2085-
"Series (e.g. a chain of arithmetic/logical operations, "
2086-
"such as `def foo(s): return s % 2 == 1`), please also "
2087-
"specify `by_row=False`. If your function contains "
2088-
"arbitrary code, it can only be applied to every element "
2089-
"in the Series individually, in which case you must "
2090-
"convert it to a BigFrames BigQuery function using "
2091-
"`bigframes.pandas.udf`, "
2092-
"or `bigframes.pandas.remote_function` before passing."
2093-
)
2074+
if by_row:
2075+
from bigframes._config import options
20942076

2077+
enable_transpile = options.experiments.enable_python_transpiler
2078+
return self._apply_by_row(
2079+
func, args=args, transpile_enabled=enable_transpile
2080+
)
20952081
try:
20962082
return func(self) # type: ignore
20972083
except Exception as ex:
@@ -2102,20 +2088,44 @@ def apply(
21022088
ex.message += f"\n{_bigquery_function_recommendation_message}"
21032089
raise
21042090

2105-
def _apply_by_row(self, func: typing.Callable, args: typing.Tuple = ()) -> Series:
2106-
from bigframes._config import options
2091+
def _apply_by_row(
2092+
self,
2093+
func: typing.Callable,
2094+
args: typing.Tuple = (),
2095+
transpile_enabled: bool = False,
2096+
) -> Series:
2097+
"""
2098+
Apply callable or deployed udf row-wise on the series.
2099+
"""
2100+
if not callable(func):
2101+
raise ValueError(
2102+
"Expected a callable function. If you meant to use a BigQuery function, please wrap it with bigframes.pandas.udf(...)"
2103+
)
2104+
try:
2105+
expr = ops.func_to_expr(func)
2106+
# We get this message even if transpiler could have in theory translated it.
2107+
except Exception:
2108+
raise ValueError(
2109+
"You have passed a functi1on as-is. If your intention is to "
2110+
"apply this function in a vectorized way (i.e. to the "
2111+
"entire Series as a whole, and you are sure that it "
2112+
"performs only the operations that are implemented for a "
2113+
"Series (e.g. a chain of arithmetic/logical operations, "
2114+
"such as `def foo(s): return s % 2 == 1`), please also "
2115+
"specify `by_row=False`. If your function contains "
2116+
"arbitrary code, it can only be applied to every element "
2117+
"in the Series individually, in which case you must "
2118+
"convert it to a BigFrames BigQuery function using "
2119+
"`bigframes.pandas.udf`, "
2120+
"or `bigframes.pandas.remote_function` before passing."
2121+
)
21072122

2108-
if isinstance(func, bigframes.functions.Udf) or (
2109-
options.experiments.enable_python_transpiler and callable(func)
2110-
):
2111-
# We are working with bigquery function at this point
2112-
result_series = self._apply_callable_expr(ops.func_to_expr(func), args)
2113-
# TODO(jialuo): Investigate why `_apply_nary_op` drops the series
2114-
# `name`. Manually reassigning it here as a temporary fix.
2115-
result_series.name = self.name
2123+
result_series = self._apply_callable_expr(expr, args)
2124+
# TODO(jialuo): Investigate why `_apply_nary_op` drops the series
2125+
# `name`. Manually reassigning it here as a temporary fix.
2126+
result_series.name = self.name
21162127

2117-
return result_series
2118-
raise ValueError(f"Cannot apply function {func} to Series {self}")
2128+
return result_series
21192129

21202130
def combine(
21212131
self,
@@ -2504,7 +2514,10 @@ def map(
25042514
map_df = map_df.set_index("keys")
25052515
elif callable(arg):
25062516
# This is for remote function and managed funtion.
2507-
return self.apply(arg)
2517+
from bigframes._config import options
2518+
2519+
enable_transpile = options.experiments.enable_python_transpiler
2520+
return self._apply_by_row(arg, transpile_enabled=enable_transpile)
25082521
else:
25092522
# Mirroring pandas, call the uncallable object
25102523
arg() # throws TypeError: object is not callable

packages/bigframes/tests/unit/test_py_udf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def foo_with_if(x):
230230
return x
231231
return -x
232232

233-
with pytest.raises(ValueError, match="Unsupported opcode:"):
233+
with pytest.raises(ValueError):
234234
scalars_df_index["int64_col"].apply(foo_with_if)
235235

236236
def foo_with_loop(x):
@@ -239,5 +239,5 @@ def foo_with_loop(x):
239239
total += i
240240
return total
241241

242-
with pytest.raises(ValueError, match="Unsupported opcode:"):
242+
with pytest.raises(ValueError):
243243
scalars_df_index["int64_col"].apply(foo_with_loop)

0 commit comments

Comments
 (0)