diff --git a/changes/3121-christophelec.md b/changes/3121-christophelec.md new file mode 100644 index 0000000000..dbcb4df610 --- /dev/null +++ b/changes/3121-christophelec.md @@ -0,0 +1 @@ +Add a `total` configuration field to create models with all fields `Optional` \ No newline at end of file diff --git a/docs/usage/model_config.md b/docs/usage/model_config.md index 411112df1a..67354e6449 100644 --- a/docs/usage/model_config.md +++ b/docs/usage/model_config.md @@ -115,6 +115,10 @@ not be included in the model schemas. **Note**: this means that attributes on th **`copy_on_model_validation`** : whether or not inherited models used as fields should be reconstructed (copied) on validation instead of being kept untouched (default: `True`) +**`total`** +: whether all fields must be present in the model. Setting this to `False` is equivalent to manually setting all fields as `Optional` with a `None` default (default: `True`) + + ## Change behaviour globally If you wish to change the behaviour of _pydantic_ globally, you can create your own custom `BaseModel` diff --git a/pydantic/config.py b/pydantic/config.py index acd20da146..a54f38e085 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -62,6 +62,7 @@ class BaseConfig: json_dumps: Callable[..., str] = json.dumps json_encoders: Dict[Type[Any], AnyCallable] = {} underscore_attrs_are_private: bool = False + total: bool = True # Whether or not inherited models as fields should be reconstructed as base model copy_on_model_validation: bool = True @@ -96,7 +97,9 @@ def prepare_field(cls, field: 'ModelField') -> None: """ Optional hook to check or modify fields during model creation. """ - pass + if not cls.total: + field.type_ = Optional[field.type_] + field.required = False def inherit_config(self_config: 'ConfigType', parent_config: 'ConfigType', **namespace: Any) -> 'ConfigType': diff --git a/tests/test_create_model.py b/tests/test_create_model.py index 0cf26418cc..d1fb9d35f8 100644 --- a/tests/test_create_model.py +++ b/tests/test_create_model.py @@ -205,3 +205,23 @@ class Config: m2 = create_model('M2', __config__=Config, a=(str, Field(...))) assert m2.schema()['properties'] == {'a': {'title': 'A', 'description': 'descr', 'type': 'string'}} + + +def test_config_all_optional_fields_model(): + class Config: + total = False + + m1 = create_model('M1', __config__=Config, a=(str, ...)) + assert m1() + + +def test_config_all_optional_fields_model_inheritance(): + class TestBase(BaseModel): + b: str + + class Test(TestBase, total=False): + a: str + + with pytest.raises(ValidationError): + TestBase() + assert Test()