Skip to content

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

Open
@zwx00

Description

@zwx00

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions