From 6e3ae3fc6f640119d7bbbd9ddee4fd2713e4477c Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 12 May 2020 13:05:00 +0200 Subject: [PATCH 01/38] basic CSS: remove first/last-child margin not only for

--- sphinx/themes/basic/static/basic.css_t | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 45908ece1..01cee109a 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -426,13 +426,13 @@ table.citation td { border-bottom: none; } -th > p:first-child, -td > p:first-child { +th > :first-child, +td > :first-child { margin-top: 0px; } -th > p:last-child, -td > p:last-child { +th > :last-child, +td > :last-child { margin-bottom: 0px; } @@ -505,11 +505,11 @@ ol.upperroman { list-style: upper-roman; } -li > p:first-child { +li > :first-child { margin-top: 0px; } -li > p:last-child { +li > :last-child { margin-bottom: 0px; } @@ -557,7 +557,7 @@ dl { margin-bottom: 15px; } -dd > p:first-child { +dd > :first-child { margin-top: 0px; } From 1b9e52613e8c498c80bc9051e60270a5cac0a052 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 12 May 2020 13:08:41 +0200 Subject: [PATCH 02/38] basic CSS: consistent 7px padding on topic and sidebar --- sphinx/themes/basic/static/basic.css_t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 01cee109a..e572959be 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -316,7 +316,7 @@ img.align-default, .figure.align-default { div.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; - padding: 7px 7px 0 7px; + padding: 7px; background-color: #ffe; width: 40%; float: right; @@ -336,7 +336,7 @@ div.admonition, div.topic, pre, div[class|="highlight"] { div.topic { border: 1px solid #ccc; - padding: 7px 7px 0 7px; + padding: 7px; margin: 10px 0 10px 0; overflow-x: auto; } From aad6c2d489689bb1dcac390a9ace8981819f2684 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 12 May 2020 13:22:53 +0200 Subject: [PATCH 03/38] basic CSS: Remove margin-bottom from last child in sidebar/topic/admonition --- sphinx/themes/basic/static/basic.css_t | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index e572959be..1adf01368 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -360,10 +360,6 @@ div.admonition dt { font-weight: bold; } -div.admonition dl { - margin-bottom: 0; -} - p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; @@ -374,6 +370,14 @@ div.body p.centered { margin-top: 25px; } +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + /* -- tables ---------------------------------------------------------------- */ table.docutils { From 2c8de2b41e4b2854a6ada9767ec7fdd8b8de31ef Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 12 May 2020 14:56:03 +0200 Subject: [PATCH 04/38] basic CSS: remove margin-bottom in last element of

--- sphinx/themes/basic/static/basic.css_t | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 1adf01368..d8875796a 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -575,6 +575,11 @@ dd { margin-left: 30px; } +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + dt:target, span.highlighted { background-color: #fbe54e; } From e0d884f39bd874d43eff377f048a81eb6dd76f2f Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 12 May 2020 15:14:45 +0200 Subject: [PATCH 05/38] basic CSS: ul/ol margins --- sphinx/themes/basic/static/basic.css_t | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index d8875796a..67ab2a63e 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -509,14 +509,30 @@ ol.upperroman { list-style: upper-roman; } -li > :first-child { +ol > li:first-child > :first-child, +ul > li:first-child > :first-child { margin-top: 0px; } -li > :last-child { +ol ol > li:first-child > :first-child, +ol ul > li:first-child > :first-child, +ul ol > li:first-child > :first-child, +ul ul > li:first-child > :first-child { + margin-top: revert; +} + +ol > li:last-child > :last-child, +ul > li:last-child > :last-child { margin-bottom: 0px; } +ol ol > li:last-child > :last-child, +ol ul > li:last-child > :last-child, +ul ol > li:last-child > :last-child, +ul ul > li:last-child > :last-child { + margin-bottom: revert; +} + dl.footnote > dt, dl.citation > dt { float: left; From 0dc6e3ac458dd6c10ed2c7bd03ca95bd694d7ec6 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 12 May 2020 15:56:39 +0200 Subject: [PATCH 06/38] basic CSS: move code block margin to outer
--- sphinx/themes/basic/static/basic.css_t | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 67ab2a63e..55ee887f3 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -680,6 +680,10 @@ span.pre { hyphens: none; } +div[class^="highlight-"] { + margin: 1em 0; +} + td.linenos pre { border: 0; background-color: transparent; @@ -688,7 +692,6 @@ td.linenos pre { table.highlighttable { display: block; - margin: 1em 0; } table.highlighttable tbody { @@ -717,11 +720,12 @@ table.highlighttable td.code { display: block; } +div.highlight pre, table.highlighttable pre { margin: 0; } -div.code-block-caption + div > table.highlighttable { +div.code-block-caption + div { margin-top: 0; } @@ -735,10 +739,6 @@ div.code-block-caption code { background-color: transparent; } -div.code-block-caption + div > div.highlight > pre { - margin-top: 0; -} - table.highlighttable td.linenos, div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ user-select: none; From 810f37c9cf41dd9f793148038b5c2f7ce5e23ec1 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 12 May 2020 16:25:32 +0200 Subject: [PATCH 07/38] basic CSS: Add hlist margin --- sphinx/themes/basic/static/basic.css_t | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 55ee887f3..841b652ae 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -482,6 +482,10 @@ table.field-list td, table.field-list th { /* -- hlist styles ---------------------------------------------------------- */ +table.hlist { + margin: 1em 0; +} + table.hlist td { vertical-align: top; } From d326f8cc7ed6d30ca69a4af3ebbc9dc97047be21 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Sun, 24 May 2020 17:34:06 +0200 Subject: [PATCH 08/38] basic CSS: avoid unnecessary padding to the left of line numbers --- sphinx/themes/basic/static/basic.css_t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t index 45908ece1..a7c58569f 100644 --- a/sphinx/themes/basic/static/basic.css_t +++ b/sphinx/themes/basic/static/basic.css_t @@ -680,7 +680,7 @@ table.highlighttable td { } table.highlighttable td.linenos { - padding: 0 0.5em; + padding-right: 0.5em; } table.highlighttable td.code { From 1f47eefb2f1c94b4003cb96bd263aaa730f26a9e Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Sun, 24 May 2020 18:17:51 +0200 Subject: [PATCH 09/38] CSS: Respect Pygments background color ... except "classic" and "scrolls" themes. --- sphinx/themes/agogo/static/agogo.css_t | 1 - sphinx/themes/haiku/static/haiku.css_t | 1 - sphinx/themes/nature/static/nature.css_t | 6 ------ sphinx/themes/pyramid/static/pyramid.css_t | 6 ------ sphinx/themes/scrolls/static/scrolls.css_t | 2 +- sphinx/themes/sphinxdoc/static/sphinxdoc.css_t | 1 - sphinx/themes/traditional/static/traditional.css_t | 1 - 7 files changed, 1 insertion(+), 17 deletions(-) diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index d74604ac1..ff43186da 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -207,7 +207,6 @@ div.document .section:first-child { div.document div.highlight { padding: 3px; - background-color: #eeeeec; border-top: 2px solid #dddddd; border-bottom: 2px solid #dddddd; margin-top: .8em; diff --git a/sphinx/themes/haiku/static/haiku.css_t b/sphinx/themes/haiku/static/haiku.css_t index cac283400..21caac0fd 100644 --- a/sphinx/themes/haiku/static/haiku.css_t +++ b/sphinx/themes/haiku/static/haiku.css_t @@ -319,7 +319,6 @@ pre { border-width: thin; margin: 0 0 12px 0; padding: 0.8em; - background-color: #f0f0f0; } hr { diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t index 13c64467b..34893b86a 100644 --- a/sphinx/themes/nature/static/nature.css_t +++ b/sphinx/themes/nature/static/nature.css_t @@ -184,10 +184,6 @@ div.admonition p.admonition-title + p { display: inline; } -div.highlight{ - background-color: white; -} - div.note { background-color: #eee; border: 1px solid #ccc; @@ -217,8 +213,6 @@ p.admonition-title:after { pre { padding: 10px; - background-color: White; - color: #222; line-height: 1.2em; border: 1px solid #C6C9CB; font-size: 1.1em; diff --git a/sphinx/themes/pyramid/static/pyramid.css_t b/sphinx/themes/pyramid/static/pyramid.css_t index 48cb2ab6f..dafd898d5 100644 --- a/sphinx/themes/pyramid/static/pyramid.css_t +++ b/sphinx/themes/pyramid/static/pyramid.css_t @@ -229,10 +229,6 @@ div.admonition { padding: 10px 20px 10px 60px; } -div.highlight{ - background-color: white; -} - div.note { border: 2px solid #7a9eec; border-right-style: none; @@ -286,8 +282,6 @@ p.admonition-title:after { pre { padding: 10px; - background-color: #fafafa; - color: #222; line-height: 1.2em; border: 2px solid #C6C9CB; font-size: 1.1em; diff --git a/sphinx/themes/scrolls/static/scrolls.css_t b/sphinx/themes/scrolls/static/scrolls.css_t index b01d4ad9f..e484f8c4f 100644 --- a/sphinx/themes/scrolls/static/scrolls.css_t +++ b/sphinx/themes/scrolls/static/scrolls.css_t @@ -188,7 +188,7 @@ a:hover { } pre { - background: #ededed url(metal.png); + background-image: url(metal.png); border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; padding: 5px; diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t index 626d8c3f2..f9961ae36 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t @@ -247,7 +247,6 @@ pre { line-height: 120%; padding: 0.5em; border: 1px solid #ccc; - background-color: #f8f8f8; } pre a { diff --git a/sphinx/themes/traditional/static/traditional.css_t b/sphinx/themes/traditional/static/traditional.css_t index 68719dcd1..0120f83a5 100644 --- a/sphinx/themes/traditional/static/traditional.css_t +++ b/sphinx/themes/traditional/static/traditional.css_t @@ -632,7 +632,6 @@ th { pre { font-family: monospace; padding: 5px; - color: #00008b; border-left: none; border-right: none; } From b345acc2840a792162845e3a1a3456c347fac08e Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Sun, 24 May 2020 20:39:31 +0200 Subject: [PATCH 10/38] classic theme: default codetextcolor/codebgcolor doesn't override Pygments Closes #7720. --- sphinx/themes/classic/theme.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/themes/classic/theme.conf b/sphinx/themes/classic/theme.conf index d7389eaa0..cd16f4b47 100644 --- a/sphinx/themes/classic/theme.conf +++ b/sphinx/themes/classic/theme.conf @@ -25,8 +25,8 @@ headtextcolor = #20435c headlinkcolor = #c60f0f linkcolor = #355f7c visitedlinkcolor = #355f7c -codebgcolor = #eeffcc -codetextcolor = #333333 +codebgcolor = unset +codetextcolor = unset bodyfont = sans-serif headfont = 'Trebuchet MS', sans-serif From c39ed6efbd6126599e5ff6a8180fef0b52086798 Mon Sep 17 00:00:00 2001 From: Cielquan Date: Sun, 31 May 2020 12:43:47 +0200 Subject: [PATCH 11/38] added config to log missing c/py coverage --- sphinx/ext/coverage.py | 49 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index e8157848f..a5defd894 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -22,6 +22,9 @@ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging +from sphinx.util.console import ( # type: ignore + blue, darkblue, darkgreen, green, red, teal, turquoise +) from sphinx.util.inspect import safe_getattr logger = logging.getLogger(__name__) @@ -121,6 +124,14 @@ class CoverageBuilder(Builder): write_header(op, filename) for typ, name in sorted(undoc): op.write(' * %-50s [%9s]\n' % (name, typ)) + if self.config.coverage_print_missing_c_items: + if self.app.quiet or self.app.warningiserror: + logger.warning(__('undocumented c api: %s [%s] in file %s'), + name, typ, filename) + else: + logger.info(red('undocumented ') + darkgreen('c ') + + darkblue('api ') + '%-30s' % (name + " [%9s]" % typ) + + red(' - in file ') + filename) op.write('\n') def ignore_pyobj(self, full_name: str) -> bool: @@ -239,16 +250,46 @@ class CoverageBuilder(Builder): if undoc['funcs']: op.write('Functions:\n') op.writelines(' * %s\n' % x for x in undoc['funcs']) + if self.config.coverage_print_missing_py_items: + if self.app.quiet or self.app.warningiserror: + for func in undoc['funcs']: + logger.warning(__('undocumented python function: %s :: %s'), + name, func) + else: + for func in undoc['funcs']: + logger.info(red('undocumented ') + green('py ') + + teal('function ') + '%-30s' % func + + red(' - in module ') + name) op.write('\n') if undoc['classes']: op.write('Classes:\n') - for name, methods in sorted( + for class_name, methods in sorted( undoc['classes'].items()): if not methods: - op.write(' * %s\n' % name) + op.write(' * %s\n' % class_name) + if self.config.coverage_print_missing_py_items: + if self.app.quiet or self.app.warningiserror: + logger.warning(__('undocumented python class: %s :: %s'), + name, class_name) + else: + logger.info(red('undocumented ') + green('py ') + + blue('class ') + '%-30s' % class_name + + red(' - in module ') + name) else: - op.write(' * %s -- missing methods:\n\n' % name) + op.write(' * %s -- missing methods:\n\n' % class_name) op.writelines(' - %s\n' % x for x in methods) + if self.config.coverage_print_missing_py_items: + if self.app.quiet or self.app.warningiserror: + for meth in methods: + logger.warning( + __('undocumented python method: %s :: %s :: %s'), + name, class_name, meth) + else: + for meth in methods: + logger.info(red('undocumented ') + green('py ') + + turquoise('method ') + '%-30s' % + (class_name + '.' + meth) + + red(' - in module ') + name) op.write('\n') if failed: @@ -273,4 +314,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('coverage_ignore_c_items', {}, False) app.add_config_value('coverage_write_headline', True, False) app.add_config_value('coverage_skip_undoc_in_source', False, False) + app.add_config_value('coverage_print_missing_c_items', False, False) + app.add_config_value('coverage_print_missing_py_items', False, False) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} From e6cf7c0922276bf26e0662680a6be8cf6f1e1ccf Mon Sep 17 00:00:00 2001 From: Cielquan Date: Sun, 31 May 2020 16:17:56 +0200 Subject: [PATCH 12/38] merged new config options into coverage_show_missing_items --- sphinx/ext/coverage.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index a5defd894..e8196bb4a 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -124,7 +124,7 @@ class CoverageBuilder(Builder): write_header(op, filename) for typ, name in sorted(undoc): op.write(' * %-50s [%9s]\n' % (name, typ)) - if self.config.coverage_print_missing_c_items: + if self.config.coverage_show_missing_items: if self.app.quiet or self.app.warningiserror: logger.warning(__('undocumented c api: %s [%s] in file %s'), name, typ, filename) @@ -250,7 +250,7 @@ class CoverageBuilder(Builder): if undoc['funcs']: op.write('Functions:\n') op.writelines(' * %s\n' % x for x in undoc['funcs']) - if self.config.coverage_print_missing_py_items: + if self.config.coverage_show_missing_items: if self.app.quiet or self.app.warningiserror: for func in undoc['funcs']: logger.warning(__('undocumented python function: %s :: %s'), @@ -267,7 +267,7 @@ class CoverageBuilder(Builder): undoc['classes'].items()): if not methods: op.write(' * %s\n' % class_name) - if self.config.coverage_print_missing_py_items: + if self.config.coverage_show_missing_items: if self.app.quiet or self.app.warningiserror: logger.warning(__('undocumented python class: %s :: %s'), name, class_name) @@ -278,7 +278,7 @@ class CoverageBuilder(Builder): else: op.write(' * %s -- missing methods:\n\n' % class_name) op.writelines(' - %s\n' % x for x in methods) - if self.config.coverage_print_missing_py_items: + if self.config.coverage_show_missing_items: if self.app.quiet or self.app.warningiserror: for meth in methods: logger.warning( @@ -314,6 +314,5 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('coverage_ignore_c_items', {}, False) app.add_config_value('coverage_write_headline', True, False) app.add_config_value('coverage_skip_undoc_in_source', False, False) - app.add_config_value('coverage_print_missing_c_items', False, False) - app.add_config_value('coverage_print_missing_py_items', False, False) + app.add_config_value('coverage_show_missing_items', False, False) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} From a59f83b6bdaf86dec6058cf72ae12eae967426c5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 May 2020 23:11:10 +0900 Subject: [PATCH 13/38] Add sphinx.util.inspect:signature_from_ast() --- sphinx/util/inspect.py | 12 ++++++++---- tests/test_util_inspect.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 0f3f47562..d4928c847 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -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) diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index fa0ff84e1..6a14dc1ac 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -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): From 640bb2e586f882df305568bf99aacb151120f84f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 May 2020 19:18:21 +0900 Subject: [PATCH 14/38] pycode: Detect @overload decorators --- sphinx/pycode/__init__.py | 3 ++ sphinx/pycode/parser.py | 34 ++++++++++++++++ tests/test_pycode_parser.py | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 4879fb349..963680a54 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -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: diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 3762c72cc..ec89a3616 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -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.""" diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py index 398c9f8a4..71847f04f 100644 --- a/tests/test_pycode_parser.py +++ b/tests/test_pycode_parser.py @@ -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 == {} From baaa32c4a30cff751d0f1fd4b37c7f22582b9688 Mon Sep 17 00:00:00 2001 From: Cielquan Date: Sun, 31 May 2020 16:40:37 +0200 Subject: [PATCH 15/38] fixed line length --- sphinx/ext/coverage.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index e8196bb4a..064a2cb36 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -130,7 +130,8 @@ class CoverageBuilder(Builder): name, typ, filename) else: logger.info(red('undocumented ') + darkgreen('c ') + - darkblue('api ') + '%-30s' % (name + " [%9s]" % typ) + + darkblue('api ') + + '%-30s' % (name + " [%9s]" % typ) + red(' - in file ') + filename) op.write('\n') @@ -253,8 +254,9 @@ class CoverageBuilder(Builder): if self.config.coverage_show_missing_items: if self.app.quiet or self.app.warningiserror: for func in undoc['funcs']: - logger.warning(__('undocumented python function: %s :: %s'), - name, func) + logger.warning( + __('undocumented python function: %s :: %s'), + name, func) else: for func in undoc['funcs']: logger.info(red('undocumented ') + green('py ') + @@ -269,8 +271,9 @@ class CoverageBuilder(Builder): op.write(' * %s\n' % class_name) if self.config.coverage_show_missing_items: if self.app.quiet or self.app.warningiserror: - logger.warning(__('undocumented python class: %s :: %s'), - name, class_name) + logger.warning( + __('undocumented python class: %s :: %s'), + name, class_name) else: logger.info(red('undocumented ') + green('py ') + blue('class ') + '%-30s' % class_name + @@ -282,7 +285,8 @@ class CoverageBuilder(Builder): if self.app.quiet or self.app.warningiserror: for meth in methods: logger.warning( - __('undocumented python method: %s :: %s :: %s'), + __('undocumented python method:' + + ' %s :: %s :: %s'), name, class_name, meth) else: for meth in methods: From 754b48c8d66dd327289a83e9eea704c3e12e3377 Mon Sep 17 00:00:00 2001 From: Cielquan Date: Sun, 31 May 2020 16:49:02 +0200 Subject: [PATCH 16/38] reduced colors on info level --- sphinx/ext/coverage.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 064a2cb36..3d186f0cb 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -129,8 +129,7 @@ class CoverageBuilder(Builder): logger.warning(__('undocumented c api: %s [%s] in file %s'), name, typ, filename) else: - logger.info(red('undocumented ') + darkgreen('c ') + - darkblue('api ') + + logger.info(red('undocumented ') + 'c ' + 'api ' + '%-30s' % (name + " [%9s]" % typ) + red(' - in file ') + filename) op.write('\n') @@ -259,9 +258,8 @@ class CoverageBuilder(Builder): name, func) else: for func in undoc['funcs']: - logger.info(red('undocumented ') + green('py ') + - teal('function ') + '%-30s' % func + - red(' - in module ') + name) + logger.info(red('undocumented ') + 'py ' + 'function ' + + '%-30s' % func + red(' - in module ') + name) op.write('\n') if undoc['classes']: op.write('Classes:\n') @@ -275,8 +273,8 @@ class CoverageBuilder(Builder): __('undocumented python class: %s :: %s'), name, class_name) else: - logger.info(red('undocumented ') + green('py ') + - blue('class ') + '%-30s' % class_name + + logger.info(red('undocumented ') + 'py ' + + 'class ' + '%-30s' % class_name + red(' - in module ') + name) else: op.write(' * %s -- missing methods:\n\n' % class_name) @@ -290,8 +288,8 @@ class CoverageBuilder(Builder): name, class_name, meth) else: for meth in methods: - logger.info(red('undocumented ') + green('py ') + - turquoise('method ') + '%-30s' % + logger.info(red('undocumented ') + 'py ' + + 'method ' + '%-30s' % (class_name + '.' + meth) + red(' - in module ') + name) op.write('\n') From fb2f777079506a6ed36077ae98c2b267d25b16b5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 24 May 2020 23:34:09 +0900 Subject: [PATCH 17/38] Close #3610: autodoc: Support overloaded functions --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 32 +++++++++++++-- .../roots/test-ext-autodoc/target/overload.py | 41 +++++++++++++++++++ tests/test_ext_autodoc.py | 33 +++++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 tests/roots/test-ext-autodoc/target/overload.py diff --git a/CHANGES b/CHANGES index 763412353..8c09f876c 100644 --- a/CHANGES +++ b/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 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index e247d3bd2..4fb6a4ce9 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -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) @@ -1693,8 +1703,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 +1726,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) diff --git a/tests/roots/test-ext-autodoc/target/overload.py b/tests/roots/test-ext-autodoc/target/overload.py new file mode 100644 index 000000000..87d1c8533 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/overload.py @@ -0,0 +1,41 @@ +from typing import 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 diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 5cc1f3c22..0a651950b 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1787,6 +1787,39 @@ 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:: 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() From d83f34794cdcb8b07d3f67e2ee7acf8f15295cf4 Mon Sep 17 00:00:00 2001 From: Cielquan Date: Sun, 31 May 2020 17:56:14 +0200 Subject: [PATCH 18/38] fixed color imports in coverage.py --- sphinx/ext/coverage.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 3d186f0cb..536b3b9d2 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -22,9 +22,7 @@ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.console import ( # type: ignore - blue, darkblue, darkgreen, green, red, teal, turquoise -) +from sphinx.util.console import red # type: ignore from sphinx.util.inspect import safe_getattr logger = logging.getLogger(__name__) From 42b755db2e8e2d6f0540e7208d190302f3ac611d Mon Sep 17 00:00:00 2001 From: Cielquan Date: Sun, 31 May 2020 18:38:21 +0200 Subject: [PATCH 19/38] added test for show_missing_items True --- tests/test_ext_coverage.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_ext_coverage.py b/tests/test_ext_coverage.py index 16f53112b..1ac828dc5 100644 --- a/tests/test_ext_coverage.py +++ b/tests/test_ext_coverage.py @@ -28,6 +28,8 @@ def test_build(app, status, warning): assert ' * mod -- No module named mod' # in the "failed import" section + assert "undocumented py" not in status.getvalue() + c_undoc = (app.outdir / 'c.txt').read_text() assert c_undoc.startswith('Undocumented C API elements\n' '===========================\n') @@ -46,6 +48,8 @@ def test_build(app, status, warning): assert 'Class' in undoc_py['autodoc_target']['classes'] assert 'undocmeth' in undoc_py['autodoc_target']['classes']['Class'] + assert "undocumented c" not in status.getvalue() + @pytest.mark.sphinx('coverage', testroot='ext-coverage') def test_coverage_ignore_pyobjects(app, status, warning): @@ -64,3 +68,16 @@ Classes: ''' assert actual == expected + + +@pytest.mark.sphinx('coverage', confoverrides={'coverage_show_missing_items': True}) +def test_show_missing_items(app, status, warning): + app.builder.build_all() + + assert "undocumented" in status.getvalue() + + assert "py function raises" in status.getvalue() + assert "py class Base" in status.getvalue() + assert "py method Class.roger" in status.getvalue() + + assert "c api Py_SphinxTest [ function]" in status.getvalue() From a7725ad8ca03ede875945cf5aafedb96f84071e6 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 1 Jun 2020 01:37:15 +0900 Subject: [PATCH 20/38] Close #7247: linkcheck: Add linkcheck_request_headers --- CHANGES | 2 ++ doc/usage/configuration.rst | 26 ++++++++++++++++++++++++++ sphinx/builders/linkcheck.py | 31 ++++++++++++++++++++++++++----- tests/test_build_linkcheck.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 763412353..51839ff69 100644 --- a/CHANGES +++ b/CHANGES @@ -82,6 +82,8 @@ Features added * #7596: py domain: Change a type annotation for variables to a hyperlink * #7582: napoleon: a type for attribute are represented like type annotation * #7734: napoleon: overescaped trailing underscore on attribute +* #7247: linkcheck: Add :confval:`linkcheck_request_headers` to send custom HTTP + headers for specific host * #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow handlers to raise specified exceptions diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index bc483fa1c..cdcc2a561 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -2390,6 +2390,32 @@ Options for the linkcheck builder .. versionadded:: 1.1 +.. confval:: linkcheck_request_headers + + A dictionary that maps baseurls to HTTP request headers. + + The key is a URL base string like ``"https://sphinx-doc.org/"``. To specify + headers for other hosts, ``"*"`` can be used. It matches all hosts only when + the URL does not match other settings. + + The value is a dictionary that maps header name to its value. + + Example: + + .. code-block:: python + + linkcheck_request_headers = { + "https://sphinx-doc.org/": { + "Accept": "text/html", + "Accept-Encoding": "utf-8", + }, + "*": { + "Accept": "text/html,application/xhtml+xml", + } + } + + .. versionadded:: 3.1 + .. confval:: linkcheck_retries The number of times the linkcheck builder will attempt to check a URL before diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 9fe689ec9..dd5317087 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -16,7 +16,7 @@ import threading from html.parser import HTMLParser from os import path from typing import Any, Dict, List, Set, Tuple -from urllib.parse import unquote +from urllib.parse import unquote, urlparse from docutils import nodes from docutils.nodes import Node @@ -36,6 +36,11 @@ from sphinx.util.requests import is_ssl_error logger = logging.getLogger(__name__) +DEFAULT_REQUEST_HEADERS = { + 'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8', +} + + class AnchorCheckParser(HTMLParser): """Specialized HTML parser that looks for a specific anchor.""" @@ -107,13 +112,25 @@ class CheckExternalLinksBuilder(Builder): def check_thread(self) -> None: kwargs = { 'allow_redirects': True, - 'headers': { - 'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8', - }, - } + } # type: Dict if self.app.config.linkcheck_timeout: kwargs['timeout'] = self.app.config.linkcheck_timeout + def get_request_headers() -> Dict: + url = urlparse(uri) + candidates = ["%s://%s" % (url.scheme, url.netloc), + "%s://%s/" % (url.scheme, url.netloc), + uri, + "*"] + + for u in candidates: + if u in self.config.linkcheck_request_headers: + headers = dict(DEFAULT_REQUEST_HEADERS) + headers.update(self.config.linkcheck_request_headers[u]) + return headers + + return {} + def check_uri() -> Tuple[str, str, int]: # split off anchor if '#' in uri: @@ -139,6 +156,9 @@ class CheckExternalLinksBuilder(Builder): else: auth_info = None + # update request headers for the URL + kwargs['headers'] = get_request_headers() + try: if anchor and self.app.config.linkcheck_anchors: # Read the whole document and see if #anchor exists @@ -337,6 +357,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('linkcheck_ignore', [], None) app.add_config_value('linkcheck_auth', [], None) + app.add_config_value('linkcheck_request_headers', {}, None) app.add_config_value('linkcheck_retries', 1, None) app.add_config_value('linkcheck_timeout', None, None, [int]) app.add_config_value('linkcheck_workers', 5, None) diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index 54bde6b68..d1fec550f 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -124,3 +124,36 @@ def test_auth(app, status, warning): assert c_kwargs['auth'] == 'authinfo2' else: assert not c_kwargs['auth'] + + +@pytest.mark.sphinx( + 'linkcheck', testroot='linkcheck', freshenv=True, + confoverrides={'linkcheck_request_headers': { + "https://localhost:7777/": { + "Accept": "text/html", + }, + "http://www.sphinx-doc.org": { # no slash at the end + "Accept": "application/json", + }, + "*": { + "X-Secret": "open sesami", + } + }}) +def test_linkcheck_request_headers(app, status, warning): + mock_req = mock.MagicMock() + mock_req.return_value = 'fake-response' + + with mock.patch.multiple('requests', get=mock_req, head=mock_req): + app.builder.build_all() + for args, kwargs in mock_req.call_args_list: + url = args[0] + headers = kwargs.get('headers', {}) + if "https://localhost:7777" in url: + assert headers["Accept"] == "text/html" + elif 'http://www.sphinx-doc.org' in url: + assert headers["Accept"] == "application/json" + elif 'https://www.google.com' in url: + assert headers["Accept"] == "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8" + assert headers["X-Secret"] == "open sesami" + else: + assert headers["Accept"] == "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8" From 49de4ab1a16664ebb343d353cf5d54d7dbd32318 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 1 Jun 2020 02:08:57 +0900 Subject: [PATCH 21/38] Support overloaded constructors (__call__, __init__ and __new__) --- sphinx/ext/autodoc/__init__.py | 46 ++++++++++++++--- .../roots/test-ext-autodoc/target/overload.py | 49 ++++++++++++++++++- tests/test_ext_autodoc.py | 21 ++++++++ 3 files changed, 107 insertions(+), 9 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 4fb6a4ce9..31301e01e 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1279,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) @@ -1299,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): @@ -1323,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 @@ -1332,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 @@ -1341,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 @@ -1351,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"), @@ -1380,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() diff --git a/tests/roots/test-ext-autodoc/target/overload.py b/tests/roots/test-ext-autodoc/target/overload.py index 87d1c8533..da43d32eb 100644 --- a/tests/roots/test-ext-autodoc/target/overload.py +++ b/tests/roots/test-ext-autodoc/target/overload.py @@ -1,4 +1,4 @@ -from typing import overload +from typing import Any, overload @overload @@ -39,3 +39,50 @@ class Math: 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""" diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 0a651950b..e80e8b357 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1796,6 +1796,27 @@ def test_overload(app): '.. 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', '', From 85da1b60723fa3a5109ac911e780ece68c23e4e1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 4 Jun 2020 00:31:53 +0900 Subject: [PATCH 22/38] Fix #7723: LaTeX: pdflatex crashed when URL contains a single quote --- CHANGES | 1 + sphinx/writers/latex.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ae466b9e6..fdcf5fdd6 100644 --- a/CHANGES +++ b/CHANGES @@ -130,6 +130,7 @@ Bugs fixed * #7646: handle errors on event handlers * #4187: LaTeX: EN DASH disappears from PDF bookmarks in Japanese documents * #7701: LaTeX: Anonymous indirect hyperlink target causes duplicated labels +* #7723: LaTeX: pdflatex crashed when URL contains a single quote * #7756: py domain: The default value for positional only argument is not shown * C++, fix rendering and xrefs in nested names explicitly starting in global scope, e.g., ``::A::B``. diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 6e7f5021b..113e28390 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1977,7 +1977,8 @@ class LaTeXTranslator(SphinxTranslator): # mainly, %, #, {, } and \ need escaping via a \ escape # in \href, the tilde is allowed and must be represented literally return self.encode(text).replace('\\textasciitilde{}', '~').\ - replace('\\sphinxhyphen{}', '-') + replace('\\sphinxhyphen{}', '-').\ + replace('\\textquotesingle{}', "'") def visit_Text(self, node: Text) -> None: text = self.encode(node.astext()) From 1e7d9269e935d8e2e7cf6d962a3179b5f46a7263 Mon Sep 17 00:00:00 2001 From: Luke Lau Date: Tue, 2 Jun 2020 17:56:01 +0100 Subject: [PATCH 23/38] Let option directive support args in the form of foo[=bar] This slightly tweaks the regex so that command line flags of the form foo[=N] are properly split up. This is useful for any programs that can take `--foo` as a flag on its own as well as with an extra argument `--foo=12`. --- sphinx/domains/std.py | 2 +- tests/roots/test-root/objects.txt | 4 +++- tests/test_build_html.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 6dc597022..fbbed3a6b 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -43,7 +43,7 @@ logger = logging.getLogger(__name__) # RE for option descriptions -option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)') +option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=[]+)(=?\s*.*)') # RE for grammar tokens token_re = re.compile(r'`(\w+)`', re.U) diff --git a/tests/roots/test-root/objects.txt b/tests/roots/test-root/objects.txt index f713e076c..adb090a44 100644 --- a/tests/roots/test-root/objects.txt +++ b/tests/roots/test-root/objects.txt @@ -180,7 +180,9 @@ Others .. option:: arg -Link to :option:`perl +p`, :option:`--ObjC++`, :option:`--plugin.option`, :option:`create-auth-token` and :option:`arg` +.. option:: -j[=N] + +Link to :option:`perl +p`, :option:`--ObjC++`, :option:`--plugin.option`, :option:`create-auth-token`, :option:`arg` and :option:`-j` .. program:: hg diff --git a/tests/test_build_html.py b/tests/test_build_html.py index b3406b74c..8d616adaf 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -331,6 +331,8 @@ def test_html4_output(app, status, warning): 'create-auth-token'), (".//a[@class='reference internal'][@href='#cmdoption-perl-arg-arg']/code/span", 'arg'), + (".//a[@class='reference internal'][@href='#cmdoption-perl-j']/code/span", + '-j'), (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", 'hg'), (".//a[@class='reference internal'][@href='#cmdoption-hg-arg-commit']/code/span", From 84e9494f80d56ab33fa8f0abfcbcab61b7b7e6d7 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 4 Jun 2020 01:00:40 +0900 Subject: [PATCH 24/38] Update CHANGES for PR #7770 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index ae466b9e6..f08b5363d 100644 --- a/CHANGES +++ b/CHANGES @@ -80,6 +80,8 @@ Features added * #7143: py domain: Add ``:final:`` option to :rst:dir:`py:class:`, :rst:dir:`py:exception:` and :rst:dir:`py:method:` directives * #7596: py domain: Change a type annotation for variables to a hyperlink +* #7770: std domain: :rst:dir:`option` directive support arguments in the form + of ``foo[=bar]`` * #7582: napoleon: a type for attribute are represented like type annotation * #7734: napoleon: overescaped trailing underscore on attribute * #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow From cb9dd0a0b47f4b5af6453d1b5e604774ab2076c5 Mon Sep 17 00:00:00 2001 From: Brandon Houghton Date: Wed, 3 Jun 2020 15:33:58 -0400 Subject: [PATCH 25/38] Fix reported dir when throwing ApplicationError if path.exists(self.outdir) and not path.isdir(self.outdir), error reported self.srcdir which should be self.outdir --- sphinx/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/application.py b/sphinx/application.py index bd23c86e7..d84a2c975 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -165,7 +165,7 @@ class Sphinx: if path.exists(self.outdir) and not path.isdir(self.outdir): raise ApplicationError(__('Output directory (%s) is not a directory') % - self.srcdir) + self.outdir) if self.srcdir == self.outdir: raise ApplicationError(__('Source directory and destination ' From 07fb907feabfd96ef21ac0c4048a0f13789dd4ab Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 5 Jun 2020 00:55:58 +0900 Subject: [PATCH 26/38] Update CHANGES for PR #7781 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index f08b5363d..ef1d36002 100644 --- a/CHANGES +++ b/CHANGES @@ -123,6 +123,7 @@ Bugs fixed * #7671: autosummary: The location of import failure warning is missing * #7535: sphinx-autogen: crashes when custom template uses inheritance * #7536: sphinx-autogen: crashes when template uses i18n feature +* #7781: sphinx-build: Wrong error message when outdir is not directory * #7653: sphinx-quickstart: Fix multiple directory creation for nested relpath * #2785: html: Bad alignment of equation links * #7581: napoleon: bad parsing of inline code in attribute docstrings From 948ae0f90810b8c2d744cc7809b84628fdd6c71f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 5 Jun 2020 02:41:42 +0900 Subject: [PATCH 27/38] Update CHANGES for PR #7718 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index f262131db..4d8806566 100644 --- a/CHANGES +++ b/CHANGES @@ -129,6 +129,8 @@ Bugs fixed * #7781: sphinx-build: Wrong error message when outdir is not directory * #7653: sphinx-quickstart: Fix multiple directory creation for nested relpath * #2785: html: Bad alignment of equation links +* #7718: html theme: some themes does not respect background color of Pygments + style (agogo, haiku, nature, pyramid, scrolls, sphinxdoc and traditional) * #7581: napoleon: bad parsing of inline code in attribute docstrings * #7628: imgconverter: runs imagemagick once unnecessary for builders not supporting images From 60f4b45bbe1da75e466042b50fcb96f437f776af Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 5 Jun 2020 02:43:24 +0900 Subject: [PATCH 28/38] Update CHANGES for PR #7717 --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4d8806566..22bc0743f 100644 --- a/CHANGES +++ b/CHANGES @@ -62,7 +62,8 @@ Features added variables for custom templates * #7530: html: Support nested elements * #7481: html theme: Add right margin to footnote/citation labels -* #7482: html theme: CSS spacing for code blocks with captions and line numbers +* #7482, #7717: html theme: CSS spacing for code blocks with captions and line + numbers * #7443: html theme: Add new options :confval:`globaltoc_collapse` and :confval:`globaltoc_includehidden` to control the behavior of globaltoc in sidebar From cc1f15246c42556a94248a8e4abc2ee13695ea82 Mon Sep 17 00:00:00 2001 From: Cielquan Date: Fri, 5 Jun 2020 09:26:53 +0200 Subject: [PATCH 29/38] added test for show_missing_items True in quiet mode --- tests/test_ext_coverage.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_ext_coverage.py b/tests/test_ext_coverage.py index 1ac828dc5..033a1c1b1 100644 --- a/tests/test_ext_coverage.py +++ b/tests/test_ext_coverage.py @@ -81,3 +81,15 @@ def test_show_missing_items(app, status, warning): assert "py method Class.roger" in status.getvalue() assert "c api Py_SphinxTest [ function]" in status.getvalue() + + +@pytest.mark.sphinx('coverage', confoverrides={'coverage_show_missing_items': True}) +def test_show_missing_items_quiet(app, status, warning): + app.quiet = True + app.builder.build_all() + + assert "undocumented python function: autodoc_target :: raises" in warning.getvalue() + assert "undocumented python class: autodoc_target :: Base" in warning.getvalue() + assert "undocumented python method: autodoc_target :: Class :: roger" in warning.getvalue() + + assert "undocumented c api: Py_SphinxTest [function]" in warning.getvalue() From 740cb3351eca1dd4058f6ca9bb794b6ecd648fa9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 5 Jun 2020 23:02:58 +0900 Subject: [PATCH 30/38] Update CHANGES for PR #7721 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 22bc0743f..58b45dfe0 100644 --- a/CHANGES +++ b/CHANGES @@ -75,6 +75,8 @@ Features added * #7542: html theme: Make admonition/topic/sidebar scrollable * #7543: html theme: Add top and bottom margins to tables * #7695: html theme: Add viewport meta tag for basic theme +* #7721: html theme: classic: default codetextcolor/codebgcolor doesn't override + Pygments * C and C++: allow semicolon in the end of declarations. * C++, parse parameterized noexcept specifiers. * #7294: C++, parse expressions with user-defined literals. From f33eb15b83deb37aac98cb6b7e51d82b8f36739b Mon Sep 17 00:00:00 2001 From: Christian Riedel Date: Fri, 5 Jun 2020 17:42:26 +0200 Subject: [PATCH 31/38] documented coverage_show_missing_items confval --- doc/usage/extensions/coverage.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/usage/extensions/coverage.rst b/doc/usage/extensions/coverage.rst index 46d31053c..8c8054d5f 100644 --- a/doc/usage/extensions/coverage.rst +++ b/doc/usage/extensions/coverage.rst @@ -51,4 +51,11 @@ should check: .. versionadded:: 1.1 -.. _Python regular expressions: https://docs.python.org/library/re \ No newline at end of file +.. conval:: coverage_show_missing_items + + Print objects that are missing to standard output also. + ``False`` by default. + + .. versionadded:: 3.1 + +.. _Python regular expressions: https://docs.python.org/library/re From cb5e094c0aee9cbfce56991c78dfe95a688ed5ab Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 6 Jun 2020 01:16:17 +0900 Subject: [PATCH 32/38] Update CHANGES for PR #7760 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 58b45dfe0..38a062516 100644 --- a/CHANGES +++ b/CHANGES @@ -142,6 +142,8 @@ Bugs fixed * #4187: LaTeX: EN DASH disappears from PDF bookmarks in Japanese documents * #7701: LaTeX: Anonymous indirect hyperlink target causes duplicated labels * #7756: py domain: The default value for positional only argument is not shown +* #7760: coverage: Add :confval:`coverage_show_missing_items` to show coverage + result to console * C++, fix rendering and xrefs in nested names explicitly starting in global scope, e.g., ``::A::B``. * C, fix rendering and xrefs in nested names explicitly starting From 2fb5232b12ed1045be9117fe193522496882eda3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 6 Jun 2020 01:21:35 +0900 Subject: [PATCH 33/38] Fix typo --- doc/usage/extensions/coverage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/usage/extensions/coverage.rst b/doc/usage/extensions/coverage.rst index 8c8054d5f..db989f38d 100644 --- a/doc/usage/extensions/coverage.rst +++ b/doc/usage/extensions/coverage.rst @@ -51,7 +51,7 @@ should check: .. versionadded:: 1.1 -.. conval:: coverage_show_missing_items +.. confval:: coverage_show_missing_items Print objects that are missing to standard output also. ``False`` by default. From 6ccab6c1504cbc8fb29d31bf1900b8bbe9413589 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 6 Jun 2020 01:45:46 +0900 Subject: [PATCH 34/38] Close #7792: setuptools: Support ``--verbosity`` option --- CHANGES | 1 + sphinx/setup_command.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 38a062516..0da886354 100644 --- a/CHANGES +++ b/CHANGES @@ -90,6 +90,7 @@ Features added * #7734: napoleon: overescaped trailing underscore on attribute * #7247: linkcheck: Add :confval:`linkcheck_request_headers` to send custom HTTP headers for specific host +* #7792: setuptools: Support ``--verbosity`` option * #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow handlers to raise specified exceptions * #7295: C++, parse (trailing) requires clauses. diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index f55158575..2c0076304 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -84,6 +84,7 @@ class BuildDoc(Command): ('link-index', 'i', 'Link index.html to the master doc'), ('copyright', None, 'The copyright string'), ('pdb', None, 'Start pdb on exception'), + ('verbosity', 'v', 'increase verbosity (can be repeated)'), ('nitpicky', 'n', 'nit-picky mode, warn about all missing references'), ('keep-going', None, 'With -W, keep going when getting warnings'), ] @@ -189,7 +190,7 @@ class BuildDoc(Command): builder, confoverrides, status_stream, freshenv=self.fresh_env, warningiserror=self.warning_is_error, - keep_going=self.keep_going) + verbosity=self.verbosity, keep_going=self.keep_going) app.build(force_all=self.all_files) if app.statuscode: raise DistutilsExecError( From 8bd5f8b214b25242487948931f8e9b9d1bcf9f98 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 30 May 2020 21:17:20 +0900 Subject: [PATCH 35/38] autodoc: Support TypeVar (refs: #7722) --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 45 ++++++++++++++++++- .../roots/test-ext-autodoc/target/typevar.py | 15 +++++++ tests/test_ext_autodoc.py | 40 +++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/target/typevar.py diff --git a/CHANGES b/CHANGES index 38a062516..274ee9f5f 100644 --- a/CHANGES +++ b/CHANGES @@ -52,6 +52,7 @@ Features added * #2106: autodoc: Support multiple signatures on docstring * #4422: autodoc: Support GenericAlias in Python 3.7 or above * #3610: autodoc: Support overloaded functions +* #7722: autodoc: Support TypeVar * #7466: autosummary: headings in generated documents are not translated * #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a caption to the toctree diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 31301e01e..09db26948 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -16,7 +16,7 @@ import warnings from inspect import Parameter, Signature from types import ModuleType from typing import ( - Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union + Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union ) from docutils.statemachine import StringList @@ -1644,6 +1644,48 @@ class GenericAliasDocumenter(DataDocumenter): super().add_content(content) +class TypeVarDocumenter(DataDocumenter): + """ + Specialized Documenter subclass for TypeVars. + """ + + objtype = 'typevar' + directivetype = 'data' + priority = DataDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return isinstance(member, TypeVar) and isattr # type: ignore + + def add_directive_header(self, sig: str) -> None: + self.options.annotation = SUPPRESS # type: ignore + super().add_directive_header(sig) + + def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: + if ignore is not None: + warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." + % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + + if self.object.__doc__ != TypeVar.__doc__: + return super().get_doc() + else: + return [] + + def add_content(self, more_content: Any, no_docstring: bool = False) -> None: + attrs = [repr(self.object.__name__)] + for constraint in self.object.__constraints__: + attrs.append(stringify_typehint(constraint)) + if self.object.__covariant__: + attrs.append("covariant=True") + if self.object.__contravariant__: + attrs.append("contravariant=True") + + content = StringList([_('alias of TypeVar(%s)') % ", ".join(attrs)], source='') + super().add_content(content) + + class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for methods (normal, static and class). @@ -2017,6 +2059,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(DataDocumenter) app.add_autodocumenter(DataDeclarationDocumenter) app.add_autodocumenter(GenericAliasDocumenter) + app.add_autodocumenter(TypeVarDocumenter) app.add_autodocumenter(FunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) diff --git a/tests/roots/test-ext-autodoc/target/typevar.py b/tests/roots/test-ext-autodoc/target/typevar.py new file mode 100644 index 000000000..9c6b0eab0 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/typevar.py @@ -0,0 +1,15 @@ +from typing import TypeVar + +#: T1 +T1 = TypeVar("T1") + +T2 = TypeVar("T2") # A TypeVar not having doc comment + +#: T3 +T3 = TypeVar("T3", int, str) + +#: T4 +T4 = TypeVar("T4", covariant=True) + +#: T5 +T5 = TypeVar("T5", contravariant=True) diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index e80e8b357..e4ec4a815 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1618,6 +1618,46 @@ def test_autodoc_GenericAlias(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_TypeVar(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.typevar', options) + assert list(actual) == [ + '', + '.. py:module:: target.typevar', + '', + '', + '.. py:data:: T1', + ' :module: target.typevar', + '', + ' T1', + '', + " alias of TypeVar('T1')", + '', + '.. py:data:: T3', + ' :module: target.typevar', + '', + ' T3', + '', + " alias of TypeVar('T3', int, str)", + '', + '.. py:data:: T4', + ' :module: target.typevar', + '', + ' T4', + '', + " alias of TypeVar('T4', covariant=True)", + '', + '.. py:data:: T5', + ' :module: target.typevar', + '', + ' T5', + '', + " alias of TypeVar('T5', contravariant=True)", + ] + + @pytest.mark.skipif(sys.version_info < (3, 9), reason='py39+ is required.') @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_Annotated(app): From f98987b24ecff3c5d30ecb34ce0c8515132cde87 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 7 Jun 2020 01:40:20 +0900 Subject: [PATCH 36/38] Fix #7791: autodoc: TypeError is raised on documenting singledispatch function --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 38a062516..988a29260 100644 --- a/CHANGES +++ b/CHANGES @@ -121,6 +121,7 @@ Bugs fixed * #7668: autodoc: wrong retann value is passed to a handler of autodoc-proccess-signature * #7711: autodoc: fails with ValueError when processing numpy objects +* #7791: autodoc: TypeError is raised on documenting singledispatch function * #7551: autosummary: a nested class is indexed as non-nested class * #7661: autosummary: autosummary directive emits warnings twices if failed to import the target module diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 31301e01e..109708d0e 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1222,7 +1222,15 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" - sig = inspect.signature(func) + try: + sig = inspect.signature(func) + except TypeError as exc: + logger.warning(__("Failed to get a function signature for %s: %s"), + self.fullname, exc) + return + except ValueError: + return + if len(sig.parameters) == 0: return @@ -1769,7 +1777,14 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" - sig = inspect.signature(func) + try: + sig = inspect.signature(func) + except TypeError as exc: + logger.warning(__("Failed to get a method signature for %s: %s"), + self.fullname, exc) + return + except ValueError: + return if len(sig.parameters) == 1: return From d0cdf073bd36aaa050858df510455b145bc2c4bd Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 8 Jun 2020 02:25:39 +0900 Subject: [PATCH 37/38] Update CHANGES for PR #7657 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 8649bc3bd..d19822de1 100644 --- a/CHANGES +++ b/CHANGES @@ -136,6 +136,7 @@ Bugs fixed * #2785: html: Bad alignment of equation links * #7718: html theme: some themes does not respect background color of Pygments style (agogo, haiku, nature, pyramid, scrolls, sphinxdoc and traditional) +* #7544: html theme: inconsistent padding in admonitions * #7581: napoleon: bad parsing of inline code in attribute docstrings * #7628: imgconverter: runs imagemagick once unnecessary for builders not supporting images From 62a9b324a2230d0c8dbfde579abd7ce4ec42d4fb Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 8 Jun 2020 22:37:01 +0900 Subject: [PATCH 38/38] Bump to 3.1.0 final --- CHANGES | 25 ++----------------------- sphinx/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/CHANGES b/CHANGES index aca79be5d..b3fffbd5e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ -Release 3.1.0 (in development) -============================== +Release 3.1.0 (released Jun 08, 2020) +===================================== Dependencies ------------ @@ -156,30 +156,9 @@ Bugs fixed * #7763: C and C++, don't crash during display stringification of unary expressions and fold expressions. -Testing --------- - Release 3.0.5 (in development) ============================== -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- - -Bugs fixed ----------- - -Testing --------- - Release 3.0.4 (released May 27, 2020) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 8caea80d0..e5aff31e0 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -32,7 +32,7 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '3.1.0+' +__version__ = '3.1.0' __released__ = '3.1.0' # used when Sphinx builds its own docs #: Version info for better programmatic use. @@ -43,7 +43,7 @@ __released__ = '3.1.0' # used when Sphinx builds its own docs #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (3, 1, 0, 'beta', 0) +version_info = (3, 1, 0, 'final', 0) package_dir = path.abspath(path.dirname(__file__))