Skip to content

Commit

Permalink
Added new error cases for invalid io signatures, modules not fond, an…
Browse files Browse the repository at this point in the history
…d column names not matching for custom functions
  • Loading branch information
vhegde14 committed Nov 25, 2023
1 parent 334c8b1 commit edb51e5
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 2 deletions.
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

0 comments on commit edb51e5

Please sign in to comment.