Skip to content

NAS-135965 / 25.10 / Move apps config schema handling from middleware classes to pydantic #16555

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

Merged
merged 73 commits into from
Jul 4, 2025

Conversation

sonicaj
Copy link
Member

@sonicaj sonicaj commented May 25, 2025

No description provided.

@sonicaj sonicaj self-assigned this May 25, 2025
@sonicaj sonicaj added the WIP label May 25, 2025
@bugclerk
Copy link
Contributor

@bugclerk bugclerk changed the title Move apps config schema handling from middleware classes to pydantic NAS-135965 / 25.10 / Move apps config schema handling from middleware classes to pydantic May 25, 2025
@sonicaj sonicaj force-pushed the NAS-135965 branch 2 times, most recently from c13b987 to d4f2332 Compare June 4, 2025 12:20
@sonicaj sonicaj force-pushed the NAS-135965 branch 4 times, most recently from 9e2055d to 0d94ea8 Compare June 20, 2025 21:52
@sonicaj sonicaj force-pushed the NAS-135965 branch 5 times, most recently from 13d2f6d to 6e667d9 Compare June 29, 2025 11:33
sonicaj added 11 commits July 2, 2025 18:43
- Add create_length_validated_hostpath function to apply min/max length validation before path conversion
- Update schema construction to use length-validated hostpath when constraints are specified
- Add comprehensive tests for path and hostpath types with length constraints
- Fix test expectations to properly validate empty strings against length constraints
- Update test assertions for path type to expect 'items' in error messages
- Fix code quality issues (trailing whitespace, blank lines)

This ensures hostpath fields properly validate string length before converting to Path objects,
preventing TypeError when length constraints are applied.
This reverts commit 2217062.

The original behavior where non-required lists and dicts automatically get
empty defaults ([] and {}) was the intended behavior for backward compatibility
with existing app configurations.

This commit restores:
- default_factory assignment for nested dicts in process_schema_field
- default_factory set to list/dict for non-required lists/dicts in create_field_info_from_schema
- Test expectations that non-required lists/dicts get empty defaults

The behavior ensures that:
- Non-required lists without explicit defaults get []
- Non-required dicts without explicit defaults get {}
- Nested dicts with default values on all subfields get instantiated with those defaults
Previously, validation errors for nested fields were prefixed with 'values.'
(e.g., 'values.database.host' instead of just 'database.host'). This made
error messages less clear and inconsistent with field naming.

Changes:
- Modified construct_schema() to use verrors.extend() instead of
  verrors.add_child('values', e) to avoid adding the prefix
- Added comprehensive tests for nested validation error paths
- Updated existing tests to expect clean paths without prefix

This improves error reporting clarity for deeply nested app configurations.
When a parent dict field is hidden by a show_if condition, its required
nested fields should not trigger validation errors. This fix propagates
the visibility state from parent dict fields to their children during
Pydantic model generation.

The implementation:
- Adds parent_hidden parameter to generate_pydantic_model to track visibility
- Evaluates show_if conditions for dict fields and propagates hidden state
- Marks all fields as NotRequired when their parent dict is hidden
- Preserves existing show_if behavior for non-nested cases

This resolves the issue where required fields inside hidden dict
structures (e.g., storage.data.host_path_config.path) would still
cause validation errors even when their parent was hidden by show_if.
- Fixed test_normalize_and_validate by properly mocking validate_values
  to return a mutable dictionary instead of a Dict schema object
- Removed unused pytest import from test_nested_show_if.py
- Fixed whitespace issues (W293, W291, W292) in both modified files
- All tests now pass successfully
When evaluating show_if conditions, the code now properly builds an
evaluation context that includes default values from sibling fields.
This fixes the issue where fields with show_if conditions inside
hidden parent dicts would still cause validation errors.

The fix addresses the actual-budget app installation issue where
the path field was required even though its parent host_path_config
was hidden due to type='ix_volume' (default).

Changes:
- Build eval_context with both provided values and defaults
- Use eval_context for show_if evaluation instead of raw new_values
- Added test case for nested show_if with field-level conditions
- Fixed flake8 whitespace issues
When creating apps with empty values {}, the schema validation was using
exclude_unset=True which prevented defaults from being populated. This
caused template rendering to fail when accessing fields like run_as.user.

Changed construct_schema to use exclude_unset=False so defaults are properly
populated for fields not provided by the user.

- Updated validate_model call to exclude_unset=False in construct_schema
- Added test to verify defaults are populated with empty input
- Updated test_normalize_and_validate to use actual validation logic
- Fix remove_not_required() to handle string representations of NotRequired objects
  that appear when using dynamic Pydantic models
- Add comprehensive test coverage for default values and NotRequired handling
- Ensure empty dicts/lists are populated for non-required fields without defaults
- Allow explicitly provided values to override show_if conditions
- Update test expectations to match correct behavior where:
  - Non-required dict fields get empty dict defaults
  - Non-required list fields get empty list defaults
  - Explicitly provided values are included even when show_if=false
  - Fields with defaults are populated when not provided by user

This ensures consistent behavior for app schema validation and proper
handling of optional fields across all schema types.
@sonicaj sonicaj marked this pull request as ready for review July 2, 2025 13:43
'string', 'int', 'boolean', 'path'
) and old_values is not NOT_PROVIDED:
# If we have a value for this field in old_values, we should not allow it to be changed
field_type = Literal[old_values]
Copy link
Contributor

Choose a reason for hiding this comment

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

This path should be tested. Passing a dictionary to Literal might not behave as expected.

Copy link
Member Author

Choose a reason for hiding this comment

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

It won't be a dictionary and yes you are right, it won't behave as expected with a dict. It will be a simple types based off the and condition we have

@sonicaj sonicaj requested a review from creatorcary July 2, 2025 17:27
@sonicaj sonicaj removed the WIP label Jul 2, 2025
@sonicaj sonicaj requested a review from a team July 2, 2025 17:27
@creatorcary
Copy link
Contributor

Thanks!

@sonicaj sonicaj merged commit fd41ff2 into master Jul 4, 2025
2 checks passed
@sonicaj sonicaj deleted the NAS-135965 branch July 4, 2025 18:13
@bugclerk
Copy link
Contributor

bugclerk commented Jul 4, 2025

This PR has been merged and conversations have been locked.
If you would like to discuss more about this issue please use our forums or raise a Jira ticket.

@truenas truenas locked as resolved and limited conversation to collaborators Jul 4, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants