diff --git a/sdk/python/feast/api/registry/rest/feature_views.py b/sdk/python/feast/api/registry/rest/feature_views.py index ede3c5d804a..61df2837b4f 100644 --- a/sdk/python/feast/api/registry/rest/feature_views.py +++ b/sdk/python/feast/api/registry/rest/feature_views.py @@ -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 {} @@ -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) @@ -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( @@ -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) diff --git a/sdk/python/feast/api/registry/rest/rest_registry_server.py b/sdk/python/feast/api/registry/rest/rest_registry_server.py index d73a7f14672..92122ba46df 100644 --- a/sdk/python/feast/api/registry/rest/rest_registry_server.py +++ b/sdk/python/feast/api/registry/rest/rest_registry_server.py @@ -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 @@ -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() diff --git a/sdk/python/tests/unit/api/test_api_rest_registry.py b/sdk/python/tests/unit/api/test_api_rest_registry.py index 91b97c4406d..00651db0f3c 100644 --- a/sdk/python/tests/unit/api/test_api_rest_registry.py +++ b/sdk/python/tests/unit/api/test_api_rest_registry.py @@ -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")