-
-
Notifications
You must be signed in to change notification settings - Fork 512
Improve types of django.contrib.admin.decorators
#1267
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,50 @@ | ||
from collections.abc import Callable, Sequence | ||
from typing import Any, TypeVar | ||
from typing import Any, TypeVar, overload | ||
|
||
from django.contrib.admin import ModelAdmin | ||
from django.contrib.admin.sites import AdminSite | ||
from django.db.models import Combinable, QuerySet | ||
from django.db.models.base import Model | ||
from django.db.models.expressions import BaseExpression | ||
from django.http import HttpRequest | ||
from django.utils.functional import _StrPromise | ||
|
||
_ModelT = TypeVar("_ModelT", bound=Model) | ||
_T = TypeVar("_T") | ||
_Model = TypeVar("_Model", bound=Model) | ||
_ModelAdmin = TypeVar("_ModelAdmin", bound=ModelAdmin) | ||
_Request = TypeVar("_Request", bound=HttpRequest) | ||
_QuerySet = TypeVar("_QuerySet", bound=QuerySet) | ||
|
||
@overload | ||
def action( | ||
function: Callable[[_ModelAdmin, _Request, _QuerySet], None], | ||
permissions: Sequence[str] | None = ..., | ||
description: _StrPromise | None = ..., | ||
) -> Callable[[_ModelAdmin, _Request, _QuerySet], None]: ... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be easier and less repetition to define _ActionCallable = TypeVar("_ActionCallable", bound=Callable[[ModelAdmin, HttpRequest, QuerySet], None]) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not gonna work. It needs a bound, if you declare them without a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, right, the bound callable won't match either. I started out with that but then you'll need nested TypeVars to get a correct bound. Else we're not supporting subclasses. See here https://mypy-play.net/?mypy=0.982&python=3.10&gist=de1239d0a4ced1707ec6afb9f4610160 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks cluttered, I agree, but this was the only way I could get it to work correctly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you manage to figure out how to declare a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK I understand now. Ordinarily with What you're trying to do here is covariant behavior: the callable should accept some subclass of It's a bit of a hack and not entirely type-safe, but I think practicality beats purity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on what it is that won't be type checked and why it's not type safe? (I presume with the current display annotations I'm not sure I'm following on how that conclusion is reached. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example given two models The "type-safe" approach is contravariant compatibility: you can define There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm yeah that's true. Didn't think of that. I suppose in order to get around that, it would be necessary for What I'm saying is e.g. |
||
@overload | ||
def action( | ||
function: Callable[[ModelAdmin, HttpRequest, QuerySet], None] | None = ..., | ||
*, | ||
permissions: Sequence[str] | None = ..., | ||
description: str | None = ..., | ||
) -> Callable: ... | ||
description: _StrPromise | None = ..., | ||
) -> Callable[ | ||
[Callable[[_ModelAdmin, _Request, _QuerySet], None]], Callable[[_ModelAdmin, _Request, _QuerySet], None] | ||
]: ... | ||
@overload | ||
def display( | ||
function: Callable[[_Model], _T], | ||
boolean: bool | None = ..., | ||
ordering: str | Combinable | BaseExpression | None = ..., | ||
description: _StrPromise | None = ..., | ||
empty_value: str | None = ..., | ||
) -> Callable[[_Model], _T]: ... | ||
@overload | ||
def display( | ||
function: Callable[[_ModelT], Any] | None = ..., | ||
*, | ||
boolean: bool | None = ..., | ||
ordering: str | Combinable | BaseExpression | None = ..., | ||
description: str | None = ..., | ||
description: _StrPromise | None = ..., | ||
empty_value: str | None = ..., | ||
) -> Callable: ... | ||
def register(*models: type[Model], site: AdminSite | None = ...) -> Callable: ... | ||
) -> Callable[[Callable[[_Model], _T]], Callable[[_Model], _T]]: ... | ||
intgr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def register( | ||
*models: type[Model], site: AdminSite | None = ... | ||
) -> Callable[[type[_ModelAdmin]], type[_ModelAdmin]]: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only remaining dilemma I have is whether the request argument should be a TypeVar, or change it to just
HttpRequest
without TypeVar.It seems most django-stubs code already assumes that requests are always
HttpRequest
and that tends to be sufficient.Technically most of the time they're
WSGIRequest
, but that seems to be an implementation detail. There is no explicit documentation aboutWSGIRequest
in Django docs (barring testing topics and release notes). AndASGIRequest
is now now rearing its head.Unless someone else chimes in, I'll trust your judgment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the type-unsafety you've already stated still stands. Having this, it should be possible for someone to do
e.g. Capturing attributes attached by middleware(s) or so.
Or doing stuff mentioned in docs: https://github.com/typeddjango/django-stubs#how-can-i-create-a-httprequest-thats-guaranteed-to-have-an-authenticated-user