diff --git a/stubs/django-filter/@tests/stubtest_allowlist.txt b/stubs/django-filter/@tests/stubtest_allowlist.txt index e38c1aad8afc..4a0cd56cafbd 100644 --- a/stubs/django-filter/@tests/stubtest_allowlist.txt +++ b/stubs/django-filter/@tests/stubtest_allowlist.txt @@ -10,3 +10,13 @@ django_filters.fields.Lookup.__doc__ # ChoiceIteratorMixin.choices: Cannot define choices property due to incompatibility with base class ChoiceField django_filters.fields.ChoiceIteratorMixin.choices + +# Our __init__ signatures are more precise -- ignore "stub does not have *args argument" +django_filters.fields.BaseCSVField.__init__ +django_filters.fields.ChoiceField.__init__ +django_filters.fields.ChoiceIteratorMixin.__init__ +django_filters.fields.LookupChoiceField.__init__ +django_filters.fields.MultipleChoiceField.__init__ +django_filters.fields.RangeField.__init__ +django_filters.filters.QuerySetRequestMixin.__init__ +django_filters.widgets.CSVWidget.__init__ diff --git a/stubs/django-filter/django_filters/fields.pyi b/stubs/django-filter/django_filters/fields.pyi index 1d9678da586e..7920f9878c9c 100644 --- a/stubs/django-filter/django_filters/fields.pyi +++ b/stubs/django-filter/django_filters/fields.pyi @@ -1,8 +1,12 @@ -from collections.abc import Sequence +from _typeshed import Unused +from collections.abc import Callable, Iterable, Mapping, Sequence from typing import Any, NamedTuple from typing_extensions import TypeAlias from django import forms +from django.db.models import Choices +from django.forms import Widget +from django_stubs_ext import StrOrPromise DJANGO_50: bool @@ -15,30 +19,49 @@ DJANGO_50: bool # `widget = Select` will not typecheck. # `Any` gives too much freedom, but does not create false positives. _ClassLevelWidget: TypeAlias = Any +# Validator parameter type depends on type of the form field used. +_ValidatorCallable: TypeAlias = Callable[[Any], None] +# Based on django-stubs utils/choices.pyi +_Choice: TypeAlias = tuple[Any, Any] +_ChoiceNamedGroup: TypeAlias = tuple[str, Iterable[_Choice]] +_Choices: TypeAlias = Iterable[_Choice | _ChoiceNamedGroup] +_ChoicesMapping: TypeAlias = Mapping[Any, Any] +_ChoicesInput: TypeAlias = _Choices | _ChoicesMapping | type[Choices] | Callable[[], _Choices | _ChoicesMapping] class RangeField(forms.MultiValueField): widget: _ClassLevelWidget = ... def __init__( - self, fields: tuple[forms.Field, forms.Field] | None = None, *args: Any, **kwargs: Any + self, + fields: tuple[forms.Field, forms.Field] | None = None, + *, + # Inherited from Django MultiValueField + require_all_fields: bool = True, + required: bool = ..., + widget: Widget | type[Widget] | None = ..., + label: StrOrPromise | None = ..., + initial: Any | None = ..., # Type depends on the form field used. + help_text: StrOrPromise = ..., + error_messages: Mapping[str, StrOrPromise] | None = ..., + show_hidden_initial: bool = ..., + validators: Sequence[_ValidatorCallable] = ..., + localize: bool = ..., + disabled: bool = ..., + label_suffix: str | None = ..., ) -> None: ... # Args/kwargs can be any field params, passes to parent def compress(self, data_list: list[Any] | None) -> slice | None: ... # Data list elements can be any field value type class DateRangeField(RangeField): widget: _ClassLevelWidget = ... - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent def compress(self, data_list: list[Any] | None) -> slice | None: ... # Date values in list can be any date type class DateTimeRangeField(RangeField): widget: _ClassLevelWidget = ... - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent class IsoDateTimeRangeField(RangeField): widget: _ClassLevelWidget = ... - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent class TimeRangeField(RangeField): widget: _ClassLevelWidget = ... - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent class Lookup(NamedTuple): value: Any # Lookup values can be any filterable type @@ -46,7 +69,24 @@ class Lookup(NamedTuple): class LookupChoiceField(forms.MultiValueField): def __init__( - self, field: forms.Field, lookup_choices: Sequence[tuple[str, str]], *args: Any, **kwargs: Any + self, + field: forms.Field, + lookup_choices: Sequence[tuple[str, str]], + *, + empty_label: StrOrPromise = ..., + widget: Unused = ..., + help_text: Unused = ..., + # Inherited from Django MultiValueField + require_all_fields: bool = True, + required: bool = ..., + label: StrOrPromise | None = ..., + initial: Any | None = ..., # Type depends on the form field used. + error_messages: Mapping[str, StrOrPromise] | None = ..., + show_hidden_initial: bool = ..., + validators: Sequence[_ValidatorCallable] = ..., + localize: bool = ..., + disabled: bool = ..., + label_suffix: str | None = ..., ) -> None: ... # Args/kwargs can be any field params, uses kwargs for empty_label def compress(self, data_list: list[Any] | None) -> Lookup | None: ... # Data list can contain any lookup components @@ -57,7 +97,6 @@ class IsoDateTimeField(forms.DateTimeField): class BaseCSVField(forms.Field): base_widget_class: _ClassLevelWidget = ... - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for widget config def clean(self, value: Any) -> Any: ... # Cleaned values can be any valid field type class BaseRangeField(BaseCSVField): @@ -78,19 +117,37 @@ class ModelChoiceIterator(forms.models.ModelChoiceIterator): def __len__(self) -> int: ... class ChoiceIteratorMixin: - null_label: str | None + null_label: StrOrPromise | None null_value: Any # Null choice values can be any type (None, empty string, etc.) - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for null config + def __init__(self, *, null_label: StrOrPromise | None, null_value: Any) -> None: ... class ChoiceField(ChoiceIteratorMixin, forms.ChoiceField): iterator = ChoiceIterator - empty_label: str | None - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for label config + empty_label: StrOrPromise + def __init__( + self, + *, + empty_label: StrOrPromise = ..., + # Inherited from Django ChoiceField + choices: _ChoicesInput = (), + required: bool = ..., + widget: Widget | type[Widget] | None = ..., + label: StrOrPromise | None = ..., + initial: Any | None = ..., # Type depends on the form field used. + help_text: StrOrPromise = ..., + error_messages: Mapping[str, StrOrPromise] | None = ..., + show_hidden_initial: bool = ..., + validators: Sequence[_ValidatorCallable] = ..., + localize: bool = ..., + disabled: bool = ..., + label_suffix: str | None = ..., + null_label: StrOrPromise | None, + null_value: Any, # Type depends on the form field used. + ) -> None: ... class MultipleChoiceField(ChoiceIteratorMixin, forms.MultipleChoiceField): iterator = ChoiceIterator - empty_label: str | None - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params, sets empty_label + empty_label: StrOrPromise | None class ModelChoiceField(ChoiceIteratorMixin, forms.ModelChoiceField[Any]): iterator = ModelChoiceIterator diff --git a/stubs/django-filter/django_filters/filters.pyi b/stubs/django-filter/django_filters/filters.pyi index 714ddc89f720..db6faefc27d0 100644 --- a/stubs/django-filter/django_filters/filters.pyi +++ b/stubs/django-filter/django_filters/filters.pyi @@ -1,9 +1,10 @@ -from collections.abc import Callable +from collections.abc import Callable, Iterable from typing import Any from django import forms from django.db.models import Q, QuerySet from django.forms import Field +from django_stubs_ext import StrOrPromise from .fields import ( BaseCSVField, @@ -12,6 +13,7 @@ from .fields import ( DateTimeRangeField, IsoDateTimeField, IsoDateTimeRangeField, + Lookup, LookupChoiceField, ModelChoiceField, ModelMultipleChoiceField, @@ -65,7 +67,7 @@ class Filter: field_name: str | None = None, lookup_expr: str | None = None, *, - label: str | None = None, + label: StrOrPromise | None = None, method: Callable[..., Any] | str | None = None, # Filter methods can return various types distinct: bool = False, exclude: bool = False, @@ -73,7 +75,7 @@ class Filter: ) -> None: ... def get_method(self, qs: QuerySet[Any]) -> Callable[..., QuerySet[Any]]: ... # Returns QuerySet filtering methods method: Callable[..., Any] | str | None # Custom filter methods return various types - label: str | None # Filter label for display + label: StrOrPromise | None # Filter label for display @property def field(self) -> Field: ... def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... # Filter value can be any user input type @@ -87,7 +89,19 @@ class BooleanFilter(Filter): class ChoiceFilter(Filter): field_class: type[Any] # Base class for choice-based filters null_value: Any # Null value can be any type (None, empty string, etc.) - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for null_value config + def __init__( + self, + field_name: str | None = None, + lookup_expr: str | None = None, + *, + null_value: Any = ..., # Null value can be any type (None, empty string, etc.) + # Inherited from Filter + label: StrOrPromise | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + distinct: bool = False, + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) + ) -> None: ... def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... class TypedChoiceFilter(Filter): @@ -101,7 +115,20 @@ class MultipleChoiceFilter(Filter): always_filter: bool conjoined: bool null_value: Any # Multiple choice null values vary by implementation - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for distinct, conjoined, null_value config + def __init__( + self, + field_name: str | None = None, + lookup_expr: str | None = None, + *, + distinct: bool = True, # Overrides distinct default + conjoined: bool = False, + null_value: Any = ..., # Multiple choice null values vary by implementation + # Inherited from Filter + label: StrOrPromise | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) + ) -> None: ... def is_noop(self, qs: QuerySet[Any], value: Any) -> bool: ... # Value can be any filter input def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... def get_filter_predicate(self, v: Any) -> Q: ... # Predicate value can be any filter input type @@ -126,7 +153,7 @@ class DurationFilter(Filter): class QuerySetRequestMixin: queryset: QuerySet[Any] | None - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for queryset config + def __init__(self, *, queryset: QuerySet[Any] | None) -> None: ... def get_request(self) -> Any: ... # Request can be HttpRequest or other request types def get_queryset(self, request: Any) -> QuerySet[Any]: ... # Request parameter accepts various request types @property @@ -134,10 +161,42 @@ class QuerySetRequestMixin: class ModelChoiceFilter(QuerySetRequestMixin, ChoiceFilter): field_class: type[ModelChoiceField] # More specific than parent ChoiceField - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for empty_label config + def __init__( + self, + field_name: str | None = None, + lookup_expr: str | None = None, + *, + # Inherited from QuerySetRequestMixin + queryset: QuerySet[Any] | None = None, + # Inherited from ChoiceFilter + null_value: Any = ..., # Null value can be any type (None, empty string, etc.) + # Inherited from Filter + label: StrOrPromise | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + distinct: bool = False, + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) + ) -> None: ... class ModelMultipleChoiceFilter(QuerySetRequestMixin, MultipleChoiceFilter): field_class: type[ModelMultipleChoiceField] # More specific than parent MultipleChoiceField + def __init__( + self, + field_name: str | None = None, + lookup_expr: str | None = None, + *, + # Inherited from QuerySetRequestMixin + queryset: QuerySet[Any] | None = None, + # Inherited from MultipleChoiceFilter + distinct: bool = True, # Overrides distinct default + conjoined: bool = False, + null_value: Any = ..., # Multiple choice null values vary by implementation + # Inherited from Filter + label: StrOrPromise | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) + ) -> None: ... class NumberFilter(Filter): field_class: type[forms.DecimalField] @@ -159,7 +218,20 @@ class DateRangeFilter(ChoiceFilter): choices: list[tuple[str, str]] | None filters: dict[str, Filter] | None def __init__( - self, choices: list[tuple[str, str]] | None = None, filters: dict[str, Filter] | None = None, *args: Any, **kwargs: Any + self, + choices: list[tuple[str, str]] | None = None, + filters: dict[str, Filter] | None = None, + field_name: str | None = None, + lookup_expr: str | None = None, + *, + # Inherited from ChoiceFilter + null_value: Any = ..., # Null value can be any type (None, empty string, etc.) + # Inherited from Filter + label: StrOrPromise | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + distinct: bool = False, + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) ) -> None: ... # Uses args/kwargs for choice and filter configuration def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... @@ -186,45 +258,64 @@ class AllValuesMultipleFilter(MultipleChoiceFilter): class BaseCSVFilter(Filter): base_field_class: type[BaseCSVField] = ... field_class: type[Any] # Base class for CSV-based filters - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for help_text and widget config -class BaseInFilter(BaseCSVFilter): - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Sets lookup_expr and passes through +class BaseInFilter(BaseCSVFilter): ... class BaseRangeFilter(BaseCSVFilter): base_field_class: type[BaseRangeField] = ... - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Sets lookup_expr and passes through class LookupChoiceFilter(Filter): field_class: type[forms.CharField] outer_class: type[LookupChoiceField] = ... - empty_label: str | None - lookup_choices: list[tuple[str, str]] | None + empty_label: StrOrPromise | None + lookup_choices: list[tuple[str, StrOrPromise]] | None def __init__( self, field_name: str | None = None, - lookup_choices: list[tuple[str, str]] | None = None, + lookup_choices: list[tuple[str, StrOrPromise]] | None = None, field_class: type[Field] | None = None, - **kwargs: Any, # Handles empty_label and other field config + *, + empty_label: StrOrPromise = ..., + # Inherited from Filter + label: StrOrPromise | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + distinct: bool = False, + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) ) -> None: ... @classmethod - def normalize_lookup(cls, lookup: Any) -> tuple[Any, str]: ... - def get_lookup_choices(self) -> list[tuple[str, str]]: ... + def normalize_lookup(cls, lookup: str | tuple[str, StrOrPromise]) -> tuple[str, StrOrPromise]: ... + def get_lookup_choices(self) -> list[tuple[str, StrOrPromise]]: ... @property def field(self) -> Field: ... lookup_expr: str - def filter(self, qs: QuerySet[Any], lookup: Any) -> QuerySet[Any]: ... + def filter(self, qs: QuerySet[Any], lookup: Lookup) -> QuerySet[Any]: ... class OrderingFilter(BaseCSVFilter, ChoiceFilter): field_class: type[BaseCSVField] # Inherits CSV field behavior for comma-separated ordering descending_fmt: str param_map: dict[str, str] | None - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for fields and field_labels config + def __init__( + self, + field_name: str | None = None, + lookup_expr: str | None = None, + *, + fields: dict[str, str] | Iterable[tuple[str, str]] = ..., + field_labels: dict[str, StrOrPromise] = ..., + # Inherited from ChoiceFilter + null_value: Any = ..., # Null value can be any type (None, empty string, etc.) + # Inherited from Filter + label: StrOrPromise | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + distinct: bool = False, + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) + ) -> None: ... def get_ordering_value(self, param: str) -> str: ... def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... @classmethod def normalize_fields(cls, fields: Any) -> list[str]: ... - def build_choices(self, fields: Any, labels: dict[str, str] | None) -> list[tuple[str, str]]: ... + def build_choices(self, fields: Any, labels: dict[str, StrOrPromise] | None) -> list[tuple[str, str]]: ... class FilterMethod: f: Filter diff --git a/stubs/django-filter/django_filters/rest_framework/__init__.pyi b/stubs/django-filter/django_filters/rest_framework/__init__.pyi index adb475260a84..560557bc8d9b 100644 --- a/stubs/django-filter/django_filters/rest_framework/__init__.pyi +++ b/stubs/django-filter/django_filters/rest_framework/__init__.pyi @@ -1,3 +1,34 @@ from .backends import DjangoFilterBackend as DjangoFilterBackend -from .filters import * +from .filters import ( + AllValuesFilter as AllValuesFilter, + AllValuesMultipleFilter as AllValuesMultipleFilter, + BaseCSVFilter as BaseCSVFilter, + BaseInFilter as BaseInFilter, + BaseRangeFilter as BaseRangeFilter, + BooleanFilter as BooleanFilter, + CharFilter as CharFilter, + ChoiceFilter as ChoiceFilter, + DateFilter as DateFilter, + DateFromToRangeFilter as DateFromToRangeFilter, + DateRangeFilter as DateRangeFilter, + DateTimeFilter as DateTimeFilter, + DateTimeFromToRangeFilter as DateTimeFromToRangeFilter, + DurationFilter as DurationFilter, + Filter as Filter, + IsoDateTimeFilter as IsoDateTimeFilter, + IsoDateTimeFromToRangeFilter as IsoDateTimeFromToRangeFilter, + LookupChoiceFilter as LookupChoiceFilter, + ModelChoiceFilter as ModelChoiceFilter, + ModelMultipleChoiceFilter as ModelMultipleChoiceFilter, + MultipleChoiceFilter as MultipleChoiceFilter, + NumberFilter as NumberFilter, + NumericRangeFilter as NumericRangeFilter, + OrderingFilter as OrderingFilter, + RangeFilter as RangeFilter, + TimeFilter as TimeFilter, + TimeRangeFilter as TimeRangeFilter, + TypedChoiceFilter as TypedChoiceFilter, + TypedMultipleChoiceFilter as TypedMultipleChoiceFilter, + UUIDFilter as UUIDFilter, +) from .filterset import FilterSet as FilterSet diff --git a/stubs/django-filter/django_filters/rest_framework/filters.pyi b/stubs/django-filter/django_filters/rest_framework/filters.pyi index c289dc59509a..9dec2084d15f 100644 --- a/stubs/django-filter/django_filters/rest_framework/filters.pyi +++ b/stubs/django-filter/django_filters/rest_framework/filters.pyi @@ -1,5 +1,3 @@ -from typing import Any - from ..filters import ( AllValuesFilter, AllValuesMultipleFilter, @@ -67,5 +65,4 @@ __all__ = [ ] # REST framework specific BooleanFilter that uses BooleanWidget by default -class BooleanFilter(_BaseBooleanFilter): - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Accepts any filter initialization params +class BooleanFilter(_BaseBooleanFilter): ... diff --git a/stubs/django-filter/django_filters/widgets.pyi b/stubs/django-filter/django_filters/widgets.pyi index ab1c51636d2a..8707dc0fdbfb 100644 --- a/stubs/django-filter/django_filters/widgets.pyi +++ b/stubs/django-filter/django_filters/widgets.pyi @@ -30,7 +30,6 @@ class LinkWidget(forms.Widget): class SuffixedMultiWidget(forms.MultiWidget): suffixes: list[str] - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any widget params for MultiWidget def suffixed(self, name: str, suffix: str) -> str: ... # Widget value and context can contain any data types def get_context(self, name: str, value: Any, attrs: dict[str, Any] | None) -> dict[str, Any]: ... @@ -70,16 +69,12 @@ class BaseCSVWidget(forms.Widget): # Can be widget class or instance - __init__ converts to instance via instantiation or deepcopy surrogate: type[Any] = ... - # Args/kwargs can be any widget params for surrogate init - def __init__(self, *args: Any, **kwargs: Any) -> None: ... # CSV widget data can contain any types def value_from_datadict(self, data: Mapping[str, Any], files: Mapping[str, Any], name: str) -> list[str]: ... # Widget value and renderer can be any type def render(self, name: str, value: Any, attrs: dict[str, Any] | None = None, renderer: Any | None = None) -> SafeString: ... -class CSVWidget(BaseCSVWidget, forms.TextInput): - # Args/kwargs can be any widget params, attrs for styling - def __init__(self, *args: Any, attrs: dict[str, Any] | None = None, **kwargs: Any) -> None: ... +class CSVWidget(BaseCSVWidget, forms.TextInput): ... class QueryArrayWidget(BaseCSVWidget, forms.TextInput): # Query array widget data can contain any types