Skip to content

feat: add conditions to validators #589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

pnilan
Copy link
Contributor

@pnilan pnilan commented Jun 6, 2025

What

  • Adds conditions to validators, enabling validators to only be invoked when they meet certain conditions, for example validate that a value adheres to schema only if its optional key is included in the config.

Summary by CodeRabbit

  • New Features
    • Added support for conditional validation in DpathValidator and PredicateValidator, enabling validation strategies to be applied only when specified conditions based on configuration are met.
  • Tests
    • Added tests confirming validation is skipped when conditions evaluate to false; updated existing tests to include the new condition parameter.

@pnilan pnilan requested a review from brianjlai June 6, 2025 20:57
@github-actions github-actions bot added the enhancement New feature or request label Jun 6, 2025
@pnilan pnilan marked this pull request as ready for review June 6, 2025 20:58
@Copilot Copilot AI review requested due to automatic review settings June 6, 2025 20:58
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds an optional condition parameter to both PredicateValidator and DpathValidator, allowing validation strategies to be skipped based on runtime config. Core changes include:

  • Extending validator constructors and validate methods with config-driven conditional logic.
  • Updating the component factory and schemas to support the new condition field.
  • Revising unit tests to pass config and condition, and adding negative-case tests for skipped validation.

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
unit_tests/.../test_predicate_validator.py Updated instantiation to include config/condition; added skip test
unit_tests/.../test_dpath_validator.py Same updates as above for DpathValidator
airbyte_cdk/.../predicate_validator.py Added config, condition fields and conditional skip in validate
airbyte_cdk/.../dpath_validator.py Added config, condition fields and conditional skip in validate
airbyte_cdk/.../model_to_component_factory.py Passes config and condition into newly extended validators
airbyte_cdk/.../declarative_component_schema.py Added condition property to Pydantic models
airbyte_cdk/.../declarative_component_schema.yaml Added condition definitions in YAML schema
Comments suppressed due to low confidence (4)

airbyte_cdk/sources/declarative/validators/predicate_validator.py:27

  • Update the validate docstring to mention the new config and condition parameters and clarify that validation is skipped when the condition evaluates to false.
def validate(self) -> None:

airbyte_cdk/sources/declarative/validators/dpath_validator.py:39

  • Enhance the docstring to explain that if condition evaluates to false based on config, the validation strategy is skipped.
def validate(self, input_data: dict[str, Any]) -> None:

unit_tests/sources/declarative/validators/test_predicate_validator.py:59

  • [nitpick] Add a complementary test where the condition evaluates to true (e.g., "{{ config.get('test') }}") to verify that the strategy is invoked when the condition passes.
def test_given_condition_is_false_when_validate_then_validate_is_not_called(self):

unit_tests/sources/declarative/validators/test_dpath_validator.py:126

  • [nitpick] Similarly, include a test where the condition evaluates to true to ensure the DpathValidator applies the strategy when expected.
def test_given_condition_is_false_when_validate_then_validate_is_not_called(self):

Copy link

github-actions bot commented Jun 6, 2025

PyTest Results (Fast)

3 659 tests  +2   3 649 ✅ +2   5m 46s ⏱️ -2s
    1 suites ±0      10 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 31900fd. ± Comparison against base commit f398ab6.

♻️ This comment has been updated with latest results.

Copy link
Contributor

coderabbitai bot commented Jun 6, 2025

📝 Walkthrough

Walkthrough

This change introduces a new optional condition property to the DpathValidator and PredicateValidator components. The validators now evaluate this condition against the provided configuration; if it evaluates to false, validation is skipped. Schema definitions, models, factories, validator logic, and unit tests were updated accordingly.

Changes

File(s) Change Summary
airbyte_cdk/sources/declarative/declarative_component_schema.yaml Added condition property to DpathValidator and PredicateValidator schema definitions.
airbyte_cdk/sources/declarative/models/declarative_component_schema.py Added condition field to DpathValidator and PredicateValidator models.
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py Updated factories to support passing condition and config to validator constructors.
airbyte_cdk/sources/declarative/validators/dpath_validator.py Added config and condition fields; updated validation logic to skip when condition is false.
airbyte_cdk/sources/declarative/validators/predicate_validator.py Added config and condition fields; updated validation logic to skip when condition is false.
unit_tests/sources/declarative/validators/test_dpath_validator.py Updated tests to use new parameters; added test for skipping validation when condition is false.
unit_tests/sources/declarative/validators/test_predicate_validator.py Updated tests to use new parameters; added test for skipping validation when condition is false.
unit_tests/sources/declarative/spec/test_spec.py Updated tests to pass config and condition to DpathValidator instances.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ValidatorFactory
    participant Validator
    participant Config

    User->>ValidatorFactory: Create Validator (with model, config)
    ValidatorFactory->>Validator: Instantiate (pass config, condition)
    User->>Validator: validate(input_data)
    Validator->>Validator: Evaluate condition against config
    alt Condition is true
        Validator->>Validator: Perform validation strategy
    else Condition is false
        Validator-->>User: Skip validation
    end
Loading

Suggested reviewers

  • lazebnyi

Would you like to consider adding more examples of complex conditions in the schema documentation to help users understand possible use cases, wdyt?

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (9)
unit_tests/sources/declarative/validators/test_predicate_validator.py (1)

59-70: Excellent test coverage for the conditional validation feature!

This test properly verifies that validation is skipped when the condition evaluates to False. The condition logic is correct: {{ not config.get('test') }} with config={"test": "test"} evaluates to not "test" which is False, so strategy.validate_called should indeed be False.

One small suggestion - would it be worth adding a comment explaining the condition evaluation for clarity, wdyt? Something like:

# Condition evaluates to False since config.get('test') returns "test" (truthy)
condition="{{ not config.get('test') }}",
airbyte_cdk/sources/declarative/validators/predicate_validator.py (3)

21-22: Consider making the condition field optional, wdyt?

Since conditional validation is a new optional feature, should the condition field be typed as Optional[str] = None to better reflect that it's not always required? This would make the API more explicit about when conditions are used.

-    condition: str
+    condition: Optional[str] = None

Don't forget to import Optional from typing if you go with this approach.


24-25: Add null safety to the post_init method, wdyt?

The current implementation always creates an InterpolatedBoolean even when condition might be None or empty. Consider adding a guard to handle this more gracefully?

 def __post_init__(self) -> None:
-    self._interpolated_condition = InterpolatedBoolean(condition=self.condition, parameters={})
+    if self.condition:
+        self._interpolated_condition = InterpolatedBoolean(condition=self.condition, parameters={})
+    else:
+        self._interpolated_condition = None

You'd also need to update the condition check in the validate method accordingly.


33-35: Consider adding error handling and updating documentation, wdyt?

The condition evaluation logic looks correct, but a couple of suggestions:

  1. Should we handle potential exceptions from _interpolated_condition.eval()? Condition evaluation errors could be confusing to debug.

  2. The class docstring doesn't mention the new conditional behavior - might be worth updating it to reflect this enhancement?

For error handling:

-        if self.condition and not self._interpolated_condition.eval(self.config):
-            return
+        if self.condition:
+            try:
+                if not self._interpolated_condition.eval(self.config):
+                    return
+            except Exception as e:
+                raise ValueError(f"Failed to evaluate validator condition '{self.condition}': {e}")

For documentation:

 class PredicateValidator:
     """
-    Validator that applies a validation strategy to a value.
+    Validator that applies a validation strategy to a value.
+    
+    If a condition is specified, the validation is only performed when the condition
+    evaluates to true against the provided configuration.
     """
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py (2)

870-879: create_dpath_validator: forward parameters from the model?

I see you’ve added config and condition support—which is great—but unlike most factory methods, you’re not forwarding model.parameters into the DpathValidator. Do we need to pass parameters=model.parameters or {} here for interpolation inside the validator? wdyt?


881-892: create_predicate_validator: consider including parameters as well

Similar to DpathValidator, you’ve wired in config and condition on PredicateValidator but didn’t forward model.parameters. Would it make sense to add parameters=model.parameters or {} to the constructor to keep it consistent with other components? wdyt?

airbyte_cdk/sources/declarative/validators/dpath_validator.py (3)

26-27: Consider making the new fields more explicit about their optionality?

