Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
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
108 changes: 108 additions & 0 deletions docs/reference/feature-servers/registry-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,114 @@ Please refer the [page](./../../../docs/getting-started/concepts/permission.md)

**Note**: Recent visits are automatically logged when users access registry objects via the REST API. The logging behavior can be configured through the `feature_server.recent_visit_logging` section in `feature_store.yaml` (see configuration section below).


### Search API

#### Search Resources
- **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
- `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
- `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")
- **Examples**:
```bash
# Basic search across all projects
curl -H "Authorization: Bearer <token>" \
"http://localhost:6572/api/v1/search?query=user"

# Search in specific projects
curl -H "Authorization: Bearer <token>" \
"http://localhost:6572/api/v1/search?query=driver&projects=ride_sharing&projects=analytics"

# Search with tag filtering
curl -H "Authorization: Bearer <token>" \
"http://localhost:6572/api/v1/search?query=features&tags=environment:production&tags=team:ml"

# Search with pagination and sorting
curl -H "Authorization: Bearer <token>" \
"http://localhost:6572/api/v1/search?query=conv_rate&page=1&limit=10&sort_by=name&sort_order=asc"

# Empty query to list all resources with filtering
curl -H "Authorization: Bearer <token>" \
"http://localhost:6572/api/v1/search?query=&projects=my_project&page=1&limit=20"
```
- **Response Example**:
```json
{
"query": "user",
"projects_searched": ["project1", "project2"],
"results": [
{
"type": "entity",
"name": "user_id",
"description": "Primary identifier for users",
"project": "project1",
"match_score": 100
},
{
"type": "featureView",
"name": "user_features",
"description": "User demographic and behavioral features",
"project": "project1",
"match_score": 100
},
{
"type": "feature",
"name": "user_age",
"description": "Age of the user in years",
"project": "project1",
"match_score": 80
},
{
"type": "dataSource",
"name": "user_analytics",
"description": "Analytics data for user behavior tracking",
"project": "project2",
"match_score": 80
}
],
"pagination": {
"page": 1,
"limit": 50,
"total_count": 4,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
}
```
- **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
- **Empty projects list**: Treated as search all projects
- **Error Responses**:
```json
// Invalid sort_by parameter
{
"detail": "Invalid sort_by parameter: 'invalid_field'. Valid options are: ['match_score', 'name', 'type']"
}

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

// No existing projects found
{
"results": [],
"pagination": { "total_count": 0 },
"query": "user",
"projects_searched": [],
"error": "No projects found"
}
```
---

## Registry Server Configuration: Recent Visit Logging
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/feast/api/registry/rest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from feast.api.registry.rest.permissions import get_permission_router
from feast.api.registry.rest.projects import get_project_router
from feast.api.registry.rest.saved_datasets import get_saved_dataset_router
from feast.api.registry.rest.search import get_search_router


