Reputation: 6025
Let's say I have the following Django models:
class Toolbox(models.Model):
name = models.CharField(max_length=255)
tools = models.ManyToManyField("Tool")
class Tool(models.Model):
class Size(models.TextChoices):
SMALL = "S"
MEDIUM = "M"
LARGE = "L"
name = models.CharField(max_length=255)
size = models.CharField(max_length=10, choices=Size.choices)
I have a function to get all small tools for each toolbox. The argument type hint comes from this SO answer:
from django.db.models import QuerySet
def get_toolbox_to_small_tools_mappings(
toolboxes: QuerySet | list[Toolbox],
) -> dict[Toolbox, list[Tool]]:
return {toolbox: toolbox.small_tools for toolbox in toolboxes}
The idea here is to require users of this function to prefetch this small_tools
field using prefetch_related()
to reduce the number of db hits and speed up the code:
toolboxes = Toolbox.objects.prefetch_related(
Prefetch(
"tools",
queryset=Tool.objects.filter(size=Tool.Size.SMALL),
to_attr="small_tools",
)
)
toolbox_to_small_tools_mappings = get_toolbox_to_small_tools_mappings(toolboxes)
This all works great but mypy is complaining with the following error:
error: "Toolbox" has no attribute "small_tools" [attr-defined]
Is there anyway to fix this?
The WithAnnotations[Model]
type from django-subs (see here) is an option but it's buggy.
Upvotes: 6
Views: 4374
Reputation: 10709
Python 3.9 introduces typing.Annotated e.g. typing.Annotated[Toolbox, "WithTools"]
.
So for scenarios where we just need to indicate it for clarity that the object is annotated:
from typing import Annotated
from django.db.models import QuerySet
def get_toolbox_to_small_tools_mappings(
toolboxes: QuerySet | list[Annotated[Toolbox, "WithTools"]],
) -> dict[Annotated[Toolbox, "WithTools"], list[Tool]]:
...
Upvotes: -1
Reputation: 6373
Your best options may be to use type annotations from django-stubs or disable annotations (type: ignore).
One alternative option is to switch to using getattr
, but this has down-sides (see below).
from django.db.models import QuerySet
def get_toolbox_to_small_tools_mappings(
toolboxes: QuerySet | list[Toolbox],
) -> dict[Toolbox, list[Tool]]:
return {toolbox: getattr(toolbox, "small_tools") for toolbox in toolboxes}
This keeps things simple, and maintains the same functionality, but at the cost of affecting refactoring tools and other developer IDE functionality that won't be able to translate this code.
Upvotes: -1