The new config and condition fields look good for supporting conditional validation. However, I'm wondering if these should be typed as optional or have default values, since conditions might not always be needed? What do you think about making this more explicit - wdyt?

-    config: Config
-    condition: str
+    config: Config
+    condition: str = ""

Or alternatively with Optional typing if conditions are truly optional?


48-50: Consider adding error handling for condition evaluation?

The conditional logic looks good and implements the feature as intended! However, I'm wondering if we should add some safety around the condition evaluation? What happens if self._interpolated_condition.eval(self.config) throws an exception or if self.config is None?

Maybe something like this to be more defensive - wdyt?

-        if self.condition and not self._interpolated_condition.eval(self.config):
-            return
+        if self.condition:
+            try:
+                if not self._interpolated_condition.eval(self.config):
+                    return
+            except Exception as e:
+                raise ValueError(f"Error evaluating validator condition '{self.condition}': {e}")

41-47: Update docstring to mention conditional behavior?

The docstring for the validate method doesn't mention the new conditional validation behavior. Would it be helpful to update it to reflect that validation can be skipped based on conditions - wdyt?

     def validate(self, input_data: dict[str, Any]) -> None:
         """
-        Extracts the value at the specified path and applies the validation strategy.
+        Extracts the value at the specified path and applies the validation strategy.
+        Validation is skipped if the condition evaluates to false.
 
         :param input_data: Dictionary containing the data to validate
         :raises ValueError: If the path doesn't exist or validation fails
         """
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f398ab6 and a2f4508.

