No Django eu tenho personalizado QuerySet
e Manager
:
from django.db import models
class CustomQuerySet(models.QuerySet):
def live(self):
return self.filter(is_draft=False)
class CustomManager(models.Manager):
def publish(self, instance: "MyModel"):
instance.is_draft = False
instance.save()
No meu modelo, quero usar ambos, então uso from_queryset
o método:
class MyModel(models.Model):
objects: CustomManager = CustomManager().from_queryset(CustomQuerySet)()
is_draft = models.BooleanField(blank=True, default=True)
Como anotei objects
como CustomManager
, Pylance (via vscode) logicamente grita para mim que MyModel.objects.live()
está errado, devido a Cannot access attribute "live" for class "CustomManager" Attribute "live" is unknown
.
A remoção da anotação de tipo leva a uma reclamação semelhante: Cannot access attribute "live" for class "BaseManager[MyModel]" Attribute "live" is unknown
.
Como fazer anotações objects
para MyModel
que o Pylance saiba que objects
também há CustomQuerySet
métodos disponíveis, e não somente CustomManager
métodos?
Olhar para o código-fonte do Django from_queryset
constrói uma nova subclasse de CustomManager
iterando pelos CustomQuerySet
métodos:
@classmethod
def from_queryset(cls, queryset_class, class_name=None):
if class_name is None:
class_name = "%sFrom%s" % (cls.__name__, queryset_class.__name__)
return type(
class_name,
(cls,),
{
"_queryset_class": queryset_class,
**cls._get_queryset_methods(queryset_class),
},
)
Então, como @chepner apontou em seu comentário, obtemos um subtipo estrutural de CustomManager
, cujo _queryset_class
atributo é CustomQuerySet
. Então a questão fundamental é: como anotar o tipo dessa subclasse gerada dinamicamente de uma forma pelo menos boa o suficiente para que o verificador de tipos e o preenchimento automático funcionem?
As abordagens que analisei até agora são insatisfatórias:
- O Django não faz anotação de tipo no retorno de
from_queryset
. - Django-types o anota como
type[Self]
, que não possuiCustomQuerySet
métodos. - O mesmo com Django-stubs .
CustomManager.from_queryset(CustomQuerySet)
cria uma nova subclasse deCustomManager
em tempo de execução que copia um conjunto de atributos deCustomQuerySet
. Como tal, a solução "correta" seria definir um protocolo que enumera os atributos copiados pelo privateManager._get_queryset_methods
usado porfrom_queryset
.Como isso provavelmente dá muito trabalho (e é frágil, pois depende de detalhes de implementação privados que podem mudar), o método mais amplo e rápido que você propõe de fingir que o objeto é uma instância de ambos
CustomManager
podeCustomQuerySet
ser suficiente para suas necessidades.(Realmente,
from_queryset
parece executar um tipo de herança "estrutural", que pode não ser completa, mas boa o suficiente para permitir a mentira branca de que a nova classe é uma subclasse nominal deCustomQuerySet
.)Não parece fazer muito sentido colocá-lo
publish(..)
no nível de gerente, já que alguém pode querer publicar todos os tipos deQuerySet
s.Normalmente você define isso com
QuerySet
:e então você injeta
CustomQuerySet
com:Você pode então publicar um conjunto de itens com o
QuerySet
, como: