From 91969a959fb110463691f558cb76706ca0cf7dd0 Mon Sep 17 00:00:00 2001 From: Christophe Lecointe Date: Thu, 19 Aug 2021 16:44:27 +0200 Subject: [PATCH 1/5] :sparkles: Add a config field to make all model fields optional --- pydantic/config.py | 5 ++++- tests/test_create_model.py | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pydantic/config.py b/pydantic/config.py index acd20da146..0ba9c0d3bf 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 + all_optionals: bool = False # 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 cls.all_optionals: + 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..a8d82bf692 100644 --- a/tests/test_create_model.py +++ b/tests/test_create_model.py @@ -205,3 +205,26 @@ 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: + all_optionals = True + + 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): + a: str + + class Config: + all_optionals = True + + with pytest.raises(ValidationError): + TestBase() + assert Test() From 51c219aa0a6cb70137223c0102666191ed948ea5 Mon Sep 17 00:00:00 2001 From: Christophe Lecointe Date: Thu, 19 Aug 2021 17:19:03 +0200 Subject: [PATCH 2/5] :art: Changes the keyword to total This is done to match TypedDict interface, see https://www.python.org/dev/peps/pep-0589/#totality --- pydantic/config.py | 4 ++-- tests/test_create_model.py | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pydantic/config.py b/pydantic/config.py index 0ba9c0d3bf..a54f38e085 100644 --- a/pydantic/config.py +++ b/pydantic/config.py @@ -62,7 +62,7 @@ class BaseConfig: json_dumps: Callable[..., str] = json.dumps json_encoders: Dict[Type[Any], AnyCallable] = {} underscore_attrs_are_private: bool = False - all_optionals: bool = False + total: bool = True # Whether or not inherited models as fields should be reconstructed as base model copy_on_model_validation: bool = True @@ -97,7 +97,7 @@ def prepare_field(cls, field: 'ModelField') -> None: """ Optional hook to check or modify fields during model creation. """ - if cls.all_optionals: + if not cls.total: field.type_ = Optional[field.type_] field.required = False diff --git a/tests/test_create_model.py b/tests/test_create_model.py index a8d82bf692..d1fb9d35f8 100644 --- a/tests/test_create_model.py +++ b/tests/test_create_model.py @@ -209,7 +209,7 @@ class Config: def test_config_all_optional_fields_model(): class Config: - all_optionals = True + total = False m1 = create_model('M1', __config__=Config, a=(str, ...)) assert m1() @@ -219,12 +219,9 @@ def test_config_all_optional_fields_model_inheritance(): class TestBase(BaseModel): b: str - class Test(TestBase): + class Test(TestBase, total=False): a: str - class Config: - all_optionals = True - with pytest.raises(ValidationError): TestBase() assert Test() From 723e7c918059c61b1ad80f0a5d1f185876df3209 Mon Sep 17 00:00:00 2001 From: Christophe Lecointe Date: Thu, 19 Aug 2021 17:22:12 +0200 Subject: [PATCH 3/5] :memo: Add release note in changes --- changes/3121-christophelec.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/3121-christophelec.md diff --git a/changes/3121-christophelec.md b/changes/3121-christophelec.md new file mode 100644 index 0000000000..62f47ea383 --- /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 From 7aeb08d293bf2ed6d36e304c2d4932591dfa272e Mon Sep 17 00:00:00 2001 From: Christophe Lecointe Date: Thu, 19 Aug 2021 17:40:03 +0200 Subject: [PATCH 4/5] :memo: Add doc about the feature --- docs/usage/model_config.md | 4 ++++ 1 file changed, 4 insertions(+) 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` From 3563b54b9acafa97339d9113c0c45a28181385a2 Mon Sep 17 00:00:00 2001 From: Christophe Lecointe Date: Sat, 4 Sep 2021 22:27:54 +0200 Subject: [PATCH 5/5] Fix typo in documentation Co-authored-by: Eric Jolibois --- changes/3121-christophelec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/3121-christophelec.md b/changes/3121-christophelec.md index 62f47ea383..dbcb4df610 100644 --- a/changes/3121-christophelec.md +++ b/changes/3121-christophelec.md @@ -1 +1 @@ -Add a `total` configuration field to create models with all fields Optional \ No newline at end of file +Add a `total` configuration field to create models with all fields `Optional` \ No newline at end of file