Alexander Myskin
Alexander Myskin

Reputation: 563

Type hints for class attribute

I have a web application with many models and many class based views. The most part of code looks like this

from typing import TypeVar, Type

M = TypeVar('M', bound='Model')
TypeModel = Type[M]


# ----------  models
class Model:
    @classmethod
    def factory(cls: TypeModel) -> M:
        return cls()


class ModelOne(Model):
    def one(self):
        return


class ModelTwo(Model):
    def two(self):
        return


# ----------  views
class BaseView:
    model: TypeModel

    @property
    def obj(self) -> M:
        return self.model.factory()

    def logic(self):
        raise NotImplementedError


class One(BaseView):
    model = ModelOne

    def logic(self):
        self.obj.  # how can i get suggest of methods of ModelOne here?
        ...


class Two(BaseView):
    model = ModelTwo

    def logic(self):
        self.obj.  # how can i get suggest of methods of ModelTwo here?
        ...

I want to have a property obj which is instance of specified model in view. How can I achieve this? Thank you

Upvotes: 2

Views: 2238

Answers (1)

Michael0x2a
Michael0x2a

Reputation: 63978

You need to make your BaseView class generic with respect to M. So, you should do something like this:

from typing import TypeVar, Type, Generic

M = TypeVar('M', bound='Model')

# Models

class Model:
    @classmethod
    def factory(cls: Type[M]) -> M:
        return cls()

class ModelOne(Model):
    def one(self):
        return

class ModelTwo(Model):
    def two(self):
        return

# Views

# A BaseView is now a generic type and will use M as a placeholder.
class BaseView(Generic[M]):
    model: Type[M]

    @property
    def obj(self) -> M:
        return self.model.factory()

    def logic(self):
        raise NotImplementedError

# The subclasses now specify what kind of model the BaseView should be
# working against when they subclass it.
class One(BaseView[ModelOne]):
    model = ModelOne

    def logic(self):
        self.obj.one()

class Two(BaseView[ModelTwo]):
    model = ModelTwo

    def logic(self):
        self.obj.two()

One note: I got rid of your TypeModel type alias. This is partly stylistic and partly pragmatic.

Stylistically, when I look at a type signature, I want to be able to immediately determine whether or not it's using generics/typevars or not. Using type aliases tends to obscure that/I don't really like using context-sensitive types.

Pragmatically, both PyCharm's type checker and mypy tend to struggle a little when you make excessive use of type aliases containing typevars.

Upvotes: 3

Related Questions