22
33from fastapi import APIRouter , Depends , Query
44
5+ from feast .api .registry .rest .codegen_utils import render_feature_view_code
56from feast .api .registry .rest .rest_utils import (
67 create_grpc_pagination_params ,
78 create_grpc_sorting_params ,
1415 parse_tags ,
1516)
1617from feast .registry_server import RegistryServer_pb2
18+ from feast .type_map import _convert_value_type_str_to_value_type
19+ from feast .types import from_value_type
1720
1821
1922def _extract_feature_view_from_any (any_feature_view : dict ) -> dict :
@@ -32,6 +35,20 @@ def _extract_feature_view_from_any(any_feature_view: dict) -> dict:
3235 return {}
3336
3437
38+ def extract_feast_types_from_fields (fields ):
39+ types = set ()
40+ for field in fields :
41+ value_type_enum = _convert_value_type_str_to_value_type (
42+ field .get ("valueType" , "" ).upper ()
43+ )
44+ feast_type = from_value_type (value_type_enum )
45+ dtype = (
46+ feast_type .__name__ if hasattr (feast_type , "__name__" ) else str (feast_type )
47+ )
48+ types .add (dtype )
49+ return list (types )
50+
51+
3552def get_feature_view_router (grpc_handler ) -> APIRouter :
3653 router = APIRouter ()
3754
@@ -116,6 +133,93 @@ def get_any_feature_view(
116133 )
117134 result ["relationships" ] = relationships
118135
136+ if result and "spec" in result :
137+ spec = result ["spec" ]
138+ fv_type = result .get ("type" , "featureView" )
139+ features = spec .get ("features" , [])
140+ if not isinstance (features , list ):
141+ features = []
142+ if fv_type == "onDemandFeatureView" :
143+ class_name = "OnDemandFeatureView"
144+ sources = spec .get ("sources" , {})
145+ if sources :
146+ source_exprs = []
147+ for k , v in sources .items ():
148+ var_name = v .get ("name" , k ) if isinstance (v , dict ) else str (v )
149+ source_exprs .append (f'"{ k } ": { var_name } ' )
150+ source_name = "{" + ", " .join (source_exprs ) + "}"
151+ else :
152+ source_name = "source_feature_view"
153+ elif fv_type == "streamFeatureView" :
154+ class_name = "StreamFeatureView"
155+ stream_source = spec .get ("streamSource" , {})
156+ source_name = stream_source .get ("name" , "stream_source" )
157+ else :
158+ class_name = "FeatureView"
159+ source_views = spec .get ("source_views" ) or spec .get ("sourceViews" )
160+ if source_views and isinstance (source_views , list ):
161+ source_vars = [sv .get ("name" , "source_view" ) for sv in source_views ]
162+ source_name = "[" + ", " .join (source_vars ) + "]"
163+ else :
164+ source = spec .get ("source" )
165+ if isinstance (source , dict ) and source .get ("name" ):
166+ source_name = source ["name" ]
167+ else :
168+ batch_source = spec .get ("batchSource" , {})
169+ source_name = batch_source .get ("name" , "driver_stats_source" )
170+
171+ # Entities
172+ entities = spec .get ("entities" ) or []
173+ entities_str = ", " .join (entities )
174+
175+ # Feature schema
176+ schema_lines = []
177+ for field in features :
178+ value_type_enum = _convert_value_type_str_to_value_type (
179+ field .get ("valueType" , "" ).upper ()
180+ )
181+ feast_type = from_value_type (value_type_enum )
182+ dtype = getattr (feast_type , "__name__" , str (feast_type ))
183+ desc = field .get ("description" )
184+ desc_str = f', description="{ desc } "' if desc else ""
185+ schema_lines .append (
186+ f' Field(name="{ field ["name" ]} ", dtype={ dtype } { desc_str } ),'
187+ )
188+
189+ # Feast types
190+ feast_types = extract_feast_types_from_fields (features )
191+
192+ # Tags
193+ tags = spec .get ("tags" , {})
194+ tags_str = f"tags={ tags } ," if tags else ""
195+
196+ # TTL
197+ ttl = spec .get ("ttl" )
198+ ttl_str = "timedelta(days=1)"
199+ if ttl :
200+ if isinstance (ttl , int ):
201+ ttl_str = f"timedelta(seconds={ ttl } )"
202+ elif isinstance (ttl , str ) and ttl .endswith ("s" ) and ttl [:- 1 ].isdigit ():
203+ ttl_str = f"timedelta(seconds={ int (ttl [:- 1 ])} )"
204+
205+ # Online
206+ online = spec .get ("online" , True )
207+
208+ # Build context
209+ context = dict (
210+ class_name = class_name ,
211+ name = spec .get ("name" , "example" ),
212+ entities_str = entities_str ,
213+ ttl_str = ttl_str ,
214+ schema_lines = schema_lines ,
215+ online = online ,
216+ source_name = source_name ,
217+ tags_str = tags_str ,
218+ feast_types = feast_types ,
219+ )
220+
221+ result ["featureDefinition" ] = render_feature_view_code (context )
222+
119223 return result
120224
121225 @router .get ("/feature_views" )
0 commit comments