Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f206864
v1
carloea2 Feb 26, 2026
299dc0f
Merge branch 'apache:main' into feat/ui-parameter
carloea2 Feb 27, 2026
5b29ad8
v2
carloea2 Feb 28, 2026
6258a99
Merge branch 'feat/ui-parameter' of https://github.com/carloea2/texer…
carloea2 Feb 28, 2026
3550ccd
v2
carloea2 Mar 2, 2026
7a210a7
py2udf
carloea2 Mar 4, 2026
4a56861
v2.1
carloea2 Mar 7, 2026
16f89ac
Merge branch 'feat/ui-parameter' of https://github.com/carloea2/texer…
carloea2 Mar 7, 2026
5aa3f4e
vnt
carloea2 Mar 7, 2026
1d82c9a
v2
carloea2 Mar 7, 2026
b36628f
v2.1
carloea2 Mar 7, 2026
dd46609
Update ui-udf-parameters.component.scss
carloea2 Mar 7, 2026
2c8167b
v2.1
carloea2 Mar 7, 2026
06d7f5a
v3
carloea2 Mar 7, 2026
ac2b265
v3
carloea2 Mar 7, 2026
c8239c2
Merge branch 'main' into feat/ui-parameter
carloea2 Mar 7, 2026
edd3869
Merge branch 'main' into feat/ui-parameter
carloea2 Mar 7, 2026
0c9f0cd
Merge branch 'main' into feat/ui-parameter
chenlica Mar 10, 2026
6ff1e86
v3CopilotVs
carloea2 Mar 16, 2026
ce96e19
Merge branch 'main' into feat/ui-parameter
carloea2 Mar 16, 2026
52845c3
Update PythonUdfUiParameterInjectorSpec.scala
carloea2 Mar 16, 2026
b863937
Merge branch 'feat/ui-parameter' of https://github.com/carloea2/texer…
carloea2 Mar 16, 2026
06ba53b
v4
carloea2 Mar 16, 2026
7d317a0
Merge branch 'main' into feat/ui-parameter
carloea2 Apr 2, 2026
1aee56e
v10
carloea2 Apr 2, 2026
ed6e53c
Merge branch 'feat/ui-parameter' of https://github.com/carloea2/texer…
carloea2 Apr 2, 2026
9425340
Merge branch 'main' into feat/ui-parameter
carloea2 Apr 2, 2026
16a9687
Merge branch 'main' into feat/ui-parameter
chenlica Apr 2, 2026
00c2deb
Handling empty values with defaults
carloea2 Apr 2, 2026
f8f8915
v21
carloea2 Apr 2, 2026
4634adc
Update ui-udf-parameters-sync.service.ts
carloea2 Apr 2, 2026
61e5bb9
Merge branch 'main' into feat/ui-parameter
carloea2 Apr 2, 2026
08b1969
Update coeditor-user-icon.component.css
carloea2 Apr 12, 2026
ff3a31a
Update coeditor-user-icon.component.css
carloea2 Apr 12, 2026
775f005
Update coeditor-user-icon.component.css
carloea2 Apr 12, 2026
6c45777
Update coeditor-user-icon.component.css
carloea2 Apr 12, 2026
610c808
Update coeditor-user-icon.component.css
carloea2 Apr 12, 2026
31548b3
Update coeditor-user-icon.component.css
carloea2 Apr 12, 2026
cd2f58a
Merge branch 'main' into feat/ui-parameter
carloea2 Apr 12, 2026
cbab5be
formatting
carloea2 Apr 12, 2026
5ce49d2
Merge remote-tracking branch 'origin/feat/ui-parameter' into feat/ui-…
carloea2 Apr 12, 2026
0488dba
formatting
carloea2 Apr 12, 2026
d9ba912
??
carloea2 Apr 12, 2026
9d506ad
Merge branch 'main' into feat/ui-parameter
aglinxinyuan Apr 15, 2026
9f2bdbd
Remove FileSelectionComponent from app.module.ts
aglinxinyuan Apr 15, 2026
672ec4b
Update app.module.ts
aglinxinyuan Apr 15, 2026
f9eaf4f
fix fmt
aglinxinyuan Apr 15, 2026
4d0107c
v9
carloea2 Apr 15, 2026
670ec8c
Merge branch 'main' into feat/ui-parameter
chenlica Apr 16, 2026
086d781
Merge branch 'main' into feat/ui-parameter
kunwp1 Apr 17, 2026
4b2c25d
Merge branch 'main' into feat/ui-parameter
kunwp1 Apr 17, 2026
50e8aa2
Merge branch 'main' into feat/ui-parameter
carloea2 Apr 18, 2026
606d241
v13
carloea2 Apr 18, 2026
496de38
Merge branch 'main' into feat/ui-parameter
aglinxinyuan Apr 18, 2026
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
47 changes: 47 additions & 0 deletions amber/src/main/python/core/models/schema/attribute_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,53 @@ class AttributeType(Enum):
}


