Skip to content

Commit

Permalink
Initial support for argcomplete for KVArgParseConfigLoader
Browse files Browse the repository at this point in the history
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: #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
  • Loading branch information
azjps committed Dec 8, 2022
1 parent 898a1bf commit ae9aa95
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 9 deletions.
16 changes: 9 additions & 7 deletions examples/myapp.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
"""A simple example of how to use traitlets.config.application.Application.
This should serve as a simple example that shows how the traitlets config
Expand Down Expand Up @@ -29,18 +31,17 @@
When the config attribute of an Application is updated, it will fire all of
the trait's events for all of the config=True attributes.
"""

from traitlets import Bool, Dict, Int, List, Unicode
from traitlets import Bool, Dict, Enum, Int, List, Unicode
from traitlets.config.application import Application
from traitlets.config.configurable import Configurable


class Foo(Configurable):
"""A class that has configurable, typed attributes."""

i = Int(0, help="The integer i.").tag(config=True)
j = Int(1, help="The integer j.").tag(config=True)
name = Unicode("Brian", help="First name.").tag(config=True, shortname="B")
mode = Enum(values=["on", "off", "other"], default_value="on").tag(config=True)


class Bar(Configurable):
Expand All @@ -60,6 +61,7 @@ class MyApp(Application):
i="Foo.i",
j="Foo.j",
name="Foo.name",
mode="Foo.mode",
running="MyApp.running",
enabled="Bar.enabled",
log_level="MyApp.log_level",
Expand Down Expand Up @@ -93,10 +95,10 @@ def start(self):
print("app.config:")
print(self.config)
print("try running with --help-all to see all available flags")
self.log.info("Info Mesage")
self.log.debug("DebugMessage")
self.log.critical("Warning")
self.log.critical("Critical mesage")
self.log.debug("Debug Message")
self.log.info("Info Message")
self.log.warning("Warning Message")
self.log.critical("Critical Message")


def main():
Expand Down
22 changes: 20 additions & 2 deletions traitlets/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import argparse
import copy
import functools
import json
import os
import re
Expand Down Expand Up @@ -1015,15 +1016,22 @@ def _add_arguments(self, aliases, flags, classes):
"dest": traitname.replace(".", _DOT_REPLACEMENT),
"metavar": traitname,
}
argcompleter = None
if traitname in argparse_traits:
argparse_kwds.update(argparse_traits[traitname][1])
trait, kwds = argparse_traits[traitname]
argparse_kwds.update(kwds)
if "action" in argparse_kwds and traitname in alias_flags:
# flag sets 'action', so can't have flag & alias with custom action
# on the same name
raise ArgumentError(
"The alias `%s` for the 'append' sequence "
"config-trait `%s` cannot be also a flag!'" % (key, traitname)
)
# For argcomplete, check if any either an argcompleter metadata tag or method
# is available. If so, it should be a callable which takes the command-line key
# string as an argument and other kwargs passed by argcomplete,
# and returns the a list of string completions.
argcompleter = trait.metadata.get("argcompleter") or getattr(trait, "argcompleter", None)
if traitname in alias_flags:
# alias and flag.
# when called with 0 args: flag
Expand All @@ -1033,7 +1041,11 @@ def _add_arguments(self, aliases, flags, classes):
argparse_kwds["flag"] = alias_flags[traitname]
argparse_kwds["alias"] = traitname
keys = ("-" + key, "--" + key) if len(key) == 1 else ("--" + key,)
paa(*keys, **argparse_kwds)
action = paa(*keys, **argparse_kwds)
if argcompleter is not None:
# argcomplete's completers are callables returning list of completion strings
action.completer = functools.partial(argcompleter, key=key)
self.argcomplete()

def _convert_to_config(self):
"""self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
Expand Down Expand Up @@ -1083,6 +1095,12 @@ def _handle_unrecognized_alias(self, arg: str):
"""
self.log.warning("Unrecognized alias: '%s', it will have no effect.", arg)

def argcomplete(self):
try:
import argcomplete
argcomplete.autocomplete(self.parser)
except ImportError:
pass

class KeyValueConfigLoader(KVArgParseConfigLoader):
"""Deprecated in traitlets 5.0
Expand Down
9 changes: 9 additions & 0 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2487,6 +2487,12 @@ def from_string(self, s):
else:
raise ValueError("%r is not 1, 0, true, or false")

def argcompleter(self, **kwargs):
"""Completion hints for argcomplete"""
completions = ["true", "1", "false", "0"]
if self.allow_none:
completions.append("None")
return completions

class CBool(Bool):
"""A casting version of the boolean trait."""
Expand Down Expand Up @@ -2538,6 +2544,9 @@ def from_string(self, s):
except TraitError:
return _safe_literal_eval(s)

def argcompleter(self, **kwargs):
"""Completion hints for argcomplete"""
return list(self.values)

class CaselessStrEnum(Enum):
"""An enum of strings where the case should be ignored."""
Expand Down

0 comments on commit ae9aa95

Please sign in to comment.