loader.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. # encoding: utf-8
  2. """A simple configuration system."""
  3. # Copyright (c) IPython Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. import argparse
  6. import copy
  7. import logging
  8. import os
  9. import re
  10. import sys
  11. import json
  12. from ast import literal_eval
  13. from ipython_genutils.path import filefind
  14. from ipython_genutils import py3compat
  15. from ipython_genutils.encoding import DEFAULT_ENCODING
  16. from six import text_type
  17. from traitlets.traitlets import HasTraits, List, Any
  18. #-----------------------------------------------------------------------------
  19. # Exceptions
  20. #-----------------------------------------------------------------------------
  21. class ConfigError(Exception):
  22. pass
  23. class ConfigLoaderError(ConfigError):
  24. pass
  25. class ConfigFileNotFound(ConfigError):
  26. pass
  27. class ArgumentError(ConfigLoaderError):
  28. pass
  29. #-----------------------------------------------------------------------------
  30. # Argparse fix
  31. #-----------------------------------------------------------------------------
  32. # Unfortunately argparse by default prints help messages to stderr instead of
  33. # stdout. This makes it annoying to capture long help screens at the command
  34. # line, since one must know how to pipe stderr, which many users don't know how
  35. # to do. So we override the print_help method with one that defaults to
  36. # stdout and use our class instead.
  37. class ArgumentParser(argparse.ArgumentParser):
  38. """Simple argparse subclass that prints help to stdout by default."""
  39. def print_help(self, file=None):
  40. if file is None:
  41. file = sys.stdout
  42. return super(ArgumentParser, self).print_help(file)
  43. print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
  44. #-----------------------------------------------------------------------------
  45. # Config class for holding config information
  46. #-----------------------------------------------------------------------------
  47. class LazyConfigValue(HasTraits):
  48. """Proxy object for exposing methods on configurable containers
  49. Exposes:
  50. - append, extend, insert on lists
  51. - update on dicts
  52. - update, add on sets
  53. """
  54. _value = None
  55. # list methods
  56. _extend = List()
  57. _prepend = List()
  58. def append(self, obj):
  59. self._extend.append(obj)
  60. def extend(self, other):
  61. self._extend.extend(other)
  62. def prepend(self, other):
  63. """like list.extend, but for the front"""
  64. self._prepend[:0] = other
  65. _inserts = List()
  66. def insert(self, index, other):
  67. if not isinstance(index, int):
  68. raise TypeError("An integer is required")
  69. self._inserts.append((index, other))
  70. # dict methods
  71. # update is used for both dict and set
  72. _update = Any()
  73. def update(self, other):
  74. if self._update is None:
  75. if isinstance(other, dict):
  76. self._update = {}
  77. else:
  78. self._update = set()
  79. self._update.update(other)
  80. # set methods
  81. def add(self, obj):
  82. self.update({obj})
  83. def get_value(self, initial):
  84. """construct the value from the initial one
  85. after applying any insert / extend / update changes
  86. """
  87. if self._value is not None:
  88. return self._value
  89. value = copy.deepcopy(initial)
  90. if isinstance(value, list):
  91. for idx, obj in self._inserts:
  92. value.insert(idx, obj)
  93. value[:0] = self._prepend
  94. value.extend(self._extend)
  95. elif isinstance(value, dict):
  96. if self._update:
  97. value.update(self._update)
  98. elif isinstance(value, set):
  99. if self._update:
  100. value.update(self._update)
  101. self._value = value
  102. return value
  103. def to_dict(self):
  104. """return JSONable dict form of my data
  105. Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
  106. """
  107. d = {}
  108. if self._update:
  109. d['update'] = self._update
  110. if self._extend:
  111. d['extend'] = self._extend
  112. if self._prepend:
  113. d['prepend'] = self._prepend
  114. elif self._inserts:
  115. d['inserts'] = self._inserts
  116. return d
  117. def _is_section_key(key):
  118. """Is a Config key a section name (does it start with a capital)?"""
  119. if key and key[0].upper()==key[0] and not key.startswith('_'):
  120. return True
  121. else:
  122. return False
  123. class Config(dict):
  124. """An attribute based dict that can do smart merges."""
  125. def __init__(self, *args, **kwds):
  126. dict.__init__(self, *args, **kwds)
  127. self._ensure_subconfig()
  128. def _ensure_subconfig(self):
  129. """ensure that sub-dicts that should be Config objects are
  130. casts dicts that are under section keys to Config objects,
  131. which is necessary for constructing Config objects from dict literals.
  132. """
  133. for key in self:
  134. obj = self[key]
  135. if _is_section_key(key) \
  136. and isinstance(obj, dict) \
  137. and not isinstance(obj, Config):
  138. setattr(self, key, Config(obj))
  139. def _merge(self, other):
  140. """deprecated alias, use Config.merge()"""
  141. self.merge(other)
  142. def merge(self, other):
  143. """merge another config object into this one"""
  144. to_update = {}
  145. for k, v in other.items():
  146. if k not in self:
  147. to_update[k] = v
  148. else: # I have this key
  149. if isinstance(v, Config) and isinstance(self[k], Config):
  150. # Recursively merge common sub Configs
  151. self[k].merge(v)
  152. else:
  153. # Plain updates for non-Configs
  154. to_update[k] = v
  155. self.update(to_update)
  156. def collisions(self, other):
  157. """Check for collisions between two config objects.
  158. Returns a dict of the form {"Class": {"trait": "collision message"}}`,
  159. indicating which values have been ignored.
  160. An empty dict indicates no collisions.
  161. """
  162. collisions = {}
  163. for section in self:
  164. if section not in other:
  165. continue
  166. mine = self[section]
  167. theirs = other[section]
  168. for key in mine:
  169. if key in theirs and mine[key] != theirs[key]:
  170. collisions.setdefault(section, {})
  171. collisions[section][key] = "%r ignored, using %r" % (mine[key], theirs[key])
  172. return collisions
  173. def __contains__(self, key):
  174. # allow nested contains of the form `"Section.key" in config`
  175. if '.' in key:
  176. first, remainder = key.split('.', 1)
  177. if first not in self:
  178. return False
  179. return remainder in self[first]
  180. return super(Config, self).__contains__(key)
  181. # .has_key is deprecated for dictionaries.
  182. has_key = __contains__
  183. def _has_section(self, key):
  184. return _is_section_key(key) and key in self
  185. def copy(self):
  186. return type(self)(dict.copy(self))
  187. def __copy__(self):
  188. return self.copy()
  189. def __deepcopy__(self, memo):
  190. new_config = type(self)()
  191. for key, value in self.items():
  192. if isinstance(value, (Config, LazyConfigValue)):
  193. # deep copy config objects
  194. value = copy.deepcopy(value, memo)
  195. elif type(value) in {dict, list, set, tuple}:
  196. # shallow copy plain container traits
  197. value = copy.copy(value)
  198. new_config[key] = value
  199. return new_config
  200. def __getitem__(self, key):
  201. try:
  202. return dict.__getitem__(self, key)
  203. except KeyError:
  204. if _is_section_key(key):
  205. c = Config()
  206. dict.__setitem__(self, key, c)
  207. return c
  208. elif not key.startswith('_'):
  209. # undefined, create lazy value, used for container methods
  210. v = LazyConfigValue()
  211. dict.__setitem__(self, key, v)
  212. return v
  213. else:
  214. raise KeyError
  215. def __setitem__(self, key, value):
  216. if _is_section_key(key):
  217. if not isinstance(value, Config):
  218. raise ValueError('values whose keys begin with an uppercase '
  219. 'char must be Config instances: %r, %r' % (key, value))
  220. dict.__setitem__(self, key, value)
  221. def __getattr__(self, key):
  222. if key.startswith('__'):
  223. return dict.__getattr__(self, key)
  224. try:
  225. return self.__getitem__(key)
  226. except KeyError as e:
  227. raise AttributeError(e)
  228. def __setattr__(self, key, value):
  229. if key.startswith('__'):
  230. return dict.__setattr__(self, key, value)
  231. try:
  232. self.__setitem__(key, value)
  233. except KeyError as e:
  234. raise AttributeError(e)
  235. def __delattr__(self, key):
  236. if key.startswith('__'):
  237. return dict.__delattr__(self, key)
  238. try:
  239. dict.__delitem__(self, key)
  240. except KeyError as e:
  241. raise AttributeError(e)
  242. #-----------------------------------------------------------------------------
  243. # Config loading classes
  244. #-----------------------------------------------------------------------------
  245. class ConfigLoader(object):
  246. """A object for loading configurations from just about anywhere.
  247. The resulting configuration is packaged as a :class:`Config`.
  248. Notes
  249. -----
  250. A :class:`ConfigLoader` does one thing: load a config from a source
  251. (file, command line arguments) and returns the data as a :class:`Config` object.
  252. There are lots of things that :class:`ConfigLoader` does not do. It does
  253. not implement complex logic for finding config files. It does not handle
  254. default values or merge multiple configs. These things need to be
  255. handled elsewhere.
  256. """
  257. def _log_default(self):
  258. from traitlets.log import get_logger
  259. return get_logger()
  260. def __init__(self, log=None):
  261. """A base class for config loaders.
  262. log : instance of :class:`logging.Logger` to use.
  263. By default loger of :meth:`traitlets.config.application.Application.instance()`
  264. will be used
  265. Examples
  266. --------
  267. >>> cl = ConfigLoader()
  268. >>> config = cl.load_config()
  269. >>> config
  270. {}
  271. """
  272. self.clear()
  273. if log is None:
  274. self.log = self._log_default()
  275. self.log.debug('Using default logger')
  276. else:
  277. self.log = log
  278. def clear(self):
  279. self.config = Config()
  280. def load_config(self):
  281. """Load a config from somewhere, return a :class:`Config` instance.
  282. Usually, this will cause self.config to be set and then returned.
  283. However, in most cases, :meth:`ConfigLoader.clear` should be called
  284. to erase any previous state.
  285. """
  286. self.clear()
  287. return self.config
  288. class FileConfigLoader(ConfigLoader):
  289. """A base class for file based configurations.
  290. As we add more file based config loaders, the common logic should go
  291. here.
  292. """
  293. def __init__(self, filename, path=None, **kw):
  294. """Build a config loader for a filename and path.
  295. Parameters
  296. ----------
  297. filename : str
  298. The file name of the config file.
  299. path : str, list, tuple
  300. The path to search for the config file on, or a sequence of
  301. paths to try in order.
  302. """
  303. super(FileConfigLoader, self).__init__(**kw)
  304. self.filename = filename
  305. self.path = path
  306. self.full_filename = ''
  307. def _find_file(self):
  308. """Try to find the file by searching the paths."""
  309. self.full_filename = filefind(self.filename, self.path)
  310. class JSONFileConfigLoader(FileConfigLoader):
  311. """A JSON file loader for config
  312. Can also act as a context manager that rewrite the configuration file to disk on exit.
  313. Example::
  314. with JSONFileConfigLoader('myapp.json','/home/jupyter/configurations/') as c:
  315. c.MyNewConfigurable.new_value = 'Updated'
  316. """
  317. def load_config(self):
  318. """Load the config from a file and return it as a Config object."""
  319. self.clear()
  320. try:
  321. self._find_file()
  322. except IOError as e:
  323. raise ConfigFileNotFound(str(e))
  324. dct = self._read_file_as_dict()
  325. self.config = self._convert_to_config(dct)
  326. return self.config
  327. def _read_file_as_dict(self):
  328. with open(self.full_filename) as f:
  329. return json.load(f)
  330. def _convert_to_config(self, dictionary):
  331. if 'version' in dictionary:
  332. version = dictionary.pop('version')
  333. else:
  334. version = 1
  335. if version == 1:
  336. return Config(dictionary)
  337. else:
  338. raise ValueError('Unknown version of JSON config file: {version}'.format(version=version))
  339. def __enter__(self):
  340. self.load_config()
  341. return self.config
  342. def __exit__(self, exc_type, exc_value, traceback):
  343. """
  344. Exit the context manager but do not handle any errors.
  345. In case of any error, we do not want to write the potentially broken
  346. configuration to disk.
  347. """
  348. self.config.version = 1
  349. json_config = json.dumps(self.config, indent=2)
  350. with open(self.full_filename, 'w') as f:
  351. f.write(json_config)
  352. class PyFileConfigLoader(FileConfigLoader):
  353. """A config loader for pure python files.
  354. This is responsible for locating a Python config file by filename and
  355. path, then executing it to construct a Config object.
  356. """
  357. def load_config(self):
  358. """Load the config from a file and return it as a Config object."""
  359. self.clear()
  360. try:
  361. self._find_file()
  362. except IOError as e:
  363. raise ConfigFileNotFound(str(e))
  364. self._read_file_as_dict()
  365. return self.config
  366. def load_subconfig(self, fname, path=None):
  367. """Injected into config file namespace as load_subconfig"""
  368. if path is None:
  369. path = self.path
  370. loader = self.__class__(fname, path)
  371. try:
  372. sub_config = loader.load_config()
  373. except ConfigFileNotFound:
  374. # Pass silently if the sub config is not there,
  375. # treat it as an empty config file.
  376. pass
  377. else:
  378. self.config.merge(sub_config)
  379. def _read_file_as_dict(self):
  380. """Load the config file into self.config, with recursive loading."""
  381. def get_config():
  382. """Unnecessary now, but a deprecation warning is more trouble than it's worth."""
  383. return self.config
  384. namespace = dict(
  385. c=self.config,
  386. load_subconfig=self.load_subconfig,
  387. get_config=get_config,
  388. __file__=self.full_filename,
  389. )
  390. fs_encoding = sys.getfilesystemencoding() or 'ascii'
  391. conf_filename = self.full_filename.encode(fs_encoding)
  392. py3compat.execfile(conf_filename, namespace)
  393. class CommandLineConfigLoader(ConfigLoader):
  394. """A config loader for command line arguments.
  395. As we add more command line based loaders, the common logic should go
  396. here.
  397. """
  398. def _exec_config_str(self, lhs, rhs):
  399. """execute self.config.<lhs> = <rhs>
  400. * expands ~ with expanduser
  401. * tries to assign with literal_eval, otherwise assigns with just the string,
  402. allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
  403. equivalent are `--C.a=4` and `--C.a='4'`.
  404. """
  405. rhs = os.path.expanduser(rhs)
  406. try:
  407. # Try to see if regular Python syntax will work. This
  408. # won't handle strings as the quote marks are removed
  409. # by the system shell.
  410. value = literal_eval(rhs)
  411. except (NameError, SyntaxError, ValueError):
  412. # This case happens if the rhs is a string.
  413. value = rhs
  414. exec(u'self.config.%s = value' % lhs)
  415. def _load_flag(self, cfg):
  416. """update self.config from a flag, which can be a dict or Config"""
  417. if isinstance(cfg, (dict, Config)):
  418. # don't clobber whole config sections, update
  419. # each section from config:
  420. for sec,c in cfg.items():
  421. self.config[sec].update(c)
  422. else:
  423. raise TypeError("Invalid flag: %r" % cfg)
  424. # raw --identifier=value pattern
  425. # but *also* accept '-' as wordsep, for aliases
  426. # accepts: --foo=a
  427. # --Class.trait=value
  428. # --alias-name=value
  429. # rejects: -foo=value
  430. # --foo
  431. # --Class.trait
  432. kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
  433. # just flags, no assignments, with two *or one* leading '-'
  434. # accepts: --foo
  435. # -foo-bar-again
  436. # rejects: --anything=anything
  437. # --two.word
  438. flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
  439. class KeyValueConfigLoader(CommandLineConfigLoader):
  440. """A config loader that loads key value pairs from the command line.
  441. This allows command line options to be gives in the following form::
  442. ipython --profile="foo" --InteractiveShell.autocall=False
  443. """
  444. def __init__(self, argv=None, aliases=None, flags=None, **kw):
  445. """Create a key value pair config loader.
  446. Parameters
  447. ----------
  448. argv : list
  449. A list that has the form of sys.argv[1:] which has unicode
  450. elements of the form u"key=value". If this is None (default),
  451. then sys.argv[1:] will be used.
  452. aliases : dict
  453. A dict of aliases for configurable traits.
  454. Keys are the short aliases, Values are the resolved trait.
  455. Of the form: `{'alias' : 'Configurable.trait'}`
  456. flags : dict
  457. A dict of flags, keyed by str name. Vaues can be Config objects,
  458. dicts, or "key=value" strings. If Config or dict, when the flag
  459. is triggered, The flag is loaded as `self.config.update(m)`.
  460. Returns
  461. -------
  462. config : Config
  463. The resulting Config object.
  464. Examples
  465. --------
  466. >>> from traitlets.config.loader import KeyValueConfigLoader
  467. >>> cl = KeyValueConfigLoader()
  468. >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
  469. >>> sorted(d.items())
  470. [('A', {'name': 'brian'}), ('B', {'number': 0})]
  471. """
  472. super(KeyValueConfigLoader, self).__init__(**kw)
  473. if argv is None:
  474. argv = sys.argv[1:]
  475. self.argv = argv
  476. self.aliases = aliases or {}
  477. self.flags = flags or {}
  478. def clear(self):
  479. super(KeyValueConfigLoader, self).clear()
  480. self.extra_args = []
  481. def _decode_argv(self, argv, enc=None):
  482. """decode argv if bytes, using stdin.encoding, falling back on default enc"""
  483. uargv = []
  484. if enc is None:
  485. enc = DEFAULT_ENCODING
  486. for arg in argv:
  487. if not isinstance(arg, text_type):
  488. # only decode if not already decoded
  489. arg = arg.decode(enc)
  490. uargv.append(arg)
  491. return uargv
  492. def load_config(self, argv=None, aliases=None, flags=None):
  493. """Parse the configuration and generate the Config object.
  494. After loading, any arguments that are not key-value or
  495. flags will be stored in self.extra_args - a list of
  496. unparsed command-line arguments. This is used for
  497. arguments such as input files or subcommands.
  498. Parameters
  499. ----------
  500. argv : list, optional
  501. A list that has the form of sys.argv[1:] which has unicode
  502. elements of the form u"key=value". If this is None (default),
  503. then self.argv will be used.
  504. aliases : dict
  505. A dict of aliases for configurable traits.
  506. Keys are the short aliases, Values are the resolved trait.
  507. Of the form: `{'alias' : 'Configurable.trait'}`
  508. flags : dict
  509. A dict of flags, keyed by str name. Values can be Config objects
  510. or dicts. When the flag is triggered, The config is loaded as
  511. `self.config.update(cfg)`.
  512. """
  513. self.clear()
  514. if argv is None:
  515. argv = self.argv
  516. if aliases is None:
  517. aliases = self.aliases
  518. if flags is None:
  519. flags = self.flags
  520. # ensure argv is a list of unicode strings:
  521. uargv = self._decode_argv(argv)
  522. for idx,raw in enumerate(uargv):
  523. # strip leading '-'
  524. item = raw.lstrip('-')
  525. if raw == '--':
  526. # don't parse arguments after '--'
  527. # this is useful for relaying arguments to scripts, e.g.
  528. # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
  529. self.extra_args.extend(uargv[idx+1:])
  530. break
  531. if kv_pattern.match(raw):
  532. lhs,rhs = item.split('=',1)
  533. # Substitute longnames for aliases.
  534. if lhs in aliases:
  535. lhs = aliases[lhs]
  536. if '.' not in lhs:
  537. # probably a mistyped alias, but not technically illegal
  538. self.log.warning("Unrecognized alias: '%s', it will probably have no effect.", raw)
  539. try:
  540. self._exec_config_str(lhs, rhs)
  541. except Exception:
  542. raise ArgumentError("Invalid argument: '%s'" % raw)
  543. elif flag_pattern.match(raw):
  544. if item in flags:
  545. cfg,help = flags[item]
  546. self._load_flag(cfg)
  547. else:
  548. raise ArgumentError("Unrecognized flag: '%s'"%raw)
  549. elif raw.startswith('-'):
  550. kv = '--'+item
  551. if kv_pattern.match(kv):
  552. raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
  553. else:
  554. raise ArgumentError("Invalid argument: '%s'"%raw)
  555. else:
  556. # keep all args that aren't valid in a list,
  557. # in case our parent knows what to do with them.
  558. self.extra_args.append(item)
  559. return self.config
  560. class ArgParseConfigLoader(CommandLineConfigLoader):
  561. """A loader that uses the argparse module to load from the command line."""
  562. def __init__(self, argv=None, aliases=None, flags=None, log=None, *parser_args, **parser_kw):
  563. """Create a config loader for use with argparse.
  564. Parameters
  565. ----------
  566. argv : optional, list
  567. If given, used to read command-line arguments from, otherwise
  568. sys.argv[1:] is used.
  569. parser_args : tuple
  570. A tuple of positional arguments that will be passed to the
  571. constructor of :class:`argparse.ArgumentParser`.
  572. parser_kw : dict
  573. A tuple of keyword arguments that will be passed to the
  574. constructor of :class:`argparse.ArgumentParser`.
  575. Returns
  576. -------
  577. config : Config
  578. The resulting Config object.
  579. """
  580. super(CommandLineConfigLoader, self).__init__(log=log)
  581. self.clear()
  582. if argv is None:
  583. argv = sys.argv[1:]
  584. self.argv = argv
  585. self.aliases = aliases or {}
  586. self.flags = flags or {}
  587. self.parser_args = parser_args
  588. self.version = parser_kw.pop("version", None)
  589. kwargs = dict(argument_default=argparse.SUPPRESS)
  590. kwargs.update(parser_kw)
  591. self.parser_kw = kwargs
  592. def load_config(self, argv=None, aliases=None, flags=None):
  593. """Parse command line arguments and return as a Config object.
  594. Parameters
  595. ----------
  596. args : optional, list
  597. If given, a list with the structure of sys.argv[1:] to parse
  598. arguments from. If not given, the instance's self.argv attribute
  599. (given at construction time) is used."""
  600. self.clear()
  601. if argv is None:
  602. argv = self.argv
  603. if aliases is None:
  604. aliases = self.aliases
  605. if flags is None:
  606. flags = self.flags
  607. self._create_parser(aliases, flags)
  608. self._parse_args(argv)
  609. self._convert_to_config()
  610. return self.config
  611. def get_extra_args(self):
  612. if hasattr(self, 'extra_args'):
  613. return self.extra_args
  614. else:
  615. return []
  616. def _create_parser(self, aliases=None, flags=None):
  617. self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
  618. self._add_arguments(aliases, flags)
  619. def _add_arguments(self, aliases=None, flags=None):
  620. raise NotImplementedError("subclasses must implement _add_arguments")
  621. def _parse_args(self, args):
  622. """self.parser->self.parsed_data"""
  623. # decode sys.argv to support unicode command-line options
  624. enc = DEFAULT_ENCODING
  625. uargs = [py3compat.cast_unicode(a, enc) for a in args]
  626. self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
  627. def _convert_to_config(self):
  628. """self.parsed_data->self.config"""
  629. for k, v in vars(self.parsed_data).items():
  630. exec("self.config.%s = v"%k, locals(), globals())
  631. class KVArgParseConfigLoader(ArgParseConfigLoader):
  632. """A config loader that loads aliases and flags with argparse,
  633. but will use KVLoader for the rest. This allows better parsing
  634. of common args, such as `ipython -c 'print 5'`, but still gets
  635. arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
  636. def _add_arguments(self, aliases=None, flags=None):
  637. self.alias_flags = {}
  638. # print aliases, flags
  639. if aliases is None:
  640. aliases = self.aliases
  641. if flags is None:
  642. flags = self.flags
  643. paa = self.parser.add_argument
  644. for key,value in aliases.items():
  645. if key in flags:
  646. # flags
  647. nargs = '?'
  648. else:
  649. nargs = None
  650. if len(key) is 1:
  651. paa('-'+key, '--'+key, type=text_type, dest=value, nargs=nargs)
  652. else:
  653. paa('--'+key, type=text_type, dest=value, nargs=nargs)
  654. for key, (value, help) in flags.items():
  655. if key in self.aliases:
  656. #
  657. self.alias_flags[self.aliases[key]] = value
  658. continue
  659. if len(key) is 1:
  660. paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
  661. else:
  662. paa('--'+key, action='append_const', dest='_flags', const=value)
  663. def _convert_to_config(self):
  664. """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
  665. # remove subconfigs list from namespace before transforming the Namespace
  666. if '_flags' in self.parsed_data:
  667. subcs = self.parsed_data._flags
  668. del self.parsed_data._flags
  669. else:
  670. subcs = []
  671. for k, v in vars(self.parsed_data).items():
  672. if v is None:
  673. # it was a flag that shares the name of an alias
  674. subcs.append(self.alias_flags[k])
  675. else:
  676. # eval the KV assignment
  677. self._exec_config_str(k, v)
  678. for subc in subcs:
  679. self._load_flag(subc)
  680. if self.extra_args:
  681. sub_parser = KeyValueConfigLoader(log=self.log)
  682. sub_parser.load_config(self.extra_args)
  683. self.config.merge(sub_parser.config)
  684. self.extra_args = sub_parser.extra_args
  685. def load_pyconfig_files(config_files, path):
  686. """Load multiple Python config files, merging each of them in turn.
  687. Parameters
  688. ==========
  689. config_files : list of str
  690. List of config files names to load and merge into the config.
  691. path : unicode
  692. The full path to the location of the config files.
  693. """
  694. config = Config()
  695. for cf in config_files:
  696. loader = PyFileConfigLoader(cf, path=path)
  697. try:
  698. next_config = loader.load_config()
  699. except ConfigFileNotFound:
  700. pass
  701. except:
  702. raise
  703. else:
  704. config.merge(next_config)
  705. return config