Skip to content

Commit 4f62e5b

Browse files
authored
Merge pull request #43 from martinostvik/dev/override-json-schema-extra-field
Correctly set required and nullable json schema values
2 parents 32a4215 + 1dabdab commit 4f62e5b

File tree

3 files changed

+34
-5
lines changed

3 files changed

+34
-5
lines changed

pydantic_partial/_compat.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
if PYDANTIC_V1: # pragma: no cover
1515
from pydantic.fields import ModelField # type: ignore
1616

17-
NULLABLE_KWARGS = {"nullable": True}
17+
NULLABLE_KWARGS = {"nullable": True, "required": False}
1818

1919
class PydanticCompat: # type: ignore
2020
model_class: type[pydantic.BaseModel]
@@ -46,7 +46,7 @@ def copy_model_field_info(self, model_field: ModelField, **kwargs: Any) -> Field
4646
return copy_field_info(model_field.field_info, **kwargs)
4747

4848
elif PYDANTIC_V2: # pragma: no cover
49-
NULLABLE_KWARGS = {"json_schema_extra": {"nullable": True}}
49+
NULLABLE_KWARGS = {"json_schema_extra": {"nullable": True, "required": False}}
5050

5151
class PydanticCompat: # type: ignore
5252
model_class: type[pydantic.BaseModel]
@@ -65,7 +65,11 @@ def get_model_field_info_annotation(self, field_info: FieldInfo) -> Optional[typ
6565
return field_info.annotation
6666

6767
def is_model_field_info_required(self, field_info: FieldInfo) -> bool:
68-
return field_info.is_required() # type: ignore
68+
json_required = (
69+
field_info.json_schema_extra is not None
70+
and field_info.json_schema_extra.get("required", False)
71+
)
72+
return field_info.is_required() or json_required # type: ignore
6973

7074
def copy_model_field_info(self, field_info: FieldInfo, **kwargs: Any) -> FieldInfo:
7175
return copy_field_info(field_info, **kwargs)

pydantic_partial/partial.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def _partial_annotation_arg(field_name_: str, field_annotation: type) -> type:
116116
field_info,
117117
default=None, # Set default to None
118118
default_factory=None, # Remove default_factory if set
119-
**NULLABLE_KWARGS, # For API usage: set field as nullable
119+
**NULLABLE_KWARGS, # For API usage: set field as nullable and not required
120120
),
121121
)
122122
elif recursive or sub_fields_requested:

tests/test_partial_without_mixin.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import json
12
from typing import Union
23

34
import pydantic
5+
import pytest
46

57
from pydantic_partial import PartialModelMixin, create_partial_model
68
from pydantic_partial._compat import PYDANTIC_V1, PYDANTIC_V2
@@ -12,7 +14,11 @@ def _field_is_required(model: Union[type[pydantic.BaseModel], pydantic.BaseModel
1214
elif PYDANTIC_V2:
1315
def _field_is_required(model: Union[type[pydantic.BaseModel], pydantic.BaseModel], field_name: str) -> bool:
1416
"""Check if a field is required on a pydantic V2 model."""
15-
return model.model_fields[field_name].is_required()
17+
json_required = (
18+
model.model_fields[field_name].json_schema_extra is not None
19+
and model.model_fields[field_name].json_schema_extra.get("required", False)
20+
)
21+
return model.model_fields[field_name].is_required() or json_required
1622
else:
1723
raise DeprecationWarning("Pydantic has to be in version 1 or 2.")
1824

@@ -21,6 +27,10 @@ class Something(pydantic.BaseModel):
2127
name: str
2228
age: int
2329
already_optional: None = None
30+
if PYDANTIC_V1:
31+
already_required: int = pydantic.Field(default=1, required=True)
32+
if PYDANTIC_V2:
33+
already_required: int = pydantic.Field(default=1, json_schema_extra={"required": True})
2434

2535

2636
class SomethingWithMixin(PartialModelMixin, pydantic.BaseModel):
@@ -62,6 +72,21 @@ def test_partial_model_will_be_the_same_on_mixin():
6272

6373
assert SomethingWithMixinPartial1 is SomethingWithMixinPartial2
6474

75+
@pytest.mark.skipif(PYDANTIC_V2, reason="Pydantic V2 does not support json_schema_extra")
76+
def test_pydantic_v1partial_model_will_override_json_required():
77+
SomethingPartial = create_partial_model(Something)
78+
assert _field_is_required(SomethingPartial, "already_required") is False
79+
schema = json.loads(SomethingPartial.schema_json())
80+
assert schema["properties"]["already_required"]["nullable"] is True
81+
assert schema["properties"]["already_required"]["required"] is False
82+
83+
@pytest.mark.skipif(PYDANTIC_V1, reason="Pydantic V1 does not support json_schema_extra")
84+
def test_pydantic_v2_partial_model_will_override_json_required():
85+
SomethingPartial = create_partial_model(Something)
86+
assert _field_is_required(SomethingPartial, "already_required") is False
87+
schema = SomethingPartial.model_json_schema()
88+
assert schema["properties"]["already_required"]["nullable"] is True
89+
assert schema["properties"]["already_required"]["required"] is False
6590

6691
def test_partial_class_name_can_be_overridden():
6792
SomethingPartial = create_partial_model(Something, "name")

0 commit comments

Comments
 (0)