mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #7716 from tk0miya/3610_support_overload
Close #3610: autodoc: Support overloaded functions
This commit is contained in:
commit
bd2caee82f
1
CHANGES
1
CHANGES
@ -51,6 +51,7 @@ Features added
|
||||
builtin base classes
|
||||
* #2106: autodoc: Support multiple signatures on docstring
|
||||
* #4422: autodoc: Support GenericAlias in Python 3.7 or above
|
||||
* #3610: autodoc: Support overloaded functions
|
||||
* #7466: autosummary: headings in generated documents are not translated
|
||||
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
|
||||
caption to the toctree
|
||||
|
@ -1192,8 +1192,14 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
self.add_line(' :async:', sourcename)
|
||||
|
||||
def format_signature(self, **kwargs: Any) -> str:
|
||||
sig = super().format_signature(**kwargs)
|
||||
sigs = [sig]
|
||||
sigs = []
|
||||
if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads:
|
||||
# Use signatures for overloaded functions instead of the implementation function.
|
||||
overloaded = True
|
||||
else:
|
||||
overloaded = False
|
||||
sig = super().format_signature(**kwargs)
|
||||
sigs.append(sig)
|
||||
|
||||
if inspect.is_singledispatch_function(self.object):
|
||||
# append signature of singledispatch'ed functions
|
||||
@ -1207,6 +1213,10 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
documenter.object = func
|
||||
documenter.objpath = [None]
|
||||
sigs.append(documenter.format_signature())
|
||||
if overloaded:
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
sig = stringify_signature(overload, **kwargs)
|
||||
sigs.append(sig)
|
||||
|
||||
return "\n".join(sigs)
|
||||
|
||||
@ -1269,6 +1279,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
'private-members': bool_option, 'special-members': members_option,
|
||||
} # type: Dict[str, Callable]
|
||||
|
||||
_signature_class = None # type: Any
|
||||
_signature_method_name = None # type: str
|
||||
|
||||
def __init__(self, *args: Any) -> None:
|
||||
super().__init__(*args)
|
||||
merge_special_members_option(self.options)
|
||||
@ -1289,7 +1302,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
self.doc_as_attr = True
|
||||
return ret
|
||||
|
||||
def _get_signature(self) -> Optional[Signature]:
|
||||
def _get_signature(self) -> Tuple[Optional[Any], Optional[str], Optional[Signature]]:
|
||||
def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
|
||||
""" Get the `attr` function or method from `obj`, if it is user-defined. """
|
||||
if inspect.is_builtin_class_method(obj, attr):
|
||||
@ -1313,7 +1326,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if call is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', call, True)
|
||||
try:
|
||||
return inspect.signature(call, bound_method=True)
|
||||
sig = inspect.signature(call, bound_method=True)
|
||||
return type(self.object), '__call__', sig
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@ -1322,7 +1336,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if new is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', new, True)
|
||||
try:
|
||||
return inspect.signature(new, bound_method=True)
|
||||
sig = inspect.signature(new, bound_method=True)
|
||||
return self.object, '__new__', sig
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@ -1331,7 +1346,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if init is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', init, True)
|
||||
try:
|
||||
return inspect.signature(init, bound_method=True)
|
||||
sig = inspect.signature(init, bound_method=True)
|
||||
return self.object, '__init__', sig
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@ -1341,20 +1357,21 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
# the signature from, so just pass the object itself to our hook.
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
||||
try:
|
||||
return inspect.signature(self.object, bound_method=False)
|
||||
sig = inspect.signature(self.object, bound_method=False)
|
||||
return None, None, sig
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Still no signature: happens e.g. for old-style classes
|
||||
# with __init__ in C and no `__text_signature__`.
|
||||
return None
|
||||
return None, None, None
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
if self.env.config.autodoc_typehints in ('none', 'description'):
|
||||
kwargs.setdefault('show_annotation', False)
|
||||
|
||||
try:
|
||||
sig = self._get_signature()
|
||||
self._signature_class, self._signature_method_name, sig = self._get_signature()
|
||||
except TypeError as exc:
|
||||
# __signature__ attribute contained junk
|
||||
logger.warning(__("Failed to get a constructor signature for %s: %s"),
|
||||
@ -1370,7 +1387,30 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if self.doc_as_attr:
|
||||
return ''
|
||||
|
||||
return super().format_signature(**kwargs)
|
||||
sig = super().format_signature()
|
||||
|
||||
overloaded = False
|
||||
qualname = None
|
||||
# TODO: recreate analyzer for the module of class (To be clear, owner of the method)
|
||||
if self._signature_class and self._signature_method_name and self.analyzer:
|
||||
qualname = '.'.join([self._signature_class.__qualname__,
|
||||
self._signature_method_name])
|
||||
if qualname in self.analyzer.overloads:
|
||||
overloaded = True
|
||||
|
||||
sigs = []
|
||||
if overloaded:
|
||||
# Use signatures for overloaded methods instead of the implementation method.
|
||||
for overload in self.analyzer.overloads.get(qualname):
|
||||
parameters = list(overload.parameters.values())
|
||||
overload = overload.replace(parameters=parameters[1:],
|
||||
return_annotation=Parameter.empty)
|
||||
sig = stringify_signature(overload, **kwargs)
|
||||
sigs.append(sig)
|
||||
else:
|
||||
sigs.append(sig)
|
||||
|
||||
return "\n".join(sigs)
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
sourcename = self.get_sourcename()
|
||||
@ -1693,8 +1733,14 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
pass
|
||||
|
||||
def format_signature(self, **kwargs: Any) -> str:
|
||||
sig = super().format_signature(**kwargs)
|
||||
sigs = [sig]
|
||||
sigs = []
|
||||
if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads:
|
||||
# Use signatures for overloaded methods instead of the implementation method.
|
||||
overloaded = True
|
||||
else:
|
||||
overloaded = False
|
||||
sig = super().format_signature(**kwargs)
|
||||
sigs.append(sig)
|
||||
|
||||
meth = self.parent.__dict__.get(self.objpath[-1])
|
||||
if inspect.is_singledispatch_method(meth):
|
||||
@ -1710,6 +1756,14 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
documenter.object = func
|
||||
documenter.objpath = [None]
|
||||
sigs.append(documenter.format_signature())
|
||||
if overloaded:
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
if not inspect.isstaticmethod(self.object, cls=self.parent,
|
||||
name=self.object_name):
|
||||
parameters = list(overload.parameters.values())
|
||||
overload = overload.replace(parameters=parameters[1:])
|
||||
sig = stringify_signature(overload, **kwargs)
|
||||
sigs.append(sig)
|
||||
|
||||
return "\n".join(sigs)
|
||||
|
||||
|
@ -12,6 +12,7 @@ import re
|
||||
import tokenize
|
||||
import warnings
|
||||
from importlib import import_module
|
||||
from inspect import Signature
|
||||
from io import StringIO
|
||||
from os import path
|
||||
from typing import Any, Dict, IO, List, Tuple, Optional
|
||||
@ -145,6 +146,7 @@ class ModuleAnalyzer:
|
||||
self.annotations = None # type: Dict[Tuple[str, str], str]
|
||||
self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
|
||||
self.finals = None # type: List[str]
|
||||
self.overloads = None # type: Dict[str, List[Signature]]
|
||||
self.tagorder = None # type: Dict[str, int]
|
||||
self.tags = None # type: Dict[str, Tuple[str, int, int]]
|
||||
|
||||
@ -163,6 +165,7 @@ class ModuleAnalyzer:
|
||||
|
||||
self.annotations = parser.annotations
|
||||
self.finals = parser.finals
|
||||
self.overloads = parser.overloads
|
||||
self.tags = parser.definitions
|
||||
self.tagorder = parser.deforders
|
||||
except Exception as exc:
|
||||
|
@ -12,12 +12,14 @@ import itertools
|
||||
import re
|
||||
import sys
|
||||
import tokenize
|
||||
from inspect import Signature
|
||||
from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
|
||||
from tokenize import COMMENT, NL
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sphinx.pycode.ast import ast # for py37 or older
|
||||
from sphinx.pycode.ast import parse, unparse
|
||||
from sphinx.util.inspect import signature_from_ast
|
||||
|
||||
|
||||
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
|
||||
@ -232,8 +234,10 @@ class VariableCommentPicker(ast.NodeVisitor):
|
||||
self.previous = None # type: ast.AST
|
||||
self.deforders = {} # type: Dict[str, int]
|
||||
self.finals = [] # type: List[str]
|
||||
self.overloads = {} # type: Dict[str, List[Signature]]
|
||||
self.typing = None # type: str
|
||||
self.typing_final = None # type: str
|
||||
self.typing_overload = None # type: str
|
||||
super().__init__()
|
||||
|
||||
def get_qualname_for(self, name: str) -> Optional[List[str]]:
|
||||
@ -257,6 +261,12 @@ class VariableCommentPicker(ast.NodeVisitor):
|
||||
if qualname:
|
||||
self.finals.append(".".join(qualname))
|
||||
|
||||
def add_overload_entry(self, func: ast.FunctionDef) -> None:
|
||||
qualname = self.get_qualname_for(func.name)
|
||||
if qualname:
|
||||
overloads = self.overloads.setdefault(".".join(qualname), [])
|
||||
overloads.append(signature_from_ast(func))
|
||||
|
||||
def add_variable_comment(self, name: str, comment: str) -> None:
|
||||
qualname = self.get_qualname_for(name)
|
||||
if qualname:
|
||||
@ -285,6 +295,22 @@ class VariableCommentPicker(ast.NodeVisitor):
|
||||
|
||||
return False
|
||||
|
||||
def is_overload(self, decorators: List[ast.expr]) -> bool:
|
||||
overload = []
|
||||
if self.typing:
|
||||
overload.append('%s.overload' % self.typing)
|
||||
if self.typing_overload:
|
||||
overload.append(self.typing_overload)
|
||||
|
||||
for decorator in decorators:
|
||||
try:
|
||||
if unparse(decorator) in overload:
|
||||
return True
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def get_self(self) -> ast.arg:
|
||||
"""Returns the name of first argument if in function."""
|
||||
if self.current_function and self.current_function.args.args:
|
||||
@ -310,6 +336,8 @@ class VariableCommentPicker(ast.NodeVisitor):
|
||||
self.typing = name.asname or name.name
|
||||
elif name.name == 'typing.final':
|
||||
self.typing_final = name.asname or name.name
|
||||
elif name.name == 'typing.overload':
|
||||
self.typing_overload = name.asname or name.name
|
||||
|
||||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
||||
"""Handles Import node and record it to definition orders."""
|
||||
@ -318,6 +346,8 @@ class VariableCommentPicker(ast.NodeVisitor):
|
||||
|
||||
if node.module == 'typing' and name.name == 'final':
|
||||
self.typing_final = name.asname or name.name
|
||||
elif node.module == 'typing' and name.name == 'overload':
|
||||
self.typing_overload = name.asname or name.name
|
||||
|
||||
def visit_Assign(self, node: ast.Assign) -> None:
|
||||
"""Handles Assign node and pick up a variable comment."""
|
||||
@ -417,6 +447,8 @@ class VariableCommentPicker(ast.NodeVisitor):
|
||||
self.add_entry(node.name) # should be called before setting self.current_function
|
||||
if self.is_final(node.decorator_list):
|
||||
self.add_final_entry(node.name)
|
||||
if self.is_overload(node.decorator_list):
|
||||
self.add_overload_entry(node)
|
||||
self.context.append(node.name)
|
||||
self.current_function = node
|
||||
for child in node.body:
|
||||
@ -518,6 +550,7 @@ class Parser:
|
||||
self.deforders = {} # type: Dict[str, int]
|
||||
self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
|
||||
self.finals = [] # type: List[str]
|
||||
self.overloads = {} # type: Dict[str, List[Signature]]
|
||||
|
||||
def parse(self) -> None:
|
||||
"""Parse the source code."""
|
||||
@ -533,6 +566,7 @@ class Parser:
|
||||
self.comments = picker.comments
|
||||
self.deforders = picker.deforders
|
||||
self.finals = picker.finals
|
||||
self.overloads = picker.overloads
|
||||
|
||||
def parse_definition(self) -> None:
|
||||
"""Parse the location of definitions from the code."""
|
||||
|
@ -527,10 +527,14 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
|
||||
def signature_from_str(signature: str) -> inspect.Signature:
|
||||
"""Create a Signature object from string."""
|
||||
module = ast.parse('def func' + signature + ': pass')
|
||||
definition = cast(ast.FunctionDef, module.body[0]) # type: ignore
|
||||
function = cast(ast.FunctionDef, module.body[0]) # type: ignore
|
||||
|
||||
# parameters
|
||||
args = definition.args
|
||||
return signature_from_ast(function)
|
||||
|
||||
|
||||
def signature_from_ast(node: ast.FunctionDef) -> inspect.Signature:
|
||||
"""Create a Signature object from AST *node*."""
|
||||
args = node.args
|
||||
defaults = list(args.defaults)
|
||||
params = []
|
||||
if hasattr(args, "posonlyargs"):
|
||||
@ -580,7 +584,7 @@ def signature_from_str(signature: str) -> inspect.Signature:
|
||||
params.append(Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD,
|
||||
annotation=annotation))
|
||||
|
||||
return_annotation = ast_unparse(definition.returns) or Parameter.empty
|
||||
return_annotation = ast_unparse(node.returns) or Parameter.empty
|
||||
|
||||
return inspect.Signature(params, return_annotation=return_annotation)
|
||||
|
||||
|
88
tests/roots/test-ext-autodoc/target/overload.py
Normal file
88
tests/roots/test-ext-autodoc/target/overload.py
Normal file
@ -0,0 +1,88 @@
|
||||
from typing import Any, overload
|
||||
|
||||
|
||||
@overload
|
||||
def sum(x: int, y: int) -> int:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def sum(x: float, y: float) -> float:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def sum(x: str, y: str) -> str:
|
||||
...
|
||||
|
||||
|
||||
def sum(x, y):
|
||||
"""docstring"""
|
||||
return x + y
|
||||
|
||||
|
||||
class Math:
|
||||
"""docstring"""
|
||||
|
||||
@overload
|
||||
def sum(self, x: int, y: int) -> int:
|
||||
...
|
||||
|
||||
@overload
|
||||
def sum(self, x: float, y: float) -> float:
|
||||
...
|
||||
|
||||
@overload
|
||||
def sum(self, x: str, y: str) -> str:
|
||||
...
|
||||
|
||||
def sum(self, x, y):
|
||||
"""docstring"""
|
||||
return x + y
|
||||
|
||||
|
||||
class Foo:
|
||||
"""docstring"""
|
||||
|
||||
@overload
|
||||
def __new__(cls, x: int, y: int) -> "Foo":
|
||||
...
|
||||
|
||||
@overload
|
||||
def __new__(cls, x: str, y: str) -> "Foo":
|
||||
...
|
||||
|
||||
def __new__(cls, x, y):
|
||||
pass
|
||||
|
||||
|
||||
class Bar:
|
||||
"""docstring"""
|
||||
|
||||
@overload
|
||||
def __init__(cls, x: int, y: int) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(cls, x: str, y: str) -> None:
|
||||
...
|
||||
|
||||
def __init__(cls, x, y):
|
||||
pass
|
||||
|
||||
|
||||
class Meta(type):
|
||||
@overload
|
||||
def __call__(cls, x: int, y: int) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __call__(cls, x: str, y: str) -> Any:
|
||||
...
|
||||
|
||||
def __call__(cls, x, y):
|
||||
pass
|
||||
|
||||
|
||||
class Baz(metaclass=Meta):
|
||||
"""docstring"""
|
@ -1787,6 +1787,60 @@ def test_final(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_overload(app):
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.overload', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.overload',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Bar(x: int, y: int)',
|
||||
' Bar(x: str, y: str)',
|
||||
' :module: target.overload',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Baz(x: int, y: int)',
|
||||
' Baz(x: str, y: str)',
|
||||
' :module: target.overload',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo(x: int, y: int)',
|
||||
' Foo(x: str, y: str)',
|
||||
' :module: target.overload',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Math()',
|
||||
' :module: target.overload',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: Math.sum(x: int, y: int) -> int',
|
||||
' Math.sum(x: float, y: float) -> float',
|
||||
' Math.sum(x: str, y: str) -> str',
|
||||
' :module: target.overload',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: sum(x: int, y: int) -> int',
|
||||
' sum(x: float, y: float) -> float',
|
||||
' sum(x: str, y: str) -> str',
|
||||
' :module: target.overload',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', testroot='ext-autodoc')
|
||||
def test_autodoc(app, status, warning):
|
||||
app.builder.build_all()
|
||||
|
@ -13,6 +13,7 @@ import sys
|
||||
import pytest
|
||||
|
||||
from sphinx.pycode.parser import Parser
|
||||
from sphinx.util.inspect import signature_from_str
|
||||
|
||||
|
||||
def test_comment_picker_basic():
|
||||
@ -452,3 +453,80 @@ def test_typing_final_not_imported():
|
||||
parser = Parser(source)
|
||||
parser.parse()
|
||||
assert parser.finals == []
|
||||
|
||||
|
||||
def test_typing_overload():
|
||||
source = ('import typing\n'
|
||||
'\n'
|
||||
'@typing.overload\n'
|
||||
'def func(x: int, y: int) -> int: pass\n'
|
||||
'\n'
|
||||
'@typing.overload\n'
|
||||
'def func(x: str, y: str) -> str: pass\n'
|
||||
'\n'
|
||||
'def func(x, y): pass\n')
|
||||
parser = Parser(source)
|
||||
parser.parse()
|
||||
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||
signature_from_str('(x: str, y: str) -> str')]}
|
||||
|
||||
|
||||
def test_typing_overload_from_import():
|
||||
source = ('from typing import overload\n'
|
||||
'\n'
|
||||
'@overload\n'
|
||||
'def func(x: int, y: int) -> int: pass\n'
|
||||
'\n'
|
||||
'@overload\n'
|
||||
'def func(x: str, y: str) -> str: pass\n'
|
||||
'\n'
|
||||
'def func(x, y): pass\n')
|
||||
parser = Parser(source)
|
||||
parser.parse()
|
||||
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||
signature_from_str('(x: str, y: str) -> str')]}
|
||||
|
||||
|
||||
def test_typing_overload_import_as():
|
||||
source = ('import typing as foo\n'
|
||||
'\n'
|
||||
'@foo.overload\n'
|
||||
'def func(x: int, y: int) -> int: pass\n'
|
||||
'\n'
|
||||
'@foo.overload\n'
|
||||
'def func(x: str, y: str) -> str: pass\n'
|
||||
'\n'
|
||||
'def func(x, y): pass\n')
|
||||
parser = Parser(source)
|
||||
parser.parse()
|
||||
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||
signature_from_str('(x: str, y: str) -> str')]}
|
||||
|
||||
|
||||
def test_typing_overload_from_import_as():
|
||||
source = ('from typing import overload as bar\n'
|
||||
'\n'
|
||||
'@bar\n'
|
||||
'def func(x: int, y: int) -> int: pass\n'
|
||||
'\n'
|
||||
'@bar\n'
|
||||
'def func(x: str, y: str) -> str: pass\n'
|
||||
'\n'
|
||||
'def func(x, y): pass\n')
|
||||
parser = Parser(source)
|
||||
parser.parse()
|
||||
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||
signature_from_str('(x: str, y: str) -> str')]}
|
||||
|
||||
|
||||
def test_typing_overload_not_imported():
|
||||
source = ('@typing.final\n'
|
||||
'def func(x: int, y: int) -> int: pass\n'
|
||||
'\n'
|
||||
'@typing.final\n'
|
||||
'def func(x: str, y: str) -> str: pass\n'
|
||||
'\n'
|
||||
'def func(x, y): pass\n')
|
||||
parser = Parser(source)
|
||||
parser.parse()
|
||||
assert parser.overloads == {}
|
||||
|
@ -9,6 +9,7 @@
|
||||
"""
|
||||
|
||||
import _testcapi
|
||||
import ast
|
||||
import datetime
|
||||
import functools
|
||||
import sys
|
||||
@ -350,6 +351,38 @@ def test_signature_from_str_invalid():
|
||||
inspect.signature_from_str('')
|
||||
|
||||
|
||||
def test_signature_from_ast():
|
||||
signature = 'def func(a, b, *args, c=0, d="blah", **kwargs): pass'
|
||||
tree = ast.parse(signature)
|
||||
sig = inspect.signature_from_ast(tree.body[0])
|
||||
assert list(sig.parameters.keys()) == ['a', 'b', 'args', 'c', 'd', 'kwargs']
|
||||
assert sig.parameters['a'].name == 'a'
|
||||
assert sig.parameters['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||
assert sig.parameters['a'].default == Parameter.empty
|
||||
assert sig.parameters['a'].annotation == Parameter.empty
|
||||
assert sig.parameters['b'].name == 'b'
|
||||
assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||
assert sig.parameters['b'].default == Parameter.empty
|
||||
assert sig.parameters['b'].annotation == Parameter.empty
|
||||
assert sig.parameters['args'].name == 'args'
|
||||
assert sig.parameters['args'].kind == Parameter.VAR_POSITIONAL
|
||||
assert sig.parameters['args'].default == Parameter.empty
|
||||
assert sig.parameters['args'].annotation == Parameter.empty
|
||||
assert sig.parameters['c'].name == 'c'
|
||||
assert sig.parameters['c'].kind == Parameter.KEYWORD_ONLY
|
||||
assert sig.parameters['c'].default == '0'
|
||||
assert sig.parameters['c'].annotation == Parameter.empty
|
||||
assert sig.parameters['d'].name == 'd'
|
||||
assert sig.parameters['d'].kind == Parameter.KEYWORD_ONLY
|
||||
assert sig.parameters['d'].default == "'blah'"
|
||||
assert sig.parameters['d'].annotation == Parameter.empty
|
||||
assert sig.parameters['kwargs'].name == 'kwargs'
|
||||
assert sig.parameters['kwargs'].kind == Parameter.VAR_KEYWORD
|
||||
assert sig.parameters['kwargs'].default == Parameter.empty
|
||||
assert sig.parameters['kwargs'].annotation == Parameter.empty
|
||||
assert sig.return_annotation == Parameter.empty
|
||||
|
||||
|
||||
def test_safe_getattr_with_default():
|
||||
class Foo:
|
||||
def __getattr__(self, item):
|
||||
|
Loading…
Reference in New Issue
Block a user