|
1 | 1 | import copy
|
2 | 2 | import functools
|
| 3 | +import inspect |
3 | 4 | import warnings
|
4 | 5 | from datetime import datetime
|
5 | 6 | from types import FunctionType
|
|
17 | 18 | from feast.feature_view_projection import FeatureViewProjection
|
18 | 19 | from feast.field import Field, from_value_type
|
19 | 20 | from feast.on_demand_pandas_transformation import OnDemandPandasTransformation
|
| 21 | +from feast.on_demand_substrait_transformation import OnDemandSubstraitTransformation |
20 | 22 | from feast.protos.feast.core.OnDemandFeatureView_pb2 import (
|
21 | 23 | OnDemandFeatureView as OnDemandFeatureViewProto,
|
22 | 24 | )
|
@@ -210,6 +212,9 @@ def to_proto(self) -> OnDemandFeatureViewProto:
|
210 | 212 | user_defined_function=self.transformation.to_proto()
|
211 | 213 | if type(self.transformation) == OnDemandPandasTransformation
|
212 | 214 | else None,
|
| 215 | + on_demand_substrait_transformation=self.transformation.to_proto() # type: ignore |
| 216 | + if type(self.transformation) == OnDemandSubstraitTransformation |
| 217 | + else None, |
213 | 218 | description=self.description,
|
214 | 219 | tags=self.tags,
|
215 | 220 | owner=self.owner,
|
@@ -255,6 +260,13 @@ def from_proto(cls, on_demand_feature_view_proto: OnDemandFeatureViewProto):
|
255 | 260 | transformation = OnDemandPandasTransformation.from_proto(
|
256 | 261 | on_demand_feature_view_proto.spec.user_defined_function
|
257 | 262 | )
|
| 263 | + elif ( |
| 264 | + on_demand_feature_view_proto.spec.WhichOneof("transformation") |
| 265 | + == "on_demand_substrait_transformation" |
| 266 | + ): |
| 267 | + transformation = OnDemandSubstraitTransformation.from_proto( |
| 268 | + on_demand_feature_view_proto.spec.on_demand_substrait_transformation |
| 269 | + ) |
258 | 270 | else:
|
259 | 271 | raise Exception("At least one transformation type needs to be provided")
|
260 | 272 |
|
@@ -460,10 +472,47 @@ def mainify(obj) -> None:
|
460 | 472 | obj.__module__ = "__main__"
|
461 | 473 |
|
462 | 474 | def decorator(user_function):
|
463 |
| - udf_string = dill.source.getsource(user_function) |
464 |
| - mainify(user_function) |
| 475 | + return_annotation = inspect.signature(user_function).return_annotation |
| 476 | + if ( |
| 477 | + return_annotation |
| 478 | + and return_annotation.__module__ == "ibis.expr.types.relations" |
| 479 | + and return_annotation.__name__ == "Table" |
| 480 | + ): |
| 481 | + import ibis |
| 482 | + import ibis.expr.datatypes as dt |
| 483 | + from ibis_substrait.compiler.core import SubstraitCompiler |
| 484 | + |
| 485 | + compiler = SubstraitCompiler() |
| 486 | + |
| 487 | + input_fields: Field = [] |
| 488 | + |
| 489 | + for s in sources: |
| 490 | + if type(s) == FeatureView: |
| 491 | + fields = s.projection.features |
| 492 | + else: |
| 493 | + fields = s.features |
| 494 | + |
| 495 | + input_fields.extend( |
| 496 | + [ |
| 497 | + ( |
| 498 | + f.name, |
| 499 | + dt.dtype( |
| 500 | + feast_value_type_to_pandas_type(f.dtype.to_value_type()) |
| 501 | + ), |
| 502 | + ) |
| 503 | + for f in fields |
| 504 | + ] |
| 505 | + ) |
| 506 | + |
| 507 | + expr = user_function(ibis.table(input_fields, "t")) |
465 | 508 |
|
466 |
| - transformation = OnDemandPandasTransformation(user_function, udf_string) |
| 509 | + transformation = OnDemandSubstraitTransformation( |
| 510 | + substrait_plan=compiler.compile(expr).SerializeToString() |
| 511 | + ) |
| 512 | + else: |
| 513 | + udf_string = dill.source.getsource(user_function) |
| 514 | + mainify(user_function) |
| 515 | + transformation = OnDemandPandasTransformation(user_function, udf_string) |
467 | 516 |
|
468 | 517 | on_demand_feature_view_obj = OnDemandFeatureView(
|
469 | 518 | name=user_function.__name__,
|
|
0 commit comments