Skip to content
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

Improve django.http types #208

Merged
merged 25 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7727f65
cookie: Refactor to include generic type
noelleleigh Oct 31, 2023
76763e8
datastructures: CaseInsensitiveMapping keys are always str
noelleleigh Oct 31, 2023
19c81d2
response: Add ResponseHeaders
noelleleigh Oct 31, 2023
55689c4
HttpResponseBase: Add more properties, make typing stricter
noelleleigh Oct 31, 2023
6da0325
HttpResponse: Make bytes iterable
noelleleigh Oct 31, 2023
d7749ed
HttpResponse: content is always bytes
noelleleigh Oct 31, 2023
0712142
HttpResponse: streaming is always bool
noelleleigh Oct 31, 2023
e863bb7
url property only exists on HttpResponseRedirectBase
noelleleigh Oct 31, 2023
4484a5d
Use Never type for methods and properties that always raise an Exception
noelleleigh Oct 31, 2023
27b90a8
ResponseHeaders: code formatting
noelleleigh Oct 31, 2023
ce2b007
HttpResponseBase: Change attributes into properties
noelleleigh Oct 31, 2023
ad50499
HttpResponse: Convert attributes into properties
noelleleigh Oct 31, 2023
3a4b3a5
HttpResponse: Add overridden methods
noelleleigh Oct 31, 2023
35bf132
StreamingHttpResponse: Add streaming
noelleleigh Oct 31, 2023
fc15079
StreamingHttpResponse: Add Iterable and AsyncIterable parents
noelleleigh Oct 31, 2023
05cf49e
FileResponse: Remove monkey-patched and non-public attributes
noelleleigh Oct 31, 2023
3716b5d
UnreadablePostError is an OSError
noelleleigh Oct 31, 2023
e9a6a79
HttpHeaders: Use single generic type
noelleleigh Oct 31, 2023
b4536e8
HttpHeaders: Add methods
noelleleigh Oct 31, 2023
def0a30
HttpRequest: Move middleware attributes to bottom
noelleleigh Oct 31, 2023
ec1aeb6
HttpRequest isn't a BytesIO subclass
noelleleigh Oct 31, 2023
a1e99b9
HttpRequest: Flesh out types
noelleleigh Oct 31, 2023
4cf4bf1
HttpResponseBase isn't iterable
noelleleigh Oct 31, 2023
7aa6689
./s/lint results
noelleleigh Oct 31, 2023
8500198
HttpResponseBase: Remove Never methods
noelleleigh Oct 31, 2023
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
2 changes: 2 additions & 0 deletions django-stubs/db/models/functions/comparison.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ class Cast(Func):
) -> None: ...

class Coalesce(Func): ...

class Collate(Func):
def __init__(self, expression: Any, collation: str) -> None: ...

class Greatest(Func): ...

class JSONObject(Func):
def __init__(self, **fields: Any) -> None: ...

