Skip to content

Metaclass type/instance difficulties #3625

Closed
@rtpg

Description

@rtpg

Consider the following code from my hypothetical ORM that has three different ways of building an object

from typing import TypeVar, Type

T = TypeVar('T')


class ModelMeta(type):
    def build(cls: Type[T]) -> T:
        return cls()

    def build_other(cls):
        return cls()


class Model(metaclass=ModelMeta):

    @classmethod
    def new(cls: Type[T]) -> T:
       return cls()

I'm having trouble defining build in a way that type checks. In this example, I get an error on build about how Type[T] is not a supertype of the class ModelMeta.

build_other passes the type checker, but usages don't seem to catch anything (implicit Any perhaps?)

class Dog(Model):
    def __init__(self):
        print("Built Dog")


class Cat(Model):
    def __init__(self):
        print("Built Cat")


c: Cat = Dog.new()  # type check fails
c2: Cat = Dog.build()  # type check fails
c3: Cat = Dog.build_other()  # passes

Considering class methods work, it feels like metaclass methods should equally be able to "unwrap" the instance type for a class, but there might be subtleties I'm missing here.

My wish list here would be:

  • have the inverse of Type (something like Instance) so I could write something like def build(cls: T) -> Instance[T]. This would be (I think) a bit clearer.
  • Have an intersection type, with which I could declare something like
    def build(cls: (Type[T] & ModelMeta)) -> T. This would help with a lot of other problems as well. This seems like it would be a useful shortcut to writing subclasses
  • Have a magic hook in subtypes.is_subtype that would let me write a custom subtype check in some types

Full context: I'm writing some stubs for Django's ORM, and was hitting issues around the type of Model.objects.create. So in my case I end up needing a "class property" (hence doing things through the metaclass), which is why the simple new() call strategy hasn't been working for me.

I think this is related to #3438, but I'm having a hard time identifying the link.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions