application.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. # encoding: utf-8
  2. """A base class for a configurable application."""
  3. # Copyright (c) IPython Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. from __future__ import print_function
  6. from copy import deepcopy
  7. import json
  8. import logging
  9. import os
  10. import re
  11. import sys
  12. from collections import defaultdict, OrderedDict
  13. from decorator import decorator
  14. from traitlets.config.configurable import Configurable, SingletonConfigurable
  15. from traitlets.config.loader import (
  16. KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
  17. )
  18. from traitlets.traitlets import (
  19. Bool, Unicode, List, Enum, Dict, Instance, TraitError, observe, observe_compat, default,
  20. )
  21. from ipython_genutils.importstring import import_item
  22. from ipython_genutils.text import indent, wrap_paragraphs, dedent
  23. from ipython_genutils import py3compat
  24. import six
  25. #-----------------------------------------------------------------------------
  26. # Descriptions for the various sections
  27. #-----------------------------------------------------------------------------
  28. # merge flags&aliases into options
  29. option_description = """
  30. Arguments that take values are actually convenience aliases to full
  31. Configurables, whose aliases are listed on the help line. For more information
  32. on full configurables, see '--help-all'.
  33. """.strip() # trim newlines of front and back
  34. keyvalue_description = """
  35. Parameters are set from command-line arguments of the form:
  36. `--Class.trait=value`.
  37. This line is evaluated in Python, so simple expressions are allowed, e.g.::
  38. `--C.a='range(3)'` For setting C.a=[0,1,2].
  39. """.strip() # trim newlines of front and back
  40. # sys.argv can be missing, for example when python is embedded. See the docs
  41. # for details: http://docs.python.org/2/c-api/intro.html#embedding-python
  42. if not hasattr(sys, "argv"):
  43. sys.argv = [""]
  44. subcommand_description = """
  45. Subcommands are launched as `{app} cmd [args]`. For information on using
  46. subcommand 'cmd', do: `{app} cmd -h`.
  47. """
  48. # get running program name
  49. #-----------------------------------------------------------------------------
  50. # Application class
  51. #-----------------------------------------------------------------------------
  52. _envvar = os.environ.get('TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR','')
  53. if _envvar.lower() in {'1','true'}:
  54. TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR = True
  55. elif _envvar.lower() in {'0','false',''} :
  56. TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR = False
  57. else:
  58. raise ValueError("Unsupported value for environment variable: 'TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
  59. @decorator
  60. def catch_config_error(method, app, *args, **kwargs):
  61. """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
  62. On a TraitError (generally caused by bad config), this will print the trait's
  63. message, and exit the app.
  64. For use on init methods, to prevent invoking excepthook on invalid input.
  65. """
  66. try:
  67. return method(app, *args, **kwargs)
  68. except (TraitError, ArgumentError) as e:
  69. app.print_help()
  70. app.log.fatal("Bad config encountered during initialization:")
  71. app.log.fatal(str(e))
  72. app.log.debug("Config at the time: %s", app.config)
  73. app.exit(1)
  74. class ApplicationError(Exception):
  75. pass
  76. class LevelFormatter(logging.Formatter):
  77. """Formatter with additional `highlevel` record
  78. This field is empty if log level is less than highlevel_limit,
  79. otherwise it is formatted with self.highlevel_format.
  80. Useful for adding 'WARNING' to warning messages,
  81. without adding 'INFO' to info, etc.
  82. """
  83. highlevel_limit = logging.WARN
  84. highlevel_format = " %(levelname)s |"
  85. def format(self, record):
  86. if record.levelno >= self.highlevel_limit:
  87. record.highlevel = self.highlevel_format % record.__dict__
  88. else:
  89. record.highlevel = ""
  90. return super(LevelFormatter, self).format(record)
  91. class Application(SingletonConfigurable):
  92. """A singleton application with full configuration support."""
  93. # The name of the application, will usually match the name of the command
  94. # line application
  95. name = Unicode(u'application')
  96. # The description of the application that is printed at the beginning
  97. # of the help.
  98. description = Unicode(u'This is an application.')
  99. # default section descriptions
  100. option_description = Unicode(option_description)
  101. keyvalue_description = Unicode(keyvalue_description)
  102. subcommand_description = Unicode(subcommand_description)
  103. python_config_loader_class = PyFileConfigLoader
  104. json_config_loader_class = JSONFileConfigLoader
  105. # The usage and example string that goes at the end of the help string.
  106. examples = Unicode()
  107. # A sequence of Configurable subclasses whose config=True attributes will
  108. # be exposed at the command line.
  109. classes = []
  110. def _classes_inc_parents(self):
  111. """Iterate through configurable classes, including configurable parents
  112. Children should always be after parents, and each class should only be
  113. yielded once.
  114. """
  115. seen = set()
  116. for c in self.classes:
  117. # We want to sort parents before children, so we reverse the MRO
  118. for parent in reversed(c.mro()):
  119. if issubclass(parent, Configurable) and (parent not in seen):
  120. seen.add(parent)
  121. yield parent
  122. # The version string of this application.
  123. version = Unicode(u'0.0')
  124. # the argv used to initialize the application
  125. argv = List()
  126. # Whether failing to load config files should prevent startup
  127. raise_config_file_errors = Bool(TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR)
  128. # The log level for the application
  129. log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
  130. default_value=logging.WARN,
  131. help="Set the log level by value or name.").tag(config=True)
  132. @observe('log_level')
  133. @observe_compat
  134. def _log_level_changed(self, change):
  135. """Adjust the log level when log_level is set."""
  136. new = change.new
  137. if isinstance(new, six.string_types):
  138. new = getattr(logging, new)
  139. self.log_level = new
  140. self.log.setLevel(new)
  141. _log_formatter_cls = LevelFormatter
  142. log_datefmt = Unicode("%Y-%m-%d %H:%M:%S",
  143. help="The date format used by logging formatters for %(asctime)s"
  144. ).tag(config=True)
  145. log_format = Unicode("[%(name)s]%(highlevel)s %(message)s",
  146. help="The Logging format template",
  147. ).tag(config=True)
  148. @observe('log_datefmt', 'log_format')
  149. @observe_compat
  150. def _log_format_changed(self, change):
  151. """Change the log formatter when log_format is set."""
  152. _log_handler = self.log.handlers[0]
  153. _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
  154. _log_handler.setFormatter(_log_formatter)
  155. @default('log')
  156. def _log_default(self):
  157. """Start logging for this application.
  158. The default is to log to stderr using a StreamHandler, if no default
  159. handler already exists. The log level starts at logging.WARN, but this
  160. can be adjusted by setting the ``log_level`` attribute.
  161. """
  162. log = logging.getLogger(self.__class__.__name__)
  163. log.setLevel(self.log_level)
  164. log.propagate = False
  165. _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
  166. while _log:
  167. if _log.handlers:
  168. return log
  169. if not _log.propagate:
  170. break
  171. else:
  172. _log = _log.parent
  173. if sys.executable and sys.executable.endswith('pythonw.exe'):
  174. # this should really go to a file, but file-logging is only
  175. # hooked up in parallel applications
  176. _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
  177. else:
  178. _log_handler = logging.StreamHandler()
  179. _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
  180. _log_handler.setFormatter(_log_formatter)
  181. log.addHandler(_log_handler)
  182. return log
  183. # the alias map for configurables
  184. aliases = Dict({'log-level' : 'Application.log_level'})
  185. # flags for loading Configurables or store_const style flags
  186. # flags are loaded from this dict by '--key' flags
  187. # this must be a dict of two-tuples, the first element being the Config/dict
  188. # and the second being the help string for the flag
  189. flags = Dict()
  190. @observe('flags')
  191. @observe_compat
  192. def _flags_changed(self, change):
  193. """ensure flags dict is valid"""
  194. new = change.new
  195. for key, value in new.items():
  196. assert len(value) == 2, "Bad flag: %r:%s" % (key, value)
  197. assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s" % (key, value)
  198. assert isinstance(value[1], six.string_types), "Bad flag: %r:%s" % (key, value)
  199. # subcommands for launching other applications
  200. # if this is not empty, this will be a parent Application
  201. # this must be a dict of two-tuples,
  202. # the first element being the application class/import string
  203. # and the second being the help string for the subcommand
  204. subcommands = Dict()
  205. # parse_command_line will initialize a subapp, if requested
  206. subapp = Instance('traitlets.config.application.Application', allow_none=True)
  207. # extra command-line arguments that don't set config values
  208. extra_args = List(Unicode())
  209. cli_config = Instance(Config, (), {},
  210. help="""The subset of our configuration that came from the command-line
  211. We re-load this configuration after loading config files,
  212. to ensure that it maintains highest priority.
  213. """
  214. )
  215. def __init__(self, **kwargs):
  216. SingletonConfigurable.__init__(self, **kwargs)
  217. # Ensure my class is in self.classes, so my attributes appear in command line
  218. # options and config files.
  219. cls = self.__class__
  220. if cls not in self.classes:
  221. if self.classes is cls.classes:
  222. # class attr, assign instead of insert
  223. cls.classes = [cls] + self.classes
  224. else:
  225. self.classes.insert(0, self.__class__)
  226. @observe('config')
  227. @observe_compat
  228. def _config_changed(self, change):
  229. super(Application, self)._config_changed(change)
  230. self.log.debug('Config changed:')
  231. self.log.debug(repr(change.new))
  232. @catch_config_error
  233. def initialize(self, argv=None):
  234. """Do the basic steps to configure me.
  235. Override in subclasses.
  236. """
  237. self.parse_command_line(argv)
  238. def start(self):
  239. """Start the app mainloop.
  240. Override in subclasses.
  241. """
  242. if self.subapp is not None:
  243. return self.subapp.start()
  244. def print_alias_help(self):
  245. """Print the alias part of the help."""
  246. if not self.aliases:
  247. return
  248. lines = []
  249. classdict = {}
  250. for cls in self.classes:
  251. # include all parents (up to, but excluding Configurable) in available names
  252. for c in cls.mro()[:-3]:
  253. classdict[c.__name__] = c
  254. for alias, longname in self.aliases.items():
  255. classname, traitname = longname.split('.',1)
  256. cls = classdict[classname]
  257. trait = cls.class_traits(config=True)[traitname]
  258. help = cls.class_get_trait_help(trait).splitlines()
  259. # reformat first line
  260. help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
  261. if len(alias) == 1:
  262. help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
  263. lines.extend(help)
  264. # lines.append('')
  265. print(os.linesep.join(lines))
  266. def print_flag_help(self):
  267. """Print the flag part of the help."""
  268. if not self.flags:
  269. return
  270. lines = []
  271. for m, (cfg,help) in self.flags.items():
  272. prefix = '--' if len(m) > 1 else '-'
  273. lines.append(prefix+m)
  274. lines.append(indent(dedent(help.strip())))
  275. # lines.append('')
  276. print(os.linesep.join(lines))
  277. def print_options(self):
  278. if not self.flags and not self.aliases:
  279. return
  280. lines = ['Options']
  281. lines.append('-'*len(lines[0]))
  282. lines.append('')
  283. for p in wrap_paragraphs(self.option_description):
  284. lines.append(p)
  285. lines.append('')
  286. print(os.linesep.join(lines))
  287. self.print_flag_help()
  288. self.print_alias_help()
  289. print()
  290. def print_subcommands(self):
  291. """Print the subcommand part of the help."""
  292. if not self.subcommands:
  293. return
  294. lines = ["Subcommands"]
  295. lines.append('-'*len(lines[0]))
  296. lines.append('')
  297. for p in wrap_paragraphs(self.subcommand_description.format(
  298. app=self.name)):
  299. lines.append(p)
  300. lines.append('')
  301. for subc, (cls, help) in self.subcommands.items():
  302. lines.append(subc)
  303. if help:
  304. lines.append(indent(dedent(help.strip())))
  305. lines.append('')
  306. print(os.linesep.join(lines))
  307. def print_help(self, classes=False):
  308. """Print the help for each Configurable class in self.classes.
  309. If classes=False (the default), only flags and aliases are printed.
  310. """
  311. self.print_description()
  312. self.print_subcommands()
  313. self.print_options()
  314. if classes:
  315. help_classes = self.classes
  316. if help_classes:
  317. print("Class parameters")
  318. print("----------------")
  319. print()
  320. for p in wrap_paragraphs(self.keyvalue_description):
  321. print(p)
  322. print()
  323. for cls in help_classes:
  324. cls.class_print_help()
  325. print()
  326. else:
  327. print("To see all available configurables, use `--help-all`")
  328. print()
  329. self.print_examples()
  330. def document_config_options(self):
  331. """Generate rST format documentation for the config options this application
  332. Returns a multiline string.
  333. """
  334. return '\n'.join(c.class_config_rst_doc()
  335. for c in self._classes_inc_parents())
  336. def print_description(self):
  337. """Print the application description."""
  338. for p in wrap_paragraphs(self.description):
  339. print(p)
  340. print()
  341. def print_examples(self):
  342. """Print usage and examples.
  343. This usage string goes at the end of the command line help string
  344. and should contain examples of the application's usage.
  345. """
  346. if self.examples:
  347. print("Examples")
  348. print("--------")
  349. print()
  350. print(indent(dedent(self.examples.strip())))
  351. print()
  352. def print_version(self):
  353. """Print the version string."""
  354. print(self.version)
  355. @catch_config_error
  356. def initialize_subcommand(self, subc, argv=None):
  357. """Initialize a subcommand with argv."""
  358. subapp,help = self.subcommands.get(subc)
  359. if isinstance(subapp, six.string_types):
  360. subapp = import_item(subapp)
  361. # clear existing instances
  362. self.__class__.clear_instance()
  363. # instantiate
  364. self.subapp = subapp.instance(parent=self)
  365. # and initialize subapp
  366. self.subapp.initialize(argv)
  367. def flatten_flags(self):
  368. """flatten flags and aliases, so cl-args override as expected.
  369. This prevents issues such as an alias pointing to InteractiveShell,
  370. but a config file setting the same trait in TerminalInteraciveShell
  371. getting inappropriate priority over the command-line arg.
  372. Only aliases with exactly one descendent in the class list
  373. will be promoted.
  374. """
  375. # build a tree of classes in our list that inherit from a particular
  376. # it will be a dict by parent classname of classes in our list
  377. # that are descendents
  378. mro_tree = defaultdict(list)
  379. for cls in self.classes:
  380. clsname = cls.__name__
  381. for parent in cls.mro()[1:-3]:
  382. # exclude cls itself and Configurable,HasTraits,object
  383. mro_tree[parent.__name__].append(clsname)
  384. # flatten aliases, which have the form:
  385. # { 'alias' : 'Class.trait' }
  386. aliases = {}
  387. for alias, cls_trait in self.aliases.items():
  388. cls,trait = cls_trait.split('.',1)
  389. children = mro_tree[cls]
  390. if len(children) == 1:
  391. # exactly one descendent, promote alias
  392. cls = children[0]
  393. aliases[alias] = '.'.join([cls,trait])
  394. # flatten flags, which are of the form:
  395. # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
  396. flags = {}
  397. for key, (flagdict, help) in self.flags.items():
  398. newflag = {}
  399. for cls, subdict in flagdict.items():
  400. children = mro_tree[cls]
  401. # exactly one descendent, promote flag section
  402. if len(children) == 1:
  403. cls = children[0]
  404. newflag[cls] = subdict
  405. flags[key] = (newflag, help)
  406. return flags, aliases
  407. @catch_config_error
  408. def parse_command_line(self, argv=None):
  409. """Parse the command line arguments."""
  410. argv = sys.argv[1:] if argv is None else argv
  411. self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
  412. if argv and argv[0] == 'help':
  413. # turn `ipython help notebook` into `ipython notebook -h`
  414. argv = argv[1:] + ['-h']
  415. if self.subcommands and len(argv) > 0:
  416. # we have subcommands, and one may have been specified
  417. subc, subargv = argv[0], argv[1:]
  418. if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
  419. # it's a subcommand, and *not* a flag or class parameter
  420. return self.initialize_subcommand(subc, subargv)
  421. # Arguments after a '--' argument are for the script IPython may be
  422. # about to run, not IPython iteslf. For arguments parsed here (help and
  423. # version), we want to only search the arguments up to the first
  424. # occurrence of '--', which we're calling interpreted_argv.
  425. try:
  426. interpreted_argv = argv[:argv.index('--')]
  427. except ValueError:
  428. interpreted_argv = argv
  429. if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
  430. self.print_help('--help-all' in interpreted_argv)
  431. self.exit(0)
  432. if '--version' in interpreted_argv or '-V' in interpreted_argv:
  433. self.print_version()
  434. self.exit(0)
  435. # flatten flags&aliases, so cl-args get appropriate priority:
  436. flags,aliases = self.flatten_flags()
  437. loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
  438. flags=flags, log=self.log)
  439. self.cli_config = deepcopy(loader.load_config())
  440. self.update_config(self.cli_config)
  441. # store unparsed args in extra_args
  442. self.extra_args = loader.extra_args
  443. @classmethod
  444. def _load_config_files(cls, basefilename, path=None, log=None, raise_config_file_errors=False):
  445. """Load config files (py,json) by filename and path.
  446. yield each config object in turn.
  447. """
  448. if not isinstance(path, list):
  449. path = [path]
  450. for path in path[::-1]:
  451. # path list is in descending priority order, so load files backwards:
  452. pyloader = cls.python_config_loader_class(basefilename+'.py', path=path, log=log)
  453. if log:
  454. log.debug("Looking for %s in %s", basefilename, path or os.getcwd())
  455. jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
  456. config = None
  457. loaded = []
  458. filenames = []
  459. for loader in [pyloader, jsonloader]:
  460. try:
  461. config = loader.load_config()
  462. except ConfigFileNotFound:
  463. pass
  464. except Exception:
  465. # try to get the full filename, but it will be empty in the
  466. # unlikely event that the error raised before filefind finished
  467. filename = loader.full_filename or basefilename
  468. # problem while running the file
  469. if raise_config_file_errors:
  470. raise
  471. if log:
  472. log.error("Exception while loading config file %s",
  473. filename, exc_info=True)
  474. else:
  475. if log:
  476. log.debug("Loaded config file: %s", loader.full_filename)
  477. if config:
  478. for filename, earlier_config in zip(filenames, loaded):
  479. collisions = earlier_config.collisions(config)
  480. if collisions and log:
  481. log.warning("Collisions detected in {0} and {1} config files."
  482. " {1} has higher priority: {2}".format(
  483. filename, loader.full_filename, json.dumps(collisions, indent=2),
  484. ))
  485. yield config
  486. loaded.append(config)
  487. filenames.append(loader.full_filename)
  488. @catch_config_error
  489. def load_config_file(self, filename, path=None):
  490. """Load config files by filename and path."""
  491. filename, ext = os.path.splitext(filename)
  492. new_config = Config()
  493. for config in self._load_config_files(filename, path=path, log=self.log,
  494. raise_config_file_errors=self.raise_config_file_errors,
  495. ):
  496. new_config.merge(config)
  497. # add self.cli_config to preserve CLI config priority
  498. new_config.merge(self.cli_config)
  499. self.update_config(new_config)
  500. def _classes_in_config_sample(self):
  501. """
  502. Yields only classes with own traits, and their subclasses.
  503. Thus, produced sample config-file will contain all classes
  504. on which a trait-value may be overridden:
  505. - either on the class owning the trait,
  506. - or on its subclasses, even if those subclasses do not define
  507. any traits themselves.
  508. """
  509. cls_to_config = OrderedDict( (cls, bool(cls.class_own_traits(config=True)))
  510. for cls
  511. in self._classes_inc_parents())
  512. def is_any_parent_included(cls):
  513. return any(b in cls_to_config and cls_to_config[b] for b in cls.__bases__)
  514. ## Mark "empty" classes for inclusion if their parents own-traits,
  515. # and loop until no more classes gets marked.
  516. #
  517. while True:
  518. to_incl_orig = cls_to_config.copy()
  519. cls_to_config = OrderedDict( (cls, inc_yes or is_any_parent_included(cls))
  520. for cls, inc_yes
  521. in cls_to_config.items())
  522. if cls_to_config == to_incl_orig:
  523. break
  524. for cl, inc_yes in cls_to_config.items():
  525. if inc_yes:
  526. yield cl
  527. def generate_config_file(self):
  528. """generate default config file from Configurables"""
  529. lines = ["# Configuration file for %s." % self.name]
  530. lines.append('')
  531. for cls in self._classes_in_config_sample():
  532. lines.append(cls.class_config_section())
  533. return '\n'.join(lines)
  534. def exit(self, exit_status=0):
  535. self.log.debug("Exiting application: %s" % self.name)
  536. sys.exit(exit_status)
  537. @classmethod
  538. def launch_instance(cls, argv=None, **kwargs):
  539. """Launch a global instance of this Application
  540. If a global instance already exists, this reinitializes and starts it
  541. """
  542. app = cls.instance(**kwargs)
  543. app.initialize(argv)
  544. app.start()
  545. #-----------------------------------------------------------------------------
  546. # utility functions, for convenience
  547. #-----------------------------------------------------------------------------
  548. def boolean_flag(name, configurable, set_help='', unset_help=''):
  549. """Helper for building basic --trait, --no-trait flags.
  550. Parameters
  551. ----------
  552. name : str
  553. The name of the flag.
  554. configurable : str
  555. The 'Class.trait' string of the trait to be set/unset with the flag
  556. set_help : unicode
  557. help string for --name flag
  558. unset_help : unicode
  559. help string for --no-name flag
  560. Returns
  561. -------
  562. cfg : dict
  563. A dict with two keys: 'name', and 'no-name', for setting and unsetting
  564. the trait, respectively.
  565. """
  566. # default helpstrings
  567. set_help = set_help or "set %s=True"%configurable
  568. unset_help = unset_help or "set %s=False"%configurable
  569. cls,trait = configurable.split('.')
  570. setter = {cls : {trait : True}}
  571. unsetter = {cls : {trait : False}}
  572. return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
  573. def get_config():
  574. """Get the config object for the global Application instance, if there is one
  575. otherwise return an empty config object
  576. """
  577. if Application.initialized():
  578. return Application.instance().config
  579. else:
  580. return Config()