AlexLordThorsen
AlexLordThorsen

Reputation: 8498

Mypy Optional dict error with an expected type that's not optional

I have a __init__ function that's able to build a correct object from three different paths. Since some of the arguments can be reused they have defaults in the top level init.

I'm unsure how to tell mypy that the given arguments are alright being optional at the top level init function and required for the given correct paths.

common_record.py:138: error: Argument 1 to "_init_from_common_record" of "CommonRecord" has incompatible type "Optional[Dict[Any, Any]]"; expected "Dict[Any, Any]"
common_record.py:142: error: Argument 1 to "_init_from_raw_data" of "CommonRecord" has incompatible type "Optional[Dict[Any, Any]]"; expected "Dict[Any, Any]"
Makefile:74: recipe for target 'type-check' failed
make: *** [type-check] Error 1
class CommonRecord:
    """A Common Record type. This is a json serializable object that contains
    sections for required and known fields that are common among data sources.
    """
    def __init__(
            self,
            record: Dict = None,
            raw_record: Dict = None,
            *,
            system: System = None,
            domain: Domain = None) -> None:
        """Initialization for the Common Record Class

        Three valid creation cases:
            * A single dictionary indicating a dictionary that's of the Common
            Record type
            * A normalized record and a raw record that will be turned into a
            Common Record.
            * A System object, a Domain object, and a raw record dictionary.
        """
        if not raw_record:
            self._init_from_common_record(record)
        elif (system and domain and raw_record):
            self._init_from_objects(system, domain, raw_record)
        else:
            self._init_from_raw_data(record, raw_record)

With the signatures of the init functions being

def _init_from_raw_data(self, record: Dict, raw_record: Dict) -> None:
  def _init_from_objects(
            self,
            system: System,
            domain: Domain,
            raw_record: Dict) -> None:
def _init_from_common_record(self, common_record: Dict) -> None:

Upvotes: 1

Views: 2098

Answers (1)

Michael0x2a
Michael0x2a

Reputation: 64228

There are three different approaches you can take.

First, you could modify your conditionals to explicitly check if record is None and do something like the following.

if not raw_record and not record:
    self._init_from_common_record(record)
elif (system and domain and raw_record):
    self._init_from_objects(system, domain, raw_record)
elif not record:
    self._init_from_raw_data(record, raw_record)
else:
    # Raise an exception here or something

Second, you could add in asserts that check to make sure record is not None.

if not raw_record:
    assert record is not None
    self._init_from_common_record(record)
elif (system and domain and raw_record):
    self._init_from_objects(system, domain, raw_record)
else:
    assert record is not None
    self._init_from_raw_data(record, raw_record)

The third solution is to cast record to the correct type and skip the checks altogether. I don't recommend this approach though -- verifying your object is being used correctly is going to be the more robust thing to do.


One additional but somewhat unrelated improvement you could also make is to refine the signature of your constructor using overloads -- basically create one overload per each method of constructing your CommonRecord. This would help verify that you're always constructing your object correctly and "teach" mypy how to verify some of the runtime checks we're doing above at type check time.

But you'd still need to do one of the three methods suggested above if you want your actual implementation to typecheck properly.


One additional thing you could do is sidestep the problem entirely by converting two of your private initialization methods into static methods that will construct and return a new instance of your CommonRecord.

This would let you potentially simplify the constructor and help you make your types more precise. The main downside, of course, is that instantiating a new CommonRecord now becomes slightly more clunky.

Upvotes: 2

Related Questions