def test_write_stacked_source():
    source1 = DictSource({'a': 1, 'b': {'c': 2}})
    source2 = DictSource({'x': 6, 'b': {'y': 7, 'd': {'e': 8}}})
    config = StackedConfig(source1, source2)

    assert config.a == 1
    assert config.b.c == 2
    assert config.b.y == 7

    config.a = 10
    config['x'] = 60
    config['b'].c = 20
    config.b['y'] = 70
    config.b['m'] = 'n'  # add new key
    config.b.d.e = 80

    assert config.a == 10
    assert config.x == 60
    assert config.b.c == 20
    assert config.b.y == 70
    assert config.b.m == 'n'
    assert config.b.d.e == 80

    assert source1.a == 10
    assert source1.b.c == 20

    assert source2.x == 60
    assert source2.b.y == 70
    assert source2.b.m == 'n'
    assert source2.b.d.e == 80
def test_set_keychain():
    config = StackedConfig(DictSource({'a': {
        'b': {
            'c': 2
        }
    }}),
                           keychain=('a', 'b'))

    assert config.dump() == {'c': 2}
def test_read_stacked_sources_with_strategies_for_none_values():
    config = StackedConfig(DictSource({'a': None}),
                           DictSource({'a': None}),
                           strategy_map={
                               'a': strategies.collect,
                           })

    result = [None, None]

    assert config.a == result
    assert list(config.items()) == [('a', result)]
def test_write_to_empty_sources():
    source1 = DictSource(auto_subsection=True)
    source2 = DictSource()
    config = StackedConfig(source2, source1)

    config.a = 10
    config['b'].c = 20

    assert source1.a == 10
    assert source1.b.c == 20
    assert source2.dump() == {}
def test_stacked_setdefault():
    source1 = DictSource({'a': 1, 'b': {'c': 2}})
    source2 = DictSource({'x': 6, 'b': {'y': 7}})
    config = StackedConfig(source1, source2)

    assert config.setdefault('a', 10) == 1
    assert config.setdefault('nonexisting', 10) == 10
    assert config.nonexisting == 10
    assert 'nonexisting' in source2

    assert config.b.setdefault('nonexisting', 20) == 20
    assert config.b.nonexisting == 20
    assert 'nonexisting' in source2.b
def test_stacked_dump():
    config = StackedConfig(DictSource({
        'a': 1,
        'b': {
            'c': 2
        }
    }), DictSource({'a': '10'}), DictSource({
        'x': 6,
        'b': {
            'y': 7
        }
    }))

    assert config.dump() == {'a': '10', 'b': {'c': 2, 'y': 7}, 'x': 6}
def test_expose_sources_for_manipulation():
    source1 = DictSource({'a': 1, 'b': {'c': 2}})
    source2 = DictSource({'a': 10, 'b': {'c': 20}})
    source3 = DictSource({'x': 6, 'b': {'y': 7}})
    config = StackedConfig()

    assert config.dump() == {}

    config.source_list.append(source1)
    assert config == source1

    config.source_list.append(source2)
    assert config == source2

    config.source_list.insert(0, source3)
    assert config.dump() == {'a': 10, 'b': {'c': 20, 'y': 7}, 'x': 6}
def test_read_stacked_sources_with_strategies():
    config = StackedConfig(
        DictSource({
            'a': 1,
            'x': [5, 6],
            'b': {
                'c': 2,
                'd': [3, 4]
            }
        }),
        DictSource({
            'a': 10,
            'x': [50, 60],
            'b': {
                'c': 20,
                'd': [30, 40]
            }
        }),
        strategy_map={
            'a': strategies.add,
            'x': strategies.collect,  # keep lists intact
            'c': strategies.collect,  # collect values into list
            'd': strategies.merge,  # merge lists
        })

    assert config.a == 11
    assert config.x == [[50, 60], [5, 6]]
    assert config.b.c == [20, 2]
    assert config.b.d == [30, 40, 3, 4]
