-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
"Dynamic metaclass not supported" false positive on generics #11672
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
Comments
I am not sure that generic metaclasses is even a thing 🤔 |
pyright supports it and it works at runtime, so i see no reason these two features shouldn't work together for this to emerge. from typing import TYPE_CHECKING, Generic, TypeVar
T = TypeVar("T")
class FooMetaclass(type, Generic[T]):
value: T
class Foo(metaclass=FooMetaclass[int]): # mypy error, no pyright error
@classmethod
def foo(cls) -> None:
if TYPE_CHECKING:
reveal_type(cls.value) # pyright says `int`, mypy says `Any`
cls.value = 1
print(cls.value)
Foo.foo() # prints 1
print(Foo.__orig_class__) # __main__.FooMetaclass[int] |
Pyright should generate an error in the first example above. The fact that it doesn't is an oversight / bug. Pyright does support the use of a specialized metaclass, as in your second example above. I don't see any reason why this shouldn't work from a type-checking standpoint. That said, it's a use case that isn't well tested, so you may run into issues. There's an interesting case not covered in either of the above two samples: from typing import TypeVar, Generic
T = TypeVar("T")
class FooMetaclass(type, Generic[T]):
pass
class Foo(Generic[T], metaclass=FooMetaclass[T]):
pass In this variant, TypeVar @DetachHead, is there a real use case here, or are you just playing around with ideas? |
yeah i found this issue: class FooMetaclass(type, Generic[T]):
metaclass_value: T
class Foo(metaclass=FooMetaclass[int]):
value: int
# wrong error message:
# Cannot assign member "value" for type "Type[Foo]"
# Member "value" is unknown
Foo.metaclass_value = "1"
# it should be more like this error instead:
# Cannot assign member "value" for type "Type[Foo]"
# Expression of type "Literal['1']" cannot be assigned to member "value" of class "Foo"
# "Literal['1']" is incompatible with "int"
Foo.value = "1"
yeah that's what i intended to put as my original example, i just forgot to include however it doesn't seem to work in this case: class FooMetaclass(type, Generic[T]):
metaclass_value: T
class Foo(Generic[T], metaclass=FooMetaclass[T]):
value: T
# should be `int` but is still the unbound generic
reveal_type(Foo[int].metaclass_value) # Type of "Foo[int].metaclass_value" is "T@Foo"
Foo[int].metaclass_value = 1 # error
Foo[int].value = 1 # no error
i stubled across this while trying to learn how metaclasses work, but i was basically trying to make a class _ReifiedGenericMetaclass(Generic[T], type):
__generics__: T
def __instancecheck__(
cls,
obj: object,
) -> bool:
...
class ReifiedGeneric(Generic[T], metaclass=_ReifiedGenericMetaclass[T]):
def __class_getitem__(
cls,
item: T,
) -> type[ReifiedGeneric[T]]:
... |
Those dymanic metaclasses would be really useful for from typing import Generic, TypeVar
T = TypeVar("T")
class FactoryMetaClass(type):
"""Factory metaclass for handling ordered declarations."""
def __call__(cls, **kwargs):
"""Override the default Factory() syntax to call the default strategy.
Returns an instance of the associated class.
"""
return cls.create(**kwargs) # type: ignore
def __new__(cls, class_name, bases, attrs):
return super().__new__(cls, class_name, bases, attrs)
class BaseFactory(Generic[T]):
"""Factory base support for sequences, attributes and stubs."""
@classmethod
def create(cls, **kwargs) -> T:
...
class Factory(BaseFactory[T], metaclass=FactoryMetaClass):
"""Factory base with build and create support.
This class has the ability to support multiple ORMs by using custom creation
functions.
"""
# ------------------------------
class Book:
name: str
author: str
class BookFactory(Factory[Book]):
...
book1 = BookFactory.create()
reveal_type(book1)
book2 = BookFactory()
reveal_type(book2) Both mypy and pyright correctly handling first case, but i have no idea how to get it to return
@erictraut maybe you have idea how to solve this without generic metaclasses? |
Yeah, use classes rather than metaclasses. This is the normal way to implement the factory pattern. from typing import Generic, TypeVar
T = TypeVar("T")
class Factory(Generic[T]):
def __init__(self, cls: type[T]):
self.cls = cls
def create(self, *args, **kwargs):
return self.cls(*args, **kwargs)
def __call__(self, *args, **kwargs):
return self.create(*args, **kwargs)
class Book:
name: str
author: str
BookFactory = Factory(Book)
book1 = BookFactory.create()
reveal_type(book1)
book2 = BookFactory()
reveal_type(book2) |
That's not how FactoryBoy works. It works like this: class BookFactory(factory.Factory):
...
book1 = Bookfactory() You don't instantiate the factory. |
I need from functools import cached_property
from typing import Callable, Literal, LiteralString, Type
class SetFunc[T: LiteralString]:
def __init__(self, id: T):
self.id = id
def __call__(self, func: Callable):
...
return func
class META_A[T: LiteralString](type):
@cached_property
def set_func(cls) -> Type[SetFunc[T]]:
...# extend SetFunc and change META_A class attributes in __init__ of SetFunc
return SetFunc
class A[T: LiteralString](metaclass=META_A[T]):
pass
class B(A[Literal["1", "2", "3"]]):
pass
# type checking for id does not work with pyright
@B.set_func("1")
def func():
pass |
https://mypy-play.net/?mypy=master&python=3.10&gist=d4b169a5ece08ed0607b19fff8e9b749
according to the documentation:
however this isn't dynamically computed, it's just a generic so mypy should be able to understand it statically
The text was updated successfully, but these errors were encountered: