mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-06 04:04:26 +08:00
Improve test client type stubs (#322)
* Added missing `follow: bool` argument to Client request methods.
* Annotated return types as `HttpResponse` instead of `Any`.
* Made `Client` class inherit from `RequestFactory`, as it does in Django.
* Changed `json()` return type to Any, as it can also be a list.
Wrt (2), these return types were reverted from `HttpResponse` to `Any`
in commit 287c64d6fb. But I suspect that
was simply to silence mypy warnings about "incompatible with supertype
RequestFactory", not because there were any issues with the annotation.
Note that `Client.request()` monkey-patches the `HttpResponse` object
with some additional attributes. Those attributes were already annotated
before, I reordered and added additional comments to make it clear where
they come from.
This commit is contained in:
@@ -62,25 +62,27 @@ class HttpResponseBase(Iterable[Any]):
|
||||
def __iter__(self) -> Iterator[Any]: ...
|
||||
|
||||
class HttpResponse(HttpResponseBase):
|
||||
client: Client
|
||||
context: Context
|
||||
content: Any
|
||||
csrf_cookie_set: bool
|
||||
redirect_chain: List[Tuple[str, int]]
|
||||
request: Dict[str, Any]
|
||||
resolver_match: ResolverMatch
|
||||
sameorigin: bool
|
||||
templates: List[Template]
|
||||
test_server_port: str
|
||||
test_was_secure_request: bool
|
||||
wsgi_request: WSGIRequest
|
||||
xframe_options_exempt: bool
|
||||
streaming: bool = ...
|
||||
def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ...
|
||||
def serialize(self) -> bytes: ...
|
||||
@property
|
||||
def url(self) -> str: ...
|
||||
def json(self) -> Dict[str, Any]: ...
|
||||
# Attributes assigned by monkey-patching in test client ClientHandler.__call__()
|
||||
wsgi_request: WSGIRequest
|
||||
# Attributes assigned by monkey-patching in test client Client.request()
|
||||
client: Client
|
||||
request: Dict[str, Any]
|
||||
templates: List[Template]
|
||||
context: Context
|
||||
resolver_match: ResolverMatch
|
||||
def json(self) -> Any: ...
|
||||
|
||||
class StreamingHttpResponse(HttpResponseBase):
|
||||
content: Any
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from io import BytesIO
|
||||
from types import TracebackType
|
||||
from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.sessions.backends.base import SessionBase
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.http.cookie import SimpleCookie
|
||||
from django.http.request import HttpRequest
|
||||
from django.http.response import HttpResponse, HttpResponseBase
|
||||
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from json import JSONEncoder
|
||||
|
||||
BOUNDARY: str = ...
|
||||
MULTIPART_CONTENT: str = ...
|
||||
@@ -37,11 +38,11 @@ def encode_multipart(boundary: str, data: Dict[str, Any]) -> bytes: ...
|
||||
def encode_file(boundary: str, key: str, file: Any) -> List[bytes]: ...
|
||||
|
||||
class RequestFactory:
|
||||
json_encoder: Type[DjangoJSONEncoder] = ...
|
||||
defaults: Dict[str, str] = ...
|
||||
cookies: SimpleCookie = ...
|
||||
errors: BytesIO = ...
|
||||
def __init__(self, *, json_encoder: Any = ..., **defaults: Any) -> None: ...
|
||||
json_encoder: Type[JSONEncoder]
|
||||
defaults: Dict[str, str]
|
||||
cookies: SimpleCookie
|
||||
errors: BytesIO
|
||||
def __init__(self, *, json_encoder: Type[JSONEncoder] = ..., **defaults: Any) -> None: ...
|
||||
def request(self, **request: Any) -> WSGIRequest: ...
|
||||
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> WSGIRequest: ...
|
||||
def post(
|
||||
@@ -54,6 +55,7 @@ class RequestFactory:
|
||||
path: str,
|
||||
data: Union[Dict[str, str], str] = ...,
|
||||
content_type: str = ...,
|
||||
follow: bool = ...,
|
||||
secure: bool = ...,
|
||||
**extra: Any
|
||||
) -> WSGIRequest: ...
|
||||
@@ -76,39 +78,50 @@ class RequestFactory:
|
||||
**extra: Any
|
||||
) -> WSGIRequest: ...
|
||||
|
||||
class Client:
|
||||
json_encoder: Type[DjangoJSONEncoder] = ...
|
||||
defaults: Dict[str, str] = ...
|
||||
cookies: SimpleCookie = ...
|
||||
errors: BytesIO = ...
|
||||
handler: ClientHandler = ...
|
||||
exc_info: None = ...
|
||||
def __init__(self, enforce_csrf_checks: bool = ..., **defaults: Any) -> None: ...
|
||||
def request(self, **request: Any) -> Any: ...
|
||||
def get(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def post(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def trace(self, path: str, secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def options(
|
||||
class Client(RequestFactory):
|
||||
handler: ClientHandler
|
||||
raise_request_exception: bool
|
||||
exc_info: Optional[Tuple[Type[BaseException], BaseException, TracebackType]]
|
||||
def __init__(
|
||||
self,
|
||||
enforce_csrf_checks: bool = ...,
|
||||
raise_request_exception: bool = ...,
|
||||
*,
|
||||
json_encoder: Type[JSONEncoder] = ...,
|
||||
**defaults: Any
|
||||
) -> None: ...
|
||||
# Silence type warnings, since this class overrides arguments and return types in an unsafe manner.
|
||||
def request(self, **request: Any) -> HttpResponse: ... # type: ignore
|
||||
def get( # type: ignore
|
||||
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def post( # type: ignore
|
||||
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def head( # type: ignore
|
||||
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def trace( # type: ignore
|
||||
self, path: str, follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def options( # type: ignore
|
||||
self,
|
||||
path: str,
|
||||
data: Union[Dict[str, str], str] = ...,
|
||||
content_type: str = ...,
|
||||
follow: bool = ...,
|
||||
secure: bool = ...,
|
||||
**extra: Any
|
||||
) -> Any: ...
|
||||
def put(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def patch(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def delete(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> Any: ...
|
||||
def generic(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
data: Any = ...,
|
||||
content_type: Optional[str] = ...,
|
||||
secure: bool = ...,
|
||||
**extra: Any
|
||||
) -> Any: ...
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def put( # type: ignore
|
||||
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def patch( # type: ignore
|
||||
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def delete( # type: ignore
|
||||
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
|
||||
) -> HttpResponse: ... # type: ignore
|
||||
def store_exc_info(self, **kwargs: Any) -> None: ...
|
||||
@property
|
||||
def session(self) -> SessionBase: ...
|
||||
|
||||
@@ -64,7 +64,12 @@ IGNORED_ERRORS = {
|
||||
'Incompatible types in string interpolation',
|
||||
'"None" has no attribute',
|
||||
'has no attribute "assert',
|
||||
'Unsupported dynamic base class'
|
||||
'Unsupported dynamic base class',
|
||||
'error: "HttpResponse" has no attribute "streaming_content"',
|
||||
'error: "HttpResponse" has no attribute "context_data"',
|
||||
],
|
||||
'admin_inlines': [
|
||||
'error: "HttpResponse" has no attribute "rendered_content"',
|
||||
],
|
||||
'admin_utils': [
|
||||
'"Article" has no attribute "non_field"',
|
||||
@@ -240,6 +245,7 @@ IGNORED_ERRORS = {
|
||||
],
|
||||
'middleware': [
|
||||
re.compile(r'"(HttpRequest|WSGIRequest)" has no attribute'),
|
||||
'Incompatible types in assignment (expression has type "HttpResponseBase", variable has type "HttpResponse")',
|
||||
],
|
||||
'many_to_many': [
|
||||
'(expression has type "List[Article]", variable has type "Article_RelatedManager2',
|
||||
@@ -376,6 +382,9 @@ IGNORED_ERRORS = {
|
||||
'settings_tests': [
|
||||
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
|
||||
],
|
||||
'shortcuts': [
|
||||
'error: "Context" has no attribute "request"',
|
||||
],
|
||||
'signals': [
|
||||
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";'
|
||||
],
|
||||
@@ -428,6 +437,7 @@ IGNORED_ERRORS = {
|
||||
'test_client_regress': [
|
||||
'(expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
|
||||
'Unsupported left operand type for + ("None")',
|
||||
'Argument 1 to "len" has incompatible type "Context"; expected "Sized"',
|
||||
],
|
||||
'transactions': [
|
||||
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
|
||||
|
||||
12
test-data/typecheck/test/test_testcase.yml
Normal file
12
test-data/typecheck/test/test_testcase.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
- case: testcase_client_attr
|
||||
main: |
|
||||
from django.test.testcases import TestCase
|
||||
|
||||
class ExampleTestCase(TestCase):
|
||||
def test_method(self) -> None:
|
||||
reveal_type(self.client) # N: Revealed type is 'django.test.client.Client'
|
||||
resp = self.client.post('/url', {'doit': 'srs'}, 'application/json', False, True, extra='value')
|
||||
reveal_type(resp.status_code) # N: Revealed type is 'builtins.int'
|
||||
# Attributes monkey-patched by test Client class:
|
||||
resp.json()
|
||||
reveal_type(resp.wsgi_request) # N: Revealed type is 'django.core.handlers.wsgi.WSGIRequest'
|
||||
Reference in New Issue
Block a user