Skip to content

[django-filter] Improve constructor param types and add missing __all__ #14556

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
94 changes: 82 additions & 12 deletions stubs/django-filter/django_filters/fields.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from collections.abc import Sequence
from typing import Any, NamedTuple
from collections.abc import Iterable, Mapping, Sequence
from typing import Any, Callable, 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

Expand All @@ -15,38 +18,72 @@ DJANGO_50: bool
# `widget = Select` will not typecheck.
# `Any` gives too much freedom, but does not create false positives.
_ClassLevelWidget: TypeAlias = Any
_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]

Comment on lines +23 to +28
Copy link
Collaborator

Choose a reason for hiding this comment

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

Considering that we require django-stubs, can't we just import those from there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried, but IIRC received a mypy error about importing non-public members. Will try again and report back.

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 MultiValueField
require_all_fields: bool = True,
required: bool = ...,
widget: Widget | type[Widget] | None = ...,
label: StrOrPromise | None = ...,
initial: Any | None = ...,
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
lookup_expr: str

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]],
*,
require_all_fields: bool = True,
required: bool = ...,
widget: Widget | type[Widget] | None = ...,
label: StrOrPromise | None = ...,
initial: Any | None = ...,
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, uses kwargs for empty_label
def compress(self, data_list: list[Any] | None) -> Lookup | None: ... # Data list can contain any lookup components

Expand All @@ -57,7 +94,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):
Expand All @@ -80,17 +116,51 @@ class ModelChoiceIterator(forms.models.ModelChoiceIterator):
class ChoiceIteratorMixin:
null_label: str | 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: str | 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
def __init__(
self,
*,
choices: _ChoicesInput = (),
required: bool = ...,
widget: Widget | type[Widget] | None = ...,
label: StrOrPromise | None = ...,
initial: Any | None = ...,
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: str | None,
null_value: Any,
) -> 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
def __init__(
self,
*,
choices: _ChoicesInput = (),
required: bool = ...,
widget: Widget | type[Widget] | None = ...,
label: StrOrPromise | None = ...,
initial: Any | None = ...,
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: str | None,
null_value: Any,
) -> None: ...

class ModelChoiceField(ChoiceIteratorMixin, forms.ModelChoiceField[Any]):
iterator = ModelChoiceIterator
Expand Down
93 changes: 82 additions & 11 deletions stubs/django-filter/django_filters/filters.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,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 = None,
# Inherited from Filter
label: str | 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):
Expand All @@ -101,7 +113,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 = None,
# Inherited from Filter
label: str | 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
Expand All @@ -126,15 +151,29 @@ 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
def field(self) -> Field: ...

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,
*,
queryset: QuerySet[Any] | None,
# Inherited from ChoiceFilter
null_value: Any = None,
# Inherited from Filter
label: str | 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
Expand All @@ -159,7 +198,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 = None,
# Inherited from Filter
label: str | 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]: ...

Expand All @@ -186,14 +238,11 @@ 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]
Expand All @@ -205,7 +254,15 @@ class LookupChoiceFilter(Filter):
field_name: str | None = None,
lookup_choices: list[tuple[str, str]] | None = None,
field_class: type[Field] | None = None,
**kwargs: Any, # Handles empty_label and other field config
*,
# Inherited from ChoiceFilter
null_value: Any = None,
# Inherited from Filter
label: str | 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]: ...
Expand All @@ -219,7 +276,21 @@ 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,
*,
# Inherited from ChoiceFilter
null_value: Any = None,
# Inherited from Filter
field_labels: dict[Any, Any] | None = None,
label: str | 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
Expand Down
35 changes: 35 additions & 0 deletions stubs/django-filter/django_filters/rest_framework/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
from .backends import DjangoFilterBackend as DjangoFilterBackend
from .filters import *
from .filterset import FilterSet as FilterSet

__all__ = [
"DjangoFilterBackend",
"FilterSet",
"BooleanFilter",
"AllValuesFilter",
"AllValuesMultipleFilter",
"BaseCSVFilter",
"BaseInFilter",
"BaseRangeFilter",
"CharFilter",
"ChoiceFilter",
"DateFilter",
"DateFromToRangeFilter",
"DateRangeFilter",
"DateTimeFilter",
"DateTimeFromToRangeFilter",
"DurationFilter",
"Filter",
"IsoDateTimeFilter",
"IsoDateTimeFromToRangeFilter",
"LookupChoiceFilter",
"ModelChoiceFilter",
"ModelMultipleChoiceFilter",
"MultipleChoiceFilter",
"NumberFilter",
"NumericRangeFilter",
"OrderingFilter",
"RangeFilter",
"TimeFilter",
"TimeRangeFilter",
"TypedChoiceFilter",
"TypedMultipleChoiceFilter",
"UUIDFilter",
]
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Any

from ..filters import (
AllValuesFilter,
AllValuesMultipleFilter,
Expand Down Expand Up @@ -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): ...
7 changes: 1 addition & 6 deletions stubs/django-filter/django_filters/widgets.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ...
Expand Down Expand Up @@ -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
Expand Down
Loading