Skip to content
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

Update Field stubs to include None as an input/output type #511

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
54 changes: 23 additions & 31 deletions rest_framework-stubs/fields.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ NOT_REQUIRED_DEFAULT: str
USE_READONLYFIELD: str
MISSING_ERROR_MESSAGE: str

_IN = TypeVar("_IN") # Instance Type
_VT = TypeVar("_VT") # Value Type
_DT = TypeVar("_DT") # Data Type
_RP = TypeVar("_RP") # Representation Type
_IN = TypeVar("_IN") # Instance Type

class SupportsToPython(Protocol):
def to_python(self, value: Any) -> Any: ...
Expand Down Expand Up @@ -127,30 +127,20 @@ class Field(Generic[_VT, _DT, _RP, _IN]):
def __deepcopy__(self, memo: Mapping[Any, Any]) -> Field: ...

class BooleanField(
Field[
bool,
str | bool | int,
bool,
Any,
]
):
TRUE_VALUES: set[str | bool | int]
FALSE_VALUES: set[str | bool | int | float]
NULL_VALUES: set[str | None]

class NullBooleanField(
Field[
bool | None,
str | bool | int | None,
bool,
bool | None,
Any,
]
):
TRUE_VALUES: set[str | bool | int]
FALSE_VALUES: set[str | bool | int | float]
NULL_VALUES: set[str | None]

class CharField(Field[str, str, str, Any]):
class NullBooleanField(BooleanField): ...
Copy link
Author

@Kangaroux Kangaroux Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like NullBooleanField was deprecated in v3.13 of DRF (and removed in v3.14)

Do you want me to remove it in this PR or make a separate one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate PR please.


class CharField(Field[str | None, str | None, str | None, Any]):
allow_blank: bool
trim_whitespace: bool
max_length: int | None
Expand Down Expand Up @@ -227,7 +217,7 @@ class SlugField(CharField):

class URLField(CharField): ...

class UUIDField(Field[uuid.UUID, uuid.UUID | str | int, str, Any]):
class UUIDField(Field[uuid.UUID | None, uuid.UUID | str | int | None, str | None, Any]):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the best way to go about this.

I suspect not all methods using these typevars can deal with None.

And many places that use these TypeVars already explicitly specify e.g. _VT | None. Seems a lot safer to just update the signatures of the to_representation() method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, IntegerField.to_representation() actually does not allow None... Not sure what to make of this.

    def to_representation(self, value):
        return int(value)

valid_formats: Sequence[str]
uuid_format: str
def __init__(
Expand Down Expand Up @@ -273,7 +263,7 @@ class IPAddressField(CharField):
min_length: int | None = ...,
) -> None: ...

class IntegerField(Field[int, float | int | str, int, Any]):
class IntegerField(Field[int | None, float | int | str | None, int | None, Any]):
MAX_STRING_LENGTH: int
re_decimal: Pattern
max_value: int | None
Expand All @@ -297,7 +287,7 @@ class IntegerField(Field[int, float | int | str, int, Any]):
allow_null: bool = ...,
) -> None: ...

class FloatField(Field[float, float | int | str, str, Any]):
class FloatField(Field[float | None, float | int | str | None, str | None, Any]):
MAX_STRING_LENGTH: int
re_decimal: Pattern
max_value: float | None
Expand All @@ -321,7 +311,7 @@ class FloatField(Field[float, float | int | str, str, Any]):
allow_null: bool = ...,
) -> None: ...

class DecimalField(Field[Decimal, int | float | str | Decimal, str, Any]):
class DecimalField(Field[Decimal | None, int | float | str | Decimal | None, str | None, Any]):
MAX_STRING_LENGTH: int
max_digits: int | None
decimal_places: int | None
Expand Down Expand Up @@ -357,7 +347,7 @@ class DecimalField(Field[Decimal, int | float | str | Decimal, str, Any]):
def validate_precision(self, value: Decimal) -> Decimal: ...
def quantize(self, value: Decimal) -> Decimal: ...

class DateTimeField(Field[datetime.datetime, datetime.datetime | str, str, Any]):
class DateTimeField(Field[datetime.datetime | None, datetime.datetime | str | None, str | None, Any]):
datetime_parser: Callable[[str, str], datetime.datetime]
format: str | None
input_formats: Sequence[str]
Expand All @@ -384,7 +374,7 @@ class DateTimeField(Field[datetime.datetime, datetime.datetime | str, str, Any])
def enforce_timezone(self, value: datetime.datetime) -> datetime.datetime: ...
def default_timezone(self) -> str | None: ...

class DateField(Field[datetime.date, datetime.date | str, str, Any]):
class DateField(Field[datetime.date | None, datetime.date | str | None, str | None, Any]):
datetime_parser: Callable[[str, str], datetime.datetime]
format: str | None
input_formats: Sequence[str]
Expand All @@ -407,7 +397,7 @@ class DateField(Field[datetime.date, datetime.date | str, str, Any]):
allow_null: bool = ...,
) -> None: ...

class TimeField(Field[datetime.time, datetime.time | str, str, Any]):
class TimeField(Field[datetime.time | None, datetime.time | str | None, str | None, Any]):
datetime_parser: Callable[[str, str], datetime.datetime]
format: str | None
input_formats: Sequence[str]
Expand All @@ -430,7 +420,7 @@ class TimeField(Field[datetime.time, datetime.time | str, str, Any]):
allow_null: bool = ...,
) -> None: ...

class DurationField(Field[datetime.timedelta, datetime.timedelta | str, str, Any]):
class DurationField(Field[datetime.timedelta | None, datetime.timedelta | str | None, str | None, Any]):
max_value: datetime.timedelta | None
min_value: datetime.timedelta | None
def __init__(
Expand All @@ -452,7 +442,7 @@ class DurationField(Field[datetime.timedelta, datetime.timedelta | str, str, Any
allow_null: bool = ...,
) -> None: ...

class ChoiceField(Field[str, str | int | tuple[str | int, str | int | tuple], str, Any]):
class ChoiceField(Field[str | None, str | int | tuple[str | int, str | int | tuple] | None, str | None, Any]):
html_cutoff: int | None
html_cutoff_text: StrOrPromise | None
allow_blank: bool
Expand Down Expand Up @@ -487,9 +477,9 @@ class ChoiceField(Field[str, str | int | tuple[str | int, str | int | tuple], st
class MultipleChoiceField(
ChoiceField,
Field[
str,
Sequence[str | int | tuple[str | int, str | int]],
Sequence[str | tuple[str | int, str | int]],
str | None,
Sequence[str | int | tuple[str | int, str | int]] | None,
Sequence[str | tuple[str | int, str | int]] | None,
Any,
],
):
Expand Down Expand Up @@ -542,7 +532,7 @@ class FilePathField(ChoiceField):
allow_blank: bool = ...,
) -> None: ...

class FileField(Field[File, File, str | None, Any]): # this field can return None without raising!
class FileField(Field[File | None, File | None, str | None, Any]): # this field can return None without raising!
max_length: int
allow_empty_file: bool
use_url: bool
Expand Down Expand Up @@ -591,7 +581,7 @@ class ImageField(FileField):

class _UnvalidatedField(Field): ...

class ListField(Field[list[Any], list[Any], list[Any], Any]):
class ListField(Field[list[Any] | None, list[Any] | None, list[Any] | None, Any]):
child: Field
allow_empty: bool
max_length: int | None
Expand All @@ -618,7 +608,7 @@ class ListField(Field[list[Any], list[Any], list[Any], Any]):
) -> None: ...
def run_child_validation(self, data: list[Mapping[Any, Any]]) -> Any: ...

class DictField(Field[dict[Any, Any], dict[Any, Any], dict[Any, Any], Any]):
class DictField(Field[dict[Any, Any] | None, dict[Any, Any] | None, dict[Any, Any] | None, Any]):
child: Field
allow_empty: bool
def __init__(
Expand All @@ -644,7 +634,9 @@ class DictField(Field[dict[Any, Any], dict[Any, Any], dict[Any, Any], Any]):
class HStoreField(DictField):
child: CharField

class JSONField(Field[dict[str, Any] | list[dict[str, Any]], dict[str, Any] | list[dict[str, Any]], str, Any]):
class JSONField(
Field[dict[str, Any] | list[dict[str, Any]] | None, dict[str, Any] | list[dict[str, Any]] | None, str | None, Any]
):
binary: bool
encoder: type[JSONEncoder] | None
decoder: type[JSONDecoder] | None
Expand Down