Show More
@@ -0,0 +1,3 | |||||
|
1 | * IPython config objects can be loaded from and serialized to JSON. | |||
|
2 | JSON config file have the same base name as their ``.py`` counterpart, | |||
|
3 | and will be loaded with higher priority if found. |
@@ -31,7 +31,7 from IPython.external.decorator import decorator | |||||
31 |
|
31 | |||
32 | from IPython.config.configurable import SingletonConfigurable |
|
32 | from IPython.config.configurable import SingletonConfigurable | |
33 | from IPython.config.loader import ( |
|
33 | from IPython.config.loader import ( | |
34 | KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, |
|
34 | KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader | |
35 | ) |
|
35 | ) | |
36 |
|
36 | |||
37 | from IPython.utils.traitlets import ( |
|
37 | from IPython.utils.traitlets import ( | |
@@ -492,34 +492,52 class Application(SingletonConfigurable): | |||||
492 |
|
492 | |||
493 | # flatten flags&aliases, so cl-args get appropriate priority: |
|
493 | # flatten flags&aliases, so cl-args get appropriate priority: | |
494 | flags,aliases = self.flatten_flags() |
|
494 | flags,aliases = self.flatten_flags() | |
495 |
|
||||
496 | loader = KVArgParseConfigLoader(argv=argv, aliases=aliases, |
|
495 | loader = KVArgParseConfigLoader(argv=argv, aliases=aliases, | |
497 | flags=flags) |
|
496 | flags=flags, log=self.log) | |
498 | config = loader.load_config() |
|
497 | config = loader.load_config() | |
499 | self.update_config(config) |
|
498 | self.update_config(config) | |
500 | # store unparsed args in extra_args |
|
499 | # store unparsed args in extra_args | |
501 | self.extra_args = loader.extra_args |
|
500 | self.extra_args = loader.extra_args | |
502 |
|
501 | |||
|
502 | @classmethod | |||
|
503 | def _load_config_file(cls, basefilename, path=None, log=None): | |||
|
504 | """Load config files (json/py) by filename and path.""" | |||
|
505 | ||||
|
506 | pyloader = PyFileConfigLoader(basefilename+'.py', path=path, log=log) | |||
|
507 | jsonloader = JSONFileConfigLoader(basefilename+'.json', path=path, log=log) | |||
|
508 | config_found = False | |||
|
509 | config = None | |||
|
510 | for loader in [pyloader, jsonloader]: | |||
|
511 | try: | |||
|
512 | config = loader.load_config() | |||
|
513 | config_found = True | |||
|
514 | except ConfigFileNotFound: | |||
|
515 | pass | |||
|
516 | except Exception: | |||
|
517 | # try to get the full filename, but it will be empty in the | |||
|
518 | # unlikely event that the error raised before filefind finished | |||
|
519 | filename = loader.full_filename or filename | |||
|
520 | # problem while running the file | |||
|
521 | log.error("Exception while loading config file %s", | |||
|
522 | filename, exc_info=True) | |||
|
523 | else: | |||
|
524 | log.debug("Loaded config file: %s", loader.full_filename) | |||
|
525 | if config : | |||
|
526 | yield config | |||
|
527 | ||||
|
528 | if not config_found : | |||
|
529 | raise ConfigFileNotFound('Neither .json, not .py file found.') | |||
|
530 | raise StopIteration | |||
|
531 | ||||
|
532 | ||||
503 | @catch_config_error |
|
533 | @catch_config_error | |
504 | def load_config_file(self, filename, path=None): |
|
534 | def load_config_file(self, filename, path=None): | |
505 |
"""Load |
|
535 | """Load config files (json/py) by filename and path.""" | |
506 | loader = PyFileConfigLoader(filename, path=path) |
|
536 | filename, ext = os.path.splitext(filename) | |
507 | try: |
|
537 | for config in self._load_config_file(filename, path=path , log=self.log): | |
508 | config = loader.load_config() |
|
|||
509 | except ConfigFileNotFound: |
|
|||
510 | # problem finding the file, raise |
|
|||
511 | raise |
|
|||
512 | except Exception: |
|
|||
513 | # try to get the full filename, but it will be empty in the |
|
|||
514 | # unlikely event that the error raised before filefind finished |
|
|||
515 | filename = loader.full_filename or filename |
|
|||
516 | # problem while running the file |
|
|||
517 | self.log.error("Exception while loading config file %s", |
|
|||
518 | filename, exc_info=True) |
|
|||
519 | else: |
|
|||
520 | self.log.debug("Loaded config file: %s", loader.full_filename) |
|
|||
521 | self.update_config(config) |
|
538 | self.update_config(config) | |
522 |
|
539 | |||
|
540 | ||||
523 | def generate_config_file(self): |
|
541 | def generate_config_file(self): | |
524 | """generate default config file from Configurables""" |
|
542 | """generate default config file from Configurables""" | |
525 | lines = ["# Configuration file for %s."%self.name] |
|
543 | lines = ["# Configuration file for %s."%self.name] |
@@ -28,9 +28,10 import copy | |||||
28 | import os |
|
28 | import os | |
29 | import re |
|
29 | import re | |
30 | import sys |
|
30 | import sys | |
|
31 | import json | |||
31 |
|
32 | |||
32 | from IPython.utils.path import filefind, get_ipython_dir |
|
33 | from IPython.utils.path import filefind, get_ipython_dir | |
33 |
from IPython.utils import py3compat |
|
34 | from IPython.utils import py3compat | |
34 | from IPython.utils.encoding import DEFAULT_ENCODING |
|
35 | from IPython.utils.encoding import DEFAULT_ENCODING | |
35 | from IPython.utils.py3compat import unicode_type, iteritems |
|
36 | from IPython.utils.py3compat import unicode_type, iteritems | |
36 | from IPython.utils.traitlets import HasTraits, List, Any, TraitError |
|
37 | from IPython.utils.traitlets import HasTraits, List, Any, TraitError | |
@@ -303,9 +304,17 class ConfigLoader(object): | |||||
303 | handled elsewhere. |
|
304 | handled elsewhere. | |
304 | """ |
|
305 | """ | |
305 |
|
306 | |||
306 |
def _ |
|
307 | def _log_default(self): | |
|
308 | from IPython.config.application import Application | |||
|
309 | return Application.instance().log | |||
|
310 | ||||
|
311 | def __init__(self, log=None): | |||
307 | """A base class for config loaders. |
|
312 | """A base class for config loaders. | |
308 |
|
313 | |||
|
314 | log : instance of :class:`logging.Logger` to use. | |||
|
315 | By default loger of :meth:`IPython.config.application.Application.instance()` | |||
|
316 | will be used | |||
|
317 | ||||
309 | Examples |
|
318 | Examples | |
310 | -------- |
|
319 | -------- | |
311 |
|
320 | |||
@@ -315,6 +324,11 class ConfigLoader(object): | |||||
315 | {} |
|
324 | {} | |
316 | """ |
|
325 | """ | |
317 | self.clear() |
|
326 | self.clear() | |
|
327 | if log is None : | |||
|
328 | self.log = self._log_default() | |||
|
329 | self.log.debug('Using default logger') | |||
|
330 | else : | |||
|
331 | self.log = log | |||
318 |
|
332 | |||
319 | def clear(self): |
|
333 | def clear(self): | |
320 | self.config = Config() |
|
334 | self.config = Config() | |
@@ -336,17 +350,8 class FileConfigLoader(ConfigLoader): | |||||
336 | As we add more file based config loaders, the common logic should go |
|
350 | As we add more file based config loaders, the common logic should go | |
337 | here. |
|
351 | here. | |
338 | """ |
|
352 | """ | |
339 | pass |
|
|||
340 |
|
||||
341 |
|
||||
342 | class PyFileConfigLoader(FileConfigLoader): |
|
|||
343 | """A config loader for pure python files. |
|
|||
344 |
|
||||
345 | This calls execfile on a plain python file and looks for attributes |
|
|||
346 | that are all caps. These attribute are added to the config Struct. |
|
|||
347 | """ |
|
|||
348 |
|
353 | |||
349 | def __init__(self, filename, path=None): |
|
354 | def __init__(self, filename, path=None, **kw): | |
350 | """Build a config loader for a filename and path. |
|
355 | """Build a config loader for a filename and path. | |
351 |
|
356 | |||
352 | Parameters |
|
357 | Parameters | |
@@ -357,11 +362,53 class PyFileConfigLoader(FileConfigLoader): | |||||
357 | The path to search for the config file on, or a sequence of |
|
362 | The path to search for the config file on, or a sequence of | |
358 | paths to try in order. |
|
363 | paths to try in order. | |
359 | """ |
|
364 | """ | |
360 |
super( |
|
365 | super(FileConfigLoader, self).__init__(**kw) | |
361 | self.filename = filename |
|
366 | self.filename = filename | |
362 | self.path = path |
|
367 | self.path = path | |
363 | self.full_filename = '' |
|
368 | self.full_filename = '' | |
364 | self.data = None |
|
369 | ||
|
370 | def _find_file(self): | |||
|
371 | """Try to find the file by searching the paths.""" | |||
|
372 | self.full_filename = filefind(self.filename, self.path) | |||
|
373 | ||||
|
374 | class JSONFileConfigLoader(FileConfigLoader): | |||
|
375 | """A Json file loader for config""" | |||
|
376 | ||||
|
377 | def load_config(self): | |||
|
378 | """Load the config from a file and return it as a Struct.""" | |||
|
379 | self.clear() | |||
|
380 | try: | |||
|
381 | self._find_file() | |||
|
382 | except IOError as e: | |||
|
383 | raise ConfigFileNotFound(str(e)) | |||
|
384 | dct = self._read_file_as_dict() | |||
|
385 | self.config = self._convert_to_config(dct) | |||
|
386 | return self.config | |||
|
387 | ||||
|
388 | def _read_file_as_dict(self): | |||
|
389 | with open(self.full_filename) as f : | |||
|
390 | return json.load(f) | |||
|
391 | ||||
|
392 | def _convert_to_config(self, dictionary): | |||
|
393 | if 'version' in dictionary: | |||
|
394 | version = dictionary.pop('version') | |||
|
395 | else : | |||
|
396 | version = 1 | |||
|
397 | self.log.warn("Unrecognized JSON config file version, assuming version : {}".format(version)) | |||
|
398 | ||||
|
399 | if version == 1: | |||
|
400 | return Config(dictionary) | |||
|
401 | else : | |||
|
402 | raise ValueError('Unknown version of JSON config file : version number {version}'.format(version=version)) | |||
|
403 | ||||
|
404 | ||||
|
405 | class PyFileConfigLoader(FileConfigLoader): | |||
|
406 | """A config loader for pure python files. | |||
|
407 | ||||
|
408 | This is responsible for locating a Python config file by filename and | |||
|
409 | profile name, then executing it in a namespace where it could have access | |||
|
410 | to subconfigs. | |||
|
411 | """ | |||
365 |
|
412 | |||
366 | def load_config(self): |
|
413 | def load_config(self): | |
367 | """Load the config from a file and return it as a Struct.""" |
|
414 | """Load the config from a file and return it as a Struct.""" | |
@@ -371,12 +418,8 class PyFileConfigLoader(FileConfigLoader): | |||||
371 | except IOError as e: |
|
418 | except IOError as e: | |
372 | raise ConfigFileNotFound(str(e)) |
|
419 | raise ConfigFileNotFound(str(e)) | |
373 | self._read_file_as_dict() |
|
420 | self._read_file_as_dict() | |
374 | self._convert_to_config() |
|
|||
375 | return self.config |
|
421 | return self.config | |
376 |
|
422 | |||
377 | def _find_file(self): |
|
|||
378 | """Try to find the file by searching the paths.""" |
|
|||
379 | self.full_filename = filefind(self.filename, self.path) |
|
|||
380 |
|
423 | |||
381 | def _read_file_as_dict(self): |
|
424 | def _read_file_as_dict(self): | |
382 | """Load the config file into self.config, with recursive loading.""" |
|
425 | """Load the config file into self.config, with recursive loading.""" | |
@@ -429,10 +472,6 class PyFileConfigLoader(FileConfigLoader): | |||||
429 | conf_filename = self.full_filename.encode(fs_encoding) |
|
472 | conf_filename = self.full_filename.encode(fs_encoding) | |
430 | py3compat.execfile(conf_filename, namespace) |
|
473 | py3compat.execfile(conf_filename, namespace) | |
431 |
|
474 | |||
432 | def _convert_to_config(self): |
|
|||
433 | if self.data is None: |
|
|||
434 | ConfigLoaderError('self.data does not exist') |
|
|||
435 |
|
||||
436 |
|
475 | |||
437 | class CommandLineConfigLoader(ConfigLoader): |
|
476 | class CommandLineConfigLoader(ConfigLoader): | |
438 | """A config loader for command line arguments. |
|
477 | """A config loader for command line arguments. | |
@@ -497,7 +536,7 class KeyValueConfigLoader(CommandLineConfigLoader): | |||||
497 | ipython --profile="foo" --InteractiveShell.autocall=False |
|
536 | ipython --profile="foo" --InteractiveShell.autocall=False | |
498 | """ |
|
537 | """ | |
499 |
|
538 | |||
500 | def __init__(self, argv=None, aliases=None, flags=None): |
|
539 | def __init__(self, argv=None, aliases=None, flags=None, **kw): | |
501 | """Create a key value pair config loader. |
|
540 | """Create a key value pair config loader. | |
502 |
|
541 | |||
503 | Parameters |
|
542 | Parameters | |
@@ -529,7 +568,7 class KeyValueConfigLoader(CommandLineConfigLoader): | |||||
529 | >>> sorted(d.items()) |
|
568 | >>> sorted(d.items()) | |
530 | [('A', {'name': 'brian'}), ('B', {'number': 0})] |
|
569 | [('A', {'name': 'brian'}), ('B', {'number': 0})] | |
531 | """ |
|
570 | """ | |
532 | self.clear() |
|
571 | super(KeyValueConfigLoader, self).__init__(**kw) | |
533 | if argv is None: |
|
572 | if argv is None: | |
534 | argv = sys.argv[1:] |
|
573 | argv = sys.argv[1:] | |
535 | self.argv = argv |
|
574 | self.argv = argv | |
@@ -606,7 +645,7 class KeyValueConfigLoader(CommandLineConfigLoader): | |||||
606 | lhs = aliases[lhs] |
|
645 | lhs = aliases[lhs] | |
607 | if '.' not in lhs: |
|
646 | if '.' not in lhs: | |
608 | # probably a mistyped alias, but not technically illegal |
|
647 | # probably a mistyped alias, but not technically illegal | |
609 |
|
|
648 | self.log.warn("Unrecognized alias: '%s', it will probably have no effect. %s,-- %s"%(lhs,raw, aliases)) | |
610 | try: |
|
649 | try: | |
611 | self._exec_config_str(lhs, rhs) |
|
650 | self._exec_config_str(lhs, rhs) | |
612 | except Exception: |
|
651 | except Exception: | |
@@ -633,7 +672,7 class KeyValueConfigLoader(CommandLineConfigLoader): | |||||
633 | class ArgParseConfigLoader(CommandLineConfigLoader): |
|
672 | class ArgParseConfigLoader(CommandLineConfigLoader): | |
634 | """A loader that uses the argparse module to load from the command line.""" |
|
673 | """A loader that uses the argparse module to load from the command line.""" | |
635 |
|
674 | |||
636 | def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw): |
|
675 | def __init__(self, argv=None, aliases=None, flags=None, log=None, *parser_args, **parser_kw): | |
637 | """Create a config loader for use with argparse. |
|
676 | """Create a config loader for use with argparse. | |
638 |
|
677 | |||
639 | Parameters |
|
678 | Parameters | |
@@ -656,7 +695,7 class ArgParseConfigLoader(CommandLineConfigLoader): | |||||
656 | config : Config |
|
695 | config : Config | |
657 | The resulting Config object. |
|
696 | The resulting Config object. | |
658 | """ |
|
697 | """ | |
659 | super(CommandLineConfigLoader, self).__init__() |
|
698 | super(CommandLineConfigLoader, self).__init__(log=log) | |
660 | self.clear() |
|
699 | self.clear() | |
661 | if argv is None: |
|
700 | if argv is None: | |
662 | argv = sys.argv[1:] |
|
701 | argv = sys.argv[1:] | |
@@ -772,7 +811,7 class KVArgParseConfigLoader(ArgParseConfigLoader): | |||||
772 | self._load_flag(subc) |
|
811 | self._load_flag(subc) | |
773 |
|
812 | |||
774 | if self.extra_args: |
|
813 | if self.extra_args: | |
775 | sub_parser = KeyValueConfigLoader() |
|
814 | sub_parser = KeyValueConfigLoader(log=self.log) | |
776 | sub_parser.load_config(self.extra_args) |
|
815 | sub_parser.load_config(self.extra_args) | |
777 | self.config.merge(sub_parser.config) |
|
816 | self.config.merge(sub_parser.config) | |
778 | self.extra_args = sub_parser.extra_args |
|
817 | self.extra_args = sub_parser.extra_args |
@@ -22,17 +22,21 Authors: | |||||
22 | import os |
|
22 | import os | |
23 | import pickle |
|
23 | import pickle | |
24 | import sys |
|
24 | import sys | |
|
25 | import json | |||
|
26 | ||||
25 | from tempfile import mkstemp |
|
27 | from tempfile import mkstemp | |
26 | from unittest import TestCase |
|
28 | from unittest import TestCase | |
27 |
|
29 | |||
28 | from nose import SkipTest |
|
30 | from nose import SkipTest | |
|
31 | import nose.tools as nt | |||
|
32 | ||||
29 |
|
33 | |||
30 | from IPython.testing.tools import mute_warn |
|
|||
31 |
|
34 | |||
32 | from IPython.config.loader import ( |
|
35 | from IPython.config.loader import ( | |
33 | Config, |
|
36 | Config, | |
34 | LazyConfigValue, |
|
37 | LazyConfigValue, | |
35 | PyFileConfigLoader, |
|
38 | PyFileConfigLoader, | |
|
39 | JSONFileConfigLoader, | |||
36 | KeyValueConfigLoader, |
|
40 | KeyValueConfigLoader, | |
37 | ArgParseConfigLoader, |
|
41 | ArgParseConfigLoader, | |
38 | KVArgParseConfigLoader, |
|
42 | KVArgParseConfigLoader, | |
@@ -53,21 +57,77 c.Foo.Bam.value=list(range(10)) # list() is just so it's the same on Python 3 | |||||
53 | c.D.C.value='hi there' |
|
57 | c.D.C.value='hi there' | |
54 | """ |
|
58 | """ | |
55 |
|
59 | |||
56 | class TestPyFileCL(TestCase): |
|
60 | json1file = """ | |
|
61 | { | |||
|
62 | "version": 1, | |||
|
63 | "a": 10, | |||
|
64 | "b": 20, | |||
|
65 | "Foo": { | |||
|
66 | "Bam": { | |||
|
67 | "value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] | |||
|
68 | }, | |||
|
69 | "Bar": { | |||
|
70 | "value": 10 | |||
|
71 | } | |||
|
72 | }, | |||
|
73 | "D": { | |||
|
74 | "C": { | |||
|
75 | "value": "hi there" | |||
|
76 | } | |||
|
77 | } | |||
|
78 | } | |||
|
79 | """ | |||
57 |
|
80 | |||
58 | def test_basic(self): |
|
81 | # should not load | |
|
82 | json2file = """ | |||
|
83 | { | |||
|
84 | "version": 2 | |||
|
85 | } | |||
|
86 | """ | |||
|
87 | ||||
|
88 | import logging | |||
|
89 | log = logging.getLogger('devnull') | |||
|
90 | log.setLevel(0) | |||
|
91 | ||||
|
92 | class TestFileCL(TestCase): | |||
|
93 | ||||
|
94 | def _check_conf(self, config): | |||
|
95 | self.assertEqual(config.a, 10) | |||
|
96 | self.assertEqual(config.b, 20) | |||
|
97 | self.assertEqual(config.Foo.Bar.value, 10) | |||
|
98 | self.assertEqual(config.Foo.Bam.value, list(range(10))) | |||
|
99 | self.assertEqual(config.D.C.value, 'hi there') | |||
|
100 | ||||
|
101 | def test_python(self): | |||
59 | fd, fname = mkstemp('.py') |
|
102 | fd, fname = mkstemp('.py') | |
60 | f = os.fdopen(fd, 'w') |
|
103 | f = os.fdopen(fd, 'w') | |
61 | f.write(pyfile) |
|
104 | f.write(pyfile) | |
62 | f.close() |
|
105 | f.close() | |
63 | # Unlink the file |
|
106 | # Unlink the file | |
64 | cl = PyFileConfigLoader(fname) |
|
107 | cl = PyFileConfigLoader(fname, log=log) | |
65 | config = cl.load_config() |
|
108 | config = cl.load_config() | |
66 |
self. |
|
109 | self._check_conf(config) | |
67 | self.assertEqual(config.b, 20) |
|
110 | ||
68 | self.assertEqual(config.Foo.Bar.value, 10) |
|
111 | def test_json(self): | |
69 | self.assertEqual(config.Foo.Bam.value, list(range(10))) |
|
112 | fd, fname = mkstemp('.json') | |
70 | self.assertEqual(config.D.C.value, 'hi there') |
|
113 | f = os.fdopen(fd, 'w') | |
|
114 | f.write(json1file) | |||
|
115 | f.close() | |||
|
116 | # Unlink the file | |||
|
117 | cl = JSONFileConfigLoader(fname, log=log) | |||
|
118 | config = cl.load_config() | |||
|
119 | self._check_conf(config) | |||
|
120 | ||||
|
121 | def test_v2raise(self): | |||
|
122 | fd, fname = mkstemp('.json') | |||
|
123 | f = os.fdopen(fd, 'w') | |||
|
124 | f.write(json2file) | |||
|
125 | f.close() | |||
|
126 | # Unlink the file | |||
|
127 | cl = JSONFileConfigLoader(fname, log=log) | |||
|
128 | with nt.assert_raises(ValueError): | |||
|
129 | cl.load_config() | |||
|
130 | ||||
71 |
|
131 | |||
72 | class MyLoader1(ArgParseConfigLoader): |
|
132 | class MyLoader1(ArgParseConfigLoader): | |
73 | def _add_arguments(self, aliases=None, flags=None): |
|
133 | def _add_arguments(self, aliases=None, flags=None): | |
@@ -121,10 +181,9 class TestKeyValueCL(TestCase): | |||||
121 | klass = KeyValueConfigLoader |
|
181 | klass = KeyValueConfigLoader | |
122 |
|
182 | |||
123 | def test_basic(self): |
|
183 | def test_basic(self): | |
124 | cl = self.klass() |
|
184 | cl = self.klass(log=log) | |
125 | argv = ['--'+s.strip('c.') for s in pyfile.split('\n')[2:-1]] |
|
185 | argv = ['--'+s.strip('c.') for s in pyfile.split('\n')[2:-1]] | |
126 | with mute_warn(): |
|
186 | config = cl.load_config(argv) | |
127 | config = cl.load_config(argv) |
|
|||
128 | self.assertEqual(config.a, 10) |
|
187 | self.assertEqual(config.a, 10) | |
129 | self.assertEqual(config.b, 20) |
|
188 | self.assertEqual(config.b, 20) | |
130 | self.assertEqual(config.Foo.Bar.value, 10) |
|
189 | self.assertEqual(config.Foo.Bar.value, 10) | |
@@ -132,31 +191,27 class TestKeyValueCL(TestCase): | |||||
132 | self.assertEqual(config.D.C.value, 'hi there') |
|
191 | self.assertEqual(config.D.C.value, 'hi there') | |
133 |
|
192 | |||
134 | def test_expanduser(self): |
|
193 | def test_expanduser(self): | |
135 | cl = self.klass() |
|
194 | cl = self.klass(log=log) | |
136 | argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"'] |
|
195 | argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"'] | |
137 | with mute_warn(): |
|
196 | config = cl.load_config(argv) | |
138 | config = cl.load_config(argv) |
|
|||
139 | self.assertEqual(config.a, os.path.expanduser('~/1/2/3')) |
|
197 | self.assertEqual(config.a, os.path.expanduser('~/1/2/3')) | |
140 | self.assertEqual(config.b, os.path.expanduser('~')) |
|
198 | self.assertEqual(config.b, os.path.expanduser('~')) | |
141 | self.assertEqual(config.c, os.path.expanduser('~/')) |
|
199 | self.assertEqual(config.c, os.path.expanduser('~/')) | |
142 | self.assertEqual(config.d, '~/') |
|
200 | self.assertEqual(config.d, '~/') | |
143 |
|
201 | |||
144 | def test_extra_args(self): |
|
202 | def test_extra_args(self): | |
145 | cl = self.klass() |
|
203 | cl = self.klass(log=log) | |
146 | with mute_warn(): |
|
204 | config = cl.load_config(['--a=5', 'b', '--c=10', 'd']) | |
147 | config = cl.load_config(['--a=5', 'b', '--c=10', 'd']) |
|
|||
148 | self.assertEqual(cl.extra_args, ['b', 'd']) |
|
205 | self.assertEqual(cl.extra_args, ['b', 'd']) | |
149 | self.assertEqual(config.a, 5) |
|
206 | self.assertEqual(config.a, 5) | |
150 | self.assertEqual(config.c, 10) |
|
207 | self.assertEqual(config.c, 10) | |
151 | with mute_warn(): |
|
208 | config = cl.load_config(['--', '--a=5', '--c=10']) | |
152 | config = cl.load_config(['--', '--a=5', '--c=10']) |
|
|||
153 | self.assertEqual(cl.extra_args, ['--a=5', '--c=10']) |
|
209 | self.assertEqual(cl.extra_args, ['--a=5', '--c=10']) | |
154 |
|
210 | |||
155 | def test_unicode_args(self): |
|
211 | def test_unicode_args(self): | |
156 | cl = self.klass() |
|
212 | cl = self.klass(log=log) | |
157 | argv = [u'--a=épsîlön'] |
|
213 | argv = [u'--a=épsîlön'] | |
158 | with mute_warn(): |
|
214 | config = cl.load_config(argv) | |
159 | config = cl.load_config(argv) |
|
|||
160 | self.assertEqual(config.a, u'épsîlön') |
|
215 | self.assertEqual(config.a, u'épsîlön') | |
161 |
|
216 | |||
162 | def test_unicode_bytes_args(self): |
|
217 | def test_unicode_bytes_args(self): | |
@@ -166,16 +221,14 class TestKeyValueCL(TestCase): | |||||
166 | except (TypeError, UnicodeEncodeError): |
|
221 | except (TypeError, UnicodeEncodeError): | |
167 | raise SkipTest("sys.stdin.encoding can't handle 'é'") |
|
222 | raise SkipTest("sys.stdin.encoding can't handle 'é'") | |
168 |
|
223 | |||
169 | cl = self.klass() |
|
224 | cl = self.klass(log=log) | |
170 | with mute_warn(): |
|
225 | config = cl.load_config([barg]) | |
171 | config = cl.load_config([barg]) |
|
|||
172 | self.assertEqual(config.a, u'é') |
|
226 | self.assertEqual(config.a, u'é') | |
173 |
|
227 | |||
174 | def test_unicode_alias(self): |
|
228 | def test_unicode_alias(self): | |
175 | cl = self.klass() |
|
229 | cl = self.klass(log=log) | |
176 | argv = [u'--a=épsîlön'] |
|
230 | argv = [u'--a=épsîlön'] | |
177 | with mute_warn(): |
|
231 | config = cl.load_config(argv, aliases=dict(a='A.a')) | |
178 | config = cl.load_config(argv, aliases=dict(a='A.a')) |
|
|||
179 | self.assertEqual(config.A.a, u'épsîlön') |
|
232 | self.assertEqual(config.A.a, u'épsîlön') | |
180 |
|
233 | |||
181 |
|
234 | |||
@@ -183,18 +236,16 class TestArgParseKVCL(TestKeyValueCL): | |||||
183 | klass = KVArgParseConfigLoader |
|
236 | klass = KVArgParseConfigLoader | |
184 |
|
237 | |||
185 | def test_expanduser2(self): |
|
238 | def test_expanduser2(self): | |
186 | cl = self.klass() |
|
239 | cl = self.klass(log=log) | |
187 | argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"] |
|
240 | argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"] | |
188 | with mute_warn(): |
|
241 | config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b')) | |
189 | config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b')) |
|
|||
190 | self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3')) |
|
242 | self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3')) | |
191 | self.assertEqual(config.A.b, '~/1/2/3') |
|
243 | self.assertEqual(config.A.b, '~/1/2/3') | |
192 |
|
244 | |||
193 | def test_eval(self): |
|
245 | def test_eval(self): | |
194 | cl = self.klass() |
|
246 | cl = self.klass(log=log) | |
195 | argv = ['-c', 'a=5'] |
|
247 | argv = ['-c', 'a=5'] | |
196 | with mute_warn(): |
|
248 | config = cl.load_config(argv, aliases=dict(c='A.c')) | |
197 | config = cl.load_config(argv, aliases=dict(c='A.c')) |
|
|||
198 | self.assertEqual(config.A.c, u"a=5") |
|
249 | self.assertEqual(config.A.c, u"a=5") | |
199 |
|
250 | |||
200 |
|
251 |
@@ -33,7 +33,7 import sys | |||||
33 | from IPython.config.loader import ( |
|
33 | from IPython.config.loader import ( | |
34 | Config, PyFileConfigLoader, ConfigFileNotFound |
|
34 | Config, PyFileConfigLoader, ConfigFileNotFound | |
35 | ) |
|
35 | ) | |
36 | from IPython.config.application import boolean_flag, catch_config_error |
|
36 | from IPython.config.application import boolean_flag, catch_config_error, Application | |
37 | from IPython.core import release |
|
37 | from IPython.core import release | |
38 | from IPython.core import usage |
|
38 | from IPython.core import usage | |
39 | from IPython.core.completer import IPCompleter |
|
39 | from IPython.core.completer import IPCompleter | |
@@ -364,7 +364,6 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): | |||||
364 | else: |
|
364 | else: | |
365 | self.log.debug("IPython not interactive...") |
|
365 | self.log.debug("IPython not interactive...") | |
366 |
|
366 | |||
367 |
|
||||
368 | def load_default_config(ipython_dir=None): |
|
367 | def load_default_config(ipython_dir=None): | |
369 | """Load the default config file from the default ipython_dir. |
|
368 | """Load the default config file from the default ipython_dir. | |
370 |
|
369 | |||
@@ -372,15 +371,14 def load_default_config(ipython_dir=None): | |||||
372 | """ |
|
371 | """ | |
373 | if ipython_dir is None: |
|
372 | if ipython_dir is None: | |
374 | ipython_dir = get_ipython_dir() |
|
373 | ipython_dir = get_ipython_dir() | |
|
374 | ||||
375 | profile_dir = os.path.join(ipython_dir, 'profile_default') |
|
375 | profile_dir = os.path.join(ipython_dir, 'profile_default') | |
376 | cl = PyFileConfigLoader("ipython_config.py", profile_dir) |
|
|||
377 | try: |
|
|||
378 | config = cl.load_config() |
|
|||
379 | except ConfigFileNotFound: |
|
|||
380 | # no config found |
|
|||
381 | config = Config() |
|
|||
382 | return config |
|
|||
383 |
|
376 | |||
|
377 | config = Config() | |||
|
378 | for cf in Application._load_config_file(filename[:-3], path=profile_dir, log=None): | |||
|
379 | config.update(cf) | |||
|
380 | ||||
|
381 | return config | |||
384 |
|
382 | |||
385 | launch_new_instance = TerminalIPythonApp.launch_instance |
|
383 | launch_new_instance = TerminalIPythonApp.launch_instance | |
386 |
|
384 |
@@ -15,10 +15,10 Each of these abstractions is represented by a Python class. | |||||
15 | Configuration object: :class:`~IPython.config.loader.Config` |
|
15 | Configuration object: :class:`~IPython.config.loader.Config` | |
16 | A configuration object is a simple dictionary-like class that holds |
|
16 | A configuration object is a simple dictionary-like class that holds | |
17 | configuration attributes and sub-configuration objects. These classes |
|
17 | configuration attributes and sub-configuration objects. These classes | |
18 | support dotted attribute style access (``Foo.bar``) in addition to the |
|
18 | support dotted attribute style access (``cfg.Foo.bar``) in addition to the | |
19 |
regular dictionary style access (``Foo['bar']``). |
|
19 | regular dictionary style access (``cfg['Foo']['bar']``). | |
20 | are smart. They know how to merge themselves with other configuration |
|
20 | The Config object is a wrapper around a simple dictionary with some convenience methods, | |
21 | objects and they automatically create sub-configuration objects. |
|
21 | such as merging and automatic section creation. | |
22 |
|
22 | |||
23 | Application: :class:`~IPython.config.application.Application` |
|
23 | Application: :class:`~IPython.config.application.Application` | |
24 | An application is a process that does a specific job. The most obvious |
|
24 | An application is a process that does a specific job. The most obvious | |
@@ -85,12 +85,24 Now, we show what our configuration objects and files look like. | |||||
85 | Configuration objects and files |
|
85 | Configuration objects and files | |
86 | =============================== |
|
86 | =============================== | |
87 |
|
87 | |||
88 | A configuration file is simply a pure Python file that sets the attributes |
|
88 | A configuration object is little more than a wrapper around a dictionary. | |
89 | of a global, pre-created configuration object. This configuration object is a |
|
89 | A configuration *file* is simply a mechanism for producing that object. | |
90 | :class:`~IPython.config.loader.Config` instance. While in a configuration |
|
90 | The main IPython configuration file is a plain Python script, | |
91 | file, to get a reference to this object, simply call the :func:`get_config` |
|
91 | which can perform extensive logic to populate the config object. | |
92 | function. We inject this function into the global namespace that the |
|
92 | IPython 2.0 introduces a JSON configuration file, | |
93 | configuration file is executed in. |
|
93 | which is just a direct JSON serialization of the config dictionary. | |
|
94 | The JSON format is easily processed by external software. | |||
|
95 | ||||
|
96 | When both Python and JSON configuration file are present, both will be loaded, | |||
|
97 | with JSON configuration having higher priority. | |||
|
98 | ||||
|
99 | Python configuration Files | |||
|
100 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
|
101 | ||||
|
102 | A Python configuration file is a pure Python file that populates a configuration object. | |||
|
103 | This configuration object is a :class:`~IPython.config.loader.Config` instance. | |||
|
104 | While in a configuration file, to get a reference to this object, simply call the :func:`get_config` | |||
|
105 | function, which is available in the global namespace of the script. | |||
94 |
|
106 | |||
95 | Here is an example of a super simple configuration file that does nothing:: |
|
107 | Here is an example of a super simple configuration file that does nothing:: | |
96 |
|
108 | |||
@@ -99,10 +111,11 Here is an example of a super simple configuration file that does nothing:: | |||||
99 | Once you get a reference to the configuration object, you simply set |
|
111 | Once you get a reference to the configuration object, you simply set | |
100 | attributes on it. All you have to know is: |
|
112 | attributes on it. All you have to know is: | |
101 |
|
113 | |||
102 |
* The name of |
|
114 | * The name of the class to configure. | |
|
115 | * The name of the attribute. | |||
103 | * The type of each attribute. |
|
116 | * The type of each attribute. | |
104 |
|
117 | |||
105 |
The answers to these |
|
118 | The answers to these questions are provided by the various | |
106 | :class:`~IPython.config.configurable.Configurable` subclasses that an |
|
119 | :class:`~IPython.config.configurable.Configurable` subclasses that an | |
107 | application uses. Let's look at how this would work for a simple configurable |
|
120 | application uses. Let's look at how this would work for a simple configurable | |
108 | subclass:: |
|
121 | subclass:: | |
@@ -118,7 +131,7 subclass:: | |||||
118 | # The rest of the class implementation would go here.. |
|
131 | # The rest of the class implementation would go here.. | |
119 |
|
132 | |||
120 | In this example, we see that :class:`MyClass` has three attributes, two |
|
133 | In this example, we see that :class:`MyClass` has three attributes, two | |
121 |
of |
|
134 | of (``name``, ``ranking``) can be configured. All of the attributes | |
122 | are given types and default values. If a :class:`MyClass` is instantiated, |
|
135 | are given types and default values. If a :class:`MyClass` is instantiated, | |
123 | but not configured, these default values will be used. But let's see how |
|
136 | but not configured, these default values will be used. But let's see how | |
124 | to configure this class in a configuration file:: |
|
137 | to configure this class in a configuration file:: | |
@@ -173,9 +186,38 attribute of ``c`` is not the actual class, but instead is another | |||||
173 | instance is dynamically created for that attribute. This allows deeply |
|
186 | instance is dynamically created for that attribute. This allows deeply | |
174 | hierarchical information created easily (``c.Foo.Bar.value``) on the fly. |
|
187 | hierarchical information created easily (``c.Foo.Bar.value``) on the fly. | |
175 |
|
188 | |||
|
189 | JSON configuration Files | |||
|
190 | ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
|
191 | ||||
|
192 | A JSON configuration file is simply a file that contain a | |||
|
193 | :class:`~IPython.config.loader.Config` dictionary serialized to JSON. | |||
|
194 | A JSON configuration file has the same base name as a Python configuration file, | |||
|
195 | just with a .json extension. | |||
|
196 | ||||
|
197 | Configuration described in previous section could be written as follow in a | |||
|
198 | JSON configuration file: | |||
|
199 | ||||
|
200 | .. sourcecode:: json | |||
|
201 | ||||
|
202 | { | |||
|
203 | "version": "1.0", | |||
|
204 | "MyClass": { | |||
|
205 | "name": "coolname", | |||
|
206 | "ranking": 10 | |||
|
207 | } | |||
|
208 | } | |||
|
209 | ||||
|
210 | JSON configuration files can be more easily generated or processed by programs | |||
|
211 | or other languages. | |||
|
212 | ||||
|
213 | ||||
176 | Configuration files inheritance |
|
214 | Configuration files inheritance | |
177 | =============================== |
|
215 | =============================== | |
178 |
|
216 | |||
|
217 | .. note:: | |||
|
218 | ||||
|
219 | This section only apply to Python configuration files. | |||
|
220 | ||||
179 | Let's say you want to have different configuration files for various purposes. |
|
221 | Let's say you want to have different configuration files for various purposes. | |
180 | Our configuration system makes it easy for one configuration file to inherit |
|
222 | Our configuration system makes it easy for one configuration file to inherit | |
181 | the information in another configuration file. The :func:`load_subconfig` |
|
223 | the information in another configuration file. The :func:`load_subconfig` |
General Comments 0
You need to be logged in to leave comments.
Login now