Skip to content

Commit

Permalink
Add an option for displaying short Literal types
Browse files Browse the repository at this point in the history
The new ``python_display_short_literal_types`` configuration option
for the ``py`` domain controls display of PEP 586 ``Literal`` types.
The 'short' format is inspired by PEP 604, using the bitwise OR operator
to distinguish the possible legal values for the argument.
  • Loading branch information
AA-Turner committed Apr 5, 2023
1 parent 609b2f2 commit 29b789b
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Features added
Patch by Jeremy Maitin-Shepard
* #11281: Support for :confval:`imgmath_latex` ``= 'tectonic'`` or
``= 'xelatex'``. Patch by Dimitar Dimitrov
* #11109, #9643: Add :confval:`python_display_short_literal_types` option for
condensed rendering of ``Literal`` types.

Bugs fixed
----------
Expand Down
30 changes: 30 additions & 0 deletions doc/usage/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2936,6 +2936,36 @@ Options for the C++ domain
Options for the Python domain
-----------------------------

.. confval:: python_display_short_literal_types

This value controls how :py:data:`~typing.Literal` types are displayed.
The setting is a boolean, default ``False``.

Examples
~~~~~~~~

The examples below use the following :rst:dir:`py:function` directive:

.. code:: reStructuredText
.. py:function:: serve_food(item: Literal["egg", "spam", "lobster thermidor"]) -> None
When ``False``, :py:data:`~typing.Literal` types display as per standard
Python syntax, i.e.:

.. code:: python
serve_food(item: Literal["egg", "spam", "lobster thermidor"]) -> None
When ``True``, :py:data:`~typing.Literal` types display with a short,
:PEP:`604`-inspired syntax, i.e.:

.. code:: python
serve_food(item: "egg" | "spam" | "lobster thermidor") -> None
.. versionadded:: 6.2

.. confval:: python_use_unqualified_type_names

If true, suppress the module name of the python reference if it can be
Expand Down
5 changes: 5 additions & 0 deletions sphinx/domains/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ def type_to_xref(target: str, env: BuildEnvironment | None = None,

def _parse_annotation(annotation: str, env: BuildEnvironment | None) -> list[Node]:
"""Parse type annotation."""
short_literals = env.config.python_display_short_literal_types

def unparse(node: ast.AST) -> list[Node]:
if isinstance(node, ast.Attribute):
return [nodes.Text(f"{unparse(node.value)[0]}.{node.attr}")]
Expand Down Expand Up @@ -182,6 +184,8 @@ def unparse(node: ast.AST) -> list[Node]:
if isinstance(node, ast.Subscript):
if getattr(node.value, 'id', '') in {'Optional', 'Union'}:
return _unparse_pep_604_annotation(node)
if short_literals and getattr(node.value, 'id', '') == 'Literal':
return _unparse_pep_604_annotation(node)
result = unparse(node.value)
result.append(addnodes.desc_sig_punctuation('', '['))
result.extend(unparse(node.slice))
Expand Down Expand Up @@ -1511,6 +1515,7 @@ def setup(app: Sphinx) -> dict[str, Any]:

app.add_domain(PythonDomain)
app.add_config_value('python_use_unqualified_type_names', False, 'env')
app.add_config_value('python_display_short_literal_types', False, 'env')
app.connect('object-description-transform', filter_meta_fields)
app.connect('missing-reference', builtin_resolver, priority=900)

Expand Down
68 changes: 68 additions & 0 deletions tests/test_domain_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -1470,3 +1470,71 @@ def test_module_content_line_number(app):
source, line = docutils.utils.get_source_line(xrefs[0])
assert 'index.rst' in source
assert line == 3


@pytest.mark.sphinx(freshenv=True, confoverrides={'python_display_short_literal_types': True})
def test_short_literal_types(app):
text = """\
.. py:function:: literal_ints(x: Literal[1, 2, 3] = 1) -> None
.. py:function:: literal_union(x: Union[Literal["a"], Literal["b"], Literal["c"]]) -> None
"""
doctree = restructuredtext.parse(app, text)
_a= 1
assert_node(doctree, (
addnodes.index,
[desc, (
[desc_signature, (
[desc_name, 'literal_ints'],
[desc_parameterlist, (
[desc_parameter, (
[desc_sig_name, 'x'],
[desc_sig_punctuation, ':'],
desc_sig_space,
[desc_sig_name, (
[desc_sig_literal_number, '1'],
desc_sig_space,
[desc_sig_punctuation, '|'],
desc_sig_space,
[desc_sig_literal_number, '2'],
desc_sig_space,
[desc_sig_punctuation, '|'],
desc_sig_space,
[desc_sig_literal_number, '3'],
)],
desc_sig_space,
[desc_sig_operator, '='],
desc_sig_space,
[nodes.inline, '1'],
)],
)],
[desc_returns, pending_xref, 'None'],
)],
[desc_content, ()],
)],
addnodes.index,
[desc, (
[desc_signature, (
[desc_name, 'literal_union'],
[desc_parameterlist, (
[desc_parameter, (
[desc_sig_name, 'x'],
[desc_sig_punctuation, ':'],
desc_sig_space,
[desc_sig_name, (
[desc_sig_literal_string, "'a'"],
desc_sig_space,
[desc_sig_punctuation, '|'],
desc_sig_space,
[desc_sig_literal_string, "'b'"],
desc_sig_space,
[desc_sig_punctuation, '|'],
desc_sig_space,
[desc_sig_literal_string, "'c'"],
)],
)],
)],
[desc_returns, pending_xref, 'None'],
)],
[desc_content, ()],
)],
))

0 comments on commit 29b789b

Please sign in to comment.