tortoise-orm icon indicating copy to clipboard operation
tortoise-orm copied to clipboard

Type definition for Field.__set__ does not expect None to be passed even if the field is nullable

Open zwx00 opened this issue 1 year ago • 3 comments

Describe the bug Trying to assign None to a field that has null=True gives a type error.

To Reproduce

import tortoise
from tortoise import fields

class Test(tortoise.Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255, null=True)

a = Test(name="test")
a.name = None

Resulting type checks:

git:(main) ✗ uv run mypy .      
test.py:12: error: Incompatible types in assignment (expression has type "None", variable has type "str")  [assignment]
Found 1 error in 1 file (checked 2 source files)
git:(main) ✗ uv run pyright .
/Users/zw/Development/tortoise-typebug/test.py
  /Users/zw/Development/tortoise-typebug/test.py:12:3 - error: Cannot assign to attribute "name" for class "Test"
    Argument of type "None" cannot be assigned to parameter "value" of type "str" in function "__set__"
      "None" is not assignable to "str" (reportAttributeAccessIssue)
1 error, 0 warnings, 0 informations 

Expected behavior Should be allowed to set a field to None. It's a legal value and the type definition should reflect this. Additional context Happy to help out with a PR if needed. Would appreciate some pointers.

https://github.com/tortoise/tortoise-orm/blob/develop/tortoise/fields/base.py#L165

Here's the problematic code. This is causing an issue because CharField is defined as follows:

class CharField(Field[str]):
    ...

I'm also including my current workaround which works but requires lot of copypaste boilerplate.

class RequiredUUIDField(fields.UUIDField):
    def __set__(self, instance: "Model", value: uuid.UUID) -> None:
        super().__set__(instance, value)


class NullableUUIDField(fields.UUIDField):
    def __set__(self, instance: "Model", value: Optional[uuid.UUID]) -> None:
        super().__set__(instance, value)  # type: ignore


@overload
def UUIDField(null: Literal[False] = False, **kwargs) -> RequiredUUIDField: ...


@overload
def UUIDField(null: Literal[True], **kwargs) -> NullableUUIDField: ...


def UUIDField(null: bool = False, **kwargs):
    if null:
        return NullableUUIDField(null=True, **kwargs)
    return RequiredUUIDField(**kwargs)

zwx00 avatar Feb 22 '25 17:02 zwx00

any update on this ? (is this planned ?) and if as the same time other type-hint issue could be fix like :

  • Type of "get_or_create" is partially unknown
  • "Meta" overrides symbol of same name in class "Model"
  • The values_list returning a tuple even tho flat is True
  • (i have others if needed) that would be incredible !

Lumabots avatar May 30 '25 17:05 Lumabots

Offtopic, but for Meta, you can fix the type error by importing and subclassing the parent Meta in your models.

zwx00 avatar May 30 '25 17:05 zwx00

Offtopic, but for Meta, you can fix the type error by importing and subclassing the parent Meta in your models.

both of them are related to strict typeing and i think that if a pr is made, its gonna fix all of the type hint issue and not only 1 Thanks tho

Lumabots avatar May 30 '25 19:05 Lumabots