Skip to content

✨ Add model_fields_optional to convert inherited fields to Optional#1830

Open
Krishnachaitanyakc wants to merge 3 commits intofastapi:mainfrom
Krishnachaitanyakc:feat/model-fields-optional
Open

✨ Add model_fields_optional to convert inherited fields to Optional#1830
Krishnachaitanyakc wants to merge 3 commits intofastapi:mainfrom
Krishnachaitanyakc:feat/model-fields-optional

Conversation

@Krishnachaitanyakc
Copy link

Summary

Adds a new model_fields_optional configuration option that makes all inherited fields Optional with a default of None when set to "all". This enables the common pattern of creating "update" models where all fields are optional, reducing boilerplate when building CRUD APIs with SQLModel.

Before (manual boilerplate):

class HeroBase(SQLModel):
    name: str
    secret_name: str
    age: Optional[int] = None

# Every field must be manually redeclared as Optional
class HeroUpdate(SQLModel):
    name: Optional[str] = None
    secret_name: Optional[str] = None
    age: Optional[int] = None

After (with model_fields_optional):

class HeroBase(SQLModel):
    name: str
    secret_name: str
    age: Optional[int] = None

class HeroUpdate(HeroBase, model_fields_optional="all"):
    pass

Key behaviors

  • All inherited fields become Optional[X] = None
  • Fields already Optional keep their existing defaults
  • Field constraints (e.g. min_length, ge) are preserved and still validated for non-None values
  • Fields explicitly redefined in the child class are not overridden
  • Works via class kwarg (model_fields_optional="all") or via model_config
  • model_dump(exclude_unset=True) correctly returns only the fields that were explicitly set
  • Compatible with table=True base models (the update model itself is not a table)

Changes

  • sqlmodel/_compat.py: Added model_fields_optional to SQLModelConfig
  • sqlmodel/main.py: Added _is_optional_annotation() helper and processing logic in SQLModelMetaclass.__new__
  • tests/test_model_fields_optional.py: 11 test cases covering basic usage, partial data, exclude_unset, field override, constraint preservation, multiple inheritance, model_config style, table base compatibility, already-optional fields, model_validate, and JSON schema

Fixes #64

Test plan

  • All 11 new test cases pass
  • All 96 existing tests pass (no regressions)
  • Tested with Field(min_length=...), Field(ge=...) constraints
  • Tested with multi-level inheritance
  • Tested both kwarg and model_config configuration styles

Krishnachaitanyakc and others added 3 commits March 24, 2026 23:18
Add support for `model_fields_optional="all"` configuration that makes
all inherited fields Optional with a default of None. This enables the
common pattern of creating "update" models where all fields are optional,
reducing boilerplate when building CRUD APIs.

Fixes fastapi#64
…nting

- Use `copy.copy()` fallback for FieldInfo when `_copy()` is unavailable
  (added in pydantic 2.12.0, but project supports >=2.11.0)
- Replace `Union[X, None]` with `X | None` to satisfy ruff UP007/UP045
- Apply ruff formatting fixes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert all fields to optional

3 participants