FROM_STRING_PARSER_MAPPING = {
AttributeType.STRING: str,
AttributeType.INT: lambda v: (
0 if v is None or (isinstance(v, str) and v.strip() == "") else int(v)
),
AttributeType.LONG: lambda v: (
0 if v is None or (isinstance(v, str) and v.strip() == "") else int(v)
),
AttributeType.DOUBLE: lambda v: (
0.0 if v is None or (isinstance(v, str) and v.strip() == "") else float(v)
),
AttributeType.BOOL: lambda v: (
False
if v is None or (isinstance(v, str) and v.strip() == "")
else (
True
if str(v).strip().lower() == "true"
else (
False
if str(v).strip().lower() == "false"
else float(str(v).strip()) != 0
)
)
),
AttributeType.BINARY: lambda v: (
(_ for _ in ()).throw(
ValueError(
"UiParameter does not support BINARY values. "
"Use a supported type instead."
)
)
),
AttributeType.TIMESTAMP: lambda v: (
datetime.datetime.fromtimestamp(0)
if v is None or (isinstance(v, str) and v.strip() == "")
else datetime.datetime.fromisoformat(v)
),
AttributeType.LARGE_BINARY: lambda v: (
(_ for _ in ()).throw(
ValueError(
"UiParameter does not support LARGE_BINARY values. "
"Use a supported type instead."
)
)
),
}
Comment thread
carloea2 marked this conversation as resolved.

# Only single-directional mapping.
TO_PYOBJECT_MAPPING = {
AttributeType.STRING: str,
Expand Down
6 changes: 5 additions & 1 deletion amber/src/main/python/pytexera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from loguru import logger
from overrides import overrides
from typing import Iterator, Optional, Union
from typing import Iterator, Optional, Union, Dict, Any

from pyamber import *
from .storage.dataset_file_document import DatasetFileDocument
Expand All @@ -30,6 +30,7 @@
UDFSourceOperator,
)
from core.models.type.large_binary import largebinary
from core.models.schema.attribute_type import *

__all__ = [
"State",
Expand All @@ -53,4 +54,7 @@
"Iterator",
"Optional",
"Union",
"Dict",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove Dict and Any

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this change is needed for the feature to work. The reason is the definitions of UiParameter use those as the return value types, so since the code comes from the User, I make them available from the import.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

Copy link
Copy Markdown
Contributor Author

@carloea2 carloea2 Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kunwp1 NameError: name 'Dict' is not defined
2026-04-18 12:17:21.586 | ERROR | core.architecture.rpc.async_rpc_server:receive:102 - name 'Dict' is not defined
File "C:\Users\carlo\AppData\Local\Temp\tmpkqz8h60kfsTempFS\udf-v1.py", line 12, in
class ProcessTupleOperator(UDFOperatorV2):
-> <class 'pytexera.udf.udf_operator.UDFOperatorV2'>

File "C:\Users\carlo\AppData\Local\Temp\tmpkqz8h60kfsTempFS\udf-v1.py", line 30, in ProcessTupleOperator
def _texera_injected_ui_parameters(self) -> Dict[str, Any]:

NameError: name 'Dict' is not defined

"Any",
"AttributeType",
]
188 changes: 188 additions & 0 deletions amber/src/main/python/pytexera/udf/test_udf_operator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

import datetime
from typing import Iterator, Optional

import pytest

from pytexera import AttributeType, Tuple, TupleLike, UDFOperatorV2
from pytexera.udf.udf_operator import _UiParameterSupport


class InjectedParametersOperator(UDFOperatorV2):
def _texera_injected_ui_parameters(self):
return {
"count": "7",
"enabled": "1",
"created_at": "2024-01-01T00:00:00",
}

