8000 Include keyword only args when generating signatures in stubgenc (#17… · python/mypy@e4de4e3 · GitHub
[go: up one dir, main page]

Skip to content

Commit e4de4e3

Browse files
Include keyword only args when generating signatures in stubgenc (#17448)
<!-- If this pull request fixes an issue, add "Fixes #NNN" with the issue number. --> Currently, signatures generated for callable by the `InspectionStubGenerator` won’t include keywords only arguments or their defaults. This change includes them in the generated signatures. <!-- Checklist: - Read the [Contributing Guidelines](https://github.com/python/mypy/blob/master/CONTRIBUTING.md) - Add tests for all changed behaviour. - If you can't add a test, please explain why and how you verified your changes work. - Make sure CI passes. - Please do not force push to the PR once it has been reviewed. --> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent a27447d commit e4de4e3

File tree

3 files changed

+79
-15
lines changed

3 files changed

+79
-15
lines changed

mypy/stubgenc.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import keyword
1313
import os.path
1414
from types import FunctionType, ModuleType
15-
from typing import Any, Mapping
15+
from typing import Any, Callable, Mapping
1616

1717
from mypy.fastparse import parse_type_comment
1818
from mypy.moduleinspect import is_c_module
@@ -292,6 +292,8 @@ def get_default_function_sig(self, func: object, ctx: FunctionContext) -> Functi
292292
varargs = argspec.varargs
293293
kwargs = argspec.varkw
294294
annotations = argspec.annotations
295+
kwonlyargs = argspec.kwonlyargs
296+
kwonlydefaults = argspec.kwonlydefaults
295297

296298
def get_annotation(key: str) -> str | None:
297299
if key not in annotations:
@@ -304,27 +306,51 @@ def get_annotation(key: str) -> str | None:
304306
return argtype
305307

306308
arglist: list[ArgSig] = []
309+
307310
# Add the arguments to the signature
308-
for i, arg in enumerate(args):
309-
# Check if the argument has a default value
310-
if defaults and i >= len(args) - len(defaults):
311-
default_value = defaults[i - (len(args) - len(defaults))]
312-
if arg in annotations:
313-
argtype = annotations[arg]
311+
def add_args(
312+
args: list[str], get_default_value: Callable[[int, str], object | None]
313+
) -> None:
314+
for i, arg in enumerate(args):
315+
# Check if the argument has a default value
316+
default_value = get_default_value(i, arg)
317+
if default_value is not None:
318+
if arg in annotations:
319+
argtype = annotations[arg]
320+
else:
321+
argtype = self.get_type_annotation(default_value)
322+
if argtype == "None":
323+
# None is not a useful annotation, but we can infer that the arg
324+
# is optional
325+
incomplete = self.add_name("_typeshed.Incomplete")
326+
argtype = f"{incomplete} | None"
327+
328+
arglist.append(ArgSig(arg, argtype, default=True))
314329
else:
315-
argtype = self.get_type_annotation(default_value)
316-
if argtype == "None":
317-
# None is not a useful annotation, but we can infer that the arg
318-
# is optional
319-
incomplete = self.add_name("_typeshed.Incomplete")
320-
argtype = f"{incomplete} | None"
321-
arglist.append(ArgSig(arg, argtype, default=True))
330+
arglist.append(ArgSig(arg, get_annotation(arg), default=False))
331+
332+
def get_pos_default(i: int, _arg: str) -> Any | None:
333+
if defaults and i >= len(args) - len(defaults):
334+
return defaults[i - (len(args) - len(defaults))]
322335
else:
323-
arglist.append(ArgSig(arg, get_annotation(arg), default=False))
336+
return None
337+
338+
add_args(args, get_pos_default)
324339

325340
# Add *args if present
326341
if varargs:
327342
arglist.append(ArgSig(f"*{varargs}", get_annotation(varargs)))
343+
# if we have keyword only args, then wee need to add "*"
344+
elif kwonlyargs:
345+
arglist.append(ArgSig("*"))
346+
347+
def get_kw_default(_i: int, arg: str) -> Any | None:
348+
if kwonlydefaults:
349+
return kwonlydefaults.get(arg)
350+
else:
351+
return None
352+
353+
add_args(kwonlyargs, get_kw_default)
328354

329355
# Add **kwargs if present
330356
if kwargs:

mypy/test/teststubgen.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,35 @@ class TestClassVariableCls:
845845
assert_equal(gen.get_imports().splitlines(), ["from typing import ClassVar"])
846846
assert_equal(output, ["class C:", " x: ClassVar[int] = ..."])
847847

848+
def test_non_c_generate_signature_with_kw_only_args(self) -> None:
849+
class TestClass:
850+
def test(
851+
self, arg0: str, *, keyword_only: str, keyword_only_with_default: int = 7
852+
) -> None:
853+
pass
854+
855+
output: list[str] = []
856+
mod = ModuleType(TestClass.__module__, "")
857+
gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod)
858+
gen.is_c_module = False
859+
gen.generate_function_stub(
860+
"test",
861+
TestClass.test,
862+
output=output,
863+
class_info=ClassInfo(
864+
self_var="self",
865+
cls=TestClass,
866+
name="TestClass",
867+
docstring=getattr(TestClass, "__doc__", None),
868+
),
869+
)
870+
assert_equal(
871+
output,
872+
[
873+
"def test(self, arg0: str, *, keyword_only: str, keyword_only_with_default: int = ...) -> None: ..."
874+
],
875+
)
876+
848877
def test_generate_c_type_inheritance(self) -> None:
849878
class TestClass(KeyError):
850879
pass

test-data/unit/stubgen.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,15 @@ def g(x, *, y=1, z=2): ...
361361
def f(x, *, y: int = 1) -> None: ...
362362
def g(x, *, y: int = 1, z: int = 2) -> None: ...
363363

364+
[case testKeywordOnlyArg_inspect]
365+
def f(x, *, y=1): ...
366+
def g(x, *, y=1, z=2): ...
367+
def h(x, *, y, z=2): ...
368+
[out]
369+
def f(x, *, y: int = ...): ...
370+
def g(x, *, y: int = ..., z: int = ...): ...
371+
def h(x, *, y, z: int = ...): ...
372+
364373
[case testProperty]
365374
class A:
366375
@property

0 commit comments

Comments
 (0)
0