New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial support for argcomplete for KVArgParseConfigLoader #811
Conversation
Very cool! I think it makes sense to add |
After enabling argcomplete shell completion for a traitlets.Application based script (e.g. via activate-global-python-argcomplete), this commit sets up tab auto-completion for command-line flags and aliases. Argcomplete completers can be added for a trait by using an "argcompleter" metadata tag, which should have a function that takes keyword arguments (passed down from argcomplete) and returns a list of string completions. Completers are also set up for Bool ("true", "false", "1", "0") & Enum. This commit does *not* add general support for arbitrary class traits of the form --Class.trait=xyz, as these are currently not added to the ArgumentParser instance, but rather directly parsed. It is probably possible to add this support for the classes in Application.classes. Issue: ipython#539 Example: ~/dev/traitlets$ python examples/myapp.py --[TAB] --debug --disable --enable --enabled --help --i --j --log_level --mode --name --running ~/dev/traitlets$ python examples/myapp.py --running [TAB] 0 1 false true ~/dev/traitlets$ python examples/myapp.py --running true --[TAB] --debug --disable --enable --enabled --help --i --j --log_level --mode --name --running ~/dev/traitlets$ python examples/myapp.py --running true --mode o[TAB] off on other
This custom finder mainly adds 2 functionalities: 1. When completing options, it will add --Class. to the list of completions, for each class in Application.classes that could complete the current option. 2. If it detects that we are currently trying to complete an option related to --Class., it will add the corresponding config traits of Class to the ArgumentParser instance, so that the traits' completers can be used. (This is currently done in a bit of a hacky manner.) Note that we are avoiding adding all config traits of all classes to the ArgumentParser, which would be easier but would add more runtime overhead and would also make completions appear more spammy. It also does not support nested class options like --Class1.Class2.trait. Example: ~/dev/traitlets$ examples/myapp.py --mode on --[TAB] --Application. --Foo. --debug --enable --help --j --mode --running --Bar. --MyApp. --disable --enabled --i --log_level --name ~/dev/traitlets$ examples/myapp.py --mode on --F[TAB] ~/dev/traitlets$ examples/myapp.py --mode on --Foo.[TAB] --Foo.i --Foo.j --Foo.mode --Foo.name ~/dev/traitlets$ examples/myapp.py --mode on --Foo.m[TAB] ~/dev/traitlets$ examples/myapp.py --mode on --Foo.mode [TAB] ~/dev/traitlets$ examples/myapp.py --mode on --Foo.mode o[TAB] off on other
for more information, see https://pre-commit.ci
Added an example application for testing argcomplete, under examples/argcomplete_app.py, with several examples of completions provided in the docstring. Fixed using completers --Class.trait arg1 arg2 [TAB] for config traits with nargs/multiplicity="+". Note that currently traitlets does not support multiplicity even though it is used in the code; refer to issue GH#690 for discussion. Add more comments since we're using argcomplete internals, and add argcomplete as dependency for coverage/mypy tests. Some other minor fixes such as minor mypy annotations fixes. Another example: add # PYTHON_ARGCOMPLETE_OK to bin/ipython, and tab-complete away: $ ipython --[TAB] --Application. --help --BaseFormatter. --i --BaseIPythonApplication. --ignore-cwd --Completer. --init --HistoryAccessor. --ipython-dir --HistoryManager. --log-level --IPCompleter. --logappend --InteractiveShell. --logfile --InteractiveShellApp. --m ... $ ipython --gui=[TAB] asyncio gtk gtk3 pyglet qt4 tk glut gtk2 osx qt qt5 wx To-do still: support subcommands. This may still take some work as traitlets does subcommand parsing independently of argparse.
523ec41
to
c0c6aab
Compare
for more information, see https://pre-commit.ci
Turns out I managed to hack around a bit and directly add all of the config traits as I've added an example script Another example: add $ ipython --
--Application. --StoreMagics. --config --m --nosep
--BaseFormatter. --TerminalIPythonApp. --confirm-exit --matplotlib --pdb
--BaseIPythonApplication. --TerminalInteractiveShell. --debug --no-autoedit-syntax --pprint
--Completer. --autocall --ext --no-autoindent --profile
--HistoryAccessor. --autoedit-syntax --gui --no-automagic --profile-dir
--HistoryManager. --autoindent --help --no-banner --pylab
--IPCompleter. --automagic --i --no-color-info --quick
--InteractiveShell. --banner --ignore-cwd --no-confirm-exit --quiet
--InteractiveShellApp. --c --init --no-ignore-cwd --simple-prompt
--LoggingMagics. --cache-size --ipython-dir --no-pdb --term-title
--PlainTextFormatter. --classic --log-level --no-pprint
--ProfileDir. --color-info --logappend --no-simple-prompt
--ScriptMagics. --colors --logfile --no-term-title
$ ipython --gui=
asyncio glut gtk gtk2 gtk3 osx pyglet qt qt4 qt5 tk wx
$ ipython --gui g
glut gtk gtk2 gtk3
$ ipython --InteractiveShellApp.
--InteractiveShellApp.code_to_run --InteractiveShellApp.hide_initial_ns
--InteractiveShellApp.exec_PYTHONSTARTUP --InteractiveShellApp.ignore_cwd
--InteractiveShellApp.exec_files --InteractiveShellApp.matplotlib
--InteractiveShellApp.exec_lines --InteractiveShellApp.module_to_run
--InteractiveShellApp.extensions --InteractiveShellApp.pylab
--InteractiveShellApp.extra_extension --InteractiveShellApp.pylab_import_all
--InteractiveShellApp.file_to_run --InteractiveShellApp.reraise_ipython_extension_failures
--InteractiveShellApp.gui Sub-commands might be tricky since IIRC they're handled by |
Looking good so far! |
Unfortunately subcommand completion might not be all that viable, so I'll leave it for a separate PR that might not happen. I'll document the issues I encountered so there's a record:
~$ jupyter lab -[TAB]
--Application. --core-mode
--ConnectionFileMixin. --debug
--ContentsManager. --dev-mode
--FileContentsManager. --expose-app-in-browser
--FileManagerMixin. --gateway-url
--GatewayClient. --generate-config
--GatewayKernelManager. --help
--GatewayKernelSpecManager. --ip
--JupyterApp. --keyfile
--KernelManager. --log-level
--KernelSpecManager. --no-browser
--LabApp. --no-mathjax
# ... In summary, subcommand handling is not very viable without some planning, and for |
argcomplete's strategy is to call the python script with no arguments e.g. len(sys.argv) == 1, run until the ArgumentParser is constructed and determine what completions are available. On the other hand, traitlet's subcommand-handling strategy is to check sys.argv[1] and see if it matches a subcommand, and if so then dynamically load the subcommand app and initialize it with sys.argv[1:]. Write a couple of helper functions to reconcile this by: 1. retrieving tokens from $COMP_LINES, etc, and setting it to argv 2. if traitlets descends into a subcommand, increment index passed via env var to argcomplete to mark where command starts There's quite a few caveats to this approach. For example, it only is evaluated currently when `App.initialize()` is passed with `argv=None` (the default). If `argv` is explicitly passed, then the `argcomplete`-specific handling is skipped currently. More details in: ipython#811 (comment) Some additional minor cleanup with respect to subcommands typing, examples, and documentation.
for more information, see https://pre-commit.ci
Sounds good, thanks again @azjps! |
Add docs for argcomplete, and various fixes and improvements to other docs such as examples for flags/aliases/subcommands. Update docs for building docs. Also fix a bug when argcomplete is not installed
* Fix ruff removing argcomplete import check * Add example scripts corresponding to examples in docs * Add new sections to docs to further explain Application configuration, methods, and philosophy. Closes: ipython#707, ipython#708, ipython#709, ipython#712
for more information, see https://pre-commit.ci
Borrow argcomplete's unit test fixtures to set up unit testing for completion of Application commands. Test a few cases of custom completers; caught a bug with Enum.argcompleter()
Fix some errors from hatch run typing:test and some other minor formatting / CI fixes.
for more information, see https://pre-commit.ci
argcomplete >= 2.0 was very recently released, and changes internal output_stream to text mode instead of bytes; fix unit test for this and some other minor fixes.
for more information, see https://pre-commit.ci
These tests run for me locally, but currently CI raises OSError: Bad file descriptor. Something with temp files perhaps?
@blink1073 I think I've gotten into a style battle with
While stashing my changes related to subcommand handling, I decided instead to just go ahead with it, its probably very fragile but it works for the simple examples at least. I added some stuff to the I've gotten some unit tests working for Besides these points, I'm satisfied with the state of these commits, probably the next thing I'll look into is opening PRs against the major traitlets-based projects (ipython, jupyter, etc) to actually enable |
#814 fixes the linting issue |
The nbconvert failure is unrelated. I'll take a look at the CI skipping, I'd rather not merge without that in place. It might be a bit before I get to it, I'm recovering from an illness. |
for more information, see https://pre-commit.ci
No worries. I re-enabled the tests and they seem to deterministically fail here, I've been trying to bisect towards the issue in #815 but haven't gotten a simpler example to fail yet 😞 |
TemporaryFile was causing some OSError(9) Bad file descriptor on flush, not sure why but using StringIO seems to work perfectly fine instead.
Only use StringIO instead of BytesIO for argcomplete >= 2.0. Stop relying on SystemExit which might be messing with pytest, instead raise a CustomError. Other minor doc fixes.
still trying to get pytest to not crash at the end ..
Changing Edit: actually was able to narrow it down as per #815 --
|
argcomplete==2.0.0 always calls fdopen(9, "w") to open a debug stream, however this could conflict with file descriptors used by pytest and lead to obscure errors. Since we are not looking at debug stream in these tests, just mock this fdopen call out.
That seems reasonable. Great work! |
except ImportError: | ||
# This module and its utility methods are written to not crash even | ||
# if argcomplete is not installed. | ||
class StubModule: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to be a bit defensive here and allow this module to be importable even if argcomplete
is not installed, maybe there's a more common pattern that can be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice approach, I'd say leave it.
Polish up a bit more docs about Application and commit corresponding examples.
config.py files have get_config, load_subconfig injected so have to get them ignored from pytest collection and mypy checking. Also minor, added some pathlib support to load_config_file().
Also link to some examples and add initial CHANGELOG entry.
I'll manually update the release notes during the release to add your language for the documentation, inlining here:
|
If you beat me to it, please remove the changelog and update the branch, otherwise I'll do it tomorrow and cut a minor release. Thanks again! |
Set up shell command-line tab-completion using argcomplete and ipython/traitlets#811 argcomplete supports following setuptools console_scripts to the corresponding package's __main__.py to look for a PYTHON_ARGCOMPLETE_OK marker.
Set up shell command-line tab-completion using argcomplete and ipython/traitlets#811. For subcommand handling, this will use list_subcommands() to try to complete the first argument if it doesn't start with a "-", and when a subcommand is detected, argcomplete will increment the start of the command-line handling past the subcommand and drop into the relevant traitlets.Application's argcomplete handling. All of the major subcommands like "jupyter lab" directly use traitlets.Application.launch_instance so this works pretty seamlessly. However, completing commands that start with the hyphenated commands, e.g. "jupyter-lab", would still require the addition of the PYTHON_ARGCOMPLETE_OK marker in the respective projects' main scripts. Note that argcomplete supports following setuptools console_scripts to the corresponding main script to look for the PYTHON_ARGCOMPLETE_OK marker.
After enabling
argcomplete
shell completion for atraitlets.Application
based script (e.g. viaactivate-global-python-argcomplete
), this commit sets up tab auto-completion for command-line flags and aliases. (Note thatargcomplete
would be completely optional and not a dependency.)Argcomplete completers can be added for a trait by using an
"argcompleter"
metadata tag, which should be a function that takes keyword arguments (passed down fromargcomplete
) and returns a list of string completions. Completers are set up forBool
("true", "false", "1", "0"
) &Enum
.This commit does not yet add general support for arbitrary class traits of the form
--Class.trait=xyz
, as these are currently not added to theArgumentParser
instance, but rather directly parsed fromsys.argv
. It is probably possible to add this support for the classes inApplication.classes
. IMHO, the most user-friendly way of doing this would be to allow--<TAB>
to also show the completions--Class1. --Class2.
and so forth, enumerating the Configurable classes but not spamming all of the available traits. Only once the class has been completed out e.g.--Class1.<TAB>
would the traits of the class become completable. While this should be possible withargcomplete
I haven't looked closely enough to figure out what the easiest way to accomplish this would be.Issue: #539
Example:
If this idea seems good to pursue, I can add documentation, update dev dependencies, and so forth.