Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 14 additions & 5 deletions sdk/python/feast/api/registry/rest/feature_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,25 @@ class ApplyFeatureViewRequestBody(BaseModel):
owner: Optional[str] = ""


def _extract_feature_view_from_any(any_feature_view: dict) -> dict:
def _extract_feature_view_from_any(
any_feature_view: dict, project: Optional[str] = None
) -> dict:
"""Extract the specific feature view type and data from an AnyFeatureView object.

Args:
any_feature_view: Dictionary containing the AnyFeatureView data
project: Project the feature view was read from, filled into an unset spec.project

Returns:
Dictionary with 'type' and feature view data, or empty dict if no valid type found
"""
for key, value in any_feature_view.items():
if value:
return {"type": key, **value}
result = {"type": key, **value}
spec = result.get("spec")
if project and isinstance(spec, dict) and not spec.get("project"):
result["spec"] = {**spec, "project": project}
return result

return {}

Expand Down Expand Up @@ -110,7 +117,9 @@ def list_feature_views_all(
response = grpc_call(grpc_handler.ListAllFeatureViews, req)
any_feature_views = response.get("featureViews", [])
for any_feature_view in any_feature_views:
feature_view = _extract_feature_view_from_any(any_feature_view)
feature_view = _extract_feature_view_from_any(
any_feature_view, project_name
)
if feature_view:
feature_view["project"] = project_name
all_feature_views.append(feature_view)
Expand Down Expand Up @@ -157,7 +166,7 @@ def get_any_feature_view(
response = grpc_call(grpc_handler.GetAnyFeatureView, req)
any_feature_view = response.get("anyFeatureView", {})

result = _extract_feature_view_from_any(any_feature_view)
result = _extract_feature_view_from_any(any_feature_view, project)

if include_relationships:
relationships = get_object_relationships(
Expand Down Expand Up @@ -315,7 +324,7 @@ def list_all_feature_views(
# Extract the specific type of feature view from each AnyFeatureView
feature_views = []
for any_feature_view in any_feature_views:
feature_view = _extract_feature_view_from_any(any_feature_view)
feature_view = _extract_feature_view_from_any(any_feature_view, project)
if feature_view:
feature_views.append(feature_view)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import re

from fastapi import Depends, FastAPI, HTTPException, Request, status
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import ValidationError
Expand Down Expand Up @@ -63,12 +63,6 @@ def __init__(self, store: FeatureStore):
root_path="/api/v1",
docs_url="/",
redoc_url="/docs",
default_status_code=status.HTTP_200_OK,
default_headers={
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"X-Frame-Options": "DENY",
},
)
self._add_exception_handlers()
self._add_logging_middleware()
Expand Down
20 changes: 20 additions & 0 deletions sdk/python/tests/unit/api/test_api_rest_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,26 @@ def test_feature_views_type_field_via_rest(fastapi_test_app):
assert data["spec"]["name"] == "user_profile"


def test_feature_views_spec_project_via_rest(fastapi_test_app):
"""Test that spec.project is populated on every feature view endpoint."""
response = fastapi_test_app.get("/feature_views?project=demo_project")
assert response.status_code == 200
for fv in response.json()["featureViews"]:
assert fv["spec"]["project"] == "demo_project"

response = fastapi_test_app.get("/feature_views/user_profile?project=demo_project")
assert response.status_code == 200
assert response.json()["spec"]["project"] == "demo_project"

response = fastapi_test_app.get("/feature_views/all")
assert response.status_code == 200
feature_views = response.json()["featureViews"]
assert feature_views
for fv in feature_views:
assert fv["project"] == "demo_project"
assert fv["spec"]["project"] == "demo_project"


def test_feature_views_entity_filtering_via_rest(fastapi_test_app):
"""Test that feature views can be filtered by entity."""
response = fastapi_test_app.get("/feature_views?project=demo_project")
Expand Down
Loading