New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
accept None as value for all optional fields #1307
accept None as value for all optional fields #1307
Conversation
25cceab
to
3eb1614
Compare
Codecov Report
@@ Coverage Diff @@
## master #1307 +/- ##
===========================================
+ Coverage 99.89% 100.00% +0.10%
===========================================
Files 21 21
Lines 3708 3737 +29
Branches 731 739 +8
===========================================
+ Hits 3704 3737 +33
+ Misses 2 0 -2
+ Partials 2 0 -2
Continue to review full report at Codecov.
|
pydantic/fields.py
Outdated
@@ -705,6 +705,10 @@ def _apply_validators( | |||
) -> 'ValidateReturn': | |||
for validator in validators: | |||
try: | |||
# optional field set to None should be valid unless a custom validator handles this case | |||
if validator.__name__ in DEFAULT_VALIDATORS_NAMES and v is None and not self.required: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really satisfied with the check on name. But the validators are wrapped so no easy way to directly check that. I'm open to proposals
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid I'm not happy with this. This is the most performance critical part of the whole of pydantic.
I think we can fix this bug without modifying this funciton.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead modify ConstrainedList
to something like
# This types superclass should be List[T], but cython chokes on that...
class ConstrainedList(list): # type: ignore
# Needed for pydantic to detect that this is a list
__origin__ = list
__args__: List[Type[T]] # type: ignore
min_items: Optional[int] = None
max_items: Optional[int] = None
item_type: Type[T] # type: ignore
@classmethod
def __get_validators__(cls) -> 'CallableGenerator':
yield cls.list_length_validator
@classmethod
def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
@classmethod
def list_length_validator(cls, v: 'List[T]', field) -> Optional['List[T]']:
if v is None and not field.required:
return None
v = list_validator(v)
v_len = len(v)
if cls.min_items is not None and v_len < cls.min_items:
raise errors.ListMinLengthError(limit_value=cls.min_items)
if cls.max_items is not None and v_len > cls.max_items:
raise errors.ListMaxLengthError(limit_value=cls.max_items)
return v
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid I'm not happy with this. This is the most performance critical part of the whole of pydantic.
I think we can fix this bug without modifying this funciton.
I definitely understand ! I was not satisfied either. But I wanted to tackle the whole problem instead of just the ConstrainList one. As a matter of fact it seems it's not needed! Sorry for the overkill
changes/1307-prettywood.md
Outdated
@@ -0,0 +1 @@ | |||
fix : value None should be valid for all optional fields by default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix : value None should be valid for all optional fields by default | |
Allow `None` as input to all optional fields |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks ! Just added list fields
as the fix is scoped to ConstrainedList
pydantic/fields.py
Outdated
@@ -705,6 +705,10 @@ def _apply_validators( | |||
) -> 'ValidateReturn': | |||
for validator in validators: | |||
try: | |||
# optional field set to None should be valid unless a custom validator handles this case | |||
if validator.__name__ in DEFAULT_VALIDATORS_NAMES and v is None and not self.required: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid I'm not happy with this. This is the most performance critical part of the whole of pydantic.
I think we can fix this bug without modifying this funciton.
pydantic/fields.py
Outdated
@@ -705,6 +705,10 @@ def _apply_validators( | |||
) -> 'ValidateReturn': | |||
for validator in validators: | |||
try: | |||
# optional field set to None should be valid unless a custom validator handles this case | |||
if validator.__name__ in DEFAULT_VALIDATORS_NAMES and v is None and not self.required: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead modify ConstrainedList
to something like
# This types superclass should be List[T], but cython chokes on that...
class ConstrainedList(list): # type: ignore
# Needed for pydantic to detect that this is a list
__origin__ = list
__args__: List[Type[T]] # type: ignore
min_items: Optional[int] = None
max_items: Optional[int] = None
item_type: Type[T] # type: ignore
@classmethod
def __get_validators__(cls) -> 'CallableGenerator':
yield cls.list_length_validator
@classmethod
def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items)
@classmethod
def list_length_validator(cls, v: 'List[T]', field) -> Optional['List[T]']:
if v is None and not field.required:
return None
v = list_validator(v)
v_len = len(v)
if cls.min_items is not None and v_len < cls.min_items:
raise errors.ListMinLengthError(limit_value=cls.min_items)
if cls.max_items is not None and v_len > cls.max_items:
raise errors.ListMaxLengthError(limit_value=cls.max_items)
return v
tests/test_main.py
Outdated
@@ -1025,3 +1026,21 @@ class FunctionModel(BaseModel): | |||
|
|||
m = FunctionModel() | |||
assert m.uid is uuid4 | |||
|
|||
|
|||
@pytest.mark.skipif(sys.version_info < (3, 7), reason='field constraints are set but not enforced with 3.6 and above') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
surely this can be removed then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a matter of fact it seems it can't :/
The problem lies here
def go(type_: Any) -> Type[Any]:
if is_literal_type(annotation) or isinstance(type_, ForwardRef) or lenient_issubclass(type_, ConstrainedList):
return type_
origin = getattr(type_, '__origin__', None)
where typing.List.__origin__
is list
on python 3.7+ but None
on python 3.6
Not sure if we want to have a get_origin
method to handle python 3.6 or if there is a backport of any kind
3eb1614
to
2fa1ed2
Compare
pydantic/types.py
Outdated
yield cls.list_length_validator | ||
|
||
@classmethod | ||
def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: | ||
update_not_none(field_schema, minItems=cls.min_items, maxItems=cls.max_items) | ||
|
||
@classmethod | ||
def list_length_validator(cls, v: 'List[T]') -> 'List[T]': | ||
def list_length_validator(cls, v: 'Optional[List[T]]', field: 'ModelField') -> 'Optional[List[T]]': # type: ignore[name-defined] # noqa: E501,F821 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samuelcolvin Thank you for the review ! Just a question on the mypy policy as I could
- not set the type of
field
and just add# type: ignore
- same but explaining the ignored error but then add a
noqa
for length - do some refactoring to avoid circular imports (overkill probably)
- let it like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you just needed to import ModelField
, imports inside if TYPE_CHECKING:
don't cause circular import problems.
Change Summary
All optional list fields with
None
as value should be valid except if there is a custom validationto handle this case.
ℹ️ Test skipped on python 3.6
Related issue number
closes #1295
Checklist
changes/<pull request or issue id>-<github username>.md
file added describing change(see changes/README.md for details)