Skip to content

Add new headers parameter to test classes #1529

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

Merged
merged 3 commits into from
Jun 6, 2023
Merged
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
174 changes: 147 additions & 27 deletions django-stubs/test/client.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,83 @@ class _RequestFactory(Generic[_T]):
errors: BytesIO
def __init__(self, *, json_encoder: type[JSONEncoder] = ..., **defaults: Any) -> None: ...
def request(self, **request: Any) -> _T: ...
def get(self, path: str, data: _GetDataType = ..., secure: bool = ..., **extra: Any) -> _T: ...
def post(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> _T: ...
def head(self, path: str, data: Any = ..., secure: bool = ..., **extra: Any) -> _T: ...
def trace(self, path: str, secure: bool = ..., **extra: Any) -> _T: ...
def get(
self,
path: str,
data: _GetDataType = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason to type the dict value as Any? Is there any type besides str that makes sense?

Copy link
Contributor

Choose a reason for hiding this comment

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

Django supports anything that can be cast to a string, such as headers={"x-user-id": user.id}.

Copy link
Collaborator

@intgr intgr Jun 6, 2023

Choose a reason for hiding this comment

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

  1. Could be solved easily with str(user.id) in user code

  2. I tested this, I don't think there's any "casting to string" that happens. Django's Client just passes the value verbatim to the view function.
    image

    So this actually misbehaves, since request.headers[x] is defined to return str, but here it returned list. And it can result code behaving differently during testing and with real world requests, causing more hair pulling for users.

  3. For testing really weird cases there's always the **extra kwarg escape hatch.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Anyway let's continue this discussion in #1534.

**extra: Any
) -> _T: ...
def post(
self,
path: str,
data: Any = ...,
content_type: str = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _T: ...
def head(
self, path: str, data: Any = ..., secure: bool = ..., *, headers: dict[str, Any] | None = ..., **extra: Any
) -> _T: ...
def trace(self, path: str, secure: bool = ..., *, headers: dict[str, Any] | None = ..., **extra: Any) -> _T: ...
def options(
self, path: str, data: dict[str, str] | str = ..., content_type: str = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: dict[str, str] | str = ...,
content_type: str = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _T: ...
def put(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> _T: ...
def patch(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> _T: ...
def delete(self, path: str, data: Any = ..., content_type: str = ..., secure: bool = ..., **extra: Any) -> _T: ...
def generic(
self, method: str, path: str, data: Any = ..., content_type: str | None = ..., secure: bool = ..., **extra: Any
def put(
self,
path: str,
data: Any = ...,
content_type: str = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _T: ...
def patch(
self,
path: str,
data: Any = ...,
content_type: str = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _T: ...
def delete(
self,
path: str,
data: Any = ...,
content_type: str = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _T: ...

class RequestFactory(_RequestFactory[WSGIRequest]): ...

class _AsyncRequestFactory(_RequestFactory[_T]):
def request(self, **request: Any) -> _T: ...
def generic(
self, method: str, path: str, data: Any = ..., content_type: str | None = ..., secure: bool = ..., **extra: Any
self,
method: str,
path: str,
data: Any = ...,
content_type: str | None = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _T: ...

class RequestFactory(_RequestFactory[WSGIRequest]): ...
class _AsyncRequestFactory(_RequestFactory[_T]): ...
class AsyncRequestFactory(_AsyncRequestFactory[ASGIRequest]): ...

# fakes to distinguish WSGIRequest and ASGIRequest
Expand Down Expand Up @@ -130,39 +185,104 @@ class Client(ClientMixin, _RequestFactory[_MonkeyPatchedWSGIResponse]):
handler: ClientHandler
raise_request_exception: bool
exc_info: tuple[type[BaseException], BaseException, TracebackType] | None
extra: dict[str, Any] | None
headers: dict[str, Any]
def __init__(
self, enforce_csrf_checks: bool = ..., raise_request_exception: bool = ..., **defaults: Any
self,
enforce_csrf_checks: bool = ...,
raise_request_exception: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**defaults: Any
) -> None: ...
# Silence type warnings, since this class overrides arguments and return types in an unsafe manner.
def request(self, **request: Any) -> _MonkeyPatchedWSGIResponse: ...
def get( # type: ignore
self, path: str, data: _GetDataType = ..., follow: bool = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: _GetDataType = ...,
follow: bool = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _MonkeyPatchedWSGIResponse: ...
def post( # type: ignore
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: Any = ...,
content_type: str = ...,
follow: bool = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _MonkeyPatchedWSGIResponse: ...
def head( # type: ignore
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: Any = ...,
follow: bool = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _MonkeyPatchedWSGIResponse: ...
def trace( # type: ignore
self, path: str, data: Any = ..., follow: bool = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: Any = ...,
follow: bool = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _MonkeyPatchedWSGIResponse: ...
def put( # type: ignore
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: Any = ...,
content_type: str = ...,
follow: bool = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _MonkeyPatchedWSGIResponse: ...
def patch( # type: ignore
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: Any = ...,
content_type: str = ...,
follow: bool = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _MonkeyPatchedWSGIResponse: ...
def delete( # type: ignore
self, path: str, data: Any = ..., content_type: str = ..., follow: bool = ..., secure: bool = ..., **extra: Any
self,
path: str,
data: Any = ...,
content_type: str = ...,
follow: bool = ...,
secure: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**extra: Any
) -> _MonkeyPatchedWSGIResponse: ...

class AsyncClient(ClientMixin, _AsyncRequestFactory[Awaitable[_MonkeyPatchedASGIResponse]]):
handler: AsyncClientHandler
raise_request_exception: bool
exc_info: Any
extra: Any
extra: dict[str, Any] | None
headers: dict[str, Any]
def __init__(
self, enforce_csrf_checks: bool = ..., raise_request_exception: bool = ..., **defaults: Any
self,
enforce_csrf_checks: bool = ...,
raise_request_exception: bool = ...,
*,
headers: dict[str, Any] | None = ...,
**defaults: Any
) -> None: ...
async def request(self, **request: Any) -> _MonkeyPatchedASGIResponse: ...