Visitar URL original
feat: Add skip_feature_view_validation parameter to FeatureStore.apply() and plan() by Copilot 路 Pull Request #5859 路 feast-dev/feast 路 GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
24 changes: 20 additions & 4 deletions sdk/python/feast/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,15 @@ def endpoint(ctx: click.Context):
is_flag=True,
help="Don't validate the data sources by checking for that the tables exist.",
)
@click.option(
"--skip-feature-view-validation",
is_flag=True,
help="Don't validate feature views. Use with caution as this skips important checks.",
)
@click.pass_context
def plan_command(ctx: click.Context, skip_source_validation: bool):
def plan_command(
ctx: click.Context, skip_source_validation: bool, skip_feature_view_validation: bool
):
"""
Create or update a feature store deployment
"""
Expand All @@ -247,7 +254,7 @@ def plan_command(ctx: click.Context, skip_source_validation: bool):
cli_check_repo(repo, fs_yaml_file)
repo_config = load_repo_config(repo, fs_yaml_file)
try:
plan(repo_config, repo, skip_source_validation)
plan(repo_config, repo, skip_source_validation, skip_feature_view_validation)
except FeastProviderLoginError as e:
print(str(e))

Expand All @@ -258,8 +265,15 @@ def plan_command(ctx: click.Context, skip_source_validation: bool):
is_flag=True,
help="Don't validate the data sources by checking for that the tables exist.",
)
@click.option(
"--skip-feature-view-validation",
is_flag=True,
help="Don't validate feature views. Use with caution as this skips important checks.",
)
@click.pass_context
def apply_total_command(ctx: click.Context, skip_source_validation: bool):
def apply_total_command(
ctx: click.Context, skip_source_validation: bool, skip_feature_view_validation: bool
):
"""
Create or update a feature store deployment
"""
Expand All @@ -269,7 +283,9 @@ def apply_total_command(ctx: click.Context, skip_source_validation: bool):

repo_config = load_repo_config(repo, fs_yaml_file)
try:
apply_total(repo_config, repo, skip_source_validation)
apply_total(
repo_config, repo, skip_source_validation, skip_feature_view_validation
)
except FeastProviderLoginError as e:
print(str(e))

