diff options
-rw-r--r-- | CHANGES | 6 | ||||
-rw-r--r-- | doc/extdev/deprecated.rst | 15 | ||||
-rw-r--r-- | doc/usage/restructuredtext/roles.rst | 4 | ||||
-rw-r--r-- | sphinx/application.py | 10 | ||||
-rw-r--r-- | sphinx/builders/gettext.py | 4 | ||||
-rw-r--r-- | sphinx/builders/html.py | 10 | ||||
-rw-r--r-- | sphinx/builders/singlehtml.py | 8 | ||||
-rw-r--r-- | sphinx/domains/std.py | 20 | ||||
-rw-r--r-- | sphinx/environment/adapters/toctree.py | 14 | ||||
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 25 | ||||
-rw-r--r-- | sphinx/ext/autodoc/mock.py | 2 | ||||
-rw-r--r-- | sphinx/ext/imgconverter.py | 1 | ||||
-rw-r--r-- | sphinx/ext/napoleon/docstring.py | 4 | ||||
-rw-r--r-- | sphinx/testing/util.py | 8 | ||||
-rw-r--r-- | sphinx/transforms/post_transforms/images.py | 1 | ||||
-rw-r--r-- | sphinx/util/images.py | 1 | ||||
-rw-r--r-- | sphinx/util/inspect.py | 115 | ||||
-rw-r--r-- | sphinx/util/jsonimpl.py | 20 | ||||
-rw-r--r-- | tests/test_autodoc.py | 9 | ||||
-rw-r--r-- | tests/test_ext_napoleon_docstring.py | 3 | ||||
-rw-r--r-- | tests/test_util_inspect.py | 170 |
21 files changed, 273 insertions, 177 deletions
@@ -61,9 +61,7 @@ Deprecated * ``sphinx.roles.Index`` * ``sphinx.util.detect_encoding()`` * ``sphinx.util.get_module_source()`` -* ``sphinx.util.inspect.Signature.format_annotation()`` -* ``sphinx.util.inspect.Signature.format_annotation_new()`` -* ``sphinx.util.inspect.Signature.format_annotation_old()`` +* ``sphinx.util.inspect.Signature`` Features added -------------- @@ -75,6 +73,7 @@ Features added * #6966: graphviz: Support ``:class:`` option * #6696: html: ``:scale:`` option of image/figure directive not working for SVG images (imagesize-1.2.0 or above is required) +* #6994: imgconverter: Support illustrator file (.ai) to .png conversion Bugs fixed ---------- @@ -84,6 +83,7 @@ Bugs fixed * #6961: latex: warning for babel shown twice * #6559: Wrong node-ids are generated in glossary directive * #6986: apidoc: misdetects module name for .so file inside module +* #6999: napoleon: fails to parse tilde in :exc: role Testing -------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index d91ea9308..f6ebc3916 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -86,20 +86,11 @@ The following is a list of deprecated interfaces. - 4.0 - N/A - * - ``sphinx.util.inspect.Signature.format_annotation()`` + * - ``sphinx.util.inspect.Signature`` - 2.4 - 4.0 - - ``sphinx.util.typing.stringify()`` - - * - ``sphinx.util.inspect.Signature.format_annotation_new()`` - - 2.4 - - 4.0 - - ``sphinx.util.typing.stringify()`` - - * - ``sphinx.util.inspect.Signature.format_annotation_old()`` - - 2.4 - - 4.0 - - ``sphinx.util.typing.stringify()`` + - ``sphinx.util.inspect.signature`` and + ``sphinx.util.inspect.stringify_signature()`` * - ``sphinx.builders.gettext.POHEADER`` - 2.3 diff --git a/doc/usage/restructuredtext/roles.rst b/doc/usage/restructuredtext/roles.rst index 637df711b..de12a41b5 100644 --- a/doc/usage/restructuredtext/roles.rst +++ b/doc/usage/restructuredtext/roles.rst @@ -189,8 +189,8 @@ Referencing downloadable files When you use this role, the referenced file is automatically marked for inclusion in the output when building (obviously, for HTML output only). - All downloadable files are put into the ``_downloads`` subdirectory of the - output directory; duplicate filenames are handled. + All downloadable files are put into a ``_downloads/<unique hash>/`` + subdirectory of the output directory; duplicate filenames are handled. An example:: diff --git a/sphinx/application.py b/sphinx/application.py index 0fd95ba91..515d962dc 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -509,7 +509,7 @@ class Sphinx: self.registry.add_translator(name, translator_class, override=override) def add_node(self, node: "Type[Element]", override: bool = False, - **kwds: Tuple[Callable, Callable]) -> None: + **kwargs: Tuple[Callable, Callable]) -> None: """Register a Docutils node class. This is necessary for Docutils internals. It may also be used in the @@ -539,17 +539,17 @@ class Sphinx: .. versionchanged:: 0.5 Added the support for keyword arguments giving visit functions. """ - logger.debug('[app] adding node: %r', (node, kwds)) + logger.debug('[app] adding node: %r', (node, kwargs)) if not override and docutils.is_node_registered(node): logger.warning(__('node class %r is already registered, ' 'its visitors will be overridden'), node.__name__, type='app', subtype='add_node') docutils.register_node(node) - self.registry.add_translation_handlers(node, **kwds) + self.registry.add_translation_handlers(node, **kwargs) def add_enumerable_node(self, node: "Type[Element]", figtype: str, title_getter: TitleGetter = None, override: bool = False, - **kwds: Tuple[Callable, Callable]) -> None: + **kwargs: Tuple[Callable, Callable]) -> None: """Register a Docutils node class as a numfig target. Sphinx numbers the node automatically. And then the users can refer it @@ -574,7 +574,7 @@ class Sphinx: .. versionadded:: 1.4 """ self.registry.add_enumerable_node(node, figtype, title_getter, override=override) - self.add_node(node, override=override, **kwds) + self.add_node(node, override=override, **kwargs) def add_directive(self, name: str, cls: "Type[Directive]", override: bool = False) -> None: """Register a Docutils directive. diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index f5264837c..638408503 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -198,8 +198,8 @@ if source_date_epoch is not None: class LocalTimeZone(tzinfo): - def __init__(self, *args: Any, **kw: Any) -> None: - super().__init__(*args, **kw) # type: ignore + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) # type: ignore self.tzdelta = tzdelta def utcoffset(self, dt: datetime) -> timedelta: diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index f76cf5ce5..80c99d3b8 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -858,11 +858,11 @@ class StandaloneHTMLBuilder(Builder): indexer_name, indexer_name), RemovedInSphinx40Warning) - def _get_local_toctree(self, docname: str, collapse: bool = True, **kwds: Any) -> str: - if 'includehidden' not in kwds: - kwds['includehidden'] = False + def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str: + if 'includehidden' not in kwargs: + kwargs['includehidden'] = False return self.render_partial(TocTree(self.env).get_toctree_for( - docname, self, collapse, **kwds))['fragment'] + docname, self, collapse, **kwargs))['fragment'] def get_outfilename(self, pagename: str) -> str: return path.join(self.outdir, os_path(pagename) + self.out_suffix) @@ -971,7 +971,7 @@ class StandaloneHTMLBuilder(Builder): return False ctx['hasdoc'] = hasdoc - ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw) + ctx['toctree'] = lambda **kwargs: self._get_local_toctree(pagename, **kwargs) self.add_sidebars(pagename, ctx) ctx.update(addctx) diff --git a/sphinx/builders/singlehtml.py b/sphinx/builders/singlehtml.py index 1c47596b8..b145109a6 100644 --- a/sphinx/builders/singlehtml.py +++ b/sphinx/builders/singlehtml.py @@ -67,10 +67,10 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder): if hashindex >= 0: refnode['refuri'] = fname + refuri[hashindex:] - def _get_local_toctree(self, docname: str, collapse: bool = True, **kwds: Any) -> str: - if 'includehidden' not in kwds: - kwds['includehidden'] = False - toctree = TocTree(self.env).get_toctree_for(docname, self, collapse, **kwds) + def _get_local_toctree(self, docname: str, collapse: bool = True, **kwargs: Any) -> str: + if 'includehidden' not in kwargs: + kwargs['includehidden'] = False + toctree = TocTree(self.env).get_toctree_for(docname, self, collapse, **kwargs) if toctree is not None: self.fix_refuris(toctree) return self.render_partial(toctree)['fragment'] diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 9e7dd2353..65ebf070e 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -128,7 +128,7 @@ class Target(SphinxDirective): targetname = '%s-%s' % (self.name, fullname) node = nodes.target('', '', ids=[targetname]) self.state.document.note_explicit_target(node) - ret = [node] # type: List[nodes.Node] + ret = [node] # type: List[Node] if self.indextemplate: indexentry = self.indextemplate % (fullname,) indextype = 'single' @@ -254,7 +254,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index if node_id: # node_id is given from outside (mainly i18n module), use it forcedly - pass + term['ids'].append(node_id) elif document: node_id = make_id(env, document, 'term', termtext) term['ids'].append(node_id) @@ -313,7 +313,7 @@ class Glossary(SphinxDirective): in_definition = True in_comment = False was_empty = True - messages = [] # type: List[nodes.Node] + messages = [] # type: List[Node] for line, (source, lineno) in zip(self.content, self.content.items): # empty line -> add to last definition if not line: @@ -369,8 +369,8 @@ class Glossary(SphinxDirective): items = [] for terms, definition in entries: termtexts = [] # type: List[str] - termnodes = [] # type: List[nodes.Node] - system_messages = [] # type: List[nodes.Node] + termnodes = [] # type: List[Node] + system_messages = [] # type: List[Node] for line, source, lineno in terms: parts = split_term_classifiers(line) # parse the term with inline markup @@ -407,7 +407,7 @@ class Glossary(SphinxDirective): def token_xrefs(text: str) -> List[Node]: - retnodes = [] # type: List[nodes.Node] + retnodes = [] # type: List[Node] pos = 0 for m in token_re.finditer(text): if m.start() > pos: @@ -436,7 +436,7 @@ class ProductionList(SphinxDirective): def run(self) -> List[Node]: domain = cast(StandardDomain, self.env.get_domain('std')) - node = addnodes.productionlist() # type: nodes.Element + node = addnodes.productionlist() # type: Element i = 0 for rule in self.arguments[0].split('\n'): @@ -538,7 +538,7 @@ class StandardDomain(Domain): nodes.figure: ('figure', None), nodes.table: ('table', None), nodes.container: ('code-block', None), - } # type: Dict[Type[nodes.Node], Tuple[str, Callable]] + } # type: Dict[Type[Node], Tuple[str, Callable]] def __init__(self, env: "BuildEnvironment") -> None: super().__init__(env) @@ -847,7 +847,7 @@ class StandardDomain(Domain): def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", target: str, node: pending_xref, contnode: Element) -> List[Tuple[str, Element]]: - results = [] # type: List[Tuple[str, nodes.Element]] + results = [] # type: List[Tuple[str, Element]] ltarget = target.lower() # :ref: lowercases its target automatically for role in ('ref', 'option'): # do not try "keyword" res = self.resolve_xref(env, fromdocname, builder, role, @@ -898,7 +898,7 @@ class StandardDomain(Domain): def get_numfig_title(self, node: Node) -> str: """Get the title of enumerable nodes to refer them using its title""" if self.is_enumerable_node(node): - elem = cast(nodes.Element, node) + elem = cast(Element, node) _, title_getter = self.enumerable_nodes.get(elem.__class__, (None, None)) if title_getter: return title_getter(elem) diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index fe8f43656..bd3abd9ed 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -315,17 +315,17 @@ class TocTree: return toc def get_toctree_for(self, docname: str, builder: "Builder", collapse: bool, - **kwds: Any) -> Element: + **kwargs: Any) -> Element: """Return the global TOC nodetree.""" doctree = self.env.get_doctree(self.env.config.master_doc) toctrees = [] # type: List[Element] - if 'includehidden' not in kwds: - kwds['includehidden'] = True - if 'maxdepth' not in kwds: - kwds['maxdepth'] = 0 - kwds['collapse'] = collapse + if 'includehidden' not in kwargs: + kwargs['includehidden'] = True + if 'maxdepth' not in kwargs: + kwargs['maxdepth'] = 0 + kwargs['collapse'] = collapse for toctreenode in doctree.traverse(addnodes.toctree): - toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwds) + toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs) if toctree: toctrees.append(toctree) if not toctrees: diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index ea6a235c9..1a00fb43f 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -31,7 +31,7 @@ from sphinx.util import logging from sphinx.util import rpartition from sphinx.util.docstrings import prepare_docstring from sphinx.util.inspect import ( - Signature, getdoc, object_description, safe_getattr, safe_getmembers + getdoc, object_description, safe_getattr, safe_getmembers, stringify_signature ) if False: @@ -1006,9 +1006,10 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ not inspect.isbuiltin(self.object) and not inspect.isclass(self.object) and hasattr(self.object, '__call__')): - args = Signature(self.object.__call__).format_args(**kwargs) + sig = inspect.signature(self.object.__call__) else: - args = Signature(self.object).format_args(**kwargs) + sig = inspect.signature(self.object) + args = stringify_signature(sig, **kwargs) except TypeError: if (inspect.is_builtin_class_method(self.object, '__new__') and inspect.is_builtin_class_method(self.object, '__init__')): @@ -1018,11 +1019,11 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ # typing) we try to use the constructor signature as function # signature without the first argument. try: - sig = Signature(self.object.__new__, bound_method=True, has_retval=False) - args = sig.format_args(**kwargs) + sig = inspect.signature(self.object.__new__, bound_method=True) + args = stringify_signature(sig, show_return_annotation=False, **kwargs) except TypeError: - sig = Signature(self.object.__init__, bound_method=True, has_retval=False) - args = sig.format_args(**kwargs) + sig = inspect.signature(self.object.__init__, bound_method=True) + args = stringify_signature(sig, show_return_annotation=False, **kwargs) # escape backslashes for reST args = args.replace('\\', '\\\\') @@ -1103,8 +1104,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)): return None try: - sig = Signature(initmeth, bound_method=True, has_retval=False) - return sig.format_args(**kwargs) + sig = inspect.signature(initmeth, bound_method=True) + return stringify_signature(sig, show_return_annotation=False, **kwargs) except TypeError: # still not possible: happens e.g. for old-style classes # with __init__ in C @@ -1306,9 +1307,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: # can never get arguments of a C function or method return None if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): - args = Signature(self.object, bound_method=False).format_args(**kwargs) + sig = inspect.signature(self.object, bound_method=False) else: - args = Signature(self.object, bound_method=True).format_args(**kwargs) + sig = inspect.signature(self.object, bound_method=True) + args = stringify_signature(sig, **kwargs) + # escape backslashes for reST args = args.replace('\\', '\\\\') return args diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 169034664..25f50d27e 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -57,7 +57,7 @@ class _MockObject: def __getattr__(self, key: str) -> "_MockObject": return _make_subclass(key, self.__display_name__, self.__class__)() - def __call__(self, *args: Any, **kw: Any) -> Any: + def __call__(self, *args: Any, **kwargs: Any) -> Any: if args and type(args[0]) in [type, FunctionType, MethodType]: # Appears to be a decorator, pass through unchanged return args[0] diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index b95ef2588..95d3fe65a 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -27,6 +27,7 @@ class ImagemagickConverter(ImageConverter): ('image/svg+xml', 'image/png'), ('image/gif', 'image/png'), ('application/pdf', 'image/png'), + ('application/illustrator', 'image/png'), ] def is_available(self) -> bool: diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index a06a79cea..81f2496cb 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -101,8 +101,8 @@ class GoogleDocstring: """ - _name_rgx = re.compile(r"^\s*((?::(?P<role>\S+):)?`(?P<name>[a-zA-Z0-9_.-]+)`|" - r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X) + _name_rgx = re.compile(r"^\s*((?::(?P<role>\S+):)?`(?P<name>~?[a-zA-Z0-9_.-]+)`|" + r" (?P<name2>~?[a-zA-Z0-9_.-]+))\s*", re.X) def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None, app: Sphinx = None, what: str = '', name: str = '', diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index a031e2523..f4ef35d61 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -92,8 +92,8 @@ def etree_parse(path: str) -> Any: class Struct: - def __init__(self, **kwds: Any) -> None: - self.__dict__.update(kwds) + def __init__(self, **kwargs: Any) -> None: + self.__dict__.update(kwargs) class SphinxTestApp(application.Sphinx): @@ -165,10 +165,10 @@ class SphinxTestAppWrapperForSkipBuilding: def __getattr__(self, name: str) -> Any: return getattr(self.app, name) - def build(self, *args: Any, **kw: Any) -> None: + def build(self, *args: Any, **kwargs: Any) -> None: if not self.app.outdir.listdir(): # type: ignore # if listdir is empty, do build. - self.app.build(*args, **kw) + self.app.build(*args, **kwargs) # otherwise, we can use built cache diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 6f51bc8e0..758e92f0d 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -222,7 +222,6 @@ class ImageConverter(BaseImageConverter): if '?' in node['candidates']: return [] elif '*' in node['candidates']: - from sphinx.util.images import guess_mimetype return [guess_mimetype(node['uri'])] else: return node['candidates'].keys() diff --git a/sphinx/util/images.py b/sphinx/util/images.py index b9065838b..17bd95685 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -28,6 +28,7 @@ mime_suffixes = OrderedDict([ ('.pdf', 'application/pdf'), ('.svg', 'image/svg+xml'), ('.svgz', 'image/svg+xml'), + ('.ai', 'application/illustrator'), ]) DataURI = NamedTuple('DataURI', [('mimetype', str), diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index f199e6748..60745be61 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -315,6 +315,112 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool: return getattr(builtins, safe_getattr(cls, '__name__', '')) is cls +def signature(subject: Callable, bound_method: bool = False) -> inspect.Signature: + """Return a Signature object for the given *subject*. + + :param bound_method: Specify *subject* is a bound method or not + """ + # check subject is not a built-in class (ex. int, str) + if (isinstance(subject, type) and + is_builtin_class_method(subject, "__new__") and + is_builtin_class_method(subject, "__init__")): + raise TypeError("can't compute signature for built-in type {}".format(subject)) + + try: + signature = inspect.signature(subject) + parameters = list(signature.parameters.values()) + return_annotation = signature.return_annotation + except IndexError: + # Until python 3.6.4, cpython has been crashed on inspection for + # partialmethods not having any arguments. + # https://bugs.python.org/issue33009 + if hasattr(subject, '_partialmethod'): + parameters = [] + return_annotation = inspect.Parameter.empty + else: + raise + + try: + # Update unresolved annotations using ``get_type_hints()``. + annotations = typing.get_type_hints(subject) + for i, param in enumerate(parameters): + if isinstance(param.annotation, str) and param.name in annotations: + parameters[i] = param.replace(annotation=annotations[param.name]) + if 'return' in annotations: + return_annotation = annotations['return'] + except Exception: + # ``get_type_hints()`` does not support some kind of objects like partial, + # ForwardRef and so on. + pass + + if bound_method: + if inspect.ismethod(subject): + # ``inspect.signature()`` considers the subject is a bound method and removes + # first argument from signature. Therefore no skips are needed here. + pass + else: + if len(parameters) > 0: + parameters.pop(0) + + return inspect.Signature(parameters, return_annotation=return_annotation) + + +def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, + show_return_annotation: bool = True) -> str: + """Stringify a Signature object. + + :param show_annotation: Show annotation in result + """ + args = [] + last_kind = None + for param in sig.parameters.values(): + # insert '*' between POSITIONAL args and KEYWORD_ONLY args:: + # func(a, b, *, c, d): + if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD, + param.POSITIONAL_ONLY, + None): + args.append('*') + + arg = StringIO() + if param.kind in (param.POSITIONAL_ONLY, + param.POSITIONAL_OR_KEYWORD, + param.KEYWORD_ONLY): + arg.write(param.name) + if show_annotation and param.annotation is not param.empty: + arg.write(': ') + arg.write(stringify_annotation(param.annotation)) + if param.default is not param.empty: + if show_annotation and param.annotation is not param.empty: + arg.write(' = ') + arg.write(object_description(param.default)) + else: + arg.write('=') + arg.write(object_description(param.default)) + elif param.kind == param.VAR_POSITIONAL: + arg.write('*') + arg.write(param.name) + if show_annotation and param.annotation is not param.empty: + arg.write(': ') + arg.write(stringify_annotation(param.annotation)) + elif param.kind == param.VAR_KEYWORD: + arg.write('**') + arg.write(param.name) + if show_annotation and param.annotation is not param.empty: + arg.write(': ') + arg.write(stringify_annotation(param.annotation)) + + args.append(arg.getvalue()) + last_kind = param.kind + + if (sig.return_annotation is inspect.Parameter.empty or + show_annotation is False or + show_return_annotation is False): + return '(%s)' % ', '.join(args) + else: + annotation = stringify_annotation(sig.return_annotation) + return '(%s) -> %s' % (', '.join(args), annotation) + + class Signature: """The Signature object represents the call signature of a callable object and its return annotation. @@ -322,6 +428,9 @@ class Signature: def __init__(self, subject: Callable, bound_method: bool = False, has_retval: bool = True) -> None: + warnings.warn('sphinx.util.inspect.Signature() is deprecated', + RemovedInSphinx40Warning) + # check subject is not a built-in class (ex. int, str) if (isinstance(subject, type) and is_builtin_class_method(subject, "__new__") and @@ -447,20 +556,14 @@ class Signature: def format_annotation(self, annotation: Any) -> str: """Return formatted representation of a type annotation.""" - warnings.warn('format_annotation() is deprecated', - RemovedInSphinx40Warning) return stringify_annotation(annotation) def format_annotation_new(self, annotation: Any) -> str: """format_annotation() for py37+""" - warnings.warn('format_annotation_new() is deprecated', - RemovedInSphinx40Warning) return stringify_annotation(annotation) def format_annotation_old(self, annotation: Any) -> str: """format_annotation() for py36 or below""" - warnings.warn('format_annotation_old() is deprecated', - RemovedInSphinx40Warning) return stringify_annotation(annotation) diff --git a/sphinx/util/jsonimpl.py b/sphinx/util/jsonimpl.py index 52b4f2d3e..35501f03a 100644 --- a/sphinx/util/jsonimpl.py +++ b/sphinx/util/jsonimpl.py @@ -28,19 +28,19 @@ class SphinxJSONEncoder(json.JSONEncoder): return super().default(obj) -def dump(obj: Any, fp: IO, *args: Any, **kwds: Any) -> None: - kwds['cls'] = SphinxJSONEncoder - json.dump(obj, fp, *args, **kwds) +def dump(obj: Any, fp: IO, *args: Any, **kwargs: Any) -> None: + kwargs['cls'] = SphinxJSONEncoder + json.dump(obj, fp, *args, **kwargs) -def dumps(obj: Any, *args: Any, **kwds: Any) -> str: - kwds['cls'] = SphinxJSONEncoder - return json.dumps(obj, *args, **kwds) +def dumps(obj: Any, *args: Any, **kwargs: Any) -> str: + kwargs['cls'] = SphinxJSONEncoder + return json.dumps(obj, *args, **kwargs) -def load(*args: Any, **kwds: Any) -> Any: - return json.load(*args, **kwds) +def load(*args: Any, **kwargs: Any) -> Any: + return json.load(*args, **kwargs) -def loads(*args: Any, **kwds: Any) -> Any: - return json.loads(*args, **kwds) +def loads(*args: Any, **kwargs: Any) -> Any: + return json.loads(*args, **kwargs) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index c5bdc801b..819cbdcde 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1342,13 +1342,13 @@ def test_partialmethod(app): ' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod', ' ', ' ', - ' .. py:method:: Cell.set_alive() -> None', + ' .. py:method:: Cell.set_alive()', ' :module: target.partialmethod', ' ', ' Make a cell alive.', ' ', ' ', - ' .. py:method:: Cell.set_dead() -> None', + ' .. py:method:: Cell.set_dead()', ' :module: target.partialmethod', ' ', ' Make a cell dead.', @@ -1360,11 +1360,6 @@ def test_partialmethod(app): ' Update state of cell to *state*.', ' ', ] - if (sys.version_info < (3, 5, 4) or - (3, 6, 5) <= sys.version_info < (3, 7) or - (3, 7, 0, 'beta', 3) <= sys.version_info): - # TODO: this condition should be updated after 3.7-final release. - expected = '\n'.join(expected).replace(' -> None', '').split('\n') options = {"members": None} actual = do_autodoc(app, 'class', 'target.partialmethod.Cell', options) diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 9547a9d2b..2ce754eff 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -479,6 +479,8 @@ Raises: If the dimensions couldn't be parsed. `InvalidArgumentsError` If the arguments are invalid. + :exc:`~ValueError` + If the arguments are wrong. """, """ Example Function @@ -488,6 +490,7 @@ Example Function :raises AttributeError: errors for missing attributes. :raises ~InvalidDimensionsError: If the dimensions couldn't be parsed. :raises InvalidArgumentsError: If the arguments are invalid. +:raises ~ValueError: If the arguments are wrong. """), ################################ (""" diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 2f4631965..68d1ac604 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -17,6 +17,7 @@ import types import pytest from sphinx.util import inspect +from sphinx.util.inspect import stringify_signature def test_getargspec(): @@ -89,39 +90,39 @@ def test_getargspec_bound_methods(): assert expected_bound == inspect.getargspec(wrapped_bound_method) -def test_Signature(): +def test_signature(): # literals with pytest.raises(TypeError): - inspect.Signature(1) + inspect.signature(1) with pytest.raises(TypeError): - inspect.Signature('') + inspect.signature('') # builitin classes with pytest.raises(TypeError): - inspect.Signature(int) + inspect.signature(int) with pytest.raises(TypeError): - inspect.Signature(str) + inspect.signature(str) # normal function def func(a, b, c=1, d=2, *e, **f): pass - sig = inspect.Signature(func).format_args() + sig = inspect.stringify_signature(inspect.signature(func)) assert sig == '(a, b, c=1, d=2, *e, **f)' -def test_Signature_partial(): +def test_signature_partial(): def fun(a, b, c=1, d=2): pass p = functools.partial(fun, 10, c=11) - sig = inspect.Signature(p).format_args() - assert sig == '(b, *, c=11, d=2)' + sig = inspect.signature(p) + assert stringify_signature(sig) == '(b, *, c=11, d=2)' -def test_Signature_methods(): +def test_signature_methods(): class Foo: def meth1(self, arg1, **kwargs): pass @@ -139,36 +140,36 @@ def test_Signature_methods(): pass # unbound method - sig = inspect.Signature(Foo.meth1).format_args() - assert sig == '(self, arg1, **kwargs)' + sig = inspect.signature(Foo.meth1) + assert stringify_signature(sig) == '(self, arg1, **kwargs)' - sig = inspect.Signature(Foo.meth1, bound_method=True).format_args() - assert sig == '(arg1, **kwargs)' + sig = inspect.signature(Foo.meth1, bound_method=True) + assert stringify_signature(sig) == '(arg1, **kwargs)' # bound method - sig = inspect.Signature(Foo().meth1).format_args() - assert sig == '(arg1, **kwargs)' + sig = inspect.signature(Foo().meth1) + assert stringify_signature(sig) == '(arg1, **kwargs)' # class method - sig = inspect.Signature(Foo.meth2).format_args() - assert sig == '(arg1, *args, **kwargs)' + sig = inspect.signature(Foo.meth2) + assert stringify_signature(sig) == '(arg1, *args, **kwargs)' - sig = inspect.Signature(Foo().meth2).format_args() - assert sig == '(arg1, *args, **kwargs)' + sig = inspect.signature(Foo().meth2) + assert stringify_signature(sig) == '(arg1, *args, **kwargs)' # static method - sig = inspect.Signature(Foo.meth3).format_args() - assert sig == '(arg1, *args, **kwargs)' + sig = inspect.signature(Foo.meth3) + assert stringify_signature(sig) == '(arg1, *args, **kwargs)' - sig = inspect.Signature(Foo().meth3).format_args() - assert sig == '(arg1, *args, **kwargs)' + sig = inspect.signature(Foo().meth3) + assert stringify_signature(sig) == '(arg1, *args, **kwargs)' # wrapped bound method - sig = inspect.Signature(wrapped_bound_method).format_args() - assert sig == '(arg1, **kwargs)' + sig = inspect.signature(wrapped_bound_method) + assert stringify_signature(sig) == '(arg1, **kwargs)' -def test_Signature_partialmethod(): +def test_signature_partialmethod(): from functools import partialmethod class Foo: @@ -183,116 +184,115 @@ def test_Signature_partialmethod(): baz = partialmethod(meth2, 1, 2) subject = Foo() - sig = inspect.Signature(subject.foo).format_args() - assert sig == '(arg3=None, arg4=None)' + sig = inspect.signature(subject.foo) + assert stringify_signature(sig) == '(arg3=None, arg4=None)' - sig = inspect.Signature(subject.bar).format_args() - assert sig == '(arg2, *, arg3=3, arg4=None)' + sig = inspect.signature(subject.bar) + assert stringify_signature(sig) == '(arg2, *, arg3=3, arg4=None)' - sig = inspect.Signature(subject.baz).format_args() - assert sig == '()' + sig = inspect.signature(subject.baz) + assert stringify_signature(sig) == '()' -def test_Signature_annotations(): +def test_signature_annotations(): from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, Node) # Class annotations - sig = inspect.Signature(f0).format_args() - assert sig == '(x: int, y: numbers.Integral) -> None' + sig = inspect.signature(f0) + assert stringify_signature(sig) == '(x: int, y: numbers.Integral) -> None' # Generic types with concrete parameters - sig = inspect.Signature(f1).format_args() - assert sig == '(x: List[int]) -> List[int]' + sig = inspect.signature(f1) + assert stringify_signature(sig) == '(x: List[int]) -> List[int]' # TypeVars and generic types with TypeVars - sig = inspect.Signature(f2).format_args() - assert sig == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' + sig = inspect.signature(f2) + assert stringify_signature(sig) == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' # Union types - sig = inspect.Signature(f3).format_args() - assert sig == '(x: Union[str, numbers.Integral]) -> None' + sig = inspect.signature(f3) + assert stringify_signature(sig) == '(x: Union[str, numbers.Integral]) -> None' # Quoted annotations - sig = inspect.Signature(f4).format_args() - assert sig == '(x: str, y: str) -> None' + sig = inspect.signature(f4) + assert stringify_signature(sig) == '(x: str, y: str) -> None' # Keyword-only arguments - sig = inspect.Signature(f5).format_args() - assert sig == '(x: int, *, y: str, z: str) -> None' + sig = inspect.signature(f5) + assert stringify_signature(sig) == '(x: int, *, y: str, z: str) -> None' # Keyword-only arguments with varargs - sig = inspect.Signature(f6).format_args() - assert sig == '(x: int, *args, y: str, z: str) -> None' + sig = inspect.signature(f6) + assert stringify_signature(sig) == '(x: int, *args, y: str, z: str) -> None' # Space around '=' for defaults - sig = inspect.Signature(f7).format_args() - assert sig == '(x: int = None, y: dict = {}) -> None' + sig = inspect.signature(f7) + assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None' # Callable types - sig = inspect.Signature(f8).format_args() - assert sig == '(x: Callable[[int, str], int]) -> None' + sig = inspect.signature(f8) + assert stringify_signature(sig) == '(x: Callable[[int, str], int]) -> None' - sig = inspect.Signature(f9).format_args() - assert sig == '(x: Callable) -> None' + sig = inspect.signature(f9) + assert stringify_signature(sig) == '(x: Callable) -> None' # Tuple types - sig = inspect.Signature(f10).format_args() - assert sig == '(x: Tuple[int, str], y: Tuple[int, ...]) -> None' + sig = inspect.signature(f10) + assert stringify_signature(sig) == '(x: Tuple[int, str], y: Tuple[int, ...]) -> None' # Instance annotations - sig = inspect.Signature(f11).format_args() - assert sig == '(x: CustomAnnotation, y: 123) -> None' - - # has_retval=False - sig = inspect.Signature(f11, has_retval=False).format_args() - assert sig == '(x: CustomAnnotation, y: 123)' + sig = inspect.signature(f11) + assert stringify_signature(sig) == '(x: CustomAnnotation, y: 123) -> None' # tuple with more than two items - sig = inspect.Signature(f12).format_args() - assert sig == '() -> Tuple[int, str, int]' + sig = inspect.signature(f12) + assert stringify_signature(sig) == '() -> Tuple[int, str, int]' # optional - sig = inspect.Signature(f13).format_args() - assert sig == '() -> Optional[str]' + sig = inspect.signature(f13) + assert stringify_signature(sig) == '() -> Optional[str]' # Any - sig = inspect.Signature(f14).format_args() - assert sig == '() -> Any' + sig = inspect.signature(f14) + assert stringify_signature(sig) == '() -> Any' # ForwardRef - sig = inspect.Signature(f15).format_args() - assert sig == '(x: Unknown, y: int) -> Any' + sig = inspect.signature(f15) + assert stringify_signature(sig) == '(x: Unknown, y: int) -> Any' # keyword only arguments (1) - sig = inspect.Signature(f16).format_args() - assert sig == '(arg1, arg2, *, arg3=None, arg4=None)' + sig = inspect.signature(f16) + assert stringify_signature(sig) == '(arg1, arg2, *, arg3=None, arg4=None)' # keyword only arguments (2) - sig = inspect.Signature(f17).format_args() - assert sig == '(*, arg3, arg4)' + sig = inspect.signature(f17) + assert stringify_signature(sig) == '(*, arg3, arg4)' - sig = inspect.Signature(f18).format_args() - assert sig == '(self, arg1: Union[int, Tuple] = 10) -> List[Dict]' + sig = inspect.signature(f18) + assert stringify_signature(sig) == '(self, arg1: Union[int, Tuple] = 10) -> List[Dict]' # annotations for variadic and keyword parameters - sig = inspect.Signature(f19).format_args() - assert sig == '(*args: int, **kwargs: str)' + sig = inspect.signature(f19) + assert stringify_signature(sig) == '(*args: int, **kwargs: str)' # type hints by string - sig = inspect.Signature(Node.children).format_args() + sig = inspect.signature(Node.children) if (3, 5, 0) <= sys.version_info < (3, 5, 3): - assert sig == '(self) -> List[Node]' + assert stringify_signature(sig) == '(self) -> List[Node]' else: - assert sig == '(self) -> List[typing_test_data.Node]' + assert stringify_signature(sig) == '(self) -> List[typing_test_data.Node]' - sig = inspect.Signature(Node.__init__).format_args() - assert sig == '(self, parent: Optional[Node]) -> None' + sig = inspect.signature(Node.__init__) + assert stringify_signature(sig) == '(self, parent: Optional[Node]) -> None' # show_annotation is False - sig = inspect.Signature(f7).format_args(show_annotation=False) - assert sig == '(x=None, y={})' + sig = inspect.signature(f7) + assert stringify_signature(sig, show_annotation=False) == '(x=None, y={})' + # show_return_annotation is False + sig = inspect.signature(f7) + assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})' def test_safe_getattr_with_default(): class Foo: |