EN VI

Python - Type hint for return of dictionary with values derived from generic base class?

2024-03-10 01:30:05
How to Python - Type hint for return of dictionary with values derived from generic base class

Background

I'm writing a system for parsing a configuration file that contains different sections. Each section can have a range of entries, each of which has a different logic for how to parse the data in the config file. I've encapsulated each section into a class (all of which derive from a base section class). For each section class, I also have a companion parser class, which acts as a factory for that specific section. So each parser will receive the loaded configuration file, parse the its section and return the corresponding section class type.

In order to allow users to either append more sections to the file or override base sections, I need to return a table with all the base parsers. The idea is that the users can tack on more parsers for their custom sections, or override a specific parser by deriving from it and modifying the parsing code as needed. This can also include returning a derived class from the original section. To do this, I need to return a dictionary mapping the name of each parser to the corresponding parser. The issue I'm having is figuring out how to type hint this function correctly.

Minimal Example

Here's a simplified example of what I'm trying to do. First, the section classes:

@dataclass
class BaseSection:
    extra: int = 0

@dataclass
class ASection(BaseSection):
    a: int = 0

@dataclass
class BSection(BaseSection):
    b: int = 0

Now the parsers:

T = TypeVar("T", bound=BaseSection)

class BaseSectionParser(Generic[T]):
    def parse(self, data: str) -> Optional[T]:
        if len(data) == 0:
            return None

        return self._get_data(data)

    @abstractmethod
    def _get_data(self, data: str) -> T:
        pass

class ASectionParser(BaseSectionParser[ASection]):
    def _get_data(self, data: str) -> ASection:
        ret = ASection()
        if data == "a":
            ret.a = 1
        return ret

class BSectionParser(BaseSectionParser[BSection]):
    def _get_data(self, data: str) -> BSection:
        ret = BSection()
        if data == "b":
            ret.b = 2
        return ret

Finally, here's the function that returns the dictionary:

def get_parsers() -> dict: # <- What do I put here for a type hint?
    return {
            "a": ASectionParser,
            "b": BSectionParser,
            }

Things I've Tried

So far, I have tried the following for type hints:

  • dict, dict[str, Any], dict[str, type]: the simplest options. Mypy gives no errors, but obviously they contain no information about what the returned dictionary is. I know these work, but I'm wondering if there's a better way to type hint this.
  • dict[str, BaseSectionParser]: Mypy gives the same error for both lines error: Dict entry 0 has incompatible type "str": "type[ASectionParser]"; expected "str": "BaseSectionParser[Any]"
  • dict[str, BaseSectionParser[T]]: Mypy also gives errors, only difference is it changes the Any argument to T.

I'm honestly stumped on what to do here. How would I type hint this in a way that conveys the notion that the function returns a "dictionary of strings to types derived from BaseSectionParser"?

Edit 1

Expanded the tried type hints from dict to include dict[str, type] and dict[str, Any]

Solution:

What about this?

def get_parsers() -> dict[str, type[ASectionParser] | type[BSectionParser]]:
    return {
        "a": ASectionParser,
        "b": BSectionParser,
    }

or...

def get_parsers() -> dict[str, type[BaseSectionParser]]:
    return {
        "a": ASectionParser,
        "b": BSectionParser,
    }

Both options will give you correct type hints and won't raise any errors.

Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login