diff options
-rw-r--r-- | CHANGES | 24 | ||||
-rw-r--r-- | doc/usage/extensions/autosummary.rst | 7 | ||||
-rw-r--r-- | sphinx/domains/c.py | 101 | ||||
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 99 | ||||
-rw-r--r-- | sphinx/ext/autosummary/__init__.py | 1 | ||||
-rw-r--r-- | sphinx/ext/autosummary/generate.py | 10 | ||||
-rw-r--r-- | sphinx/pycode/parser.py | 56 | ||||
-rw-r--r-- | sphinx/themes/basic/static/basic.css_t | 2 | ||||
-rw-r--r-- | sphinx/util/typing.py | 9 | ||||
-rw-r--r-- | tests/roots/test-ext-autodoc/target/typed_vars.py | 9 | ||||
-rw-r--r-- | tests/roots/test-templating/_templates/autosummary/class.rst | 1 | ||||
-rw-r--r-- | tests/test_autodoc.py | 39 | ||||
-rw-r--r-- | tests/test_domain_c.py | 15 | ||||
-rw-r--r-- | tests/test_ext_autosummary.py | 3 | ||||
-rw-r--r-- | tests/test_templating.py | 14 | ||||
-rw-r--r-- | tests/test_util_typing.py | 8 |
16 files changed, 281 insertions, 117 deletions
@@ -70,11 +70,15 @@ Features added * #2044: autodoc: Suppress default value for instance attributes * #7473: autodoc: consider a member public if docstring contains ``:meta public:`` in info-field-list +* #7487: autodoc: Allow to generate docs for singledispatch functions by + py:autofunction * #7466: autosummary: headings in generated documents are not translated * #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a caption to the toctree * #248, #6040: autosummary: Add ``:recursive:`` option to autosummary directive to generate stub files recursively +* #4030: autosummary: Add :confval:`autosummary_context` to add template + variables for custom templates * #7535: sphinx-autogen: crashes when custom template uses inheritance * #7536: sphinx-autogen: crashes when template uses i18n feature * #7481: html theme: Add right margin to footnote/citation labels @@ -88,6 +92,7 @@ Features added * #7533: html theme: Avoid whitespace at the beginning of genindex.html * #7541: html theme: Add a "clearer" at the end of the "body" * #7542: html theme: Make admonition/topic/sidebar scrollable +* #7543: html theme: Add top and bottom margins to tables * C and C++: allow semicolon in the end of declarations. * C++, parse parameterized noexcept specifiers. @@ -95,11 +100,12 @@ Bugs fixed ---------- * #6703: autodoc: incremental build does not work for imported objects +* #7564: autodoc: annotations not to be shown for descriptors Testing -------- -Release 3.0.3 (in development) +Release 3.0.4 (in development) ============================== Dependencies @@ -117,9 +123,25 @@ Features added Bugs fixed ---------- +* #7567: autodoc: parametrized types are shown twice for generic types + Testing -------- +Release 3.0.3 (released Apr 26, 2020) +===================================== + +Features added +-------------- + +* C, parse array declarators with static, qualifiers, and VLA specification. + +Bugs fixed +---------- + +* #7516: autodoc: crashes if target object raises an error on accessing + its attributes + Release 3.0.2 (released Apr 19, 2020) ===================================== diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index 5915b30cd..f3a5aea0e 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -151,6 +151,13 @@ Generating stub pages automatically If you do not want to create stub pages with :program:`sphinx-autogen`, you can also use these config values: +.. confval:: autosummary_context + + A dictionary of values to pass into the template engine's context for + autosummary stubs files. + + .. versionadded:: 3.1 + .. confval:: autosummary_generate Boolean indicating whether to scan all found documents for autosummary diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 1e5eb57a0..8854d7941 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -792,20 +792,60 @@ class ASTDeclSpecs(ASTBase): ################################################################################ class ASTArray(ASTBase): - def __init__(self, size: ASTExpression): + def __init__(self, static: bool, const: bool, volatile: bool, restrict: bool, + vla: bool, size: ASTExpression): + self.static = static + self.const = const + self.volatile = volatile + self.restrict = restrict + self.vla = vla self.size = size + if vla: + assert size is None + if size is not None: + assert not vla def _stringify(self, transform: StringifyTransform) -> str: - if self.size: - return '[' + transform(self.size) + ']' - else: - return '[]' + el = [] + if self.static: + el.append('static') + if self.restrict: + el.append('restrict') + if self.volatile: + el.append('volatile') + if self.const: + el.append('const') + if self.vla: + return '[' + ' '.join(el) + '*]' + elif self.size: + el.append(transform(self.size)) + return '[' + ' '.join(el) + ']' def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) signode.append(nodes.Text("[")) - if self.size: + addSpace = False + + def _add(signode: TextElement, text: str) -> bool: + if addSpace: + signode += nodes.Text(' ') + signode += addnodes.desc_annotation(text, text) + return True + + if self.static: + addSpace = _add(signode, 'static') + if self.restrict: + addSpace = _add(signode, 'restrict') + if self.volatile: + addSpace = _add(signode, 'volatile') + if self.const: + addSpace = _add(signode, 'const') + if self.vla: + signode.append(nodes.Text('*')) + elif self.size: + if addSpace: + signode += nodes.Text(' ') self.size.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text("]")) @@ -2595,18 +2635,45 @@ class DefinitionParser(BaseParser): self.skip_ws() if typed and self.skip_string('['): self.skip_ws() - if self.skip_string(']'): - arrayOps.append(ASTArray(None)) - continue - - def parser() -> ASTExpression: - return self._parse_expression() + static = False + const = False + volatile = False + restrict = False + while True: + if not static: + if self.skip_word_and_ws('static'): + static = True + continue + if not const: + if self.skip_word_and_ws('const'): + const = True + continue + if not volatile: + if self.skip_word_and_ws('volatile'): + volatile = True + continue + if not restrict: + if self.skip_word_and_ws('restrict'): + restrict = True + continue + break + vla = False if static else self.skip_string_and_ws('*') + if vla: + if not self.skip_string(']'): + self.fail("Expected ']' in end of array operator.") + size = None + else: + if self.skip_string(']'): + size = None + else: - value = self._parse_expression_fallback([']'], parser) - if not self.skip_string(']'): - self.fail("Expected ']' in end of array operator.") - arrayOps.append(ASTArray(value)) - continue + def parser(): + return self._parse_expression() + size = self._parse_expression_fallback([']'], parser) + self.skip_ws() + if not self.skip_string(']'): + self.fail("Expected ']' in end of array operator.") + arrayOps.append(ASTArray(static, const, volatile, restrict, vla, size)) else: break param = self._parse_parameters(paramMode) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 8f8c609ff..490a1f689 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -581,9 +581,9 @@ class Documenter: isprivate = membername.startswith('_') keep = False - if getattr(member, '__sphinx_mock__', False): + if safe_getattr(member, '__sphinx_mock__', False): # mocked module or object - keep = False + pass elif want_all and membername.startswith('__') and \ membername.endswith('__') and len(membername) > 4: # special __methods__ @@ -1077,30 +1077,15 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() - super().add_directive_header(sig) + if inspect.is_singledispatch_function(self.object): + self.add_singledispatch_directive_header(sig) + else: + super().add_directive_header(sig) if inspect.iscoroutinefunction(self.object): self.add_line(' :async:', sourcename) - -class SingledispatchFunctionDocumenter(FunctionDocumenter): - """ - Specialized Documenter subclass for singledispatch'ed functions. - """ - objtype = 'singledispatch_function' - directivetype = 'function' - member_order = 30 - - # before FunctionDocumenter - priority = FunctionDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - return (super().can_document_member(member, membername, isattr, parent) and - inspect.is_singledispatch_function(member)) - - def add_directive_header(self, sig: str) -> None: + def add_singledispatch_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() # intercept generated directive headers @@ -1140,6 +1125,14 @@ class SingledispatchFunctionDocumenter(FunctionDocumenter): func.__signature__ = sig.replace(parameters=params) # type: ignore +class SingledispatchFunctionDocumenter(FunctionDocumenter): + """ + Used to be a specialized Documenter subclass for singledispatch'ed functions. + + Retained for backwards compatibility, now does the same as the FunctionDocumenter + """ + + class DecoratorDocumenter(FunctionDocumenter): """ Specialized Documenter subclass for decorator functions. @@ -1474,7 +1467,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return args def add_directive_header(self, sig: str) -> None: - super().add_directive_header(sig) + meth = self.parent.__dict__.get(self.objpath[-1]) + if inspect.is_singledispatch_method(meth): + self.add_singledispatch_directive_header(sig) + else: + super().add_directive_header(sig) sourcename = self.get_sourcename() obj = self.parent.__dict__.get(self.object_name, self.object) @@ -1490,28 +1487,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: def document_members(self, all_members: bool = False) -> None: pass - -class SingledispatchMethodDocumenter(MethodDocumenter): - """ - Specialized Documenter subclass for singledispatch'ed methods. - """ - objtype = 'singledispatch_method' - directivetype = 'method' - member_order = 50 - - # before MethodDocumenter - priority = MethodDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - if super().can_document_member(member, membername, isattr, parent) and parent.object: - meth = parent.object.__dict__.get(membername) - return inspect.is_singledispatch_method(meth) - else: - return False - - def add_directive_header(self, sig: str) -> None: + def add_singledispatch_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() # intercept generated directive headers @@ -1552,6 +1528,14 @@ class SingledispatchMethodDocumenter(MethodDocumenter): func.__signature__ = sig.replace(parameters=params) # type: ignore +class SingledispatchMethodDocumenter(MethodDocumenter): + """ + Used to be a specialized Documenter subclass for singledispatch'ed methods. + + Retained for backwards compatibility, now does the same as the MethodDocumenter + """ + + class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for attributes. @@ -1603,18 +1587,19 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: - if not self._datadescriptor: - # obtain annotation for this attribute - annotations = getattr(self.parent, '__annotations__', {}) - if annotations and self.objpath[-1] in annotations: - objrepr = stringify_typehint(annotations.get(self.objpath[-1])) - self.add_line(' :type: ' + objrepr, sourcename) - else: - key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) - if self.analyzer and key in self.analyzer.annotations: - self.add_line(' :type: ' + self.analyzer.annotations[key], - sourcename) + # obtain type annotation for this attribute + annotations = getattr(self.parent, '__annotations__', {}) + if annotations and self.objpath[-1] in annotations: + objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + self.add_line(' :type: ' + objrepr, sourcename) + else: + key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) + if self.analyzer and key in self.analyzer.annotations: + self.add_line(' :type: ' + self.analyzer.annotations[key], + sourcename) + # data descriptors do not have useful values + if not self._datadescriptor: try: if self.object is INSTANCEATTR: pass @@ -1769,10 +1754,8 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(DataDocumenter) app.add_autodocumenter(DataDeclarationDocumenter) app.add_autodocumenter(FunctionDocumenter) - app.add_autodocumenter(SingledispatchFunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) - app.add_autodocumenter(SingledispatchMethodDocumenter) app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(PropertyDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 155e57b30..539d72057 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -771,6 +771,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_directive('autosummary', Autosummary) app.add_role('autolink', AutoLink()) app.connect('builder-inited', process_generate_options) + app.add_config_value('autosummary_context', {}, True) app.add_config_value('autosummary_generate', [], True, [bool]) app.add_config_value('autosummary_generate_overwrite', True, False) app.add_config_value('autosummary_mock_imports', diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 85f491d40..acc2a2883 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -66,6 +66,7 @@ class DummyApplication: self._warncount = 0 self.warningiserror = False + self.config.add('autosummary_context', {}, True, None) self.config.init_values() def emit_firstresult(self, *args: Any) -> None: @@ -175,7 +176,7 @@ class AutosummaryRenderer: def generate_autosummary_content(name: str, obj: Any, parent: Any, template: AutosummaryRenderer, template_name: str, imported_members: bool, app: Any, - recursive: bool) -> str: + recursive: bool, context: Dict) -> str: doc = get_documenter(app, obj, parent) def skip_member(obj: Any, name: str, objtype: str) -> bool: @@ -224,6 +225,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, return public, items ns = {} # type: Dict[str, Any] + ns.update(context) if doc.objtype == 'module': ns['members'] = dir(obj) @@ -329,8 +331,12 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, _warn(__('[autosummary] failed to import %r: %s') % (entry.name, e)) continue + context = {} + if app: + context.update(app.config.autosummary_context) + content = generate_autosummary_content(name, obj, parent, template, entry.template, - imported_members, app, entry.recursive) + imported_members, app, entry.recursive, context) filename = os.path.join(path, name + suffix) if os.path.isfile(filename): diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 5b6bf4b7f..c6ff67bec 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -14,7 +14,7 @@ import sys import tokenize from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING from tokenize import COMMENT, NL -from typing import Any, Dict, List, Tuple +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 @@ -233,41 +233,33 @@ class VariableCommentPicker(ast.NodeVisitor): self.deforders = {} # type: Dict[str, int] super().__init__() - def add_entry(self, name: str) -> None: + def get_qualname_for(self, name: str) -> Optional[List[str]]: + """Get qualified name for given object as a list of string.""" if self.current_function: if self.current_classes and self.context[-1] == "__init__": # store variable comments inside __init__ method of classes - definition = self.context[:-1] + [name] + return self.context[:-1] + [name] else: - return + return None else: - definition = self.context + [name] + return self.context + [name] - self.deforders[".".join(definition)] = next(self.counter) + def add_entry(self, name: str) -> None: + qualname = self.get_qualname_for(name) + if qualname: + self.deforders[".".join(qualname)] = next(self.counter) def add_variable_comment(self, name: str, comment: str) -> None: - if self.current_function: - if self.current_classes and self.context[-1] == "__init__": - # store variable comments inside __init__ method of classes - context = ".".join(self.context[:-1]) - else: - return - else: - context = ".".join(self.context) - - self.comments[(context, name)] = comment + qualname = self.get_qualname_for(name) + if qualname: + basename = ".".join(qualname[:-1]) + self.comments[(basename, name)] = comment def add_variable_annotation(self, name: str, annotation: ast.AST) -> None: - if self.current_function: - if self.current_classes and self.context[-1] == "__init__": - # store variable comments inside __init__ method of classes - context = ".".join(self.context[:-1]) - else: - return - else: - context = ".".join(self.context) - - self.annotations[(context, name)] = unparse(annotation) + qualname = self.get_qualname_for(name) + if qualname: + basename = ".".join(qualname[:-1]) + self.annotations[(basename, name)] = unparse(annotation) def get_self(self) -> ast.arg: """Returns the name of first argument if in function.""" @@ -288,18 +280,12 @@ class VariableCommentPicker(ast.NodeVisitor): def visit_Import(self, node: ast.Import) -> None: """Handles Import node and record it to definition orders.""" for name in node.names: - if name.asname: - self.add_entry(name.asname) - else: - self.add_entry(name.name) + self.add_entry(name.asname or name.name) - def visit_ImportFrom(self, node: ast.Import) -> None: + def visit_ImportFrom(self, node: ast.ImportFrom) -> None: """Handles Import node and record it to definition orders.""" for name in node.names: - if name.asname: - self.add_entry(name.asname) - else: - self.add_entry(name.name) + self.add_entry(name.asname or name.name) def visit_Assign(self, node: ast.Assign) -> None: """Handles Assign node and pick up a variable comment.""" diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 346698240..1c2282b23 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -377,6 +377,8 @@ div.body p.centered { /* -- tables ---------------------------------------------------------------- */ table.docutils { + margin-top: 10px; + margin-bottom: 10px; border: 0; border-collapse: collapse; } diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index c2e683eee..72da10dd1 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -75,8 +75,13 @@ def _stringify_py37(annotation: Any) -> str: qualname = stringify(annotation.__origin__) # ex. Union elif hasattr(annotation, '__qualname__'): qualname = '%s.%s' % (module, annotation.__qualname__) + elif hasattr(annotation, '__origin__'): + # instantiated generic provided by a user + qualname = stringify(annotation.__origin__) else: - qualname = repr(annotation) + # we weren't able to extract the base type, appending arguments would + # only make them appear twice + return repr(annotation) if getattr(annotation, '__args__', None): if qualname == 'Union': @@ -91,7 +96,7 @@ def _stringify_py37(annotation: Any) -> str: return '%s[[%s], %s]' % (qualname, args, returns) elif str(annotation).startswith('typing.Annotated'): # for py39+ return stringify(annotation.__args__[0]) - elif annotation._special: + elif getattr(annotation, '_special', False): return qualname else: args = ', '.join(stringify(a) for a in annotation.__args__) diff --git a/tests/roots/test-ext-autodoc/target/typed_vars.py b/tests/roots/test-ext-autodoc/target/typed_vars.py index b0782787e..65302fa44 100644 --- a/tests/roots/test-ext-autodoc/target/typed_vars.py +++ b/tests/roots/test-ext-autodoc/target/typed_vars.py @@ -6,11 +6,20 @@ attr2: str attr3 = '' # type: str +class _Descriptor: + def __init__(self, name): + self.__doc__ = "This is {}".format(name) + def __get__(self): + pass + + class Class: attr1: int = 0 attr2: int attr3 = 0 # type: int + descr4: int = _Descriptor("descr4") + def __init__(self): self.attr4: int = 0 #: attr4 self.attr5: int #: attr5 diff --git a/tests/roots/test-templating/_templates/autosummary/class.rst b/tests/roots/test-templating/_templates/autosummary/class.rst index 7f1536173..6f505649c 100644 --- a/tests/roots/test-templating/_templates/autosummary/class.rst +++ b/tests/roots/test-templating/_templates/autosummary/class.rst @@ -3,6 +3,7 @@ {% block methods %} .. note:: autosummary/class.rst method block overloading + {{ sentence }} {{ super() }} {% endblock %} diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 3907e3465..51ae3ddc3 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1511,6 +1511,13 @@ def test_autodoc_typed_instance_variables(app): ' attr6', '', '', + ' .. py:attribute:: Class.descr4', + ' :module: target.typed_vars', + ' :type: int', + '', + ' This is descr4', + '', + '', '.. py:data:: attr1', ' :module: target.typed_vars', ' :type: str', @@ -1596,6 +1603,21 @@ def test_singledispatch(): ] +@pytest.mark.usefixtures('setup_test') +def test_singledispatch_autofunction(): + options = {} + actual = do_autodoc(app, 'function', 'target.singledispatch.func', options) + assert list(actual) == [ + '', + '.. py:function:: func(arg, kwarg=None)', + ' func(arg: int, kwarg=None)', + ' func(arg: str, kwarg=None)', + ' :module: target.singledispatch', + '', + ' A function for general use.', + '', + ] + @pytest.mark.skipif(sys.version_info < (3, 8), reason='singledispatchmethod is available since python3.8') @pytest.mark.usefixtures('setup_test') @@ -1623,6 +1645,23 @@ def test_singledispatchmethod(): ] +@pytest.mark.skipif(sys.version_info < (3, 8), + reason='singledispatchmethod is available since python3.8') +@pytest.mark.usefixtures('setup_test') +def test_singledispatchmethod_automethod(): + options = {} + actual = do_autodoc(app, 'method', 'target.singledispatchmethod.Foo.meth', options) + assert list(actual) == [ + '', + '.. py:method:: Foo.meth(arg, kwarg=None)', + ' Foo.meth(arg: int, kwarg=None)', + ' Foo.meth(arg: str, kwarg=None)', + ' :module: target.singledispatchmethod', + '', + ' A method for general use.', + '', + ] + @pytest.mark.usefixtures('setup_test') @pytest.mark.skipif(pyximport is None, reason='cython is not installed') def test_cython(): diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index f85a0e62e..237519fcc 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -362,6 +362,21 @@ def test_function_definitions(): check('function', 'void f(enum E e)', {1: 'f'}) check('function', 'void f(union E e)', {1: 'f'}) + # array declarators + check('function', 'void f(int arr[])', {1: 'f'}) + check('function', 'void f(int arr[*])', {1: 'f'}) + cvrs = ['', 'const', 'volatile', 'restrict', 'restrict volatile const'] + for cvr in cvrs: + space = ' ' if len(cvr) != 0 else '' + check('function', 'void f(int arr[{}*])'.format(cvr), {1: 'f'}) + check('function', 'void f(int arr[{}])'.format(cvr), {1: 'f'}) + check('function', 'void f(int arr[{}{}42])'.format(cvr, space), {1: 'f'}) + check('function', 'void f(int arr[static{}{} 42])'.format(space, cvr), {1: 'f'}) + check('function', 'void f(int arr[{}{}static 42])'.format(cvr, space), {1: 'f'}, + output='void f(int arr[static{}{} 42])'.format(space, cvr)) + check('function', 'void f(int arr[const static volatile 42])', {1: 'f'}, + output='void f(int arr[static volatile const 42])') + def test_union_definitions(): check('struct', 'A', {1: 'A'}) diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index 7e7a20663..114694166 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -353,7 +353,8 @@ def test_autosummary_imported_members(app, status, warning): sys.modules.pop('autosummary_dummy_package', None) -@pytest.mark.sphinx(testroot='ext-autodoc') +@pytest.mark.sphinx(testroot='ext-autodoc', + confoverrides={'extensions': ['sphinx.ext.autosummary']}) def test_generate_autosummary_docs_property(app): with patch('sphinx.ext.autosummary.generate.find_autosummary_in_files') as mock: mock.return_value = [AutosummaryEntry('target.methods.Base.prop', 'prop', None, False)] diff --git a/tests/test_templating.py b/tests/test_templating.py index becacda0d..f2c1d563b 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -33,3 +33,17 @@ def test_autosummary_class_template_overloading(make_app, app_params): result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').read_text() assert 'autosummary/class.rst method block overloading' in result + assert 'foobar' not in result + + +@pytest.mark.sphinx('html', testroot='templating', + confoverrides={'autosummary_context': {'sentence': 'foobar'}}) +def test_autosummary_context(make_app, app_params): + args, kwargs = app_params + app = make_app(*args, **kwargs) + setup_documenters(app) + app.builder.build_update() + + result = (app.outdir / 'generated' / 'sphinx.application.TemplateBridge.html').read_text() + assert 'autosummary/class.rst method block overloading' in result + assert 'foobar' in result diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index f6fd35fb0..41d2a19c2 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -10,7 +10,7 @@ import sys from numbers import Integral -from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional +from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional, Generic import pytest @@ -24,6 +24,11 @@ class MyClass1: class MyClass2(MyClass1): __qualname__ = '<MyClass2>' +T = TypeVar('T') + +class MyList(List[T]): + pass + def test_stringify(): assert stringify(int) == "int" @@ -42,6 +47,7 @@ def test_stringify_type_hints_containers(): assert stringify(Tuple[str, str, str]) == "Tuple[str, str, str]" assert stringify(Tuple[str, ...]) == "Tuple[str, ...]" assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]" + assert stringify(MyList[Tuple[int, int]]) == "test_util_typing.MyList[Tuple[int, int]]" @pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') |