Expand Down
31 changes: 20 additions & 11 deletions sdk/python/feast/feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,9 @@ def _get_feature_views_to_materialize(
return feature_views_to_materialize

def plan(
self, desired_repo_contents: RepoContents
self,
desired_repo_contents: RepoContents,
skip_feature_view_validation: bool = False,
) -> Tuple[RegistryDiff, InfraDiff, Infra]:
"""Dry-run registering objects to metadata store.

Expand All @@ -733,6 +735,8 @@ def plan(

Args:
desired_repo_contents: The desired repo state.
skip_feature_view_validation: If True, skip validation of feature views. This can be useful when the validation
system is being overly strict. Use with caution and report any issues on GitHub. Default is False.

Raises:
ValueError: The 'objects' parameter could not be parsed properly.
Expand Down Expand Up @@ -767,11 +771,12 @@ def plan(
... permissions=list())) # register entity and feature view
"""
# Validate and run inference on all the objects to be registered.
self._validate_all_feature_views(
desired_repo_contents.feature_views,
desired_repo_contents.on_demand_feature_views,
desired_repo_contents.stream_feature_views,
)
if not skip_feature_view_validation:
self._validate_all_feature_views(
desired_repo_contents.feature_views,
desired_repo_contents.on_demand_feature_views,
desired_repo_contents.stream_feature_views,
)
_validate_data_sources(desired_repo_contents.data_sources)
self._make_inferences(
desired_repo_contents.data_sources,
Expand Down Expand Up @@ -835,6 +840,7 @@ def apply(
],
objects_to_delete: Optional[List[FeastObject]] = None,
partial: bool = True,
skip_feature_view_validation: bool = False,
):
"""Register objects to metadata store and update related infrastructure.

Expand All @@ -849,6 +855,8 @@ def apply(
provider's infrastructure. This deletion will only be performed if partial is set to False.
partial: If True, apply will only handle the specified objects; if False, apply will also delete
all the objects in objects_to_delete, and tear down any associated cloud resources.
skip_feature_view_validation: If True, skip validation of feature views. This can be useful when the validation
system is being overly strict. Use with caution and report any issues on GitHub. Default is False.

Raises:
ValueError: The 'objects' parameter could not be parsed properly.
Expand Down Expand Up @@ -950,11 +958,12 @@ def apply(
entities_to_update.append(DUMMY_ENTITY)

# Validate all feature views and make inferences.
self._validate_all_feature_views(
views_to_update,
odfvs_to_update,
sfvs_to_update,
)
if not skip_feature_view_validation:
self._validate_all_feature_views(
views_to_update,
odfvs_to_update,
sfvs_to_update,
)
self._make_inferences(
data_sources_to_update,
entities_to_update,
Expand Down
37 changes: 31 additions & 6 deletions sdk/python/feast/repo_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,12 @@ def parse_repo(repo_root: Path) -> RepoContents:
return res


def plan(repo_config: RepoConfig, repo_path: Path, skip_source_validation: bool):
def plan(
repo_config: RepoConfig,
repo_path: Path,
skip_source_validation: bool,
skip_feature_view_validation: bool = False,
):
os.chdir(repo_path)
repo = _get_repo_contents(repo_path, repo_config.project, repo_config)
for project in repo.projects:
Expand All @@ -234,7 +239,9 @@ def plan(repo_config: RepoConfig, repo_path: Path, skip_source_validation: bool)
for data_source in data_sources:
provider.validate_data_source(store.config, data_source)

registry_diff, infra_diff, _ = store.plan(repo)
registry_diff, infra_diff, _ = store.plan(
repo, skip_feature_view_validation=skip_feature_view_validation
)
click.echo(registry_diff.to_string())
click.echo(infra_diff.to_string())

Expand Down Expand Up @@ -334,6 +341,7 @@ def apply_total_with_repo_instance(
registry: BaseRegistry,
repo: RepoContents,
skip_source_validation: bool,
skip_feature_view_validation: bool = False,
):
if not skip_source_validation:
provider = store._get_provider()
Expand All @@ -351,13 +359,20 @@ def apply_total_with_repo_instance(
) = extract_objects_for_apply_delete(project_name, registry, repo)

if store._should_use_plan():
registry_diff, infra_diff, new_infra = store.plan(repo)
registry_diff, infra_diff, new_infra = store.plan(
repo, skip_feature_view_validation=skip_feature_view_validation
)
click.echo(registry_diff.to_string())

store._apply_diffs(registry_diff, infra_diff, new_infra)
click.echo(infra_diff.to_string())
else:
store.apply(all_to_apply, objects_to_delete=all_to_delete, partial=False)
store.apply(
all_to_apply,
objects_to_delete=all_to_delete,
partial=False,
skip_feature_view_validation=skip_feature_view_validation,
)
log_infra_changes(views_to_keep, views_to_delete)


Expand Down Expand Up @@ -396,7 +411,12 @@ def create_feature_store(
return FeatureStore(repo_path=str(repo), fs_yaml_file=fs_yaml_file)


def apply_total(repo_config: RepoConfig, repo_path: Path, skip_source_validation: bool):
def apply_total(
repo_config: RepoConfig,
repo_path: Path,
skip_source_validation: bool,
skip_feature_view_validation: bool = False,
):
os.chdir(repo_path)
repo = _get_repo_contents(repo_path, repo_config.project, repo_config)
for project in repo.projects:
Expand All @@ -411,7 +431,12 @@ def apply_total(repo_config: RepoConfig, repo_path: Path, skip_source_validation
# TODO: When we support multiple projects in a single repo, we should filter repo contents by project. Currently there is no way to associate Feast objects to project.
print(f"Applying changes for project {project.name}")
apply_total_with_repo_instance(
store, project.name, registry, repo, skip_source_validation
store,
project.name,
registry,
repo,
skip_source_validation,
skip_feature_view_validation,
)


Expand Down
71 changes: 71 additions & 0 deletions sdk/python/tests/unit/test_skip_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
Tests for skip_feature_view_validation parameter in FeatureStore.apply() and FeatureStore.plan()

This feature allows users to skip Feature View validation when the validation system
is being overly strict. This is particularly important for:
- Feature transformations that go through validation (e.g., _construct_random_input in ODFVs)
- Cases where the type/validation system is being too restrictive

Users should be encouraged to report issues on GitHub when they need to use this flag.
"""

import inspect

from feast.feature_store import FeatureStore


def test_apply_has_skip_feature_view_validation_parameter():
"""Test that FeatureStore.apply() method has skip_feature_view_validation parameter"""
# Get the signature of the apply method
sig = inspect.signature(FeatureStore.apply)

# Check that skip_feature_view_validation parameter exists
assert "skip_feature_view_validation" in sig.parameters

# Check that it has a default value of False
param = sig.parameters["skip_feature_view_validation"]
assert param.default is False

# Check that it's a boolean type hint (if type hints are present)
if param.annotation != inspect.Parameter.empty:
assert param.annotation == bool


def test_plan_has_skip_feature_view_validation_parameter():
"""Test that FeatureStore.plan() method has skip_feature_view_validation parameter"""
# Get the signature of the plan method
sig = inspect.signature(FeatureStore.plan)

# Check that skip_feature_view_validation parameter exists
assert "skip_feature_view_validation" in sig.parameters

# Check that it has a default value of False
param = sig.parameters["skip_feature_view_validation"]
assert param.default is False

# Check that it's a boolean type hint (if type hints are present)
if param.annotation != inspect.Parameter.empty:
assert param.annotation == bool


def test_skip_feature_view_validation_use_case_documentation():
"""
Documentation test: This test documents the key use case for skip_feature_view_validation.

The skip_feature_view_validation flag is particularly important for On-Demand Feature Views (ODFVs)
that use feature transformations. During the apply() process, ODFVs call infer_features()
which internally uses _construct_random_input() to validate the transformation.

Sometimes this validation can be overly strict or fail for complex transformations.
In such cases, users can use skip_feature_view_validation=True to bypass this check.

Example use case from the issue:
- User has an ODFV with a complex transformation
- The _construct_random_input validation fails or is too restrictive
- User can now call: fs.apply([odfv], skip_feature_view_validation=True)
- The ODFV is registered without going through the validation

Note: Users should be encouraged to report such cases on GitHub so the Feast team
can improve the validation system.
"""
pass # This is a documentation test
Loading