2
2
3
3
from fastapi import APIRouter , Depends , Query
4
4
5
+ from feast .api .registry .rest .codegen_utils import render_feature_view_code
5
6
from feast .api .registry .rest .rest_utils import (
6
7
create_grpc_pagination_params ,
7
8
create_grpc_sorting_params ,
14
15
parse_tags ,
15
16
)
16
17
from 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
17
20
18
21
19
22
def _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:
32
35
return {}
33
36
34
37
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
+
35
52
def get_feature_view_router (grpc_handler ) -> APIRouter :
36
53
router = APIRouter ()
37
54
@@ -116,6 +133,93 @@ def get_any_feature_view(
116
133
)
117
134
result ["relationships" ] = relationships
118
135
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
+
119
223
return result
120
224
121
225
@router .get ("/feature_views" )
0 commit comments