diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index 5222592abd5..a07042781e8 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -127,6 +127,17 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. automodule:: foo :no-undoc-members: + .. tip:: + + You can use autodoc directive options to temporarily override or + extend default options which takes list as an input. For example:: + + .. autoclass:: Noodle + :members: eat + :private-members: +_spicy, _garlickly + + .. versionchanged:: 3.5 + The default options can be overridden or extended temporarily. * Members without docstrings will be left out, unless you give the ``undoc-members`` flag option:: diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index b4c5fd28d92..c932c6f9f74 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -37,6 +37,9 @@ 'ignore-module-all', 'exclude-members', 'member-order', 'imported-members'] +AUTODOC_EXTENDABLE_OPTIONS = ['members', 'private-members', 'special-members', + 'exclude-members'] + class DummyOptionSpec(dict): """An option_spec allows any options.""" @@ -90,7 +93,19 @@ def process_documenter_options(documenter: "Type[Documenter]", config: Config, o else: negated = options.pop('no-' + name, True) is None if name in config.autodoc_default_options and not negated: - options[name] = config.autodoc_default_options[name] + if name in options and isinstance(config.autodoc_default_options[name], str): + # take value from options if present or extend it + # with autodoc_default_options if necessary + if name in AUTODOC_EXTENDABLE_OPTIONS: + if options[name] is not None and options[name].startswith('+'): + options[name] = ','.join([config.autodoc_default_options[name], + options[name][1:]]) + else: + options[name] = config.autodoc_default_options[name] + + elif options.get(name) is not None: + # remove '+' from option argument if there's nothing to merge it with + options[name] = options[name].lstrip('+') return Options(assemble_option_dict(options.items(), documenter.option_spec)) diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 93b80f7adf5..42aa3e80828 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -569,6 +569,36 @@ def test_autodoc_members(app): ' .. py:method:: Base.inheritedstaticmeth(cls)' ] + # ALL-members override autodoc_default_options + options = {"members": None} + app.config.autodoc_default_options["members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedclassmeth()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)' + ] + + # members override autodoc_default_options + options = {"members": "inheritedmeth"} + app.config.autodoc_default_options["members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedmeth()', + ] + + # members extends autodoc_default_options + options = {"members": "+inheritedmeth"} + app.config.autodoc_default_options["members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)' + ] + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_exclude_members(app): @@ -588,6 +618,57 @@ def test_autodoc_exclude_members(app): '.. py:class:: Base()', ] + # + has no effect when autodoc_default_options are not present + options = {"members": None, + "exclude-members": "+inheritedmeth,inheritedstaticmeth"} + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedclassmeth()' + ] + + # exclude-members overrides autodoc_default_options + options = {"members": None, + "exclude-members": "inheritedmeth"} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedclassmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)' + ] + + # exclude-members extends autodoc_default_options + options = {"members": None, + "exclude-members": "+inheritedmeth"} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedclassmeth()', + ] + + # no exclude-members causes use autodoc_default_options + options = {"members": None} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth,inheritedmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedclassmeth()', + ] + + # empty exclude-members cancels autodoc_default_options + options = {"members": None, + "exclude-members": None} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth,inheritedmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedclassmeth()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)' + ] + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_undoc_members(app): @@ -612,6 +693,48 @@ def test_autodoc_undoc_members(app): ' .. py:method:: Class.undocmeth()' ] + # use autodoc_default_options + options = {"members": None} + app.config.autodoc_default_options["undoc-members"] = None + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + ' .. py:attribute:: Class.skipattr', + ' .. py:method:: Class.skipmeth()', + ' .. py:attribute:: Class.udocattr', + ' .. py:method:: Class.undocmeth()' + ] + + # options negation work check + options = {"members": None, + "no-undoc-members": None} + app.config.autodoc_default_options["undoc-members"] = None + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.skipmeth()', + ' .. py:attribute:: Class.udocattr', + ] + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_inherited_members(app): @@ -713,6 +836,38 @@ def test_autodoc_special_members(app): ' .. py:method:: Class.undocmeth()' ] + # specific special methods from autodoc_default_options + options = {"undoc-members": None} + app.config.autodoc_default_options["special-members"] = "__special2__" + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__special2__()', + ] + + # specific special methods option with autodoc_default_options + options = {"undoc-members": None, + "special-members": "__init__,__special1__"} + app.config.autodoc_default_options["special-members"] = "__special2__" + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ] + + # specific special methods merge with autodoc_default_options + options = {"undoc-members": None, + "special-members": "+__init__,__special1__"} + app.config.autodoc_default_options["special-members"] = "__special2__" + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ' .. py:method:: Class.__special2__()', + ] + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_ignore_module_all(app): @@ -740,7 +895,7 @@ def test_autodoc_ignore_module_all(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_noindex(app): - options = {"noindex": True} + options = {"noindex": None} actual = do_autodoc(app, 'module', 'target', options) assert list(actual) == [ '', @@ -821,7 +976,7 @@ def test_autodoc_inner_class(app): '', ] - options['show-inheritance'] = True + options['show-inheritance'] = None actual = do_autodoc(app, 'class', 'target.InnerChild', options) assert list(actual) == [ '', @@ -865,7 +1020,7 @@ def test_autodoc_staticmethod(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_descriptor(app): options = {"members": None, - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'class', 'target.descriptor.Class', options) assert list(actual) == [ '', @@ -893,7 +1048,7 @@ def test_autodoc_descriptor(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_cached_property(app): options = {"members": None, - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'class', 'target.cached_property.Foo', options) assert list(actual) == [ '', @@ -913,8 +1068,8 @@ def test_autodoc_member_order(app): # case member-order='bysource' options = {"members": None, 'member-order': 'bysource', - "undoc-members": True, - 'private-members': True} + "undoc-members": None, + 'private-members': None} actual = do_autodoc(app, 'class', 'target.Class', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Class(arg)', @@ -938,8 +1093,8 @@ def test_autodoc_member_order(app): # case member-order='groupwise' options = {"members": None, 'member-order': 'groupwise', - "undoc-members": True, - 'private-members': True} + "undoc-members": None, + 'private-members': None} actual = do_autodoc(app, 'class', 'target.Class', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Class(arg)', @@ -962,8 +1117,8 @@ def test_autodoc_member_order(app): # case member-order=None options = {"members": None, - "undoc-members": True, - 'private-members': True} + "undoc-members": None, + 'private-members': None} actual = do_autodoc(app, 'class', 'target.Class', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Class(arg)', @@ -990,7 +1145,7 @@ def test_autodoc_module_member_order(app): # case member-order='bysource' options = {"members": 'foo, Bar, baz, qux, Quux, foobar', 'member-order': 'bysource', - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'module', 'target.sort_by_all', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:module:: target.sort_by_all', @@ -1005,8 +1160,8 @@ def test_autodoc_module_member_order(app): # case member-order='bysource' and ignore-module-all options = {"members": 'foo, Bar, baz, qux, Quux, foobar', 'member-order': 'bysource', - "undoc-members": True, - "ignore-module-all": True} + "undoc-members": None, + "ignore-module-all": None} actual = do_autodoc(app, 'module', 'target.sort_by_all', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:module:: target.sort_by_all', @@ -1053,7 +1208,7 @@ def test_autodoc_class_scope(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_class_attributes(app): options = {"members": None, - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'class', 'target.AttCls', options) assert list(actual) == [ '', @@ -1163,7 +1318,7 @@ def test_autoattribute_instance_attributes(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_slots(app): options = {"members": None, - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'module', 'target.slots', options) assert list(actual) == [ '', @@ -1561,7 +1716,7 @@ def test_partialmethod_undoc_members(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_typed_instance_variables(app): options = {"members": None, - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'module', 'target.typed_vars', options) assert list(actual) == [ '', @@ -1660,8 +1815,8 @@ def test_autodoc_typed_instance_variables(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_typed_inherited_instance_variables(app): options = {"members": None, - "undoc-members": True, - "inherited-members": True} + "undoc-members": None, + "inherited-members": None} actual = do_autodoc(app, 'class', 'target.typed_vars.Derived', options) assert list(actual) == [ '', @@ -2284,7 +2439,7 @@ def test_type_union_operator(app): @pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_hide_value(app): - options = {'members': True} + options = {'members': None} actual = do_autodoc(app, 'module', 'target.hide_value', options) assert list(actual) == [ '', diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py index 74ddb02f98e..e729f220de0 100644 --- a/tests/test_ext_autodoc_autoclass.py +++ b/tests/test_ext_autodoc_autoclass.py @@ -53,7 +53,7 @@ def test_classes(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_instance_variable(app): - options = {'members': True} + options = {'members': None} actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options) assert list(actual) == [ '', @@ -77,8 +77,8 @@ def test_instance_variable(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_inherited_instance_variable(app): - options = {'members': True, - 'inherited-members': True} + options = {'members': None, + 'inherited-members': None} actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options) assert list(actual) == [ '', @@ -228,7 +228,7 @@ def test_slots_attribute(app): @pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_show_inheritance_for_subclass_of_generic_type(app): - options = {'show-inheritance': True} + options = {'show-inheritance': None} actual = do_autodoc(app, 'class', 'target.classes.Quux', options) assert list(actual) == [ '', diff --git a/tests/test_ext_autodoc_automodule.py b/tests/test_ext_autodoc_automodule.py index df57724b304..3332704bbe6 100644 --- a/tests/test_ext_autodoc_automodule.py +++ b/tests/test_ext_autodoc_automodule.py @@ -18,7 +18,7 @@ @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_empty_all(app): - options = {'members': True} + options = {'members': None} actual = do_autodoc(app, 'module', 'target.empty_all', options) assert list(actual) == [ '', @@ -39,6 +39,6 @@ def test_empty_all(app): def test_subclass_of_mocked_object(app): sys.modules.pop('target', None) # unload target module to clear the module cache - options = {'members': True} + options = {'members': None} actual = do_autodoc(app, 'module', 'target.need_mocks', options) assert '.. py:class:: Inherited(*args: Any, **kwargs: Any)' in actual diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index bae684397b5..06bf39c24c4 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -486,7 +486,7 @@ def test_mocked_module_imports(app, warning): confoverrides={'autodoc_typehints': "signature"}) def test_autodoc_typehints_signature(app): options = {"members": None, - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'module', 'target.typehints', options) assert list(actual) == [ '', @@ -552,7 +552,7 @@ def test_autodoc_typehints_signature(app): confoverrides={'autodoc_typehints': "none"}) def test_autodoc_typehints_none(app): options = {"members": None, - "undoc-members": True} + "undoc-members": None} actual = do_autodoc(app, 'module', 'target.typehints', options) assert list(actual) == [ '', @@ -851,7 +851,7 @@ def test_autodoc_default_options(app): assert ' .. py:attribute:: EnumCls.val4' not in actual # with :members: = True - app.config.autodoc_default_options = {'members': True} + app.config.autodoc_default_options = {'members': None} actual = do_autodoc(app, 'class', 'target.enums.EnumCls') assert ' .. py:attribute:: EnumCls.val1' in actual assert ' .. py:attribute:: EnumCls.val4' not in actual