1

My function definition contains both named and unnamed kwargs:

def safe_format(text: str, max_errors: int = 10, **kwargs: str) -> None:
    print(text)
    print(max_errors)
    print(kwargs)

If I call it without specifying max_errors:

safe_format(some_str, **some_dict)

I do get the expected result (some_str is printed, then 10, then some_dict). Yet mypy is unhappy and believes I'm trying to use some_dict as a value for max_errors:

Argument 2 to "safe_format" has incompatible type "**Dict[str, str]"; expected "int"

Is there a specific syntax I can use for mypy to recognize what I'm doing?

totooooo
  • 1,050
  • 1
  • 12
  • 32
  • Are you using Python3.8 or later? – AMK Aug 04 '23 at 13:50
  • You may use TypedDict if you have a certain set of keys. It resolves your problem – AMK Aug 04 '23 at 14:23
  • `safe_format` is intended to be used as a safe version of `.format` (inspired by this SO: https://stackoverflow.com/a/9955931/6710525), so the keys cannot be predicted – totooooo Aug 04 '23 at 15:25
  • 1
    So you may have to use typing.overload decorator – AMK Aug 04 '23 at 15:48
  • you could make max_errors a keyword only parameter. I would think that would satisfy mypy, but don't have the opportunity to test it right now – nigh_anxiety Aug 04 '23 at 18:56
  • Yes, you'll have to either (1) use `@typing.overload`, making an overload which omits `max_errors` and an overload which accepts it, or (2) bite the bullet and call `safe_format` with an argument even if it's the default value. – dROOOze Aug 04 '23 at 22:20
  • @totooooo re: _"`safe_format` is intended to be used as a safe version of `.format`"_, Are the keys not predictable because the format string is being provided by the user? If so, and if you're using `str.format` under the hood, do note that using [`str.format` with user provided templates can lead to security vulnerabilities](https://stackoverflow.com/q/76783239/11082165) – Brian61354270 Aug 11 '23 at 14:07
  • Thanks. Only admin users can create format strings but it's good to be aware of this vulnerability. I will implement a safer way – totooooo Aug 11 '23 at 15:21

1 Answers1

0

Based on the comments, I managed to make it work with typing.overload

from typing import overload


@overload
def safe_format(text: str, max_errors: int = 10, **kwargs: str) -> None:
    ...


@overload
def safe_format(text: str, max_errors=..., **kwargs: str) -> None:
    ...


def safe_format(text: str, max_errors: int = 10, **kwargs: str) -> None:
    print(text)
    print(max_errors)
    print(kwargs)


# mypy doesn't complain
safe_format("FOO", **{"kwarg1": "bar", "kwarg2": "BIGBAR"})
# mypy does complain because of kwarg1
safe_format("FOO", **{"kwarg1": 42, "kwarg2": "BIGBAR"})
totooooo
  • 1,050
  • 1
  • 12
  • 32