
I got a chance to play with @dataclass_transform and Django Models. Looks promising. Please see the following file - containing a lightweight reimplementation of Django's Model and Field classes - and its inline comments: # experiment.py import typing import typing_extensions class Field: # django.db.models.fields def __init__(self, *args, **kwargs): pass # NOTE: The following syntax is just a proposal. #@typing_extensions.dataclass_field_descriptor(value_type=str) class CharField(Field): # django.db.models.fields pass # NOTE: The following syntax is just a proposal. #@typing_extensions.dataclass_field_descriptor(value_type=int) class AutoField(Field): # django.db.models.fields pass @typing_extensions.dataclass_transform( # ⚠️ Forward-reference like 'Field' is NOT supported here, # but appears to be necessary in real Django field_descriptors=(Field,) ) class ModelBase(type): # django.db.models.base pass class Model(metaclass=ModelBase): # django.db.models.base # NOTE: Must explicitly define that an "id" AutoField exists by default. # # However sometimes it is actually a BigAutoField, depending on # the configuration of the Django app package that defines the # Model subclass. (However even if it IS a BigAutoField, it's # still of int type, which is convenient.) id: int = typing.cast(int, AutoField()) def __init__(self, *args, **kwargs): pass class Question(Model): # 😬 Must use cast() because otherwise pyright complains that # a CharField is not a str. # # 🌸 It would be nice if there was a way to derive that # a CharField(...) should result in a str, so you could just write: # question_text = CharField(max_length=200) # # Similarly a CharField(required=False, ...) should result # in an Optional[str]. question_text: str = typing.cast(str, CharField(max_length=200)) q = Question( id=1, # ✅ works, with "id" field defined on Model base class question_text=1, # ✅ error: "Literal[1]" cannot be assigned to "str" boom=2, # ✅ error: No parameter named "boom" ) Some patterns/comments: * dataclass_transform(field_descriptors=...) does not currently support string-based forward references to types, which it looks like Django will require. * It would be useful to have syntax to describe what the "value type" of a particular field descriptor is, so that I don't have to use cast()s everywhere. See the above proposed syntax: - @typing_extensions.dataclass_field_descriptor(value_type=...) * I managed to define an implicit "id" field by just putting it on Django's Model class directly. If this caused runtime problems I could additionally guard that field definition with an "if TYPE_CHECKING" block. I hope this experimental feedback is helpful! I'm very much looking forward to being able to use @dataclass_transform in real code. Best, --- David Foster | Seattle, WA, USA Contributor to TypedDict, mypy, and Python's typing system On 3/10/22 6:32 PM, Erik De Bonte wrote:
Thanks David.
we would need buy-in from a maintainer of attrs. Know anyone we could ask?
Yes, I emailed Hynek Schlawack about this yesterday.
I'm a Django contributor myself...What kinds of feedback are you looking for?
I was hoping someone could try decorating the relevant functions/classes in Django with dataclass_transform and determine if the resulting user experience is acceptable.
Btw, Carlton Gibson told me that Django's Field.default can be either a value or a callable, whereas the PEP expects it to be a value. Factory callables need to be provided via the default_factory parameter instead. So that's one issue, but I think as with the attrs hash/unsafe_hash issue, this would be best fixed by Django adding a default_factory (or factory) property.
-Erik