def test_reverse_source_order(reverse, values):
    sources = [
        DictSource({
            'a': 1,
            'b': {}
        }),
        DictSource({
            'a': 10,
            'b': {
                'c': 20
            }
        }),
        DictSource({
            'a': 100,
            'b': {
                'c': 200
            }
        }),
        DictSource({
            'a': 1000,
            'b': {}
        }),
    ]

    config = StackedConfig(*sources, reverse=reverse)

    assert config.a == values[0]
    assert config.b.c == values[1]
def test_read_stacked_sources():
    config = StackedConfig(DictSource({
        'a': 1,
        'b': {
            'c': 2
        }
    }), DictSource({
        'x': 6,
        'b': {
            'y': 7,
            'd': {
                'e': 8
            }
        }
    }))

    assert config.a == 1
    assert config.x == 6
    assert config.b.c == 2
    assert config.b.y == 7

    assert config['a'] == 1
    assert config['x'] == 6
    assert config['b'].c == 2
    assert config.b['y'] == 7
    assert config.b.d.e == 8
def test_write_stacked_source_fails(key, message):
    source1 = DictSource({'a': 1, 'b': {'c': 2}}, readonly=True)
    config = StackedConfig(source1)

    with pytest.raises(TypeError) as exc_info:
        config[key] = 10

    assert message in str(exc_info.value)
def test_source_items(monkeypatch):
    monkeypatch.setenv('MVP_A', '10')
    config = StackedConfig(DictSource({
        'a': 1,
        'b': {
            'c': 2
        }
    }), Environment('MVP'), DictSource({
        'x': 6,
        'b': {
            'y': 7
        }
    }))

    items = list(config.items())
    assert items == [('a', 10), ('b', config.b), ('x', 6)]

    items = list(config.b.items())
    assert items == [('c', 2), ('y', 7)]
def test_read_stacked_sources_with_joining_strategy():
    source1 = DictSource({'path': '/path/to/default/file'})
    source2 = DictSource({'path': '/path/to/user/file'})

    config = StackedConfig(
        source1,
        source2,
        strategy_map={'path': strategies.make_join(separator=':')})

    assert config.path == '/path/to/user/file:/path/to/default/file'
def test_read_complex_stacked_sources(monkeypatch):
    monkeypatch.setenv('MVP1_A', '1000')
    monkeypatch.setenv('MVP2_B_M_E', '4000')

    config = StackedConfig(
        Environment('MVP1_'),  # untyped shadowing
        DictSource({
            'a': 1,
            'b': {
                'c': 2,
                'e': 400
            }
        }),
        DictSource({
            'x': 6,
            'b': {
                'y': 7,
                'd': {
                    'e': 8
                }
            }
        }),
        DictSource({
            'a': 100,
            'b': {
                'm': {
                    'e': 800
                }
            }
        }),  # shadowing
        DictSource({
            'x': 'x',
            'b': {
                'y': 0.7,
                'd': 800
            }
        }),  # type changing
        Environment('MVP2_'),  # untyped shadowing
    )

    assert config.a == 100
    assert config.x == 'x'  # changed int to str
    assert config.b.c == 2
    assert config.b.y == 0.7  # changed int to float
    assert config.b.d == 800  # changed subsection (dict) to single value
    assert config.b.e == 400  # 'e' should not be shadowed by other 'e'
    assert config.b.m.e == 4000  # shadowed by untyped but casted to type

    with pytest.raises(AttributeError) as exc_info:
        config.b.x

    # config.b.d.e overrides a dict with a value
    with pytest.raises(AttributeError) as exc_info:
        config.b.d.e
    assert "no attribute 'e'" in str(exc_info.value)
def test_source_items_prevent_shadowing_between_subsections_and_values(
        reverse):
    sources = [
        DictSource({
            'a': 1,
            'b': {
                'c': 2
            }
        }),
        DictSource({
            'x': 6,
            'b': 5
        }),
    ]
    config = StackedConfig(*sources, reverse=reverse)

    with pytest.raises(ValueError) as exc_info:
        list(config.items())

    assert "conflicts" in str(exc_info.value)
