Traits CLI Documentation Release 0.1.0 Takafumi Arakaki March 22, 2013
CONTENTS 1 Links 3 2 Installation 5 3 Dependencies 7 4 Sample 9 5 CLI base class 11 6 Utility functions 19 7 Change log 21 7.1 v0.1.................................................... 21 Python Module Index 23 i
ii
Traits CLI is based on Enthought s Traits library. Some benefits: Automatically set type (int/float/) of command line argument. Help string generation. Deep value configuration: e.g., --dict[ a ][ b ][ c ]=1 is equivalent to obj.dict[ a ][ b ][ c ] = 1 in Python code. Nested class configuration: e.g., --sub.attr=val is equivalent to obj.sub.attr = val in Python code. Parameter file support (ini/conf, json, yaml, etc.). Load parameter from file then set attribute. CONTENTS 1
2 CONTENTS
CHAPTER ONE LINKS Documentaions (at Read the Docs) Source code repository (at GitHub) Issue tracker (at GitHub) PyPI Travis CI 3
4 Chapter 1. Links
CHAPTER TWO INSTALLATION pip install traitscli 5
6 Chapter 2. Installation
CHAPTER THREE DEPENDENCIES traits argparse (for Python < 2.7) 7
8 Chapter 3. Dependencies
CHAPTER FOUR SAMPLE Source code: from traitscli import TraitsCLIBase from traits.api import Bool, Float, Int, Str, Enum class SampleCLI(TraitsCLIBase): Sample CLI using traitscli. Example:: %(prog)s --yes %(prog)s --string something %(prog)s --choice x # => obj.yes = True # => obj.string = string # => raise error (x is not in {a, b, c}) # These variables are configurable by command line option yes = Bool(desc= yes flag for sample CLI, config=true) no = Bool(True, config=true) fnum = Float(config=True) inum = Int(config=True) string = Str(config=True) choice = Enum([ a, b, c ], config=true) # You can have "internal" attributes which cannot be set via CLI. not_configurable_from_cli = Bool() def do_run(self): names = self.class_trait_names(config=true) width = max(map(len, names)) for na in names: print "{0:{1}} : {2!r}".format(na, width, getattr(self, na)) if name == main : # Run command line interface SampleCLI.cli() Example run: 9
$ python sample.py --help usage: sample.py [-h] [--choice {a,b,c}] [--fnum FNUM] [--inum INUM] [--no] [--string STRING] [--yes] Sample CLI using traitscli. Example:: sample.py --yes sample.py --string something sample.py --choice x # => obj.yes = True # => obj.string = string # => raise error (x is not in {a, b, c}) optional arguments: -h, --help show this help message and exit --choice {a,b,c} (default: a) --fnum FNUM (default: 0.0) --inum INUM (default: 0) --no (default: True) --string STRING (default: ) --yes yes flag for sample CLI (default: False) $ python sample.py --yes --choice a string : no : True fnum : 0.0 choice : a inum : 0 yes : True $ python sample.py --inum invalid_argument usage: sample.py [-h] [--choice {a,b,c}] [--fnum FNUM] [--inum INUM] [--no] [--string STRING] [--yes] sample.py: error: argument --inum: invalid int value: invalid_argument 10 Chapter 4. Sample
CHAPTER FIVE CLI BASE CLASS class traitscli.traitsclibase(**kwds) CLI generator base class. Usage. You will need to define: 1.Parameters (traits). When it has config=true metadata, it is configurable via command line argument. See: Defining Traits: Initialization and Validation section in Traits user manual. 2.do_run() method. This method gets no argument (except self ). Do whatever this class needs to do based on its attributes. cli() function sets attributes based on command line options and then call do_run() method. Examples To make class attribute configurable from command line options, set metadata config=true: int = Int(config=True) >>> obj = SampleCLI.cli([ --int, 1 ]) >>> obj.int 1 For dict and list type attribute, you can modify it using subscript access: dict = Dict(config=True) >>> obj = SampleCLI.cli([ --dict["k"], 1 ]) >>> obj.dict[ k ] 1 You don t need to quote string if dict/list attribute set its value trait to str-like trait: dict = Dict(value_trait=Str, config=true) >>> obj = SampleCLI.cli([ --dict["k"], unquoted string ]) >>> obj.dict[ k ] unquoted string >>> obj = SampleCLI.cli([ --dict["k"]=unquoted string ]) >>> obj.dict[ k ] unquoted string Attributes of nested class can be set using dot access: 11
>>> class SubObject(TraitsCLIBase): int = Int(config=True) # Here, args=() is required to initialize sub. sub = Instance(SubObject, args=(), config=true) >>> obj = SampleCLI.cli([ --sub.int, 1 ]) >>> obj.sub.int 1 Metadata for traits config [bool] If this metadata of an attribute is True, this attribute is configurable via CLI. configurable = Int(config=True) hidden = Int() >>> with hidestderr(): SampleCLI.cli([ --configurable, 1, --hidden, 2 ]) # hidden is not configurable, so it fails: Traceback (most recent call last): SystemExit: 2 >>> obj = SampleCLI.cli([ --configurable, 1 ]) >>> obj.configurable 1 >>> obj.hidden = 2 >>> obj.hidden 2 desc [string] Description of this attribute. Passed to help argument of ArgumentParser.add_argument. a = Int(desc= help string for attribute a, config=true) b = Float(desc= help string for attribute b, config=true) >>> SampleCLI.get_argparser().print_help() usage: [-h] [--a A] [--b B] optional arguments: -h, --help show this help message and exit --a A help string for attribute a (default: 0) --b B help string for attribute b (default: 0.0) cli_positional [bool] If True, corresponding command line argument is interpreted as a positional argument. int = Int(cli_positional=True, config=true) >>> obj = SampleCLI.cli([ 1 ]) # no --a here! >>> obj.int 1 cli_required [bool] Passed to required argument of ArgumentParser.add_argument int = Int(cli_required=True, config=true) 12 Chapter 5. CLI base class
>>> with hidestderr(): SampleCLI.cli([]) Traceback (most recent call last): SystemExit: 2 >>> obj = SampleCLI.cli([ --int, 1 ]) >>> obj.int 1 cli_metavar [str] Passed to metavar argument of ArgumentParser.add_argument int = Int(cli_metavar= NUM, config=true) >>> SampleCLI.get_argparser().print_help() usage: [-h] [--int NUM] optional arguments: -h, --help show this help message and exit --int NUM (default: 0) cli_paramfile [bool] This attribute has special meaning. When this metadata is True, this attribute indicate the path to parameter file The instance is first initialized using parameters defined in the parameter file, then command line arguments are used to override the parameters. Idioms int = Int(config=True) paramfile = Str(cli_paramfile=True, config=true) >>> import json >>> from tempfile import NamedTemporaryFile >>> param = { int : 1} >>> with NamedTemporaryFile(suffix=.json ) as f: json.dump(param, f) f.flush() obj = SampleCLI.cli([ --paramfile, f.name]) >>> obj.int 1 Get a dictionary containing configurable attributes. a = Int(0, config=true) b = Int(1, config=true) c = Int(2) >>> obj = SampleCLI() >>> obj.trait_get() == { a : 0, b : 1, c : 2} True >>> obj.trait_get(config=true) == { a : 0, b : 1} True Get a list of configurable attribute names. >>> names = SampleCLI.class_trait_names(config=True) >>> sorted(names) 13
[ a, b ] See Traits user manual for more information. Especially, Defining Traits: Initialization and Validation is useful to quickly glance traits API. Entry points classmethod cli(args=none) Call run() using command line arguments. When args is given, it is used instead of sys.argv[1:]. Essentially, the following two should do the same thing: $ python yourcli.py --alpha 1 >>> YourCLI.run(alpha=1) # doctest: +SKIP classmethod run(**kwds) Make an instance with args kwds and call do_run(). do_run() Actual implementation of run(). Child class must implement this method. API to access attributes classmethod config_traits(**metadata) Return configurable traits as a (possibly nested) dict. The returned dict can be nested if this class has Instance trait of TraitsCLIBase. flattendict() to get a flat dictionary with dotted keys. It is equivalent to cls.class_traits(config=true) if cls has no Instance trait. Use >>> class SubObject(TraitsCLIBase): int = Int(config=True) nonconfigurable = Int() int = Int(config=True) sub = Instance(SubObject, args=(), config=true) >>> traits = SampleCLI.config_traits() >>> traits { int : <traits.traits.ctrait at >, sub : { int : <traits.traits.ctrait at >}} >>> traits[ int ].trait_type <traits.trait_types.int object at > >>> traits[ sub ][ int ].trait_type <traits.trait_types.int object at > setattrs(attrs, only_configurable=false) Set attribute given a dictionary attrs. Keys of attrs can be dot-separated name (e.g., a.b.c). In this case, nested attribute will be set to its attribute. The values of attrs can be a dict. If the corresponding attribute is an instance of TraitsCLIBase, attributes of this instance is set using this dictionary. Otherwise, it will issue an error. 14 Chapter 5. CLI base class
>>> obj = TraitsCLIBase() >>> obj.b = TraitsCLIBase() >>> obj.setattrs({ a : 1, b : { c : 2}}) >>> obj.a 1 >>> obj.b.c 2 >>> obj.setattrs({ b.a : 111, b.c : 222}) >>> obj.b.a 111 >>> obj.b.c 222 >>> obj.setattrs({ x.a : 0}) Traceback (most recent call last): AttributeError: TraitsCLIBase object has no attribute x If only_configurable is True, attempt to set non-configurable attributes raises an error. a = Int(config=True) b = Int() >>> obj = SampleCLI() >>> obj.setattrs({ a : 1}, only_configurable=true) # This is OK. >>> obj.setattrs({ b : 1}, only_configurable=true) # This is not! Traceback (most recent call last): TraitsCLIAttributeError: Non-configurable key is given: b load_paramfile(path, only_configurable=true) Load attributes from parameter file at path. To support new parameter file, add a class-method called loader_{ext} where {ext} is the file extension of the parameter file. You can also redefine dispatch_paramfile_loader class-method to change how loader function is chosen. >>> from tempfile import NamedTemporaryFile int = Int(config=True) >>> obj = SampleCLI() >>> with NamedTemporaryFile(suffix=.json ) as f: f.write( {"int": 1} ) f.flush() obj.load_paramfile(f.name) >>> obj.int 1 You can use only_configurable=false to set non-configurable option. >>> obj = TraitsCLIBase() >>> with NamedTemporaryFile(suffix=.json ) as f: f.write( {"nonconfigurable": 1} ) f.flush() obj.load_paramfile(f.name, only_configurable=false) >>> obj.nonconfigurable 15
1 load_all_paramfiles() Load attributes from all parameter files set in paramfile attributes. Path of parameter file is defined by attributes whose metadata cli_paramfile is True. >>> from tempfile import NamedTemporaryFile >>> from contextlib import nested int = Int(config=True) str = Str(config=True) paramfiles = List(cli_paramfile=True, config=true) >>> obj = SampleCLI() >>> with nested(namedtemporaryfile(suffix=.json ), NamedTemporaryFile(suffix=.json )) as (f, g): f.write( {"int": 1} ) f.flush() g.write( {"str": "a"} ) g.flush() obj.paramfiles = [f.name, g.name] obj.load_all_paramfiles() >>> obj.int 1 >>> obj.str u a classmethod dispatch_paramfile_loader(path) Return an parameter file loader function based on path. This classmethod returns classmethod/staticmethod named laoder_{ext} where {ext} is the file extension of path. You can redefine this classmethod to change the dispatching behavior. Call signature of the loader function must be loader(path) where path is a string file path to the parameter file. static loader_json(path, _open=<built-in function open>) Load JSON file located at path. It is equivalent to json.load(open(path)). static loader_yaml(path, _open=<built-in function open>) Load YAML file located at path. It is equivalent to yaml.load(open(path)). You need PyYAML module to use this loader. static loader_yml(path, _open=<built-in function open>) Alias to loader_yaml(). classmethod loader_conf(path, _open=<built-in function open>) Load parameter from conf/ini file. As conf file has no type information, class traits will be used at load time. >>> class SubObject(TraitsCLIBase): c = Int(config=True) a = Int(config=True) b = Instance(SubObject, args=(), config=true) 16 Chapter 5. CLI base class
d = Instance(SubObject, args=(), config=true) cli_conf_root_section = root # this is default You can write options using dot-separated name. Use the section specified by cli_conf_root_section for top-level attributes. >>> from tempfile import NamedTemporaryFile >>> source = [root] a = 1 b.c = 2 >>> with NamedTemporaryFile() as f: f.write(source) f.flush() param = SampleCLI.loader_conf(f.name) >>> param == { a : 1, b.c : 2} True Options in sections other than cli_conf_root_section are prefixed by section name. >>> from tempfile import NamedTemporaryFile >>> source = [root] a = 1 [b] c = 2 [d] c = 3 >>> with NamedTemporaryFile() as f: f.write(source) f.flush() param = SampleCLI.loader_conf(f.name) >>> param == { a : 1, b.c : 2, d.c : 3} True classmethod loader_ini(path, _open=<built-in function open>) Alias to loader_conf(). cli_conf_root_section = root Root section name for conf/ini file loader (loader_conf()). Options in this section will not be prefixed by section name. static loader_py(path, _open=<built-in function open>) Load parameter from Python file located at path. >>> from tempfile import NamedTemporaryFile >>> source = a = 1 b = dict(c=2) _underscored_value_ = will_not_be_loaded >>> with NamedTemporaryFile() as f: f.write(source) f.flush() param = TraitsCLIBase.loader_py(f.name) >>> param == { a : 1, b : { c : 2}} True 17
Parser API ArgumentParser = <class argparse.argumentparser > Argument parser class/factory. This attribute must be a callable object which returns an instance of argparse.argumentparser or its subclass. classmethod get_argparser() Return an instance of ArgumentParser for this class. Parser options are set according to the configurable traits of this class. classmethod add_parser(parser, prefix= ) Call parser.add_argument based on class traits of cls. This classmethod is called from get_argparser(). 18 Chapter 5. CLI base class
CHAPTER SIX UTILITY FUNCTIONS traitscli.multi_command_cli(command_class_pairs, args=none, ArgumentParser=None) Launch CLI to call multiple classes. Usage: >>> class SampleBase(TraitsCLIBase): a = Int(config=True) def do_run(self): print "Running", print {0}(a={1!r}).format(self. class. name, self.a) >>> class SampleInit(SampleBase): pass >>> class SampleCheckout(SampleBase): pass >>> class SampleBranch(SampleBase): pass >>> obj = multi_command_cli( # CLI classes and subcommand names [( init, SampleInit), ( checkout, SampleCheckout), ( branch, SampleBranch), ], # Command line arguments [ init, --a, 1 ]) Running SampleInit(a=1) >>> isinstance(obj, SampleInit) # used CLI object is returned. True If ArgumentParser is not specified, ArgumentParser of the first class will be used. traitscli.flattendict(dct) Flatten dictionary using key concatenated by dot. >>> flattendict({ a : 1, b : 2}) == { a : 1, b : 2} True >>> flattendict({ a : 1, b : { c : 2}}) == { a : 1, b.c : 2} True 19
20 Chapter 6. Utility functions
CHAPTER SEVEN CHANGE LOG 7.1 v0.1 Classes which inherits HasTraits but does not inherit TraitsCLIBase also can be used as a configurable trait. 21
22 Chapter 7. Change log
PYTHON MODULE INDEX t traitscli, 1 23