-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Handling mixins that reference fields that they do not contain #5837
Comments
How about (for python 2.7; in python 3.6 or greater you'd use a variable type annotation):
(The I had hoped it would work to declare |
That actually will work, and probably will do it this way, but if there was a way to define some parent class that only mypy would see would be perfect. So I would need to define these fields only once. |
You can probably do that with something like:
|
That actually seems to work rather well, at least for the code so far. I used TYPE_CHECKING from typing instead of MYPY, is there any disadvantage to use that instead? |
You should use |
Awesome, thank you all for your help. |
Actually looks like this still has an issue. When I'm now checking for classes that includes mixins I'm getting error similar to this: |
So what’s the class structure?
--
--Guido (mobile)
|
This is how the code looks now after the change: #! /usr/bin/env python
from typing import TYPE_CHECKING
class A(object):
def __init__(self, var1, var2):
# type: (str, str) -> None
self.var1 = var1
self.var2 = var2
if TYPE_CHECKING:
_Base = A
else:
_Base = object
class Mixin(_Base):
def funcA(self):
# type: () -> str
return self.var1
class B(A, Mixin):
pass
def main():
# type: () -> None
a = B('value1', 'value2')
print(a.funcA())
if __name__ == '__main__':
main() |
Yeah, if I place mixin first then things work and I no longer even need to make type checking a special case, just have the base class a parent of mixin by default. Seems like that is the correct way of using mixins. My only concern is that this is an existing code base and I'm wondering if this could break something this way. |
I don't think we can help you with that... |
This helps accomodate the growing Bot class, since it's easily divided into several logical components anyway. Each component is an ABC (Abstract Base Class) that functions as a mixin and thus can access all the other attributes of the Bot class. Bot subclasses all the mixins to unify them. Telethon uses the same approach to divide TelegramClient into many mixins that each contain different categories of API methods. An adaptive MixinBase reference that is defined as abc.ABC during runtime and Bot during type checking is used as the subclass for each mixin to work around python/mypy#5837. Furthermore, the MixinBase reference itself has its type specified as Any to work around python/mypy#2477. The only change for outsiders should be the Bot class being moved to the `core` module. Signed-off-by: Danny Lin <danny@kdrag0n.dev>
Try with: from typing import Type, TYPE_CHECKING, TypeVar
T = TypeVar('T')
def with_typehint(baseclass: Type[T]) -> Type[T]:
"""
Useful function to make mixins with baseclass typehint
```
class ReadonlyMixin(with_typehint(BaseAdmin))):
...
```
"""
if TYPE_CHECKING:
return baseclass
return object Example: class ReadOnlyInlineMixin(with_typehint(BaseModelAdmin)):
def get_readonly_fields(self,
request: WSGIRequest,
obj: Optional[Model] = None) -> List[str]:
if self.readonly_fields is None:
readonly_fields = []
else:
readonly_fields = self.readonly_fields # self get is typed by baseclass
return self._get_readonly_fields(request, obj) + list(readonly_fields)
def has_change_permission(self,
request: WSGIRequest,
obj: Optional[Model] = None) -> bool:
return (
request.method in ['GET', 'HEAD']
and super().has_change_permission(request, obj) # super is typed by baseclass
)
>>> ReadOnlyAdminMixin.__mro__
(<class 'custom.django.admin.mixins.ReadOnlyAdminMixin'>, <class 'object'>) |
@leonardon473 trying your proposal results in:
using: The closest reference I find to that error is here: which suggests that mypy does not accept dynamically generated base classes? Also mypy complained that |
An alternate solution for future folks which this doesn't work: I was having a similar problem making a QuerySet Mixin for Django. class Mixin:
def do_stuff(self, value: str) -> QuerySet:
return self.filter(abc=value) Resulted in So I tried the solution above: if TYPE_CHECKING:
_Base = QuerySet
else:
_Base = object
class Mixin(_Base):
def do_stuff(self, value: str) -> QuerySet:
return self.filter(abc=value) And got Not sure why it's failing with QuerySet, but also with Model and Field based classes. Most other classes seem to work fine, just not those (maybe something in the metaclasses Django uses?). I finally got it to work by type hinting class Mixin:
def do_stuff(self: QuerySet, value: str) -> QuerySet:
return self.filter(abc=value) If anyone has feedback as to why the other ways aren't working I'd be happy to hear about it (CPYthon 3.8.0, Mypy: 0.780), but this solution worked and is also more succinct that the others. |
choose 1. u can specify needed field with their typehints as classvariables of Mixin class. It's will be more pythonic(more explicit) |
This is more of a question or a feature request if there's not a good way to do it.
I'm currently adding types to existing python 2.7 code base hoping that it will help me with migrating the code to Python 3.
I run into code that looks like this:
This is valid Python code, i.e.:
But mypy is complaining:
I understand the issue, there's nothing in
Mixin
class that states thatvar1
will be available.My question is is there a way to tell mypy somehow that this attribute will be available?
I did not have much luck with using abstract classes and
Protocol
.I also had a suggestion to use stub file, but that doesn't look like be perfect solution either, since I might need to define types outside of the module and keep them in sync. And this pattern is used in multiple places not just one.
Is there a way how this could be expressed in mypy? If the code needs to be slightly refactored without changing how it works I suppose that might also be acceptable.
The text was updated successfully, but these errors were encountered: