1

For example this mixin:

from lib import stringlib

class NiceNameMixin:
    @property
    def nice_first_name(self):
        return stringlib.clean_name(self.first_name)

    @property
    def nice_last_name(self):
        return stringlib.clean_name(self.last_name)

warns me:

Cannot access member "first_name" for type "NiceNameMixin" Member "first_name" is unknown.

What is the correct was of telling the type checker that the attributes self.first_name and self.last_name will exist, and it's okay?

Note I normally just # type: ignore these kind of things, but I am starting to think there must be a more suitable way.

Note: Using Pyright

Update: Class property example for @chepner

from rest_framework import serializers

class UtilsMixin:
    @classproperty
    def model_class(cls: serializers.ModelSerializer):  # Type of parameter Cls must be a supertype of its class "UtilsMixin"
        return cls.Meta.model  # Cannot access member "Meta" for type "ModelSerializer"   Member "Meta" is unknown
run_the_race
  • 1,344
  • 2
  • 36
  • 62
  • 1
    If the required attributes don't exist, do you want an error at runtime when calling the methods, or when inheriting from `NiceNameMixin`? – chepner Oct 07 '22 at 12:33

2 Answers2

3

Adding to the excellent answer by @chepner. If one wants to express with types, that the child class has to implement the first_name and last_name attributes, consider annotating self with an appropriate typing.Protocol.

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Protocol


class HasFirstAndLastName(Protocol):
    first_name: str
    last_name: str


class NiceNameMixin(ABC):
    @property
    def nice_first_name(self: HasFirstAndLastName) -> str:
        return self.first_name.capitalize()

    @property
    def nice_last_name(self: HasFirstAndLastName):
        return self.last_name.capitalize()


@dataclass
class User(NiceNameMixin):
    first_name: str
    last_name: str


@dataclass
class User2(NiceNameMixin):
    first_name: str


User("John", "Wick").nice_first_name  # OK
User2("John").nice_first_name  # Err
Paweł Rubin
  • 2,030
  • 1
  • 14
  • 25
  • Alternatively, you can use `ABC` to declare `first_name` and `last_name` as abstract properties (attribute is definitely a drop-in replacement for property, being even wider, so `mypy` and other type checkers accept it too). – STerliakov Oct 07 '22 at 14:45
  • Nice way to do it. It works for this example, I tried to use for a class property and it did nto work, please see my update – run_the_race Oct 08 '22 at 07:39
1

You can simply add annotated, but uninitialized, names to the class:

class NiceNameMixin:
    first_name: str
    last_name: str

    @property
    def nice_first_name(self):
        return stringlib.clean_name(self.first_name)

    @property
    def nice_last_name(self):
        return stringlib.clean_name(self.last_name)

This does not guarantee that a class inheriting from NiceNameMixin will actually have those attributes, but it makes pyright happy with NiceNameMixin itself.

chepner
  • 497,756
  • 71
  • 530
  • 681