@@ -349,6 +349,160 @@ def apply_feature_view(body: ApplyFeatureViewRequestBody):
349349 },
350350 )
351351
352+ @router .put ("/feature_views/{name}/enable" )
353+ def enable_feature_view (
354+ name : str ,
355+ project : str = Query (...),
356+ ):
357+ from feast .feature_view import FeatureView
358+ from feast .on_demand_feature_view import OnDemandFeatureView
359+ from feast .stream_feature_view import StreamFeatureView
360+
361+ req = RegistryServer_pb2 .GetAnyFeatureViewRequest (
362+ name = name ,
363+ project = project ,
364+ )
365+ resp = grpc_call (grpc_handler .GetAnyFeatureView , req )
366+ any_fv = resp .any_feature_view
367+
368+ if any_fv .HasField ("feature_view" ):
369+ fv = FeatureView .from_proto (any_fv .feature_view )
370+ elif any_fv .HasField ("on_demand_feature_view" ):
371+ fv = OnDemandFeatureView .from_proto (any_fv .on_demand_feature_view )
372+ elif any_fv .HasField ("stream_feature_view" ):
373+ fv = StreamFeatureView .from_proto (any_fv .stream_feature_view )
374+ else :
375+ return JSONResponse (
376+ status_code = 404 ,
377+ content = {"error" : f"Feature view '{ name } ' not found." },
378+ )
379+
380+ fv .enabled = True
381+ apply_req = RegistryServer_pb2 .ApplyFeatureViewRequest (
382+ project = project , commit = True
383+ )
384+ if isinstance (fv , StreamFeatureView ):
385+ apply_req .stream_feature_view .CopyFrom (fv .to_proto ())
386+ elif isinstance (fv , OnDemandFeatureView ):
387+ apply_req .on_demand_feature_view .CopyFrom (fv .to_proto ())
388+ else :
389+ apply_req .feature_view .CopyFrom (fv .to_proto ())
390+ grpc_call (grpc_handler .ApplyFeatureView , apply_req )
391+
392+ return {"name" : name , "project" : project , "enabled" : True }
393+
394+ @router .put ("/feature_views/{name}/disable" )
395+ def disable_feature_view (
396+ name : str ,
397+ project : str = Query (...),
398+ ):
399+ from feast .feature_view import FeatureView
400+ from feast .on_demand_feature_view import OnDemandFeatureView
401+ from feast .stream_feature_view import StreamFeatureView
402+
403+ req = RegistryServer_pb2 .GetAnyFeatureViewRequest (
404+ name = name ,
405+ project = project ,
406+ )
407+ resp = grpc_call (grpc_handler .GetAnyFeatureView , req )
408+ any_fv = resp .any_feature_view
409+
410+ if any_fv .HasField ("feature_view" ):
411+ fv = FeatureView .from_proto (any_fv .feature_view )
412+ elif any_fv .HasField ("on_demand_feature_view" ):
413+ fv = OnDemandFeatureView .from_proto (any_fv .on_demand_feature_view )
414+ elif any_fv .HasField ("stream_feature_view" ):
415+ fv = StreamFeatureView .from_proto (any_fv .stream_feature_view )
416+ else :
417+ return JSONResponse (
418+ status_code = 404 ,
419+ content = {"error" : f"Feature view '{ name } ' not found." },
420+ )
421+
422+ fv .enabled = False
423+ apply_req = RegistryServer_pb2 .ApplyFeatureViewRequest (
424+ project = project , commit = True
425+ )
426+ if isinstance (fv , StreamFeatureView ):
427+ apply_req .stream_feature_view .CopyFrom (fv .to_proto ())
428+ elif isinstance (fv , OnDemandFeatureView ):
429+ apply_req .on_demand_feature_view .CopyFrom (fv .to_proto ())
430+ else :
431+ apply_req .feature_view .CopyFrom (fv .to_proto ())
432+ grpc_call (grpc_handler .ApplyFeatureView , apply_req )
433+
434+ return {"name" : name , "project" : project , "enabled" : False }
435+
436+ @router .put ("/feature_views/{name}/set-state" )
437+ def set_feature_view_state (
438+ name : str ,
439+ state : str = Query (...),
440+ project : str = Query (...),
441+ ):
442+ from feast .feature_view import (
443+ _VALID_STATE_TRANSITIONS ,
444+ FeatureView ,
445+ FeatureViewState ,
446+ )
447+ from feast .on_demand_feature_view import OnDemandFeatureView
448+ from feast .stream_feature_view import StreamFeatureView
449+
450+ try :
451+ new_state = FeatureViewState [state .upper ()]
452+ except KeyError :
453+ return JSONResponse (
454+ status_code = 400 ,
455+ content = {
456+ "error" : f"Invalid state '{ state } '. "
457+ f"Valid states: CREATED, GENERATED, MATERIALIZING, AVAILABLE_ONLINE."
458+ },
459+ )
460+
461+ req = RegistryServer_pb2 .GetAnyFeatureViewRequest (
462+ name = name ,
463+ project = project ,
464+ )
465+ resp = grpc_call (grpc_handler .GetAnyFeatureView , req )
466+ any_fv = resp .any_feature_view
467+
468+ if any_fv .HasField ("feature_view" ):
469+ fv = FeatureView .from_proto (any_fv .feature_view )
470+ elif any_fv .HasField ("on_demand_feature_view" ):
471+ fv = OnDemandFeatureView .from_proto (any_fv .on_demand_feature_view )
472+ elif any_fv .HasField ("stream_feature_view" ):
473+ fv = StreamFeatureView .from_proto (any_fv .stream_feature_view )
474+ else :
475+ return JSONResponse (
476+ status_code = 404 ,
477+ content = {"error" : f"Feature view '{ name } ' not found." },
478+ )
479+
480+ if not fv .state .can_transition_to (new_state ):
481+ current = fv .state .name
482+ allowed = _VALID_STATE_TRANSITIONS .get (fv .state , set ())
483+ allowed_names = ", " .join (sorted (s .name for s in allowed )) or "none"
484+ return JSONResponse (
485+ status_code = 400 ,
486+ content = {
487+ "error" : f"Invalid state transition: { current } -> { new_state .name } . "
488+ f"Allowed transitions from { current } : { allowed_names } ."
489+ },
490+ )
491+
492+ fv .state = new_state
493+ apply_req = RegistryServer_pb2 .ApplyFeatureViewRequest (
494+ project = project , commit = True
495+ )
496+ if isinstance (fv , StreamFeatureView ):
497+ apply_req .stream_feature_view .CopyFrom (fv .to_proto ())
498+ elif isinstance (fv , OnDemandFeatureView ):
499+ apply_req .on_demand_feature_view .CopyFrom (fv .to_proto ())
500+ else :
501+ apply_req .feature_view .CopyFrom (fv .to_proto ())
502+ grpc_call (grpc_handler .ApplyFeatureView , apply_req )
503+
504+ return {"name" : name , "project" : project , "state" : new_state .name }
505+
352506 @router .delete ("/feature_views/{name}" )
353507 def delete_feature_view (
354508 name : str ,
0 commit comments