I have a class hierarchy. I'd like to be able to:
- avoid repeating base class constructor parameters in child classes
- create instances in Python code with intellisense hints for both child and parent constructor arguments
- create instances from a given dict (loaded from yaml config)
To satisfy 1, I can use **kwargs in child constructors but then I lose intellisense 2.
3 requires explicit type conversion on certain fields (str --> Dog.Breed, str --> int, etc.) in Python code so I can't just pass yaml dict as **kwargs. Additionally I have to essentially repeat parameters listed in __init__ method in each child class.
How can I achieve all 3 of the above code qualities Python?
from abc import ABC, abstractmethod
from enum import Enum
from typing import Dict
import pydoc
class Animal(ABC):
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age
    @abstractmethod
    def say(self) -> str:
        pass
class Dog(Animal):
    class Breed(Enum):
        Bulldog = 1
        Puddle = 2
    def __init__(
        self, breed: Breed, **kwargs
    ):  # Because of **kwargs, Intellisense won't know base class arguments
        super().__init__(**kwargs)
        self.breed = breed
    def say(self):
        return "woof"
    @classmethod
    def from_config(cls, config: Dict) -> "Dog":
        return cls(
            breed=cls.Breed[config["breed"]],
            name=config["name"],  # name and age should somehow be in the base class
            age=int(config["age"]),
        )
class Cat(Animal):
    def __init__(self, tail_length: float, **kwargs):
        super().__init__(**kwargs)
        self.tail_length = tail_length
    def say(self):
        return "meow"
    @classmethod
    def from_config(cls, config: Dict) -> "Cat":
        return cls(
            tail_length=config["tail_length"],
            name=config["name"],  # this code is duplicate from Dog class
            age=int(config["age"]),
        )
def test_from_config():
    config = {
        "name": "Charlie",
        "breed": "Bulldog",
        "age": 2,
    }
    dog = Dog.from_config(config)
    assert dog.name == "Charlie"
    assert dog.breed == Dog.Breed.Bulldog
def test_intellisense():
    assert "breed" in pydoc.render_doc(Dog)
    assert "breed" not in pydoc.render_doc(Cat)
    assert "tail_length" not in pydoc.render_doc(Dog)
    assert "tail_length" in pydoc.render_doc(Cat)
    assert "age" in pydoc.render_doc(Animal)
    assert "age" in pydoc.render_doc(Dog)
    assert "age" in pydoc.render_doc(Cat)
