Oh, that’s exactly what we need.
Here’s my current version (going with protocol). And, it works perfectly for mypy (actually no, it does not throw error when assigning, but revealed types is wrong), with pyright throwing an error on a line i care about. But that’s probably problem on the side of pyright, thanks a lot!
from __future__ import annotations
from typing import Any, Generic, Iterable, Protocol, TypeVar, overload
from typing_extensions import reveal_type
ClassT = TypeVar("ClassT")
InstanceT = TypeVar("InstanceT")
class ClassOrInstanceAttribute(Protocol, Generic[ClassT, InstanceT]):
@overload
def __get__(self, obj: None, owner: type) -> ClassT: ...
@overload
def __get__(self, obj: object, owner: type) -> InstanceT: ...
def __set__(self, obj: object, value: InstanceT) -> None: ...
class TranslationField: ...
class FieldsAggregationMetaClass(type):
fields: ClassOrInstanceAttribute[Iterable[str], dict[str, set[TranslationField]]]
def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> type: ...
class TranslationOptions(metaclass=FieldsAggregationMetaClass):
def __init__(self) -> None:
...
# self.fields: dict[str, set[TranslationField]] = {f: set() for f in self.fields}
class BookTranslationOptions(TranslationOptions):
fields = ["name"] # This is a problematic line
reveal_type(BookTranslationOptions.fields)
reveal_type(BookTranslationOptions().fields)
Mypy:
types-test.py:10: error: Invariant type variable "ClassT" used in protocol where covariant one is expected [misc]
class ClassOrInstanceAttribute(Protocol, Generic[ClassT, InstanceT]):
^
types-test.py: note: In member "__new__" of class "FieldsAggregationMetaClass":
types-test.py:24: error: Missing return statement [empty-body]
def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> type: ...
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
types-test.py: note: At top level:
types-test.py:37: note: Revealed type is "typing.Iterable[builtins.str]"
types-test.py:38: note: Revealed type is "builtins.list[builtins.str]"
Pyright:
types-test.py:10:7 - warning: Type variable "ClassT" used in generic protocol "ClassOrInstanceAttribute" should be covariant (reportInvalidTypeVarUse)
types-test.py:34:14 - error: Expression of type "list[str]" is incompatible with declared type "ClassOrInstanceAttribute[Iterable[str], dict[str, set[TranslationField]]]"
"list[str]" is incompatible with protocol "ClassOrInstanceAttribute[Iterable[str], dict[str, set[TranslationField]]]"
"__get__" is not present
"__set__" is not present (reportAssignmentType)
types-test.py:37:13 - information: Type of "BookTranslationOptions.fields" is "Iterable[str]"
types-test.py:38:13 - information: Type of "BookTranslationOptions().fields" is "dict[str, set[TranslationField]]"
1 error, 1 warning, 2 informations