Skip to content

← Getting Started with Tests and Type Checks step 4 of 13

Easy Framework

Find the Redundant Flags in a mypy Command

Your team's CI runs this command:

mypy --strict --warn-return-any --warn-unreachable src/

--warn-return-any does nothing here, because --strict already turns it on. --warn-unreachable adds a check that --strict leaves out. Write a function that sorts a list of mypy flags into those two groups, and reports any flag it does not know.

solve(["--strict", "--warn-return-any", "--warn-unreachable"])
# {
#     "redundant": frozenset({"--warn-return-any"}),
#     "additive": frozenset({"--warn-unreachable"}),
#     "unknown": frozenset(),
# }

The two tables

The starter defines two tables of flags. Use them as they are.

  • STRICT_IMPLIES holds the thirteen flags that --strict turns on, from the list in the previous article.
  • BEYOND_STRICT holds seven other flags.

A flag is known if it is --strict or is in either table. Final in the annotations tells mypy that these names are never reassigned.

What to write

explain_strict(flags) returns a pair, (redundant, additive):

  • Check the flags in input order. At the first unknown flag, raise UnknownFlagError(flag).
  • With --strict in the list, redundant holds the given flags that are in STRICT_IMPLIES. additive holds the rest, the flags that --strict does not turn on.
  • Without --strict, redundant is empty and additive holds every given flag.
  • Leave --strict itself out of both sets. A flag given twice appears once.

UnknownFlagError is a subclass of ValueError. Give it an __init__(self, flag: str) -> None that calls the parent __init__ and stores the flag as self.flag, so a caller can read exc.flag.

solve(flags) calls explain_strict and returns a dictionary with the keys "redundant", "additive" and "unknown". Each value is a frozenset[str].

  • On success, unknown is empty.
  • If explain_strict raises UnknownFlagError, return empty redundant and additive sets, and put only exc.flag in unknown.

Edge cases

  • An empty list gives three empty sets.
  • ["--strict"] on its own gives three empty sets.
  • If two flags are unknown, report the first one in the input.

What mypy checks here

Write frozenset[str], never a bare frozenset, in every annotation. A bare one fails with [type-arg]. Annotate __init__ too, or mypy reports [no-untyped-def].