Skip to content

Make Field constructors keyword-only #7632

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

Merged
merged 1 commit into from
Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ class Field:
default_empty_html = empty
initial = None

def __init__(self, read_only=False, write_only=False,
def __init__(self, *, read_only=False, write_only=False,
required=None, default=empty, initial=empty, source=None,
label=None, help_text=None, style=None,
error_messages=None, validators=None, allow_null=False):
Expand Down Expand Up @@ -1161,14 +1161,14 @@ class DateTimeField(Field):
}
datetime_parser = datetime.datetime.strptime

def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs):
def __init__(self, format=empty, input_formats=None, default_timezone=None, **kwargs):
if format is not empty:
self.format = format
if input_formats is not None:
self.input_formats = input_formats
if default_timezone is not None:
self.timezone = default_timezone
super().__init__(*args, **kwargs)
super().__init__(**kwargs)

def enforce_timezone(self, value):
"""
Expand Down Expand Up @@ -1247,12 +1247,12 @@ class DateField(Field):
}
datetime_parser = datetime.datetime.strptime

def __init__(self, format=empty, input_formats=None, *args, **kwargs):
def __init__(self, format=empty, input_formats=None, **kwargs):
if format is not empty:
self.format = format
if input_formats is not None:
self.input_formats = input_formats
super().__init__(*args, **kwargs)
super().__init__(**kwargs)

def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
Expand Down Expand Up @@ -1313,12 +1313,12 @@ class TimeField(Field):
}
datetime_parser = datetime.datetime.strptime

def __init__(self, format=empty, input_formats=None, *args, **kwargs):
def __init__(self, format=empty, input_formats=None, **kwargs):
if format is not empty:
self.format = format
if input_formats is not None:
self.input_formats = input_formats
super().__init__(*args, **kwargs)
super().__init__(**kwargs)

def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS)
Expand Down Expand Up @@ -1468,9 +1468,9 @@ class MultipleChoiceField(ChoiceField):
}
default_empty_html = []

def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.allow_empty = kwargs.pop('allow_empty', True)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)

def get_value(self, dictionary):
if self.field_name not in dictionary:
Expand Down Expand Up @@ -1527,12 +1527,12 @@ class FileField(Field):
'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
}

def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.max_length = kwargs.pop('max_length', None)
self.allow_empty_file = kwargs.pop('allow_empty_file', False)
if 'use_url' in kwargs:
self.use_url = kwargs.pop('use_url')
super().__init__(*args, **kwargs)
super().__init__(**kwargs)

def to_internal_value(self, data):
try:
Expand Down Expand Up @@ -1576,9 +1576,9 @@ class ImageField(FileField):
),
}

def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)

def to_internal_value(self, data):
# Image validation is a bit grungy, so we'll just outright
Expand All @@ -1593,8 +1593,8 @@ def to_internal_value(self, data):
# Composite field types...

class _UnvalidatedField(Field):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.allow_blank = True
self.allow_null = True

Expand All @@ -1615,7 +1615,7 @@ class ListField(Field):
'max_length': _('Ensure this field has no more than {max_length} elements.')
}

def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True)
self.max_length = kwargs.pop('max_length', None)
Expand All @@ -1627,7 +1627,7 @@ def __init__(self, *args, **kwargs):
"Remove `source=` from the field declaration."
)

super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.child.bind(field_name='', parent=self)
if self.max_length is not None:
message = lazy_format(self.error_messages['max_length'], max_length=self.max_length)
Expand Down Expand Up @@ -1692,7 +1692,7 @@ class DictField(Field):
'empty': _('This dictionary may not be empty.'),
}

def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True)

Expand All @@ -1702,7 +1702,7 @@ def __init__(self, *args, **kwargs):
"Remove `source=` from the field declaration."
)

super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.child.bind(field_name='', parent=self)

def get_value(self, dictionary):
Expand Down Expand Up @@ -1751,8 +1751,8 @@ def run_child_validation(self, data):
class HStoreField(DictField):
child = CharField(allow_blank=True, allow_null=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
assert isinstance(self.child, CharField), (
"The `child` argument must be an instance of `CharField`, "
"as the hstore extension stores values as strings."
Expand All @@ -1767,11 +1767,11 @@ class JSONField(Field):
# Workaround for isinstance calls when importing the field isn't possible
_is_jsonfield = True

def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
self.binary = kwargs.pop('binary', False)
self.encoder = kwargs.pop('encoder', None)
self.decoder = kwargs.pop('decoder', None)
super().__init__(*args, **kwargs)
super().__init__(**kwargs)

def get_value(self, dictionary):
if html.is_html_input(dictionary) and self.field_name in dictionary:
Expand Down
5 changes: 5 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,11 @@ def test_collection_types_are_invalid_input(self):
field.to_internal_value(input_value)
assert exc_info.value.detail == ['Expected a list of items but got type "dict".']

def test_constructor_misuse_raises(self):
# Test that `ListField` can only be instantiated with keyword arguments
with pytest.raises(TypeError):
serializers.ListField(serializers.CharField())


class TestNestedListField(FieldValues):
"""
Expand Down