100100from feast .transformation .pandas_transformation import PandasTransformation
101101from feast .transformation .python_transformation import PythonTransformation
102102from feast .utils import _get_feature_view_vector_field_metadata , _utc_now
103+ from feast .version_utils import parse_version
103104
104105_track_materialization = None # Lazy-loaded on first materialization call
105106_track_materialization_loaded = False
@@ -772,9 +773,39 @@ def _make_inferences(
772773 for feature_service in feature_services_to_update :
773774 feature_service .infer_features (fvs_to_update = fvs_to_update_map )
774775
776+ def _validate_materialize_version (
777+ self ,
778+ version : Optional [str ],
779+ feature_views : Optional [List [str ]],
780+ ) -> Optional [int ]:
781+ """Validate and parse the version parameter for materialize calls.
782+
783+ Returns the parsed version number, or None if no version was specified.
784+ """
785+ if version is None :
786+ return None
787+
788+ if not feature_views or len (feature_views ) != 1 :
789+ raise ValueError (
790+ "--version requires --views with exactly one feature view."
791+ )
792+
793+ if not self .config .registry .enable_online_feature_view_versioning :
794+ raise ValueError (
795+ "Version-aware materialization requires "
796+ "'enable_online_feature_view_versioning: true' under 'registry' "
797+ "in feature_store.yaml."
798+ )
799+
800+ is_latest , version_number = parse_version (version )
801+ if is_latest :
802+ return None
803+ return version_number
804+
775805 def _get_feature_views_to_materialize (
776806 self ,
777807 feature_views : Optional [List [str ]],
808+ version : Optional [int ] = None ,
778809 ) -> List [Union [FeatureView , OnDemandFeatureView ]]:
779810 """
780811 Returns the list of feature views that should be materialized.
@@ -783,6 +814,8 @@ def _get_feature_views_to_materialize(
783814
784815 Args:
785816 feature_views: List of names of feature views to materialize.
817+ version: If set, load this specific version number from the registry
818+ instead of the active definition. Requires exactly one feature view name.
786819
787820 Raises:
788821 FeatureViewNotFoundException: One of the specified feature views could not be found.
@@ -814,15 +847,25 @@ def _get_feature_views_to_materialize(
814847 else :
815848 for name in feature_views :
816849 feature_view : Union [FeatureView , OnDemandFeatureView ]
817- try :
818- feature_view = self ._get_feature_view (name , hide_dummy_entity = False )
819- except FeatureViewNotFoundException :
850+ if version is not None :
851+ feature_view = cast (
852+ Union [FeatureView , OnDemandFeatureView ],
853+ self .registry .get_feature_view_by_version (
854+ name , self .project , version
855+ ),
856+ )
857+ else :
820858 try :
821- feature_view = self ._get_stream_feature_view (
859+ feature_view = self ._get_feature_view (
822860 name , hide_dummy_entity = False
823861 )
824862 except FeatureViewNotFoundException :
825- feature_view = self .get_on_demand_feature_view (name )
863+ try :
864+ feature_view = self ._get_stream_feature_view (
865+ name , hide_dummy_entity = False
866+ )
867+ except FeatureViewNotFoundException :
868+ feature_view = self .get_on_demand_feature_view (name )
826869
827870 if hasattr (feature_view , "online" ) and not feature_view .online :
828871 raise ValueError (
@@ -1152,38 +1195,6 @@ def apply(
11521195 for ent in entities_to_update :
11531196 self .registry .apply_entity (ent , project = self .project , commit = False )
11541197
1155- # Gate: feature services must not reference versioned FVs when online versioning is off
1156- if not self .config .registry .enable_online_feature_view_versioning :
1157- fvs_in_batch = {
1158- fv .name : fv
1159- for fv in itertools .chain (
1160- views_to_update , odfvs_to_update , sfvs_to_update
1161- )
1162- }
1163- for feature_service in services_to_update :
1164- for projection in feature_service .feature_view_projections :
1165- ref_fv : Optional [BaseFeatureView ] = fvs_in_batch .get (
1166- projection .name
1167- )
1168- if ref_fv is None :
1169- try :
1170- ref_fv = self .registry .get_any_feature_view (
1171- projection .name , self .project
1172- )
1173- except FeatureViewNotFoundException :
1174- continue
1175- cur_ver : Optional [int ] = getattr (
1176- ref_fv , "current_version_number" , None
1177- )
1178- if cur_ver is not None and cur_ver > 0 :
1179- raise ValueError (
1180- f"Feature service '{ feature_service .name } ' references feature view "
1181- f"'{ projection .name } ' which is at version v{ cur_ver } . "
1182- f"To use versioned feature views in feature services, set "
1183- f"'enable_online_feature_view_versioning: true' under 'registry' "
1184- f"in feature_store.yaml."
1185- )
1186-
11871198 for feature_service in services_to_update :
11881199 self .registry .apply_feature_service (
11891200 feature_service , project = self .project , commit = False
@@ -1678,6 +1689,7 @@ def materialize_incremental(
16781689 end_date : datetime ,
16791690 feature_views : Optional [List [str ]] = None ,
16801691 full_feature_names : bool = False ,
1692+ version : Optional [str ] = None ,
16811693 ) -> None :
16821694 """
16831695 Materialize incremental new data from the offline store into the online store.
@@ -1694,6 +1706,8 @@ def materialize_incremental(
16941706 materialization for the specified feature views.
16951707 full_feature_names (bool): If True, feature names will be prefixed with the corresponding
16961708 feature view name.
1709+ version (str): Optional version to materialize (e.g., 'v2'). Requires feature_views
1710+ with exactly one entry and enable_online_feature_view_versioning to be enabled.
16971711
16981712 Raises:
16991713 Exception: A feature view being materialized does not have a TTL set.
@@ -1709,8 +1723,9 @@ def materialize_incremental(
17091723 <BLANKLINE>
17101724 ...
17111725 """
1726+ parsed_version = self ._validate_materialize_version (version , feature_views )
17121727 feature_views_to_materialize = self ._get_feature_views_to_materialize (
1713- feature_views
1728+ feature_views , version = parsed_version
17141729 )
17151730 _print_materialization_log (
17161731 None ,
@@ -1831,6 +1846,7 @@ def materialize(
18311846 feature_views : Optional [List [str ]] = None ,
18321847 disable_event_timestamp : bool = False ,
18331848 full_feature_names : bool = False ,
1849+ version : Optional [str ] = None ,
18341850 ) -> None :
18351851 """
18361852 Materialize data from the offline store into the online store.
@@ -1847,6 +1863,8 @@ def materialize(
18471863 disable_event_timestamp (bool): If True, materializes all available data using current datetime as event timestamp instead of source event timestamps
18481864 full_feature_names (bool): If True, feature names will be prefixed with the corresponding
18491865 feature view name.
1866+ version (str): Optional version to materialize (e.g., 'v2'). Requires feature_views
1867+ with exactly one entry and enable_online_feature_view_versioning to be enabled.
18501868
18511869 Examples:
18521870 Materialize all features into the online store over the interval
@@ -1866,8 +1884,9 @@ def materialize(
18661884 f"The given start_date { start_date } is greater than the given end_date { end_date } ."
18671885 )
18681886
1887+ parsed_version = self ._validate_materialize_version (version , feature_views )
18691888 feature_views_to_materialize = self ._get_feature_views_to_materialize (
1870- feature_views
1889+ feature_views , version = parsed_version
18711890 )
18721891 _print_materialization_log (
18731892 start_date ,
0 commit comments