Skip to content

Commit 2720b74

Browse files
committed
add proper generic support for get_object_or_404/get_list_or_404, fixes #22
1 parent 563c0ad commit 2720b74

File tree

3 files changed

+25
-8
lines changed

3 files changed

+25
-8
lines changed

django-stubs/shortcuts.pyi

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from typing import Any, Callable, Dict, List, Optional, Type, Union, Sequence, Protocol
1+
from typing import Any, Callable, Dict, List, Optional, Protocol, Sequence, Type, TypeVar, Union
22

3-
from django.db.models import Manager, QuerySet
43
from django.db.models.base import Model
54
from django.http.response import HttpResponse as HttpResponse, HttpResponseRedirect as HttpResponseRedirect
65

6+
from django.db.models import Manager, QuerySet
77
from django.http import HttpRequest
88

99
def render_to_response(
@@ -28,6 +28,9 @@ class SupportsGetAbsoluteUrl(Protocol):
2828
def redirect(
2929
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: bool = ..., **kwargs: Any
3030
) -> HttpResponseRedirect: ...
31-
def get_object_or_404(klass: Union[Type[Model], Manager, QuerySet], *args: Any, **kwargs: Any) -> Model: ...
32-
def get_list_or_404(klass: Union[Type[Model], Manager, QuerySet], *args: Any, **kwargs: Any) -> List[Model]: ...
31+
32+
_T = TypeVar("_T", bound=Model)
33+
34+
def get_object_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> _T: ...
35+
def get_list_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> List[_T]: ...
3336
def resolve_url(to: Union[Callable, Model, str], *args: Any, **kwargs: Any) -> str: ...

scripts/typecheck_tests.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,10 @@
149149
],
150150
'get_object_or_404': [
151151
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
152-
+ 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"',
153-
'Argument 1 to "get_object_or_404" has incompatible type "Type[CustomClass]"; '
154-
+ 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"',
152+
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
155153
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
156-
+ 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"'
154+
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
155+
'CustomClass'
157156
],
158157
'get_or_create': [
159158
'Argument 1 to "update_or_create" of "QuerySet" has incompatible type "**Dict[str, object]"; expected "MutableMapping[str, Any]"'

test-data/typecheck/managers.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,19 @@ class AbstractBase2(models.Model):
136136

137137
class Child(AbstractBase1, AbstractBase2):
138138
pass
139+
[out]
140+
141+
[CASE get_object_or_404_returns_proper_types]
142+
from django.shortcuts import get_object_or_404, get_list_or_404
143+
from django.db import models
144+
145+
class MyModel(models.Model):
146+
pass
147+
reveal_type(get_object_or_404(MyModel)) # E: Revealed type is 'main.MyModel*'
148+
reveal_type(get_object_or_404(MyModel.objects)) # E: Revealed type is 'main.MyModel*'
149+
reveal_type(get_object_or_404(MyModel.objects.get_queryset())) # E: Revealed type is 'main.MyModel*'
150+
151+
reveal_type(get_list_or_404(MyModel)) # E: Revealed type is 'builtins.list[main.MyModel*]'
152+
reveal_type(get_list_or_404(MyModel.objects)) # E: Revealed type is 'builtins.list[main.MyModel*]'
153+
reveal_type(get_list_or_404(MyModel.objects.get_queryset())) # E: Revealed type is 'builtins.list[main.MyModel*]'
139154
[out]

0 commit comments

Comments
 (0)