Skip to content

Commit cc05efc

Browse files
#30: Correction for variadic inputs (#32)
* Corrected variadic input access * Removed DeprecationWarning * Added validations * Called getattr directly * Updated naming
1 parent 903595f commit cc05efc

File tree

5 files changed

+201
-23
lines changed

5 files changed

+201
-23
lines changed

exasol_udf_mock_python/mock_context_run_wrapper.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ def _disallowed_function(*args, **kw):
99

1010
class MockContextRunWrapper:
1111

12-
def __init__(
13-
self, mock_context: MockContext, input_type: str, output_type: str):
12+
def __init__(self, mock_context: MockContext, input_type: str,
13+
output_type: str, is_variadic_input: bool):
1414
self._output_type = output_type
1515
self._input_type = input_type
1616
self._mock_context = mock_context
17+
self._is_variadic_input = is_variadic_input
1718
if self._output_type == "RETURNS":
1819
self.emit = _disallowed_function
1920
else:
@@ -28,7 +29,30 @@ def __init__(
2829
self.size = self._mock_context.size
2930

3031
def __getattr__(self, name):
32+
"""
33+
Variadic UDFs' columns are only integer values. Since integers are not
34+
valid identifier in python, this method cannot be used by variadic UDFs.
35+
"""
36+
if self._is_variadic_input:
37+
raise RuntimeError(f"E-UDF-CL-SL-PYTHON-1085: Iterator has no "
38+
f"object with name '{name}'")
3139
return self._mock_context.__getattr__(name)
3240

3341
def __getitem__(self, item):
34-
return self._mock_context._data[item]
42+
"""
43+
Variadic UDFs can retrieve items by index. The index value can be given
44+
as an integer (e.g. ctx[1]) or as a string integer (e.g. ctx["1"]).
45+
46+
Non-variadic UDFs can retrieve items by index, that can be either an
47+
integer (e.g. ctx[1]) or a column name (e.g. ctx["col_name"]). They do
48+
not accept string integers as index.
49+
"""
50+
item = int(item) if self._is_variadic_input else item
51+
if isinstance(item, int):
52+
return self._mock_context._data[item]
53+
else:
54+
try:
55+
return self._mock_context.__getattr__(item)
56+
except KeyError:
57+
raise RuntimeError(f"E-UDF-CL-SL-PYTHON-1082: Column with name "
58+
f"'{item}' does not exist")

exasol_udf_mock_python/mock_meta_data.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import re
2+
import dill
23
import textwrap
34
from typing import List
4-
5-
import dill
6-
75
from exasol_udf_mock_python.column import Column
86

97

@@ -15,6 +13,7 @@ def __init__(
1513
input_type: str,
1614
output_columns: List[Column],
1715
output_type: str,
16+
is_variadic_input: bool = False,
1817
script_name: str="TEST_UDF",
1918
script_schema: str="TEST_SCHEMA",
2019
current_user: str="sys",
@@ -30,6 +29,13 @@ def __init__(
3029
statement_id: int="123456789",
3130
memory_limit: int=4*1073741824,
3231
):
32+
33+
assert input_type.upper() in ["SET", "SCALAR"]
34+
assert output_type.upper() in ["EMITS", "RETURNS"]
35+
if is_variadic_input:
36+
for i in range(len(input_columns)):
37+
assert str(i+1) == str(input_columns[i].name)
38+
3339
self._script_language = "PYTHON3"
3440
self._script_name = script_name
3541
self._script_schema = script_schema
@@ -54,6 +60,7 @@ def __init__(
5460
self._validate_column_defintions(output_columns)
5561
self._output_column_count = len(output_columns)
5662
self._output_columns = output_columns
63+
self._is_variadic_input = is_variadic_input
5764

5865
def _extract_script_code(self, script_code_wrapper_function):
5966
function_code = textwrap.dedent(dill.source.getsource(script_code_wrapper_function))
@@ -173,5 +180,9 @@ def output_column_count(self):
173180
def output_columns(self):
174181
return self._output_columns
175182

183+
@property
184+
def is_variadic_input(self):
185+
return self._is_variadic_input
186+
176187
def __repr__(self):
177-
return str(self.__class__) + ": " + str(self.__dict__)
188+
return str(self.__class__) + ": " + str(self.__dict__)

exasol_udf_mock_python/udf_mock_executor.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
from multiprocessing import Lock
22
from typing import Dict, Any, Iterator, List, Union, Callable, Tuple
3-
43
from exasol_udf_mock_python.group import Group
54
from exasol_udf_mock_python.mock_context import MockContext
65
from exasol_udf_mock_python.mock_context_run_wrapper import MockContextRunWrapper
76
from exasol_udf_mock_python.mock_exa_environment import MockExaEnvironment
87

8+
99
def _loop_groups(ctx:MockContext, exa:MockExaEnvironment, runfunc:Callable):
1010
while ctx._next_group():
1111
_wrapped_run(ctx, exa, runfunc)
1212

13-
def _wrapped_run(ctx:MockContext, exa:MockExaEnvironment, runfunc:Callable):
14-
wrapped_ctx = MockContextRunWrapper(ctx, exa.meta.input_type, exa.meta.output_type)
13+
14+
def _wrapped_run(ctx:MockContext, exa: MockExaEnvironment, runfunc: Callable):
15+
wrapped_ctx = MockContextRunWrapper(
16+
ctx, exa.meta.input_type, exa.meta.output_type, exa.meta.is_variadic_input)
1517
if exa.meta.input_type == "SET":
1618
if exa.meta.output_type == "RETURNS":
1719
run_with_returns(ctx, runfunc, wrapped_ctx)

tests/test_executor_context_set_emits.py

Lines changed: 153 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
2+
import unittest
33
from exasol_udf_mock_python.column import Column
44
from exasol_udf_mock_python.group import Group
55
from exasol_udf_mock_python.mock_exa_environment import MockExaEnvironment
@@ -366,26 +366,59 @@ def run(ctx):
366366
result = executor.run([Group([(1,), (2,), (3,), (4,), (5,), (6,)])], exa)
367367

368368

369-
def test_context_parameters():
369+
def test_access_variadic_inputs():
370370
def udf_wrapper():
371371
def run(ctx):
372-
ctx.emit(ctx[0], ctx.t1)
373-
ctx.emit(ctx[1], ctx.t2)
374-
ctx.emit(ctx[2], ctx.t3)
372+
ctx.emit(ctx[0])
373+
ctx.emit(ctx[1])
374+
ctx.emit(ctx["2"])
375+
376+
input_columns = [Column("1", int, "INTEGER"),
377+
Column("2", int, "INTEGER"),
378+
Column("3", int, "INTEGER")]
379+
output_columns = [Column("o1", int, "INTEGER")]
380+
meta = MockMetaData(
381+
script_code_wrapper_function=udf_wrapper,
382+
input_type="SET",
383+
input_columns=input_columns,
384+
output_type="EMITS",
385+
output_columns=output_columns,
386+
is_variadic_input=True)
387+
388+
input_data = [(1, 2, 3), (4, 5, 6)]
389+
exa = MockExaEnvironment(meta)
390+
executor = UDFMockExecutor()
391+
result = executor.run([Group(input_data)], exa)
392+
for i, group in enumerate(result):
393+
result_row = group.rows
394+
assert len(result_row) == len(input_columns)
395+
for j in range(len(result_row)):
396+
assert len(result_row[j]) == len(output_columns)
397+
assert input_data[i][j] == result_row[j][0]
398+
399+
400+
def test_access_non_variadic_inputs():
401+
def udf_wrapper():
402+
def run(ctx):
403+
ctx.emit(ctx[0])
404+
ctx.emit(ctx[1])
405+
ctx.emit(ctx["t3"])
406+
ctx.emit(ctx["4"])
375407

376408
input_columns = [Column("t1", int, "INTEGER"),
377409
Column("t2", int, "INTEGER"),
378-
Column("t3", int, "INTEGER")]
379-
output_columns = [Column("o1", int, "INTEGER"),
380-
Column("o2", int, "INTEGER")]
410+
Column("t3", int, "INTEGER"),
411+
Column("4", int, "INTEGER")]
412+
output_columns = [Column("o1", int, "INTEGER")]
381413
meta = MockMetaData(
382414
script_code_wrapper_function=udf_wrapper,
383415
input_type="SET",
384416
input_columns=input_columns,
385417
output_type="EMITS",
386-
output_columns=output_columns
387-
)
388-
input_data = [(1, 2, 3), (4, 5, 6)]
418+
output_columns=output_columns,
419+
is_variadic_input=False)
420+
421+
input_data = [(1, 2, 3, 4), (5, 6, 7, 8)]
389422
exa = MockExaEnvironment(meta)
390423
executor = UDFMockExecutor()
391424
result = executor.run([Group(input_data)], exa)
@@ -394,4 +427,112 @@ def run(ctx):
394427
assert len(result_row) == len(input_columns)
395428
for j in range(len(result_row)):
396429
assert len(result_row[j]) == len(output_columns)
397-
assert input_data[i][j] == result_row[j][0] == result_row[j][1]
430+
assert input_data[i][j] == result_row[j][0]
431+
432+
433+
class InvalidTestsForVariadicInputs(unittest.TestCase):
434+
def test_access_variadic_inputs_by_name(self):
435+
def udf_wrapper():
436+
def run(ctx):
437+
ctx.emit(ctx.t1)
438+
439+
input_columns = [Column("1", int, "INTEGER")]
440+
output_columns = [Column("o1", int, "INTEGER")]
441+
meta = MockMetaData(
442+
script_code_wrapper_function=udf_wrapper,
443+
input_type="SET",
444+
input_columns=input_columns,
445+
output_type="EMITS",
446+
output_columns=output_columns,
447+
is_variadic_input=True)
448+
449+
input_data = [(1,)]
450+
exa = MockExaEnvironment(meta)
451+
executor = UDFMockExecutor()
452+
with self.assertRaises(RuntimeError):
453+
result = executor.run([Group(input_data)], exa)
454+
455+
def test_invalid_variadic_input_columns_name(self):
456+
def udf_wrapper():
457+
def run(ctx):
458+
ctx.emit(ctx[0])
459+
460+
invalid_input_columns_list = [
461+
[Column("t1", int, "INTEGER")],
462+
[Column("1", int, "INTEGER"), Column("3", int, "INTEGER")]
463+
]
464+
for input_columns in invalid_input_columns_list:
465+
output_columns = [Column("o1", int, "INTEGER")]
466+
with self.assertRaises(AssertionError):
467+
meta = MockMetaData(
468+
script_code_wrapper_function=udf_wrapper,
469+
input_type="SET",
470+
input_columns=input_columns,
471+
output_type="EMITS",
472+
output_columns=output_columns,
473+
is_variadic_input=True)
474+
475+
input_data = [(1,)]
476+
exa = MockExaEnvironment(meta)
477+
executor = UDFMockExecutor()
478+
result = executor.run([Group(input_data)], exa)
479+
480+
481+
class InvalidTestsForNonVariadicInputs(unittest.TestCase):
482+
def test_invalid_access_to_inputs(self):
483+
def udf_wrapper():
484+
def run(ctx):
485+
ctx.emit(ctx["1"])
486+
487+
input_columns = [Column("t1", int, "INTEGER")]
488+
output_columns = [Column("o1", int, "INTEGER")]
489+
490+
with self.assertRaises(RuntimeError):
491+
meta = MockMetaData(
492+
script_code_wrapper_function=udf_wrapper,
493+
input_type="SET",
494+
input_columns=input_columns,
495+
output_type="EMITS",
496+
output_columns=output_columns,
497+
is_variadic_input=False)
498+
499+
input_data = [(1,)]
500+
exa = MockExaEnvironment(meta)
501+
executor = UDFMockExecutor()
502+
result = executor.run([Group(input_data)], exa)
503+
504+
505+
class InvalidTestsForUDFTypes(unittest.TestCase):
506+
def test_invalid_input_types(self):
507+
def udf_wrapper():
508+
def run(ctx):
509+
ctx.emit(ctx.t1)
510+
511+
input_columns = [Column("1", int, "INTEGER")]
512+
output_columns = [Column("o1", int, "INTEGER")]
513+
for input_type in ["SETS", "SCALARS", "INVALID"]:
514+
with self.assertRaises(AssertionError):
515+
MockMetaData(
516+
script_code_wrapper_function=udf_wrapper,
517+
input_type=input_type,
518+
input_columns=input_columns,
519+
output_type="EMITS",
520+
output_columns=output_columns,
521+
is_variadic_input=True)
522+
523+
def test_invalid_output_types(self):
524+
def udf_wrapper():
525+
def run(ctx):
526+
ctx.emit(ctx.t1)
527+
528+
input_columns = [Column("1", int, "INTEGER")]
529+
output_columns = [Column("o1", int, "INTEGER")]
530+
for output_types in ["EMIT", "RETURN", "INVALID"]:
531+
with self.assertRaises(AssertionError):
532+
MockMetaData(
533+
script_code_wrapper_function=udf_wrapper,
534+
input_type="SET",
535+
input_columns=input_columns,
536+
output_type=output_types,
537+
output_columns=output_columns,
538+
is_variadic_input=True)

tests/test_group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from collections import Iterable
1+
from collections.abc import Iterable
22

33
from exasol_udf_mock_python.group import Group, IterableWithSize
44

0 commit comments

Comments
 (0)