Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Added
- Allow providing a function with return type a class in ``class_path``
(`lightning#13613
<https://github.com/Lightning-AI/pytorch-lightning/discussions/13613>`__)
- Automatic ``--print_shtab`` option when ``shtab`` is installed, providing
completions for many type hints without the need to modify code (`#528
<https://github.com/omni-us/jsonargparse/pull/528>`__).

Fixed
^^^^^
Expand Down
100 changes: 83 additions & 17 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2544,17 +2544,41 @@ function to provide an instance of the parser to :class:`.ActionParser`.
Tab completion
==============

Tab completion is available for jsonargparse parsers by using the `argcomplete
<https://pypi.org/project/argcomplete/>`__ package. There is no need to
implement completer functions or to call :func:`argcomplete.autocomplete` since
this is done automatically by :py:meth:`.ArgumentParser.parse_args`. The only
requirement to enable tab completion is to install argcomplete either directly
or by installing jsonargparse with the ``argcomplete`` extras require as
explained in section :ref:`installation`. Then the tab completion can be enabled
`globally <https://kislyuk.github.io/argcomplete/#global-completion>`__ for all
argcomplete compatible tools or for each `individual
<https://kislyuk.github.io/argcomplete/#synopsis>`__ tool. A simple
``example.py`` tool would be:
Tab completion is available for jsonargparse parsers by using either the `shtab
<https://pypi.org/project/shtab/>`__ package or the `argcomplete
<https://pypi.org/project/argcomplete/>`__ package.

shtab
-----

For ``shtab`` to work, there is no need to set ``complete``/``choices`` to the
parser actions, and no need to call :func:`shtab.add_argument_to`. This is done
automatically by :py:meth:`.ArgumentParser.parse_args`. The only requirement is
to install shtab either directly or by installing jsonargparse with the
``shtab`` extras require as explained in section :ref:`installation`.

.. note::

Automatic shtab support is currently experimental and subject to change.

Once ``shtab`` is installed, parsers will automatically have the
``--print_shtab`` option that can be used to print the completion script for the
supported shells. For example in linux to enable bash completions for all users,
as root it would be used as:

.. code-block:: bash

# example.py --print_shtab=bash > /etc/bash_completion.d/example

Without installing, completion scripts can be tested by sourcing or evaluating
them, for instance:

.. code-block:: bash

$ eval "$(example.py --print_shtab=bash)"

The scripts work both to complete when there are choices, but also gives
instructions to the user for guidance. Take for example the parser:

.. testsetup:: tab_completion

Expand All @@ -2572,18 +2596,60 @@ argcomplete compatible tools or for each `individual

parser.parse_args()

Then in a bash shell you can add the executable bit to the script, activate tab
completion and use it as follows:
The completions print the type of the argument, how many options are matched,
and afterward the list of choices matched up to that point. If only one option
matches, then the value is completed without printing guidance. For example:

.. code-block:: bash

$ example.py --bool <TAB><TAB>
Expected type: Optional[bool]; 3/3 matched choices
true false null
$ example.py --bool f<TAB>
$ example.py --bool false

For the case of subclass types, the import class paths for known subclasses are
completed, both for the switch to select the class and for the corresponding
``--*.help`` switch. The ``init_args`` for known subclasses are also completed,
giving as guidance which of the subclasses accepts it. An example would be:

.. code-block:: bash

$ example.py --cls <TAB><TAB>
Expected type: BaseClass; 3/3 matched choices
some.module.BaseClass other.module.SubclassA
other.module.SubclassB
$ example.py --cls other.module.SubclassA --cls.<TAB><TAB>
--cls.param1 --cls.param2
$ example.py --cls other.module.SubclassA --cls.param2 <TAB><TAB>
Expected type: int; Accepted by subclasses: SubclassA

argcomplete
-----------

For ``argcompete`` to work, there is no need to implement completer functions or
to call :func:`argcomplete.autocomplete` since this is done automatically by
:py:meth:`.ArgumentParser.parse_args`. The only requirement to enable tab
completion is to install argcomplete either directly or by installing
jsonargparse with the ``argcomplete`` extras require as explained in section
:ref:`installation`.

The tab completion can be enabled `globally
<https://kislyuk.github.io/argcomplete/#global-completion>`__ for all
argcomplete compatible tools or for each `individual
<https://kislyuk.github.io/argcomplete/#synopsis>`__ tool.

Using the same ``bool`` example as shown above, activate tab completion and use
it as follows:

.. code-block:: bash

$ chmod +x example.py
$ eval "$(register-python-argcomplete example.py)"

$ ./example.py --bool <TAB><TAB>
$ example.py --bool <TAB><TAB>
false null true
$ ./example.py --bool f<TAB>
$ ./example.py --bool false
$ example.py --bool f<TAB>
$ example.py --bool false


.. _logging:
Expand Down
10 changes: 8 additions & 2 deletions jsonargparse/_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from ._common import Action, get_class_instantiator, is_subclass, parser_context
from ._loaders_dumpers import get_loader_exceptions, load_value
from ._namespace import Namespace, NSKeyError, split_key, split_key_root
from ._optionals import FilesCompleterMethod, get_config_read_mode
from ._optionals import get_config_read_mode
from ._type_checking import ArgumentParser
from ._util import (
NoneType,
Expand Down Expand Up @@ -145,7 +145,7 @@ def filter_default_actions(actions):
return {k: a for k, a in actions.items() if not isinstance(a, default)}


class ActionConfigFile(Action, FilesCompleterMethod):
class ActionConfigFile(Action):

Check failure

Code scanning / CodeQL

Missing call to `__init__` during object initialization

Class ActionConfigFile may not be initialized properly as [method Action.__init__](1) is not called from its [__init__ method](2).
"""Action to indicate that an argument is a configuration file or a configuration string."""

def __init__(self, **kwargs):
Expand Down Expand Up @@ -208,6 +208,12 @@ def apply_config(parser, cfg, dest, value) -> None:
cfg[dest] = []
cfg[dest].append(cfg_path)

def completer(self, prefix, **kwargs):
from ._completions import get_files_completer

files_completer = get_files_completer()
return sorted(files_completer(prefix, **kwargs))


previous_config: ContextVar = ContextVar("previous_config", default=None)

Expand Down
Loading