Search code examples
pythontypesdynamicsqlalchemypycharm

Type hinting in Pycharm for dynamically generated classes


I'm trying to get Pycharm's linter to stop complaining when I use a dynamically created class via the Python type( ) function. A simplified version of the actual code looks like the following.

Yes, it's weird to be working with the class object like that, but in the real code, it makes sense (SQLAlchemy ORM classes, if you're curious). Note the final line which points out the Pycharm warning.

class MyClass(object):
    ID_NUM: int = 0

    def __init__(self, id_num: int, first_name: str, last_name: str):
        self.ID_NUM = id_num
        self.first_name = first_name
        self.last_name = last_name


def make_augmented_class(cls):
    def augmented__eq__(self, other):
        try:
            return self.id_num == other.id_num
        except AttributeError:
            return False

    new_cls = type('{}Augmented'.format(cls.__name__), tuple([cls]), {})
    new_cls.__eq__ = augmented__eq__
    return new_cls


def do_stuff(my_class):
    print(my_class.ID_NUM)


if __name__ == '__main__':
    do_stuff(MyClass)

    augmented_class = make_augmented_class(MyClass)
    do_stuff(augmented_class)  # <=== PYCHARM CODE LINTER COMPLAINS THAT "Type 'type' doesn't have expected attribute 'ID_NUM'"

I've tried a couple of type hints for the do_stuff( ) function, such as def do_stuff(my_class: type): and def do_stuff(my_class: MyClass):, but that just causes different linter warnings.

The code works, but I'd like to eliminate the linter warnings somehow...


Solution

  • There is no good reason to use the type function to create a class for your use case. Instead, simply create the new class in the closure of the function and return the class object so that the linter can more easily infer that the new class is a subclass of the given class. That said, I found that you would still need to type hint the argument and the returning value of the function in order for PyCharm not to complain:

    from typing import TypeVar
    
    T = TypeVar('T')
    
    def make_augmented_class(cls: type[T]) -> type[T]:
        class _AugmentingWrapper(cls):
            def __eq__(self, other):
                try:
                    return self.ID_NUM == other.ID_NUM
                except AttributeError:
                    return False
        return _AugmentingWrapper