📒 Files selected for processing (7)
  • airbyte_cdk/sources/declarative/declarative_component_schema.yaml (2 hunks)
  • airbyte_cdk/sources/declarative/models/declarative_component_schema.py (3 hunks)
  • airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py (1 hunks)
  • airbyte_cdk/sources/declarative/validators/dpath_validator.py (3 hunks)
  • airbyte_cdk/sources/declarative/validators/predicate_validator.py (2 hunks)
  • unit_tests/sources/declarative/validators/test_dpath_validator.py (7 hunks)
  • unit_tests/sources/declarative/validators/test_predicate_validator.py (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
unit_tests/sources/declarative/validators/test_predicate_validator.py (2)
airbyte_cdk/sources/declarative/models/declarative_component_schema.py (1)
  • PredicateValidator (2024-2050)
unit_tests/sources/declarative/validators/test_dpath_validator.py (3)
  • validate (18-22)
  • test_given_condition_is_false_when_validate_then_validate_is_not_called (126-137)
  • MockValidationStrategy (11-22)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Check: 'source-amplitude' (skip=false)
  • GitHub Check: Check: 'source-pokeapi' (skip=false)
  • GitHub Check: Check: 'source-hardcoded-records' (skip=false)
  • GitHub Check: Check: 'source-shopify' (skip=false)
  • GitHub Check: SDM Docker Image Build
  • GitHub Check: Pytest (All, Python 3.10, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.11, Ubuntu)
  • GitHub Check: Pytest (Fast)
🔇 Additional comments (15)
unit_tests/sources/declarative/validators/test_predicate_validator.py (2)

1-2: Copyright header update looks good!

Nice to see the copyright year updated to 2025.


29-29: Good approach to maintain backward compatibility.

Adding the new config={} and condition="" parameters with sensible defaults ensures existing behavior is preserved. This is a clean way to introduce the conditional validation feature, wdyt?

unit_tests/sources/declarative/validators/test_dpath_validator.py (3)

1-2: Copyright header update is consistent.

Good to see the year updated consistently across test files.


28-33: Consistent parameter addition maintains backward compatibility.

The approach of adding config={} and condition="" with empty defaults is applied consistently across all existing tests. This ensures that the new conditional validation feature doesn't break existing functionality.


126-137: Well-implemented conditional validation test!

This test effectively verifies the conditional validation behavior and mirrors the approach used in the PredicateValidator tests, which shows good consistency across the codebase. The test logic is sound - the condition {{ not config.get('test') }} correctly evaluates to False with the provided config, ensuring validation is skipped.

The test data structure with nested objects ({"user": {"profile": {"email": "test@example.com"}}}) also properly exercises the DpathValidator's path traversal functionality, wdyt?

airbyte_cdk/sources/declarative/declarative_component_schema.yaml (2)

4279-4288: Looks great!
The new condition field for DpathValidator is correctly declared with a default empty string, proper interpolation context, and clear examples. wdyt?


4323-4332: LGTM!
The condition property for PredicateValidator mirrors the DpathValidator addition and maintains consistency in defaults, context, and examples. wdyt?

airbyte_cdk/sources/declarative/validators/predicate_validator.py (1)

8-10: LGTM on the import additions!

The new imports for InterpolatedBoolean and Config are necessary for the conditional validation functionality and are appropriately placed.

airbyte_cdk/sources/declarative/models/declarative_component_schema.py (2)

2013-2021: Add optional condition to DpathValidator looks good
The new condition field aligns perfectly with the schema changes and enables skipping validation based on a template expression. wdyt?


2042-2050: Add optional condition to PredicateValidator looks good
This mirrors the DpathValidator update and provides the same conditional flexibility for predicate-based validation. wdyt?

airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py (3)

2489-2493: create_types_map: new condition default looks good

You’ve introduced condition with a fallback to "True" so that mappings are applied by default. This aligns with TypesMap semantics—looks solid.


2997-2999: create_record_filter: conditional filtering enabled

The addition of condition=model.condition or "" along with passing config and parameters correctly enables skipping filters when not needed. Implementation matches the pattern used elsewhere.


3096-3098: create_remove_fields: conditional removal looks correct

condition=model.condition or "" has been added to RemoveFields, enabling optional field removal. This change is consistent with ConfigRemoveFields and other transformations.

airbyte_cdk/sources/declarative/validators/dpath_validator.py (2)

10-10: LGTM on the new imports!

The imports for InterpolatedBoolean and Config are correctly added and align with the new functionality.

Also applies to: 14-14


30-30: Question about the interpolation pattern consistency?

I notice that the InterpolatedBoolean is created with empty parameters {}, but later in the validate method it's evaluated with self.config. This seems different from the field path pattern where both creation and evaluation use empty contexts.

Is this intentional because conditions need access to config during evaluation? Just want to make sure this follows the expected pattern for this codebase - wdyt?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
unit_tests/sources/declarative/spec/test_spec.py (1)

212-276: 🛠️ Refactor suggestion

Consider adding tests for the new conditional validation feature

While these changes correctly adapt the existing tests to the new API, it looks like we're missing test coverage for the actual conditional validation functionality. Would you like me to help generate some test cases that verify validators are skipped/executed based on different condition values? This would ensure the new feature works as expected, wdyt?

🧹 Nitpick comments (2)
unit_tests/sources/declarative/spec/test_spec.py (2)

237-239: Quick question about the empty condition behavior

The addition of config=input_config and condition="" parameters aligns with the new conditional validation feature. However, I'm curious about the empty condition string - does this mean the validation will always run, or does it get skipped? Would it be clearer to use a more explicit value like condition="true" or document the expected behavior, wdyt?


269-271: Same question about condition behavior

Same observation as the previous test - the empty condition="" works, but would benefit from clarification on whether this means "always validate" or "never validate", wdyt?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2f4508 and 31900fd.

📒 Files selected for processing (1)
  • unit_tests/sources/declarative/spec/test_spec.py (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: Check: 'source-pokeapi' (skip=false)
  • GitHub Check: Check: 'source-shopify' (skip=false)
  • GitHub Check: Check: 'source-amplitude' (skip=false)
  • GitHub Check: Check: 'source-hardcoded-records' (skip=false)
  • GitHub Check: SDM Docker Image Build
  • GitHub Check: Pytest (All, Python 3.10, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.11, Ubuntu)
  • GitHub Check: Pytest (Fast)
  • GitHub Check: Analyze (python)
🔇 Additional comments (2)
unit_tests/sources/declarative/spec/test_spec.py (2)

213-213: Good initialization timing!

Moving the input_config initialization before the component_spec creation makes sense since it's now needed for the DpathValidator constructor.


246-246: Consistent initialization pattern

Good consistency with the first test function - initializing input_config early for use in the validator constructor.

@pnilan
Copy link
Contributor Author

pnilan commented Jun 6, 2025

Closing in favor of simply skipping validation if returned values are falsy: #590

@pnilan pnilan closed this Jun 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant