-
Notifications
You must be signed in to change notification settings - Fork 242
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
Declaring classes as pure mixins #246
Comments
(copying some items from my comments in #241) I think that omitting the signatures for |
Agreed that the code is not super clear, as there is no explicit documentation about what the mixin expects. I probably wouldn't use this for new code -- it would mainly be aimed at making it easier to migrate legacy code to static typing. |
I like this idea, here are few comments:
|
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 tested in Pyright: 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 This is a nice trick, but it can't work with static type checkers, unfortunately. E. g. mypy throws Unsupported dynamic base class "with_typehint" error. I usually want to declare a Mixin that is common for classes with some common superclass (let's say Django generic views), so I'd like an api like this: @mixin(Thing) # or @baseclass(Thing)
class MyMixin:
x = 1
def do_stuff(self) -> int:
return self.one_thing() # ok, because it's defined in Thing class
def do_other_stuff(self) -> int:
return self.y # fail, there is no attribute `y` in Thing or MyMixin
class Thing(MyMixin):
def one_thing(self) -> int:
return 1
def second_thing(self, x: int) -> str:
return self.x # ok, because `x` is defined in MyMixin |
Actually the idea described by @leonardon473 and @stinovlas
Now to my Use Case: So what I actually looking for is a command that generates the Protocol of the third Party library for me. Doing so I would get type hints i.e. from PyCharm and type checking warning ones the interface of the third party tool changes. So actually @JukkaL proposal of just ignoring the type checks would not satisfy my use case here. A MixIn should be defined properly, so that it is clear for which classes that MixIn is intended for. As an example. Imaging I want to test implementations of a from typing import Type
from unittest import TestCase
class BaseClass:
def __init__(self, init: str):
self.name = init
def foo(self, bar: int) -> str:
pass
class A(BaseClass):
def foo(self, bar: int) -> str:
return f"aaaa {bar} bbbb"
class B(BaseClass):
def foo(self, bar: int) -> str:
return f"bbbbbb {bar} aaaaaa"
@mixin(TestCase)
class TestingBaseMixin:
TEST_CLASS: Type[BaseClass]
def setUp(self) -> None:
self.test_obj = self.TEST_CLASS("my_init_stuff")
def test_something(self) -> None:
self.assertIn(" 100 ", self.test_obj.foo(100))
class TestA(TestingBaseMixin, TestCase):
TEST_CLASS = A
class TestB(TestingBaseMixin, TestCase):
TEST_CLASS = B What would I expect from
Probably there are some edges cases here. Like what if you plan write a MixIn that can be applied to two different Bases? Another idea would be to implement something like Any suggestions on how to bring this topic (Propper type checks with MixIn from Third Party packages) forward? |
Years have passed ... and it is still challenging to get the type hints of mixins correctly (if possible at all). Are there any new approaches to type mixins? The solution @leonardon473 came up with seems to work with |
Since this was originally opened, protocols have been added to the type system. Here's a solution to Jukka's original problem using a protocol with a partial implementation. It enforces that the final class, which includes the mixin as a base class, implements the methods that are assumed by the mixin implementation. from typing import Protocol
class MyMixin(Protocol):
def do_stuff(self) -> str:
self.one_thing()
return self.second_thing(1)
# The following methods must be implemented by the final class.
def one_thing(self) -> None:
...
def second_thing(self, val: int) -> str:
...
class Thing(MyMixin):
def one_thing(self) -> None:
pass
def second_thing(self, val: int) -> str:
return "x" |
Indeed this is a little improvement but I still need to duplicate things, as I have to create a protocol for existing classes. And I have to manually verify the my protocol is identical to the external class I want to write an Mixin for. I could do something like from typing import TYPE_CHECKING, Protocol
from extralib import Extra
class MixinProto(Protocol):
foo: str
if TYPE_CHECKING:
_: MixinProto = Extra() # hopefully the class __init__ allows for that... But this is rather suboptimal and work intensive. And I don't see that
If the final class inherits our Mixin but doesn't provide the given attribute, there won't be any typing errors given... |
(This is an extension to #241 and copied from the discussion there.)
What about having a magic class decorator (with no or minimal runtime effect) or a base class that declares a class as a "pure mixin"? A pure mixin could only be used for implementation inheritance, and it wouldn't be type checked by itself at all -- it would only be type checked as part of implementation inheritance. I think that this would allow type checkers to check some mixin use cases that are tricky right now with minimal code changes. The alternative would be to use ABCs to declare the things that the mixin expects to be defined elsewhere, but they are harder to use and arguably add boilerplate.
Example:
The text was updated successfully, but these errors were encountered: