Skip to content
40 changes: 40 additions & 0 deletions docs/reference/beta-on-demand-feature-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,44 @@ There are new CLI commands to manage on demand feature views:
feast on-demand-feature-views list: Lists all registered on demand feature views after feast apply is run.
feast on-demand-feature-views describe [NAME]: Describes the definition of an on demand feature view.

## Troubleshooting

### Validation Issues with Complex Transformations

When defining On Demand Feature Views with complex transformations, you may encounter validation errors during `feast apply`. Feast validates ODFVs by constructing random inputs and running the transformation function to infer the output schema. This validation can sometimes be overly strict or fail for complex transformation logic.

If you encounter validation errors that you believe are incorrect, you can skip feature view validation:

**Python SDK:**
```python
from feast import FeatureStore

store = FeatureStore(repo_path=".")
store.apply([my_odfv], skip_feature_view_validation=True)
```

**CLI:**
```bash
feast apply --skip-feature-view-validation
```

{% hint style="warning" %}
Skipping validation bypasses important checks. Use this option only when the validation system is being overly strict. We encourage you to report validation issues on the [Feast GitHub repository](https://github.com/feast-dev/feast/issues) so the team can improve the validation system.
{% endhint %}

**When to use skip_feature_view_validation:**
- Your ODFV transformation uses complex logic that fails the random input validation
- You've verified your transformation works correctly with real data
- The validation error doesn't reflect an actual issue with your transformation

**What validation is skipped:**
- Feature view name uniqueness checks
- ODFV transformation validation via `_construct_random_input()`
- Schema inference for features

**What is NOT skipped:**
- Data source validation (use `--skip-source-validation` to skip)
- Registry operations
- Infrastructure updates


21 changes: 20 additions & 1 deletion docs/reference/feast-cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,32 @@ Creates or updates a feature store deployment
feast apply
```

**Options:**
* `--skip-source-validation`: Skip validation of data sources (don't check if tables exist)
* `--skip-feature-view-validation`: Skip validation of feature views. Use with caution as this skips important checks

```bash
# Skip only data source validation
feast apply --skip-source-validation

# Skip only feature view validation
feast apply --skip-feature-view-validation

# Skip both validations
feast apply --skip-source-validation --skip-feature-view-validation
```

**What does Feast apply do?**

1. Feast will scan Python files in your feature repository and find all Feast object definitions, such as feature views, entities, and data sources.
2. Feast will validate your feature definitions (e.g. for uniqueness of features)
2. Feast will validate your feature definitions (e.g. for uniqueness of features). This validation can be skipped using the `--skip-feature-view-validation` flag if the type/validation system is being overly strict.
3. Feast will sync the metadata about Feast objects to the registry. If a registry does not exist, then it will be instantiated. The standard registry is a simple protobuf binary file that is stored on disk \(locally or in an object store\).
4. Feast CLI will create all necessary feature store infrastructure. The exact infrastructure that is deployed or configured depends on the `provider` configuration that you have set in `feature_store.yaml`. For example, setting `local` as your provider will result in a `sqlite` online store being created.

{% hint style="info" %}
The `--skip-feature-view-validation` flag is particularly useful for On-Demand Feature Views (ODFVs) with complex transformations that may fail validation. However, use it with caution and please report any validation issues to the Feast team on GitHub.
{% endhint %}

{% hint style="warning" %}
`feast apply` \(when configured to use cloud provider like `gcp` or `aws`\) will create cloud infrastructure. This may incur costs.
{% endhint %}
Expand Down
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 @@ -853,6 +859,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 @@ -954,11 +962,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