Visitar URL original
feat: Added global search api and necessary unit tests by aniketpalu · Pull Request #5532 · feast-dev/feast · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
Optimized Code, better error handling, created reusable functions and…
… combined test cases

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
  • Loading branch information
aniketpalu committed Aug 7, 2025
commit b3533172139b2599f6b50c7135a66aa220c658a7
57 changes: 40 additions & 17 deletions docs/reference/feature-servers/registry-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -1130,14 +1130,20 @@ Please refer the [page](./../../../docs/getting-started/concepts/permission.md)
- **Endpoint**: `GET /api/v1/search`
- **Description**: Search across all Feast resources including entities, feature views, features, feature services, data sources, and saved datasets. Supports cross-project search, fuzzy matching, relevance scoring, and advanced filtering.
- **Parameters**:
- `query` (required): Search query string. Searches in resource names, descriptions, and tags
- `query` (required): Search query string. Searches in resource names, descriptions, and tags. Empty string returns all resources.
- `projects` (optional): List of project names to search in. If not specified, searches all projects
- `allow_cache` (optional, default: `true`): Whether to allow cached data
- `tags` (optional): Filter results by tags in key=value format (e.g., `tags=environment:production&tags=team:ml`)
- `page` (optional, default: `1`): Page number for pagination
- `tags` (optional): Filter results by tags in key:value format (e.g., `tags=environment:production&tags=team:ml`)
- `page` (optional, default: `1`): Page number for pagination (starts from 1)
- `limit` (optional, default: `50`, max: `100`): Number of items per page
- `sort_by` (optional, default: `match_score`): Field to sort by (`match_score`, `name`, or `type`)
- `sort_order` (optional, default: `desc`): Sort order ("asc" or "desc")
- **Search Algorithm**:
- **Exact name match**: Highest priority (score: 100)
- **Description match**: High priority (score: 80)
- **Feature name match**: Medium-high priority (score: 50)
- **Tag match**: Medium priority (score: 60)
- **Fuzzy name match**: Lower priority (score: 40, similarity threshold: 50%)
- **Examples**:
```bash
# Basic search across all projects
Expand Down Expand Up @@ -1198,37 +1204,54 @@ Please refer the [page](./../../../docs/getting-started/concepts/permission.md)
"pagination": {
"page": 1,
"limit": 50,
"total_count": 4,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
"totalCount": 4,
"totalPages": 1,
"hasNext": false,
"hasPrevious": false
},
"errors": []
}
```
- **Project Handling**:
- **No projects specified**: Searches all available projects
- **Single project**: Searches only that project (returns empty if project doesn't exist)
- **Multiple projects**: Searches only existing projects, warns about non-existent ones
- **Single project**: Searches only that project (includes warning if project doesn't exist)
- **Multiple projects**: Searches only existing projects, includes warnings about non-existent ones
- **Empty projects list**: Treated as search all projects
- **Error Responses**:
```json
// Invalid sort_by parameter
// Invalid sort_by parameter (HTTP 400)
{
"detail": "Invalid sort_by parameter: 'invalid_field'. Valid options are: ['match_score', 'name', 'type']"
}

// Invalid sort_order parameter
// Invalid sort_order parameter (HTTP 400)
{
"detail": "Invalid sort_order parameter: 'invalid_order'. Valid options are: ['asc', 'desc']"
}

Comment thread
aniketpalu marked this conversation as resolved.
// No existing projects found
// Missing required query parameter (HTTP 422)
{
"detail": [
{
"type": "missing",
"loc": ["query_params", "query"],
"msg": "Field required"
}
]
}

// Successful response with warnings
{
"results": [],
"pagination": { "total_count": 0 },
"query": "user",
"projects_searched": [],
"error": "No projects found"
"projects_searched": ["existing_project"],
"results": [],
"pagination": {
"page": 1,
"limit": 50,
"totalCount": 0,
"totalPages": 0
},
"errors": ["Following projects do not exist: nonexistent_project"]
}
```
---
Expand Down
31 changes: 23 additions & 8 deletions sdk/python/feast/api/registry/rest/lineage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""REST API endpoints for registry lineage and relationships."""

from typing import Optional
import logging

from fastapi import APIRouter, Depends, HTTPException, Query

Expand All @@ -14,6 +15,8 @@
)
from feast.protos.feast.registry import RegistryServer_pb2

logger = logging.getLogger(__name__)


def get_lineage_router(grpc_handler) -> APIRouter:
router = APIRouter()
Expand Down Expand Up @@ -144,15 +147,23 @@ def get_complete_registry_data(
lineage_response = grpc_call(grpc_handler.GetRegistryLineage, lineage_req)

# Get all registry objects using shared helper function
project_resources = get_all_project_resources(
project_resources, pagination, errors = get_all_project_resources(
grpc_handler,
project,
allow_cache,
tags={},
pagination_params=pagination_params,
sorting_params=sorting_params,
)

if errors and not project_resources:
logger.error(f"Error getting project resources for project {project}: {errors}")
return {
"project": project,
"objects": {},
"relationships": [],
"indirectRelationships": [],
"pagination": {},
}
return {
"project": project,
"objects": {
Expand All @@ -166,17 +177,17 @@ def get_complete_registry_data(
"indirectRelationships": lineage_response.get("indirectRelationships", []),
"pagination": {
# Get pagination metadata from project_resources if available, otherwise use empty dicts
"entities": project_resources.get("pagination", {}).get("entities", {}),
"dataSources": project_resources.get("pagination", {}).get(
"entities": pagination.get("entities", {}),
"dataSources": pagination.get(
"dataSources", {}
),
"featureViews": project_resources.get("pagination", {}).get(
"featureViews": pagination.get(
"featureViews", {}
),
"featureServices": project_resources.get("pagination", {}).get(
"featureServices": pagination.get(
"featureServices", {}
),
"features": project_resources.get("pagination", {}).get("features", {}),
"features": pagination.get("features", {}),
"relationships": lineage_response.get("relationshipsPagination", {}),
"indirectRelationships": lineage_response.get(
"indirectRelationshipsPagination", {}
Expand Down Expand Up @@ -240,10 +251,14 @@ def get_complete_registry_data_all(
lineage_response = grpc_call(grpc_handler.GetRegistryLineage, lineage_req)

# Get all registry objects using shared helper function
project_resources = get_all_project_resources(
project_resources, _, errors = get_all_project_resources(
grpc_handler, project_name, allow_cache, tags={}
)

if errors and not project_resources:
logger.error(f"Error getting project resources for project {project_name}: {errors}")
continue

# Add project field to each object
for entity in project_resources.get("entities", []):
entity["project"] = project_name
Expand Down
22 changes: 14 additions & 8 deletions sdk/python/feast/api/registry/rest/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
get_pagination_params,
get_sorting_params,
grpc_call,
search_all_projects,
)
from feast.protos.feast.registry import RegistryServer_pb2

Expand All @@ -32,17 +33,22 @@ def list_projects(
pagination_params: dict = Depends(get_pagination_params),
sorting_params: dict = Depends(get_sorting_params),
):
req = RegistryServer_pb2.ListProjectsRequest(
allow_cache=allow_cache,
pagination=create_grpc_pagination_params(pagination_params),
sorting=create_grpc_sorting_params(sorting_params),
)
response = grpc_call(grpc_handler.ListProjects, req)
projects = response.get("projects", [])
try:
projects, pagination, err_msg = search_all_projects(
grpc_handler=grpc_handler,
allow_cache=allow_cache,
pagination_params=pagination_params,
sorting_params=sorting_params,
)
except Exception as e:
return {"error": str(e)}

if err_msg:
return {"error": err_msg}

return {
"projects": projects,
"pagination": response.get("pagination", {}),
"pagination": pagination,
}

return router
Loading