Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New error cases for custom functions #1403

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions evadb/expression/function_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ def _apply_function_expression(self, func: Callable, batch: Batch, **kwargs):
if not self._cache:
return func_args.apply_function_expression(func)

for column_name, obj_column in zip(func_args.columns, self.function_obj.args):
if obj_column.name != column_name:
raise RuntimeError(f"Column name {column_name} is not matching")

output_cols = [obj.name for obj in self.function_obj.outputs]

# 1. check cache
Expand Down
9 changes: 8 additions & 1 deletion evadb/functions/decorators/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from evadb.catalog.models.function_io_catalog import FunctionIOCatalogEntry
from evadb.functions.abstract.abstract_function import AbstractFunction
from evadb.executor.executor_utils import ExecutorError


def load_io_from_function_decorators(
Expand Down Expand Up @@ -45,7 +46,13 @@ def load_io_from_function_decorators(

assert (
io_signature is not None
), f"Cannot infer io signature from the decorator for {function}."
), f"No io signature was given for function {function}."

if len(io_signature) > 1:
if is_input:
raise ExecutorError("forward method can only have single DataFrame as input")
else:
raise ExecutorError("forward method can only output single DataFrame")

result_list = []
for io in io_signature:
Expand Down
52 changes: 52 additions & 0 deletions evadb/functions/test_invalid_signature_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import pandas as pd

from evadb.catalog.catalog_type import ColumnType, NdArrayType
from evadb.functions.abstract.abstract_function import AbstractFunction
from evadb.functions.decorators.decorators import forward, setup
from evadb.functions.decorators.io_descriptors.data_types import PandasDataframe

class InvalidSignatureInput(AbstractFunction):
"""
Arguments:
None

Input Signatures:
multiple input dataframes which is an invalid input signature
"""
@property
def name(self) -> str:
return "InvalidSignature"

@setup(cacheable=False)
def setup(self) -> None:
# Any setup or initialization can be done here if needed
pass

@forward(
input_signatures=[
PandasDataframe(
columns=["col1", "col2"],
column_types=[NdArrayType.STR, NdArrayType.STR],
column_shapes=[(None,), (None,)],
),
PandasDataframe(
columns=["col1", "col2"],
column_types=[NdArrayType.STR, NdArrayType.STR],
column_shapes=[(None,), (None,)],
)
],
output_signatures=[
PandasDataframe(
columns=["data1", "data2"],
column_types=[NdArrayType.STR, NdArrayType.STR],
column_shapes=[(None,), (None,)],
)
],
)
def forward(self, input_df):
# Create a DataFrame from the parsed data
ans = []
ans.append({'data1': 'data1', 'data2': 'data2'})
output_dataframe = pd.DataFrame(ans, columns=['data1', 'data2'])

return output_dataframe
53 changes: 53 additions & 0 deletions evadb/functions/test_invalid_signature_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pandas as pd

from evadb.catalog.catalog_type import ColumnType, NdArrayType
from evadb.functions.abstract.abstract_function import AbstractFunction
from evadb.functions.decorators.decorators import forward, setup
from evadb.functions.decorators.io_descriptors.data_types import PandasDataframe

class InvalidSignatureInput(AbstractFunction):
"""
Arguments:
None

Input Signatures:
multiple input dataframes which is an invalid input signature
"""
@property
def name(self) -> str:
return "InvalidSignature"

@setup(cacheable=False)
def setup(self) -> None:
# Any setup or initialization can be done here if needed
pass

@forward(
input_signatures=[
PandasDataframe(
columns=["col1", "col2"],
column_types=[NdArrayType.STR, NdArrayType.STR],
column_shapes=[(None,), (None,)],
)
],
output_signatures=[
PandasDataframe(
columns=["data1", "data2"],
column_types=[NdArrayType.STR, NdArrayType.STR],
column_shapes=[(None,), (None,)],
),
PandasDataframe(
columns=["data1", "data2"],
column_types=[NdArrayType.STR, NdArrayType.STR],
column_shapes=[(None,), (None,)],
)
],
)
def forward(self, input_df):
# Create a DataFrame from the parsed data
ans = []
ans.append({'data1': 'data1', 'data2': 'data2'})
output_dataframe = pd.DataFrame(ans, columns=['data1', 'data2'])
output_df_2 = pd.DataFrame(ans, columns=['data1', 'data2'])

return output_dataframe, output_df_2
5 changes: 4 additions & 1 deletion evadb/utils/generic_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,12 @@ def load_function_class_from_file(filepath, classname=None):
spec = importlib.util.spec_from_file_location(abs_path.stem, abs_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
except ModuleNotFoundError as e:
err_msg = f"ModuleNotFoundError : Couldn't load function from {filepath} : {str(e)}. Not able to load the code provided in the file {abs_path}. Please ensure that the file contains the implementation code for the function."
raise ModuleNotFoundError(err_msg)
except ImportError as e:
# ImportError in the case when we are able to find the file but not able to load the module
err_msg = f"ImportError : Couldn't load function from {filepath} : {str(e)}. Not able to load the code provided in the file {abs_path}. Please ensure that the file contains the implementation code for the function."
err_msg = f"ImportError : Couldn't load function from {filepath} : {str(e)}. Please ensure that all the correct packages are installed correctly."
raise ImportError(err_msg)
except FileNotFoundError as e:
# FileNotFoundError in the case when we are not able to find the file at all at the path.
Expand Down
14 changes: 14 additions & 0 deletions test/integration_tests/long/test_function_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,20 @@ def test_should_raise_if_function_file_is_modified(self):
# disabling warning for function modification for now
# with self.assertRaises(AssertionError):
execute_query_fetch_all(self.evadb, select_query)

def test_should_raise_with_multiple_input_dataframes(self):
with self.assertRaises(ExecutorError) as cm:
execute_query_fetch_all(
self.evadb, "CREATE FUNCTION IF NOT EXISTS InvalidInput IMPL 'evadb/functions/test_invalid_signature_input.py'"
)
self.assertEqual(str(cm.exception), "forward method can only have single DataFrame as input")

def test_should_raise_with_multiple_output_dataframes(self):
with self.assertRaises(ExecutorError) as cm:
execute_query_fetch_all(
self.evadb, "CREATE FUNCTION IF NOT EXISTS InvalidOutput IMPL 'evadb/functions/test_invalid_signature_output.py'"
)
self.assertEqual(str(cm.exception), "forward method can only output single DataFrame")

def test_create_function_with_decorators(self):
execute_query_fetch_all(
Expand Down