Expand Down
4 changes: 3 additions & 1 deletion django-stubs/db/models/manager.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class BaseManager(QuerySet[_T]):
def __init__(self) -> None: ...
def deconstruct(
self,
) -> tuple[bool, str | None, str | None, tuple[Any, ...] | None, dict[str, Any] | None]: ...
) -> tuple[
bool, str | None, str | None, tuple[Any, ...] | None, dict[str, Any] | None
]: ...
def check(self, **kwargs: Any) -> list[Any]: ...
@classmethod
def from_queryset(
Expand Down
4 changes: 3 additions & 1 deletion django-stubs/http/cookie.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from http.cookies import SimpleCookie as SimpleCookie
from http import cookies

SimpleCookie = cookies.SimpleCookie[str]

def parse_cookie(cookie: str) -> dict[str, str]: ...
51 changes: 37 additions & 14 deletions django-stubs/http/request.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from collections.abc import Iterable, Mapping
from io import BytesIO
from re import Pattern
from typing import Any, BinaryIO, overload
from typing import Any, BinaryIO, TypeVar, overload
from typing_extensions import Self

from django.contrib.auth.base_user import AbstractBaseUser
Expand All @@ -19,22 +18,34 @@ from django.utils.datastructures import (
RAISE_ERROR: object = ...
host_validation_re: Pattern[str] = ...

class UnreadablePostError(IOError): ...
class UnreadablePostError(OSError): ...
class RawPostDataException(Exception): ...

UploadHandlerList = (
list[uploadhandler.FileUploadHandler]
| ImmutableList[uploadhandler.FileUploadHandler]
)

class HttpHeaders(CaseInsensitiveMapping[str, str]):
T = TypeVar("T")

class HttpHeaders(CaseInsensitiveMapping[str]):
HTTP_PREFIX: str = ...
UNPREFIXED_HEADERS: set[str] = ...
def __init__(self, environ: Mapping[str, Any]) -> None: ...
@classmethod
def parse_header_name(cls, header: str) -> str | None: ...
@classmethod
def to_wsgi_name(cls, header: str) -> str: ...
@classmethod
def to_asgi_name(cls, header: str) -> str: ...
@classmethod
def to_wsgi_names(cls, headers: Mapping[str, T]) -> Mapping[str, T]: ...
@classmethod
def to_asgi_names(cls, headers: Mapping[str, T]) -> Mapping[str, T]: ...

class MediaType: ...

class HttpRequest(BytesIO):
class HttpRequest:
GET: QueryDict = ...
POST: QueryDict = ...
COOKIES: dict[str, str] = ...
Expand All @@ -43,31 +54,33 @@ class HttpRequest(BytesIO):
path: str = ...
path_info: str = ...
method: str | None = ...
resolver_match: ResolverMatch = ...
resolver_match: ResolverMatch | None = ...
content_type: str | None = ...
content_params: dict[str, str] | None = ...
user: AbstractBaseUser | AnonymousUser
site: Site
session: SessionBase
encoding: str | None = ...
upload_handlers: UploadHandlerList = ...
def __init__(self) -> None: ...
@property
def accepted_types(self) -> list[MediaType]: ...
def accepts(self, media_type: str) -> bool: ...
def get_host(self) -> str: ...
def get_port(self) -> str: ...
def get_full_path(self, force_append_slash: bool = ...) -> str: ...
def get_full_path_info(self, force_append_slash: bool = ...) -> str: ...
def get_signed_cookie(
self,
key: str,
default: Any = ...,
default: T = ...,
salt: str = ...,
max_age: int | None = ...,
) -> str | None: ...
) -> str | T: ...
def get_raw_uri(self) -> str: ...
def build_absolute_uri(self, location: str | None = ...) -> str: ...
@property
def scheme(self) -> str | None: ...
def scheme(self) -> str: ...
def is_secure(self) -> bool: ...
@property
def encoding(self) -> str: ...
@property
def upload_handlers(self) -> UploadHandlerList: ...
def is_ajax(self) -> bool: ...
def parse_file_upload(
self, META: Mapping[str, Any], post_data: BinaryIO
Expand All @@ -77,6 +90,16 @@ class HttpRequest(BytesIO):
@property
def body(self) -> bytes: ...
def _load_post_and_files(self) -> None: ...
def close(self) -> None: ...
def read(self, *args: Any, **kwargs: Any) -> bytes: ...
def readline(self, *args: Any, **kwargs: Any) -> bytes: ...
def __iter__(self) -> Iterable[bytes]: ...
def readlines(self) -> list[bytes]: ...

# Attributes added by commonly-used middleware
user: AbstractBaseUser | AnonymousUser
site: Site
session: SessionBase

class QueryDict(MultiValueDict[str, str]):
encoding: str = ...
Expand Down
68 changes: 38 additions & 30 deletions django-stubs/http/response.pyi
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
import datetime
from collections.abc import AsyncIterable, Iterable, Iterator
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
from io import BytesIO
from json import JSONEncoder
from typing import Any, overload
from typing_extensions import Never

from django.core.handlers.wsgi import WSGIRequest
from django.http.cookie import SimpleCookie
from django.template import Context, Template
from django.test.client import Client
from django.urls import ResolverMatch
from django.utils.datastructures import CaseInsensitiveMapping

class ResponseHeaders(CaseInsensitiveMapping[str]):
def pop(self, key: str, default: str | None = ...) -> str: ...
def setdefault(self, key: str, value: str) -> None: ...

class BadHeaderError(ValueError): ...

class HttpResponseBase(Iterable[Any]):
status_code: int = ...
cookies: SimpleCookie[str] = ...
reason_phrase: str = ...
charset: str = ...
closed: bool = ...
class HttpResponseBase:
cookies: SimpleCookie
headers: ResponseHeaders
status_code: int
closed: bool
def __init__(
self,
content_type: str | None = ...,
status: int | None = ...,
reason: str | None = ...,
charset: str | None = ...,
) -> None: ...
@property
def reason_phrase(self) -> str: ...
@property
def charset(self) -> str: ...
def serialize_headers(self) -> bytes: ...
def __setitem__(self, header: str | bytes, value: str | bytes | int) -> None: ...
def __delitem__(self, header: str | bytes) -> None: ...
Expand Down Expand Up @@ -56,28 +65,30 @@ class HttpResponseBase(Iterable[Any]):
) -> None: ...
def make_bytes(self, value: object) -> bytes: ...
def close(self) -> None: ...
def write(self, content: str | bytes) -> None: ...
def flush(self) -> None: ...
def tell(self) -> int: ...
def readable(self) -> bool: ...
def seekable(self) -> bool: ...
def writable(self) -> bool: ...
def writelines(self, lines: Iterable[object]) -> Any: ...
def __iter__(self) -> Iterator[Any]: ...

class HttpResponse(HttpResponseBase):
content: Any
class HttpResponse(HttpResponseBase, Iterable[bytes]):
csrf_cookie_set: bool
redirect_chain: list[tuple[str, int]]
sameorigin: bool
test_server_port: str
test_was_secure_request: bool
xframe_options_exempt: bool
streaming: bool = ...
def __init__(self, content: object = ..., *args: Any, **kwargs: Any) -> None: ...
streaming: bool
def __init__(self, content: bytes = ..., *args: Any, **kwargs: Any) -> None: ...
def serialize(self) -> bytes: ...
@property
def url(self) -> str: ...
def content(self) -> bytes: ...
def __iter__(self) -> Iterator[bytes]: ...
def write(self, content: str | bytes) -> None: ...
def tell(self) -> int: ...
def getvalue(self) -> bytes: ...
def writable(self) -> bool: ...
def writelines(self, lines: Iterable[str | bytes]) -> None: ...

# Attributes assigned by monkey-patching in test client ClientHandler.__call__()
wsgi_request: WSGIRequest
# Attributes assigned by monkey-patching in test client Client.request()
Expand All @@ -87,10 +98,9 @@ class HttpResponse(HttpResponseBase):
context: Context
resolver_match: ResolverMatch
def json(self) -> Any: ...
def getvalue(self) -> bytes: ...

class StreamingHttpResponse(HttpResponseBase):
content: Any
class StreamingHttpResponse(HttpResponseBase, Iterable[bytes], AsyncIterable[bytes]):
streaming: bool
streaming_content: Iterable[bytes] | AsyncIterable[bytes]
def __init__(
self,
Expand All @@ -99,27 +109,25 @@ class StreamingHttpResponse(HttpResponseBase):
**kwargs: Any
) -> None: ...
def getvalue(self) -> bytes: ...
@property
def content(self) -> Never: ...
def __iter__(self) -> Iterator[bytes]: ...
def __aiter__(self) -> AsyncIterator[bytes]: ...

class FileResponse(StreamingHttpResponse):
client: Client
context: None
file_to_stream: BytesIO | None
request: dict[str, str]
resolver_match: ResolverMatch
templates: list[Any]
wsgi_request: WSGIRequest
block_size: int = ...
as_attachment: bool = ...
filename: str = ...
block_size: int
as_attachment: bool
filename: str
def __init__(
self, *args: Any, as_attachment: bool = ..., filename: str = ..., **kwargs: Any
) -> None: ...
def set_headers(self, filelike: BytesIO) -> None: ...
def json(self) -> dict[str, Any]: ...

class HttpResponseRedirectBase(HttpResponse):
allowed_schemes: list[str] = ...
def __init__(self, redirect_to: str, *args: Any, **kwargs: Any) -> None: ...
@property
def url(self) -> str: ...

class HttpResponseRedirect(HttpResponseRedirectBase): ...
class HttpResponsePermanentRedirect(HttpResponseRedirectBase): ...
Expand Down
2 changes: 1 addition & 1 deletion django-stubs/test/client.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ _RequestData = Any | None
class RequestFactory:
json_encoder: type[JSONEncoder]
defaults: dict[str, str]
cookies: SimpleCookie[str]
cookies: SimpleCookie
errors: BytesIO
def __init__(
self, *, json_encoder: type[JSONEncoder] = ..., **defaults: Any
Expand Down
2 changes: 1 addition & 1 deletion django-stubs/test/testcases.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ from django.core.servers.basehttp import ThreadedWSGIServer, WSGIRequestHandler
from django.db import connections as connections # noqa: F401
from django.db.backends.sqlite3.base import DatabaseWrapper
from django.db.models import Model
from django.db.models.query import _BaseQuerySet, ValuesQuerySet
from django.db.models.query import ValuesQuerySet, _BaseQuerySet
from django.forms.fields import EmailField
from django.http.response import HttpResponse, HttpResponseBase
from django.template.base import Template
Expand Down
6 changes: 3 additions & 3 deletions django-stubs/utils/datastructures.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ class DictWrapper(dict[str, _V]):
self, data: Iterable[tuple[str, _V]], func: Callable[[_V], _V], prefix: str
) -> None: ...

class CaseInsensitiveMapping(Mapping[_K, _V]):
class CaseInsensitiveMapping(Mapping[str, _V]):
def __init__(self, data: Any) -> None: ...
def __getitem__(self, key: _K) -> Any: ...
def __getitem__(self, key: str) -> Any: ...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[_K]: ...
def __iter__(self) -> Iterator[str]: ...
def copy(self) -> Self: ...