Skip to content
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ I’d also like to thank my employer, Dropbox, for supporting mypy development.

Posted by Wesley Collin Wright

Stubtest will ignore private function/method parameters when they are missing from the stub. Private parameters
names start with a single underscore and have a default (PR [16507](https://github.com/python/mypy/pull/16507)).

## Mypy 1.7

We’ve just uploaded mypy 1.7 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows:
Expand Down
14 changes: 12 additions & 2 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,8 @@ def _verify_signature(
elif len(stub.pos) < len(runtime.pos):
for runtime_arg in runtime.pos[len(stub.pos) :]:
if runtime_arg.name not in stub.kwonly:
yield f'stub does not have argument "{runtime_arg.name}"'
if not _is_private_parameter(runtime_arg):
yield f'stub does not have argument "{runtime_arg.name}"'
Comment thread
srittau marked this conversation as resolved.
else:
yield f'runtime argument "{runtime_arg.name}" is not keyword-only'

Expand Down Expand Up @@ -976,7 +977,8 @@ def _verify_signature(
):
yield f'stub argument "{arg}" is not keyword-only'
else:
yield f'stub does not have argument "{arg}"'
if not _is_private_parameter(runtime.kwonly[arg]):
yield f'stub does not have argument "{arg}"'

# Checks involving **kwargs
if stub.varkw is None and runtime.varkw is not None:
Expand All @@ -991,6 +993,14 @@ def _verify_signature(
yield f'runtime does not have **kwargs argument "{stub.varkw.variable.name}"'


def _is_private_parameter(arg: inspect.Parameter) -> bool:
return (
arg.name.startswith("_")
and not arg.name.startswith("__")
and arg.default is not inspect.Parameter.empty
)


@verify.register(nodes.FuncItem)
def verify_funcitem(
stub: nodes.FuncItem, runtime: MaybeMissing[Any], object_path: list[str]
Expand Down
72 changes: 72 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,78 @@ def test_arg_kind(self) -> Iterator[Case]:
error="stub_posonly_570",
)

@collect_cases
def test_private_parameters(self) -> Iterator[Case]:
# Private parameters can optionally be omitted.
yield Case(
stub="def priv_pos_arg_missing() -> None: ...",
runtime="def priv_pos_arg_missing(_p1=None): pass",
error=None,
)
yield Case(
stub="def multi_priv_args() -> None: ...",
runtime="def multi_priv_args(_p='', _q=''): pass",
error=None,
)
yield Case(
stub="def priv_kwarg_missing() -> None: ...",
runtime="def priv_kwarg_missing(*, _p2=''): pass",
error=None,
)
# But if they are included, they must be correct.
yield Case(
stub="def priv_pos_arg_wrong(_p: int = ...) -> None: ...",
runtime="def priv_pos_arg_wrong(_p=None): pass",
error="priv_pos_arg_wrong",
)
yield Case(
stub="def priv_kwarg_wrong(*, _p: int = ...) -> None: ...",
runtime="def priv_kwarg_wrong(*, _p=None): pass",
error="priv_kwarg_wrong",
)
# Private parameters must have a default and start with exactly one
# underscore.
yield Case(
stub="def pos_arg_no_default() -> None: ...",
runtime="def pos_arg_no_default(_np): pass",
error="pos_arg_no_default",
)
yield Case(
stub="def kwarg_no_default() -> None: ...",
runtime="def kwarg_no_default(*, _np): pass",
error="kwarg_no_default",
)
yield Case(
stub="def double_underscore_pos_arg() -> None: ...",
runtime="def double_underscore_pos_arg(__np = None): pass",
error="double_underscore_pos_arg",
)
yield Case(
stub="def double_underscore_kwarg() -> None: ...",
runtime="def double_underscore_kwarg(*, __np = None): pass",
error="double_underscore_kwarg",
)
# But spot parameters that are accidentally not marked kw-only and
# vice-versa.
yield Case(
stub="def priv_arg_is_kwonly(_p=...) -> None: ...",
runtime="def priv_arg_is_kwonly(*, _p=''): pass",
error="priv_arg_is_kwonly",
)
yield Case(
stub="def priv_arg_is_positional(*, _p=...) -> None: ...",
runtime="def priv_arg_is_positional(_p=''): pass",
error="priv_arg_is_positional",
)
# Private parameters not at the end of the parameter list must be
# included so that users can pass the following arguments using
# positional syntax.
yield Case(
stub="def priv_args_not_at_end(*, q='') -> None: ...",
runtime="def priv_args_not_at_end(_p='', q=''): pass",
error="priv_args_not_at_end",
)

@collect_cases
def test_default_presence(self) -> Iterator[Case]:
yield Case(
Expand Down