diff --git a/django-stubs/http/response.pyi b/django-stubs/http/response.pyi index 813cd13..29ffa90 100644 --- a/django-stubs/http/response.pyi +++ b/django-stubs/http/response.pyi @@ -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 diff --git a/django-stubs/test/client.pyi b/django-stubs/test/client.pyi index e3cb3fd..ece73fe 100644 --- a/django-stubs/test/client.pyi +++ b/django-stubs/test/client.pyi @@ -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: ... diff --git a/scripts/enabled_test_modules.py b/scripts/enabled_test_modules.py index 89d0bb9..c6722e9 100644 --- a/scripts/enabled_test_modules.py +++ b/scripts/enabled_test_modules.py @@ -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[, ]", 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]")' diff --git a/test-data/typecheck/test/test_testcase.yml b/test-data/typecheck/test/test_testcase.yml new file mode 100644 index 0000000..d7125c3 --- /dev/null +++ b/test-data/typecheck/test/test_testcase.yml @@ -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'