Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1292,12 +1292,14 @@ spec:
enum:
- snowflake.online
- redis
- eg-valkey
- ikv
- datastore
- dynamodb
- bigtable
- postgres
- cassandra
- scylladb
- mysql
- hazelcast
- singlestore
Expand All @@ -1306,6 +1308,7 @@ spec:
- qdrant
- couchbase.online
- milvus
- eg-milvus
type: string
required:
- secretRef
Expand Down Expand Up @@ -1767,7 +1770,9 @@ spec:
want to use.
enum:
- sql
- sql-fallback
- snowflake.registry
- http
type: string
required:
- secretRef
Expand Down Expand Up @@ -5285,12 +5290,14 @@ spec:
enum:
- snowflake.online
- redis
- eg-valkey
- ikv
- datastore
- dynamodb
- bigtable
- postgres
- cassandra
- scylladb
- mysql
- hazelcast
- singlestore
Expand All @@ -5299,6 +5306,7 @@ spec:
- qdrant
- couchbase.online
- milvus
- eg-milvus
type: string
required:
- secretRef
Expand Down Expand Up @@ -5772,7 +5780,9 @@ spec:
you want to use.
enum:
- sql
- sql-fallback
- snowflake.registry
- http
type: string
required:
- secretRef
Expand Down
10 changes: 10 additions & 0 deletions infra/feast-operator/dist/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1300,12 +1300,14 @@ spec:
enum:
- snowflake.online
- redis
- eg-valkey
- ikv
- datastore
- dynamodb
- bigtable
- postgres
- cassandra
- scylladb
- mysql
- hazelcast
- singlestore
Expand All @@ -1314,6 +1316,7 @@ spec:
- qdrant
- couchbase.online
- milvus
- eg-milvus
type: string
required:
- secretRef
Expand Down Expand Up @@ -1775,7 +1778,9 @@ spec:
want to use.
enum:
- sql
- sql-fallback
- snowflake.registry
- http
type: string
required:
- secretRef
Expand Down Expand Up @@ -5293,12 +5298,14 @@ spec:
enum:
- snowflake.online
- redis
- eg-valkey
- ikv
- datastore
- dynamodb
- bigtable
- postgres
- cassandra
- scylladb
- mysql
- hazelcast
- singlestore
Expand All @@ -5307,6 +5314,7 @@ spec:
- qdrant
- couchbase.online
- milvus
- eg-milvus
type: string
required:
- secretRef
Expand Down Expand Up @@ -5780,7 +5788,9 @@ spec:
you want to use.
enum:
- sql
- sql-fallback
- snowflake.registry
- http
type: string
required:
- secretRef
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,14 @@ class FeatureViewProjectionModel(BaseModel):
"""

name: str
name_alias: Optional[str]
name_alias: Optional[str] = None
desired_features: List[str]
features: List[FieldModel]
join_key_map: Dict[str, str]
timestamp_field: Optional[str]
date_partition_column: Optional[str]
created_timestamp_column: Optional[str]
batch_source: Optional[AnyBatchDataSource]
timestamp_field: Optional[str] = None
date_partition_column: Optional[str] = None
created_timestamp_column: Optional[str] = None
batch_source: Optional[AnyBatchDataSource] = None

def to_feature_view_projection(self) -> FeatureViewProjection:
return FeatureViewProjection(
Expand Down
99 changes: 99 additions & 0 deletions sdk/python/tests/unit/test_pydantic_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,44 @@ def test_idempotent_feature_view_projection_conversion():
assert pydantic_obj == FeatureViewProjectionModel.model_validate(pydantic_json)


def test_feature_view_projection_backwards_compatibility():
# Test deserialization with minimal fields (missing new optional fields)
# https://github.com/ExpediaGroup/feast/pull/295/files#diff-5ad1ae3dd32afd2194e090c33d3661dcc68de8a49b1358f2a7c2796394e1f2fc
minimal_dict = {
"name": "test_projection",
"desired_features": [],
"features": [],
"join_key_map": {},
}
pydantic_obj = FeatureViewProjectionModel.model_validate(minimal_dict)
assert pydantic_obj.name == "test_projection"
assert pydantic_obj.name_alias is None
assert pydantic_obj.desired_features == []
assert pydantic_obj.features == []
assert pydantic_obj.join_key_map == {}
assert pydantic_obj.timestamp_field is None
assert pydantic_obj.date_partition_column is None
assert pydantic_obj.created_timestamp_column is None
assert pydantic_obj.batch_source is None

minimal_json = '{"name": "test_projection_json", "desired_features": [], "features": [], "join_key_map": {}}'
pydantic_obj_from_json = FeatureViewProjectionModel.model_validate_json(
minimal_json
)
assert pydantic_obj_from_json.name == "test_projection_json"
assert pydantic_obj_from_json.timestamp_field is None
assert pydantic_obj_from_json.date_partition_column is None
assert pydantic_obj_from_json.created_timestamp_column is None
assert pydantic_obj_from_json.batch_source is None

converted_python_obj = pydantic_obj.to_feature_view_projection()
assert converted_python_obj.name == "test_projection"
assert converted_python_obj.timestamp_field is None
assert converted_python_obj.date_partition_column is None
assert converted_python_obj.created_timestamp_column is None
assert converted_python_obj.batch_source is None


def test_idempotent_on_demand_feature_view_conversion():
tags = {
"tag1": "val1",
Expand Down Expand Up @@ -831,6 +869,67 @@ def test_idempotent_feature_service_conversion():
assert pydantic_obj == FeatureServiceModel.model_validate(pydantic_json)


def test_feature_service_backwards_compatibility():
# Test deserialization with feature_view_projections missing optional fields
# https://github.com/ExpediaGroup/feast/pull/295/files#diff-5ad1ae3dd32afd2194e090c33d3661dcc68de8a49b1358f2a7c2796394e1f2fc
field1 = Field(name="feature1", dtype=Float32)
field2 = Field(name="feature2", dtype=String)
field1_model = FieldModel.from_field(field1)
field2_model = FieldModel.from_field(field2)

minimal_dict = {
"name": "test_feature_service",
"features": [],
"feature_view_projections": [
{
"name": "projection1",
"desired_features": ["feature1", "feature2"],
"features": [
field1_model.model_dump(),
field2_model.model_dump(),
],
"join_key_map": {"entity_id": "entity_id"},
},
{
"name": "projection2",
"desired_features": [],
"features": [],
"join_key_map": {},
},
],
"description": "Test feature service",
"tags": {"env": "test"},
"owner": "test@example.com",
"created_timestamp": None,
"last_updated_timestamp": None,
}
pydantic_obj = FeatureServiceModel.model_validate(minimal_dict)
assert pydantic_obj.name == "test_feature_service"
assert len(pydantic_obj.feature_view_projections) == 2

proj1 = pydantic_obj.feature_view_projections[0]
assert proj1.name == "projection1"
assert proj1.timestamp_field is None
assert proj1.date_partition_column is None
assert proj1.created_timestamp_column is None
assert proj1.batch_source is None
assert len(proj1.features) == 2

proj2 = pydantic_obj.feature_view_projections[1]
assert proj2.name == "projection2"
assert proj2.timestamp_field is None
assert proj2.date_partition_column is None
assert proj2.created_timestamp_column is None
assert proj2.batch_source is None

pydantic_json = pydantic_obj.model_dump_json()
pydantic_obj_from_json = FeatureServiceModel.model_validate_json(pydantic_json)
assert pydantic_obj_from_json.name == "test_feature_service"
assert len(pydantic_obj_from_json.feature_view_projections) == 2
assert pydantic_obj_from_json.feature_view_projections[0].timestamp_field is None
assert pydantic_obj_from_json.feature_view_projections[1].batch_source is None


def test_idempotent_project_metadata_conversion():
python_obj = ProjectMetadata(
project_name="test_project",
Expand Down
Loading