diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6eab884ca..1b17cec5d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Changelog Added ^^^^^ - Added psycopg backend support. +- Added __set_name__ support for custom fields Fixed ^^^^^ - Fix `bulk_create` doesn't work correctly with more than 1 update_fields. (#1046) diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index df2ef7cc2..0902d5534 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -50,6 +50,7 @@ Contributors * Vinay Karanam ``@vinayinvicible`` * Aleksandr Rozum ``@rozumalex`` * Mojix Coder ``@MojixCoder`` +* Rick van Hattem ``@wolph`` Special Thanks ============== diff --git a/tests/fields/subclass_fields.py b/tests/fields/subclass_fields.py index 8c8f6e6b6..f39d9af22 100644 --- a/tests/fields/subclass_fields.py +++ b/tests/fields/subclass_fields.py @@ -76,3 +76,13 @@ def to_python_value(self, value: Any) -> Any: return self.enum_type(value) except ValueError: raise ValueError(f"Database value {value} does not exist on Enum {self.enum_type}.") + + +class NamedField(CharField): + def __init__(self, name: str, **kwargs): + super().__init__(128, **kwargs) + self.name = name + + def __set_name__(self, owner: Type[Any], name: str) -> None: + if self.name != name: + raise RuntimeError(f"Field name {name} does not match {self.name}") diff --git a/tests/fields/subclass_models.py b/tests/fields/subclass_models.py index a99d5a68a..46675b643 100644 --- a/tests/fields/subclass_models.py +++ b/tests/fields/subclass_models.py @@ -1,6 +1,6 @@ from enum import Enum, IntEnum -from tests.fields.subclass_fields import EnumField, IntEnumField +from tests.fields.subclass_fields import EnumField, IntEnumField, NamedField from tortoise import fields from tortoise.models import Model @@ -29,3 +29,7 @@ class ContactTypeEnum(IntEnum): class Contact(Model): id = fields.IntField(pk=True) type = IntEnumField(ContactTypeEnum, default=ContactTypeEnum.other) + + +class NamedFields(Model): + a = NamedField("a") diff --git a/tortoise/fields/base.py b/tortoise/fields/base.py index f4a32ec37..7f04b753c 100644 --- a/tortoise/fields/base.py +++ b/tortoise/fields/base.py @@ -168,6 +168,15 @@ def __init__( self.model: Type["Model"] = model # type: ignore self.reference: "Optional[Field]" = None + def __set_name__(self, owner: Type[Any], name: str) -> None: + """ + Set the name of the field on the model and the model. + + Needed because Mypy is not yet __set_name__ aware: + https://github.com/python/mypy/issues/8057 + """ + ... + def to_db_value(self, value: Any, instance: "Union[Type[Model], Model]") -> Any: """ Converts from the Python type to the DB type. diff --git a/tortoise/models.py b/tortoise/models.py index af7cb9487..354238108 100644 --- a/tortoise/models.py +++ b/tortoise/models.py @@ -619,9 +619,14 @@ def __search_for_field_attributes(base: Type, attrs: dict) -> None: meta.abstract = True new_class = super().__new__(mcs, name, bases, attrs) - for field in meta.fields_map.values(): + for name, field in meta.fields_map.items(): field.model = new_class # type: ignore + # Call __set_name__ so fields know their own attribute name and parent model + # https://docs.python.org/3/reference/datamodel.html#object.__set_name__ + if hasattr(field, "__set_name__"): + field.__set_name__(new_class, name) + for fname, comment in _get_comments(new_class).items(): # type: ignore if fname in fields_map: fields_map[fname].docstring = comment