summaryrefslogtreecommitdiffstats
path: root/tests/test_util
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/test_util/__init__.py0
-rw-r--r--tests/test_util/intersphinx_data.py64
-rw-r--r--tests/test_util/test_util.py (renamed from tests/test_util.py)0
-rw-r--r--tests/test_util/test_util_console.py90
-rw-r--r--tests/test_util/test_util_display.py (renamed from tests/test_util_display.py)22
-rw-r--r--tests/test_util/test_util_docstrings.py (renamed from tests/test_util_docstrings.py)0
-rw-r--r--tests/test_util/test_util_docutils.py (renamed from tests/test_util_docutils.py)0
-rw-r--r--tests/test_util/test_util_docutils_sphinx_directive.py139
-rw-r--r--tests/test_util/test_util_fileutil.py (renamed from tests/test_util_fileutil.py)43
-rw-r--r--tests/test_util/test_util_i18n.py (renamed from tests/test_util_i18n.py)12
-rw-r--r--tests/test_util/test_util_images.py (renamed from tests/test_util_images.py)0
-rw-r--r--tests/test_util/test_util_inspect.py (renamed from tests/test_util_inspect.py)124
-rw-r--r--tests/test_util/test_util_inventory.py (renamed from tests/test_util_inventory.py)79
-rw-r--r--tests/test_util/test_util_logging.py (renamed from tests/test_util_logging.py)24
-rw-r--r--tests/test_util/test_util_matching.py (renamed from tests/test_util_matching.py)0
-rw-r--r--tests/test_util/test_util_nodes.py (renamed from tests/test_util_nodes.py)4
-rw-r--r--tests/test_util/test_util_rst.py (renamed from tests/test_util_rst.py)0
-rw-r--r--tests/test_util/test_util_template.py (renamed from tests/test_util_template.py)0
-rw-r--r--tests/test_util/test_util_typing.py (renamed from tests/test_util_typing.py)428
-rw-r--r--tests/test_util/typing_test_data.py (renamed from tests/typing_test_data.py)10
20 files changed, 782 insertions, 257 deletions
diff --git a/tests/test_util/__init__.py b/tests/test_util/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test_util/__init__.py
diff --git a/tests/test_util/intersphinx_data.py b/tests/test_util/intersphinx_data.py
new file mode 100644
index 0000000..95cf80a
--- /dev/null
+++ b/tests/test_util/intersphinx_data.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+import zlib
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from typing import Final
+
+INVENTORY_V1: Final[bytes] = b'''\
+# Sphinx inventory version 1
+# Project: foo
+# Version: 1.0
+module mod foo.html
+module.cls class foo.html
+'''
+
+INVENTORY_V2: Final[bytes] = b'''\
+# Sphinx inventory version 2
+# Project: foo
+# Version: 2.0
+# The remainder of this file is compressed with zlib.
+''' + zlib.compress(b'''\
+module1 py:module 0 foo.html#module-module1 Long Module desc
+module2 py:module 0 foo.html#module-$ -
+module1.func py:function 1 sub/foo.html#$ -
+module1.Foo.bar py:method 1 index.html#foo.Bar.baz -
+CFunc c:function 2 cfunc.html#CFunc -
+std cpp:type 1 index.html#std -
+std::uint8_t cpp:type 1 index.html#std_uint8_t -
+foo::Bar cpp:class 1 index.html#cpp_foo_bar -
+foo::Bar::baz cpp:function 1 index.html#cpp_foo_bar_baz -
+foons cpp:type 1 index.html#foons -
+foons::bartype cpp:type 1 index.html#foons_bartype -
+a term std:term -1 glossary.html#term-a-term -
+ls.-l std:cmdoption 1 index.html#cmdoption-ls-l -
+docname std:doc -1 docname.html -
+foo js:module 1 index.html#foo -
+foo.bar js:class 1 index.html#foo.bar -
+foo.bar.baz js:method 1 index.html#foo.bar.baz -
+foo.bar.qux js:data 1 index.html#foo.bar.qux -
+a term including:colon std:term -1 glossary.html#term-a-term-including-colon -
+The-Julia-Domain std:label -1 write_inventory/#$ The Julia Domain
+''')
+
+INVENTORY_V2_NO_VERSION: Final[bytes] = b'''\
+# Sphinx inventory version 2
+# Project: foo
+# Version:
+# The remainder of this file is compressed with zlib.
+''' + zlib.compress(b'''\
+module1 py:module 0 foo.html#module-module1 Long Module desc
+''')
+
+INVENTORY_V2_AMBIGUOUS_TERMS: Final[bytes] = b'''\
+# Sphinx inventory version 2
+# Project: foo
+# Version: 2.0
+# The remainder of this file is compressed with zlib.
+''' + zlib.compress(b'''\
+a term std:term -1 glossary.html#term-a-term -
+A term std:term -1 glossary.html#term-a-term -
+b term std:term -1 document.html#id5 -
+B term std:term -1 document.html#B -
+''')
diff --git a/tests/test_util.py b/tests/test_util/test_util.py
index 4389894..4389894 100644
--- a/tests/test_util.py
+++ b/tests/test_util/test_util.py
diff --git a/tests/test_util/test_util_console.py b/tests/test_util/test_util_console.py
new file mode 100644
index 0000000..b617a33
--- /dev/null
+++ b/tests/test_util/test_util_console.py
@@ -0,0 +1,90 @@
+from __future__ import annotations
+
+import itertools
+import operator
+from typing import TYPE_CHECKING
+
+import pytest
+
+from sphinx.util.console import blue, reset, strip_colors, strip_escape_sequences
+
+if TYPE_CHECKING:
+ from collections.abc import Callable, Sequence
+ from typing import Final, TypeVar
+
+ _T = TypeVar('_T')
+
+CURSOR_UP: Final[str] = '\x1b[2A' # ignored ANSI code
+ERASE_LINE: Final[str] = '\x1b[2K' # supported ANSI code
+TEXT: Final[str] = '\x07 Hello world!'
+
+
+@pytest.mark.parametrize(
+ ('strip_function', 'ansi_base_blocks', 'text_base_blocks'),
+ [
+ (
+ strip_colors,
+ # double ERASE_LINE so that the tested strings may have 2 of them
+ [TEXT, blue(TEXT), reset(TEXT), ERASE_LINE, ERASE_LINE, CURSOR_UP],
+ # :func:`strip_colors` removes color codes but keeps ERASE_LINE and CURSOR_UP
+ [TEXT, TEXT, TEXT, ERASE_LINE, ERASE_LINE, CURSOR_UP],
+ ),
+ (
+ strip_escape_sequences,
+ # double ERASE_LINE so that the tested strings may have 2 of them
+ [TEXT, blue(TEXT), reset(TEXT), ERASE_LINE, ERASE_LINE, CURSOR_UP],
+ # :func:`strip_escape_sequences` strips ANSI codes known by Sphinx
+ [TEXT, TEXT, TEXT, '', '', CURSOR_UP],
+ ),
+ ],
+ ids=[strip_colors.__name__, strip_escape_sequences.__name__],
+)
+def test_strip_ansi(
+ strip_function: Callable[[str], str],
+ ansi_base_blocks: Sequence[str],
+ text_base_blocks: Sequence[str],
+) -> None:
+ assert callable(strip_function)
+ assert len(text_base_blocks) == len(ansi_base_blocks)
+ N = len(ansi_base_blocks)
+
+ def next_ansi_blocks(choices: Sequence[str], n: int) -> Sequence[str]:
+ # Get a list of *n* words from a cyclic sequence of *choices*.
+ #
+ # For instance ``next_ansi_blocks(['a', 'b'], 3) == ['a', 'b', 'a']``.
+ stream = itertools.cycle(choices)
+ return list(map(operator.itemgetter(0), zip(stream, range(n))))
+
+ # generate all permutations of length N
+ for sigma in itertools.permutations(range(N), N):
+ # apply the permutation on the blocks with ANSI codes
+ ansi_blocks = list(map(ansi_base_blocks.__getitem__, sigma))
+ # apply the permutation on the blocks with stripped codes
+ text_blocks = list(map(text_base_blocks.__getitem__, sigma))
+
+ for glue, n in itertools.product(['.', '\n', '\r\n'], range(4 * N)):
+ ansi_strings = next_ansi_blocks(ansi_blocks, n)
+ text_strings = next_ansi_blocks(text_blocks, n)
+ assert len(ansi_strings) == len(text_strings) == n
+
+ ansi_string = glue.join(ansi_strings)
+ text_string = glue.join(text_strings)
+ assert strip_function(ansi_string) == text_string
+
+
+def test_strip_ansi_short_forms():
+ # In Sphinx, we always "normalize" the color codes so that they
+ # match "\x1b\[(\d\d;){0,2}(\d\d)m" but it might happen that
+ # some messages use '\x1b[0m' instead of ``reset(s)``, so we
+ # test whether this alternative form is supported or not.
+
+ for strip_function in [strip_colors, strip_escape_sequences]:
+ # \x1b[m and \x1b[0m are equivalent to \x1b[00m
+ assert strip_function('\x1b[m') == ''
+ assert strip_function('\x1b[0m') == ''
+
+ # \x1b[1m is equivalent to \x1b[01m
+ assert strip_function('\x1b[1mbold\x1b[0m') == 'bold'
+
+ # \x1b[K is equivalent to \x1b[0K
+ assert strip_escape_sequences('\x1b[K') == ''
diff --git a/tests/test_util_display.py b/tests/test_util/test_util_display.py
index 9ecdd6a..a18fa1e 100644
--- a/tests/test_util_display.py
+++ b/tests/test_util/test_util_display.py
@@ -2,8 +2,8 @@
import pytest
-from sphinx.testing.util import strip_escseq
from sphinx.util import logging
+from sphinx.util.console import strip_colors
from sphinx.util.display import (
SkipProgressMessage,
display_chunk,
@@ -28,13 +28,14 @@ def test_status_iterator_length_0(app, status, warning):
status.seek(0)
status.truncate(0)
yields = list(status_iterator(['hello', 'sphinx', 'world'], 'testing ... '))
- output = strip_escseq(status.getvalue())
+ output = strip_colors(status.getvalue())
assert 'testing ... hello sphinx world \n' in output
assert yields == ['hello', 'sphinx', 'world']
@pytest.mark.sphinx('dummy')
-def test_status_iterator_verbosity_0(app, status, warning):
+def test_status_iterator_verbosity_0(app, status, warning, monkeypatch):
+ monkeypatch.setenv("FORCE_COLOR", "1")
logging.setup(app, status, warning)
# test for status_iterator (verbosity=0)
@@ -42,7 +43,7 @@ def test_status_iterator_verbosity_0(app, status, warning):
status.truncate(0)
yields = list(status_iterator(['hello', 'sphinx', 'world'], 'testing ... ',
length=3, verbosity=0))
- output = strip_escseq(status.getvalue())
+ output = strip_colors(status.getvalue())
assert 'testing ... [ 33%] hello\r' in output
assert 'testing ... [ 67%] sphinx\r' in output
assert 'testing ... [100%] world\r\n' in output
@@ -50,7 +51,8 @@ def test_status_iterator_verbosity_0(app, status, warning):
@pytest.mark.sphinx('dummy')
-def test_status_iterator_verbosity_1(app, status, warning):
+def test_status_iterator_verbosity_1(app, status, warning, monkeypatch):
+ monkeypatch.setenv("FORCE_COLOR", "1")
logging.setup(app, status, warning)
# test for status_iterator (verbosity=1)
@@ -58,7 +60,7 @@ def test_status_iterator_verbosity_1(app, status, warning):
status.truncate(0)
yields = list(status_iterator(['hello', 'sphinx', 'world'], 'testing ... ',
length=3, verbosity=1))
- output = strip_escseq(status.getvalue())
+ output = strip_colors(status.getvalue())
assert 'testing ... [ 33%] hello\n' in output
assert 'testing ... [ 67%] sphinx\n' in output
assert 'testing ... [100%] world\n\n' in output
@@ -73,14 +75,14 @@ def test_progress_message(app, status, warning):
with progress_message('testing'):
logger.info('blah ', nonl=True)
- output = strip_escseq(status.getvalue())
+ output = strip_colors(status.getvalue())
assert 'testing... blah done\n' in output
# skipping case
with progress_message('testing'):
raise SkipProgressMessage('Reason: %s', 'error') # NoQA: EM101
- output = strip_escseq(status.getvalue())
+ output = strip_colors(status.getvalue())
assert 'testing... skipped\nReason: error\n' in output
# error case
@@ -90,7 +92,7 @@ def test_progress_message(app, status, warning):
except Exception:
pass
- output = strip_escseq(status.getvalue())
+ output = strip_colors(status.getvalue())
assert 'testing... failed\n' in output
# decorator
@@ -99,5 +101,5 @@ def test_progress_message(app, status, warning):
logger.info('in func ', nonl=True)
func()
- output = strip_escseq(status.getvalue())
+ output = strip_colors(status.getvalue())
assert 'testing... in func done\n' in output
diff --git a/tests/test_util_docstrings.py b/tests/test_util/test_util_docstrings.py
index 813e84e..813e84e 100644
--- a/tests/test_util_docstrings.py
+++ b/tests/test_util/test_util_docstrings.py
diff --git a/tests/test_util_docutils.py b/tests/test_util/test_util_docutils.py
index 69999eb..69999eb 100644
--- a/tests/test_util_docutils.py
+++ b/tests/test_util/test_util_docutils.py
diff --git a/tests/test_util/test_util_docutils_sphinx_directive.py b/tests/test_util/test_util_docutils_sphinx_directive.py
new file mode 100644
index 0000000..8f5ab3f
--- /dev/null
+++ b/tests/test_util/test_util_docutils_sphinx_directive.py
@@ -0,0 +1,139 @@
+from __future__ import annotations
+
+from types import SimpleNamespace
+
+from docutils import nodes
+from docutils.parsers.rst.languages import en as english # type: ignore[attr-defined]
+from docutils.parsers.rst.states import Inliner, RSTState, RSTStateMachine, state_classes
+from docutils.statemachine import StringList
+
+from sphinx.util.docutils import SphinxDirective, new_document
+
+
+def make_directive(*, env: SimpleNamespace, input_lines: StringList | None = None) -> SphinxDirective:
+ _, directive = make_directive_and_state(env=env, input_lines=input_lines)
+ return directive
+
+
+def make_directive_and_state(*, env: SimpleNamespace, input_lines: StringList | None = None) -> tuple[RSTState, SphinxDirective]:
+ sm = RSTStateMachine(state_classes, initial_state='Body')
+ sm.reporter = object()
+ if input_lines is not None:
+ sm.input_lines = input_lines
+ state = RSTState(sm)
+ state.document = new_document('<tests>')
+ state.document.settings.env = env
+ state.document.settings.tab_width = 4
+ state.document.settings.pep_references = None
+ state.document.settings.rfc_references = None
+ inliner = Inliner()
+ inliner.init_customizations(state.document.settings)
+ state.inliner = inliner
+ state.parent = None
+ state.memo = SimpleNamespace(
+ document=state.document,
+ language=english,
+ inliner=state.inliner,
+ reporter=state.document.reporter,
+ section_level=0,
+ title_styles=[],
+ )
+ directive = SphinxDirective(
+ name='test_directive',
+ arguments=[],
+ options={},
+ content=StringList(),
+ lineno=0,
+ content_offset=0,
+ block_text='',
+ state=state,
+ state_machine=state.state_machine,
+ )
+ return state, directive
+
+
+def test_sphinx_directive_env():
+ state, directive = make_directive_and_state(env=SimpleNamespace())
+
+ assert hasattr(directive, 'env')
+ assert directive.env is state.document.settings.env
+
+
+def test_sphinx_directive_config():
+ env = SimpleNamespace(config=object())
+ state, directive = make_directive_and_state(env=env)
+
+ assert hasattr(directive, 'config')
+ assert directive.config is directive.env.config
+ assert directive.config is state.document.settings.env.config
+
+
+def test_sphinx_directive_get_source_info():
+ env = SimpleNamespace()
+ input_lines = StringList(['spam'], source='<source>')
+ directive = make_directive(env=env, input_lines=input_lines)
+
+ assert directive.get_source_info() == ('<source>', 1)
+
+
+def test_sphinx_directive_set_source_info():
+ env = SimpleNamespace()
+ input_lines = StringList(['spam'], source='<source>')
+ directive = make_directive(env=env, input_lines=input_lines)
+
+ node = nodes.Element()
+ directive.set_source_info(node)
+ assert node.source == '<source>'
+ assert node.line == 1
+
+
+def test_sphinx_directive_get_location():
+ env = SimpleNamespace()
+ input_lines = StringList(['spam'], source='<source>')
+ directive = make_directive(env=env, input_lines=input_lines)
+
+ assert directive.get_location() == '<source>:1'
+
+
+def test_sphinx_directive_parse_content_to_nodes():
+ directive = make_directive(env=SimpleNamespace())
+ content = 'spam\n====\n\nEggs! *Lobster thermidor.*'
+ directive.content = StringList(content.split('\n'), source='<source>')
+
+ parsed = directive.parse_content_to_nodes(allow_section_headings=True)
+ assert len(parsed) == 1
+ node = parsed[0]
+ assert isinstance(node, nodes.section)
+ assert len(node.children) == 2
+ assert isinstance(node.children[0], nodes.title)
+ assert node.children[0].astext() == 'spam'
+ assert isinstance(node.children[1], nodes.paragraph)
+ assert node.children[1].astext() == 'Eggs! Lobster thermidor.'
+
+
+def test_sphinx_directive_parse_text_to_nodes():
+ directive = make_directive(env=SimpleNamespace())
+ content = 'spam\n====\n\nEggs! *Lobster thermidor.*'
+
+ parsed = directive.parse_text_to_nodes(content, allow_section_headings=True)
+ assert len(parsed) == 1
+ node = parsed[0]
+ assert isinstance(node, nodes.section)
+ assert len(node.children) == 2
+ assert isinstance(node.children[0], nodes.title)
+ assert node.children[0].astext() == 'spam'
+ assert isinstance(node.children[1], nodes.paragraph)
+ assert node.children[1].astext() == 'Eggs! Lobster thermidor.'
+
+
+def test_sphinx_directive_parse_inline():
+ directive = make_directive(env=SimpleNamespace())
+ content = 'Eggs! *Lobster thermidor.*'
+
+ parsed, messages = directive.parse_inline(content)
+ assert len(parsed) == 2
+ assert messages == []
+ assert parsed[0] == nodes.Text('Eggs! ')
+ assert isinstance(parsed[1], nodes.emphasis)
+ assert parsed[1].rawsource == '*Lobster thermidor.*'
+ assert parsed[1][0] == nodes.Text('Lobster thermidor.')
diff --git a/tests/test_util_fileutil.py b/tests/test_util/test_util_fileutil.py
index 9c23821..2071fc3 100644
--- a/tests/test_util_fileutil.py
+++ b/tests/test_util/test_util_fileutil.py
@@ -2,8 +2,11 @@
from unittest import mock
+import pytest
+
from sphinx.jinja2glue import BuiltinTemplateLoader
-from sphinx.util.fileutil import copy_asset, copy_asset_file
+from sphinx.util import strip_colors
+from sphinx.util.fileutil import _template_basename, copy_asset, copy_asset_file
class DummyTemplateLoader(BuiltinTemplateLoader):
@@ -28,9 +31,9 @@ def test_copy_asset_file(tmp_path):
assert src.read_text(encoding='utf8') == dest.read_text(encoding='utf8')
# copy template file
- src = (tmp_path / 'asset.txt_t')
+ src = (tmp_path / 'asset.txt.jinja')
src.write_text('# {{var1}} data', encoding='utf8')
- dest = (tmp_path / 'output.txt_t')
+ dest = (tmp_path / 'output.txt.jinja')
copy_asset_file(str(src), str(dest), {'var1': 'template'}, renderer)
assert not dest.exists()
@@ -38,7 +41,7 @@ def test_copy_asset_file(tmp_path):
assert (tmp_path / 'output.txt').read_text(encoding='utf8') == '# template data'
# copy template file to subdir
- src = (tmp_path / 'asset.txt_t')
+ src = (tmp_path / 'asset.txt.jinja')
src.write_text('# {{var1}} data', encoding='utf8')
subdir1 = (tmp_path / 'subdir')
subdir1.mkdir(parents=True, exist_ok=True)
@@ -48,14 +51,14 @@ def test_copy_asset_file(tmp_path):
assert (subdir1 / 'asset.txt').read_text(encoding='utf8') == '# template data'
# copy template file without context
- src = (tmp_path / 'asset.txt_t')
+ src = (tmp_path / 'asset.txt.jinja')
subdir2 = (tmp_path / 'subdir2')
subdir2.mkdir(parents=True, exist_ok=True)
copy_asset_file(src, subdir2)
assert not (subdir2 / 'asset.txt').exists()
- assert (subdir2 / 'asset.txt_t').exists()
- assert (subdir2 / 'asset.txt_t').read_text(encoding='utf8') == '# {{var1}} data'
+ assert (subdir2 / 'asset.txt.jinja').exists()
+ assert (subdir2 / 'asset.txt.jinja').read_text(encoding='utf8') == '# {{var1}} data'
def test_copy_asset(tmp_path):
@@ -65,12 +68,12 @@ def test_copy_asset(tmp_path):
source = (tmp_path / 'source')
source.mkdir(parents=True, exist_ok=True)
(source / 'index.rst').write_text('index.rst', encoding='utf8')
- (source / 'foo.rst_t').write_text('{{var1}}.rst', encoding='utf8')
+ (source / 'foo.rst.jinja').write_text('{{var1}}.rst', encoding='utf8')
(source / '_static').mkdir(parents=True, exist_ok=True)
(source / '_static' / 'basic.css').write_text('basic.css', encoding='utf8')
(source / '_templates').mkdir(parents=True, exist_ok=True)
(source / '_templates' / 'layout.html').write_text('layout.html', encoding='utf8')
- (source / '_templates' / 'sidebar.html_t').write_text('sidebar: {{var2}}', encoding='utf8')
+ (source / '_templates' / 'sidebar.html.jinja').write_text('sidebar: {{var2}}', encoding='utf8')
# copy a single file
assert not (tmp_path / 'test1').exists()
@@ -101,3 +104,25 @@ def test_copy_asset(tmp_path):
assert not (destdir / '_static' / 'basic.css').exists()
assert (destdir / '_templates' / 'layout.html').exists()
assert not (destdir / '_templates' / 'sidebar.html').exists()
+
+
+@pytest.mark.sphinx('html', testroot='util-copyasset_overwrite')
+def test_copy_asset_overwrite(app):
+ app.build()
+ src = app.srcdir / 'myext_static' / 'custom-styles.css'
+ dst = app.outdir / '_static' / 'custom-styles.css'
+ assert (
+ f'Copying the source path {src} to {dst} will overwrite data, '
+ 'as a file already exists at the destination path '
+ 'and the content does not match.\n'
+ ) in strip_colors(app.status.getvalue())
+
+
+def test_template_basename():
+ assert _template_basename('asset.txt') is None
+ assert _template_basename('asset.txt.jinja') == 'asset.txt'
+ assert _template_basename('sidebar.html.jinja') == 'sidebar.html'
+
+
+def test_legacy_template_basename():
+ assert _template_basename('asset.txt_t') == 'asset.txt'
diff --git a/tests/test_util_i18n.py b/tests/test_util/test_util_i18n.py
index 9a1ecc5..f2f3249 100644
--- a/tests/test_util_i18n.py
+++ b/tests/test_util/test_util_i18n.py
@@ -75,16 +75,10 @@ def test_format_date():
format = '%x'
assert i18n.format_date(format, date=datet, language='en') == 'Feb 7, 2016'
format = '%X'
- if BABEL_VERSION >= (2, 12):
- assert i18n.format_date(format, date=datet, language='en') == '5:11:17\u202fAM'
- else:
- assert i18n.format_date(format, date=datet, language='en') == '5:11:17 AM'
+ assert i18n.format_date(format, date=datet, language='en') == '5:11:17\u202fAM'
assert i18n.format_date(format, date=date, language='en') == 'Feb 7, 2016'
format = '%c'
- if BABEL_VERSION >= (2, 12):
- assert i18n.format_date(format, date=datet, language='en') == 'Feb 7, 2016, 5:11:17\u202fAM'
- else:
- assert i18n.format_date(format, date=datet, language='en') == 'Feb 7, 2016, 5:11:17 AM'
+ assert i18n.format_date(format, date=datet, language='en') == 'Feb 7, 2016, 5:11:17\u202fAM'
assert i18n.format_date(format, date=date, language='en') == 'Feb 7, 2016'
# timezone
@@ -160,6 +154,8 @@ def test_CatalogRepository(tmp_path):
(tmp_path / 'loc2' / 'xx' / 'LC_MESSAGES').mkdir(parents=True, exist_ok=True)
(tmp_path / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#', encoding='utf8')
(tmp_path / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test7.po').write_text('#', encoding='utf8')
+ (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / '.dotdir2').mkdir(parents=True, exist_ok=True)
+ (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / '.dotdir2' / 'test8.po').write_text('#', encoding='utf8')
# for language xx
repo = i18n.CatalogRepository(tmp_path, ['loc1', 'loc2'], 'xx', 'utf-8')
diff --git a/tests/test_util_images.py b/tests/test_util/test_util_images.py
index 15853c7..15853c7 100644
--- a/tests/test_util_images.py
+++ b/tests/test_util/test_util_images.py
diff --git a/tests/test_util_inspect.py b/tests/test_util/test_util_inspect.py
index 73f9656..764ca20 100644
--- a/tests/test_util_inspect.py
+++ b/tests/test_util/test_util_inspect.py
@@ -62,6 +62,7 @@ async def coroutinefunc():
async def asyncgenerator():
yield
+
partial_func = functools.partial(func)
partial_coroutinefunc = functools.partial(coroutinefunc)
@@ -222,170 +223,146 @@ def test_signature_partialmethod():
def test_signature_annotations():
- from .typing_test_data import (
- Node,
- f0,
- f1,
- f2,
- f3,
- f4,
- f5,
- f6,
- f7,
- f8,
- f9,
- f10,
- f11,
- f12,
- f13,
- f14,
- f15,
- f16,
- f17,
- f18,
- f19,
- f20,
- f21,
- f22,
- f23,
- f24,
- f25,
- )
+ import tests.test_util.typing_test_data as mod
# Class annotations
- sig = inspect.signature(f0)
+ sig = inspect.signature(mod.f0)
assert stringify_signature(sig) == '(x: int, y: numbers.Integral) -> None'
# Generic types with concrete parameters
- sig = inspect.signature(f1)
+ sig = inspect.signature(mod.f1)
assert stringify_signature(sig) == '(x: list[int]) -> typing.List[int]'
# TypeVars and generic types with TypeVars
- sig = inspect.signature(f2)
- assert stringify_signature(sig) == ('(x: typing.List[tests.typing_test_data.T],'
- ' y: typing.List[tests.typing_test_data.T_co],'
- ' z: tests.typing_test_data.T'
- ') -> typing.List[tests.typing_test_data.T_contra]')
+ sig = inspect.signature(mod.f2)
+ assert stringify_signature(sig) == ('(x: typing.List[tests.test_util.typing_test_data.T],'
+ ' y: typing.List[tests.test_util.typing_test_data.T_co],'
+ ' z: tests.test_util.typing_test_data.T'
+ ') -> typing.List[tests.test_util.typing_test_data.T_contra]')
# Union types
- sig = inspect.signature(f3)
+ sig = inspect.signature(mod.f3)
assert stringify_signature(sig) == '(x: str | numbers.Integral) -> None'
# Quoted annotations
- sig = inspect.signature(f4)
+ sig = inspect.signature(mod.f4)
assert stringify_signature(sig) == '(x: str, y: str) -> None'
# Keyword-only arguments
- sig = inspect.signature(f5)
+ sig = inspect.signature(mod.f5)
assert stringify_signature(sig) == '(x: int, *, y: str, z: str) -> None'
# Keyword-only arguments with varargs
- sig = inspect.signature(f6)
+ sig = inspect.signature(mod.f6)
assert stringify_signature(sig) == '(x: int, *args, y: str, z: str) -> None'
# Space around '=' for defaults
- sig = inspect.signature(f7)
+ sig = inspect.signature(mod.f7)
if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig) == '(x: int | None = None, y: dict = {}) -> None'
else:
assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None'
# Callable types
- sig = inspect.signature(f8)
+ sig = inspect.signature(mod.f8)
assert stringify_signature(sig) == '(x: typing.Callable[[int, str], int]) -> None'
- sig = inspect.signature(f9)
+ sig = inspect.signature(mod.f9)
assert stringify_signature(sig) == '(x: typing.Callable) -> None'
# Tuple types
- sig = inspect.signature(f10)
+ sig = inspect.signature(mod.f10)
assert stringify_signature(sig) == '(x: typing.Tuple[int, str], y: typing.Tuple[int, ...]) -> None'
# Instance annotations
- sig = inspect.signature(f11)
+ sig = inspect.signature(mod.f11)
assert stringify_signature(sig) == '(x: CustomAnnotation, y: 123) -> None'
# tuple with more than two items
- sig = inspect.signature(f12)
+ sig = inspect.signature(mod.f12)
assert stringify_signature(sig) == '() -> typing.Tuple[int, str, int]'
# optional
- sig = inspect.signature(f13)
+ sig = inspect.signature(mod.f13)
assert stringify_signature(sig) == '() -> str | None'
# optional union
- sig = inspect.signature(f20)
+ sig = inspect.signature(mod.f20)
assert stringify_signature(sig) in ('() -> int | str | None',
'() -> str | int | None')
# Any
- sig = inspect.signature(f14)
+ sig = inspect.signature(mod.f14)
assert stringify_signature(sig) == '() -> typing.Any'
# ForwardRef
- sig = inspect.signature(f15)
+ sig = inspect.signature(mod.f15)
assert stringify_signature(sig) == '(x: Unknown, y: int) -> typing.Any'
# keyword only arguments (1)
- sig = inspect.signature(f16)
+ sig = inspect.signature(mod.f16)
assert stringify_signature(sig) == '(arg1, arg2, *, arg3=None, arg4=None)'
# keyword only arguments (2)
- sig = inspect.signature(f17)
+ sig = inspect.signature(mod.f17)
assert stringify_signature(sig) == '(*, arg3, arg4)'
- sig = inspect.signature(f18)
+ sig = inspect.signature(mod.f18)
assert stringify_signature(sig) == ('(self, arg1: int | typing.Tuple = 10) -> '
'typing.List[typing.Dict]')
# annotations for variadic and keyword parameters
- sig = inspect.signature(f19)
+ sig = inspect.signature(mod.f19)
assert stringify_signature(sig) == '(*args: int, **kwargs: str)'
# default value is inspect.Signature.empty
- sig = inspect.signature(f21)
+ sig = inspect.signature(mod.f21)
assert stringify_signature(sig) == "(arg1='whatever', arg2)"
# type hints by string
- sig = inspect.signature(Node.children)
- assert stringify_signature(sig) == '(self) -> typing.List[tests.typing_test_data.Node]'
+ sig = inspect.signature(mod.Node.children)
+ assert stringify_signature(sig) == '(self) -> typing.List[tests.test_util.typing_test_data.Node]'
- sig = inspect.signature(Node.__init__)
- assert stringify_signature(sig) == '(self, parent: tests.typing_test_data.Node | None) -> None'
+ sig = inspect.signature(mod.Node.__init__)
+ assert stringify_signature(sig) == '(self, parent: tests.test_util.typing_test_data.Node | None) -> None'
# show_annotation is False
- sig = inspect.signature(f7)
+ sig = inspect.signature(mod.f7)
assert stringify_signature(sig, show_annotation=False) == '(x=None, y={})'
# show_return_annotation is False
- sig = inspect.signature(f7)
+ sig = inspect.signature(mod.f7)
if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig, show_return_annotation=False) == '(x: int | None = None, y: dict = {})'
else:
assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})'
# unqualified_typehints is True
- sig = inspect.signature(f7)
+ sig = inspect.signature(mod.f7)
if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig, unqualified_typehints=True) == '(x: int | None = None, y: dict = {}) -> None'
else:
assert stringify_signature(sig, unqualified_typehints=True) == '(x: int = None, y: dict = {}) -> None'
# case: separator at head
- sig = inspect.signature(f22)
+ sig = inspect.signature(mod.f22)
assert stringify_signature(sig) == '(*, a, b)'
# case: separator in the middle
- sig = inspect.signature(f23)
+ sig = inspect.signature(mod.f23)
assert stringify_signature(sig) == '(a, b, /, c, d)'
- sig = inspect.signature(f24)
+ sig = inspect.signature(mod.f24)
assert stringify_signature(sig) == '(a, /, *, b)'
# case: separator at tail
- sig = inspect.signature(f25)
+ sig = inspect.signature(mod.f25)
assert stringify_signature(sig) == '(a, b, /)'
+ # collapse Literal types
+ sig = inspect.signature(mod.f26)
+ assert stringify_signature(sig) == "(x: typing.Literal[1, 2, 3] = 1, y: typing.Literal['a', 'b'] = 'a') -> None"
+
def test_signature_from_str_basic():
signature = '(a, b, *args, c=0, d="blah", **kwargs)'
@@ -667,6 +644,17 @@ def test_object_description_enum():
assert inspect.object_description(MyEnum.FOO) == "MyEnum.FOO"
+def test_object_description_enum_custom_repr():
+ class MyEnum(enum.Enum):
+ FOO = 1
+ BAR = 2
+
+ def __repr__(self):
+ return self.name
+
+ assert inspect.object_description(MyEnum.FOO) == "FOO"
+
+
def test_getslots():
class Foo:
pass
@@ -678,7 +666,7 @@ def test_getslots():
__slots__ = {'attr': 'docstring'}
class Qux:
- __slots__ = 'attr'
+ __slots__ = 'attr' # NoQA: PLC0205
assert inspect.getslots(Foo) is None
assert inspect.getslots(Bar) == {'attr': None}
@@ -842,7 +830,7 @@ def test_getdoc_inherited_decorated_method():
"""
class Bar(Foo):
- @functools.lru_cache # noqa: B019
+ @functools.lru_cache # NoQA: B019
def meth(self):
# inherited and decorated method
pass
diff --git a/tests/test_util_inventory.py b/tests/test_util/test_util_inventory.py
index 2c20763..211dc17 100644
--- a/tests/test_util_inventory.py
+++ b/tests/test_util/test_util_inventory.py
@@ -1,59 +1,22 @@
"""Test inventory util functions."""
import os
import posixpath
-import zlib
from io import BytesIO
+import sphinx.locale
from sphinx.testing.util import SphinxTestApp
from sphinx.util.inventory import InventoryFile
-inventory_v1 = b'''\
-# Sphinx inventory version 1
-# Project: foo
-# Version: 1.0
-module mod foo.html
-module.cls class foo.html
-'''
-
-inventory_v2 = b'''\
-# Sphinx inventory version 2
-# Project: foo
-# Version: 2.0
-# The remainder of this file is compressed with zlib.
-''' + zlib.compress(b'''\
-module1 py:module 0 foo.html#module-module1 Long Module desc
-module2 py:module 0 foo.html#module-$ -
-module1.func py:function 1 sub/foo.html#$ -
-module1.Foo.bar py:method 1 index.html#foo.Bar.baz -
-CFunc c:function 2 cfunc.html#CFunc -
-std cpp:type 1 index.html#std -
-std::uint8_t cpp:type 1 index.html#std_uint8_t -
-foo::Bar cpp:class 1 index.html#cpp_foo_bar -
-foo::Bar::baz cpp:function 1 index.html#cpp_foo_bar_baz -
-foons cpp:type 1 index.html#foons -
-foons::bartype cpp:type 1 index.html#foons_bartype -
-a term std:term -1 glossary.html#term-a-term -
-ls.-l std:cmdoption 1 index.html#cmdoption-ls-l -
-docname std:doc -1 docname.html -
-foo js:module 1 index.html#foo -
-foo.bar js:class 1 index.html#foo.bar -
-foo.bar.baz js:method 1 index.html#foo.bar.baz -
-foo.bar.qux js:data 1 index.html#foo.bar.qux -
-a term including:colon std:term -1 glossary.html#term-a-term-including-colon -
-''')
-
-inventory_v2_not_having_version = b'''\
-# Sphinx inventory version 2
-# Project: foo
-# Version:
-# The remainder of this file is compressed with zlib.
-''' + zlib.compress(b'''\
-module1 py:module 0 foo.html#module-module1 Long Module desc
-''')
+from tests.test_util.intersphinx_data import (
+ INVENTORY_V1,
+ INVENTORY_V2,
+ INVENTORY_V2_AMBIGUOUS_TERMS,
+ INVENTORY_V2_NO_VERSION,
+)
def test_read_inventory_v1():
- f = BytesIO(inventory_v1)
+ f = BytesIO(INVENTORY_V1)
invdata = InventoryFile.load(f, '/util', posixpath.join)
assert invdata['py:module']['module'] == \
('foo', '1.0', '/util/foo.html#module-module', '-')
@@ -62,7 +25,7 @@ def test_read_inventory_v1():
def test_read_inventory_v2():
- f = BytesIO(inventory_v2)
+ f = BytesIO(INVENTORY_V2)
invdata = InventoryFile.load(f, '/util', posixpath.join)
assert len(invdata['py:module']) == 2
@@ -80,12 +43,30 @@ def test_read_inventory_v2():
def test_read_inventory_v2_not_having_version():
- f = BytesIO(inventory_v2_not_having_version)
+ f = BytesIO(INVENTORY_V2_NO_VERSION)
invdata = InventoryFile.load(f, '/util', posixpath.join)
assert invdata['py:module']['module1'] == \
('foo', '', '/util/foo.html#module-module1', 'Long Module desc')
+def test_ambiguous_definition_warning(warning, status):
+ f = BytesIO(INVENTORY_V2_AMBIGUOUS_TERMS)
+ InventoryFile.load(f, '/util', posixpath.join)
+
+ def _multiple_defs_notice_for(entity: str) -> str:
+ return f'contains multiple definitions for {entity}'
+
+ # was warning-level; reduced to info-level - see https://github.com/sphinx-doc/sphinx/issues/12613
+ mult_defs_a, mult_defs_b = (
+ _multiple_defs_notice_for('std:term:a'),
+ _multiple_defs_notice_for('std:term:b'),
+ )
+ assert mult_defs_a not in warning.getvalue().lower()
+ assert mult_defs_a not in status.getvalue().lower()
+ assert mult_defs_b not in warning.getvalue().lower()
+ assert mult_defs_b in status.getvalue().lower()
+
+
def _write_appconfig(dir, language, prefix=None):
prefix = prefix or language
os.makedirs(dir / prefix, exist_ok=True)
@@ -99,8 +80,8 @@ def _write_appconfig(dir, language, prefix=None):
def _build_inventory(srcdir):
app = SphinxTestApp(srcdir=srcdir)
app.build()
- app.cleanup()
- return (app.outdir / 'objects.inv')
+ sphinx.locale.translators.clear()
+ return app.outdir / 'objects.inv'
def test_inventory_localization(tmp_path):
diff --git a/tests/test_util_logging.py b/tests/test_util/test_util_logging.py
index 4d506a8..4ee548a 100644
--- a/tests/test_util_logging.py
+++ b/tests/test_util/test_util_logging.py
@@ -8,9 +8,8 @@ import pytest
from docutils import nodes
from sphinx.errors import SphinxWarning
-from sphinx.testing.util import strip_escseq
from sphinx.util import logging, osutil
-from sphinx.util.console import colorize
+from sphinx.util.console import colorize, strip_colors
from sphinx.util.logging import is_suppressed_warning, prefixed_warnings
from sphinx.util.parallel import ParallelTasks
@@ -110,7 +109,7 @@ def test_once_warning_log(app, status, warning):
logger.warning('message: %d', 1, once=True)
logger.warning('message: %d', 2, once=True)
- assert 'WARNING: message: 1\nWARNING: message: 2\n' in strip_escseq(warning.getvalue())
+ assert 'WARNING: message: 1\nWARNING: message: 2\n' in strip_colors(warning.getvalue())
def test_is_suppressed_warning():
@@ -278,7 +277,7 @@ def test_pending_warnings(app, status, warning):
assert 'WARNING: message3' not in warning.getvalue()
# actually logged as ordered
- assert 'WARNING: message2\nWARNING: message3' in strip_escseq(warning.getvalue())
+ assert 'WARNING: message2\nWARNING: message3' in strip_colors(warning.getvalue())
def test_colored_logs(app, status, warning):
@@ -396,3 +395,20 @@ def test_get_node_location_abspath():
location = logging.get_node_location(n)
assert location == absolute_filename + ':'
+
+
+@pytest.mark.sphinx(confoverrides={'show_warning_types': True})
+def test_show_warning_types(app, status, warning):
+ logging.setup(app, status, warning)
+ logger = logging.getLogger(__name__)
+ logger.warning('message2')
+ logger.warning('message3', type='test')
+ logger.warning('message4', type='test', subtype='logging')
+
+ warnings = strip_colors(warning.getvalue()).splitlines()
+
+ assert warnings == [
+ 'WARNING: message2',
+ 'WARNING: message3 [test]',
+ 'WARNING: message4 [test.logging]',
+ ]
diff --git a/tests/test_util_matching.py b/tests/test_util/test_util_matching.py
index 7d865ba..7d865ba 100644
--- a/tests/test_util_matching.py
+++ b/tests/test_util/test_util_matching.py
diff --git a/tests/test_util_nodes.py b/tests/test_util/test_util_nodes.py
index 92e4dc1..ddd5974 100644
--- a/tests/test_util_nodes.py
+++ b/tests/test_util/test_util_nodes.py
@@ -237,8 +237,8 @@ def test_split_explicit_target(title, expected):
def test_apply_source_workaround_literal_block_no_source():
"""Regression test for #11091.
- Test that apply_source_workaround doesn't raise.
- """
+ Test that apply_source_workaround doesn't raise.
+ """
literal_block = nodes.literal_block('', '')
list_item = nodes.list_item('', literal_block)
bullet_list = nodes.bullet_list('', list_item)
diff --git a/tests/test_util_rst.py b/tests/test_util/test_util_rst.py
index d50c90c..d50c90c 100644
--- a/tests/test_util_rst.py
+++ b/tests/test_util/test_util_rst.py
diff --git a/tests/test_util_template.py b/tests/test_util/test_util_template.py
index 4601179..4601179 100644
--- a/tests/test_util_template.py
+++ b/tests/test_util/test_util_template.py
diff --git a/tests/test_util_typing.py b/tests/test_util/test_util_typing.py
index d79852e..956cffe 100644
--- a/tests/test_util_typing.py
+++ b/tests/test_util/test_util_typing.py
@@ -1,16 +1,42 @@
"""Tests util.typing functions."""
+import dataclasses
import sys
+import typing as t
+from collections import abc
+from contextvars import Context, ContextVar, Token
from enum import Enum
from numbers import Integral
from struct import Struct
-from types import TracebackType
+from types import (
+ AsyncGeneratorType,
+ BuiltinFunctionType,
+ BuiltinMethodType,
+ CellType,
+ ClassMethodDescriptorType,
+ CodeType,
+ CoroutineType,
+ FrameType,
+ FunctionType,
+ GeneratorType,
+ GetSetDescriptorType,
+ LambdaType,
+ MappingProxyType,
+ MemberDescriptorType,
+ MethodDescriptorType,
+ MethodType,
+ MethodWrapperType,
+ ModuleType,
+ TracebackType,
+ WrapperDescriptorType,
+)
from typing import (
+ Annotated,
Any,
- Callable,
Dict,
- Generator,
+ ForwardRef,
List,
+ Literal,
NewType,
Optional,
Tuple,
@@ -21,7 +47,7 @@ from typing import (
import pytest
from sphinx.ext.autodoc import mock
-from sphinx.util.typing import INVALID_BUILTIN_CLASSES, restify, stringify_annotation
+from sphinx.util.typing import _INVALID_BUILTIN_CLASSES, restify, stringify_annotation
class MyClass1:
@@ -48,6 +74,11 @@ class BrokenType:
__args__ = int
+@dataclasses.dataclass(frozen=True)
+class Gt:
+ gt: float
+
+
def test_restify():
assert restify(int) == ":py:class:`int`"
assert restify(int, "smart") == ":py:class:`int`"
@@ -76,11 +107,55 @@ def test_restify():
def test_is_invalid_builtin_class():
# if these tests start failing, it means that the __module__
- # of one of these classes has changed, and INVALID_BUILTIN_CLASSES
+ # of one of these classes has changed, and _INVALID_BUILTIN_CLASSES
# in sphinx.util.typing needs to be updated.
- assert INVALID_BUILTIN_CLASSES.keys() == {Struct, TracebackType}
+ assert _INVALID_BUILTIN_CLASSES.keys() == {
+ Context,
+ ContextVar,
+ Token,
+ Struct,
+ AsyncGeneratorType,
+ BuiltinFunctionType,
+ BuiltinMethodType,
+ CellType,
+ ClassMethodDescriptorType,
+ CodeType,
+ CoroutineType,
+ FrameType,
+ FunctionType,
+ GeneratorType,
+ GetSetDescriptorType,
+ LambdaType,
+ MappingProxyType,
+ MemberDescriptorType,
+ MethodDescriptorType,
+ MethodType,
+ MethodWrapperType,
+ ModuleType,
+ TracebackType,
+ WrapperDescriptorType,
+ }
assert Struct.__module__ == '_struct'
+ assert AsyncGeneratorType.__module__ == 'builtins'
+ assert BuiltinFunctionType.__module__ == 'builtins'
+ assert BuiltinMethodType.__module__ == 'builtins'
+ assert CellType.__module__ == 'builtins'
+ assert ClassMethodDescriptorType.__module__ == 'builtins'
+ assert CodeType.__module__ == 'builtins'
+ assert CoroutineType.__module__ == 'builtins'
+ assert FrameType.__module__ == 'builtins'
+ assert FunctionType.__module__ == 'builtins'
+ assert GeneratorType.__module__ == 'builtins'
+ assert GetSetDescriptorType.__module__ == 'builtins'
+ assert LambdaType.__module__ == 'builtins'
+ assert MappingProxyType.__module__ == 'builtins'
+ assert MemberDescriptorType.__module__ == 'builtins'
+ assert MethodDescriptorType.__module__ == 'builtins'
+ assert MethodType.__module__ == 'builtins'
+ assert MethodWrapperType.__module__ == 'builtins'
+ assert ModuleType.__module__ == 'builtins'
assert TracebackType.__module__ == 'builtins'
+ assert WrapperDescriptorType.__module__ == 'builtins'
def test_restify_type_hints_containers():
@@ -103,42 +178,80 @@ def test_restify_type_hints_containers():
assert restify(List[Dict[str, Tuple]]) == (":py:class:`~typing.List`\\ "
"[:py:class:`~typing.Dict`\\ "
"[:py:class:`str`, :py:class:`~typing.Tuple`]]")
- assert restify(MyList[Tuple[int, int]]) == (":py:class:`tests.test_util_typing.MyList`\\ "
+ assert restify(MyList[Tuple[int, int]]) == (":py:class:`tests.test_util.test_util_typing.MyList`\\ "
"[:py:class:`~typing.Tuple`\\ "
"[:py:class:`int`, :py:class:`int`]]")
- assert restify(Generator[None, None, None]) == (":py:class:`~typing.Generator`\\ "
- "[:py:obj:`None`, :py:obj:`None`, "
- ":py:obj:`None`]")
+ assert restify(t.Generator[None, None, None]) == (":py:class:`~typing.Generator`\\ "
+ "[:py:obj:`None`, :py:obj:`None`, "
+ ":py:obj:`None`]")
+ assert restify(abc.Generator[None, None, None]) == (":py:class:`collections.abc.Generator`\\ "
+ "[:py:obj:`None`, :py:obj:`None`, "
+ ":py:obj:`None`]")
+ assert restify(t.Iterator[None]) == (":py:class:`~typing.Iterator`\\ "
+ "[:py:obj:`None`]")
+ assert restify(abc.Iterator[None]) == (":py:class:`collections.abc.Iterator`\\ "
+ "[:py:obj:`None`]")
-def test_restify_type_hints_Callable():
- assert restify(Callable) == ":py:class:`~typing.Callable`"
+def test_restify_Annotated():
+ assert restify(Annotated[str, "foo", "bar"]) == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']"
+ assert restify(Annotated[str, "foo", "bar"], 'smart') == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']"
+ assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`tests.test_util.test_util_typing.Gt`\\ (gt=\\ -10.0)]'
+ assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`~tests.test_util.test_util_typing.Gt`\\ (gt=\\ -10.0)]'
+
- assert restify(Callable[[str], int]) == (":py:class:`~typing.Callable`\\ "
- "[[:py:class:`str`], :py:class:`int`]")
- assert restify(Callable[..., int]) == (":py:class:`~typing.Callable`\\ "
- "[[...], :py:class:`int`]")
+def test_restify_type_hints_Callable():
+ assert restify(t.Callable) == ":py:class:`~typing.Callable`"
+ assert restify(t.Callable[[str], int]) == (":py:class:`~typing.Callable`\\ "
+ "[[:py:class:`str`], :py:class:`int`]")
+ assert restify(t.Callable[..., int]) == (":py:class:`~typing.Callable`\\ "
+ "[[...], :py:class:`int`]")
+ assert restify(abc.Callable) == ":py:class:`collections.abc.Callable`"
+ assert restify(abc.Callable[[str], int]) == (":py:class:`collections.abc.Callable`\\ "
+ "[[:py:class:`str`], :py:class:`int`]")
+ assert restify(abc.Callable[..., int]) == (":py:class:`collections.abc.Callable`\\ "
+ "[[...], :py:class:`int`]")
def test_restify_type_hints_Union():
- assert restify(Optional[int]) == ":py:obj:`~typing.Optional`\\ [:py:class:`int`]"
- assert restify(Union[str, None]) == ":py:obj:`~typing.Optional`\\ [:py:class:`str`]"
- assert restify(Union[int, str]) == (":py:obj:`~typing.Union`\\ "
- "[:py:class:`int`, :py:class:`str`]")
- assert restify(Union[int, Integral]) == (":py:obj:`~typing.Union`\\ "
- "[:py:class:`int`, :py:class:`numbers.Integral`]")
- assert restify(Union[int, Integral], "smart") == (":py:obj:`~typing.Union`\\ "
- "[:py:class:`int`,"
- " :py:class:`~numbers.Integral`]")
+ assert restify(Union[int]) == ":py:class:`int`"
+ assert restify(Union[int, str]) == ":py:class:`int` | :py:class:`str`"
+ assert restify(Optional[int]) == ":py:class:`int` | :py:obj:`None`"
+
+ assert restify(Union[str, None]) == ":py:class:`str` | :py:obj:`None`"
+ assert restify(Union[None, str]) == ":py:obj:`None` | :py:class:`str`"
+ assert restify(Optional[str]) == ":py:class:`str` | :py:obj:`None`"
+
+ assert restify(Union[int, str, None]) == (
+ ":py:class:`int` | :py:class:`str` | :py:obj:`None`"
+ )
+ assert restify(Optional[Union[int, str]]) in {
+ ":py:class:`str` | :py:class:`int` | :py:obj:`None`",
+ ":py:class:`int` | :py:class:`str` | :py:obj:`None`",
+ }
+
+ assert restify(Union[int, Integral]) == (
+ ":py:class:`int` | :py:class:`numbers.Integral`"
+ )
+ assert restify(Union[int, Integral], "smart") == (
+ ":py:class:`int` | :py:class:`~numbers.Integral`"
+ )
assert (restify(Union[MyClass1, MyClass2]) ==
- (":py:obj:`~typing.Union`\\ "
- "[:py:class:`tests.test_util_typing.MyClass1`, "
- ":py:class:`tests.test_util_typing.<MyClass2>`]"))
+ (":py:class:`tests.test_util.test_util_typing.MyClass1`"
+ " | :py:class:`tests.test_util.test_util_typing.<MyClass2>`"))
assert (restify(Union[MyClass1, MyClass2], "smart") ==
- (":py:obj:`~typing.Union`\\ "
- "[:py:class:`~tests.test_util_typing.MyClass1`,"
- " :py:class:`~tests.test_util_typing.<MyClass2>`]"))
+ (":py:class:`~tests.test_util.test_util_typing.MyClass1`"
+ " | :py:class:`~tests.test_util.test_util_typing.<MyClass2>`"))
+
+ assert (restify(Optional[Union[MyClass1, MyClass2]]) ==
+ (":py:class:`tests.test_util.test_util_typing.MyClass1`"
+ " | :py:class:`tests.test_util.test_util_typing.<MyClass2>`"
+ " | :py:obj:`None`"))
+ assert (restify(Optional[Union[MyClass1, MyClass2]], "smart") ==
+ (":py:class:`~tests.test_util.test_util_typing.MyClass1`"
+ " | :py:class:`~tests.test_util.test_util_typing.<MyClass2>`"
+ " | :py:obj:`None`"))
def test_restify_type_hints_typevars():
@@ -146,35 +259,35 @@ def test_restify_type_hints_typevars():
T_co = TypeVar('T_co', covariant=True)
T_contra = TypeVar('T_contra', contravariant=True)
- assert restify(T) == ":py:obj:`tests.test_util_typing.T`"
- assert restify(T, "smart") == ":py:obj:`~tests.test_util_typing.T`"
+ assert restify(T) == ":py:obj:`tests.test_util.test_util_typing.T`"
+ assert restify(T, "smart") == ":py:obj:`~tests.test_util.test_util_typing.T`"
- assert restify(T_co) == ":py:obj:`tests.test_util_typing.T_co`"
- assert restify(T_co, "smart") == ":py:obj:`~tests.test_util_typing.T_co`"
+ assert restify(T_co) == ":py:obj:`tests.test_util.test_util_typing.T_co`"
+ assert restify(T_co, "smart") == ":py:obj:`~tests.test_util.test_util_typing.T_co`"
- assert restify(T_contra) == ":py:obj:`tests.test_util_typing.T_contra`"
- assert restify(T_contra, "smart") == ":py:obj:`~tests.test_util_typing.T_contra`"
+ assert restify(T_contra) == ":py:obj:`tests.test_util.test_util_typing.T_contra`"
+ assert restify(T_contra, "smart") == ":py:obj:`~tests.test_util.test_util_typing.T_contra`"
- assert restify(List[T]) == ":py:class:`~typing.List`\\ [:py:obj:`tests.test_util_typing.T`]"
- assert restify(List[T], "smart") == ":py:class:`~typing.List`\\ [:py:obj:`~tests.test_util_typing.T`]"
+ assert restify(List[T]) == ":py:class:`~typing.List`\\ [:py:obj:`tests.test_util.test_util_typing.T`]"
+ assert restify(List[T], "smart") == ":py:class:`~typing.List`\\ [:py:obj:`~tests.test_util.test_util_typing.T`]"
- assert restify(list[T]) == ":py:class:`list`\\ [:py:obj:`tests.test_util_typing.T`]"
- assert restify(list[T], "smart") == ":py:class:`list`\\ [:py:obj:`~tests.test_util_typing.T`]"
+ assert restify(list[T]) == ":py:class:`list`\\ [:py:obj:`tests.test_util.test_util_typing.T`]"
+ assert restify(list[T], "smart") == ":py:class:`list`\\ [:py:obj:`~tests.test_util.test_util_typing.T`]"
if sys.version_info[:2] >= (3, 10):
- assert restify(MyInt) == ":py:class:`tests.test_util_typing.MyInt`"
- assert restify(MyInt, "smart") == ":py:class:`~tests.test_util_typing.MyInt`"
+ assert restify(MyInt) == ":py:class:`tests.test_util.test_util_typing.MyInt`"
+ assert restify(MyInt, "smart") == ":py:class:`~tests.test_util.test_util_typing.MyInt`"
else:
assert restify(MyInt) == ":py:class:`MyInt`"
assert restify(MyInt, "smart") == ":py:class:`MyInt`"
def test_restify_type_hints_custom_class():
- assert restify(MyClass1) == ":py:class:`tests.test_util_typing.MyClass1`"
- assert restify(MyClass1, "smart") == ":py:class:`~tests.test_util_typing.MyClass1`"
+ assert restify(MyClass1) == ":py:class:`tests.test_util.test_util_typing.MyClass1`"
+ assert restify(MyClass1, "smart") == ":py:class:`~tests.test_util.test_util_typing.MyClass1`"
- assert restify(MyClass2) == ":py:class:`tests.test_util_typing.<MyClass2>`"
- assert restify(MyClass2, "smart") == ":py:class:`~tests.test_util_typing.<MyClass2>`"
+ assert restify(MyClass2) == ":py:class:`tests.test_util.test_util_typing.<MyClass2>`"
+ assert restify(MyClass2, "smart") == ":py:class:`~tests.test_util.test_util_typing.<MyClass2>`"
def test_restify_type_hints_alias():
@@ -187,7 +300,6 @@ def test_restify_type_hints_alias():
def test_restify_type_ForwardRef():
- from typing import ForwardRef # type: ignore[attr-defined]
assert restify(ForwardRef("MyInt")) == ":py:class:`MyInt`"
assert restify(list[ForwardRef("MyInt")]) == ":py:class:`list`\\ [:py:class:`MyInt`]"
@@ -196,11 +308,10 @@ def test_restify_type_ForwardRef():
def test_restify_type_Literal():
- from typing import Literal # type: ignore[attr-defined]
assert restify(Literal[1, "2", "\r"]) == ":py:obj:`~typing.Literal`\\ [1, '2', '\\r']"
- assert restify(Literal[MyEnum.a], 'fully-qualified-except-typing') == ':py:obj:`~typing.Literal`\\ [:py:attr:`tests.test_util_typing.MyEnum.a`]'
- assert restify(Literal[MyEnum.a], 'smart') == ':py:obj:`~typing.Literal`\\ [:py:attr:`~tests.test_util_typing.MyEnum.a`]'
+ assert restify(Literal[MyEnum.a], 'fully-qualified-except-typing') == ':py:obj:`~typing.Literal`\\ [:py:attr:`tests.test_util.test_util_typing.MyEnum.a`]'
+ assert restify(Literal[MyEnum.a], 'smart') == ':py:obj:`~typing.Literal`\\ [:py:attr:`~tests.test_util.test_util_typing.MyEnum.a`]'
def test_restify_pep_585():
@@ -223,22 +334,47 @@ def test_restify_pep_585():
"[:py:class:`str`, :py:class:`~typing.Tuple`\\ "
"[:py:class:`str`, ...]]]")
assert restify(tuple[MyList[list[int]], int]) == (":py:class:`tuple`\\ ["
- ":py:class:`tests.test_util_typing.MyList`\\ "
+ ":py:class:`tests.test_util.test_util_typing.MyList`\\ "
"[:py:class:`list`\\ [:py:class:`int`]], "
":py:class:`int`]")
+def test_restify_Unpack():
+ from typing_extensions import Unpack as UnpackCompat
+
+ class X(t.TypedDict):
+ x: int
+ y: int
+ label: str
+
+ # Unpack is considered as typing special form so we always have '~'
+ if sys.version_info[:2] >= (3, 12):
+ expect = r':py:obj:`~typing.Unpack`\ [:py:class:`X`]'
+ assert restify(UnpackCompat['X'], 'fully-qualified-except-typing') == expect
+ assert restify(UnpackCompat['X'], 'smart') == expect
+ else:
+ expect = r':py:obj:`~typing_extensions.Unpack`\ [:py:class:`X`]'
+ assert restify(UnpackCompat['X'], 'fully-qualified-except-typing') == expect
+ assert restify(UnpackCompat['X'], 'smart') == expect
+
+ if sys.version_info[:2] >= (3, 11):
+ expect = r':py:obj:`~typing.Unpack`\ [:py:class:`X`]'
+ assert restify(t.Unpack['X'], 'fully-qualified-except-typing') == expect
+ assert restify(t.Unpack['X'], 'smart') == expect
+
+
@pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason='python 3.10+ is required.')
def test_restify_type_union_operator():
assert restify(int | None) == ":py:class:`int` | :py:obj:`None`" # type: ignore[attr-defined]
+ assert restify(None | int) == ":py:obj:`None` | :py:class:`int`" # type: ignore[attr-defined]
assert restify(int | str) == ":py:class:`int` | :py:class:`str`" # type: ignore[attr-defined]
assert restify(int | str | None) == (":py:class:`int` | :py:class:`str` | " # type: ignore[attr-defined]
":py:obj:`None`")
def test_restify_broken_type_hints():
- assert restify(BrokenType) == ':py:class:`tests.test_util_typing.BrokenType`'
- assert restify(BrokenType, "smart") == ':py:class:`~tests.test_util_typing.BrokenType`'
+ assert restify(BrokenType) == ':py:class:`tests.test_util.test_util_typing.BrokenType`'
+ assert restify(BrokenType, "smart") == ':py:class:`~tests.test_util.test_util_typing.BrokenType`'
def test_restify_mock():
@@ -249,6 +385,21 @@ def test_restify_mock():
assert restify(unknown.secret.Class, "smart") == ':py:class:`~unknown.secret.Class`'
+@pytest.mark.xfail(sys.version_info[:2] <= (3, 9), reason='ParamSpec not supported in Python 3.9.')
+def test_restify_type_hints_paramspec():
+ from typing import ParamSpec
+ P = ParamSpec('P')
+
+ assert restify(P) == ":py:obj:`tests.test_util.test_util_typing.P`"
+ assert restify(P, "smart") == ":py:obj:`~tests.test_util.test_util_typing.P`"
+
+ assert restify(P.args) == "P.args"
+ assert restify(P.args, "smart") == "P.args"
+
+ assert restify(P.kwargs) == "P.kwargs"
+ assert restify(P.kwargs, "smart") == "P.kwargs"
+
+
def test_stringify_annotation():
assert stringify_annotation(int, 'fully-qualified-except-typing') == "int"
assert stringify_annotation(int, "smart") == "int"
@@ -315,13 +466,25 @@ def test_stringify_type_hints_containers():
assert stringify_annotation(List[Dict[str, Tuple]], "fully-qualified") == "typing.List[typing.Dict[str, typing.Tuple]]"
assert stringify_annotation(List[Dict[str, Tuple]], "smart") == "~typing.List[~typing.Dict[str, ~typing.Tuple]]"
- assert stringify_annotation(MyList[Tuple[int, int]], 'fully-qualified-except-typing') == "tests.test_util_typing.MyList[Tuple[int, int]]"
- assert stringify_annotation(MyList[Tuple[int, int]], "fully-qualified") == "tests.test_util_typing.MyList[typing.Tuple[int, int]]"
- assert stringify_annotation(MyList[Tuple[int, int]], "smart") == "~tests.test_util_typing.MyList[~typing.Tuple[int, int]]"
+ assert stringify_annotation(MyList[Tuple[int, int]], 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.MyList[Tuple[int, int]]"
+ assert stringify_annotation(MyList[Tuple[int, int]], "fully-qualified") == "tests.test_util.test_util_typing.MyList[typing.Tuple[int, int]]"
+ assert stringify_annotation(MyList[Tuple[int, int]], "smart") == "~tests.test_util.test_util_typing.MyList[~typing.Tuple[int, int]]"
+
+ assert stringify_annotation(t.Generator[None, None, None], 'fully-qualified-except-typing') == "Generator[None, None, None]"
+ assert stringify_annotation(t.Generator[None, None, None], "fully-qualified") == "typing.Generator[None, None, None]"
+ assert stringify_annotation(t.Generator[None, None, None], "smart") == "~typing.Generator[None, None, None]"
- assert stringify_annotation(Generator[None, None, None], 'fully-qualified-except-typing') == "Generator[None, None, None]"
- assert stringify_annotation(Generator[None, None, None], "fully-qualified") == "typing.Generator[None, None, None]"
- assert stringify_annotation(Generator[None, None, None], "smart") == "~typing.Generator[None, None, None]"
+ assert stringify_annotation(abc.Generator[None, None, None], 'fully-qualified-except-typing') == "collections.abc.Generator[None, None, None]"
+ assert stringify_annotation(abc.Generator[None, None, None], "fully-qualified") == "collections.abc.Generator[None, None, None]"
+ assert stringify_annotation(abc.Generator[None, None, None], "smart") == "~collections.abc.Generator[None, None, None]"
+
+ assert stringify_annotation(t.Iterator[None], 'fully-qualified-except-typing') == "Iterator[None]"
+ assert stringify_annotation(t.Iterator[None], "fully-qualified") == "typing.Iterator[None]"
+ assert stringify_annotation(t.Iterator[None], "smart") == "~typing.Iterator[None]"
+
+ assert stringify_annotation(abc.Iterator[None], 'fully-qualified-except-typing') == "collections.abc.Iterator[None]"
+ assert stringify_annotation(abc.Iterator[None], "fully-qualified") == "collections.abc.Iterator[None]"
+ assert stringify_annotation(abc.Iterator[None], "smart") == "~collections.abc.Iterator[None]"
def test_stringify_type_hints_pep_585():
@@ -346,9 +509,9 @@ def test_stringify_type_hints_pep_585():
assert stringify_annotation(list[dict[str, tuple]], 'fully-qualified-except-typing') == "list[dict[str, tuple]]"
assert stringify_annotation(list[dict[str, tuple]], "smart") == "list[dict[str, tuple]]"
- assert stringify_annotation(MyList[tuple[int, int]], 'fully-qualified-except-typing') == "tests.test_util_typing.MyList[tuple[int, int]]"
- assert stringify_annotation(MyList[tuple[int, int]], "fully-qualified") == "tests.test_util_typing.MyList[tuple[int, int]]"
- assert stringify_annotation(MyList[tuple[int, int]], "smart") == "~tests.test_util_typing.MyList[tuple[int, int]]"
+ assert stringify_annotation(MyList[tuple[int, int]], 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.MyList[tuple[int, int]]"
+ assert stringify_annotation(MyList[tuple[int, int]], "fully-qualified") == "tests.test_util.test_util_typing.MyList[tuple[int, int]]"
+ assert stringify_annotation(MyList[tuple[int, int]], "smart") == "~tests.test_util.test_util_typing.MyList[tuple[int, int]]"
assert stringify_annotation(type[int], 'fully-qualified-except-typing') == "type[int]"
assert stringify_annotation(type[int], "smart") == "type[int]"
@@ -359,9 +522,36 @@ def test_stringify_type_hints_pep_585():
def test_stringify_Annotated():
- from typing import Annotated # type: ignore[attr-defined]
- assert stringify_annotation(Annotated[str, "foo", "bar"], 'fully-qualified-except-typing') == "str"
- assert stringify_annotation(Annotated[str, "foo", "bar"], "smart") == "str"
+ assert stringify_annotation(Annotated[str, "foo", "bar"], 'fully-qualified-except-typing') == "Annotated[str, 'foo', 'bar']"
+ assert stringify_annotation(Annotated[str, "foo", "bar"], 'smart') == "~typing.Annotated[str, 'foo', 'bar']"
+ assert stringify_annotation(Annotated[float, Gt(-10.0)], 'fully-qualified-except-typing') == "Annotated[float, tests.test_util.test_util_typing.Gt(gt=-10.0)]"
+ assert stringify_annotation(Annotated[float, Gt(-10.0)], 'smart') == "~typing.Annotated[float, ~tests.test_util.test_util_typing.Gt(gt=-10.0)]"
+
+
+def test_stringify_Unpack():
+ from typing_extensions import Unpack as UnpackCompat
+
+ class X(t.TypedDict):
+ x: int
+ y: int
+ label: str
+
+ if sys.version_info[:2] >= (3, 11):
+ # typing.Unpack is introduced in 3.11 but typing_extensions.Unpack only
+ # uses typing.Unpack in 3.12+, so the objects are not synchronised with
+ # each other, but we will assume that users use typing.Unpack.
+ import typing
+
+ UnpackCompat = typing.Unpack # NoQA: F811
+ assert stringify_annotation(UnpackCompat['X']) == 'Unpack[X]'
+ assert stringify_annotation(UnpackCompat['X'], 'smart') == '~typing.Unpack[X]'
+ else:
+ assert stringify_annotation(UnpackCompat['X']) == 'typing_extensions.Unpack[X]'
+ assert stringify_annotation(UnpackCompat['X'], 'smart') == '~typing_extensions.Unpack[X]'
+
+ if sys.version_info[:2] >= (3, 11):
+ assert stringify_annotation(t.Unpack['X']) == 'Unpack[X]'
+ assert stringify_annotation(t.Unpack['X'], 'smart') == '~typing.Unpack[X]'
def test_stringify_type_hints_string():
@@ -395,17 +585,29 @@ def test_stringify_type_hints_string():
def test_stringify_type_hints_Callable():
- assert stringify_annotation(Callable, 'fully-qualified-except-typing') == "Callable"
- assert stringify_annotation(Callable, "fully-qualified") == "typing.Callable"
- assert stringify_annotation(Callable, "smart") == "~typing.Callable"
+ assert stringify_annotation(t.Callable, 'fully-qualified-except-typing') == "Callable"
+ assert stringify_annotation(t.Callable, "fully-qualified") == "typing.Callable"
+ assert stringify_annotation(t.Callable, "smart") == "~typing.Callable"
- assert stringify_annotation(Callable[[str], int], 'fully-qualified-except-typing') == "Callable[[str], int]"
- assert stringify_annotation(Callable[[str], int], "fully-qualified") == "typing.Callable[[str], int]"
- assert stringify_annotation(Callable[[str], int], "smart") == "~typing.Callable[[str], int]"
+ assert stringify_annotation(t.Callable[[str], int], 'fully-qualified-except-typing') == "Callable[[str], int]"
+ assert stringify_annotation(t.Callable[[str], int], "fully-qualified") == "typing.Callable[[str], int]"
+ assert stringify_annotation(t.Callable[[str], int], "smart") == "~typing.Callable[[str], int]"
- assert stringify_annotation(Callable[..., int], 'fully-qualified-except-typing') == "Callable[[...], int]"
- assert stringify_annotation(Callable[..., int], "fully-qualified") == "typing.Callable[[...], int]"
- assert stringify_annotation(Callable[..., int], "smart") == "~typing.Callable[[...], int]"
+ assert stringify_annotation(t.Callable[..., int], 'fully-qualified-except-typing') == "Callable[[...], int]"
+ assert stringify_annotation(t.Callable[..., int], "fully-qualified") == "typing.Callable[[...], int]"
+ assert stringify_annotation(t.Callable[..., int], "smart") == "~typing.Callable[[...], int]"
+
+ assert stringify_annotation(abc.Callable, 'fully-qualified-except-typing') == "collections.abc.Callable"
+ assert stringify_annotation(abc.Callable, "fully-qualified") == "collections.abc.Callable"
+ assert stringify_annotation(abc.Callable, "smart") == "~collections.abc.Callable"
+
+ assert stringify_annotation(abc.Callable[[str], int], 'fully-qualified-except-typing') == "collections.abc.Callable[[str], int]"
+ assert stringify_annotation(abc.Callable[[str], int], "fully-qualified") == "collections.abc.Callable[[str], int]"
+ assert stringify_annotation(abc.Callable[[str], int], "smart") == "~collections.abc.Callable[[str], int]"
+
+ assert stringify_annotation(abc.Callable[..., int], 'fully-qualified-except-typing') == "collections.abc.Callable[[...], int]"
+ assert stringify_annotation(abc.Callable[..., int], "fully-qualified") == "collections.abc.Callable[[...], int]"
+ assert stringify_annotation(abc.Callable[..., int], "smart") == "~collections.abc.Callable[[...], int]"
def test_stringify_type_hints_Union():
@@ -413,9 +615,12 @@ def test_stringify_type_hints_Union():
assert stringify_annotation(Optional[int], "fully-qualified") == "int | None"
assert stringify_annotation(Optional[int], "smart") == "int | None"
- assert stringify_annotation(Union[str, None], 'fully-qualified-except-typing') == "str | None"
- assert stringify_annotation(Union[str, None], "fully-qualified") == "str | None"
- assert stringify_annotation(Union[str, None], "smart") == "str | None"
+ assert stringify_annotation(Union[int, None], 'fully-qualified-except-typing') == "int | None"
+ assert stringify_annotation(Union[None, int], 'fully-qualified-except-typing') == "None | int"
+ assert stringify_annotation(Union[int, None], "fully-qualified") == "int | None"
+ assert stringify_annotation(Union[None, int], "fully-qualified") == "None | int"
+ assert stringify_annotation(Union[int, None], "smart") == "int | None"
+ assert stringify_annotation(Union[None, int], "smart") == "None | int"
assert stringify_annotation(Union[int, str], 'fully-qualified-except-typing') == "int | str"
assert stringify_annotation(Union[int, str], "fully-qualified") == "int | str"
@@ -426,11 +631,11 @@ def test_stringify_type_hints_Union():
assert stringify_annotation(Union[int, Integral], "smart") == "int | ~numbers.Integral"
assert (stringify_annotation(Union[MyClass1, MyClass2], 'fully-qualified-except-typing') ==
- "tests.test_util_typing.MyClass1 | tests.test_util_typing.<MyClass2>")
+ "tests.test_util.test_util_typing.MyClass1 | tests.test_util.test_util_typing.<MyClass2>")
assert (stringify_annotation(Union[MyClass1, MyClass2], "fully-qualified") ==
- "tests.test_util_typing.MyClass1 | tests.test_util_typing.<MyClass2>")
+ "tests.test_util.test_util_typing.MyClass1 | tests.test_util.test_util_typing.<MyClass2>")
assert (stringify_annotation(Union[MyClass1, MyClass2], "smart") ==
- "~tests.test_util_typing.MyClass1 | ~tests.test_util_typing.<MyClass2>")
+ "~tests.test_util.test_util_typing.MyClass1 | ~tests.test_util.test_util_typing.<MyClass2>")
def test_stringify_type_hints_typevars():
@@ -438,35 +643,35 @@ def test_stringify_type_hints_typevars():
T_co = TypeVar('T_co', covariant=True)
T_contra = TypeVar('T_contra', contravariant=True)
- assert stringify_annotation(T, 'fully-qualified-except-typing') == "tests.test_util_typing.T"
- assert stringify_annotation(T, "smart") == "~tests.test_util_typing.T"
+ assert stringify_annotation(T, 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.T"
+ assert stringify_annotation(T, "smart") == "~tests.test_util.test_util_typing.T"
- assert stringify_annotation(T_co, 'fully-qualified-except-typing') == "tests.test_util_typing.T_co"
- assert stringify_annotation(T_co, "smart") == "~tests.test_util_typing.T_co"
+ assert stringify_annotation(T_co, 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.T_co"
+ assert stringify_annotation(T_co, "smart") == "~tests.test_util.test_util_typing.T_co"
- assert stringify_annotation(T_contra, 'fully-qualified-except-typing') == "tests.test_util_typing.T_contra"
- assert stringify_annotation(T_contra, "smart") == "~tests.test_util_typing.T_contra"
+ assert stringify_annotation(T_contra, 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.T_contra"
+ assert stringify_annotation(T_contra, "smart") == "~tests.test_util.test_util_typing.T_contra"
- assert stringify_annotation(List[T], 'fully-qualified-except-typing') == "List[tests.test_util_typing.T]"
- assert stringify_annotation(List[T], "smart") == "~typing.List[~tests.test_util_typing.T]"
+ assert stringify_annotation(List[T], 'fully-qualified-except-typing') == "List[tests.test_util.test_util_typing.T]"
+ assert stringify_annotation(List[T], "smart") == "~typing.List[~tests.test_util.test_util_typing.T]"
- assert stringify_annotation(list[T], 'fully-qualified-except-typing') == "list[tests.test_util_typing.T]"
- assert stringify_annotation(list[T], "smart") == "list[~tests.test_util_typing.T]"
+ assert stringify_annotation(list[T], 'fully-qualified-except-typing') == "list[tests.test_util.test_util_typing.T]"
+ assert stringify_annotation(list[T], "smart") == "list[~tests.test_util.test_util_typing.T]"
if sys.version_info[:2] >= (3, 10):
- assert stringify_annotation(MyInt, 'fully-qualified-except-typing') == "tests.test_util_typing.MyInt"
- assert stringify_annotation(MyInt, "smart") == "~tests.test_util_typing.MyInt"
+ assert stringify_annotation(MyInt, 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.MyInt"
+ assert stringify_annotation(MyInt, "smart") == "~tests.test_util.test_util_typing.MyInt"
else:
assert stringify_annotation(MyInt, 'fully-qualified-except-typing') == "MyInt"
assert stringify_annotation(MyInt, "smart") == "MyInt"
def test_stringify_type_hints_custom_class():
- assert stringify_annotation(MyClass1, 'fully-qualified-except-typing') == "tests.test_util_typing.MyClass1"
- assert stringify_annotation(MyClass1, "smart") == "~tests.test_util_typing.MyClass1"
+ assert stringify_annotation(MyClass1, 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.MyClass1"
+ assert stringify_annotation(MyClass1, "smart") == "~tests.test_util.test_util_typing.MyClass1"
- assert stringify_annotation(MyClass2, 'fully-qualified-except-typing') == "tests.test_util_typing.<MyClass2>"
- assert stringify_annotation(MyClass2, "smart") == "~tests.test_util_typing.<MyClass2>"
+ assert stringify_annotation(MyClass2, 'fully-qualified-except-typing') == "tests.test_util.test_util_typing.<MyClass2>"
+ assert stringify_annotation(MyClass2, "smart") == "~tests.test_util.test_util_typing.<MyClass2>"
def test_stringify_type_hints_alias():
@@ -481,13 +686,12 @@ def test_stringify_type_hints_alias():
def test_stringify_type_Literal():
- from typing import Literal # type: ignore[attr-defined]
assert stringify_annotation(Literal[1, "2", "\r"], 'fully-qualified-except-typing') == "Literal[1, '2', '\\r']"
assert stringify_annotation(Literal[1, "2", "\r"], "fully-qualified") == "typing.Literal[1, '2', '\\r']"
assert stringify_annotation(Literal[1, "2", "\r"], "smart") == "~typing.Literal[1, '2', '\\r']"
- assert stringify_annotation(Literal[MyEnum.a], 'fully-qualified-except-typing') == 'Literal[tests.test_util_typing.MyEnum.a]'
- assert stringify_annotation(Literal[MyEnum.a], 'fully-qualified') == 'typing.Literal[tests.test_util_typing.MyEnum.a]'
+ assert stringify_annotation(Literal[MyEnum.a], 'fully-qualified-except-typing') == 'Literal[tests.test_util.test_util_typing.MyEnum.a]'
+ assert stringify_annotation(Literal[MyEnum.a], 'fully-qualified') == 'typing.Literal[tests.test_util.test_util_typing.MyEnum.a]'
assert stringify_annotation(Literal[MyEnum.a], 'smart') == '~typing.Literal[MyEnum.a]'
@@ -510,8 +714,8 @@ def test_stringify_type_union_operator():
def test_stringify_broken_type_hints():
- assert stringify_annotation(BrokenType, 'fully-qualified-except-typing') == 'tests.test_util_typing.BrokenType'
- assert stringify_annotation(BrokenType, "smart") == '~tests.test_util_typing.BrokenType'
+ assert stringify_annotation(BrokenType, 'fully-qualified-except-typing') == 'tests.test_util.test_util_typing.BrokenType'
+ assert stringify_annotation(BrokenType, "smart") == '~tests.test_util.test_util_typing.BrokenType'
def test_stringify_mock():
@@ -523,8 +727,6 @@ def test_stringify_mock():
def test_stringify_type_ForwardRef():
- from typing import ForwardRef # type: ignore[attr-defined]
-
assert stringify_annotation(ForwardRef("MyInt")) == "MyInt"
assert stringify_annotation(ForwardRef("MyInt"), 'smart') == "MyInt"
@@ -534,3 +736,21 @@ def test_stringify_type_ForwardRef():
assert stringify_annotation(Tuple[dict[ForwardRef("MyInt"), str], list[List[int]]]) == "Tuple[dict[MyInt, str], list[List[int]]]" # type: ignore[attr-defined]
assert stringify_annotation(Tuple[dict[ForwardRef("MyInt"), str], list[List[int]]], 'fully-qualified-except-typing') == "Tuple[dict[MyInt, str], list[List[int]]]" # type: ignore[attr-defined]
assert stringify_annotation(Tuple[dict[ForwardRef("MyInt"), str], list[List[int]]], 'smart') == "~typing.Tuple[dict[MyInt, str], list[~typing.List[int]]]" # type: ignore[attr-defined]
+
+
+@pytest.mark.xfail(sys.version_info[:2] <= (3, 9), reason='ParamSpec not supported in Python 3.9.')
+def test_stringify_type_hints_paramspec():
+ from typing import ParamSpec
+ P = ParamSpec('P')
+
+ assert stringify_annotation(P, 'fully-qualified') == "~P"
+ assert stringify_annotation(P, 'fully-qualified-except-typing') == "~P"
+ assert stringify_annotation(P, "smart") == "~P"
+
+ assert stringify_annotation(P.args, 'fully-qualified') == "typing.~P"
+ assert stringify_annotation(P.args, 'fully-qualified-except-typing') == "~P"
+ assert stringify_annotation(P.args, "smart") == "~typing.~P"
+
+ assert stringify_annotation(P.kwargs, 'fully-qualified') == "typing.~P"
+ assert stringify_annotation(P.kwargs, 'fully-qualified-except-typing') == "~P"
+ assert stringify_annotation(P.kwargs, "smart") == "~typing.~P"
diff --git a/tests/typing_test_data.py b/tests/test_util/typing_test_data.py
index 8a7ebc4..0588836 100644
--- a/tests/typing_test_data.py
+++ b/tests/test_util/typing_test_data.py
@@ -1,6 +1,6 @@
from inspect import Signature
from numbers import Integral
-from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union
+from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, TypeVar, Union
def f0(x: int, y: Integral) -> None:
@@ -39,7 +39,7 @@ def f6(x: int, *args, y: str, z: str) -> None:
pass
-def f7(x: int = None, y: dict = {}) -> None: # NoQA: B006
+def f7(x: int = None, y: dict = {}) -> None: # NoQA: B006,RUF013
pass
@@ -77,7 +77,7 @@ def f14() -> Any:
pass
-def f15(x: "Unknown", y: "int") -> Any: # noqa: F821 # type: ignore[attr-defined]
+def f15(x: "Unknown", y: "int") -> Any: # NoQA: F821 # type: ignore[attr-defined]
pass
@@ -121,6 +121,10 @@ def f25(a, b, /):
pass
+def f26(x: Literal[1, 2, 3] = 1, y: Union[Literal["a"], Literal["b"]] = "a") -> None:
+ pass
+
+
class Node:
def __init__(self, parent: Optional['Node']) -> None:
pass