|
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