mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x' into master_with_merged_3.x
This commit is contained in:
commit
fbafb308b8
11
CHANGES
11
CHANGES
@ -119,6 +119,7 @@ Features added
|
|||||||
info-field-list
|
info-field-list
|
||||||
* #8514: autodoc: Default values of overloaded functions are taken from actual
|
* #8514: autodoc: Default values of overloaded functions are taken from actual
|
||||||
implementation if they're ellipsis
|
implementation if they're ellipsis
|
||||||
|
* #8775: autodoc: Support type union operator (PEP-604) in Python 3.10 or above
|
||||||
* #8619: html: kbd role generates customizable HTML tags for compound keys
|
* #8619: html: kbd role generates customizable HTML tags for compound keys
|
||||||
* #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter
|
* #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter
|
||||||
for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()`
|
for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()`
|
||||||
@ -135,6 +136,7 @@ Features added
|
|||||||
* #8004: napoleon: Type definitions in Google style docstrings are rendered as
|
* #8004: napoleon: Type definitions in Google style docstrings are rendered as
|
||||||
references when :confval:`napoleon_preprocess_types` enabled
|
references when :confval:`napoleon_preprocess_types` enabled
|
||||||
* #6241: mathjax: Include mathjax.js only on the document using equations
|
* #6241: mathjax: Include mathjax.js only on the document using equations
|
||||||
|
* #8775: py domain: Support type union operator (PEP-604)
|
||||||
* #8651: std domain: cross-reference for a rubric having inline item is broken
|
* #8651: std domain: cross-reference for a rubric having inline item is broken
|
||||||
* #7642: std domain: Optimize case-insensitive match of term
|
* #7642: std domain: Optimize case-insensitive match of term
|
||||||
* #8681: viewcode: Support incremental build
|
* #8681: viewcode: Support incremental build
|
||||||
@ -158,6 +160,8 @@ Bugs fixed
|
|||||||
contains invalid type comments
|
contains invalid type comments
|
||||||
* #8693: autodoc: Default values for overloaded functions are rendered as string
|
* #8693: autodoc: Default values for overloaded functions are rendered as string
|
||||||
* #8134: autodoc: crashes when mocked decorator takes arguments
|
* #8134: autodoc: crashes when mocked decorator takes arguments
|
||||||
|
* #8800: autodoc: Uninitialized attributes in superclass are recognized as
|
||||||
|
undocumented
|
||||||
* #8306: autosummary: mocked modules are documented as empty page when using
|
* #8306: autosummary: mocked modules are documented as empty page when using
|
||||||
:recursive: option
|
:recursive: option
|
||||||
* #8232: graphviz: Image node is not rendered if graph file is in subdirectory
|
* #8232: graphviz: Image node is not rendered if graph file is in subdirectory
|
||||||
@ -168,13 +172,16 @@ Bugs fixed
|
|||||||
* #8123: html search: fix searching for terms containing + (Requires a custom
|
* #8123: html search: fix searching for terms containing + (Requires a custom
|
||||||
search language that does not split on +)
|
search language that does not split on +)
|
||||||
* #8665: html theme: Could not override globaltoc_maxdepth in theme.conf
|
* #8665: html theme: Could not override globaltoc_maxdepth in theme.conf
|
||||||
|
* #8446: html: consecutive spaces are displayed as single space
|
||||||
* #8745: i18n: crashes with KeyError when translation message adds a new auto
|
* #8745: i18n: crashes with KeyError when translation message adds a new auto
|
||||||
footnote reference
|
footnote reference
|
||||||
* #4304: linkcheck: Fix race condition that could lead to checking the
|
* #4304: linkcheck: Fix race condition that could lead to checking the
|
||||||
availability of the same URL twice
|
availability of the same URL twice
|
||||||
|
* #8791: linkcheck: The docname for each hyperlink is not displayed
|
||||||
* #7118: sphinx-quickstart: questionare got Mojibake if libreadline unavailable
|
* #7118: sphinx-quickstart: questionare got Mojibake if libreadline unavailable
|
||||||
* #8094: texinfo: image files on the different directory with document are not
|
* #8094: texinfo: image files on the different directory with document are not
|
||||||
copied
|
copied
|
||||||
|
* #8782: todo: Cross references in todolist get broken
|
||||||
* #8720: viewcode: module pages are generated for epub on incremental build
|
* #8720: viewcode: module pages are generated for epub on incremental build
|
||||||
* #8704: viewcode: anchors are generated in incremental build after singlehtml
|
* #8704: viewcode: anchors are generated in incremental build after singlehtml
|
||||||
* #8756: viewcode: highlighted code is generated even if not referenced
|
* #8756: viewcode: highlighted code is generated even if not referenced
|
||||||
@ -194,6 +201,8 @@ Bugs fixed
|
|||||||
:confval:`numfig` is not True
|
:confval:`numfig` is not True
|
||||||
* #8442: LaTeX: some indexed terms are ignored when using xelatex engine
|
* #8442: LaTeX: some indexed terms are ignored when using xelatex engine
|
||||||
(or pdflatex and :confval:`latex_use_xindy` set to True) with memoir class
|
(or pdflatex and :confval:`latex_use_xindy` set to True) with memoir class
|
||||||
|
* #8750: LaTeX: URLs as footnotes fail to show in PDF if originating from
|
||||||
|
inside function type signatures
|
||||||
* #8780: LaTeX: long words in narrow columns may not be hyphenated
|
* #8780: LaTeX: long words in narrow columns may not be hyphenated
|
||||||
* #8788: LaTeX: ``\titleformat`` last argument in sphinx.sty should be
|
* #8788: LaTeX: ``\titleformat`` last argument in sphinx.sty should be
|
||||||
bracketed, not braced (and is anyhow not needed)
|
bracketed, not braced (and is anyhow not needed)
|
||||||
@ -222,6 +231,8 @@ Bugs fixed
|
|||||||
* #8655: autodoc: Failed to generate document if target module contains an
|
* #8655: autodoc: Failed to generate document if target module contains an
|
||||||
object that raises an exception on ``hasattr()``
|
object that raises an exception on ``hasattr()``
|
||||||
* C, ``expr`` role should start symbol lookup in the current scope.
|
* C, ``expr`` role should start symbol lookup in the current scope.
|
||||||
|
* #8796: LaTeX: potentially critical low level TeX coding mistake has gone
|
||||||
|
unnoticed so far
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
--------
|
--------
|
||||||
|
@ -70,13 +70,14 @@ Project information
|
|||||||
The author name(s) of the document. The default value is ``'unknown'``.
|
The author name(s) of the document. The default value is ``'unknown'``.
|
||||||
|
|
||||||
.. confval:: copyright
|
.. confval:: copyright
|
||||||
.. confval:: project_copyright
|
|
||||||
|
|
||||||
A copyright statement in the style ``'2008, Author Name'``.
|
A copyright statement in the style ``'2008, Author Name'``.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. confval:: project_copyright
|
||||||
|
|
||||||
As an alias, ``project_copyright`` is also allowed.
|
An alias of :confval:`copyright`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
.. confval:: version
|
.. confval:: version
|
||||||
|
|
||||||
|
@ -108,11 +108,11 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
|
|
||||||
def init(self) -> None:
|
def init(self) -> None:
|
||||||
self.hyperlinks = {} # type: Dict[str, Hyperlink]
|
self.hyperlinks = {} # type: Dict[str, Hyperlink]
|
||||||
self.to_ignore = [re.compile(x) for x in self.app.config.linkcheck_ignore]
|
self.to_ignore = [re.compile(x) for x in self.config.linkcheck_ignore]
|
||||||
self.anchors_ignore = [re.compile(x)
|
self.anchors_ignore = [re.compile(x)
|
||||||
for x in self.app.config.linkcheck_anchors_ignore]
|
for x in self.config.linkcheck_anchors_ignore]
|
||||||
self.auth = [(re.compile(pattern), auth_info) for pattern, auth_info
|
self.auth = [(re.compile(pattern), auth_info) for pattern, auth_info
|
||||||
in self.app.config.linkcheck_auth]
|
in self.config.linkcheck_auth]
|
||||||
self._good = set() # type: Set[str]
|
self._good = set() # type: Set[str]
|
||||||
self._broken = {} # type: Dict[str, str]
|
self._broken = {} # type: Dict[str, str]
|
||||||
self._redirected = {} # type: Dict[str, Tuple[str, int]]
|
self._redirected = {} # type: Dict[str, Tuple[str, int]]
|
||||||
@ -124,13 +124,13 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
self.wqueue = queue.PriorityQueue() # type: queue.PriorityQueue
|
self.wqueue = queue.PriorityQueue() # type: queue.PriorityQueue
|
||||||
self.rqueue = queue.Queue() # type: queue.Queue
|
self.rqueue = queue.Queue() # type: queue.Queue
|
||||||
self.workers = [] # type: List[threading.Thread]
|
self.workers = [] # type: List[threading.Thread]
|
||||||
for i in range(self.app.config.linkcheck_workers):
|
for i in range(self.config.linkcheck_workers):
|
||||||
thread = threading.Thread(target=self.check_thread, daemon=True)
|
thread = threading.Thread(target=self.check_thread, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
self.workers.append(thread)
|
self.workers.append(thread)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def good(self):
|
def good(self) -> Set[str]:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"%s.%s is deprecated." % (self.__class__.__name__, "good"),
|
"%s.%s is deprecated." % (self.__class__.__name__, "good"),
|
||||||
RemovedInSphinx50Warning,
|
RemovedInSphinx50Warning,
|
||||||
@ -139,7 +139,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
return self._good
|
return self._good
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def broken(self):
|
def broken(self) -> Dict[str, str]:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"%s.%s is deprecated." % (self.__class__.__name__, "broken"),
|
"%s.%s is deprecated." % (self.__class__.__name__, "broken"),
|
||||||
RemovedInSphinx50Warning,
|
RemovedInSphinx50Warning,
|
||||||
@ -148,7 +148,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
return self._broken
|
return self._broken
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def redirected(self):
|
def redirected(self) -> Dict[str, Tuple[str, int]]:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"%s.%s is deprecated." % (self.__class__.__name__, "redirected"),
|
"%s.%s is deprecated." % (self.__class__.__name__, "redirected"),
|
||||||
RemovedInSphinx50Warning,
|
RemovedInSphinx50Warning,
|
||||||
@ -158,8 +158,8 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
|
|
||||||
def check_thread(self) -> None:
|
def check_thread(self) -> None:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if self.app.config.linkcheck_timeout:
|
if self.config.linkcheck_timeout:
|
||||||
kwargs['timeout'] = self.app.config.linkcheck_timeout
|
kwargs['timeout'] = self.config.linkcheck_timeout
|
||||||
|
|
||||||
def get_request_headers() -> Dict:
|
def get_request_headers() -> Dict:
|
||||||
url = urlparse(uri)
|
url = urlparse(uri)
|
||||||
@ -205,9 +205,9 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
kwargs['headers'] = get_request_headers()
|
kwargs['headers'] = get_request_headers()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if anchor and self.app.config.linkcheck_anchors:
|
if anchor and self.config.linkcheck_anchors:
|
||||||
# Read the whole document and see if #anchor exists
|
# Read the whole document and see if #anchor exists
|
||||||
response = requests.get(req_url, stream=True, config=self.app.config,
|
response = requests.get(req_url, stream=True, config=self.config,
|
||||||
auth=auth_info, **kwargs)
|
auth=auth_info, **kwargs)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
found = check_anchor(response, unquote(anchor))
|
found = check_anchor(response, unquote(anchor))
|
||||||
@ -219,7 +219,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
# try a HEAD request first, which should be easier on
|
# try a HEAD request first, which should be easier on
|
||||||
# the server and the network
|
# the server and the network
|
||||||
response = requests.head(req_url, allow_redirects=True,
|
response = requests.head(req_url, allow_redirects=True,
|
||||||
config=self.app.config, auth=auth_info,
|
config=self.config, auth=auth_info,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except (HTTPError, TooManyRedirects) as err:
|
except (HTTPError, TooManyRedirects) as err:
|
||||||
@ -228,7 +228,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
# retry with GET request if that fails, some servers
|
# retry with GET request if that fails, some servers
|
||||||
# don't like HEAD requests.
|
# don't like HEAD requests.
|
||||||
response = requests.get(req_url, stream=True,
|
response = requests.get(req_url, stream=True,
|
||||||
config=self.app.config,
|
config=self.config,
|
||||||
auth=auth_info, **kwargs)
|
auth=auth_info, **kwargs)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except HTTPError as err:
|
except HTTPError as err:
|
||||||
@ -297,7 +297,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
return 'ignored', '', 0
|
return 'ignored', '', 0
|
||||||
|
|
||||||
# need to actually check the URI
|
# need to actually check the URI
|
||||||
for _ in range(self.app.config.linkcheck_retries):
|
for _ in range(self.config.linkcheck_retries):
|
||||||
status, info, code = check_uri()
|
status, info, code = check_uri()
|
||||||
if status != "broken":
|
if status != "broken":
|
||||||
break
|
break
|
||||||
@ -360,7 +360,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
next_check = time.time() + delay
|
next_check = time.time() + delay
|
||||||
netloc = urlparse(response.url).netloc
|
netloc = urlparse(response.url).netloc
|
||||||
if next_check is None:
|
if next_check is None:
|
||||||
max_delay = self.app.config.linkcheck_rate_limit_timeout
|
max_delay = self.config.linkcheck_rate_limit_timeout
|
||||||
try:
|
try:
|
||||||
rate_limit = self.rate_limits[netloc]
|
rate_limit = self.rate_limits[netloc]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -390,7 +390,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
self.write_linkstat(linkstat)
|
self.write_linkstat(linkstat)
|
||||||
return
|
return
|
||||||
if lineno:
|
if lineno:
|
||||||
logger.info('(line %4d) ', lineno, nonl=True)
|
logger.info('(%16s: line %4d) ', docname, lineno, nonl=True)
|
||||||
if status == 'ignored':
|
if status == 'ignored':
|
||||||
if info:
|
if info:
|
||||||
logger.info(darkgray('-ignored- ') + uri + ': ' + info)
|
logger.info(darkgray('-ignored- ') + uri + ': ' + info)
|
||||||
|
@ -101,17 +101,19 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
|
|||||||
def unparse(node: ast.AST) -> List[Node]:
|
def unparse(node: ast.AST) -> List[Node]:
|
||||||
if isinstance(node, ast.Attribute):
|
if isinstance(node, ast.Attribute):
|
||||||
return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))]
|
return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))]
|
||||||
elif isinstance(node, ast.Constant): # type: ignore
|
elif isinstance(node, ast.BinOp):
|
||||||
if node.value is Ellipsis:
|
result = unparse(node.left) # type: List[Node]
|
||||||
return [addnodes.desc_sig_punctuation('', "...")]
|
result.extend(unparse(node.op))
|
||||||
else:
|
result.extend(unparse(node.right))
|
||||||
return [nodes.Text(node.value)]
|
return result
|
||||||
|
elif isinstance(node, ast.BitOr):
|
||||||
|
return [nodes.Text(' '), addnodes.desc_sig_punctuation('', '|'), nodes.Text(' ')]
|
||||||
elif isinstance(node, ast.Expr):
|
elif isinstance(node, ast.Expr):
|
||||||
return unparse(node.value)
|
return unparse(node.value)
|
||||||
elif isinstance(node, ast.Index):
|
elif isinstance(node, ast.Index):
|
||||||
return unparse(node.value)
|
return unparse(node.value)
|
||||||
elif isinstance(node, ast.List):
|
elif isinstance(node, ast.List):
|
||||||
result = [addnodes.desc_sig_punctuation('', '[')] # type: List[Node]
|
result = [addnodes.desc_sig_punctuation('', '[')]
|
||||||
for elem in node.elts:
|
for elem in node.elts:
|
||||||
result.extend(unparse(elem))
|
result.extend(unparse(elem))
|
||||||
result.append(addnodes.desc_sig_punctuation('', ', '))
|
result.append(addnodes.desc_sig_punctuation('', ', '))
|
||||||
@ -157,7 +159,7 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod
|
|||||||
tree = ast_parse(annotation)
|
tree = ast_parse(annotation)
|
||||||
result = unparse(tree)
|
result = unparse(tree)
|
||||||
for i, node in enumerate(result):
|
for i, node in enumerate(result):
|
||||||
if isinstance(node, nodes.Text):
|
if isinstance(node, nodes.Text) and node.strip():
|
||||||
result[i] = type_to_xref(str(node), env)
|
result[i] = type_to_xref(str(node), env)
|
||||||
return result
|
return result
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
|
@ -1012,10 +1012,6 @@ class ModuleDocumenter(Documenter):
|
|||||||
try:
|
try:
|
||||||
if not self.options.ignore_module_all:
|
if not self.options.ignore_module_all:
|
||||||
self.__all__ = inspect.getall(self.object)
|
self.__all__ = inspect.getall(self.object)
|
||||||
except AttributeError as exc:
|
|
||||||
# __all__ raises an error.
|
|
||||||
logger.warning(__('%s.__all__ raises an error. Ignored: %r'),
|
|
||||||
(self.fullname, exc), type='autodoc')
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
# invalid __all__ found.
|
# invalid __all__ found.
|
||||||
logger.warning(__('__all__ should be a list of strings, not %r '
|
logger.warning(__('__all__ should be a list of strings, not %r '
|
||||||
@ -1056,14 +1052,11 @@ class ModuleDocumenter(Documenter):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# annotation only member (ex. attr: int)
|
# annotation only member (ex. attr: int)
|
||||||
try:
|
for name in inspect.getannotations(self.object):
|
||||||
for name in inspect.getannotations(self.object):
|
if name not in members:
|
||||||
if name not in members:
|
docstring = attr_docs.get(('', name), [])
|
||||||
docstring = attr_docs.get(('', name), [])
|
members[name] = ObjectMember(name, INSTANCEATTR,
|
||||||
members[name] = ObjectMember(name, INSTANCEATTR,
|
docstring="\n".join(docstring))
|
||||||
docstring="\n".join(docstring))
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return members
|
return members
|
||||||
|
|
||||||
@ -1890,16 +1883,16 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
|
|||||||
|
|
||||||
def update_annotations(self, parent: Any) -> None:
|
def update_annotations(self, parent: Any) -> None:
|
||||||
"""Update __annotations__ to support type_comment and so on."""
|
"""Update __annotations__ to support type_comment and so on."""
|
||||||
try:
|
annotations = dict(inspect.getannotations(parent))
|
||||||
annotations = dict(inspect.getannotations(parent))
|
parent.__annotations__ = annotations
|
||||||
parent.__annotations__ = annotations
|
|
||||||
|
|
||||||
|
try:
|
||||||
analyzer = ModuleAnalyzer.for_module(self.modname)
|
analyzer = ModuleAnalyzer.for_module(self.modname)
|
||||||
analyzer.analyze()
|
analyzer.analyze()
|
||||||
for (classname, attrname), annotation in analyzer.annotations.items():
|
for (classname, attrname), annotation in analyzer.annotations.items():
|
||||||
if classname == '' and attrname not in annotations:
|
if classname == '' and attrname not in annotations:
|
||||||
annotations[attrname] = annotation
|
annotations[attrname] = annotation
|
||||||
except AttributeError:
|
except PycodeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def import_object(self, raiseerror: bool = False) -> bool:
|
def import_object(self, raiseerror: bool = False) -> bool:
|
||||||
@ -2212,7 +2205,7 @@ class SlotsMixin(DataDocumenterMixinBase):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
except (AttributeError, ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def import_object(self, raiseerror: bool = False) -> bool:
|
def import_object(self, raiseerror: bool = False) -> bool:
|
||||||
@ -2238,7 +2231,7 @@ class SlotsMixin(DataDocumenterMixinBase):
|
|||||||
return [docstring]
|
return [docstring]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
except (AttributeError, ValueError) as exc:
|
except ValueError as exc:
|
||||||
logger.warning(__('Invalid __slots__ found on %s. Ignored.'),
|
logger.warning(__('Invalid __slots__ found on %s. Ignored.'),
|
||||||
(self.parent.__qualname__, exc), type='autodoc')
|
(self.parent.__qualname__, exc), type='autodoc')
|
||||||
return []
|
return []
|
||||||
@ -2429,8 +2422,6 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
|
|||||||
annotations[attrname] = annotation
|
annotations[attrname] = annotation
|
||||||
except (AttributeError, PycodeError):
|
except (AttributeError, PycodeError):
|
||||||
pass
|
pass
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Failed to set __annotations__ (built-in, extensions, etc.)
|
# Failed to set __annotations__ (built-in, extensions, etc.)
|
||||||
pass
|
pass
|
||||||
@ -2484,22 +2475,19 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_attribute_comment(self, parent: Any, attrname: str) -> Optional[List[str]]:
|
def get_attribute_comment(self, parent: Any, attrname: str) -> Optional[List[str]]:
|
||||||
try:
|
for cls in inspect.getmro(parent):
|
||||||
for cls in inspect.getmro(parent):
|
try:
|
||||||
try:
|
module = safe_getattr(cls, '__module__')
|
||||||
module = safe_getattr(cls, '__module__')
|
qualname = safe_getattr(cls, '__qualname__')
|
||||||
qualname = safe_getattr(cls, '__qualname__')
|
|
||||||
|
|
||||||
analyzer = ModuleAnalyzer.for_module(module)
|
analyzer = ModuleAnalyzer.for_module(module)
|
||||||
analyzer.analyze()
|
analyzer.analyze()
|
||||||
if qualname and self.objpath:
|
if qualname and self.objpath:
|
||||||
key = (qualname, attrname)
|
key = (qualname, attrname)
|
||||||
if key in analyzer.attr_docs:
|
if key in analyzer.attr_docs:
|
||||||
return list(analyzer.attr_docs[key])
|
return list(analyzer.attr_docs[key])
|
||||||
except (AttributeError, PycodeError):
|
except (AttributeError, PycodeError):
|
||||||
pass
|
pass
|
||||||
except (AttributeError, PycodeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -156,12 +156,9 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# annotation only member (ex. attr: int)
|
# annotation only member (ex. attr: int)
|
||||||
try:
|
for name in getannotations(module):
|
||||||
for name in getannotations(module):
|
if name not in members:
|
||||||
if name not in members:
|
members[name] = (name, INSTANCEATTR)
|
||||||
members[name] = (name, INSTANCEATTR)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return sorted(list(members.values()))
|
return sorted(list(members.values()))
|
||||||
|
|
||||||
@ -202,7 +199,7 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
|||||||
|
|
||||||
for name in __slots__:
|
for name in __slots__:
|
||||||
members[name] = Attribute(name, True, SLOTSATTR)
|
members[name] = Attribute(name, True, SLOTSATTR)
|
||||||
except (AttributeError, TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# other members
|
# other members
|
||||||
@ -218,13 +215,10 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
|||||||
|
|
||||||
# annotation only member (ex. attr: int)
|
# annotation only member (ex. attr: int)
|
||||||
for i, cls in enumerate(getmro(subject)):
|
for i, cls in enumerate(getmro(subject)):
|
||||||
try:
|
for name in getannotations(cls):
|
||||||
for name in getannotations(cls):
|
name = unmangle(cls, name)
|
||||||
name = unmangle(cls, name)
|
if name and name not in members:
|
||||||
if name and name not in members:
|
members[name] = Attribute(name, i == 0, INSTANCEATTR)
|
||||||
members[name] = Attribute(name, i == 0, INSTANCEATTR)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if analyzer:
|
if analyzer:
|
||||||
# append instance attributes (cf. self.attr1) if analyzer knows
|
# append instance attributes (cf. self.attr1) if analyzer knows
|
||||||
@ -267,7 +261,7 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
|
|||||||
for name, docstring in __slots__.items():
|
for name, docstring in __slots__.items():
|
||||||
members[name] = ObjectMember(name, SLOTSATTR, class_=subject,
|
members[name] = ObjectMember(name, SLOTSATTR, class_=subject,
|
||||||
docstring=docstring)
|
docstring=docstring)
|
||||||
except (AttributeError, TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# other members
|
# other members
|
||||||
@ -288,27 +282,35 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
for cls in getmro(subject):
|
for cls in getmro(subject):
|
||||||
# annotation only member (ex. attr: int)
|
|
||||||
try:
|
|
||||||
for name in getannotations(cls):
|
|
||||||
name = unmangle(cls, name)
|
|
||||||
if name and name not in members:
|
|
||||||
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# append instance attributes (cf. self.attr1) if analyzer knows
|
|
||||||
try:
|
try:
|
||||||
modname = safe_getattr(cls, '__module__')
|
modname = safe_getattr(cls, '__module__')
|
||||||
qualname = safe_getattr(cls, '__qualname__')
|
qualname = safe_getattr(cls, '__qualname__')
|
||||||
analyzer = ModuleAnalyzer.for_module(modname)
|
analyzer = ModuleAnalyzer.for_module(modname)
|
||||||
analyzer.analyze()
|
analyzer.analyze()
|
||||||
|
except AttributeError:
|
||||||
|
qualname = None
|
||||||
|
analyzer = None
|
||||||
|
except PycodeError:
|
||||||
|
analyzer = None
|
||||||
|
|
||||||
|
# annotation only member (ex. attr: int)
|
||||||
|
for name in getannotations(cls):
|
||||||
|
name = unmangle(cls, name)
|
||||||
|
if name and name not in members:
|
||||||
|
if analyzer and (qualname, name) in analyzer.attr_docs:
|
||||||
|
docstring = '\n'.join(analyzer.attr_docs[qualname, name])
|
||||||
|
else:
|
||||||
|
docstring = None
|
||||||
|
|
||||||
|
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
|
||||||
|
docstring=docstring)
|
||||||
|
|
||||||
|
# append instance attributes (cf. self.attr1) if analyzer knows
|
||||||
|
if analyzer:
|
||||||
for (ns, name), docstring in analyzer.attr_docs.items():
|
for (ns, name), docstring in analyzer.attr_docs.items():
|
||||||
if ns == qualname and name not in members:
|
if ns == qualname and name not in members:
|
||||||
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
|
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
|
||||||
docstring='\n'.join(docstring))
|
docstring='\n'.join(docstring))
|
||||||
except (AttributeError, PycodeError):
|
|
||||||
pass
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ from docutils.parsers.rst import directives
|
|||||||
from docutils.parsers.rst.directives.admonitions import BaseAdmonition
|
from docutils.parsers.rst.directives.admonitions import BaseAdmonition
|
||||||
|
|
||||||
import sphinx
|
import sphinx
|
||||||
|
from sphinx import addnodes
|
||||||
from sphinx.application import Sphinx
|
from sphinx.application import Sphinx
|
||||||
from sphinx.domains import Domain
|
from sphinx.domains import Domain
|
||||||
from sphinx.environment import BuildEnvironment
|
from sphinx.environment import BuildEnvironment
|
||||||
@ -123,12 +124,12 @@ class TodoListProcessor:
|
|||||||
self.config = app.config
|
self.config = app.config
|
||||||
self.env = app.env
|
self.env = app.env
|
||||||
self.domain = cast(TodoDomain, app.env.get_domain('todo'))
|
self.domain = cast(TodoDomain, app.env.get_domain('todo'))
|
||||||
|
self.document = new_document('')
|
||||||
|
|
||||||
self.process(doctree, docname)
|
self.process(doctree, docname)
|
||||||
|
|
||||||
def process(self, doctree: nodes.document, docname: str) -> None:
|
def process(self, doctree: nodes.document, docname: str) -> None:
|
||||||
todos = sum(self.domain.todos.values(), []) # type: List[todo_node]
|
todos = sum(self.domain.todos.values(), []) # type: List[todo_node]
|
||||||
document = new_document('')
|
|
||||||
for node in doctree.traverse(todolist):
|
for node in doctree.traverse(todolist):
|
||||||
if not self.config.todo_include_todos:
|
if not self.config.todo_include_todos:
|
||||||
node.parent.remove(node)
|
node.parent.remove(node)
|
||||||
@ -144,12 +145,7 @@ class TodoListProcessor:
|
|||||||
new_todo = todo.deepcopy()
|
new_todo = todo.deepcopy()
|
||||||
new_todo['ids'].clear()
|
new_todo['ids'].clear()
|
||||||
|
|
||||||
# (Recursively) resolve references in the todo content
|
self.resolve_reference(new_todo, docname)
|
||||||
#
|
|
||||||
# Note: To resolve references, it is needed to wrap it with document node
|
|
||||||
document += new_todo
|
|
||||||
self.env.resolve_references(document, todo['docname'], self.builder)
|
|
||||||
document.remove(new_todo)
|
|
||||||
content.append(new_todo)
|
content.append(new_todo)
|
||||||
|
|
||||||
todo_ref = self.create_todo_reference(todo, docname)
|
todo_ref = self.create_todo_reference(todo, docname)
|
||||||
@ -185,6 +181,17 @@ class TodoListProcessor:
|
|||||||
|
|
||||||
return para
|
return para
|
||||||
|
|
||||||
|
def resolve_reference(self, todo: todo_node, docname: str) -> None:
|
||||||
|
"""Resolve references in the todo content."""
|
||||||
|
for node in todo.traverse(addnodes.pending_xref):
|
||||||
|
if 'refdoc' in node:
|
||||||
|
node['refdoc'] = docname
|
||||||
|
|
||||||
|
# Note: To resolve references, it is needed to wrap it with document node
|
||||||
|
self.document += todo
|
||||||
|
self.env.resolve_references(self.document, docname, self.builder)
|
||||||
|
self.document.remove(todo)
|
||||||
|
|
||||||
|
|
||||||
def visit_todo_node(self: HTMLTranslator, node: todo_node) -> None:
|
def visit_todo_node(self: HTMLTranslator, node: todo_node) -> None:
|
||||||
if self.config.todo_include_todos:
|
if self.config.todo_include_todos:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
\NeedsTeXFormat{LaTeX2e}
|
\NeedsTeXFormat{LaTeX2e}
|
||||||
\ProvidesPackage{sphinxpackagefootnote}%
|
\ProvidesPackage{sphinxpackagefootnote}%
|
||||||
[2021/01/26 v1.1b footnotehyper adapted to sphinx (Sphinx team)]
|
[2021/01/29 v1.1c footnotehyper adapted to sphinx (Sphinx team)]
|
||||||
% Provides support for this output mark-up from Sphinx latex writer:
|
% Provides support for this output mark-up from Sphinx latex writer:
|
||||||
% - footnote environment
|
% - footnote environment
|
||||||
% - savenotes environment (table templates)
|
% - savenotes environment (table templates)
|
||||||
@ -8,7 +8,7 @@
|
|||||||
%
|
%
|
||||||
%%
|
%%
|
||||||
%% Package: sphinxpackagefootnote
|
%% Package: sphinxpackagefootnote
|
||||||
%% Version: based on footnotehyper.sty 2021/01/26 v1.1b
|
%% Version: based on footnotehyper.sty 2021/01/29 v1.1c
|
||||||
%% as available at https://www.ctan.org/pkg/footnotehyper
|
%% as available at https://www.ctan.org/pkg/footnotehyper
|
||||||
%% License: the one applying to Sphinx
|
%% License: the one applying to Sphinx
|
||||||
%%
|
%%
|
||||||
@ -25,6 +25,7 @@
|
|||||||
\DeclareOption*{\PackageWarning{sphinxpackagefootnote}{Option `\CurrentOption' is unknown}}%
|
\DeclareOption*{\PackageWarning{sphinxpackagefootnote}{Option `\CurrentOption' is unknown}}%
|
||||||
\ProcessOptions\relax
|
\ProcessOptions\relax
|
||||||
\newbox\FNH@notes
|
\newbox\FNH@notes
|
||||||
|
\newtoks\FNH@toks % 1.1c
|
||||||
\newdimen\FNH@width
|
\newdimen\FNH@width
|
||||||
\let\FNH@colwidth\columnwidth
|
\let\FNH@colwidth\columnwidth
|
||||||
\newif\ifFNH@savingnotes
|
\newif\ifFNH@savingnotes
|
||||||
@ -32,6 +33,7 @@
|
|||||||
\let\FNH@latex@footnote \footnote
|
\let\FNH@latex@footnote \footnote
|
||||||
\let\FNH@latex@footnotetext\footnotetext
|
\let\FNH@latex@footnotetext\footnotetext
|
||||||
\let\FNH@H@@footnotetext \@footnotetext
|
\let\FNH@H@@footnotetext \@footnotetext
|
||||||
|
\let\FNH@H@@mpfootnotetext \@mpfootnotetext
|
||||||
\newenvironment{savenotes}
|
\newenvironment{savenotes}
|
||||||
{\FNH@savenotes\ignorespaces}{\FNH@spewnotes\ignorespacesafterend}%
|
{\FNH@savenotes\ignorespaces}{\FNH@spewnotes\ignorespacesafterend}%
|
||||||
\let\spewnotes \FNH@spewnotes
|
\let\spewnotes \FNH@spewnotes
|
||||||
@ -42,6 +44,7 @@
|
|||||||
\@ifpackageloaded{hyperref}
|
\@ifpackageloaded{hyperref}
|
||||||
{\ifHy@hyperfootnotes
|
{\ifHy@hyperfootnotes
|
||||||
\let\FNH@H@@footnotetext\H@@footnotetext
|
\let\FNH@H@@footnotetext\H@@footnotetext
|
||||||
|
\let\FNH@H@@mpfootnotetext\H@@mpfootnotetext
|
||||||
\else
|
\else
|
||||||
\let\FNH@hyper@fntext\FNH@nohyp@fntext
|
\let\FNH@hyper@fntext\FNH@nohyp@fntext
|
||||||
\fi}%
|
\fi}%
|
||||||
@ -116,14 +119,33 @@
|
|||||||
\fi
|
\fi
|
||||||
}%
|
}%
|
||||||
\def\FNH@spewnotes {%
|
\def\FNH@spewnotes {%
|
||||||
\endgroup
|
\if@endpe\ifx\par\@@par\FNH@toks{}\else
|
||||||
|
\FNH@toks\expandafter{\expandafter
|
||||||
|
\def\expandafter\par\expandafter{\par}\@endpetrue}%
|
||||||
|
\expandafter\expandafter\expandafter
|
||||||
|
\FNH@toks
|
||||||
|
\expandafter\expandafter\expandafter
|
||||||
|
{\expandafter\the\expandafter\FNH@toks
|
||||||
|
\expandafter\def\expandafter\@par\expandafter{\@par}}%
|
||||||
|
\expandafter\expandafter\expandafter
|
||||||
|
\FNH@toks
|
||||||
|
\expandafter\expandafter\expandafter
|
||||||
|
{\expandafter\the\expandafter\FNH@toks
|
||||||
|
\expandafter\everypar\expandafter{\the\everypar}}\fi
|
||||||
|
\else\FNH@toks{}\fi
|
||||||
|
\expandafter
|
||||||
|
\endgroup\the\FNH@toks
|
||||||
\ifFNH@savingnotes\else
|
\ifFNH@savingnotes\else
|
||||||
\ifvoid\FNH@notes\else
|
\ifvoid\FNH@notes\else
|
||||||
\begingroup
|
\begingroup
|
||||||
\let\@makefntext\@empty
|
\let\@makefntext\@empty
|
||||||
\let\@finalstrut\@gobble
|
\let\@finalstrut\@gobble
|
||||||
\let\rule\@gobbletwo
|
\let\rule\@gobbletwo
|
||||||
\FNH@H@@footnotetext{\unvbox\FNH@notes}%
|
\ifx\@footnotetext\@mpfootnotetext
|
||||||
|
\expandafter\FNH@H@@mpfootnotetext
|
||||||
|
\else
|
||||||
|
\expandafter\FNH@H@@footnotetext
|
||||||
|
\fi{\unvbox\FNH@notes}%
|
||||||
\endgroup
|
\endgroup
|
||||||
\fi
|
\fi
|
||||||
\fi
|
\fi
|
||||||
|
@ -145,7 +145,6 @@ def getall(obj: Any) -> Optional[Sequence[str]]:
|
|||||||
"""Get __all__ attribute of the module as dict.
|
"""Get __all__ attribute of the module as dict.
|
||||||
|
|
||||||
Return None if given *obj* does not have __all__.
|
Return None if given *obj* does not have __all__.
|
||||||
Raises AttributeError if given *obj* raises an error on accessing __all__.
|
|
||||||
Raises ValueError if given *obj* have invalid __all__.
|
Raises ValueError if given *obj* have invalid __all__.
|
||||||
"""
|
"""
|
||||||
__all__ = safe_getattr(obj, '__all__', None)
|
__all__ = safe_getattr(obj, '__all__', None)
|
||||||
@ -159,10 +158,7 @@ def getall(obj: Any) -> Optional[Sequence[str]]:
|
|||||||
|
|
||||||
|
|
||||||
def getannotations(obj: Any) -> Mapping[str, Any]:
|
def getannotations(obj: Any) -> Mapping[str, Any]:
|
||||||
"""Get __annotations__ from given *obj* safely.
|
"""Get __annotations__ from given *obj* safely."""
|
||||||
|
|
||||||
Raises AttributeError if given *obj* raises an error on accessing __attribute__.
|
|
||||||
"""
|
|
||||||
__annotations__ = safe_getattr(obj, '__annotations__', None)
|
__annotations__ = safe_getattr(obj, '__annotations__', None)
|
||||||
if isinstance(__annotations__, Mapping):
|
if isinstance(__annotations__, Mapping):
|
||||||
return __annotations__
|
return __annotations__
|
||||||
@ -171,10 +167,7 @@ def getannotations(obj: Any) -> Mapping[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def getmro(obj: Any) -> Tuple["Type", ...]:
|
def getmro(obj: Any) -> Tuple["Type", ...]:
|
||||||
"""Get __mro__ from given *obj* safely.
|
"""Get __mro__ from given *obj* safely."""
|
||||||
|
|
||||||
Raises AttributeError if given *obj* raises an error on accessing __mro__.
|
|
||||||
"""
|
|
||||||
__mro__ = safe_getattr(obj, '__mro__', None)
|
__mro__ = safe_getattr(obj, '__mro__', None)
|
||||||
if isinstance(__mro__, tuple):
|
if isinstance(__mro__, tuple):
|
||||||
return __mro__
|
return __mro__
|
||||||
@ -186,7 +179,6 @@ def getslots(obj: Any) -> Optional[Dict]:
|
|||||||
"""Get __slots__ attribute of the class as dict.
|
"""Get __slots__ attribute of the class as dict.
|
||||||
|
|
||||||
Return None if gienv *obj* does not have __slots__.
|
Return None if gienv *obj* does not have __slots__.
|
||||||
Raises AttributeError if given *obj* raises an error on accessing __slots__.
|
|
||||||
Raises TypeError if given *obj* is not a class.
|
Raises TypeError if given *obj* is not a class.
|
||||||
Raises ValueError if given *obj* have invalid __slots__.
|
Raises ValueError if given *obj* have invalid __slots__.
|
||||||
"""
|
"""
|
||||||
@ -464,12 +456,7 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
|
|||||||
but PyPy implements it by pure Python code.
|
but PyPy implements it by pure Python code.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
mro = inspect.getmro(obj)
|
mro = getmro(obj)
|
||||||
except AttributeError:
|
|
||||||
# no __mro__, assume the object has no methods as we know them
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
cls = next(c for c in mro if attr_name in safe_getattr(c, '__dict__', {}))
|
cls = next(c for c in mro if attr_name in safe_getattr(c, '__dict__', {}))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return False
|
return False
|
||||||
|
@ -30,6 +30,11 @@ else:
|
|||||||
ref = _ForwardRef(self.arg)
|
ref = _ForwardRef(self.arg)
|
||||||
return ref._eval_type(globalns, localns)
|
return ref._eval_type(globalns, localns)
|
||||||
|
|
||||||
|
if sys.version_info > (3, 10):
|
||||||
|
from types import Union as types_Union
|
||||||
|
else:
|
||||||
|
types_Union = None
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
from typing import Type # NOQA # for python3.5.1
|
from typing import Type # NOQA # for python3.5.1
|
||||||
@ -72,7 +77,9 @@ def get_type_hints(obj: Any, globalns: Dict = None, localns: Dict = None) -> Dic
|
|||||||
# Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
|
# Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
|
||||||
return safe_getattr(obj, '__annotations__', {})
|
return safe_getattr(obj, '__annotations__', {})
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return {}
|
# Invalid object is given. But try to get __annotations__ as a fallback for
|
||||||
|
# the code using type union operator (PEP 604) in python 3.9 or below.
|
||||||
|
return safe_getattr(obj, '__annotations__', {})
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084)
|
# a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084)
|
||||||
return {}
|
return {}
|
||||||
@ -97,6 +104,12 @@ def restify(cls: Optional["Type"]) -> str:
|
|||||||
return ':class:`struct.Struct`'
|
return ':class:`struct.Struct`'
|
||||||
elif inspect.isNewType(cls):
|
elif inspect.isNewType(cls):
|
||||||
return ':class:`%s`' % cls.__name__
|
return ':class:`%s`' % cls.__name__
|
||||||
|
elif types_Union and isinstance(cls, types_Union):
|
||||||
|
if len(cls.__args__) > 1 and None in cls.__args__:
|
||||||
|
args = ' | '.join(restify(a) for a in cls.__args__ if a)
|
||||||
|
return 'Optional[%s]' % args
|
||||||
|
else:
|
||||||
|
return ' | '.join(restify(a) for a in cls.__args__)
|
||||||
elif cls.__module__ in ('__builtin__', 'builtins'):
|
elif cls.__module__ in ('__builtin__', 'builtins'):
|
||||||
return ':class:`%s`' % cls.__name__
|
return ':class:`%s`' % cls.__name__
|
||||||
else:
|
else:
|
||||||
@ -290,6 +303,8 @@ def _stringify_py37(annotation: Any) -> str:
|
|||||||
elif hasattr(annotation, '__origin__'):
|
elif hasattr(annotation, '__origin__'):
|
||||||
# instantiated generic provided by a user
|
# instantiated generic provided by a user
|
||||||
qualname = stringify(annotation.__origin__)
|
qualname = stringify(annotation.__origin__)
|
||||||
|
elif types_Union and isinstance(annotation, types_Union): # types.Union (for py3.10+)
|
||||||
|
qualname = 'types.Union'
|
||||||
else:
|
else:
|
||||||
# we weren't able to extract the base type, appending arguments would
|
# we weren't able to extract the base type, appending arguments would
|
||||||
# only make them appear twice
|
# only make them appear twice
|
||||||
@ -309,6 +324,12 @@ def _stringify_py37(annotation: Any) -> str:
|
|||||||
else:
|
else:
|
||||||
args = ', '.join(stringify(a) for a in annotation.__args__)
|
args = ', '.join(stringify(a) for a in annotation.__args__)
|
||||||
return 'Union[%s]' % args
|
return 'Union[%s]' % args
|
||||||
|
elif qualname == 'types.Union':
|
||||||
|
if len(annotation.__args__) > 1 and None in annotation.__args__:
|
||||||
|
args = ' | '.join(stringify(a) for a in annotation.__args__ if a)
|
||||||
|
return 'Optional[%s]' % args
|
||||||
|
else:
|
||||||
|
return ' | '.join(stringify(a) for a in annotation.__args__)
|
||||||
elif qualname == 'Callable':
|
elif qualname == 'Callable':
|
||||||
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
|
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
|
||||||
returns = stringify(annotation.__args__[-1])
|
returns = stringify(annotation.__args__[-1])
|
||||||
|
@ -116,8 +116,10 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
|
|||||||
def visit_desc_signature(self, node: Element) -> None:
|
def visit_desc_signature(self, node: Element) -> None:
|
||||||
# the id is set automatically
|
# the id is set automatically
|
||||||
self.body.append(self.starttag(node, 'dt'))
|
self.body.append(self.starttag(node, 'dt'))
|
||||||
|
self.protect_literal_text += 1
|
||||||
|
|
||||||
def depart_desc_signature(self, node: Element) -> None:
|
def depart_desc_signature(self, node: Element) -> None:
|
||||||
|
self.protect_literal_text -= 1
|
||||||
if not node.get('is_multiline'):
|
if not node.get('is_multiline'):
|
||||||
self.add_permalink_ref(node, _('Permalink to this definition'))
|
self.add_permalink_ref(node, _('Permalink to this definition'))
|
||||||
self.body.append('</dt>\n')
|
self.body.append('</dt>\n')
|
||||||
|
@ -87,8 +87,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
|||||||
def visit_desc_signature(self, node: Element) -> None:
|
def visit_desc_signature(self, node: Element) -> None:
|
||||||
# the id is set automatically
|
# the id is set automatically
|
||||||
self.body.append(self.starttag(node, 'dt'))
|
self.body.append(self.starttag(node, 'dt'))
|
||||||
|
self.protect_literal_text += 1
|
||||||
|
|
||||||
def depart_desc_signature(self, node: Element) -> None:
|
def depart_desc_signature(self, node: Element) -> None:
|
||||||
|
self.protect_literal_text -= 1
|
||||||
if not node.get('is_multiline'):
|
if not node.get('is_multiline'):
|
||||||
self.add_permalink_ref(node, _('Permalink to this definition'))
|
self.add_permalink_ref(node, _('Permalink to this definition'))
|
||||||
self.body.append('</dt>\n')
|
self.body.append('</dt>\n')
|
||||||
|
@ -702,12 +702,18 @@ class LaTeXTranslator(SphinxTranslator):
|
|||||||
self.body.append(self.context.pop())
|
self.body.append(self.context.pop())
|
||||||
|
|
||||||
def visit_desc(self, node: Element) -> None:
|
def visit_desc(self, node: Element) -> None:
|
||||||
self.body.append('\n\n\\begin{fulllineitems}\n')
|
if self.config.latex_show_urls == 'footnote':
|
||||||
|
self.body.append('\n\n\\begin{savenotes}\\begin{fulllineitems}\n')
|
||||||
|
else:
|
||||||
|
self.body.append('\n\n\\begin{fulllineitems}\n')
|
||||||
if self.table:
|
if self.table:
|
||||||
self.table.has_problematic = True
|
self.table.has_problematic = True
|
||||||
|
|
||||||
def depart_desc(self, node: Element) -> None:
|
def depart_desc(self, node: Element) -> None:
|
||||||
self.body.append('\n\\end{fulllineitems}\n\n')
|
if self.config.latex_show_urls == 'footnote':
|
||||||
|
self.body.append('\n\\end{fulllineitems}\\end{savenotes}\n\n')
|
||||||
|
else:
|
||||||
|
self.body.append('\n\\end{fulllineitems}\n\n')
|
||||||
|
|
||||||
def _visit_signature_line(self, node: Element) -> None:
|
def _visit_signature_line(self, node: Element) -> None:
|
||||||
for child in node:
|
for child in node:
|
||||||
|
16
tests/roots/test-ext-autodoc/target/pep604.py
Normal file
16
tests/roots/test-ext-autodoc/target/pep604.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
attr: int | str #: docstring
|
||||||
|
|
||||||
|
|
||||||
|
def sum(x: int | str, y: int | str) -> int | str:
|
||||||
|
"""docstring"""
|
||||||
|
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
"""docstring"""
|
||||||
|
|
||||||
|
attr: int | str #: docstring
|
||||||
|
|
||||||
|
def meth(self, x: int | str, y: int | str) -> int | str:
|
||||||
|
"""docstring"""
|
@ -0,0 +1,8 @@
|
|||||||
|
class Base:
|
||||||
|
attr1: int #: docstring
|
||||||
|
attr2: str
|
||||||
|
|
||||||
|
|
||||||
|
class Derived(Base):
|
||||||
|
attr3: int #: docstring
|
||||||
|
attr4: str
|
@ -169,3 +169,9 @@ Footnote in term [#]_
|
|||||||
|
|
||||||
def bar(x,y):
|
def bar(x,y):
|
||||||
return x+y
|
return x+y
|
||||||
|
|
||||||
|
The section with an object description
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. py:function:: dummy(N)
|
||||||
|
:noindex:
|
||||||
|
@ -178,8 +178,8 @@ def test_html4_output(app, status, warning):
|
|||||||
],
|
],
|
||||||
'autodoc.html': [
|
'autodoc.html': [
|
||||||
(".//dl[@class='py class']/dt[@id='autodoc_target.Class']", ''),
|
(".//dl[@class='py class']/dt[@id='autodoc_target.Class']", ''),
|
||||||
(".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span", r'\*\*'),
|
(".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span/span", r'\*\*'),
|
||||||
(".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span", r'kwds'),
|
(".//dl[@class='py function']/dt[@id='autodoc_target.function']/em/span/span", r'kwds'),
|
||||||
(".//dd/p", r'Return spam\.'),
|
(".//dd/p", r'Return spam\.'),
|
||||||
],
|
],
|
||||||
'extapi.html': [
|
'extapi.html': [
|
||||||
@ -279,8 +279,10 @@ def test_html4_output(app, status, warning):
|
|||||||
'objects.html': [
|
'objects.html': [
|
||||||
(".//dt[@id='mod.Cls.meth1']", ''),
|
(".//dt[@id='mod.Cls.meth1']", ''),
|
||||||
(".//dt[@id='errmod.Error']", ''),
|
(".//dt[@id='errmod.Error']", ''),
|
||||||
(".//dt/code", r'long\(parameter,\s* list\)'),
|
(".//dt/code/span", r'long\(parameter,'),
|
||||||
(".//dt/code", 'another one'),
|
(".//dt/code/span", r'list\)'),
|
||||||
|
(".//dt/code/span", 'another'),
|
||||||
|
(".//dt/code/span", 'one'),
|
||||||
(".//a[@href='#mod.Cls'][@class='reference internal']", ''),
|
(".//a[@href='#mod.Cls'][@class='reference internal']", ''),
|
||||||
(".//dl[@class='std userdesc']", ''),
|
(".//dl[@class='std userdesc']", ''),
|
||||||
(".//dt[@id='userdesc-myobj']", ''),
|
(".//dt[@id='userdesc-myobj']", ''),
|
||||||
|
@ -828,6 +828,7 @@ def test_latex_show_urls_is_inline(app, status, warning):
|
|||||||
assert '\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result
|
assert '\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result
|
||||||
assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}'
|
assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}'
|
||||||
'{sphinx\\sphinxhyphen{}dev@googlegroups.com}') in result
|
'{sphinx\\sphinxhyphen{}dev@googlegroups.com}') in result
|
||||||
|
assert '\\begin{savenotes}\\begin{fulllineitems}' not in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(
|
@pytest.mark.sphinx(
|
||||||
@ -882,6 +883,7 @@ def test_latex_show_urls_is_footnote(app, status, warning):
|
|||||||
assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result)
|
assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result)
|
||||||
assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}'
|
assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}'
|
||||||
'{sphinx\\sphinxhyphen{}dev@googlegroups.com}\n') in result
|
'{sphinx\\sphinxhyphen{}dev@googlegroups.com}\n') in result
|
||||||
|
assert '\\begin{savenotes}\\begin{fulllineitems}' in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(
|
@pytest.mark.sphinx(
|
||||||
@ -925,6 +927,7 @@ def test_latex_show_urls_is_no(app, status, warning):
|
|||||||
assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result)
|
assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result)
|
||||||
assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}'
|
assert ('\\sphinxhref{mailto:sphinx-dev@googlegroups.com}'
|
||||||
'{sphinx\\sphinxhyphen{}dev@googlegroups.com}\n') in result
|
'{sphinx\\sphinxhyphen{}dev@googlegroups.com}\n') in result
|
||||||
|
assert '\\begin{savenotes}\\begin{fulllineitems}' not in result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(
|
@pytest.mark.sphinx(
|
||||||
|
@ -431,6 +431,20 @@ def test_pyfunction_with_number_literals(app):
|
|||||||
[nodes.inline, "1_6_0"])])])
|
[nodes.inline, "1_6_0"])])])
|
||||||
|
|
||||||
|
|
||||||
|
def test_pyfunction_with_union_type_operator(app):
|
||||||
|
text = ".. py:function:: hello(age: int | None)"
|
||||||
|
doctree = restructuredtext.parse(app, text)
|
||||||
|
assert_node(doctree[1][0][1],
|
||||||
|
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "age"],
|
||||||
|
[desc_sig_punctuation, ":"],
|
||||||
|
" ",
|
||||||
|
[desc_sig_name, ([pending_xref, "int"],
|
||||||
|
" ",
|
||||||
|
[desc_sig_punctuation, "|"],
|
||||||
|
" ",
|
||||||
|
[pending_xref, "None"])])])])
|
||||||
|
|
||||||
|
|
||||||
def test_optional_pyfunction_signature(app):
|
def test_optional_pyfunction_signature(app):
|
||||||
text = ".. py:function:: compile(source [, filename [, symbol]]) -> ast object"
|
text = ".. py:function:: compile(source [, filename [, symbol]]) -> ast object"
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
@ -498,6 +512,20 @@ def test_pydata_signature_old(app):
|
|||||||
domain="py", objtype="data", noindex=False)
|
domain="py", objtype="data", noindex=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pydata_with_union_type_operator(app):
|
||||||
|
text = (".. py:data:: version\n"
|
||||||
|
" :type: int | str")
|
||||||
|
doctree = restructuredtext.parse(app, text)
|
||||||
|
assert_node(doctree[1][0],
|
||||||
|
([desc_name, "version"],
|
||||||
|
[desc_annotation, (": ",
|
||||||
|
[pending_xref, "int"],
|
||||||
|
" ",
|
||||||
|
[desc_sig_punctuation, "|"],
|
||||||
|
" ",
|
||||||
|
[pending_xref, "str"])]))
|
||||||
|
|
||||||
|
|
||||||
def test_pyobject_prefix(app):
|
def test_pyobject_prefix(app):
|
||||||
text = (".. py:class:: Foo\n"
|
text = (".. py:class:: Foo\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -2226,6 +2226,50 @@ def test_name_mangling(app):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
|
||||||
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
|
def test_type_union_operator(app):
|
||||||
|
options = {'members': None}
|
||||||
|
actual = do_autodoc(app, 'module', 'target.pep604', options)
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. py:module:: target.pep604',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:class:: Foo()',
|
||||||
|
' :module: target.pep604',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:attribute:: Foo.attr',
|
||||||
|
' :module: target.pep604',
|
||||||
|
' :type: int | str',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:method:: Foo.meth(x: int | str, y: int | str) -> int | str',
|
||||||
|
' :module: target.pep604',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:data:: attr',
|
||||||
|
' :module: target.pep604',
|
||||||
|
' :type: int | str',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:function:: sum(x: int | str, y: int | str) -> int | str',
|
||||||
|
' :module: target.pep604',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
def test_hide_value(app):
|
def test_hide_value(app):
|
||||||
|
@ -106,6 +106,73 @@ def test_inherited_instance_variable(app):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.')
|
||||||
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
|
def test_uninitialized_attributes(app):
|
||||||
|
options = {"members": None,
|
||||||
|
"inherited-members": True}
|
||||||
|
actual = do_autodoc(app, 'class', 'target.uninitialized_attributes.Derived', options)
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. py:class:: Derived()',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:attribute:: Derived.attr1',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
' :type: int',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:attribute:: Derived.attr3',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
' :type: int',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.')
|
||||||
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
|
def test_undocumented_uninitialized_attributes(app):
|
||||||
|
options = {"members": None,
|
||||||
|
"inherited-members": True,
|
||||||
|
"undoc-members": True}
|
||||||
|
actual = do_autodoc(app, 'class', 'target.uninitialized_attributes.Derived', options)
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. py:class:: Derived()',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:attribute:: Derived.attr1',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
' :type: int',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:attribute:: Derived.attr2',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
' :type: str',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:attribute:: Derived.attr3',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
' :type: int',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:attribute:: Derived.attr4',
|
||||||
|
' :module: target.uninitialized_attributes',
|
||||||
|
' :type: str',
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_decorators(app):
|
def test_decorators(app):
|
||||||
actual = do_autodoc(app, 'class', 'target.decorator.Baz')
|
actual = do_autodoc(app, 'class', 'target.decorator.Baz')
|
||||||
assert list(actual) == [
|
assert list(actual) == [
|
||||||
|
@ -250,10 +250,10 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning):
|
|||||||
'<span class="pre">Bar</span></code></a>' in html)
|
'<span class="pre">Bar</span></code></a>' in html)
|
||||||
assert ('<a class="reference external"'
|
assert ('<a class="reference external"'
|
||||||
' href="https://docs.python.org/index.html#foons"'
|
' href="https://docs.python.org/index.html#foons"'
|
||||||
' title="(in foo v2.0)">foons</a>' in html)
|
' title="(in foo v2.0)"><span class="pre">foons</span></a>' in html)
|
||||||
assert ('<a class="reference external"'
|
assert ('<a class="reference external"'
|
||||||
' href="https://docs.python.org/index.html#foons_bartype"'
|
' href="https://docs.python.org/index.html#foons_bartype"'
|
||||||
' title="(in foo v2.0)">bartype</a>' in html)
|
' title="(in foo v2.0)"><span class="pre">bartype</span></a>' in html)
|
||||||
|
|
||||||
|
|
||||||
def test_missing_reference_jsdomain(tempdir, app, status, warning):
|
def test_missing_reference_jsdomain(tempdir, app, status, warning):
|
||||||
|
@ -117,6 +117,13 @@ def test_restify_type_ForwardRef():
|
|||||||
assert restify(ForwardRef("myint")) == ":class:`myint`"
|
assert restify(ForwardRef("myint")) == ":class:`myint`"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.')
|
||||||
|
def test_restify_type_union_operator():
|
||||||
|
assert restify(int | None) == "Optional[:class:`int`]" # type: ignore
|
||||||
|
assert restify(int | str) == ":class:`int` | :class:`str`" # type: ignore
|
||||||
|
assert restify(int | str | None) == "Optional[:class:`int` | :class:`str`]" # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def test_restify_broken_type_hints():
|
def test_restify_broken_type_hints():
|
||||||
assert restify(BrokenType) == ':class:`tests.test_util_typing.BrokenType`'
|
assert restify(BrokenType) == ':class:`tests.test_util_typing.BrokenType`'
|
||||||
|
|
||||||
@ -206,5 +213,12 @@ def test_stringify_type_hints_alias():
|
|||||||
assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore
|
assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.')
|
||||||
|
def test_stringify_type_union_operator():
|
||||||
|
assert stringify(int | None) == "Optional[int]" # type: ignore
|
||||||
|
assert stringify(int | str) == "int | str" # type: ignore
|
||||||
|
assert stringify(int | str | None) == "Optional[int | str]" # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def test_stringify_broken_type_hints():
|
def test_stringify_broken_type_hints():
|
||||||
assert stringify(BrokenType) == 'tests.test_util_typing.BrokenType'
|
assert stringify(BrokenType) == 'tests.test_util_typing.BrokenType'
|
||||||
|
Loading…
Reference in New Issue
Block a user