def open(self):
self.count_parameter = self.UiParameter("count", AttributeType.INT)
self.enabled_parameter = self.UiParameter(
name="enabled", type=AttributeType.BOOL
)
self.created_at_parameter = self.UiParameter(
"created_at", type=AttributeType.TIMESTAMP
)

def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]:
yield tuple_


class ConflictingParameterOperator(UDFOperatorV2):
def _texera_injected_ui_parameters(self):
return {"duplicate": "1"}

def open(self):
self.UiParameter("duplicate", AttributeType.INT)
self.UiParameter("duplicate", AttributeType.STRING)

def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]:
yield tuple_


class FirstIndependentParameterOperator(UDFOperatorV2):
def _texera_injected_ui_parameters(self):
return {"count": "1"}

def open(self):
self.count_parameter = self.UiParameter("count", AttributeType.INT)

def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]:
yield tuple_


class SecondIndependentParameterOperator(UDFOperatorV2):
def _texera_injected_ui_parameters(self):
return {"count": "2"}

def open(self):
self.count_parameter = self.UiParameter("count", AttributeType.INT)

def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]:
yield tuple_


class TestUiParameterSupport:
def test_injected_values_are_applied_before_open(self):
operator = InjectedParametersOperator()

operator.open()

assert operator.count_parameter.value == 7
assert operator.enabled_parameter.value is True
assert operator.created_at_parameter.value == datetime.datetime(
2024, 1, 1, 0, 0
)

def test_duplicate_parameter_names_with_conflicting_types_raise(self):
operator = ConflictingParameterOperator()

with pytest.raises(ValueError) as exc_info:
operator.open()

assert "Duplicate UiParameter name 'duplicate'" in str(exc_info.value)

@pytest.mark.parametrize(
("raw_value", "attr_type", "expected"),
[
("hello", AttributeType.STRING, "hello"),
("7", AttributeType.INT, 7),
("99", AttributeType.LONG, 99),
("3.14", AttributeType.DOUBLE, 3.14),
("1", AttributeType.BOOL, True),
(
"2024-01-01T00:00:00",
AttributeType.TIMESTAMP,
datetime.datetime(2024, 1, 1, 0, 0),
),
],
)
def test_parse_supported_types(self, raw_value, attr_type, expected):
assert _UiParameterSupport._parse(raw_value, attr_type) == expected

@pytest.mark.parametrize(
("raw_value", "expected"),
[
("", False),
(" ", False),
("True", True),
("true", True),
("1", True),
("1.0", True),
("2", True),
("-1", True),
("False", False),
("false", False),
("0", False),
("0.0", False),
],
)
def test_parse_bool_string_values(self, raw_value, expected):
assert _UiParameterSupport._parse(raw_value, AttributeType.BOOL) is expected

@pytest.mark.parametrize(
("raw_value", "attr_type", "expected_message"),
[
(
"payload",
AttributeType.BINARY,
"UiParameter does not support BINARY values",
),
(
"s3://bucket/path/to/object",
AttributeType.LARGE_BINARY,
"UiParameter does not support LARGE_BINARY values",
),
],
)
def test_parse_binary_types_raise_helpful_error(
self, raw_value, attr_type, expected_message
):
with pytest.raises(ValueError, match=expected_message):
_UiParameterSupport._parse(raw_value, attr_type)

def test_parse_unsupported_type_raises_helpful_error(self):
with pytest.raises(TypeError, match="UiParameter.type .* is not supported"):
_UiParameterSupport._parse("value", object())

def test_wrapped_open_uses_instance_local_state(self):
assert (
getattr(
FirstIndependentParameterOperator.open,
"__texera_ui_params_wrapped__",
False,
)
is True
)

first_operator = FirstIndependentParameterOperator()
second_operator = SecondIndependentParameterOperator()

first_operator.open()
second_operator.open()

assert first_operator.count_parameter.value == 1
assert second_operator.count_parameter.value == 2
assert first_operator._ui_parameter_injected_values == {"count": "1"}
assert second_operator._ui_parameter_injected_values == {"count": "2"}
assert (
first_operator._ui_parameter_injected_values
is not second_operator._ui_parameter_injected_values
)
Loading
Loading