Skip to content

Commit 97fcf03

Browse files
authored
template decorator (#46)
* template decorator * comment edit
1 parent 2eacb21 commit 97fcf03

File tree

6 files changed

+65
-11
lines changed

6 files changed

+65
-11
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,22 @@ def RECENCY() -> Column:
282282
return F.months_between(F.lit(rfm.FeatureMaker.make_date), F.col("DATE"))
283283
```
284284

285+
#### @template
286+
Provides an option to pass a template string that's to be used to create a text feature, this template is saved in [metadata](#metadata).
287+
288+
```python
289+
import pyspark.sql.functions as F
290+
import rialto.maker as rfm
291+
from pyspark.sql import Column
292+
from rialto.metadata import ValueType as VT
293+
294+
@rfm.feature(VT.numerical)
295+
@rfm.desc("Age of customer")
296+
@rfm.template("Customer is $X years old")
297+
def AGE() -> Column:
298+
return F.col("AGE")
299+
```
300+
285301
#### @param
286302
Inspired by @pytest.mark.parametrize, it has similar interface and fulfills the same role. It allows you to invoke the feature function multiple times with different values of the parameter.
287303
If multiple @params are used, the number of final features will be a product of all parameters. The feature function has to expect a parameter with the same name as the @params name.

rialto/maker/containers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def __init__(self, name: str, callable_object: typing.Callable, value_type: Valu
3333
self.dependencies: typing.List[str] = []
3434
self.type = value_type
3535
self.description = "basic feature"
36+
self.template = "value $X"
3637

3738
def __str__(self) -> str:
3839
"""
@@ -44,7 +45,8 @@ def __str__(self) -> str:
4445
f"Name: {self.name}\n\t"
4546
f"Parameters: {self.parameters}\n\t"
4647
f"Type: {self.get_type()}\n\t"
47-
f"Description: {self.description}"
48+
f"Description: {self.description}\n\t"
49+
f"Template: {self.template}"
4850
)
4951

5052
def metadata(self) -> FeatureMetadata:
@@ -53,7 +55,9 @@ def metadata(self) -> FeatureMetadata:
5355
5456
:return: metadata dict
5557
"""
56-
return FeatureMetadata(name=self.get_feature_name(), value_type=self.type, description=self.description)
58+
return FeatureMetadata(
59+
name=self.get_feature_name(), value_type=self.type, description=self.description, template=self.template
60+
)
5761

5862
def get_feature_name(self) -> str:
5963
"""

rialto/maker/wrappers.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
__all__ = ["feature", "desc", "param", "depends"]
15+
__all__ = ["feature", "desc", "template", "param", "depends"]
1616

1717
import typing
1818
from copy import deepcopy
@@ -89,7 +89,7 @@ def desc(feature_functions: typing.Union[typing.Callable, FeatureHolder], desc:
8989
Wrap feature with string description, used in metadata
9090
9191
:param feature_functions: FeatureHolder or pure function
92-
:param type: FeatureType enum (numerical, ordinal, nominal)
92+
:param desc: text description of the feature
9393
:return: FeatureHolder
9494
"""
9595
logger.trace(f"Wrapping {feature_functions} with description")
@@ -109,6 +109,32 @@ def wrapper() -> FeatureHolder:
109109
return wrapper()
110110

111111

112+
@decorator_with_args
113+
def template(feature_functions: typing.Union[typing.Callable, FeatureHolder], template: str):
114+
"""
115+
Wrap feature with string template to create a text feature
116+
117+
:param feature_functions: FeatureHolder or pure function
118+
:param template: string template of the feature
119+
:return: FeatureHolder
120+
"""
121+
logger.trace(f"Wrapping {feature_functions} with text template")
122+
123+
def wrapper() -> FeatureHolder:
124+
if isinstance(feature_functions, FeatureHolder):
125+
for f in feature_functions:
126+
f.template = template
127+
return feature_functions
128+
else:
129+
func_list = FeatureHolder()
130+
new_feature_f = FeatureFunction(feature_functions.__name__, feature_functions)
131+
new_feature_f.template = template
132+
func_list.append(new_feature_f)
133+
return func_list
134+
135+
return wrapper()
136+
137+
112138
@decorator_with_args
113139
def param(feature_functions: typing.Union[typing.Callable, FeatureHolder], parameter_name: str, values: typing.List):
114140
"""

rialto/metadata/data_classes/feature_metadata.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ class FeatureMetadata:
3131
value_type: ValueType
3232
name: str
3333
description: str
34+
template: str = None
3435
group: GroupMetadata = None
3536

3637
def __repr__(self) -> str:
3738
"""Serialize object to string"""
3839
return (
3940
"FeatureMetadata("
4041
f"name={self.name!r}, value_type={self.value_type!r}, "
41-
f"description={self.description!r}, group={self.group!r}, "
42+
f"description={self.description!r}, template={self.template!r}, "
43+
f"group={self.group!r}"
4244
")"
4345
)
4446

@@ -49,7 +51,7 @@ def to_tuple(self, group_name: str) -> Tuple:
4951
:param group_name: Feature group name
5052
:return: tuple with feature information
5153
"""
52-
return (self.name, self.value_type.value, self.description, group_name)
54+
return (self.name, self.value_type.value, self.description, self.template, group_name)
5355

5456
def add_group(self, group: GroupMetadata) -> Self:
5557
"""
@@ -73,4 +75,5 @@ def from_spark(cls, record: Row) -> Self:
7375
value_type=ValueType[record.feature_type],
7476
name=record.feature_name,
7577
description=record.feature_description,
78+
template=record.feature_template,
7679
)

tests/maker/test_FeatureFunction.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def test_serialization():
5757
func.parameters["paramC"] = 1
5858
func.parameters["paramA"] = 4
5959
assert (
60-
func.__str__()
61-
== "Name: feature\n\tParameters: {'paramC': 1, 'paramA': 4}\n\tType: nominal\n\tDescription: basic feature"
60+
func.__str__() == "Name: feature\n\tParameters: {'paramC': 1, 'paramA': 4}\n\t"
61+
"Type: nominal\n\tDescription: basic feature\n\tTemplate: value $X"
6262
)
6363

6464

@@ -68,7 +68,9 @@ def test_metadata():
6868
func.parameters["paramA"] = 4
6969
func.dependencies = ["featureB", "featureC"]
7070
func.description = "nice feature"
71+
func.template = "value $X something"
7172

7273
assert func.metadata().name == "FEATURE_PARAMA_4_PARAMC_1"
7374
assert func.metadata().value_type == ValueType.ordinal
7475
assert func.metadata().description == "nice feature"
76+
assert func.metadata().template == "value $X something"

tests/metadata/resources.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
StructField("feature_name", StringType(), True),
3232
StructField("feature_type", StringType(), True),
3333
StructField("feature_description", StringType(), True),
34+
StructField("feature_template", StringType(), True),
3435
StructField("group_name", StringType(), True),
3536
]
3637
)
@@ -41,8 +42,8 @@
4142
]
4243

4344
feature_base = [
44-
("Feature1", "nominal", "feature1", "Group2"),
45-
("Feature2", "nominal", "feature2", "Group2"),
45+
("Feature1", "nominal", "feature1", "template1", "Group2"),
46+
("Feature2", "nominal", "feature2", "template2", "Group2"),
4647
]
4748

4849
group_md1 = GroupMetadata(
@@ -64,4 +65,6 @@
6465
features=["Feature1", "Feature2"],
6566
)
6667

67-
feature_md1 = FeatureMetadata(name="Feature1", value_type=ValueType.nominal, description="feature1", group=group_md2)
68+
feature_md1 = FeatureMetadata(
69+
name="Feature1", value_type=ValueType.nominal, description="feature1", template="template1", group=group_md2
70+
)

0 commit comments

Comments
 (0)