def register_all_routes(app: FastAPI, grpc_handler, server=None):
Expand All @@ -22,4 +23,5 @@ def register_all_routes(app: FastAPI, grpc_handler, server=None):
app.include_router(get_permission_router(grpc_handler))
app.include_router(get_project_router(grpc_handler))
app.include_router(get_saved_dataset_router(grpc_handler))
app.include_router(get_search_router(grpc_handler))
app.include_router(get_metrics_router(grpc_handler, server))
135 changes: 39 additions & 96 deletions sdk/python/feast/api/registry/rest/lineage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from feast.api.registry.rest.rest_utils import (
create_grpc_pagination_params,
create_grpc_sorting_params,
get_all_project_resources,
get_pagination_params,
get_sorting_params,
grpc_call,
Expand Down Expand Up @@ -142,69 +143,40 @@ def get_complete_registry_data(
)
lineage_response = grpc_call(grpc_handler.GetRegistryLineage, lineage_req)

# Get all registry objects
entities_req = RegistryServer_pb2.ListEntitiesRequest(
project=project,
allow_cache=allow_cache,
pagination=grpc_pagination,
sorting=grpc_sorting,
)
entities_response = grpc_call(grpc_handler.ListEntities, entities_req)

data_sources_req = RegistryServer_pb2.ListDataSourcesRequest(
project=project,
allow_cache=allow_cache,
pagination=grpc_pagination,
sorting=grpc_sorting,
)
data_sources_response = grpc_call(
grpc_handler.ListDataSources, data_sources_req
)

feature_views_req = RegistryServer_pb2.ListAllFeatureViewsRequest(
project=project,
allow_cache=allow_cache,
pagination=grpc_pagination,
sorting=grpc_sorting,
)
feature_views_response = grpc_call(
grpc_handler.ListAllFeatureViews, feature_views_req
)

feature_services_req = RegistryServer_pb2.ListFeatureServicesRequest(
project=project,
allow_cache=allow_cache,
pagination=grpc_pagination,
sorting=grpc_sorting,
)
feature_services_response = grpc_call(
grpc_handler.ListFeatureServices, feature_services_req
# Get all registry objects using shared helper function
project_resources = get_all_project_resources(
grpc_handler,
project,
allow_cache,
tags={},
pagination_params=pagination_params,
sorting_params=sorting_params,
)

features_req = RegistryServer_pb2.ListFeaturesRequest(
project=project,
pagination=grpc_pagination,
sorting=grpc_sorting,
)
features_response = grpc_call(grpc_handler.ListFeatures, features_req)

return {
"project": project,
"objects": {
"entities": entities_response.get("entities", []),
"dataSources": data_sources_response.get("dataSources", []),
"featureViews": feature_views_response.get("featureViews", []),
"featureServices": feature_services_response.get("featureServices", []),
"features": features_response.get("features", []),
"entities": project_resources.get("entities", []),
"dataSources": project_resources.get("dataSources", []),
"featureViews": project_resources.get("featureViews", []),
"featureServices": project_resources.get("featureServices", []),
"features": project_resources.get("features", []),
},
"relationships": lineage_response.get("relationships", []),
"indirectRelationships": lineage_response.get("indirectRelationships", []),
"pagination": {
"entities": entities_response.get("pagination", {}),
"dataSources": data_sources_response.get("pagination", {}),
"featureViews": feature_views_response.get("pagination", {}),
"featureServices": feature_services_response.get("pagination", {}),
"features": features_response.get("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(
"dataSources", {}
),
"featureViews": project_resources.get("pagination", {}).get(
"featureViews", {}
),
"featureServices": project_resources.get("pagination", {}).get(
"featureServices", {}
),
"features": project_resources.get("pagination", {}).get("features", {}),
"relationships": lineage_response.get("relationshipsPagination", {}),
"indirectRelationships": lineage_response.get(
"indirectRelationshipsPagination", {}
Expand Down Expand Up @@ -266,61 +238,32 @@ def get_complete_registry_data_all(
allow_cache=allow_cache,
)
lineage_response = grpc_call(grpc_handler.GetRegistryLineage, lineage_req)
# Get all registry objects
entities_req = RegistryServer_pb2.ListEntitiesRequest(
project=project_name,
allow_cache=allow_cache,
)
entities_response = grpc_call(grpc_handler.ListEntities, entities_req)
data_sources_req = RegistryServer_pb2.ListDataSourcesRequest(
project=project_name,
allow_cache=allow_cache,
)
data_sources_response = grpc_call(
grpc_handler.ListDataSources, data_sources_req
)
feature_views_req = RegistryServer_pb2.ListAllFeatureViewsRequest(
project=project_name,
allow_cache=allow_cache,
)
feature_views_response = grpc_call(
grpc_handler.ListAllFeatureViews, feature_views_req
)
feature_services_req = RegistryServer_pb2.ListFeatureServicesRequest(
project=project_name,
allow_cache=allow_cache,
)
feature_services_response = grpc_call(
grpc_handler.ListFeatureServices, feature_services_req
)

features_req = RegistryServer_pb2.ListFeaturesRequest(
project=project_name,
# Get all registry objects using shared helper function
project_resources = get_all_project_resources(
grpc_handler, project_name, allow_cache, tags={}
)
features_response = grpc_call(grpc_handler.ListFeatures, features_req)

# Add project field to each object
for entity in entities_response.get("entities", []):
for entity in project_resources.get("entities", []):
entity["project"] = project_name
for ds in data_sources_response.get("dataSources", []):
for ds in project_resources.get("dataSources", []):
ds["project"] = project_name
for fv in feature_views_response.get("featureViews", []):
for fv in project_resources.get("featureViews", []):
fv["project"] = project_name
for fs in feature_services_response.get("featureServices", []):
for fs in project_resources.get("featureServices", []):
fs["project"] = project_name
for feat in features_response.get("features", []):
for feat in project_resources.get("features", []):
feat["project"] = project_name
all_data.append(
{
"project": project_name,
"objects": {
"entities": entities_response.get("entities", []),
"dataSources": data_sources_response.get("dataSources", []),
"featureViews": feature_views_response.get("featureViews", []),
"featureServices": feature_services_response.get(
"featureServices", []
),
"features": features_response.get("features", []),
"entities": project_resources.get("entities", []),
"dataSources": project_resources.get("dataSources", []),
"featureViews": project_resources.get("featureViews", []),
"featureServices": project_resources.get("featureServices", []),
"features": project_resources.get("features", []),
},
"relationships": lineage_response.get("relationships", []),
"indirectRelationships": lineage_response.get(
Expand Down
Loading