Skip to content

Commit 2bb3b53

Browse files
[django-filter] Add type stubs (#14540)
1 parent 81c8fcb commit 2bb3b53

18 files changed

+773
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
SECRET_KEY = "1"
2+
3+
INSTALLED_APPS = (
4+
"django.contrib.contenttypes",
5+
"django.contrib.sites",
6+
"django.contrib.sessions",
7+
"django.contrib.messages",
8+
"django.contrib.admin.apps.SimpleAdminConfig",
9+
"django.contrib.staticfiles",
10+
"django.contrib.auth",
11+
"django_filters",
12+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Iterator class attributes: Runtime behavior differs due to Django 5.0 compatibility logic in choice setters
2+
django_filters.fields.ChoiceField.iterator
3+
django_filters.fields.ModelChoiceField.iterator
4+
django_filters.fields.ModelMultipleChoiceField.iterator
5+
django_filters.fields.MultipleChoiceField.iterator
6+
7+
# Lookup NamedTuple: Parameter name mismatch between inferred stub and runtime
8+
django_filters.fields.Lookup.__new__
9+
django_filters.fields.Lookup.__doc__
10+
11+
# ChoiceIteratorMixin.choices: Cannot define choices property due to incompatibility with base class ChoiceField
12+
django_filters.fields.ChoiceIteratorMixin.choices

stubs/django-filter/METADATA.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
version = "25.1.*"
2+
upstream_repository = "https://github.com/carltongibson/django-filter/"
3+
requires = ["django-stubs"]
4+
5+
[tool.stubtest]
6+
mypy_plugins = ["mypy_django_plugin.main"]
7+
mypy_plugins_config = {"django-stubs" = {"django_settings_module" = "@tests.django_settings"}}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Final
2+
3+
from .filters import *
4+
from .filterset import FilterSet as FilterSet, UnknownFieldBehavior as UnknownFieldBehavior
5+
6+
__version__: Final[str]
7+
8+
def parse_version(version: str) -> tuple[str | int]: ...
9+
10+
VERSION: tuple[str | int, ...]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def is_crispy() -> bool: ...
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from _typeshed import Unused
2+
from typing import Any
3+
4+
DEFAULTS: dict[str, Any] # Configuration values can be strings, booleans, callables, etc.
5+
DEPRECATED_SETTINGS: list[str]
6+
7+
def is_callable(value: Any) -> bool: ... # Accepts any value to test if it's callable
8+
9+
class Settings:
10+
# Setting values can be of any type, so getter and setter methods return/accept Any
11+
def __getattr__(self, name: str) -> Any: ... # Returns setting values of various types
12+
def get_setting(self, setting: str) -> Any: ... # Setting values vary by configuration option
13+
def change_setting(
14+
self, setting: str, value: Any, enter: bool, **kwargs: Unused
15+
) -> None: ... # Accepts any setting value type
16+
17+
settings: Settings
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from typing import Any, Final
2+
3+
# String constant used to indicate all model fields should be included
4+
ALL_FIELDS: Final[str] = "__all__"
5+
# Collection of values considered empty by Django filters - tuple type allows various empty containers
6+
EMPTY_VALUES: Final[Any] = ...
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from typing import Any
2+
3+
from django.core.exceptions import FieldError
4+
from django.db import models
5+
6+
class FieldLookupError(FieldError):
7+
# Field type params are runtime-determined
8+
def __init__(self, model_field: models.Field[Any, Any], lookup_expr: str) -> None: ...
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from collections.abc import Sequence
2+
from typing import Any, NamedTuple
3+
from typing_extensions import TypeAlias
4+
5+
from django import forms
6+
7+
DJANGO_50: bool
8+
9+
# Ref: django-stubs/forms/fields.pyi
10+
# Problem: attribute `widget` is always of type `Widget` after field instantiation.
11+
# However, on class level it can be set to `Type[Widget]` too.
12+
# If we annotate it as `Union[Widget, Type[Widget]]`, every code that uses field
13+
# instances will not typecheck.
14+
# If we annotate it as `Widget`, any widget subclasses that do e.g.
15+
# `widget = Select` will not typecheck.
16+
# `Any` gives too much freedom, but does not create false positives.
17+
_ClassLevelWidget: TypeAlias = Any
18+
19+
class RangeField(forms.MultiValueField):
20+
widget: _ClassLevelWidget = ...
21+
def __init__(
22+
self, fields: tuple[forms.Field, forms.Field] | None = None, *args: Any, **kwargs: Any
23+
) -> None: ... # Args/kwargs can be any field params, passes to parent
24+
def compress(self, data_list: list[Any] | None) -> slice | None: ... # Data list elements can be any field value type
25+
26+
class DateRangeField(RangeField):
27+
widget: _ClassLevelWidget = ...
28+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent
29+
def compress(self, data_list: list[Any] | None) -> slice | None: ... # Date values in list can be any date type
30+
31+
class DateTimeRangeField(RangeField):
32+
widget: _ClassLevelWidget = ...
33+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent
34+
35+
class IsoDateTimeRangeField(RangeField):
36+
widget: _ClassLevelWidget = ...
37+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent
38+
39+
class TimeRangeField(RangeField):
40+
widget: _ClassLevelWidget = ...
41+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent
42+
43+
class Lookup(NamedTuple):
44+
value: Any # Lookup values can be any filterable type
45+
lookup_expr: str
46+
47+
class LookupChoiceField(forms.MultiValueField):
48+
def __init__(
49+
self, field: forms.Field, lookup_choices: Sequence[tuple[str, str]], *args: Any, **kwargs: Any
50+
) -> None: ... # Args/kwargs can be any field params, uses kwargs for empty_label
51+
def compress(self, data_list: list[Any] | None) -> Lookup | None: ... # Data list can contain any lookup components
52+
53+
class IsoDateTimeField(forms.DateTimeField):
54+
ISO_8601: str
55+
input_formats: list[str]
56+
def strptime(self, value: str, format: str) -> Any: ... # Returns datetime objects or parsing results
57+
58+
class BaseCSVField(forms.Field):
59+
base_widget_class: _ClassLevelWidget = ...
60+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for widget config
61+
def clean(self, value: Any) -> Any: ... # Cleaned values can be any valid field type
62+
63+
class BaseRangeField(BaseCSVField):
64+
widget: _ClassLevelWidget = ...
65+
def clean(self, value: Any) -> Any: ... # Input and output values can be any range type
66+
67+
class ChoiceIterator:
68+
field: ChoiceField
69+
choices: Sequence[tuple[Any, str]] # Choice values can be any type (int, str, Model, etc.)
70+
def __init__(
71+
self, field: ChoiceField, choices: Sequence[tuple[Any, str]]
72+
) -> None: ... # Choice values can be any selectable type
73+
def __iter__(self) -> Any: ... # Iterator yields choice tuples with any value types
74+
def __len__(self) -> int: ...
75+
76+
class ModelChoiceIterator(forms.models.ModelChoiceIterator):
77+
def __iter__(self) -> Any: ... # Iterator yields choice tuples with any value types
78+
def __len__(self) -> int: ...
79+
80+
class ChoiceIteratorMixin:
81+
null_label: str | None
82+
null_value: Any # Null choice values can be any type (None, empty string, etc.)
83+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for null config
84+
85+
class ChoiceField(ChoiceIteratorMixin, forms.ChoiceField):
86+
iterator = ChoiceIterator
87+
empty_label: str | None
88+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for label config
89+
90+
class MultipleChoiceField(ChoiceIteratorMixin, forms.MultipleChoiceField):
91+
iterator = ChoiceIterator
92+
empty_label: str | None
93+
def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params, sets empty_label
94+
95+
class ModelChoiceField(ChoiceIteratorMixin, forms.ModelChoiceField[Any]):
96+
iterator = ModelChoiceIterator
97+
def to_python(self, value: Any) -> Any: ... # Converts any input to Python model objects or values
98+
99+
class ModelMultipleChoiceField(ChoiceIteratorMixin, forms.ModelMultipleChoiceField[Any]):
100+
iterator = ModelChoiceIterator

0 commit comments

Comments
 (0)