mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '1.7'
This commit is contained in:
commit
3e57ea0a52
23
CHANGES
23
CHANGES
@ -181,7 +181,7 @@ Documentation
|
||||
* #5083: Fix wrong make.bat option for internationalization.
|
||||
* #5115: napoleon: add admonitions added by #4613 to the docs.
|
||||
|
||||
Release 1.7.6 (in development)
|
||||
Release 1.7.7 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
@ -199,6 +199,15 @@ Features added
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 1.7.6 (released Jul 17, 2018)
|
||||
=====================================
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #5037: LaTeX ``\sphinxupquote{}`` breaks in Russian
|
||||
* sphinx.testing uses deprecated pytest API; ``Node.get_marker(name)``
|
||||
* #5016: crashed when recommonmark.AutoStrictify is enabled
|
||||
@ -216,6 +225,7 @@ Bugs fixed
|
||||
* #5091: latex: curly braces in index entries are not handled correctly
|
||||
* #5070: epub: Wrong internal href fragment links
|
||||
* #5104: apidoc: Interface of ``sphinx.apidoc:main()`` has changed
|
||||
* #4272: PDF builds of French projects have issues with XeTeX
|
||||
* #5076: napoleon raises RuntimeError with python 3.7
|
||||
* #5125: sphinx-build: Interface of ``sphinx:main()`` has changed
|
||||
* sphinx-build: ``sphinx.cmd.build.main()`` refers ``sys.argv`` instead of given
|
||||
@ -225,9 +235,14 @@ Bugs fixed
|
||||
* autosummary: warnings of autosummary indicates wrong location (refs: #5146)
|
||||
* #5143: autodoc: crashed on inspecting dict like object which does not support
|
||||
sorting
|
||||
|
||||
Testing
|
||||
--------
|
||||
* #5139: autodoc: Enum argument missing if it shares value with another
|
||||
* #4946: py domain: rtype field could not handle "None" as a type
|
||||
* #5176: LaTeX: indexing of terms containing ``@``, ``!``, or ``"`` fails
|
||||
* #5161: html: crashes if copying static files are failed
|
||||
* #5167: autodoc: Fix formatting type annotations for tuples with more than two
|
||||
arguments
|
||||
* #3329: i18n: crashed by auto-symbol footnote references
|
||||
* #5158: autosummary: module summary has been broken when it starts with heading
|
||||
|
||||
Release 1.7.5 (released May 29, 2018)
|
||||
=====================================
|
||||
|
@ -31,7 +31,7 @@ directory = sphinx/locale/
|
||||
|
||||
[flake8]
|
||||
max-line-length = 95
|
||||
ignore = E116,E241,E251,E741,I101
|
||||
ignore = E116,E241,E251,E741,W504,I101
|
||||
exclude = .git,.tox,.venv,tests/*,build/*,doc/_build/*,sphinx/search/*,doc/usage/extensions/example*.py
|
||||
application-import-names = sphinx
|
||||
import-order-style = smarkets
|
||||
|
@ -848,85 +848,94 @@ class StandaloneHTMLBuilder(Builder):
|
||||
try:
|
||||
copyfile(path.join(self.srcdir, src),
|
||||
path.join(self.outdir, '_downloads', dest))
|
||||
except Exception as err:
|
||||
except EnvironmentError as err:
|
||||
logger.warning(__('cannot copy downloadable file %r: %s'),
|
||||
path.join(self.srcdir, src), err)
|
||||
|
||||
def copy_static_files(self):
|
||||
# type: () -> None
|
||||
# copy static files
|
||||
logger.info(bold(__('copying static files... ')), nonl=True)
|
||||
ensuredir(path.join(self.outdir, '_static'))
|
||||
# first, create pygments style file
|
||||
with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f:
|
||||
f.write(self.highlighter.get_stylesheet()) # type: ignore
|
||||
# then, copy translations JavaScript file
|
||||
if self.config.language is not None:
|
||||
jsfile = self._get_translations_js()
|
||||
if jsfile:
|
||||
copyfile(jsfile, path.join(self.outdir, '_static',
|
||||
'translations.js'))
|
||||
try:
|
||||
# copy static files
|
||||
logger.info(bold(__('copying static files... ')), nonl=True)
|
||||
ensuredir(path.join(self.outdir, '_static'))
|
||||
# first, create pygments style file
|
||||
with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f:
|
||||
f.write(self.highlighter.get_stylesheet()) # type: ignore
|
||||
# then, copy translations JavaScript file
|
||||
if self.config.language is not None:
|
||||
jsfile = self._get_translations_js()
|
||||
if jsfile:
|
||||
copyfile(jsfile, path.join(self.outdir, '_static',
|
||||
'translations.js'))
|
||||
|
||||
# copy non-minified stemmer JavaScript file
|
||||
if self.indexer is not None:
|
||||
jsfile = self.indexer.get_js_stemmer_rawcode()
|
||||
if jsfile:
|
||||
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
|
||||
# copy non-minified stemmer JavaScript file
|
||||
if self.indexer is not None:
|
||||
jsfile = self.indexer.get_js_stemmer_rawcode()
|
||||
if jsfile:
|
||||
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
|
||||
|
||||
ctx = self.globalcontext.copy()
|
||||
ctx = self.globalcontext.copy()
|
||||
|
||||
# add context items for search function used in searchtools.js_t
|
||||
if self.indexer is not None:
|
||||
ctx.update(self.indexer.context_for_searchtool())
|
||||
# add context items for search function used in searchtools.js_t
|
||||
if self.indexer is not None:
|
||||
ctx.update(self.indexer.context_for_searchtool())
|
||||
|
||||
# then, copy over theme-supplied static files
|
||||
if self.theme:
|
||||
for theme_path in self.theme.get_theme_dirs()[::-1]:
|
||||
entry = path.join(theme_path, 'static')
|
||||
copy_asset(entry, path.join(self.outdir, '_static'), excluded=DOTFILES,
|
||||
# then, copy over theme-supplied static files
|
||||
if self.theme:
|
||||
for theme_path in self.theme.get_theme_dirs()[::-1]:
|
||||
entry = path.join(theme_path, 'static')
|
||||
copy_asset(entry, path.join(self.outdir, '_static'), excluded=DOTFILES,
|
||||
context=ctx, renderer=self.templates)
|
||||
# then, copy over all user-supplied static files
|
||||
excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
|
||||
for static_path in self.config.html_static_path:
|
||||
entry = path.join(self.confdir, static_path)
|
||||
if not path.exists(entry):
|
||||
logger.warning(__('html_static_path entry %r does not exist'), entry)
|
||||
continue
|
||||
copy_asset(entry, path.join(self.outdir, '_static'), excluded,
|
||||
context=ctx, renderer=self.templates)
|
||||
# then, copy over all user-supplied static files
|
||||
excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
|
||||
for static_path in self.config.html_static_path:
|
||||
entry = path.join(self.confdir, static_path)
|
||||
if not path.exists(entry):
|
||||
logger.warning(__('html_static_path entry %r does not exist'), entry)
|
||||
continue
|
||||
copy_asset(entry, path.join(self.outdir, '_static'), excluded,
|
||||
context=ctx, renderer=self.templates)
|
||||
# copy logo and favicon files if not already in static path
|
||||
if self.config.html_logo:
|
||||
logobase = path.basename(self.config.html_logo)
|
||||
logotarget = path.join(self.outdir, '_static', logobase)
|
||||
if not path.isfile(path.join(self.confdir, self.config.html_logo)):
|
||||
logger.warning(__('logo file %r does not exist'), self.config.html_logo)
|
||||
elif not path.isfile(logotarget):
|
||||
copyfile(path.join(self.confdir, self.config.html_logo),
|
||||
logotarget)
|
||||
if self.config.html_favicon:
|
||||
iconbase = path.basename(self.config.html_favicon)
|
||||
icontarget = path.join(self.outdir, '_static', iconbase)
|
||||
if not path.isfile(path.join(self.confdir, self.config.html_favicon)):
|
||||
logger.warning(__('favicon file %r does not exist'), self.config.html_favicon)
|
||||
elif not path.isfile(icontarget):
|
||||
copyfile(path.join(self.confdir, self.config.html_favicon),
|
||||
icontarget)
|
||||
logger.info('done')
|
||||
# copy logo and favicon files if not already in static path
|
||||
if self.config.html_logo:
|
||||
logobase = path.basename(self.config.html_logo)
|
||||
logotarget = path.join(self.outdir, '_static', logobase)
|
||||
if not path.isfile(path.join(self.confdir, self.config.html_logo)):
|
||||
logger.warning(__('logo file %r does not exist'), self.config.html_logo)
|
||||
elif not path.isfile(logotarget):
|
||||
copyfile(path.join(self.confdir, self.config.html_logo),
|
||||
logotarget)
|
||||
if self.config.html_favicon:
|
||||
iconbase = path.basename(self.config.html_favicon)
|
||||
icontarget = path.join(self.outdir, '_static', iconbase)
|
||||
if not path.isfile(path.join(self.confdir, self.config.html_favicon)):
|
||||
logger.warning(__('favicon file %r does not exist'),
|
||||
self.config.html_favicon)
|
||||
elif not path.isfile(icontarget):
|
||||
copyfile(path.join(self.confdir, self.config.html_favicon),
|
||||
icontarget)
|
||||
logger.info('done')
|
||||
except EnvironmentError as err:
|
||||
# TODO: In py3, EnvironmentError (and IOError) was merged into OSError.
|
||||
# So it should be replaced by IOError on dropping py2 support
|
||||
logger.warning(__('cannot copy static file %r'), err)
|
||||
|
||||
def copy_extra_files(self):
|
||||
# type: () -> None
|
||||
# copy html_extra_path files
|
||||
logger.info(bold(__('copying extra files... ')), nonl=True)
|
||||
excluded = Matcher(self.config.exclude_patterns)
|
||||
try:
|
||||
# copy html_extra_path files
|
||||
logger.info(bold(__('copying extra files... ')), nonl=True)
|
||||
excluded = Matcher(self.config.exclude_patterns)
|
||||
|
||||
for extra_path in self.config.html_extra_path:
|
||||
entry = path.join(self.confdir, extra_path)
|
||||
if not path.exists(entry):
|
||||
logger.warning(__('html_extra_path entry %r does not exist'), entry)
|
||||
continue
|
||||
for extra_path in self.config.html_extra_path:
|
||||
entry = path.join(self.confdir, extra_path)
|
||||
if not path.exists(entry):
|
||||
logger.warning(__('html_extra_path entry %r does not exist'), entry)
|
||||
continue
|
||||
|
||||
copy_asset(entry, self.outdir, excluded)
|
||||
logger.info(__('done'))
|
||||
copy_asset(entry, self.outdir, excluded)
|
||||
logger.info(__('done'))
|
||||
except EnvironmentError as err:
|
||||
logger.warning(__('cannot copy extra file %r'), err)
|
||||
|
||||
def write_buildinfo(self):
|
||||
# type: () -> None
|
||||
|
@ -160,7 +160,7 @@ class CheckExternalLinksBuilder(Builder):
|
||||
# the server and the network
|
||||
response = requests.head(req_url, config=self.app.config, **kwargs)
|
||||
response.raise_for_status()
|
||||
except HTTPError as err:
|
||||
except HTTPError:
|
||||
# retry with GET request if that fails, some servers
|
||||
# don't like HEAD requests.
|
||||
response = requests.get(req_url, stream=True, config=self.app.config,
|
||||
|
@ -168,7 +168,15 @@ class PyXrefMixin(object):
|
||||
|
||||
|
||||
class PyField(PyXrefMixin, Field):
|
||||
pass
|
||||
def make_xref(self, rolename, domain, target,
|
||||
innernode=nodes.emphasis, contnode=None, env=None):
|
||||
# type: (unicode, unicode, unicode, nodes.Node, nodes.Node, BuildEnvironment) -> nodes.Node # NOQA
|
||||
if rolename == 'class' and target == 'None':
|
||||
# None is not a type, so use obj role instead.
|
||||
rolename = 'obj'
|
||||
|
||||
return super(PyField, self).make_xref(rolename, domain, target,
|
||||
innernode, contnode, env)
|
||||
|
||||
|
||||
class PyGroupedField(PyXrefMixin, GroupedField):
|
||||
|
@ -221,12 +221,21 @@ def get_object_members(subject, objpath, attrgetter, analyzer=None):
|
||||
for name, value in subject.__members__.items():
|
||||
obj_dict[name] = value
|
||||
|
||||
members = {}
|
||||
members = {} # type: Dict[str, Attribute]
|
||||
|
||||
# enum members
|
||||
if isenumclass(subject):
|
||||
for name, value in subject.__members__.items():
|
||||
if name not in members:
|
||||
members[name] = Attribute(name, True, value)
|
||||
|
||||
# other members
|
||||
for name in dir(subject):
|
||||
try:
|
||||
value = attrgetter(subject, name)
|
||||
directly_defined = name in obj_dict
|
||||
members[name] = Attribute(name, directly_defined, value)
|
||||
if name not in members:
|
||||
members[name] = Attribute(name, directly_defined, value)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
|
@ -473,21 +473,32 @@ def extract_summary(doc, document):
|
||||
doc = doc[:i]
|
||||
break
|
||||
|
||||
# Try to find the "first sentence", which may span multiple lines
|
||||
sentences = periods_re.split(" ".join(doc)) # type: ignore
|
||||
if len(sentences) == 1:
|
||||
summary = sentences[0].strip()
|
||||
if doc == []:
|
||||
return ''
|
||||
|
||||
# parse the docstring
|
||||
state_machine = RSTStateMachine(state_classes, 'Body')
|
||||
node = new_document('', document.settings)
|
||||
node.reporter = NullReporter()
|
||||
state_machine.run(doc, node)
|
||||
|
||||
if not isinstance(node[0], nodes.paragraph):
|
||||
# document starts with non-paragraph: pick up the first line
|
||||
summary = doc[0].strip()
|
||||
else:
|
||||
summary = ''
|
||||
state_machine = RSTStateMachine(state_classes, 'Body')
|
||||
while sentences:
|
||||
summary += sentences.pop(0) + '.'
|
||||
node = new_document('', document.settings)
|
||||
node.reporter = NullReporter()
|
||||
state_machine.run([summary], node)
|
||||
if not node.traverse(nodes.system_message):
|
||||
# considered as that splitting by period does not break inline markups
|
||||
break
|
||||
# Try to find the "first sentence", which may span multiple lines
|
||||
sentences = periods_re.split(" ".join(doc)) # type: ignore
|
||||
if len(sentences) == 1:
|
||||
summary = sentences[0].strip()
|
||||
else:
|
||||
summary = ''
|
||||
while sentences:
|
||||
summary += sentences.pop(0) + '.'
|
||||
node[:] = []
|
||||
state_machine.run([summary], node)
|
||||
if not node.traverse(nodes.system_message):
|
||||
# considered as that splitting by period does not break inline markups
|
||||
break
|
||||
|
||||
# strip literal notation mark ``::`` from tail of summary
|
||||
summary = literal_re.sub('.', summary)
|
||||
|
@ -269,7 +269,7 @@ def find_autosummary_in_docstring(name, module=None, filename=None):
|
||||
pass
|
||||
except ImportError as e:
|
||||
print("Failed to import '%s': %s" % (name, e))
|
||||
except SystemExit as e:
|
||||
except SystemExit:
|
||||
print("Failed to import '%s'; the module executes module level "
|
||||
"statement and it might call sys.exit()." % name)
|
||||
return []
|
||||
|
@ -276,10 +276,9 @@ class Locale(SphinxTransform):
|
||||
continue # skip
|
||||
|
||||
# auto-numbered foot note reference should use original 'ids'.
|
||||
def is_autonumber_footnote_ref(node):
|
||||
def is_autofootnote_ref(node):
|
||||
# type: (nodes.Node) -> bool
|
||||
return isinstance(node, nodes.footnote_reference) and \
|
||||
node.get('auto') == 1
|
||||
return isinstance(node, nodes.footnote_reference) and node.get('auto')
|
||||
|
||||
def list_replace_or_append(lst, old, new):
|
||||
# type: (List, Any, Any) -> None
|
||||
@ -287,8 +286,8 @@ class Locale(SphinxTransform):
|
||||
lst[lst.index(old)] = new
|
||||
else:
|
||||
lst.append(new)
|
||||
old_foot_refs = node.traverse(is_autonumber_footnote_ref)
|
||||
new_foot_refs = patch.traverse(is_autonumber_footnote_ref)
|
||||
old_foot_refs = node.traverse(is_autofootnote_ref)
|
||||
new_foot_refs = patch.traverse(is_autofootnote_ref)
|
||||
if len(old_foot_refs) != len(new_foot_refs):
|
||||
old_foot_ref_rawsources = [ref.rawsource for ref in old_foot_refs]
|
||||
new_foot_ref_rawsources = [ref.rawsource for ref in new_foot_refs]
|
||||
@ -309,8 +308,14 @@ class Locale(SphinxTransform):
|
||||
new['ids'] = old['ids']
|
||||
for id in new['ids']:
|
||||
self.document.ids[id] = new
|
||||
list_replace_or_append(
|
||||
self.document.autofootnote_refs, old, new)
|
||||
|
||||
if new['auto'] == 1:
|
||||
# autofootnote_refs
|
||||
list_replace_or_append(self.document.autofootnote_refs, old, new)
|
||||
else:
|
||||
# symbol_footnote_refs
|
||||
list_replace_or_append(self.document.symbol_footnote_refs, old, new)
|
||||
|
||||
if refname:
|
||||
list_replace_or_append(
|
||||
self.document.footnote_refs.setdefault(refname, []),
|
||||
|
@ -532,6 +532,13 @@ class Signature(object):
|
||||
|
||||
if annotation.__module__ == 'builtins':
|
||||
return annotation.__qualname__ # type: ignore
|
||||
elif (hasattr(typing, 'TupleMeta') and
|
||||
isinstance(annotation, typing.TupleMeta) and # type: ignore
|
||||
not hasattr(annotation, '__tuple_params__')):
|
||||
# This is for Python 3.6+, 3.5 case is handled below
|
||||
params = annotation.__args__
|
||||
param_str = ', '.join(self.format_annotation(p) for p in params)
|
||||
return '%s[%s]' % (qualified_name, param_str)
|
||||
elif (hasattr(typing, 'GenericMeta') and # for py36 or below
|
||||
isinstance(annotation, typing.GenericMeta)):
|
||||
# In Python 3.5.2+, all arguments are stored in __args__,
|
||||
|
@ -485,6 +485,14 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
||||
# sort out some elements
|
||||
self.elements = DEFAULT_SETTINGS.copy()
|
||||
self.elements.update(ADDITIONAL_SETTINGS.get(builder.config.latex_engine, {}))
|
||||
# for xelatex+French, don't use polyglossia
|
||||
if self.elements['latex_engine'] == 'xelatex':
|
||||
if builder.config.language:
|
||||
if builder.config.language[:2] == 'fr':
|
||||
self.elements.update({
|
||||
'polyglossia': '',
|
||||
'babel': '\\usepackage{babel}',
|
||||
})
|
||||
# allow the user to override them all
|
||||
self.check_latex_elements()
|
||||
self.elements.update(builder.config.latex_elements)
|
||||
|
@ -233,3 +233,4 @@ class EnumCls(enum.Enum):
|
||||
val2 = 23 #: doc for val2
|
||||
val3 = 34
|
||||
"""doc for val3"""
|
||||
val4 = 34
|
||||
|
@ -19,8 +19,8 @@ msgstr ""
|
||||
msgid "i18n with Footnote"
|
||||
msgstr "I18N WITH FOOTNOTE"
|
||||
|
||||
msgid "[100]_ Contents [#]_ for `i18n with Footnote`_ [ref]_ [#named]_."
|
||||
msgstr "`I18N WITH FOOTNOTE`_ INCLUDE THIS CONTENTS [#named]_ [ref]_ [#]_ [100]_."
|
||||
msgid "[100]_ Contents [#]_ for `i18n with Footnote`_ [ref]_ [#named]_ [*]_."
|
||||
msgstr "`I18N WITH FOOTNOTE`_ INCLUDE THIS CONTENTS [#named]_ [ref]_ [#]_ [100]_ [*]_."
|
||||
|
||||
msgid "This is a auto numbered footnote."
|
||||
msgstr "THIS IS A AUTO NUMBERED FOOTNOTE."
|
||||
@ -34,3 +34,5 @@ msgstr "THIS IS A NUMBERED FOOTNOTE."
|
||||
msgid "This is a auto numbered named footnote."
|
||||
msgstr "THIS IS A AUTO NUMBERED NAMED FOOTNOTE."
|
||||
|
||||
msgid "This is a auto symbol footnote."
|
||||
msgstr "THIS IS A AUTO SYMBOL FOOTNOTE."
|
||||
|
@ -4,9 +4,10 @@ i18n with Footnote
|
||||
==================
|
||||
.. #955 cant-build-html-with-footnotes-when-using
|
||||
|
||||
[100]_ Contents [#]_ for `i18n with Footnote`_ [ref]_ [#named]_.
|
||||
[100]_ Contents [#]_ for `i18n with Footnote`_ [ref]_ [#named]_ [*]_.
|
||||
|
||||
.. [#] This is a auto numbered footnote.
|
||||
.. [ref] This is a named footnote.
|
||||
.. [100] This is a numbered footnote.
|
||||
.. [#named] This is a auto numbered named footnote.
|
||||
.. [*] This is a auto symbol footnote.
|
||||
|
@ -873,13 +873,14 @@ def test_generate():
|
||||
# test members with enum attributes
|
||||
directive.env.ref_context['py:module'] = 'target'
|
||||
options.inherited_members = False
|
||||
options.undoc_members = False
|
||||
options.undoc_members = True
|
||||
options.members = ALL
|
||||
assert_processes([
|
||||
('class', 'target.EnumCls'),
|
||||
('attribute', 'target.EnumCls.val1'),
|
||||
('attribute', 'target.EnumCls.val2'),
|
||||
('attribute', 'target.EnumCls.val3'),
|
||||
('attribute', 'target.EnumCls.val4'),
|
||||
], 'class', 'EnumCls')
|
||||
assert_result_contains(
|
||||
' :annotation: = 12', 'attribute', 'EnumCls.val1')
|
||||
|
@ -85,6 +85,11 @@ def test_extract_summary(capsys):
|
||||
doc = ['blah blah::']
|
||||
assert extract_summary(doc, document) == 'blah blah.'
|
||||
|
||||
# heading
|
||||
doc = ['blah blah',
|
||||
'=========']
|
||||
assert extract_summary(doc, document) == 'blah blah'
|
||||
|
||||
_, err = capsys.readouterr()
|
||||
assert err == ''
|
||||
|
||||
|
@ -794,7 +794,7 @@ def test_xml_footnotes(app, warning):
|
||||
assert_elem(
|
||||
para0[0],
|
||||
['I18N WITH FOOTNOTE', 'INCLUDE THIS CONTENTS',
|
||||
'2', '[ref]', '1', '100', '.'],
|
||||
'2', '[ref]', '1', '100', '*', '.'],
|
||||
['i18n-with-footnote', 'ref'])
|
||||
|
||||
footnote0 = secs[0].findall('footnote')
|
||||
@ -813,6 +813,11 @@ def test_xml_footnotes(app, warning):
|
||||
['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'],
|
||||
None,
|
||||
['named'])
|
||||
assert_elem(
|
||||
footnote0[3],
|
||||
['*', 'THIS IS A AUTO SYMBOL FOOTNOTE.'],
|
||||
None,
|
||||
None)
|
||||
|
||||
citation0 = secs[0].findall('citation')
|
||||
assert_elem(
|
||||
|
@ -231,7 +231,7 @@ def test_Signature_partialmethod():
|
||||
@pytest.mark.skipif(sys.version_info < (3, 5),
|
||||
reason='type annotation test is available on py35 or above')
|
||||
def test_Signature_annotations():
|
||||
from typing_test_data import f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11
|
||||
from typing_test_data import f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12
|
||||
|
||||
# Class annotations
|
||||
sig = inspect.Signature(f0).format_args()
|
||||
@ -284,6 +284,10 @@ def test_Signature_annotations():
|
||||
sig = inspect.Signature(f11, has_retval=False).format_args()
|
||||
assert sig == '(x: CustomAnnotation, y: 123)'
|
||||
|
||||
# tuple with more than two items
|
||||
sig = inspect.Signature(f12).format_args()
|
||||
assert sig == '() -> Tuple[int, str, int]'
|
||||
|
||||
|
||||
def test_safe_getattr_with_default():
|
||||
class Foo(object):
|
||||
|
@ -62,3 +62,7 @@ class CustomAnnotation:
|
||||
|
||||
def f11(x: CustomAnnotation(), y: 123) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def f12() -> Tuple[int, str, int]:
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user