def test_source_items_with_strategies_and_untyped_source(
        monkeypatch, inimaker):
    monkeypatch.setenv('MVP_A', '100')
    untyped_source = inimaker(u"""
        [__root__]
        a=1000
    """)

    config = StackedConfig(
        Environment('MVP'),  # last source still needs a typed source
        DictSource({
            'a': 1,
            'x': [5, 6],
            'b': {
                'c': 2,
                'd': [3, 4]
            }
        }),
        DictSource({
            'a': 10,
            'x': [50, 60],
            'b': {
                'c': 20,
                'd': [30, 40]
            }
        }),
        INIFile(untyped_source),
        strategy_map={
            'a': strategies.add,
            'x': strategies.collect,  # keep lists intact
            'c': strategies.collect,  # collect values into list
            'd': strategies.merge,  # merge lists
        })

    items = list(config.items())
    assert items == [('a', 1111), ('b', config.b), ('x', [[50, 60], [5, 6]])]

    items = list(config.b.items())
    assert items == [('c', [20, 2]), ('d', [30, 40, 3, 4])]
def test_stacked_config_with_untyped_source_and_converters(inimaker):
    typed = DictSource({'a': 1})
    untyped = INIFile(inimaker(u"""
        [__root__]
        a=11
    """))
    converter_list = [
        ('a', lambda v: v * 2, lambda v: v / 2),
    ]

    config = StackedConfig(typed, untyped, converters=converter_list)

    assert config.a == 22
def test_stacked_simple_update(container):
    source1 = DictSource({'a': 1, 'b': {'c': 2}})
    source2 = DictSource({'x': 6, 'b': {'y': 7}})
    config = StackedConfig(source1, source2)

    data1 = container({'a': 10, 'x': 60})
    data2 = container({'c': 20})
    data3 = container({'y': 70})

    config.update(data1)
    config.b.update(data2)
    config.b.update(data3)

    assert config.a == 10
    assert config.x == 60
    assert config.b.c == 20
    assert config.b.y == 70

    assert source1.a == 10
    assert source1.b.c == 20

    assert source2.x == 60
    assert source2.b.y == 70
def test_stacked_config_with_type_conversions(inimaker):
    typed_source1 = {
        'a': 1,
        'b': 3.0,
        'c': 4 + 5j,
        'd': 'some string',
        'e': u'some unicode',
        'f': True,
        'g': False,
        'h': False,
        'i': True,
        'm': [1, 2],
        'n': (1, 2),
        'o': set([1, 2]),
    }
    untyped_source1 = inimaker(u"""
        [__root__]
        a=10
        b=20.01
        c=5+6j
        d=some other string
        e=some other unicode
        f=false
        g=True
        h=yes
        i=nope
        m=3, 4
        n=3, 4
        o=3, 4
    """)
    typed1 = DictSource(typed_source1)
    untyped1 = INIFile(untyped_source1)
    config = StackedConfig(typed1, untyped1)

    assert config.a == 10
    assert config.b == 20.01
    assert config.c == 5 + 6j
    assert config.d == 'some other string'
    assert config.e == u'some other unicode'
    assert config.f is False
    assert config.g is True
    assert config.h is True
    assert config.i == "nope"
    # the individual values cannot be converted
    # as we do not know their intended type
    assert config.m == ['3', '4']
    assert config.n == ('3', '4')
    assert config.o == set(['3', '4'])
def test_stacked_len():
    config = StackedConfig(DictSource({
        'a': 1,
        'b': {
            'c': 2
        }
    }), DictSource({
        'x': 6,
        'b': {
            'y': 7,
            'd': {
                'e': 8
            }
        }
    }))

    assert len(config) == 3
def test_source_keys():
    config = StackedConfig(DictSource({
        'a': 1,
        'b': {
            'c': 2
        }
    }), DictSource({
        'x': 6,
        'b': {
            'y': 7,
            'd': {
                'e': 8
            }
        }
    }))

    keys = list(config.b.keys())
    assert keys == ['c', 'd', 'y']
def test_source_values():
    config = StackedConfig(DictSource({
        'a': 1,
        'b': {
            'c': 2
        }
    }), DictSource({
        'x': 6,
        'b': {
            'y': 7,
            'd': {
                'e': 8
            }
        }
    }))

    values = list(config.b.values())
    assert values == [2, config.b.d, 7]
def test_stacked_config_with_untyped_source(inimaker):
    typed_source1 = {'x': 5, 'b': {'y': 6}}
    typed_source2 = {'a': 1, 'b': {'c': 2}}
    untyped_source1 = inimaker(u"""
        [__root__]
        a=11
    """)
    untyped_source2 = inimaker(u"""
        [__root__]
        a=10
        x=50

        [b]
        c=20
        y=60

        [b.d]
        e=30
    """)
    typed1 = DictSource(typed_source1)
    typed2 = DictSource(typed_source2)
    untyped1 = INIFile(untyped_source1)
    untyped2 = INIFile(untyped_source2, subsection_token='.')
    config = StackedConfig(typed1, typed2, untyped1, untyped2)

    assert typed1.x == 5
    assert typed1.b.y == 6

    assert typed2.a == 1
    assert typed2.b.c == 2
    with pytest.raises(AttributeError):
        typed2.b.d.e

    assert untyped1.a == '11'

    assert untyped2.a == '10'
    assert untyped2.b.c == '20'
    assert untyped2.b.d.e == '30'

    assert config.a == 10  # found in first typed source
    assert config.x == 50  # found in second typed source
    assert config.b.c == 20
    assert config.b.y == 60
    assert config.b.d.e == '30'
def mytype_config():
    class MyType:
        def __init__(self, c):
            self.c = c

    def load_mytype(config):
        return MyType(config.c)

    def unload_mytype(mytype):
        return {'c': mytype.c}

    data = {'a': {'b': {'c': 1}}}
    converter_list = [('b', load_mytype, unload_mytype)]

    config = StackedConfig(
        DictSource(data),
        DictSource(data),
        converters=converter_list,
    )

    return MyType, data, config
def test_read_stacked_sources_with_strategies_and_untyped_sources(
        monkeypatch, inimaker):
    monkeypatch.setenv('MVP_A', '100')
    untyped_source = inimaker(u"""
        [__root__]
        a=1000
    """)

    config = StackedConfig(
        Environment('MVP'),  # last source still needs a typed source
        DictSource({
            'a': 1,
            'x': [5, 6],
            'b': {
                'c': 2,
                'd': [3, 4]
            }
        }),
        DictSource({
            'a': 10,
            'x': [50, 60],
            'b': {
                'c': 20,
                'd': [30, 40]
            }
        }),
        INIFile(untyped_source),
        strategy_map={
            'a': strategies.add,
            'x': strategies.collect,  # keep lists intact
            'c': strategies.collect,  # collect values into list
            'd': strategies.merge,  # merge lists
        })

    assert config.a == 1111
    assert config.x == [[50, 60], [5, 6]]
    assert config.b.c == [20, 2]
    assert config.b.d == [30, 40, 3, 4]
def test_stacked_get():
    config = StackedConfig(DictSource({
        'a': 1,
        'b': {
            'c': 2
        }
    }), DictSource({
        'x': 6,
        'b': {
            'y': 7,
            'd': {
                'e': 8
            }
        }
    }))

    assert config.get('a') == 1
    assert config.get('x') == 6
    assert config.get('b').get('c') == 2
    assert config.get('b').get('y') == 7
    assert config.get('nonexisting') is None
    assert config.get('nonexisting', 'default') == 'default'
    assert 'nonexisting' not in config
def test_use_dictsource_on_empty_stacked_config():
    config = StackedConfig()

    assert config.dump() == {}
def test_is_writable_in_low_priority_source():
    config = StackedConfig(DictSource(), DictSource(readonly=True))

    assert config.is_writable() is True
def test_is_typed(source, typed):
    config = StackedConfig(source)

    assert config.is_typed() is typed
def test_properly_return_none_values():
    config = StackedConfig(DictSource({'a': None}))

    assert config.a is None