Skip to content

Commit 5e1177b

Browse files
peter-doggartPeter Doggart
andauthored
A fix to allow nullable fields.nested for input validation. (#640)
Co-authored-by: Peter Doggart <peter.doggart@pulseai.io>
1 parent 0f4f534 commit 5e1177b

File tree

3 files changed

+30
-1
lines changed

3 files changed

+30
-1
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ Bug Fixes
3535
::
3636

3737
* Add python version requirement on setup.py (#586) [jason-the-j]
38+
* Fix Nested field schema generation for nullable fields. (#638) [peter-doggart]
3839
* Fix reference resolution for definitions in schema. (#553) [peter-doggart]
3940

40-
4141
.. _section-1.3.0:
4242
1.3.0
4343
-----

flask_restx/fields.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ class Raw(object):
134134
:param bool readonly: Is the field read only ? (for documentation purpose)
135135
:param example: An optional data example (for documentation purpose)
136136
:param callable mask: An optional mask function to be applied to output
137+
:param bool nullable: Whether the field accepts null values in input
138+
validation. When True, the generated JSON Schema will allow null
139+
values for this field during request payload validation.
137140
"""
138141

139142
#: The JSON/Swagger schema type
@@ -153,6 +156,7 @@ def __init__(
153156
readonly=None,
154157
example=None,
155158
mask=None,
159+
nullable=None,
156160
**kwargs
157161
):
158162
self.attribute = attribute
@@ -163,6 +167,7 @@ def __init__(
163167
self.readonly = readonly
164168
self.example = example if example is not None else self.__schema_example__
165169
self.mask = mask
170+
self.nullable = nullable
166171

167172
def format(self, value):
168173
"""
@@ -284,6 +289,19 @@ def schema(self):
284289
schema["allOf"] = allOf
285290
else:
286291
schema["$ref"] = ref
292+
293+
# If nullable is True, wrap using anyOf to permit nulls for input validation
294+
if self.nullable:
295+
# Remove structural keys that conflict with anyOf composition
296+
for key in ("$ref", "allOf", "type", "items"):
297+
schema.pop(key, None)
298+
# Create anyOf with the original schema and null type
299+
anyOf = [{"$ref": ref}]
300+
if self.as_list:
301+
anyOf = [{"type": "array", "items": {"$ref": ref}}]
302+
anyOf.append({"type": "null"})
303+
schema["anyOf"] = anyOf
304+
287305
return schema
288306

289307
def clone(self, mask=None):

tests/test_fields.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,17 @@ def test_with_allow_null(self, api):
881881
assert field.allow_null
882882
assert field.__schema__ == {"$ref": "#/definitions/NestedModel"}
883883

884+
def test_with_nullable_schema(self, api):
885+
nested_fields = api.model("NestedModel", {"name": fields.String})
886+
field = fields.Nested(nested_fields, nullable=True)
887+
# Should allow null in schema via anyOf
888+
assert field.__schema__ == {
889+
"anyOf": [
890+
{"$ref": "#/definitions/NestedModel"},
891+
{"type": "null"},
892+
]
893+
}
894+
884895
def test_with_skip_none(self, api):
885896
nested_fields = api.model("NestedModel", {"name": fields.String})
886897
field = fields.Nested(nested_fields, skip_none=True)

0 commit comments

Comments
 (0)