From e6f00948f38f9b2ec0bfd1f148b0db7aabc6f97d Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 3 Apr 2013 12:20:26 +0200 Subject: [PATCH] optimisations and all is properties --- test/test_config.py | 23 +- test/test_config_api.py | 38 +- test/test_option_consistency.py | 105 ++--- test/test_option_default.py | 21 +- test/test_option_owner.py | 24 +- test/test_option_setting.py | 99 ++--- test/test_option_type.py | 69 ++-- test/test_parsing_group.py | 14 +- tiramisu/autolib.py | 32 +- tiramisu/basetype.py | 77 ---- tiramisu/config.py | 671 ++++++++++++++------------------ tiramisu/option.py | 551 +++++++++++++------------- tiramisu/setting.py | 208 +++++----- tiramisu/value.py | 257 ++++++------ 14 files changed, 1045 insertions(+), 1144 deletions(-) delete mode 100644 tiramisu/basetype.py diff --git a/test/test_config.py b/test/test_config.py index beb75ad..6c13c32 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -6,14 +6,14 @@ from tiramisu.config import * from tiramisu.option import * def make_description(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) - stroption = StrOption('str', 'Test string option', default="abc") + stroption = StrOption('str', 'Test string option', default="abc", properties=('mandatory', )) boolop = BoolOption('boolop', 'Test boolean option op', default=True) wantref_option = BoolOption('wantref', 'Test requires', default=False) wantframework_option = BoolOption('wantframework', 'Test requires', @@ -52,8 +52,8 @@ def test_base_config_and_groups(): assert nm._name == 'name' gc = config.unwrap_from_path('gc') assert gc._name == 'gc' - nm = config.unwrap_from_name('name') - assert nm._name == 'name' + #nm = config.unwrap_from_name('name') + #assert nm._name == 'name' def test_base_config_in_a_tree(): "how options are organized into a tree" @@ -105,3 +105,16 @@ def test_cfgimpl_get_home_by_path(): assert config.cfgimpl_get_home_by_path('dummy')[1] == 'dummy' assert config.getpaths(include_groups=False) == ['gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] assert config.getpaths(include_groups=True) == ['gc', 'gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] + +def test_mandatory_warnings(): + descr = make_description() + config = Config(descr) + assert(MandatoryError, "config.str = ''") + setting = config.cfgimpl_get_settings() + setting.read_write() + assert list(mandatory_warnings(config)) == [] + setting.disable_property('mandatory') + config.str = '' + assert list(mandatory_warnings(config)) == ['str'] + setting.enable_property('mandatory') + assert list(mandatory_warnings(config)) == ['str'] diff --git a/test/test_config_api.py b/test/test_config_api.py index eac3117..4272389 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -6,10 +6,10 @@ from tiramisu.config import * from tiramisu.option import * def make_description(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) @@ -63,7 +63,8 @@ def test_cfgimpl_get_value(): "same as getattr." descr = make_description() conf = Config(descr) - assert conf.cfgimpl_get_value(('gc', 'dummy')) == False + #FIXME + #assert conf.cfgimpl_get_value(('gc', 'dummy')) == False #____________________________________________________________ def test_getpaths(): @@ -84,9 +85,8 @@ def test_getpaths(): def test_getpaths_with_hidden(): objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') - booloption = BoolOption('bool', 'Test boolean option', default=True) - booloption.hide() + ('std', 'thunk'), 'std') + booloption = BoolOption('bool', 'Test boolean option', default=True, properties=('hidden',)) intoption = IntOption('int', 'Test int option', default=0) stroption = StrOption('str', 'Test string option', default="abc") boolop = BoolOption('boolop', 'Test boolean option op', default=True) @@ -100,7 +100,7 @@ def test_getpaths_with_hidden(): intoption, boolop]) config = Config(descr) - result = ['objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] + result = ['bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] assert config.getpaths() == result r2 = ['bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] assert config.getpaths(allpaths=True) == r2 @@ -149,20 +149,20 @@ def test_find_in_config(): "finds option in config" descr = make_description() conf = Config(descr) - assert conf.find(byname='dummy') == [conf.unwrap_from_name('dummy')] - assert conf.find_first(byname='dummy') == conf.unwrap_from_name('dummy') - assert conf.find(bytype=ChoiceOption) == [conf.unwrap_from_name('name'), conf.unwrap_from_name('objspace')] - assert conf.find_first(bytype=ChoiceOption) == conf.unwrap_from_name('name') - assert conf.find(byvalue='ref') == [conf.unwrap_from_name('name')] - assert conf.find_first(byvalue='ref') == conf.unwrap_from_name('name') + assert conf.find(byname='dummy') == [conf.unwrap_from_path('gc.dummy')] + assert conf.find_first(byname='dummy') == conf.unwrap_from_path('gc.dummy') + assert conf.find(bytype=ChoiceOption) == [conf.unwrap_from_path('gc.name'), conf.unwrap_from_path('objspace')] + assert conf.find_first(bytype=ChoiceOption) == conf.unwrap_from_path('gc.name') + assert conf.find(byvalue='ref') == [conf.unwrap_from_path('gc.name')] + assert conf.find_first(byvalue='ref') == conf.unwrap_from_path('gc.name') # combinaison of filters - assert conf.find(bytype=BoolOption, byname='dummy') == [conf.unwrap_from_name('dummy')] - assert conf.find_first(bytype=BoolOption, byname='dummy') == conf.unwrap_from_name('dummy') - assert conf.find(byvalue=False, byname='dummy') == [conf.unwrap_from_name('dummy')] - assert conf.find_first(byvalue=False, byname='dummy') == conf.unwrap_from_name('dummy') + assert conf.find(bytype=BoolOption, byname='dummy') == [conf.unwrap_from_path('gc.dummy')] + assert conf.find_first(bytype=BoolOption, byname='dummy') == conf.unwrap_from_path('gc.dummy') + assert conf.find(byvalue=False, byname='dummy') == [conf.unwrap_from_path('gc.dummy')] + assert conf.find_first(byvalue=False, byname='dummy') == conf.unwrap_from_path('gc.dummy') # byattrs - assert conf.find_first(byattrs= dict(default=2.3)) == conf.unwrap_from_name('float') - assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_name('dummy') + assert conf.find_first(byattrs= dict(default=2.3)) == conf.unwrap_from_path('gc.float') + assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_path('gc.dummy') def test_does_not_find_in_config(): descr = make_description() diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index 0b43775..8bc9b10 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -5,20 +5,20 @@ from tiramisu.config import * from tiramisu.option import * def make_description(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) stroption = StrOption('str', 'Test string option', default="abc") boolop = BoolOption('boolop', 'Test boolean option op', default=True) wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) wantframework_option = BoolOption('wantframework', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, wantref_option, stroption, @@ -27,21 +27,21 @@ def make_description(): return descr def make_description_duplicates(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') ## dummy 1 gcdummy = BoolOption('dummy', 'dummy', default=False) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) stroption = StrOption('str', 'Test string option', default="abc") boolop = BoolOption('boolop', 'Test boolean option op', default=True) wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) wantframework_option = BoolOption('wantframework', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) # dummy2 (same path) gcdummy2 = BoolOption('dummy', 'dummy2', default=True) # dummy3 (same name) @@ -57,8 +57,7 @@ def test_identical_paths(): """If in the schema (the option description) there is something that have the same name, an exection is raised """ - descr = make_description_duplicates() - raises(ConflictConfigError, "cfg = Config(descr)") + raises(ConflictConfigError, "make_description_duplicates()") def make_description2(): gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') @@ -75,11 +74,11 @@ def make_description2(): boolop = BoolOption('boolop', 'Test boolean option op', default=True) boolop.enable_multi() wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) # second multi wantframework_option = BoolOption('wantframework', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) wantframework_option.enable_multi() gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) @@ -110,20 +109,20 @@ def make_description2(): # raises(ConflictConfigError, "cfg.wantframework = [False, False, True]") # ____________________________________________________________ # adding dynamically new options description schema -def test_newoption_add_in_descr(): - descr = make_description() - newoption = BoolOption('newoption', 'dummy twoo', default=False) - descr.add_child(newoption) - config = Config(descr) - assert config.newoption == False +#def test_newoption_add_in_descr(): +# descr = make_description() +# newoption = BoolOption('newoption', 'dummy twoo', default=False) +# descr.add_child(newoption) +# config = Config(descr) +# assert config.newoption == False -def test_newoption_add_in_subdescr(): - descr = make_description() - newoption = BoolOption('newoption', 'dummy twoo', default=False) - descr.gc.add_child(newoption) - config = Config(descr) - config.bool = False - assert config.gc.newoption == False +#def test_newoption_add_in_subdescr(): +# descr = make_description() +# newoption = BoolOption('newoption', 'dummy twoo', default=False) +# descr.gc.add_child(newoption) +# config = Config(descr) +# config.bool = False +# assert config.gc.newoption == False #def test_newoption_add_in_config(): # descr = make_description() @@ -135,17 +134,17 @@ def test_newoption_add_in_subdescr(): # assert config.newoption == False # ____________________________________________________________ def make_description_requires(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) floatoption = FloatOption('float', 'Test float option', default=2.3) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) stroption = StrOption('str', 'Test string option', default="abc", - requires=[('int', 1, 'hide')]) + requires=(('int', 1, 'hidden'),)) gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, @@ -155,72 +154,76 @@ def make_description_requires(): def test_hidden_if_in(): descr = make_description_requires() cfg = Config(descr) + setting = cfg.cfgimpl_get_settings() + setting.read_write() intoption = cfg.unwrap_from_path('int') stroption = cfg.unwrap_from_path('str') - assert not stroption._is_hidden() + assert not setting.has_property('hidden', stroption) cfg.int = 1 raises(PropertiesOptionError, "cfg.str") raises(PropertiesOptionError, 'cfg.str= "uvw"') - assert stroption._is_hidden() + assert setting.has_property('hidden', stroption) def test_hidden_if_in_with_group(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) floatoption = FloatOption('float', 'Test float option', default=2.3) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) stroption = StrOption('str', 'Test string option', default="abc") gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption], - requires=[('int', 1, 'hide')]) + requires=(('int', 1, 'hidden'),)) descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, stroption, intoption]) cfg = Config(descr) - assert not gcgroup._is_hidden() + setting = cfg.cfgimpl_get_settings() + setting.read_write() + assert not setting.has_property('hidden', stroption) cfg.int = 1 raises(PropertiesOptionError, "cfg.gc.name") - assert gcgroup._is_hidden() def test_disabled_with_group(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) floatoption = FloatOption('float', 'Test float option', default=2.3) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) stroption = StrOption('str', 'Test string option', default="abc") gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption], - requires=[('int', 1, 'disable')]) + requires=(('int', 1, 'disabled'),)) descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, stroption, intoption]) cfg = Config(descr) - assert not gcgroup._is_disabled() + setting = cfg.cfgimpl_get_settings() + setting.read_write() + assert cfg.gc.name cfg.int = 1 raises(PropertiesOptionError, "cfg.gc.name") - assert gcgroup._is_disabled() #____________________________________________________________ def make_description_callback(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', callback="toto") objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) stroption = StrOption('str', 'Test string option', default="abc") boolop = BoolOption('boolop', 'Test boolean option op', default=True) wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) wantframework_option = BoolOption('wantframework', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, wantref_option, stroption, @@ -232,21 +235,23 @@ def test_has_callback(): descr = make_description_callback() # here the owner is 'default' config = Config(descr) + setting = config.cfgimpl_get_settings() + setting.read_write() config.bool = False # because dummy has a callback dummy = config.unwrap_from_path('gc.dummy') - settings = config.cfgimpl_get_settings() - settings.freeze() - dummy.freeze() + setting.enable_property('freeze') + setting.add_property('frozen', dummy) raises(TypeError, "config.gc.dummy = True") def test_freeze_and_has_callback_with_setoption(): descr = make_description_callback() config = Config(descr) + setting = config.cfgimpl_get_settings() + setting.read_write() config.bool = False - settings = config.cfgimpl_get_settings() - settings.freeze() + config.cfgimpl_get_settings().enable_property('freeze') dummy = config.unwrap_from_path('gc.dummy') - dummy.freeze() + config.cfgimpl_get_settings().add_property('frozen', dummy) raises(TypeError, "config.gc.setoption('dummy', True, 'gen_config')") #____________________________________________________________ diff --git a/test/test_option_default.py b/test/test_option_default.py index 84652d0..ad1e1a8 100644 --- a/test/test_option_default.py +++ b/test/test_option_default.py @@ -51,31 +51,36 @@ def test_set_defaut_value_from_option_object(): assert b.getdefault() == False def test_mandatory(): - dummy1 = BoolOption('dummy1', 'doc dummy', mandatory=True) - dummy2 = BoolOption('dummy2', 'doc dummy', mandatory=True) + dummy1 = BoolOption('dummy1', 'doc dummy', properties=('mandatory', )) + dummy2 = BoolOption('dummy2', 'doc dummy', properties=('mandatory', )) group = OptionDescription('group', '', [dummy1, dummy2]) config = Config(group) + setting = config.cfgimpl_get_settings() + setting.read_only() # config.setoption('dummy1', True) raises(MandatoryError, 'config.dummy1') + setting.read_write() config.dummy1 = True + setting.read_only() assert config.dummy1 == True raises(MandatoryError, 'config.dummy2 == None') # raises(MandatoryError, "config.override({'dummy2':None})") + setting.read_write() config.set(dummy2=True) config.dummy2 = False + setting.read_only() assert config.dummy2 == False def test_force_default_on_freeze(): "a frozen option wich is forced returns his default" - dummy1 = BoolOption('dummy1', 'doc dummy', default=False) + dummy1 = BoolOption('dummy1', 'doc dummy', default=False, properties=('force_default_on_freeze',)) dummy2 = BoolOption('dummy2', 'doc dummy', default=True) group = OptionDescription('group', '', [dummy1, dummy2]) config = Config(group) config.dummy1 = True config.dummy2 = False - dummy1.freeze() - dummy1.force_default() - dummy2.freeze() + config.cfgimpl_get_settings().add_property('frozen', dummy1) + config.cfgimpl_get_settings().add_property('frozen', dummy2) assert config.dummy1 == False assert config.dummy2 == False @@ -104,14 +109,14 @@ def test_overrides_changes_option_value(): # test various option types def test_choice_with_no_default(): descr = OptionDescription("test", "", [ - ChoiceOption("backend", "", ["c", "cli"])]) + ChoiceOption("backend", "", ("c", "cli"))]) config = Config(descr) assert config.backend is None config.backend = "c" def test_choice_with_default(): descr = OptionDescription("test", "", [ - ChoiceOption("backend", "", ["c", "cli"], default="cli")]) + ChoiceOption("backend", "", ("c", "cli"), default="cli")]) config = Config(descr) assert config.backend == "cli" diff --git a/test/test_option_owner.py b/test/test_option_owner.py index 2421389..5bdbf38 100644 --- a/test/test_option_owner.py +++ b/test/test_option_owner.py @@ -32,9 +32,9 @@ def test_default_owner(): cfg = Config(descr) assert cfg.dummy == False dm = cfg.unwrap_from_path('dummy') - assert dm.getowner(cfg) == 'default' - dm.setowner(cfg, owners.user) - assert dm.getowner(cfg) == owners.user + assert cfg.cfgimpl_get_values().getowner(dm) == 'default' + cfg.dummy = True + assert cfg.cfgimpl_get_values().getowner(dm) == owners.user def test_add_owner(): gcdummy = BoolOption('dummy', 'dummy', default=False) @@ -42,10 +42,11 @@ def test_add_owner(): cfg = Config(descr) assert cfg.dummy == False dm = cfg.unwrap_from_path('dummy') - assert dm.getowner(cfg) == 'default' + assert cfg.cfgimpl_get_values().getowner(dm) == 'default' owners.add_owner("gen_config") - dm.setowner(cfg, owners.gen_config) - assert dm.getowner(cfg) == owners.gen_config + cfg.cfgimpl_get_settings().setowner(owners.gen_config) + cfg.dummy = True + assert cfg.cfgimpl_get_values().getowner(dm) == owners.gen_config def test_owner_is_not_a_string(): gcdummy = BoolOption('dummy', 'dummy', default=False) @@ -53,9 +54,8 @@ def test_owner_is_not_a_string(): cfg = Config(descr) assert cfg.dummy == False dm = cfg.unwrap_from_path('dummy') - assert dm.getowner(cfg) == owners.default - assert dm.getowner(cfg) == 'default' - assert isinstance(dm.getowner(cfg), owners.Owner) - dm.setowner(cfg, owners.user) - - assert dm.getowner(cfg) == 'user' + assert cfg.cfgimpl_get_values().getowner(dm) == owners.default + assert cfg.cfgimpl_get_values().getowner(dm) == 'default' + assert isinstance(cfg.cfgimpl_get_values().getowner(dm), owners.Owner) + cfg.dummy = True + assert cfg.cfgimpl_get_values().getowner(dm) == 'user' diff --git a/test/test_option_setting.py b/test/test_option_setting.py index 97a0705..c030fdb 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -5,12 +5,13 @@ from py.test import raises from tiramisu.config import * from tiramisu.option import * from tiramisu.error import * +from tiramisu.setting import owners def make_description(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) @@ -40,9 +41,7 @@ def test_setitem(): s = StrOption("string", "", default=["string", "sdfsdf"], default_multi="prout", multi=True) descr = OptionDescription("options", "", [s]) config = Config(descr) - print config.string[1] config.string[1] = "titi" - print config.string[1] def test_reset(): "if value is None, resets to default owner" @@ -51,10 +50,10 @@ def test_reset(): config = Config(descr) config.string = "foo" assert config.string == "foo" - assert config._cfgimpl_values.owners[s] == owners.user + assert config.cfgimpl_get_values().getowner(s) == owners.user config.unwrap_from_path("string").reset(config) assert config.string == 'string' - assert config._cfgimpl_values.owners[s] == owners.default + assert config.cfgimpl_get_values().getowner(s) == owners.default def test_reset_with_multi(): s = StrOption("string", "", default=["string"], default_multi="string" , multi=True) @@ -63,13 +62,13 @@ def test_reset_with_multi(): # config.string = [] config.unwrap_from_path("string").reset(config) assert config.string == ["string"] - assert config._cfgimpl_values.owners[s] == 'default' + assert config.cfgimpl_get_values().getowner(s) == 'default' config.string = ["eggs", "spam", "foo"] - assert config._cfgimpl_values.owners[s] == 'user' + assert config.cfgimpl_get_values().getowner(s) == 'user' config.string = [] config.unwrap_from_path("string").reset(config) # assert config.string == ["string"] - assert config._cfgimpl_values.owners[s] == 'default' + assert config.cfgimpl_get_values().getowner(s) == 'default' raises(ConfigError, "config.string = None") def test_default_with_multi(): @@ -127,13 +126,15 @@ def test_multi_with_requires(): s = StrOption("string", "", default=["string"], default_multi="string", multi=True) intoption = IntOption('int', 'Test int option', default=0) stroption = StrOption('str', 'Test string option', default=["abc"], default_multi = "abc", - requires=[('int', 1, 'hide')], multi=True) + requires=[('int', 1, 'hidden')], multi=True) descr = OptionDescription("options", "", [s, intoption, stroption]) config = Config(descr) - assert stroption._is_hidden() == False + setting = config.cfgimpl_get_settings() + setting.read_write() + assert not config.cfgimpl_get_settings().has_property('hidden', stroption) config.int = 1 raises(PropertiesOptionError, "config.str = ['a', 'b']") - assert stroption._is_hidden() + assert config.cfgimpl_get_settings().has_property('hidden', stroption) def test__requires_with_inverted(): s = StrOption("string", "", default=["string"], multi=True) @@ -142,40 +143,41 @@ def test__requires_with_inverted(): requires=[('int', 1, 'hide', 'inverted')], multi=True) descr = OptionDescription("options", "", [s, intoption, stroption]) config = Config(descr) - assert stroption._is_hidden() == False + assert not config.cfgimpl_get_settings().has_property('hidden', stroption) config.int = 1 - assert stroption._is_hidden() == False + assert not config.cfgimpl_get_settings().has_property('hidden', stroption) def test_multi_with_requires_in_another_group(): s = StrOption("string", "", default=["string"], multi=True) intoption = IntOption('int', 'Test int option', default=0) descr = OptionDescription("options", "", [intoption]) stroption = StrOption('str', 'Test string option', default=["abc"], - requires=[('int', 1, 'hide')], multi=True) + requires=[('int', 1, 'hidden')], multi=True) descr = OptionDescription("opt", "", [stroption]) descr2 = OptionDescription("opt2", "", [intoption, s, descr]) config = Config(descr2) - assert stroption._is_hidden() == False + setting = config.cfgimpl_get_settings() + setting.read_write() + assert not config.cfgimpl_get_settings().has_property('hidden', stroption) config.int = 1 raises(PropertiesOptionError, "config.opt.str = ['a', 'b']") - assert stroption._is_hidden() + assert config.cfgimpl_get_settings().has_property('hidden', stroption) def test_apply_requires_from_config(): s = StrOption("string", "", default=["string"], multi=True) intoption = IntOption('int', 'Test int option', default=0) descr = OptionDescription("options", "", [intoption]) stroption = StrOption('str', 'Test string option', default=["abc"], - requires=[('int', 1, 'hide')], multi=True) + requires=[('int', 1, 'hidden')], multi=True) descr = OptionDescription("opt", "", [stroption]) descr2 = OptionDescription("opt2", "", [intoption, s, descr]) config = Config(descr2) - assert stroption._is_hidden() == False + setting = config.cfgimpl_get_settings() + setting.read_write() + assert not config.cfgimpl_get_settings().has_property('hidden', stroption) config.int = 1 - try: - config.opt.str - except: - pass - assert stroption._is_hidden() + raises(PropertiesOptionError, 'config.opt.str') + assert config.cfgimpl_get_settings().has_property('hidden', stroption) def test_apply_requires_with_disabled(): @@ -183,43 +185,46 @@ def test_apply_requires_with_disabled(): intoption = IntOption('int', 'Test int option', default=0) descr = OptionDescription("options", "", [intoption]) stroption = StrOption('str', 'Test string option', default=["abc"], - requires=[('int', 1, 'disable')], multi=True) + requires=[('int', 1, 'disabled')], multi=True) descr = OptionDescription("opt", "", [stroption]) descr2 = OptionDescription("opt2", "", [intoption, s, descr]) config = Config(descr2) - assert stroption._is_disabled() == False + setting = config.cfgimpl_get_settings() + setting.read_write() + assert not config.cfgimpl_get_settings().has_property('disabled', stroption) config.int = 1 - try: - config.opt.str - except: - pass - assert stroption._is_disabled() + raises(PropertiesOptionError, 'config.opt.str') + assert config.cfgimpl_get_settings().has_property('disabled', stroption) def test_multi_with_requires_with_disabled_in_another_group(): s = StrOption("string", "", default=["string"], multi=True) intoption = IntOption('int', 'Test int option', default=0) descr = OptionDescription("options", "", [intoption]) stroption = StrOption('str', 'Test string option', default=["abc"], - requires=[('int', 1, 'disable')], multi=True) + requires=[('int', 1, 'disabled')], multi=True) descr = OptionDescription("opt", "", [stroption]) descr2 = OptionDescription("opt2", "", [intoption, s, descr]) config = Config(descr2) - assert stroption._is_disabled() == False + setting = config.cfgimpl_get_settings() + setting.read_write() + assert not config.cfgimpl_get_settings().has_property('disabled', stroption) config.int = 1 raises(PropertiesOptionError, "config.opt.str = ['a', 'b']") - assert stroption._is_disabled() + assert config.cfgimpl_get_settings().has_property('disabled', stroption) def test_multi_with_requires_that_is_multi(): s = StrOption("string", "", default=["string"], multi=True) intoption = IntOption('int', 'Test int option', default=[0], multi=True) stroption = StrOption('str', 'Test string option', default=["abc"], - requires=[('int', [1, 1], 'hide')], multi=True) + requires=[('int', [1, 1], 'hidden')], multi=True) descr = OptionDescription("options", "", [s, intoption, stroption]) config = Config(descr) - assert stroption._is_hidden() == False + setting = config.cfgimpl_get_settings() + setting.read_write() + assert not config.cfgimpl_get_settings().has_property('hidden', stroption) config.int = [1, 1] raises(PropertiesOptionError, "config.str = ['a', 'b']") - assert stroption._is_hidden() + assert config.cfgimpl_get_settings().has_property('hidden', stroption) def test_multi_with_bool(): s = BoolOption("bool", "", default=[False], multi=True) @@ -227,7 +232,7 @@ def test_multi_with_bool(): config = Config(descr) assert descr.bool.multi == True config.bool = [True, False] - assert config._cfgimpl_context._cfgimpl_values[s] == [True, False] + assert config.cfgimpl_get_values()[s] == [True, False] assert config.bool == [True, False] def test_multi_with_bool_two(): @@ -238,7 +243,7 @@ def test_multi_with_bool_two(): raises(ConfigError, "config.bool = True") def test_choice_access_with_multi(): - ch = ChoiceOption("t1", "", ["a", "b"], default=["a"], multi=True) + ch = ChoiceOption("t1", "", ("a", "b"), default=["a"], multi=True) descr = OptionDescription("options", "", [ch]) config = Config(descr) config.t1 = ["a", "b", "a", "b"] @@ -257,7 +262,7 @@ def test_dwim_set(): descr = OptionDescription("opt", "", [ OptionDescription("sub", "", [ BoolOption("b1", ""), - ChoiceOption("c1", "", ['a', 'b', 'c'], 'a'), + ChoiceOption("c1", "", ('a', 'b', 'c'), 'a'), BoolOption("d1", ""), ]), BoolOption("b2", ""), @@ -287,13 +292,14 @@ def test_more_set(): assert config.int == 23 def test_set_with_hidden_option(): - boolopt = BoolOption("a", "", default=False) - boolopt.hide() + boolopt = BoolOption("a", "", default=False, properties=(('hidden'),)) descr = OptionDescription("opt", "", [ OptionDescription("s1", "", [boolopt]), IntOption("int", "", default=42)]) d = {'s1.a': True, 'int': 23} config = Config(descr) + setting = config.cfgimpl_get_settings() + setting.read_write() raises(PropertiesOptionError, "config.set(**d)") def test_set_with_unknown_option(): @@ -380,16 +386,17 @@ def test_access_by_get(): assert cfg.get('dummy') == False def test_access_by_get_whith_hide(): - b1 = BoolOption("b1", "") - b1.hide() + b1 = BoolOption("b1", "", properties=(('hidden'),)) descr = OptionDescription("opt", "", [ OptionDescription("sub", "", [ b1, - ChoiceOption("c1", "", ['a', 'b', 'c'], 'a'), + ChoiceOption("c1", "", ('a', 'b', 'c'), 'a'), BoolOption("d1", ""), ]), BoolOption("b2", ""), BoolOption("d1", ""), ]) c = Config(descr) - raises(PropertiesOptionError, "c.get('b1')") + setting = c.cfgimpl_get_settings() + setting.read_write() + raises(NotFoundError, "c.get('b1')") diff --git a/test/test_option_type.py b/test/test_option_type.py index aab0ac7..4a83c4e 100644 --- a/test/test_option_type.py +++ b/test/test_option_type.py @@ -7,22 +7,20 @@ from tiramisu.config import * from tiramisu.option import * def make_description(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') - gcdummy = BoolOption('dummy', 'dummy', default=False) - # hidding dummy here - gcdummy.hide() + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') + gcdummy = BoolOption('dummy', 'dummy', default=False, properties=(('hidden'),)) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) stroption = StrOption('str', 'Test string option', default="abc") wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=[('gc.name', 'ref')]) + requires=(('gc.name', 'ref', 'hidden'),)) wantframework_option = BoolOption('wantframework', 'Test requires', default=False, - requires=[('gc.name', 'framework')]) + requires=(('gc.name', 'framework', 'hidden'),)) # ____________________________________________________________ booloptiontwo = BoolOption('booltwo', 'Test boolean option two', default=False) @@ -38,20 +36,20 @@ def make_description(): #____________________________________________________________ #freeze def make_description_freeze(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') + gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') + ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) floatoption = FloatOption('float', 'Test float option', default=2.3) stroption = StrOption('str', 'Test string option', default="abc") boolop = BoolOption('boolop', 'Test boolean option op', default=True) wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) wantframework_option = BoolOption('wantframework', 'Test requires', default=False, - requires=['boolop']) + requires=(('boolop', True, 'hidden'),)) gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption, @@ -64,10 +62,10 @@ def test_freeze_whole_config(): descr = make_description_freeze() conf = Config(descr) settings = conf.cfgimpl_get_settings() - settings.freeze_everything() + settings.enable_property('everything_frozen') assert conf.gc.dummy == False raises(TypeError, "conf.gc.dummy = True") - settings.un_freeze_everything() + settings.disable_property('everything_frozen') conf.gc.dummy = True assert conf.gc.dummy == True @@ -75,8 +73,11 @@ def test_freeze_one_option(): "freeze an option " descr = make_description_freeze() conf = Config(descr) + setting = conf.cfgimpl_get_settings() + setting.read_write() #freeze only one option - conf.gc._cfgimpl_descr.dummy.freeze() + dummy = conf.unwrap_from_path('gc.dummy') + conf.gc.cfgimpl_get_settings().add_property('frozen', dummy) assert conf.gc.dummy == False raises(TypeError, "conf.gc.dummy = True") @@ -85,45 +86,48 @@ def test_frozen_value(): s = StrOption("string", "", default="string") descr = OptionDescription("options", "", [s]) config = Config(descr) - settings = config.cfgimpl_get_settings() - settings.freeze() - s.freeze() + settings = config.cfgimpl_get_settings().enable_property('frozen') + config.cfgimpl_get_settings().add_property('frozen', s) raises(TypeError, 'config.string = "egg"') def test_freeze(): "freeze a whole configuration object" descr = make_description() conf = Config(descr) - settings = conf.cfgimpl_get_settings() - settings.freeze() + settings = conf.cfgimpl_get_settings().enable_property('frozen') name = conf.unwrap_from_path("gc.name") - name.freeze() + conf.cfgimpl_get_settings().add_property('frozen', name) raises(TypeError, "conf.gc.name = 'framework'") # ____________________________________________________________ def test_is_hidden(): descr = make_description() config = Config(descr) - assert config.gc._cfgimpl_descr.dummy._is_hidden() == True + setting = config.cfgimpl_get_settings() + setting.read_write() + dummy = config.unwrap_from_path('gc.dummy') + assert not config.cfgimpl_get_settings().has_property('frozen', dummy) # setattr raises(PropertiesOptionError, "config.gc.dummy == False") # getattr raises(PropertiesOptionError, "config.gc.dummy") # I want to access to this option anyway opt = config.unwrap_from_path("gc.dummy") - assert config._cfgimpl_context._cfgimpl_values[opt] == False + assert config.cfgimpl_get_values()[opt] == False def test_group_is_hidden(): descr = make_description() config = Config(descr) + setting = config.cfgimpl_get_settings() + setting.read_write() gc = config.unwrap_from_path('gc') dummy = config.unwrap_from_path('gc.dummy') - gc.hide() + config.cfgimpl_get_settings().add_property('hidden', gc) raises(PropertiesOptionError, "config.gc.dummy") - assert gc._is_hidden() + assert config.cfgimpl_get_settings().has_property('hidden', gc) raises(PropertiesOptionError, "config.gc.float") # manually set the subconfigs to "show" - gc.show() - assert gc._is_hidden() == False + config.cfgimpl_get_settings().del_property('hidden', gc) + assert not config.cfgimpl_get_settings().has_property('hidden', gc) assert config.gc.float == 2.3 #dummy est en hide raises(PropertiesOptionError, "config.gc.dummy == False") @@ -131,17 +135,22 @@ def test_group_is_hidden(): def test_global_show(): descr = make_description() config = Config(descr) - assert config.gc._cfgimpl_descr.dummy._is_hidden() == True + setting = config.cfgimpl_get_settings() + setting.read_write() + dummy = config.unwrap_from_path('gc.dummy') + config.cfgimpl_get_settings().add_property('hidden', dummy) + assert config.cfgimpl_get_settings().has_property('hidden', dummy) raises(PropertiesOptionError, "config.gc.dummy == False") def test_with_many_subgroups(): descr = make_description() config = Config(descr) - assert config.gc.subgroup._cfgimpl_descr.booltwo._is_hidden() == False + booltwo = config.unwrap_from_path('gc.subgroup.booltwo') + assert not config.cfgimpl_get_settings().has_property('hidden', booltwo) assert config.gc.subgroup.booltwo == False - config.gc.subgroup._cfgimpl_descr.booltwo.hide() + config.cfgimpl_get_settings().add_property('hidden', booltwo) path = 'gc.subgroup.booltwo' homeconfig, name = config.cfgimpl_get_home_by_path(path) assert name == "booltwo" option = getattr(homeconfig._cfgimpl_descr, name) - assert option._is_hidden() + assert config.cfgimpl_get_settings().has_property('hidden', booltwo) diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index 9148194..09797c1 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -17,7 +17,7 @@ def make_description(): default=False) adresse_serveur_ntp = StrOption('serveur_ntp', "adresse serveur ntp", multi=True) time_zone = ChoiceOption('time_zone', 'fuseau horaire du serveur', - ['Paris', 'Londres'], 'Paris') + ('Paris', 'Londres'), 'Paris') ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé") netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau") @@ -134,10 +134,10 @@ def test_values_with_master_and_slaves(): opt = cfg.unwrap_from_path("ip_admin_eth0.ip_admin_eth0") owner = cfg._cfgimpl_context._cfgimpl_settings.getowner() assert interface1.get_group_type() == groups.master - assert opt.getowner(cfg) == owners.default + assert cfg.cfgimpl_get_values().getowner(opt) == owners.default cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") assert cfg.ip_admin_eth0.ip_admin_eth0 == ["192.168.230.145"] - assert opt.getowner(cfg) == owner + assert cfg.cfgimpl_get_values().getowner(opt) == owner def test_reset_values_with_master_and_slaves(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) @@ -149,9 +149,9 @@ def test_reset_values_with_master_and_slaves(): opt = cfg.unwrap_from_path("ip_admin_eth0.ip_admin_eth0") owner = cfg._cfgimpl_context._cfgimpl_settings.getowner() assert interface1.get_group_type() == groups.master - assert opt.getowner(cfg) == owners.default + assert cfg.cfgimpl_get_values().getowner(opt) == owners.default cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") - assert opt.getowner(cfg) == owner - cfg._cfgimpl_context._cfgimpl_values.reset(opt) - assert opt.getowner(cfg) == owners.default + assert cfg.cfgimpl_get_values().getowner(opt) == owner + cfg.cfgimpl_get_values().reset(opt) + assert cfg.cfgimpl_get_values().getowner(opt) == owners.default assert cfg.ip_admin_eth0.ip_admin_eth0 == [] diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 44b8934..cf5d40c 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -27,14 +27,15 @@ from tiramisu.error import PropertiesOptionError, ConflictConfigError # # in case of an 'auto' and a 'fill' without a value, # # we have to carry out a calculation # return calc_factory(name, callback, callback_params, config) -def carry_out_calculation(name, option, config): + + +def carry_out_calculation(name, config, callback, callback_params): # FIXME we have to know the exact status of the config # not to disrupt it # config.freeze() - callback=option.getcallback() - callback_params=option.getcallback_params() - if callback_params is None: - callback_params = {} + #callback, callback_params = option.getcallback() + #if callback_params is None: + # callback_params = {} tcparams = {} one_is_multi = False len_multi = 0 @@ -44,7 +45,7 @@ def carry_out_calculation(name, option, config): if type(value) == tuple: path, check_disabled = value try: - opt_value = config._getattr(path, permissive=True) + opt_value = config._getattr(path, force_permissive=True) opt = config.unwrap_from_path(path) except PropertiesOptionError, err: if check_disabled: @@ -52,12 +53,14 @@ def carry_out_calculation(name, option, config): raise PropertiesOptionError(err, err.proptype) is_multi = opt.is_multi() if is_multi: - if opt_value != None: + if opt_value is not None: len_value = len(opt_value) if len_multi != 0 and len_multi != len_value: - raise ConflictConfigError('unable to carry out a calculation, ' - 'option values with multi types must have same length for: ' - + name) + raise ConflictConfigError('unable to carry out ' + 'a calculation, option ' + 'values with multi types' + ' must have same length ' + 'for: ' + name) len_multi = len_value one_is_multi = True tcparams.setdefault(key, []).append((opt_value, is_multi)) @@ -72,7 +75,7 @@ def carry_out_calculation(name, option, config): for key, couples in tcparams.items(): for couple in couples: value, ismulti = couple - if ismulti and value != None: + if ismulti and value is not None: if key == '': params.append(value[incr]) else: @@ -104,6 +107,7 @@ def carry_out_calculation(name, option, config): tcp[key] = couple[0] return calculate(name, callback, params, tcp) + def calculate(name, callback, params, tcparams): try: # XXX not only creole... @@ -113,5 +117,5 @@ def calculate(name, callback, params, tcparams): import traceback traceback.print_exc() raise ConflictConfigError("callback: {0} return error {1} for " - "option: {2}".format(callback, str(err), name)) - + "option: {2}".format(callback, str(err), + name)) diff --git a/tiramisu/basetype.py b/tiramisu/basetype.py deleted file mode 100644 index 1a8ab13..0000000 --- a/tiramisu/basetype.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -"base 'interface' types for option types" -# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# The original `Config` design model is unproudly borrowed from -# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/ -# the whole pypy projet is under MIT licence -# ____________________________________________________________ - -class CommonType(object): - def has_properties(self): - return bool(len(self.properties)) - def has_property(self, propname): - return propname in self.properties - def get_properties(self): - return self.properties - def add_property(self, propname): - if not propname in self.properties: - self.properties.append(propname) - def del_property(self, propname): - if self.has_property(propname): - self.properties.remove(propname) - -#class HiddenBaseType(BaseType): -# def hide(self): -# self.add_property('hidden') -# def show(self): -# self.del_property('hidden') -# def _is_hidden(self): -# # dangerous method: how an Option() can determine its status by itself ? -# return self.has_property('hidden') - -#class DisabledBaseType(BaseType): -# def disable(self): -# self.add_property('disabled') -# def enable(self): -# self.del_property('disabled') -# def _is_disabled(self): -# return self.has_property('disabled') - -# commonly used properties. Option and OptionDescription will have these methods -common_properties = {'hidden': ('hide', 'show', '_is_hidden'), - 'disabled': ('disable', 'enable', '_is_disabled') - } - -basetype_methods = dict() - -def build_properties(prop, add_prop, del_prop, is_prop): - def add_property(self): - self.add_property(prop) - def del_property(self): - self.del_property(prop) - def is_property(self): - return self.has_property(prop) - return {add_prop:add_property, del_prop:del_property, - is_prop:is_property} - -for propname, meth_names in common_properties.items(): - basetype_methods.update(build_properties(propname, meth_names[0], meth_names[1], - meth_names[2])) - -BaseType = type('BaseType', (CommonType,), basetype_methods) - diff --git a/tiramisu/config.py b/tiramisu/config.py index 469e198..caf808d 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -20,134 +20,50 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -from copy import copy -from inspect import getmembers, ismethod -from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError, - AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, - MandatoryError, MethodCallError, NoValueReturned) +#from inspect import getmembers, ismethod +from tiramisu.error import (PropertiesOptionError, NotFoundError, + AmbigousOptionError, NoMatchingOptionFound, MandatoryError) from tiramisu.option import (OptionDescription, Option, SymLinkOption, - apply_requires) -from tiramisu.setting import groups, owners, Setting + apply_requires) +from tiramisu.setting import groups, Setting from tiramisu.value import Values -# ____________________________________________________________ -class Config(object): - "main configuration management entry" - __slots__ = ('_cfgimpl_descr', '_cfgimpl_subconfigs', - '_cfgimpl_parent', '_cfgimpl_warnings', '_cfgimpl_permissive', - '_cfgimpl_context', '_cfgimpl_settings', '_cfgimpl_values', - '_cfgimpl_slots', '_cfgimpl_build_all_paths') - def __init__(self, descr, parent=None, context=None, valid_opt_names=True): +class SubConfig(object): + "sub configuration management entry" + __slots__ = ('_cfgimpl_descr', '_cfgimpl_subconfigs', '_cfgimpl_parent', + '_cfgimpl_context') + + def __init__(self, descr, parent, context): # FIXME , slots): """ Configuration option management master class :param descr: describes the configuration schema :type descr: an instance of ``option.OptionDescription`` - :param parent: is None if the ``Config`` is root parent Config otherwise - :type parent: ``Config`` + :param parent: parent's `Config` + :type parent: `Config` :param context: the current root config :type context: `Config` """ # main option description self._cfgimpl_descr = descr # sub option descriptions - self._cfgimpl_subconfigs = {} + self._cfgimpl_subconfigs = None self._cfgimpl_parent = parent - self._cfgimpl_permissive = [] - if context is None: - self._cfgimpl_context = self - else: - self._cfgimpl_context = context - if parent is None: - self._cfgimpl_settings = Setting() - self._cfgimpl_settings.valid_opt_names = valid_opt_names - self._cfgimpl_values = Values(self._cfgimpl_context) - #self._cfgimpl_all_paths = {} - else: - if context is None: - raise ConfigError("cannot find a value for this config") - valid_opt_names = context._cfgimpl_settings.valid_opt_names - "warnings are a great idea, let's make up a better use of it" - self._cfgimpl_warnings = [] - if valid_opt_names: - # some api members shall not be used as option's names ! - methods = getmembers(self, ismethod) - self._cfgimpl_slots = [key for key, value in methods - if not key.startswith("_")] - else: - self._cfgimpl_slots = [] - self._cfgimpl_build() - if parent is None: - self._cfgimpl_build_all_paths() + self._cfgimpl_context = context + #self._cfgimpl_build(slots) - def _cfgimpl_build_all_paths(self): - self._cfgimpl_descr.build_cache() + def cfgimpl_get_context(self): + return self._cfgimpl_context def cfgimpl_get_settings(self): return self._cfgimpl_context._cfgimpl_settings - def cfgimpl_set_settings(self, settings): - if not isinstance(settings, Setting): - raise ConfigError("setting not allowed") - self._cfgimpl_context._cfgimpl_settings = settings + def cfgimpl_get_values(self): + return self._cfgimpl_context._cfgimpl_values def cfgimpl_get_description(self): return self._cfgimpl_descr - def cfgimpl_get_value(self, path): - """same as multiple getattrs - - :param path: list or tuple of path - - example : ``path = (sub_conf, opt)`` makes a getattr of sub_conf, then - a getattr of opt, and then returns the opt's value. - - :returns: subconf or option's value - """ - subpaths = list(path) - subconf_or_opt = self - for subpath in subpaths: - subconf_or_opt = getattr(subconf_or_opt, subpath) - return subconf_or_opt - - def _validate_duplicates(self, children): - """duplicates Option names in the schema - :type children: list of `Option` or `OptionDescription` - """ - duplicates = [] - for dup in children: - if dup._name not in duplicates: - duplicates.append(dup._name) - else: - raise ConflictConfigError('duplicate option name: ' - '{0}'.format(dup._name)) - - def _cfgimpl_build(self): - """ - - builds the config object from the schema - - settles various default values for options - """ - self._validate_duplicates(self._cfgimpl_descr._children) - if self._cfgimpl_descr.group_type == groups.master: - mastername = self._cfgimpl_descr._name - masteropt = getattr(self._cfgimpl_descr, mastername) - self._cfgimpl_context._cfgimpl_values.masters[masteropt] = [] - - for child in self._cfgimpl_descr._children: - if isinstance(child, OptionDescription): - self._validate_duplicates(child._children) - self._cfgimpl_subconfigs[child] = Config(child, parent=self, - context=self._cfgimpl_context) - else: - if child._name in self._cfgimpl_slots: - raise NameError("invalid name for the option:" - " {0}".format(child._name)) - - if (self._cfgimpl_descr.group_type == groups.master and - child != masteropt): - self._cfgimpl_context._cfgimpl_values.slaves[child] = masteropt - self._cfgimpl_context._cfgimpl_values.masters[masteropt].append(child) - # ____________________________________________________________ # attribute methods def __setattr__(self, name, value): @@ -156,53 +72,44 @@ class Config(object): #self.__dict__[name] = value object.__setattr__(self, name, value) return + self._setattr(name, value) + + def _setattr(self, name, value, force_permissive=False): if '.' in name: homeconfig, name = self.cfgimpl_get_home_by_path(name) - return setattr(homeconfig, name, value) + return homeconfig.__setattr__(name, value) if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption: - self._validate(name, getattr(self._cfgimpl_descr, name)) - if name in self._cfgimpl_slots: - raise NameError("invalid name for the option:" - " {0}".format(name)) + self._validate(name, getattr(self._cfgimpl_descr, name), force_permissive=force_permissive) self.setoption(name, value) - def _validate(self, name, opt_or_descr, permissive=False): + def _validate(self, name, opt_or_descr, force_permissive=False): "validation for the setattr and the getattr" - apply_requires(opt_or_descr, self, permissive=permissive) + apply_requires(opt_or_descr, self) if not isinstance(opt_or_descr, Option) and \ not isinstance(opt_or_descr, OptionDescription): raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr))) - properties = set(copy(opt_or_descr.properties)) - properties = properties & set(self._cfgimpl_context._cfgimpl_settings.get_properties()) - if permissive: - properties = properties - set(self._cfgimpl_context._cfgimpl_settings.get_permissive()) - properties = properties - set(self._cfgimpl_permissive) + properties = set(self.cfgimpl_get_settings().get_properties(opt_or_descr)) + #remove this properties, those properties are validate in value/setting + properties = properties - set(['mandatory', 'frozen']) + set_properties = set(self.cfgimpl_get_settings().get_properties()) + properties = properties & set_properties + if force_permissive is True or self.cfgimpl_get_settings().has_property('permissive'): + properties = properties - set(self.cfgimpl_get_settings().get_permissive()) + properties = properties - set(self.cfgimpl_get_settings().get_permissive(self.cfgimpl_get_description())) properties = list(properties) if properties != []: raise PropertiesOptionError("trying to access" - " to an option named: {0} with properties" - " {1}".format(name, str(properties)), - properties) + " to an option named: {0} with properties" + " {1}".format(name, str(properties)), + properties) def __getattr__(self, name): return self._getattr(name) -# def fill_multi(self, opt, result, use_default_multi=False, default_multi=None): -# """fills a multi option with default and calculated values -# """ -# # FIXME C'EST ENCORE DU N'IMPORTE QUOI -# if not isinstance(result, list): -# _result = [result] -# else: -# _result = result -# return Multi(_result, self._cfgimpl_context, opt) - - def _getattr(self, name, permissive=False): + def _getattr(self, name, force_permissive=False, force_properties=None): """ attribute notation mechanism for accessing the value of an option :param name: attribute name - :param permissive: permissive doesn't raise some property error - (see ``permissive``) :return: option's value if name is an option name, OptionDescription otherwise """ @@ -210,38 +117,222 @@ class Config(object): # for instance getattr(self, "creole.general.family.adresse_ip_eth0") if '.' in name: homeconfig, name = self.cfgimpl_get_home_by_path(name) - return homeconfig._getattr(name, permissive) + return homeconfig._getattr(name) opt_or_descr = getattr(self._cfgimpl_descr, name) # symlink options if type(opt_or_descr) == SymLinkOption: - rootconfig = self._cfgimpl_get_toplevel() - return getattr(rootconfig, opt_or_descr.path) + rootconfig = self.cfgimpl_get_context() + path = rootconfig.cfgimpl_get_description().get_path_by_opt(opt_or_descr.opt) + return getattr(rootconfig, path) - self._validate(name, opt_or_descr, permissive) + self._validate(name, opt_or_descr, force_permissive=force_permissive) if isinstance(opt_or_descr, OptionDescription): - if opt_or_descr not in self._cfgimpl_subconfigs: - raise AttributeError("%s with name %s object has no attribute %s" % - (self.__class__, opt_or_descr._name, name)) - return self._cfgimpl_subconfigs[opt_or_descr] + children = self.cfgimpl_get_description()._children + if opt_or_descr not in children[1]: + raise AttributeError("{0} with name {1} object has " + "no attribute {2}".format(self.__class__, + opt_or_descr._name, + name)) + return SubConfig(opt_or_descr, self, self._cfgimpl_context) # special attributes if name.startswith('_cfgimpl_'): # if it were in __dict__ it would have been found already - return self.__dict__[name] - return self._cfgimpl_context._cfgimpl_values[opt_or_descr] + object.__getattr__(self, name) + return self.cfgimpl_get_values()._getitem(opt_or_descr, + force_properties=force_properties) - def unwrap_from_name(self, name): - """convenience method to extract and Option() object from the Config() - **and it is slow**: it recursively searches into the namespaces - - :returns: Option() + def setoption(self, name, value, who=None): + """effectively modifies the value of an Option() + (typically called by the __setattr__) """ - paths = self.getpaths(allpaths=True) - opts = dict([(path, self.unwrap_from_path(path)) for path in paths]) - all_paths = [p.split(".") for p in self.getpaths()] - for pth in all_paths: - if name in pth: - return opts[".".join(pth)] - raise NotFoundError("name: {0} not found".format(name)) + child = getattr(self._cfgimpl_descr, name) + child.setoption(self, value) + + def cfgimpl_get_home_by_path(self, path): + """:returns: tuple (config, name)""" + path = path.split('.') + for step in path[:-1]: + self = getattr(self, step) + return self, path[-1] + + def _cfgimpl_get_path(self): + "the path in the attribute access meaning." + #FIXME optimisation + subpath = [] + obj = self + while obj._cfgimpl_parent is not None: + subpath.insert(0, obj._cfgimpl_descr._name) + obj = obj._cfgimpl_parent + return ".".join(subpath) + + def getkey(self): + return self._cfgimpl_descr.getkey(self) + + def __hash__(self): + return hash(self.getkey()) + + def __eq__(self, other): + "Config comparison" + if not isinstance(other, Config): + return False + return self.getkey() == other.getkey() + + def __ne__(self, other): + "Config comparison" + return not self == other + + # ______________________________________________________________________ + def __iter__(self): + """Pythonesque way of parsing group's ordered options. + iteration only on Options (not OptionDescriptions)""" + for child in self._cfgimpl_descr._children[1]: + if not isinstance(child, OptionDescription): + try: + yield child._name, getattr(self, child._name) + except GeneratorExit: + raise StopIteration + except: + pass # option with properties + + def iter_all(self): + """A way of parsing options **and** groups. + iteration on Options and OptionDescriptions.""" + for child in self._cfgimpl_descr._children[1]: + try: + yield child._name, getattr(self, child._name) + except GeneratorExit: + raise StopIteration + except: + pass # option with properties + + def iter_groups(self, group_type=None): + """iteration on groups objects only. + All groups are returned if `group_type` is `None`, otherwise the groups + can be filtered by categories (families, or whatever). + + :param group_type: if defined, is an instance of `groups.GroupType` + or `groups.MasterGroupType` that lives in + `setting.groups` + + """ + if group_type is not None: + if not isinstance(group_type, groups.GroupType): + raise TypeError("Unknown group_type: {0}".format(group_type)) + for child in self._cfgimpl_descr._children[1]: + if isinstance(child, OptionDescription): + try: + if group_type is not None: + if child.get_group_type() == group_type: + yield child._name, getattr(self, child._name) + else: + yield child._name, getattr(self, child._name) + except GeneratorExit: + raise StopIteration + except: + pass + # ______________________________________________________________________ + + def cfgimpl_set_permissive(self, permissive): + if not isinstance(permissive, list): + raise TypeError('permissive must be a list') + self.cfgimpl_get_settings().set_permissive(permissive, self.cfgimpl_get_description()) + + # ______________________________________________________________________ + def __str__(self): + "Config's string representation" + lines = [] + for name, grp in self.iter_groups(): + lines.append("[%s]" % name) + for name, value in self: + try: + lines.append("%s = %s" % (name, value)) + except: + pass + return '\n'.join(lines) + + __repr__ = __str__ + + def getpaths(self, include_groups=False, allpaths=False, mandatory=False): + """returns a list of all paths in self, recursively, taking care of + the context of properties (hidden/disabled) + + :param include_groups: if true, OptionDescription are included + :param allpaths: all the options (event the properties protected ones) + :param mandatory: includes the mandatory options + :returns: list of all paths + """ + paths = [] + for path in self._cfgimpl_descr.getpaths(include_groups=include_groups): + if allpaths: + paths.append(path) + else: + try: + getattr(self, path) + except MandatoryError: + if mandatory: + paths.append(path) + except PropertiesOptionError: + pass + else: + paths.append(path) + return paths + + def getpath(self): + descr = self.cfgimpl_get_description() + context_descr = self.cfgimpl_get_context().cfgimpl_get_description() + return context_descr.get_path_by_opt(descr) + + def get(self, name): + path = self.getpath() + return self.cfgimpl_get_context().get(name, _subpath=path) + + def find(self, bytype=None, byname=None, byvalue=None, byattrs=None): + path = self.getpath() + return self.cfgimpl_get_context().find(bytype=bytype, byname=byname, + byvalue=byvalue, + byattrs=byattrs, + _subpath=path) + + def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None): + path = self.getpath() + return self.cfgimpl_get_context().find_first(bytype=bytype, + byname=byname, + byvalue=byvalue, + byattrs=byattrs, + _subpath=path) + + +# ____________________________________________________________ +class Config(SubConfig): + "main configuration management entry" + __slots__ = ('_cfgimpl_settings', '_cfgimpl_values') + + def __init__(self, descr, valid_opt_names=True): + """ Configuration option management master class + + :param descr: describes the configuration schema + :type descr: an instance of ``option.OptionDescription`` + :param parent: is None if the ``Config`` is root parent Config otherwise + :type parent: ``Config`` + :param context: the current root config + :type context: `Config` + """ + self._cfgimpl_settings = Setting() + self._cfgimpl_values = Values(self) + #if valid_opt_names: + # # some api members shall not be used as option's names ! + # #FIXME fait une boucle infini ... + # #methods = getmembers(self, ismethod) + # #slots = tuple([key for key, value in methods + # # if not key.startswith("_")]) + # slots = [] + #else: + # slots = [] + super(Config, self).__init__(descr, None, self) # , slots) + self._cfgimpl_build_all_paths() + + def _cfgimpl_build_all_paths(self): + self._cfgimpl_descr.build_cache() def unwrap_from_path(self, path): """convenience method to extract and Option() object from the Config() @@ -255,13 +346,6 @@ class Config(object): return getattr(homeconfig._cfgimpl_descr, path) return getattr(self._cfgimpl_descr, path) - def setoption(self, name, value, who=None): - """effectively modifies the value of an Option() - (typically called by the __setattr__) - """ - child = getattr(self._cfgimpl_descr, name) - child.setoption(self, value) - def set(self, **kwargs): """ do what I mean"-interface to option setting. Searches all paths @@ -292,7 +376,7 @@ class Config(object): 'there is no option that matches %s' ' or the option is hidden or disabled' % (key, )) - def get(self, name): + def get(self, name, _subpath=None): """ same as a `find_first()` method in a config that has identical names: it returns the first item of an option named `name` @@ -300,181 +384,46 @@ class Config(object): much like the attribute access way, except that the search for the option is performed recursively in the whole configuration tree. - **carefull**: very slow ! :returns: option value. """ - paths = self.getpaths(allpaths=True) - pathsvalues = [] - for path in paths: - pathname = path.split('.')[-1] - if pathname == name: - try: - value = getattr(self, path) - return value - except Exception, e: - raise e - raise NotFoundError("option {0} not found in config".format(name)) + return self._find(byname=name, bytype=None, byvalue=None, byattrs=None, + first=True, getvalue=True, _subpath=_subpath) - def cfgimpl_get_home_by_path(self, path): - """:returns: tuple (config, name)""" - path = path.split('.') - for step in path[:-1]: - self = getattr(self, step) - return self, path[-1] - - def _cfgimpl_get_toplevel(self): - ":returns: root config" - while self._cfgimpl_parent is not None: - self = self._cfgimpl_parent - return self - - def _cfgimpl_get_path(self): - "the path in the attribute access meaning." - subpath = [] - obj = self - while obj._cfgimpl_parent is not None: - subpath.insert(0, obj._cfgimpl_descr._name) - obj = obj._cfgimpl_parent - return ".".join(subpath) - # ______________________________________________________________________ -# def cfgimpl_previous_value(self, path): -# "stores the previous value" -# home, name = self.cfgimpl_get_home_by_path(path) -# # FIXME fucking name -# return home._cfgimpl_context._cfgimpl_values.previous_values[name] - -# def get_previous_value(self, name): -# "for the time being, only the previous Option's value is accessible" -# return self._cfgimpl_context._cfgimpl_values.previous_values[name] - # ______________________________________________________________________ - def add_warning(self, warning): - "Config implements its own warning pile. Could be useful" - self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning) - - def get_warnings(self): - "Config implements its own warning pile" - return self._cfgimpl_get_toplevel()._cfgimpl_warnings - - # ____________________________________________________________ - def getkey(self): - return self._cfgimpl_descr.getkey(self) - - def __hash__(self): - return hash(self.getkey()) - - def __eq__(self, other): - "Config comparison" - if not isinstance(other, Config): - return False - return self.getkey() == other.getkey() - - def __ne__(self, other): - "Config comparison" - return not self == other - - # ______________________________________________________________________ - def __iter__(self): - """Pythonesque way of parsing group's ordered options. - iteration only on Options (not OptionDescriptions)""" - for child in self._cfgimpl_descr._children: - if not isinstance(child, OptionDescription): - try: - yield child._name, getattr(self, child._name) - except GeneratorExit: - raise StopIteration - except: - pass # option with properties - - def iter_all(self): - """A way of parsing options **and** groups. - iteration on Options and OptionDescriptions.""" - for child in self._cfgimpl_descr._children: - try: - yield child._name, getattr(self, child._name) - except GeneratorExit: - raise StopIteration - except: - pass # option with properties - - def iter_groups(self, group_type=None): - """iteration on groups objects only. - All groups are returned if `group_type` is `None`, otherwise the groups - can be filtered by categories (families, or whatever). - - :param group_type: if defined, is an instance of `groups.GroupType` - or `groups.MasterGroupType` that lives in - `setting.groups` - - """ - if group_type is not None: - if not isinstance(group_type, groups.GroupType): - raise TypeError("Unknown group_type: {0}".format(group_type)) - for child in self._cfgimpl_descr._children: - if isinstance(child, OptionDescription): - try: - if group_type is not None: - if child.get_group_type() == group_type: - yield child._name, getattr(self, child._name) - else: - yield child._name, getattr(self, child._name) - except GeneratorExit: - raise StopIteration - except: - pass - # ______________________________________________________________________ - def cfgimpl_set_permissive(self, permissive): - if not isinstance(permissive, list): - raise TypeError('permissive must be a list') - self._cfgimpl_permissive = permissive - # ______________________________________________________________________ - def __str__(self): - "Config's string representation" - lines = [] - for name, grp in self.iter_groups(): - lines.append("[%s]" % name) - for name, value in self: - try: - lines.append("%s = %s" % (name, value)) - except: - pass - return '\n'.join(lines) - - __repr__ = __str__ - - def getpaths(self, include_groups=False, allpaths=False, mandatory=False): - """returns a list of all paths in self, recursively, taking care of - the context of properties (hidden/disabled) - - :param include_groups: if true, OptionDescription are included - :param allpaths: all the options (event the properties protected ones) - :param mandatory: includes the mandatory options - :returns: list of all paths - """ - paths = [] - for path in self._cfgimpl_descr.getpaths(include_groups=include_groups): - if allpaths: - paths.append(path) - else: - try: - value = getattr(self, path) - - except MandatoryError: - if mandatory: - paths.append(path) - except PropertiesOptionError: - pass - else: - paths.append(path) - return paths - - def _find(self, bytype, byname, byvalue, byattrs, first): + def _find(self, bytype, byname, byvalue, byattrs, first, getvalue=False, + _subpath=None): """ convenience method for finding an option that lives only in the subtree :param first: return only one option if True, a list otherwise :return: find list or an exception if nothing has been found """ + def _filter_by_name(): + if byname is None: + return True + if path == byname or path.endswith('.' + byname): + return True + else: + return False + + def _filter_by_value(): + if byvalue is None: + return True + try: + value = getattr(self, path) + if value == byvalue: + return True + except: # a property restricts the access of the value + pass + return False + + def _filter_by_type(): + if bytype is None: + return True + if isinstance(option, bytype): + return True + return False + def _filter_by_attrs(): if byattrs is None: return True @@ -487,37 +436,15 @@ class Config(object): else: continue return True - def _filter_by_name(): - if byname is None: - return True - pathname = path.split('.')[-1] - if pathname == byname: - return True - else: - return False - def _filter_by_value(): - if byvalue is None: - return True - try: - value = getattr(self, path) - if value == byvalue: - return True - except: # a property restricts the access of the value - pass - return False - def _filter_by_type(): - if bytype is None: - return True - if isinstance(option, bytype): - return True - return False find_results = [] - paths = self.getpaths(allpaths=True) - for path in paths: - try: - option = self.unwrap_from_path(path) - except PropertiesOptionError, err: + opts, paths = self.cfgimpl_get_description()._cache_paths + for index in range(0, len(paths)): + path = paths[index] + option = opts[index] + if isinstance(option, OptionDescription): + continue + if _subpath is not None and not path.startswith(_subpath + '.'): continue if not _filter_by_name(): continue @@ -527,17 +454,27 @@ class Config(object): continue if not _filter_by_attrs(): continue + #remove option with propertyerror, ... + try: + value = getattr(self, path) + except: # a property restricts the access of the value + continue if first: - return option + if getvalue: + return value + else: + return option else: - find_results.append(option) - + if getvalue: + find_results.append(value) + else: + find_results.append(option) if find_results == []: raise NotFoundError("no option found in config with these criteria") else: return find_results - def find(self, bytype=None, byname=None, byvalue=None, byattrs=None): + def find(self, bytype=None, byname=None, byvalue=None, byattrs=None, _subpath=None): """ finds a list of options recursively in the config @@ -547,9 +484,9 @@ class Config(object): :param byattrs: dict of option attributes (default, callback...) :returns: list of matching Option objects """ - return self._find(bytype, byname, byvalue, byattrs, first=False) + return self._find(bytype, byname, byvalue, byattrs, first=False, _subpath=_subpath) - def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None): + def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None, _subpath=None): """ finds an option recursively in the config @@ -559,7 +496,7 @@ class Config(object): :param byattrs: dict of option attributes (default, callback...) :returns: list of matching Option objects """ - return self._find(bytype, byname, byvalue, byattrs, first=True) + return self._find(bytype, byname, byvalue, byattrs, first=True, _subpath=_subpath) def make_dict(config, flatten=False): @@ -586,15 +523,11 @@ def mandatory_warnings(config): where no value has been set :returns: generator of mandatory Option's path - FIXME : CAREFULL : not multi-user """ - mandatory = config._cfgimpl_context._cfgimpl_settings.mandatory - config._cfgimpl_context._cfgimpl_settings.mandatory = True - for path in config._cfgimpl_descr.getpaths(include_groups=True): + for path in config.cfgimpl_get_description().getpaths(include_groups=True): try: - value = config._getattr(path, permissive=True) + config._getattr(path, force_properties=('mandatory',)) except MandatoryError: yield path except PropertiesOptionError: pass - config._cfgimpl_context._cfgimpl_settings.mandatory = mandatory diff --git a/tiramisu/option.py b/tiramisu/option.py index 9ed8aed..3710d6d 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -21,26 +21,17 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ import re +from copy import copy from types import FunctionType -from tiramisu.basetype import BaseType -from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError, - RequiresError, RequirementRecursionError, MandatoryError, - PropertiesOptionError) +from tiramisu.error import (ConfigError, NotFoundError, ConflictConfigError, + RequiresError, RequirementRecursionError, + PropertiesOptionError) from tiramisu.autolib import carry_out_calculation -from tiramisu.setting import groups, owners +from tiramisu.setting import groups, multitypes -requires_actions = [('hide', 'show'), ('enable', 'disable'), - ('freeze', 'unfreeze')] - -available_actions = [] -reverse_actions = {} -for act1, act2 in requires_actions: - available_actions.extend([act1, act2]) - reverse_actions[act1] = act2 - reverse_actions[act2] = act1 -# ____________________________________________________________ name_regexp = re.compile(r'^\d+') + def valid_name(name): try: name = str(name) @@ -53,7 +44,9 @@ def valid_name(name): #____________________________________________________________ # -class BaseInformation: + +class BaseInformation(object): + __slots__ = ('informations') def set_information(self, key, value): """updates the information's attribute @@ -64,29 +57,33 @@ class BaseInformation: """ self.informations[key] = value - def get_information(self, key): + def get_information(self, key, default=None): """retrieves one information's item :param key: the item string (ex: "help") """ if key in self.informations: return self.informations[key] + elif default is not None: + return default else: raise ValueError("Information's item not found: {0}".format(key)) -class Option(BaseType, BaseInformation): + +class Option(BaseInformation): """ Abstract base class for configuration option's. Reminder: an Option object is **not** a container for the value """ - #freeze means: cannot modify the value of an Option once set - _frozen = False - #if an Option has been frozen, shall return the default value - _force_default_on_freeze = False + __slots__ = ('_name', '_requires', 'multi', '_validator', 'default_multi', + 'default', '_properties', 'callback', 'multitype', + 'master_slaves') + def __init__(self, name, doc, default=None, default_multi=None, - requires=None, mandatory=False, multi=False, callback=None, - callback_params=None, validator=None, validator_args={}): + requires=None, multi=False, callback=None, + callback_params=None, validator=None, validator_args=None, + properties=None): """ :param name: the option's name :param doc: the option's description @@ -107,51 +104,58 @@ class Option(BaseType, BaseInformation): if not valid_name(name): raise NameError("invalid name: {0} for option".format(name)) self._name = name - self.doc = doc + self.informations = {} + self.set_information('doc', doc) + validate_requires_arg(requires, self._name) self._requires = requires - self._mandatory = mandatory self.multi = multi - self._validator = None - self._validator_args = None + #self._validator_args = None if validator is not None: if type(validator) != FunctionType: raise TypeError("validator must be a function") - self._validator = validator - if validator_args is not None: - self._validator_args = validator_args + self._validator = (validator, validator_args) + else: + self._validator = None if not self.multi and default_multi is not None: raise ConfigError("a default_multi is set whereas multi is False" - " in option: {0}".format(name)) + " in option: {0}".format(name)) if default_multi is not None and not self._validate(default_multi): raise ConfigError("invalid default_multi value {0} " - "for option {1}".format(str(default_multi), name)) - self.default_multi = default_multi - #if self.multi and default_multi is None: - # _cfgimpl_warnings[name] = DefaultMultiWarning + "for option {1}".format(str(default_multi), name)) if callback is not None and (default is not None or default_multi is not None): raise ConfigError("defaut values not allowed if option: {0} " - "is calculated".format(name)) - self.callback = callback - if self.callback is None and callback_params is not None: - raise ConfigError("params defined for a callback function but" - " no callback defined yet for option {0}".format(name)) - self.callback_params = callback_params - if self.multi == True: - if default == None: + "is calculated".format(name)) + if callback is None and callback_params is not None: + raise ConfigError("params defined for a callback function but " + "no callback defined yet for option {0}".format(name)) + if callback is not None: + self.callback = (callback, callback_params) + else: + self.callback = None + if self.multi: + if default is None: default = [] if not isinstance(default, list): raise ConfigError("invalid default value {0} " - "for option {1} : not list type".format(str(default), name)) + "for option {1} : not list type" + "".format(str(default), name)) if not self.validate(default, False): raise ConfigError("invalid default value {0} " - "for option {1}".format(str(default), name)) + "for option {1}" + "".format(str(default), name)) + self.multitype = multitypes.default + self.default_multi = default_multi else: - if default != None and not self.validate(default, False): + if default is not None and not self.validate(default, False): raise ConfigError("invalid default value {0} " - "for option {1}".format(str(default), name)) + "for option {1}".format(str(default), name)) self.default = default - self.properties = [] # 'hidden', 'disabled'... - self.informations = {} + if properties is None: + properties = () + if not isinstance(properties, tuple): + raise ConfigError('invalid properties type {0} for {1},' + ' must be a tuple'.format(type(properties), self._name)) + self._properties = properties # 'hidden', 'disabled'... def validate(self, value, validate=True): """ @@ -159,25 +163,25 @@ class Option(BaseType, BaseInformation): :param validate: if true enables ``self._validator`` validation """ # generic calculation - if self.multi == False: + if not self.multi: # None allows the reset of the value - if value != None: + if value is not None: # customizing the validator if validate and self._validator is not None and \ - not self._validator(value, **self._validator_args): + not self._validator[0](value, **self._validator[1]): return False return self._validate(value) else: if not isinstance(value, list): raise ConfigError("invalid value {0} " - "for option {1} which must be a list".format(value, - self._name)) + "for option {1} which must be a list" + "".format(value, self._name)) for val in value: # None allows the reset of the value - if val != None: + if val is not None: # customizing the validator if validate and self._validator is not None and \ - not self._validator(val, **self._validator_args): + not self._validator[0](val, **self._validator[1]): return False if not self._validate(val): return False @@ -185,7 +189,7 @@ class Option(BaseType, BaseInformation): def getdefault(self, default_multi=False): "accessing the default value" - if default_multi == False or not self.is_multi(): + if not default_multi or not self.is_multi(): return self.default else: return self.getdefault_multi() @@ -196,148 +200,88 @@ class Option(BaseType, BaseInformation): def is_empty_by_default(self): "no default value has been set yet" - if ((not self.is_multi() and self.default == None) or + if ((not self.is_multi() and self.default is None) or (self.is_multi() and (self.default == [] or None in self.default))): return True return False - def force_default(self): - "if an Option has been frozen, shall return the default value" - self._force_default_on_freeze = True - - def hascallback_and_isfrozen(): - return self._frozen and self.has_callback() - - def is_forced_on_freeze(self): - "if an Option has been frozen, shall return the default value" - return self._frozen and self._force_default_on_freeze - def getdoc(self): "accesses the Option's doc" - return self.doc - - def getcallback(self): - "a callback is only a link, the name of an external hook" - return self.callback + return self.get_information('doc') def has_callback(self): "to know if a callback has been defined or not" - if self.callback == None: + if self.callback is None: return False else: return True def getcallback_value(self, config): - return carry_out_calculation(self._name, - option=self, config=config) - - def getcallback_params(self): - "if a callback has been defined, returns his arity" - return self.callback_params - - def setowner(self, config, owner): - """ - :param config: *must* be only the **parent** config - (not the toplevel config) - :param owner: is a **real** owner, that is an object - that lives in setting.owners - """ - name = self._name - if not isinstance(owner, owners.Owner): - raise ConfigError("invalid type owner for option: {0}".format( - str(name))) - config._cfgimpl_context._cfgimpl_values.owners[self] = owner - - def getowner(self, config): - "config *must* be only the **parent** config (not the toplevel config)" - return config._cfgimpl_context._cfgimpl_values.getowner(self) - - def get_previous_value(self, config): - return config._cfgimpl_context._cfgimpl_values.get_previous_value(self) + callback, callback_params = self.callback + if callback_params is None: + callback_params = {} + return carry_out_calculation(self._name, config=config, + callback=callback, + callback_params=callback_params) def reset(self, config): """resets the default value and owner """ config._cfgimpl_context._cfgimpl_values.reset(self) - def is_default_owner(self, config): - """ - :param config: *must* be only the **parent** config - (not the toplevel config) - :return: boolean - """ - return self.getowner(config) == owners.default - def setoption(self, config, value): """changes the option's value with the value_owner's who :param config: the parent config is necessary here to store the value """ name = self._name - rootconfig = config._cfgimpl_get_toplevel() - if not self.validate(value, - config._cfgimpl_context._cfgimpl_settings.validator): + setting = config.cfgimpl_get_settings() + if not self.validate(value, setting.has_property('validator')): raise ConfigError('invalid value %s for option %s' % (value, name)) - if self.is_mandatory(): - # value shall not be '' for a mandatory option - # so '' is considered as being None - if not self.is_multi() and value == '': - value = None -# if self.is_multi() and '' in value: -# value = Multi([{'': None}.get(i, i) for i in value], -# config._cfgimpl_context, self) - if config._cfgimpl_context._cfgimpl_settings.is_mandatory() \ - and ((self.is_multi() and value == []) or \ - (not self.is_multi() and value is None)): - raise MandatoryError('cannot change the value to %s for ' - 'option %s' % (value, name)) - if self not in config._cfgimpl_descr._children: + if self not in config._cfgimpl_descr._children[1]: raise AttributeError('unknown option %s' % (name)) - if config._cfgimpl_context._cfgimpl_settings.is_frozen_for_everything(): + if setting.has_property('everything_frozen'): raise TypeError("cannot set a value to the option {} if the whole " - "config has been frozen".format(name)) + "config has been frozen".format(name)) - if config._cfgimpl_context._cfgimpl_settings.is_frozen() \ - and self.is_frozen(): + if setting.has_property('frozen') and setting.has_property('frozen', + self): raise TypeError('cannot change the value to %s for ' - 'option %s this option is frozen' % (str(value), name)) + 'option %s this option is frozen' % (str(value), name)) apply_requires(self, config) - config._cfgimpl_context._cfgimpl_values[self] = value + config.cfgimpl_get_values()[self] = value def getkey(self, value): return value - # ____________________________________________________________ - "freeze utility" - def freeze(self): - self._frozen = True - return True - def unfreeze(self): - self._frozen = False - def is_frozen(self): - return self._frozen - # ____________________________________________________________ + def is_multi(self): return self.multi - def is_mandatory(self): - return self._mandatory + class ChoiceOption(Option): + __slots__ = ('values', 'open_values', 'opt_type') opt_type = 'string' def __init__(self, name, doc, values, default=None, default_multi=None, - requires=None, mandatory=False, multi=False, callback=None, + requires=None, multi=False, callback=None, callback_params=None, open_values=False, validator=None, - validator_args={}): + validator_args=None, properties=()): + if not isinstance(values, tuple): + raise ConfigError('values must be a tuple for {0}'.format(name)) self.values = values - if open_values not in [True, False]: + if open_values not in (True, False): raise ConfigError('Open_values must be a boolean for ' '{0}'.format(name)) self.open_values = open_values super(ChoiceOption, self).__init__(name, doc, default=default, - default_multi=default_multi, callback=callback, - callback_params=callback_params, requires=requires, - multi=multi, mandatory=mandatory, validator=validator, - validator_args=validator_args) + default_multi=default_multi, + callback=callback, + callback_params=callback_params, + requires=requires, + multi=multi, + validator=validator, + validator_args=validator_args, + properties=properties) def _validate(self, value): if not self.open_values: @@ -345,72 +289,91 @@ class ChoiceOption(Option): else: return True + class BoolOption(Option): + __slots__ = ('opt_type') opt_type = 'bool' def _validate(self, value): return isinstance(value, bool) + class IntOption(Option): + __slots__ = ('opt_type') opt_type = 'int' def _validate(self, value): return isinstance(value, int) + class FloatOption(Option): + __slots__ = ('opt_type') opt_type = 'float' def _validate(self, value): return isinstance(value, float) + class StrOption(Option): + __slots__ = ('opt_type') opt_type = 'string' def _validate(self, value): return isinstance(value, str) + class UnicodeOption(Option): + __slots__ = ('opt_type') opt_type = 'unicode' def _validate(self, value): return isinstance(value, unicode) + class SymLinkOption(object): + __slots__ = ('_name', 'opt') opt_type = 'symlink' def __init__(self, name, path, opt): self._name = name - self.path = path self.opt = opt def setoption(self, config, value): - setattr(config._cfgimpl_get_toplevel(), self.path, value) + context = config.cfgimpl_get_context() + path = context.cfgimpl_get_description().get_path_by_opt(self.opt) + setattr(context, path, value) def __getattr__(self, name): - if name in ('_name', 'path', 'opt', 'setoption'): - return self.__dict__[name] + if name in ('_name', 'opt', 'setoption'): + return object.__gettattr__(self, name) else: return getattr(self.opt, name) + class IPOption(Option): + __slots__ = ('opt_type') opt_type = 'ip' def _validate(self, value): # by now the validation is nothing but a string, use IPy instead return isinstance(value, str) + class NetmaskOption(Option): + __slots__ = ('opt_type') opt_type = 'netmask' def _validate(self, value): # by now the validation is nothing but a string, use IPy instead return isinstance(value, str) -class OptionDescription(BaseType, BaseInformation): + +class OptionDescription(BaseInformation): """Config's schema (organisation, group) and container of Options""" - # the group_type is useful for filtering OptionDescriptions in a config - group_type = groups.default - def __init__(self, name, doc, children, requires=None): + __slots__ = ('_name', '_requires', '_cache_paths', '_group_type', + '_properties', '_children') + + def __init__(self, name, doc, children, requires=None, properties=()): """ :param children: is a list of option descriptions (including ``OptionDescription`` instances for nested namespaces). @@ -418,49 +381,55 @@ class OptionDescription(BaseType, BaseInformation): if not valid_name(name): raise NameError("invalid name: {0} for option descr".format(name)) self._name = name - self.doc = doc - self._children = children - self._requires = requires - self._build() - self.properties = [] # 'hidden', 'disabled'... self.informations = {} - self._cache_paths = {} + self.set_information('doc', doc) + child_names = [child._name for child in children] + #better performance like this + valid_child = copy(child_names) + valid_child.sort() + old = None + for child in valid_child: + if child == old: + raise ConflictConfigError('duplicate option name: ' + '{0}'.format(child)) + old = child + self._children = (tuple(child_names), tuple(children)) + validate_requires_arg(requires, self._name) + self._requires = requires + self._cache_paths = None + if not isinstance(properties, tuple): + raise ConfigError('invalid properties type {0} for {1},' + ' must be a tuple'.format(type(properties), self._name)) + self._properties = properties # 'hidden', 'disabled'... + # the group_type is useful for filtering OptionDescriptions in a config + self._group_type = groups.default def getdoc(self): - return self.doc + return self.get_information('doc') - def _build(self): - for child in self._children: - setattr(self, child._name, child) - - def add_child(self, child): - "dynamically adds a configuration option" - #Nothing is static. Even the Mona Lisa is falling apart. - for ch in self._children: - if isinstance(ch, Option): - if child._name == ch._name: - raise ConflictConfigError("existing option : {0}".format( - child._name)) - self._children.append(child) - setattr(self, child._name, child) - - def update_child(self, child): - "modification of an existing option" - # XXX : corresponds to the `redefine`, is it usefull - pass + def __getattr__(self, name): + if name in self._children[0]: + return self._children[1][self._children[0].index(name)] + else: + try: + object.__getattr__(self, name) + except AttributeError: + raise AttributeError('unknown Option {} in OptionDescription {}' + ''.format(name, self._name)) def getkey(self, config): return tuple([child.getkey(getattr(config, child._name)) - for child in self._children]) + for child in self._children[1]]) def getpaths(self, include_groups=False, currpath=None): """returns a list of all paths in self, recursively currpath should not be provided (helps with recursion) """ + #FIXME : cache if currpath is None: currpath = [] paths = [] - for option in self._children: + for option in self._children[1]: attr = option._name if attr.startswith('_cfgimpl'): continue @@ -469,29 +438,57 @@ class OptionDescription(BaseType, BaseInformation): paths.append('.'.join(currpath + [attr])) currpath.append(attr) paths += option.getpaths(include_groups=include_groups, - currpath=currpath) + currpath=currpath) currpath.pop() else: paths.append('.'.join(currpath + [attr])) return paths - def build_cache(self, cache=None, currpath=None): - if currpath is None and self._cache_paths != {}: + def build_cache(self, cache_path=None, cache_option=None, currpath=None): + if currpath is None and self._cache_paths is not None: return if currpath is None: + save = True currpath = [] - if cache is None: - cache = self._cache_paths - for option in self._children: + else: + save = False + if cache_path is None: + cache_path = [] + cache_option = [] + for option in self._children[1]: attr = option._name if attr.startswith('_cfgimpl'): continue + cache_option.append(option) + cache_path.append(str('.'.join(currpath + [attr]))) if isinstance(option, OptionDescription): currpath.append(attr) - option.build_cache(cache, currpath) + option.build_cache(cache_path, cache_option, currpath) currpath.pop() - else: - cache[option] = str('.'.join(currpath + [attr])) + if save: + #valid no duplicated option + valid_child = copy(cache_option) + valid_child.sort() + old = None + for child in valid_child: + if child == old: + raise ConflictConfigError('duplicate option: ' + '{0}'.format(child)) + old = child + self._cache_paths = (tuple(cache_option), tuple(cache_path)) + + def get_opt_by_path(self, path): + try: + return self._cache_paths[0][self._cache_paths[1].index(path)] + except ValueError: + raise NotFoundError('no option for path {}'.format(path)) + + def get_path_by_opt(self, opt): + try: + return self._cache_paths[1][self._cache_paths[0].index(opt)] + except ValueError: + raise NotFoundError('no option {} found'.format(opt)) + # ____________________________________________________________ def set_group_type(self, group_type): """sets a given group object to an OptionDescription @@ -499,115 +496,123 @@ class OptionDescription(BaseType, BaseInformation): :param group_type: an instance of `GroupType` or `MasterGroupType` that lives in `setting.groups` """ + if self._group_type != groups.default: + ConfigError('cannot change group_type if already set ' + '(old {}, new {})'.format(self._group_type, group_type)) if isinstance(group_type, groups.GroupType): - self.group_type = group_type + self._group_type = group_type if isinstance(group_type, groups.MasterGroupType): + #if master (same name has group) is set identical_master_child_name = False - for child in self._children: + #for collect all slaves + slaves = [] + master = None + for child in self._children[1]: if isinstance(child, OptionDescription): raise ConfigError("master group {} shall not have " - "a subgroup".format(self._name)) + "a subgroup".format(self._name)) if not child.multi: raise ConfigError("not allowed option {0} in group {1}" - ": this option is not a multi".format(child._name, - self._name)) + ": this option is not a multi" + "".format(child._name, self._name)) if child._name == self._name: identical_master_child_name = True + child.multitype = multitypes.master + master = child + else: + slaves.append(child) + if master is None: + raise ConfigError('master group with wrong master name for {}' + ''.format(self._name)) + master.master_slaves = tuple(slaves) + for child in self._children[1]: + if child != master: + child.master_slaves = master + child.multitype = multitypes.slave if not identical_master_child_name: raise ConfigError("the master group: {} has not any " - "master child".format(self._name)) + "master child".format(self._name)) else: raise ConfigError('not allowed group_type : {0}'.format(group_type)) def get_group_type(self): - return self.group_type - # ____________________________________________________________ - "actions API" - def hide(self): - super(OptionDescription, self).hide() - for child in self._children: - if isinstance(child, OptionDescription): - child.hide() - def show(self): - super(OptionDescription, self).show() - for child in self._children: - if isinstance(child, OptionDescription): - child.show() + return self._group_type - def disable(self): - super(OptionDescription, self).disable() - for child in self._children: - if isinstance(child, OptionDescription): - child.disable() - def enable(self): - super(OptionDescription, self).enable() - for child in self._children: - if isinstance(child, OptionDescription): - child.enable() -# ____________________________________________________________ def validate_requires_arg(requires, name): - "malformed requirements" - config_action = [] - for req in requires: - if not type(req) == tuple and len(req) != 3: - raise RequiresError("malformed requirements for option:" - " {0}".format(name)) - action = req[2] - if action not in available_actions: - raise RequiresError("malformed requirements for option: {0}" - " unknown action: {1}".format(name, action)) - if reverse_actions[action] in config_action: - raise RequiresError("inconsistency in action types for option: {0}" - " action: {1} in contradiction with {2}\n" - " ({3})".format(name, action, - reverse_actions[action], requires)) - config_action.append(action) + "check malformed requirements" + if requires is not None: + config_action = {} + for req in requires: + if not type(req) == tuple: + raise RequiresError("malformed requirements type for option:" + " {0}, must be a tuple".format(name)) + if len(req) == 3: + action = req[2] + inverse = False + elif len(req) == 4: + action = req[2] + inverse = req[3] + else: + raise RequiresError("malformed requirements for option: {0}" + " invalid len".format(name)) + if action in config_action: + if inverse != config_action[action]: + raise RequiresError("inconsistency in action types for option: {0}" + " action: {1}".format(name, action)) + else: + config_action[action] = inverse -def build_actions(requires): - "action are hide, show, enable, disable..." - trigger_actions = {} - for require in requires: - action = require[2] - trigger_actions.setdefault(action, []).append(require) - return trigger_actions -def apply_requires(opt, config, permissive=False): +def apply_requires(opt, config): "carries out the jit (just in time requirements between options" + def build_actions(requires): + "action are hide, show, enable, disable..." + trigger_actions = {} + for require in requires: + action = require[2] + trigger_actions.setdefault(action, []).append(require) + return trigger_actions + #for symlink if hasattr(opt, '_requires') and opt._requires is not None: - rootconfig = config._cfgimpl_get_toplevel() - validate_requires_arg(opt._requires, opt._name) # filters the callbacks + setting = config.cfgimpl_get_settings() trigger_actions = build_actions(opt._requires) + if isinstance(opt, OptionDescription): + optpath = config._cfgimpl_get_path() + '.' + opt._name + else: + optpath = config.cfgimpl_get_context().cfgimpl_get_description().get_path_by_opt(opt) for requires in trigger_actions.values(): matches = False for require in requires: - name, expected, action = require - path = config._cfgimpl_get_path() + '.' + opt._name - if name.startswith(path): + if len(require) == 3: + path, expected, action = require + inverse = False + elif len(require) == 4: + path, expected, action, inverse = require + if path.startswith(optpath): raise RequirementRecursionError("malformed requirements " - "imbrication detected for option: '{0}' " - "with requirement on: '{1}'".format(path, name)) - homeconfig, shortname = rootconfig.cfgimpl_get_home_by_path(name) + "imbrication detected for option: '{0}' " + "with requirement on: '{1}'".format(optpath, path)) try: - value = homeconfig._getattr(shortname, permissive=True) + value = config.cfgimpl_get_context()._getattr(path, force_permissive=True) except PropertiesOptionError, err: properties = err.proptype - if permissive: - for perm in \ - config._cfgimpl_context._cfgimpl_settings.permissive: - if perm in properties: - properties.remove(perm) - if properties != []: - raise NotFoundError("option '{0}' has requirement's property error: " - "{1} {2}".format(opt._name, name, properties)) + raise NotFoundError("option '{0}' has requirement's property error: " + "{1} {2}".format(opt._name, path, properties)) except Exception, err: raise NotFoundError("required option not found: " - "{0}".format(name)) + "{0}".format(path)) if value == expected: - getattr(opt, action)() #.hide() or show() or... - # FIXME generic programming opt.property_launch(action, False) + if inverse: + setting.del_property(action, opt) + else: + setting.add_property(action, opt) matches = True - # no callback has been triggered, then just reverse the action + #FIXME optimisation : fait un double break non ? voire un return + # no requirement has been triggered, then just reverse the action if not matches: - getattr(opt, reverse_actions[action])() + if inverse: + setting.add_property(action, opt) + else: + setting.del_property(action, opt) diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 7477fd9..1c1e8db 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -20,20 +20,25 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ + + class _const: """convenient class that emulates a module and builds constants (that is, unique names)""" - class ConstError(TypeError): pass + class ConstError(TypeError): + pass def __setattr__(self, name, value): - if self.__dict__.has_key(name): - raise self.ConstError, "Can't rebind group (%s)"%name + if name in self.__dict__: + raise self.ConstError, "Can't rebind group ({})".format(name) self.__dict__[name] = value def __delattr__(self, name): - if self.__dict__.has_key(name): - raise self.ConstError, "Can't unbind group (%s)"%name - raise NameError, name + if name in self.__dict__: + raise self.ConstError, "Can't unbind group ({})".format(name) + raise NameError(name) + + # ____________________________________________________________ class GroupModule(_const): "emulates a module to manage unique group (OptionDescription) names" @@ -42,6 +47,7 @@ class GroupModule(_const): *normal* means : groups that are not master """ pass + class DefaultGroupType(GroupType): """groups that are default (typically 'default')""" pass @@ -54,6 +60,7 @@ class GroupModule(_const): # setting.groups (emulates a module) groups = GroupModule() + def populate_groups(): "populates the available groups in the appropriate namespaces" groups.master = groups.MasterGroupType('master') @@ -62,6 +69,8 @@ def populate_groups(): # names are in the module now populate_groups() + + # ____________________________________________________________ class OwnerModule(_const): """emulates a module to manage unique owner names. @@ -72,12 +81,14 @@ class OwnerModule(_const): """allowed owner names """ pass + class DefaultOwner(Owner): """groups that are default (typically 'default')""" pass # setting.owners (emulates a module) owners = OwnerModule() + def populate_owners(): """populates the available owners in the appropriate namespaces @@ -85,7 +96,8 @@ def populate_owners(): - 'default' is the config owner after init time """ setattr(owners, 'default', owners.DefaultOwner('default')) - setattr(owners,'user', owners.Owner('user')) + setattr(owners, 'user', owners.Owner('user')) + def add_owner(name): """ :param name: the name of the new owner @@ -96,18 +108,23 @@ def populate_owners(): # names are in the module now populate_owners() + class MultiTypeModule(_const): class MultiType(str): pass + class DefaultMultiType(MultiType): pass + class MasterMultiType(MultiType): pass + class SlaveMultiType(MultiType): pass multitypes = MultiTypeModule() + def populate_multitypes(): setattr(multitypes, 'default', multitypes.DefaultMultiType('default')) setattr(multitypes, 'master', multitypes.MasterMultiType('master')) @@ -115,118 +132,88 @@ def populate_multitypes(): populate_multitypes() + #____________________________________________________________ -class Setting(): +class Setting(object): "``Config()``'s configuration options" - # properties attribute: the name of a property enables this property - properties = ['hidden', 'disabled'] - # overrides the validations in the acces of the option values - permissive = [] - # a mandatory option must have a value that is not None - mandatory = True - frozen = True - # enables validation function for options if set - validator = False - # generic owner - owner = owners.user - # in order to freeze everything, not **only** the frozen options - everything_frozen = False - # enables at build time to raise an exception if the option's name - # has the name of a config's method - valid_opt_names = True + __slots__ = ('properties', 'permissives', 'owner') + + def __init__(self): + # properties attribute: the name of a property enables this property + # key is None for global properties + self.properties = {None: []} # ['hidden', 'disabled', 'mandatory', 'frozen', 'validator']} + # permissive properties + self.permissives = {} + # generic owner + self.owner = owners.user + #____________________________________________________________ # properties methods - def has_properties(self): + def has_properties(self, opt=None): "has properties means the Config's properties attribute is not empty" - return bool(len(self.properties)) + return bool(len(self.get_properties(opt))) + def get_properties(self, opt=None): + if opt is None: + default = [] + else: + default = list(opt._properties) + return self.properties.get(opt, default) - def get_properties(self): - return self.properties - - def has_property(self, propname): + def has_property(self, propname, opt=None): """has property propname in the Config's properties attribute :param property: string wich is the name of the property""" - return propname in self.properties + return propname in self.get_properties(opt) def enable_property(self, propname): "puts property propname in the Config's properties attribute" - if propname not in self.properties: - self.properties.append(propname) + props = self.get_properties() + if propname not in props: + props.append(propname) + self.set_properties(props) def disable_property(self, propname): "deletes property propname in the Config's properties attribute" - if self.has_property(propname): - self.properties.remove(propname) - #____________________________________________________________ - def get_permissive(self): - return self.permissive + props = self.get_properties() + if propname in props: + props.remove(propname) + self.set_properties(props) - def set_permissive(self, permissive): + def set_properties(self, properties, opt=None): + """save properties for specified opt + (never save properties if same has option properties) + """ + if opt is None: + self.properties[opt] = properties + else: + if opt._properties == properties: + if opt in self.properties: + del(self.properties[opt]) + else: + self.properties[opt] = properties + + def add_property(self, propname, opt): + properties = self.get_properties(opt) + if not propname in properties: + properties.append(propname) + self.set_properties(properties, opt) + + def del_property(self, propname, opt): + properties = self.get_properties(opt) + if propname in properties: + properties.remove(propname) + self.set_properties(properties, opt) + + #____________________________________________________________ + def get_permissive(self, config=None): + return self.permissives.get(config, []) + + def set_permissive(self, permissive, config=None): if not isinstance(permissive, list): raise TypeError('permissive must be a list') - self.permissive = permissive + self.permissives[config] = permissive + #____________________________________________________________ - # complete freeze methods - def freeze_everything(self): - """everything is frozen, not only the option that are tagged "frozen" - """ - self.everything_frozen = True - - def un_freeze_everything(self): - """everything is frozen, not only the option that are tagged "frozen" - """ - self.everything_frozen = False - - def is_frozen_for_everything(self): - """frozen for a whole config (not only the options - that have been set to frozen)""" - return self.everything_frozen - #____________________________________________________________ - def read_only(self): - "convenience method to freeze, hidde and disable" - self.freeze_everything() - self.freeze() # can be usefull... - self.disable_property('hidden') - self.enable_property('disabled') - self.mandatory = True - self.validator = True - - def read_write(self): - "convenience method to freeze, hidde and disable" - self.un_freeze_everything() - self.freeze() - self.enable_property('hidden') - self.enable_property('disabled') - self.mandatory = False - self.validator = False - - def non_mandatory(self): - """mandatory at the Config level means that the Config raises an error - if a mandatory option is found""" - self.mandatory = False - - def mandatory(self): - """mandatory at the Config level means that the Config raises an error - if a mandatory option is found""" - self.mandatory = True - - def is_mandatory(self): - "all mandatory Options shall have a value" - return self.mandatory - - def freeze(self): - "cannot modify the frozen `Option`'s" - self.frozen = True - - def unfreeze(self): - "can modify the Options that are frozen" - self.frozen = False - - def is_frozen(self): - "freeze flag at Config level" - return self.frozen - def setowner(self, owner): ":param owner: sets the default value for owner at the Config level" if not isinstance(owner, owners.Owner): @@ -235,3 +222,24 @@ class Setting(): def getowner(self): return self.owner + + #____________________________________________________________ + def read_only(self): + "convenience method to freeze, hidde and disable" + self.enable_property('everything_frozen') + self.enable_property('frozen') # can be usefull... + self.disable_property('hidden') + self.enable_property('disabled') + self.enable_property('mandatory') + self.enable_property('validator') + self.disable_property('permissive') + + def read_write(self): + "convenience method to freeze, hidde and disable" + self.disable_property('everything_frozen') + self.enable_property('frozen') # can be usefull... + self.enable_property('hidden') + self.enable_property('disabled') + self.disable_property('mandatory') + self.disable_property('validator') + self.enable_property('permissive') diff --git a/tiramisu/value.py b/tiramisu/value.py index d1ddbc8..24b268d 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -16,144 +16,123 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# The original `Config` design model is unproudly borrowed from -# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ -# the whole pypy projet is under MIT licence # ____________________________________________________________ from tiramisu.error import NoValueReturned, MandatoryError, MultiTypeError, \ - ConfigError + ConfigError # , OptionValueError from tiramisu.setting import owners, multitypes + class Values(object): + __slots__ = ('values', 'context') + def __init__(self, context): """ Initializes the values's dict. - :param context: the context is the home config's values and properties + :param context: the context is the home config's values """ - self.owners = {} "Config's root indeed is in charge of the `Option()`'s values" self.values = {} - self.previous_values = {} - self.masters = {} - self.slaves = {} self.context = context - def _get_multitype(self, opt): - if opt in self.slaves: - # slave - multitype = multitypes.slave - elif opt in self.masters: - # master - multitype = multitypes.master - # FIXME : default value for a multi, we shall work on groups - else: - multitype = multitypes.default - return multitype - def _get_value(self, opt): "special case for the multis: they never return None" if opt not in self.values: if opt.is_multi(): - multitype = self._get_multitype(opt) - value = Multi(opt.getdefault(), self.context, opt, multitype) + value = Multi(opt.getdefault(), self.context, opt) + if opt.multitype == multitypes.slave: + masterpath = self.context._cfgimpl_descr.get_path_by_opt(opt.master_slaves) + mastervalue = getattr(self.context, masterpath) + masterlen = len(mastervalue) + if len(value) < masterlen: + for num in range(0, masterlen - len(value)): + value.append(None, force=True) else: value = opt.getdefault() - if opt in self.slaves: - masterpath = self.context._cfgimpl_descr._cache_paths[self.slaves[opt]] - mastervalue = getattr(self.context, masterpath) - masterlen = len(mastervalue) - if len(value) < masterlen: - for num in range(0, masterlen - len(value)): - value.append(None, force=True) return value - return self.values[opt] + return self.values[opt][1] def reset(self, opt): if opt in self.values: - self.set_previous_value(opt) del(self.values[opt]) - self.setowner(opt, owners.default) - - def set_previous_value(self, opt): - if opt in self.values: - old_value = self.values[opt] - elif opt.is_multi(): - old_value = [] - else: - old_value = None - if type(old_value) == Multi: - self.previous_values[opt] = list(old_value) - else: - self.previous_values[opt] = old_value - - def get_previous_value(self, opt): - if opt in self.previous_values: - prec_value = self.previous_values[opt] - elif opt.is_multi(): - prec_value = [] - else: - prec_value = None - return prec_value def _is_empty(self, opt, value=None): "convenience method to know if an option is empty" - if value is not None: - return False - if (not opt.is_multi() and value == None) or \ - (opt.is_multi() and (value == [] or \ - None in self._get_value(opt))): + #FIXME: buggy ? + #if value is not None: + # return False + if (not opt.is_multi() and value is None) or \ + (opt.is_multi() and (value == [] or + None in self._get_value(opt))): return True return False def is_empty(self, opt): + #FIXME that not empty ... just no value! if opt not in self.values: return True - value = self.values[opt] + value = self.values[opt][1] if not opt.is_multi(): - if self._get_value(opt) == None: + if self._get_value(opt) is None: return True return False else: value = list(value) for val in value: - if val != None: + if val is not None: return False return True - def _test_mandatory(self, opt, value=None): - # mandatory options - mandatory = self.context._cfgimpl_settings.mandatory - if opt.is_mandatory() and mandatory: + def _test_mandatory(self, opt, value, force_properties=None): + setting = self.context.cfgimpl_get_settings() + if force_properties is None: + set_mandatory = setting.has_property('mandatory') + else: + set_mandatory = ('mandatory' in force_properties or + setting.has_property('mandatory')) + if setting.has_property('mandatory', opt) and set_mandatory: if self._is_empty(opt, value) and \ opt.is_empty_by_default(): raise MandatoryError("option: {0} is mandatory " - "and shall have a value".format(opt._name)) + "and shall have a value".format(opt._name)) + #empty value + if opt.is_multi(): + for val in value: + if val == '': + raise MandatoryError("option: {0} is mandatory " + "and shall have not empty value".format(opt._name)) + else: + if value == '': + raise MandatoryError("option: {0} is mandatory " + "and shall have not empty value".format(opt._name)) def fill_multi(self, opt, result): """fills a multi option with default and calculated values """ - value = self._get_value(opt) if not isinstance(result, list): _result = [result] else: _result = result - multitype = self._get_multitype(opt) - return Multi(_result, self.context, opt, multitype) + #multitype = self._get_multitype(opt) + return Multi(_result, self.context, opt) # , multitype) def __getitem__(self, opt): + return self._getitem(opt) + + def _getitem(self, opt, force_properties=None): # options with callbacks value = self._get_value(opt) if opt.has_callback(): - if (not opt.is_frozen() or \ - not opt.is_forced_on_freeze()) and \ - not opt.is_default_owner(self.context): + setting = self.context.cfgimpl_get_settings() + if (not setting.has_property('frozen', opt) or + (setting.has_property('frozen', opt) and + not setting.has_property('force_default_on_freeze', opt) + )) and not self.context.cfgimpl_get_values().is_default_owner(opt): return self._get_value(opt) try: - result = opt.getcallback_value( - self.context) - except NoValueReturned, err: + result = opt.getcallback_value(self.context) + except NoValueReturned: pass else: if opt.is_multi(): @@ -162,85 +141,94 @@ class Values(object): # this result **shall not** be a list if isinstance(result, list): raise ConfigError('invalid calculated value returned ' - 'for option {0} : shall not be a list'.format(opt._name)) + 'for option {0} : shall not be a list' + ''.format(opt._name)) value = result - if value != None and not opt.validate(value, - self.context._cfgimpl_settings.validator): + if value is not None and \ + not opt.validate(value, setting.has_property('validator')): raise ConfigError('invalid calculated value returned' - ' for option {0}'.format(opt._name)) + ' for option {0}'.format(opt._name)) # frozen and force default - if not opt.has_callback() and opt.is_forced_on_freeze(): + if not opt.has_callback() and self.context.cfgimpl_get_settings().has_property('force_default_on_freeze', opt): value = opt.getdefault() if opt.is_multi(): value = self.fill_multi(opt, value) - self._test_mandatory(opt, value) + self._test_mandatory(opt, value, force_properties) return value def __setitem__(self, opt, value): - if opt in self.masters: - masterlen = len(value) - for slave in self.masters[opt]: - value_slave = self._get_value(slave) - if len(value_slave) > masterlen: - raise MultiTypeError("invalid len for the slave: {0}" - " which has {1} as master".format(slave._name, - opt._name)) - elif len(value_slave) < masterlen: - for num in range(0, masterlen - len(value_slave)): - value_slave.append(None, force=True) + if opt.is_multi(): + if opt.multitype == multitypes.master: + masterlen = len(value) + for slave in self.opt.master_slaves: + value_slave = self._get_value(slave) + if len(value_slave) > masterlen: + raise MultiTypeError("invalid len for the slave: {0}" + " which has {1} as master".format( + slave._name, opt._name)) + elif len(value_slave) < masterlen: + for num in range(0, masterlen - len(value_slave)): + value_slave.append(None, force=True) - elif opt in self.slaves: - if len(self._get_value(self.slaves[opt])) != len(value): - raise MultiTypeError("invalid len for the slave: {0}" - " which has {1} as master".format(opt._name, - self.slaves[opt]._name)) - if opt.is_multi() and not isinstance(value, Multi): - value = Multi(value, self.context, opt, multitypes.default) + elif opt.multitype == multitypes.slave: + if len(self._get_value(self.opt.master_slaves)) != len(value): + raise MultiTypeError("invalid len for the slave: {0}" + " which has {1} as master".format( + opt._name, self.opt.master_slaves._name)) + if not isinstance(value, Multi): + value = Multi(value, self.context, opt) self.setitem(opt, value) def setitem(self, opt, value): - self.set_previous_value(opt) if type(value) == list: raise MultiTypeError("the type of the value {0} which is multi shall " "be Multi and not list".format(str(value))) - self.values[opt] = value - self.setowner(opt, self.context._cfgimpl_settings.getowner()) + self._test_mandatory(opt, value) + self.values[opt] = (self.context.cfgimpl_get_settings().getowner(), value) def __contains__(self, opt): return opt in self.values - #____________________________________________________________ - def setowner(self, opt, owner): - if isinstance(owner, owners.Owner): - self.owners[opt] = owner - else: - raise OptionValueError("Bad owner: " + str(owner)) def getowner(self, opt): - return self.owners.get(opt, owners.default) + return self.values.get(opt, (owners.default, None))[0] + + def setowner(self, opt, owner): + if opt not in self.values: + raise ConfigError('no value for {} cannot change owner to {}'.format(opt)) + if not isinstance(owner, owners.Owner): + raise TypeError("invalid generic owner {0}".format(str(owner))) + self.values[opt] = (owner, self.values[opt][1]) + + def is_default_owner(self, opt): + """ + :param config: *must* be only the **parent** config + (not the toplevel config) + :return: boolean + """ + return self.getowner(opt) == owners.default # ____________________________________________________________ # multi types + + class Multi(list): """multi options values container that support item notation for the values of multi options""" - def __init__(self, lst, context, opt, multitype): + __slots__ = ('opt', 'context') + + def __init__(self, lst, context, opt): """ :param lst: the Multi wraps a list value - :param context: the home config that has the settings and the values + :param context: the home config that has the values :param opt: the option object that have this Multi value """ - self.settings = context._cfgimpl_settings self.opt = opt - self.values = context._cfgimpl_values - self.multitype = multitype + self.context = context super(Multi, self).__init__(lst) - if multitype == multitypes.master: - self.slaves = context._cfgimpl_values.masters[opt] - else: - self.slaves = None + def __setitem__(self, key, value): self._validate(value) - self.values[self.opt] = self + self.context.cfgimpl_get_values()[self.opt] = self super(Multi, self).__setitem__(key, value) def append(self, value, force=False): @@ -248,20 +236,21 @@ class Multi(list): only if the option is a master """ if not force: - if self.multitype == multitypes.slave: + if self.opt.multitype == multitypes.slave: raise MultiTypeError("cannot append a value on a multi option {0}" - " which is a slave".format(self.opt._name)) - elif self.multitype == multitypes.master: - for slave in self.slaves: - self.values[slave].append(None, force=True) + " which is a slave".format(self.opt._name)) + elif self.opt.multitype == multitypes.master: + for slave in self.opt.master_slaves: + self.context.cfgimpl_get_values()[slave].append(None, force=True) self._validate(value) - self.values.setitem(self.opt, self) + self.context.cfgimpl_get_values().setitem(self.opt, self) super(Multi, self).append(value) def _validate(self, value): - if value != None and not self.opt._validate(value): + if value is not None and not self.opt._validate(value): raise ConfigError("invalid value {0} " - "for option {1}".format(str(value), self.opt._name)) + "for option {1}".format(str(value), + self.opt._name)) def pop(self, key, force=False): """the list value can be updated (poped) @@ -271,11 +260,11 @@ class Multi(list): :return: the requested element """ if not force: - if self.multitype == multitypes.slave: + if self.opt.multitype == multitypes.slave: raise MultiTypeError("cannot append a value on a multi option {0}" - " which is a slave".format(self.opt._name)) - elif self.multitype == multitypes.master: - for slave in self.slaves: - self.values[slave].pop(key, force=True) - self.values.setitem(self.opt, self) + " which is a slave".format(self.opt._name)) + elif self.opt.multitype == multitypes.master: + for slave in self.opt.master_slaves: + self.context.cfgimpl_get_values()[slave].pop(key, force=True) + self.context.cfgimpl_get_values().setitem(self.opt, self) return super(Multi, self).pop(key)